From 0604c90c351f61b1fae76413adea1f303f77a3e2 Mon Sep 17 00:00:00 2001 From: Nikhar Asthana <83886454+NikharAsthana@users.noreply.github.com> Date: Wed, 21 Oct 2026 16:54:08 +0530 Subject: [PATCH 1/5] feat(hipdnn): add simple TensorAttributes accessors to Python bindings Bind the following TensorAttributes methods: - get_alignment / set_alignment - get_ragged_offset / set_ragged_offset (accepts None) / has_ragged_offset - get_ragged_offset_multiplier / set_ragged_offset_multiplier / has_ragged_offset_multiplier - clear_value / set_as_runtime_parameter / get_has_compile_time_constant Add tests for getter/setter round trips, method chaining, None handling and the validate() rules for alignment and the ragged offset multiplier. Part of #10586. --- .../frontend_bindings/src/tensor_bindings.cpp | 18 +++ .../tests/test_tensor.py | 119 ++++++++++++++++++ 2 files changed, 137 insertions(+) diff --git a/projects/hipdnn/python/frontend_bindings/src/tensor_bindings.cpp b/projects/hipdnn/python/frontend_bindings/src/tensor_bindings.cpp index 4467065d18b7..827475fd2505 100644 --- a/projects/hipdnn/python/frontend_bindings/src/tensor_bindings.cpp +++ b/projects/hipdnn/python/frontend_bindings/src/tensor_bindings.cpp @@ -157,5 +157,23 @@ void tensorBindings(nb::module_& m) .def("get_volume", &TensorAttributes::get_volume) .def("has_uid", &TensorAttributes::has_uid) .def("clear_uid", &TensorAttributes::clear_uid, nb::rv_policy::reference_internal) + .def("get_alignment", &TensorAttributes::get_alignment) + .def("set_alignment", &TensorAttributes::set_alignment, nb::rv_policy::reference_internal) + .def("get_ragged_offset_multiplier", &TensorAttributes::get_ragged_offset_multiplier) + .def("set_ragged_offset_multiplier", + &TensorAttributes::set_ragged_offset_multiplier, + nb::rv_policy::reference_internal) + .def("has_ragged_offset_multiplier", &TensorAttributes::has_ragged_offset_multiplier) + .def("clear_value", &TensorAttributes::clear_value, nb::rv_policy::reference_internal) + .def("set_as_runtime_parameter", + &TensorAttributes::set_as_runtime_parameter, + nb::rv_policy::reference_internal) + .def("get_has_compile_time_constant", &TensorAttributes::get_has_compile_time_constant) + .def("has_ragged_offset", &TensorAttributes::has_ragged_offset) + .def("get_ragged_offset", &TensorAttributes::get_ragged_offset) + .def("set_ragged_offset", + &TensorAttributes::set_ragged_offset, + nb::arg("value").none(), + nb::rv_policy::reference_internal) .def("validate", &TensorAttributes::validate); } diff --git a/projects/hipdnn/python/frontend_wheel_package/tests/test_tensor.py b/projects/hipdnn/python/frontend_wheel_package/tests/test_tensor.py index 7e5341196c69..3aae2375c6da 100644 --- a/projects/hipdnn/python/frontend_wheel_package/tests/test_tensor.py +++ b/projects/hipdnn/python/frontend_wheel_package/tests/test_tensor.py @@ -90,6 +90,125 @@ def test_is_virtual_round_trip(self): assert tensor.get_is_virtual() + def test_alignment_round_trip(self): + """set_alignment()/get_alignment() round-trip.""" + tensor = hipdnn.Tensor.create([4], hipdnn.DataType.FLOAT) + + assert tensor.get_alignment() == 16 + tensor.set_alignment(32) + assert tensor.get_alignment() == 32 + + def test_set_alignment_returns_self(self): + """set_alignment() returns self for chaining.""" + tensor = hipdnn.Tensor.create([4], hipdnn.DataType.FLOAT) + + assert tensor.set_alignment(32) is tensor + + def test_validate_rejects_alignment_below_one(self): + """validate() fails when alignment is less than 1.""" + tensor = hipdnn.Tensor.create([4], hipdnn.DataType.FLOAT) + tensor.set_alignment(0) + + result = tensor.validate() + + assert not result.is_good() + assert " alignment must be >= 1" in result.get_message() + + def test_ragged_offset_multiplier_round_trip(self): + """Ragged offset multiplier setter/getter round-trip.""" + tensor = hipdnn.Tensor.create([4], hipdnn.DataType.FLOAT) + + assert tensor.get_ragged_offset_multiplier() == 1 + assert not tensor.has_ragged_offset_multiplier() + + tensor.set_ragged_offset_multiplier(4) + + assert tensor.get_ragged_offset_multiplier() == 4 + assert tensor.has_ragged_offset_multiplier() + + def test_validate_rejects_multiplier_below_one(self): + """validate() fails when the ragged offset multiplier is less than 1.""" + tensor = hipdnn.Tensor.create([4], hipdnn.DataType.FLOAT) + tensor.set_ragged_offset_multiplier(0) + + result = tensor.validate() + + assert not result.is_good() + assert " ragged offset multiplier must be >= 1" in result.get_message() + + def test_validate_rejects_multiplier_without_ragged_offset(self): + """validate() fails when a multiplier is set without a ragged offset.""" + tensor = hipdnn.Tensor.create([4], hipdnn.DataType.FLOAT) + tensor.set_ragged_offset_multiplier(4) + + result = tensor.validate() + + assert not result.is_good() + assert ( + " ragged offset multiplier requires a ragged offset" in result.get_message() + ) + + def test_has_compile_time_constant_follows_set_value(self): + """set_value() makes a compile-time constant; clear_value() removes it.""" + tensor = hipdnn.Tensor.create([1], hipdnn.DataType.FLOAT) + + assert not tensor.get_has_compile_time_constant() + + tensor.set_value(2.5) + assert tensor.get_has_compile_time_constant() + + assert tensor.clear_value() is tensor + assert not tensor.get_has_compile_time_constant() + + def test_set_as_runtime_parameter(self): + """set_as_runtime_parameter() marks the tensor runtime pass-by-value.""" + tensor = hipdnn.Tensor.create([1], hipdnn.DataType.FLOAT) + tensor.set_value(2.5) + + assert tensor.set_as_runtime_parameter() is tensor + assert tensor.get_is_runtime_pass_by_value() + assert not tensor.get_has_compile_time_constant() + + def test_ragged_offset_round_trip(self): + """set_ragged_offset()/get_ragged_offset() round-trip.""" + tensor = hipdnn.Tensor.create([4], hipdnn.DataType.FLOAT) + offset = hipdnn.Tensor.create([4], hipdnn.DataType.INT32) + + assert not tensor.has_ragged_offset() + assert tensor.get_ragged_offset() is None + + tensor.set_ragged_offset(offset) + + assert tensor.has_ragged_offset() + assert tensor.get_ragged_offset() is offset + + def test_set_ragged_offset_accepts_none(self): + """set_ragged_offset(None) clears the ragged offset.""" + tensor = hipdnn.Tensor.create([4], hipdnn.DataType.FLOAT) + tensor.set_ragged_offset(hipdnn.Tensor.create([4], hipdnn.DataType.INT32)) + + tensor.set_ragged_offset(None) + + assert not tensor.has_ragged_offset() + assert tensor.get_ragged_offset() is None + + def test_multiplier_with_ragged_offset_validates(self): + """A multiplier is valid once a ragged offset is set.""" + tensor = hipdnn.Tensor.create([4], hipdnn.DataType.FLOAT) + tensor.set_ragged_offset(hipdnn.Tensor.create([4], hipdnn.DataType.INT32)) + tensor.set_ragged_offset_multiplier(4) + + result = tensor.validate() + + assert result.is_good(), result.get_message() + + def test_set_ragged_offset_returns_self(self): + """set_ragged_offset() returns self for chaining.""" + tensor = hipdnn.Tensor.create([4], hipdnn.DataType.FLOAT) + offset = hipdnn.Tensor.create([4], hipdnn.DataType.INT32) + + assert tensor.set_ragged_offset(offset) is tensor + def test_create_does_not_auto_assign_uid(self): """create() leaves the uid unset until set_uid() is called.""" tensor = hipdnn.Tensor.create([1, 2], hipdnn.DataType.FLOAT) From e046427894cefb2e1db7dc1cf4dd797873749d5d Mon Sep 17 00:00:00 2001 From: Nikhar Asthana <83886454+NikharAsthana@users.noreply.github.com> Date: Thu, 22 Oct 2026 19:22:05 +0530 Subject: [PATCH 2/5] feat(hipdnn): add Python getters for tensor scalar values Add get_value_variant, get_pass_by_value and get_compile_time_constant to the Tensor bindings. All three share one helper that converts the stored scalar to a plain Python value: half, bfloat16, float and double become float, integer types become int, bool stays bool, and no stored value becomes None. Use get_data_type() for the exact type. Add tests for every supported scalar type and for the value, runtime pass-by-value and cleared states. Part of #10586. --- .../frontend_bindings/src/tensor_bindings.cpp | 42 +++++++++++++++++ .../tests/test_tensor.py | 45 +++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/projects/hipdnn/python/frontend_bindings/src/tensor_bindings.cpp b/projects/hipdnn/python/frontend_bindings/src/tensor_bindings.cpp index 827475fd2505..ad08cbb5470f 100644 --- a/projects/hipdnn/python/frontend_bindings/src/tensor_bindings.cpp +++ b/projects/hipdnn/python/frontend_bindings/src/tensor_bindings.cpp @@ -15,6 +15,8 @@ #include #include #include +#include +#include namespace nb = nanobind; using namespace hipdnn_frontend; @@ -110,6 +112,32 @@ TensorAttributes& } } +// Converts a stored scalar to a plain Python value; an empty value becomes None. +nb::object valueToPython(const TensorAttributes::ValueVariant& value) +{ + return std::visit( + [](const auto& v) -> nb::object { + using T = std::decay_t; + if constexpr(std::is_same_v) + { + return nb::none(); + } + else if constexpr(std::is_same_v) + { + return nb::bool_(v); + } + else if constexpr(std::is_integral_v) + { + return nb::int_(static_cast(v)); + } + else + { + return nb::float_(static_cast(v)); + } + }, + value); +} + } // namespace void tensorBindings(nb::module_& m) @@ -175,5 +203,19 @@ void tensorBindings(nb::module_& m) &TensorAttributes::set_ragged_offset, nb::arg("value").none(), nb::rv_policy::reference_internal) + .def("get_value_variant", + [](const TensorAttributes& tensor) { + return valueToPython(tensor.get_value_variant()); + }) + .def("get_pass_by_value", + [](const TensorAttributes& tensor) { + return valueToPython( + tensor.get_pass_by_value().value_or(TensorAttributes::ValueVariant{})); + }) + .def("get_compile_time_constant", + [](const TensorAttributes& tensor) { + return valueToPython( + tensor.get_compile_time_constant().value_or(TensorAttributes::ValueVariant{})); + }) .def("validate", &TensorAttributes::validate); } diff --git a/projects/hipdnn/python/frontend_wheel_package/tests/test_tensor.py b/projects/hipdnn/python/frontend_wheel_package/tests/test_tensor.py index 3aae2375c6da..4e0022516777 100644 --- a/projects/hipdnn/python/frontend_wheel_package/tests/test_tensor.py +++ b/projects/hipdnn/python/frontend_wheel_package/tests/test_tensor.py @@ -311,3 +311,48 @@ def test_rejects_unconvertible_value(self): hipdnn.Tensor().set_value(1.5, hipdnn.DataType.INT32) with pytest.raises(TypeError, match="cannot infer"): hipdnn.Tensor().set_value("2.5") + + +class TestScalarValueGetters: + """Getters return the stored scalar as a plain Python value.""" + + @pytest.mark.parametrize( + "data_type, value, python_type", + [ + (hipdnn.DataType.FLOAT, 2.5, float), + (hipdnn.DataType.DOUBLE, 0.1, float), + (hipdnn.DataType.HALF, 2.5, float), + (hipdnn.DataType.BFLOAT16, 2.5, float), + (hipdnn.DataType.UINT8, 200, int), + (hipdnn.DataType.INT32, -7, int), + (hipdnn.DataType.INT64, 1 << 40, int), + (hipdnn.DataType.BOOLEAN, True, bool), + ], + ) + def test_value_variant_returns_plain_python_scalar( + self, data_type, value, python_type + ): + tensor = hipdnn.Tensor().set_value(value, data_type) + + result = tensor.get_value_variant() + + assert type(result) is python_type + assert result == value + + def test_getters_follow_the_scalar_state(self): + tensor = hipdnn.Tensor.create([1], hipdnn.DataType.FLOAT) + + assert tensor.get_value_variant() is None + assert tensor.get_compile_time_constant() is None + assert tensor.get_pass_by_value() is None + + tensor.set_value(2.5) + assert tensor.get_compile_time_constant() == 2.5 + assert tensor.get_pass_by_value() is None + + tensor.set_is_pass_by_value(True) + assert tensor.get_pass_by_value() == 2.5 + assert tensor.get_compile_time_constant() is None + + tensor.clear_value() + assert tensor.get_value_variant() is None From 3a1fb9bcdaa914367dce15810450ddfdf8cae17a Mon Sep 17 00:00:00 2001 From: Nikhar Asthana <83886454+NikharAsthana@users.noreply.github.com> Date: Fri, 23 Oct 2026 03:01:54 +0530 Subject: [PATCH 3/5] feat(hipdnn): add set_compile_time_constant to Python bindings Bind set_compile_time_constant as an alias of set_value, with the same arguments (value, optional data_type). In C++ it is a thin wrapper around set_value, so both names share one implementation. Add tests for baking a constant, inferring the data type and rejecting a multi-element tensor. Part of #10586. --- .../frontend_bindings/src/tensor_bindings.cpp | 6 +++++ .../tests/test_tensor.py | 22 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/projects/hipdnn/python/frontend_bindings/src/tensor_bindings.cpp b/projects/hipdnn/python/frontend_bindings/src/tensor_bindings.cpp index ad08cbb5470f..b0191d6c7a08 100644 --- a/projects/hipdnn/python/frontend_bindings/src/tensor_bindings.cpp +++ b/projects/hipdnn/python/frontend_bindings/src/tensor_bindings.cpp @@ -177,6 +177,12 @@ void tensorBindings(nb::module_& m) "data_type, else the tensor's current type, else inferred from value " "(bool -> BOOLEAN, int -> INT64, float -> FLOAT). Resets dims and strides to " "[1]; the tensor must have one element.") + .def("set_compile_time_constant", + &setValue, + nb::arg("value"), + nb::arg("data_type") = nb::none(), + nb::rv_policy::reference_internal, + "Alias of set_value(): store a compile-time constant scalar.") .def("get_is_pass_by_value", &TensorAttributes::get_is_pass_by_value) .def("get_is_runtime_pass_by_value", &TensorAttributes::get_is_runtime_pass_by_value) .def("set_is_pass_by_value", diff --git a/projects/hipdnn/python/frontend_wheel_package/tests/test_tensor.py b/projects/hipdnn/python/frontend_wheel_package/tests/test_tensor.py index 4e0022516777..f35b1dc00f1f 100644 --- a/projects/hipdnn/python/frontend_wheel_package/tests/test_tensor.py +++ b/projects/hipdnn/python/frontend_wheel_package/tests/test_tensor.py @@ -356,3 +356,25 @@ def test_getters_follow_the_scalar_state(self): tensor.clear_value() assert tensor.get_value_variant() is None + + +class TestSetCompileTimeConstant: + """set_compile_time_constant() is an alias of set_value().""" + + def test_bakes_a_constant_like_set_value(self): + tensor = hipdnn.Tensor() + + assert tensor.set_compile_time_constant(2.5, hipdnn.DataType.HALF) is tensor + assert tensor.get_data_type() == hipdnn.DataType.HALF + assert tensor.get_compile_time_constant() == 2.5 + + def test_type_inferred_from_python_value(self): + tensor = hipdnn.Tensor().set_compile_time_constant(3) + + assert tensor.get_data_type() == hipdnn.DataType.INT64 + + def test_rejects_multi_element_tensor(self): + tensor = hipdnn.Tensor.create([2], hipdnn.DataType.FLOAT) + + with pytest.raises(ValueError, match="one element"): + tensor.set_compile_time_constant(1.0) From 8ddf63db7bb2f7ccf375d97d0038f4f0edc78fe7 Mon Sep 17 00:00:00 2001 From: Nikhar Asthana <83886454+NikharAsthana@users.noreply.github.com> Date: Wed, 28 Oct 2026 18:53:11 +0530 Subject: [PATCH 4/5] feat(hipdnn): add Graph.tensor_scalar and ScalarType to Python bindings Bind the ScalarType enum and add Graph.tensor_scalar(value, scalar_type) as four overloads (float, double, int32_t, int64_t, tried in that order) that call Graph::tensor. A Python 0.5 becomes FLOAT, 0.1 becomes DOUBLE, 3 becomes INT32 and 2**40 becomes INT64. scalar_type selects a compile-time constant or a runtime parameter with a default value. Add tests for the data type chosen for each Python value and for both scalar types. Part of #10586. --- .../frontend_bindings/src/graph_bindings.cpp | 24 +++++++++++++ .../frontend_bindings/src/tensor_bindings.cpp | 3 ++ .../tests/test_tensor.py | 36 +++++++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/projects/hipdnn/python/frontend_bindings/src/graph_bindings.cpp b/projects/hipdnn/python/frontend_bindings/src/graph_bindings.cpp index de919937f628..7abfc96271c5 100644 --- a/projects/hipdnn/python/frontend_bindings/src/graph_bindings.cpp +++ b/projects/hipdnn/python/frontend_bindings/src/graph_bindings.cpp @@ -160,6 +160,12 @@ std::pair, AutotuneCacheWriteOutcome> return {results, outcome}; } +template +std::shared_ptr tensorScalar(T value, graph::ScalarType scalarType) +{ + return graph::Graph::tensor(value, scalarType); +} + } // namespace void graphBindings(nb::module_& m) @@ -782,6 +788,24 @@ void graphBindings(nb::module_& m) "Create a tensor with the metadata of a hipdnn Tensor or a __dlpack__ producer. " "A host (cpu) producer becomes a runtime pass-by-value tensor " "whose value is passed to execute() as a host tensor.") + .def_static("tensor_scalar", + &tensorScalar, + nb::arg("value"), + nb::arg("scalar_type"), + "Create a pass-by-value scalar tensor. scalar_type selects a compile-time " + "constant or a runtime parameter with a default value.") + .def_static("tensor_scalar", + &tensorScalar, + nb::arg("value"), + nb::arg("scalar_type")) + .def_static("tensor_scalar", + &tensorScalar, + nb::arg("value"), + nb::arg("scalar_type")) + .def_static("tensor_scalar", + &tensorScalar, + nb::arg("value"), + nb::arg("scalar_type")) .def( "to_json", [](graph::Graph& g) { diff --git a/projects/hipdnn/python/frontend_bindings/src/tensor_bindings.cpp b/projects/hipdnn/python/frontend_bindings/src/tensor_bindings.cpp index b0191d6c7a08..8bcf8552afbb 100644 --- a/projects/hipdnn/python/frontend_bindings/src/tensor_bindings.cpp +++ b/projects/hipdnn/python/frontend_bindings/src/tensor_bindings.cpp @@ -142,6 +142,9 @@ nb::object valueToPython(const TensorAttributes::ValueVariant& value) void tensorBindings(nb::module_& m) { + nb::enum_(m, "ScalarType") + .value("RUNTIME_PARAM", ScalarType::RUNTIME_PARAM) + .value("COMPILE_TIME_CONST", ScalarType::COMPILE_TIME_CONST); nb::class_(m, "Tensor") .def(nb::init<>()) .def_static( diff --git a/projects/hipdnn/python/frontend_wheel_package/tests/test_tensor.py b/projects/hipdnn/python/frontend_wheel_package/tests/test_tensor.py index f35b1dc00f1f..9b56fd37069b 100644 --- a/projects/hipdnn/python/frontend_wheel_package/tests/test_tensor.py +++ b/projects/hipdnn/python/frontend_wheel_package/tests/test_tensor.py @@ -378,3 +378,39 @@ def test_rejects_multi_element_tensor(self): with pytest.raises(ValueError, match="one element"): tensor.set_compile_time_constant(1.0) + + +class TestGraphTensorScalar: + """Graph.tensor_scalar() creates a scalar tensor; the Python value picks the type.""" + + @pytest.mark.parametrize( + "value, data_type", + [ + (0.5, hipdnn.DataType.FLOAT), + (0.1, hipdnn.DataType.DOUBLE), + (3, hipdnn.DataType.INT32), + (2**40, hipdnn.DataType.INT64), + ], + ) + def test_data_type_follows_the_python_value(self, value, data_type): + kind = hipdnn.ScalarType.COMPILE_TIME_CONST + + scalar = hipdnn.Graph.tensor_scalar(value, kind) + + assert scalar.get_data_type() == data_type + + def test_compile_time_constant_keeps_the_value(self): + kind = hipdnn.ScalarType.COMPILE_TIME_CONST + + scalar = hipdnn.Graph.tensor_scalar(0.5, kind) + + assert scalar.get_compile_time_constant() == 0.5 + assert not scalar.get_is_runtime_pass_by_value() + + def test_runtime_param_is_runtime_with_default(self): + kind = hipdnn.ScalarType.RUNTIME_PARAM + + scalar = hipdnn.Graph.tensor_scalar(0.5, kind) + + assert scalar.get_pass_by_value() == 0.5 + assert scalar.get_is_runtime_pass_by_value() From 84d098b7621ee96fea1f5018f920643f48384687 Mon Sep 17 00:00:00 2001 From: Nikhar Asthana <83886454+NikharAsthana@users.noreply.github.com> Date: Fri, 30 Oct 2026 21:18:14 +0530 Subject: [PATCH 5/5] test(hipdnn): cover tensor_scalar serialization and strengthen runtime-parameter test Address review feedback on #14332: - Add a test that places a Graph.tensor_scalar tensor in a graph and checks the serialized data type, value and minimum engine API version for both scalar types. - Assert that set_as_runtime_parameter() clears the stored value, which distinguishes it from set_is_pass_by_value(True). Part of #10586. --- .../frontend_wheel_package/tests/test_tensor.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/projects/hipdnn/python/frontend_wheel_package/tests/test_tensor.py b/projects/hipdnn/python/frontend_wheel_package/tests/test_tensor.py index 9b56fd37069b..0aaaa1f832fc 100644 --- a/projects/hipdnn/python/frontend_wheel_package/tests/test_tensor.py +++ b/projects/hipdnn/python/frontend_wheel_package/tests/test_tensor.py @@ -168,6 +168,7 @@ def test_set_as_runtime_parameter(self): assert tensor.set_as_runtime_parameter() is tensor assert tensor.get_is_runtime_pass_by_value() assert not tensor.get_has_compile_time_constant() + assert tensor.get_value_variant() is None def test_ragged_offset_round_trip(self): """set_ragged_offset()/get_ragged_offset() round-trip.""" @@ -414,3 +415,17 @@ def test_runtime_param_is_runtime_with_default(self): assert scalar.get_pass_by_value() == 0.5 assert scalar.get_is_runtime_pass_by_value() + + @pytest.mark.parametrize( + "scalar_type, api", + [ + (hipdnn.ScalarType.COMPILE_TIME_CONST, (1, 0)), + (hipdnn.ScalarType.RUNTIME_PARAM, (1, 2)), + ], + ) + def test_serializes_in_a_graph(self, scalar_type, api): + entry, min_api = _serialized_scalar( + hipdnn.Graph.tensor_scalar(0.5, scalar_type) + ) + + assert (entry["data_type"], entry["value"], min_api) == ("float", 0.5, api)