diff --git a/.gitignore b/.gitignore index ba8698ec024..b0294825de2 100644 --- a/.gitignore +++ b/.gitignore @@ -97,3 +97,6 @@ docs/_toc.yml # Ignore CMake user presets CMakeUserPresets.json +# local Python virtual environment +.venv/ +.cache/ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 67cadd37609..65a7b6e4efe 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -61,6 +61,7 @@ add_library(migraphx convert_to_json.cpp cpp_generator.cpp dead_code_elimination.cpp + dim_like.cpp dom_info.cpp dynamic_loader.cpp eliminate_allocation.cpp diff --git a/src/dim_like.cpp b/src/dim_like.cpp new file mode 100644 index 00000000000..7c375a51085 --- /dev/null +++ b/src/dim_like.cpp @@ -0,0 +1,50 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015-2026 Advanced Micro Devices, Inc. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { + +void migraphx_to_value(value& v, const dim_like& d) +{ + v = visit([](const auto& x) { return migraphx::to_value(x); }, d); +} + +void migraphx_from_value(const value& v, dim_like& d) +{ + if(v.is_object()) + { + shape::dynamic_dimension dd; + migraphx::from_value(v, dd); + d = std::move(dd); + return; + } + d = v.to(); +} + +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx diff --git a/src/include/migraphx/dim_like.hpp b/src/include/migraphx/dim_like.hpp new file mode 100644 index 00000000000..d3933fe2a40 --- /dev/null +++ b/src/include/migraphx/dim_like.hpp @@ -0,0 +1,68 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015-2026 Advanced Micro Devices, Inc. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MIGRAPHX_GUARD_MIGRAPHLIB_DIM_LIKE_HPP +#define MIGRAPHX_GUARD_MIGRAPHLIB_DIM_LIKE_HPP + +#include +#include +#include + +#include +#include +#include +#include + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { + +struct value; + +// Routes any integral type through int64_t so call sites don't need casts. +struct dim_like_picker +{ + template {})> + static int64_t apply(T v) + { + return static_cast(v); + } + + static shape::dynamic_dimension apply(shape::dynamic_dimension d) { return d; } +}; + +// A dim attribute entry that may be either a plain int64_t or a dynamic_dimension. +using dim_like = picked_variant; + +inline std::ostream& operator<<(std::ostream& os, const dim_like& d) +{ + visit([&](const auto& x) { os << x; }, d); + return os; +} + +MIGRAPHX_EXPORT void migraphx_to_value(value& v, const dim_like& d); +MIGRAPHX_EXPORT void migraphx_from_value(const value& v, dim_like& d); + +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx + +#endif diff --git a/src/include/migraphx/op/reshape.hpp b/src/include/migraphx/op/reshape.hpp index acc84dddd2a..0c5d32943b5 100644 --- a/src/include/migraphx/op/reshape.hpp +++ b/src/include/migraphx/op/reshape.hpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -57,7 +58,7 @@ namespace op { */ struct reshape { - std::vector dims; + std::vector dims; template static auto reflect(Self& self, F f) @@ -71,9 +72,9 @@ struct reshape // Makes no checks for the validity of the `dims` attribute for the given input shape. shape dyn_1arg_compute_shape(shape s0) const { - auto input_dyn_dims = s0.dyn_dims(); - const auto neg_dim_num = - std::distance(this->dims.begin(), std::find(this->dims.begin(), this->dims.end(), -1)); + auto input_dyn_dims = s0.dyn_dims(); + const auto neg_dim_num = std::distance( + this->dims.begin(), std::find(this->dims.begin(), this->dims.end(), dim_like{-1})); const bool has_negative_dim_attr = neg_dim_num < dims.size(); // construct output dynamic shape from dims attribute std::vector output_dyn_dims(dims.size()); @@ -81,17 +82,17 @@ struct reshape for(std::size_t i = 0; i < dims.size(); ++i) { auto d = dims.at(i); - if(d == 0) + if(d == dim_like{0}) { output_dyn_dims.at(i) = input_dyn_dims.at(i); } - else if(d == -1) + else if(d == dim_like{-1}) { output_dyn_dims.at(i) = {1, 1}; } else { - std::size_t u_dim = d; + std::size_t u_dim = std::get(d); output_dyn_dims.at(i) = {u_dim, u_dim}; } } @@ -139,15 +140,18 @@ struct reshape { check_shapes{inputs, *this}.has(1); auto&& idims = inputs.front().lens(); - std::vector rdims(dims.begin(), dims.end()); + std::vector rdims(dims.size()); + std::transform(dims.begin(), dims.end(), rdims.begin(), [](const dim_like& d) { + return std::get(d); + }); for(std::size_t i = 0; i < dims.size(); i++) { - if(dims[i] == 0) + if(dims[i] == dim_like{0}) rdims[i] = idims[i]; // convert -1 to 1 for rdims since rdims uses size_t (-1 is max_int for size_t) - if(dims[i] == -1) + if(dims[i] == dim_like{-1}) rdims[i] = 1; } @@ -158,7 +162,7 @@ struct reshape std::accumulate(rdims.begin(), rdims.end(), 1, std::multiplies()); for(std::size_t i = 0; i < rdims.size(); i++) { - if(dims[i] == -1) + if(dims[i] == dim_like{-1}) rdims[i] = missing_dim; } } @@ -182,7 +186,12 @@ struct reshape { check_shapes{inputs, *this, true}.has(1, 2); - auto n_neg_dims = std::count(dims.begin(), dims.end(), -1); + if(std::any_of(dims.begin(), dims.end(), [](const auto& d) { + return std::holds_alternative(d); + })) + MIGRAPHX_THROW("Reshape: dynamic_dimension dim entries are not currently supported"); + + auto n_neg_dims = std::count(dims.begin(), dims.end(), dim_like{-1}); if(n_neg_dims > 1) MIGRAPHX_THROW("Reshape: Dimensions for reshape can only have one -1 dim"); diff --git a/src/include/migraphx/op/reshape_lazy.hpp b/src/include/migraphx/op/reshape_lazy.hpp index 48250141d1c..278500469a7 100644 --- a/src/include/migraphx/op/reshape_lazy.hpp +++ b/src/include/migraphx/op/reshape_lazy.hpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -37,7 +38,7 @@ namespace op { struct reshape_lazy { - std::vector dims; + std::vector dims; template static auto reflect(Self& self, F f) @@ -65,12 +66,12 @@ struct reshape_lazy { if(dyn_dims[i].is_fixed()) { - num_dims_ele *= dims[i]; + num_dims_ele *= std::get(dims[i]); num_dd_ele *= dyn_dims[i].get_interval().min; } else { - if(dims[i] != 0 and dims[i] != -1) + if(dims[i] != dim_like{0} and dims[i] != dim_like{-1}) { MIGRAPHX_THROW( "reshape_lazy: Non-fixed dynamic_dimension doesn't match with 0 or -1 " @@ -89,9 +90,10 @@ struct reshape_lazy dims.cend(), dyn_dims.cbegin(), output_dyn_dims.begin(), - [](std::size_t dim, auto dyn_dim) { + [](const dim_like& d, auto dyn_dim) { if(not dyn_dim.is_fixed()) return dyn_dim; + std::size_t dim = std::get(d); return shape::dynamic_dimension{dim, dim}; }); return {s0.type(), output_dyn_dims}; @@ -101,16 +103,19 @@ struct reshape_lazy { check_shapes{inputs, *this}.has(1); auto&& idims = inputs.front().lens(); - std::vector rdims(dims.begin(), dims.end()); + std::vector rdims(dims.size()); + std::transform(dims.begin(), dims.end(), rdims.begin(), [](const dim_like& d) { + return std::get(d); + }); for(std::size_t i = 0; i < dims.size(); i++) { - if(dims[i] == 0) + if(dims[i] == dim_like{0}) rdims[i] = idims[i]; // since rdims using size_t type, -1 is the max value // is size_t that cause later compuation incorrect - if(dims[i] == -1) + if(dims[i] == dim_like{-1}) rdims[i] = 1; } @@ -121,7 +126,7 @@ struct reshape_lazy std::accumulate(rdims.begin(), rdims.end(), 1, std::multiplies()); for(std::size_t i = 0; i < rdims.size(); i++) { - if(dims[i] == -1) + if(dims[i] == dim_like{-1}) rdims[i] = missing_dim; } } @@ -143,7 +148,13 @@ struct reshape_lazy shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1); - auto n_neg_dims = std::count(dims.begin(), dims.end(), -1); + if(std::any_of(dims.begin(), dims.end(), [](const auto& d) { + return std::holds_alternative(d); + })) + MIGRAPHX_THROW( + "reshape_lazy: dynamic_dimension dim entries are not currently supported"); + + auto n_neg_dims = std::count(dims.begin(), dims.end(), dim_like{-1}); if(n_neg_dims > 1) MIGRAPHX_THROW("reshape_lazy: Dimensions for reshape_lazy can only have one -1 dim"); const auto& s0 = inputs[0]; diff --git a/src/include/migraphx/picked_variant.hpp b/src/include/migraphx/picked_variant.hpp index 38736f1af17..b4baaa987af 100644 --- a/src/include/migraphx/picked_variant.hpp +++ b/src/include/migraphx/picked_variant.hpp @@ -73,7 +73,9 @@ struct picked_variant : std::variant using base_t = std::variant; using base_t::base_t; // inherit default, in_place_type, in_place_index ctors - template >{})> + template >{}), + class = decltype(Picker::apply(std::declval()))> constexpr picked_variant(T&& x) : base_t(Picker::apply(std::forward(x))) { } diff --git a/test/dim_like_test.cpp b/test/dim_like_test.cpp new file mode 100644 index 00000000000..0918c079620 --- /dev/null +++ b/test/dim_like_test.cpp @@ -0,0 +1,306 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015-2026 Advanced Micro Devices, Inc. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "test.hpp" + +using migraphx::dim_like; +using dd = migraphx::shape::dynamic_dimension; + +static dim_like round_trip(const dim_like& d) +{ + auto v = migraphx::to_value(d); + return migraphx::from_value(v); +} + +// =================================================================== +// Construction and alternative inspection +// =================================================================== + +TEST_CASE(construct_default) +{ + dim_like d; + EXPECT(std::holds_alternative(d)); + EXPECT(std::get(d) == 0); +} + +TEST_CASE(construct_int_marker_zero) +{ + dim_like d = 0; + EXPECT(std::holds_alternative(d)); + EXPECT(std::get(d) == 0); +} + +TEST_CASE(construct_int_marker_neg_one) +{ + dim_like d = -1; + EXPECT(std::holds_alternative(d)); + EXPECT(std::get(d) == -1); +} + +TEST_CASE(construct_int_value) +{ + dim_like d = 42; + EXPECT(std::holds_alternative(d)); + EXPECT(std::get(d) == 42); +} + +TEST_CASE(construct_from_size_t) +{ + std::size_t n = 7; + dim_like d = n; + EXPECT(std::holds_alternative(d)); + EXPECT(std::get(d) == 7); +} + +TEST_CASE(construct_from_dynamic_dimension_range) +{ + dim_like d = dd{1, 4}; + EXPECT(std::holds_alternative
(d)); + EXPECT(std::get
(d) == dd{1, 4}); +} + +TEST_CASE(construct_from_dynamic_dimension_symbolic) +{ + dim_like d = dd{migraphx::sym::var("n", {1, 8})}; + EXPECT(std::holds_alternative
(d)); + EXPECT(std::get
(d).is_symbolic()); +} + +TEST_CASE(get_throws_on_wrong_alternative) +{ + dim_like d = 42; + EXPECT(test::throws([&] { (void)std::get
(d); })); +} + +// =================================================================== +// Equality and std::count on 0 / -1 markers +// =================================================================== + +TEST_CASE(equality_int_marker_zero) +{ + dim_like d = 0; + EXPECT(d == dim_like{0}); + EXPECT(dim_like{0} == d); + EXPECT(not(d == dim_like{-1})); +} + +TEST_CASE(equality_int_marker_neg_one) +{ + dim_like d = -1; + EXPECT(d == dim_like{-1}); + EXPECT(dim_like{-1} == d); + EXPECT(not(d == dim_like{0})); +} + +TEST_CASE(equality_int_value) +{ + dim_like d = 5; + EXPECT(d == dim_like{5}); + EXPECT(dim_like{5} == d); + EXPECT(d != dim_like{4}); +} + +TEST_CASE(equality_dd_alternative_never_matches_marker) +{ + dim_like d = dd{0, 4}; + EXPECT(d != dim_like{0}); + EXPECT(d != dim_like{-1}); +} + +TEST_CASE(equality_between_alternatives) +{ + dim_like a = 3; + dim_like b = dd{3, 3}; + EXPECT(a != b); +} + +TEST_CASE(std_count_marker) +{ + std::vector dims = {0, 0, 6, -1}; + EXPECT(std::count(dims.begin(), dims.end(), dim_like{-1}) == 1); + EXPECT(std::count(dims.begin(), dims.end(), dim_like{0}) == 2); +} + +// =================================================================== +// Streaming +// =================================================================== + +TEST_CASE(stream_int) +{ + std::ostringstream ss; + ss << dim_like{42}; + EXPECT(ss.str() == "42"); +} + +TEST_CASE(stream_neg_one) +{ + std::ostringstream ss; + ss << dim_like{-1}; + EXPECT(ss.str() == "-1"); +} + +TEST_CASE(stream_dd) +{ + std::ostringstream ss; + ss << dim_like{dd{1, 4}}; + std::ostringstream expected; + expected << dd{1, 4}; + EXPECT(ss.str() == expected.str()); +} + +TEST_CASE(stream_dd_symbolic) +{ + auto sd = dd{migraphx::sym::var("n", {1, 8})}; + std::ostringstream ss; + ss << dim_like{sd}; + std::ostringstream expected; + expected << sd; + EXPECT(ss.str() == expected.str()); +} + +// =================================================================== +// Serialization round-trip +// =================================================================== + +TEST_CASE(serialize_int_zero) +{ + dim_like d = 0; + auto rt = round_trip(d); + EXPECT(rt == d); + EXPECT(std::holds_alternative(rt)); +} + +TEST_CASE(serialize_int_neg_one) +{ + dim_like d = -1; + auto rt = round_trip(d); + EXPECT(rt == d); + EXPECT(std::holds_alternative(rt)); +} + +TEST_CASE(serialize_int_value) +{ + dim_like d = 42; + auto rt = round_trip(d); + EXPECT(rt == d); + EXPECT(std::holds_alternative(rt)); +} + +TEST_CASE(serialize_dd_range) +{ + dim_like d = dd{1, 4}; + auto rt = round_trip(d); + EXPECT(rt == d); + EXPECT(std::holds_alternative
(rt)); +} + +TEST_CASE(serialize_dd_symbolic) +{ + dim_like d = dd{migraphx::sym::var("n", {1, 8})}; + auto rt = round_trip(d); + EXPECT(rt == d); + EXPECT(std::holds_alternative
(rt)); +} + +// =================================================================== +// Backward-compat: load and save against legacy int / size_t arrays +// =================================================================== + +TEST_CASE(from_value_legacy_int_array) +{ + std::vector legacy = {0, 0, 6, -1}; + auto loaded = migraphx::from_value>(migraphx::to_value(legacy)); + EXPECT(loaded == std::vector{0, 0, 6, -1}); +} + +TEST_CASE(from_value_size_t_array) +{ + std::vector lens = {4, 24, 1}; + auto loaded = migraphx::from_value>(migraphx::to_value(lens)); + EXPECT(loaded == std::vector{4, 24, 1}); +} + +TEST_CASE(to_value_int_array_byte_compat) +{ + std::vector dims = {0, 0, 6, -1}; + std::vector legacy{0, 0, 6, -1}; + EXPECT(migraphx::to_value(dims) == migraphx::to_value(legacy)); +} + +TEST_CASE(round_trip_mixed_vector) +{ + std::vector dims = { + dim_like{0}, + dim_like{42}, + dim_like{dd{1, 4}}, + dim_like{-1}, + }; + auto v = migraphx::to_value(dims); + auto loaded = migraphx::from_value>(v); + EXPECT(loaded == dims); +} + +// =================================================================== +// ADL visit +// =================================================================== + +TEST_CASE(visit_int) +{ + dim_like d = 42; + auto seen = visit( + [](const auto& x) -> std::string { + if constexpr(std::is_same, int64_t>{}) + return "int"; + else + return "dd"; + }, + d); + EXPECT(seen == "int"); +} + +TEST_CASE(visit_dd) +{ + dim_like d = dd{1, 4}; + auto seen = visit( + [](const auto& x) -> std::string { + if constexpr(std::is_same, int64_t>{}) + return "int"; + else + return "dd"; + }, + d); + EXPECT(seen == "dd"); +} + +int main(int argc, const char* argv[]) { test::run(argc, argv); } diff --git a/test/onnx/parse/reshape_test.cpp b/test/onnx/parse/reshape_test.cpp index 7ee9e492198..be51e4b74dd 100644 --- a/test/onnx/parse/reshape_test.cpp +++ b/test/onnx/parse/reshape_test.cpp @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. + * Copyright (c) 2015-2026 Advanced Micro Devices, Inc. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -34,7 +34,7 @@ TEST_CASE(reshape_test) mm->add_literal( migraphx::literal{migraphx::shape{migraphx::shape::int64_type, {2}}, reshape_dims}); auto l0 = mm->add_parameter("0", migraphx::shape{migraphx::shape::float_type, {4, 2, 3}}); - op.dims = reshape_dims; + op.dims.assign(reshape_dims.begin(), reshape_dims.end()); mm->add_instruction(op, l0); mm->add_instruction(op, l0); auto prog = optimize_onnx("reshape_test.onnx");