From e1a7bc79e1dad11c01c6473a529e866e51cbf22e Mon Sep 17 00:00:00 2001 From: Shiv Date: Mon, 29 Jun 2026 16:40:57 -0700 Subject: [PATCH 01/42] allow sym slice attributes --- src/include/migraphx/dim_like.hpp | 13 ++++ src/include/migraphx/op/slice.hpp | 90 ++++++++++++----------- src/normalize_attributes.cpp | 4 + src/onnx/parse_slice.cpp | 27 +++++-- src/simplify_algebra.cpp | 47 +++++++----- src/simplify_dyn_ops.cpp | 12 +-- src/simplify_reshapes.cpp | 23 ++++-- test/op_shape_test.cpp | 117 ++++++++++++++++++++++++++++-- 8 files changed, 246 insertions(+), 87 deletions(-) diff --git a/src/include/migraphx/dim_like.hpp b/src/include/migraphx/dim_like.hpp index d3933fe2a40..094e475b1ac 100644 --- a/src/include/migraphx/dim_like.hpp +++ b/src/include/migraphx/dim_like.hpp @@ -24,9 +24,11 @@ #ifndef MIGRAPHX_GUARD_MIGRAPHLIB_DIM_LIKE_HPP #define MIGRAPHX_GUARD_MIGRAPHLIB_DIM_LIKE_HPP +#include #include #include #include +#include #include #include @@ -59,6 +61,17 @@ inline std::ostream& operator<<(std::ostream& os, const dim_like& d) return os; } +// Extracts the concrete int64_t from each entry; throws (via std::get) if any +// entry holds a symbolic dynamic_dimension. +inline std::vector to_ints(const std::vector& dims) +{ + std::vector result(dims.size()); + std::transform(dims.begin(), dims.end(), result.begin(), [](const dim_like& d) { + return std::get(d); + }); + return result; +} + MIGRAPHX_EXPORT void migraphx_to_value(value& v, const dim_like& d); MIGRAPHX_EXPORT void migraphx_from_value(const value& v, dim_like& d); diff --git a/src/include/migraphx/op/slice.hpp b/src/include/migraphx/op/slice.hpp index ab601ec0ed5..78a1b0c467a 100644 --- a/src/include/migraphx/op/slice.hpp +++ b/src/include/migraphx/op/slice.hpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -54,8 +55,8 @@ namespace op { * * Attributes: * axes: constant axes to slice over (optional) - * starts: constant slice starting indices (optional) - * ends: constant slice ending indices (optional) + * starts: slice starting indices, constant or symbolic (optional) + * ends: slice ending indices, constant or symbolic (optional) * * Parameters: * data: the input tensor to slice (dynamic or static shape) @@ -66,8 +67,8 @@ namespace op { struct slice { std::vector axes{}; - std::vector starts{}; - std::vector ends{}; + std::vector starts{}; + std::vector ends{}; /** * Named arrays for the set attribute possibilities. @@ -134,12 +135,7 @@ struct slice /// Get the attributes that are non-empty std::array get_set_attributes() const { - std::array, 3> attrs = {this->starts, this->ends, this->axes}; - std::array bool_vec; - std::transform(attrs.cbegin(), attrs.cend(), bool_vec.begin(), [](const auto& a) { - return not a.empty(); - }); - return bool_vec; + return {not starts.empty(), not ends.empty(), not axes.empty()}; } /// Helper function for normalize_compute_shape() @@ -259,6 +255,28 @@ struct slice return shape{input_shape.type(), dds}; } + static sym::expr bound_expr(const dim_like& d) + { + if(std::holds_alternative(d)) + return std::get(d).sym_expr; + return sym::lit(std::get(d)); + } + + // Static and symbolic input share one path: promote to a symbolic shape, set + // each sliced axis to the extent ends - starts, carry the kept strides through, + // then demote back to static when the result is fully fixed (slice is a view). + shape symbolic_compute_shape(const shape& s) const + { + auto sym_in = s.to_symbolic(); + auto dds = sym_in.dyn_dims(); + for(std::size_t i = 0; i < axes.size(); ++i) + dds[axes[i]] = shape::dynamic_dimension{bound_expr(ends[i]) - bound_expr(starts[i])}; + shape result{s.type(), dds, sym_in.dyn_strides()}; + if(not s.symbolic() and result.is_fixed()) + return result.to_static(); + return result; + } + // uses the normalize_axes flag to normalize axes, starts, and ends shape normalize_compute_shape(std::vector inputs) const { @@ -271,31 +289,21 @@ struct slice if(set_attributes != all_set) MIGRAPHX_THROW("SLICE 1_arg: Invalid 1 input and attributes configuration"); - // TODO: support slicing non-fixed symbolic dims (output dim would be - // a sym::expr derived from starts/ends and the symbolic axis bound). - if(input_shape.dynamic() and std::any_of(axes.begin(), axes.end(), [&](auto axis) { - return not input_shape.dyn_dims()[axis].is_fixed(); - })) + if(input_shape.dynamic() and not input_shape.symbolic()) { - MIGRAPHX_THROW("SLICE 1_arg: slicing is not allowed on non-fixed dynamic input axis "); - } - - auto new_lens = lens_calc(input_shape.max_lens(), this->starts, this->ends, this->axes); - - if(not input_shape.dynamic()) - return shape{input_shape.type(), new_lens, input_shape.strides()}; + if(std::any_of(axes.begin(), axes.end(), [&](auto axis) { + return not input_shape.dyn_dims()[axis].is_fixed(); + })) + MIGRAPHX_THROW("SLICE 1_arg: slicing is not allowed on a non-fixed input axis "); - auto dds = input_shape.dyn_dims(); - for(auto axis : this->axes) - { - dds[axis] = input_shape.symbolic() - ? shape::dynamic_dimension{sym::lit(new_lens[axis])} - : shape::dynamic_dimension{new_lens[axis], new_lens[axis]}; + auto new_lens = lens_calc(input_shape.max_lens(), to_ints(starts), to_ints(ends), axes); + auto dds = input_shape.dyn_dims(); + for(auto axis : axes) + dds[axis] = shape::dynamic_dimension{new_lens[axis], new_lens[axis]}; + return shape{input_shape.type(), dds}; } - if(input_shape.symbolic()) - return shape{input_shape.type(), dds, input_shape.dyn_strides()}; - return shape{input_shape.type(), dds}; + return symbolic_compute_shape(input_shape); } /** @@ -314,14 +322,14 @@ struct slice for(std::size_t i = 0; i < axes.size(); i++) { auto axis = axes[i]; - offset += starts[i] * strides[axis]; + offset += std::get(starts[i]) * strides[axis]; } } else { for(std::size_t axis = 0; axis < lens.size(); axis++) { - offset += starts[axis] * strides[axis]; + offset += std::get(starts[axis]) * strides[axis]; } } return offset * s.type_size(); @@ -388,7 +396,7 @@ struct slice } else { - norm_starts = this->starts; + norm_starts = to_ints(this->starts); } if(input_ends) { @@ -400,7 +408,7 @@ struct slice } else { - norm_ends = this->ends; + norm_ends = to_ints(this->ends); } return {{"norm_starts", norm_starts}, {"norm_ends", norm_ends}, {"norm_axes", norm_axes}}; } @@ -428,7 +436,7 @@ struct slice norm_inputs = normalize_starts_ends_axes(input_shape, input_starts.template to_vector(), - this->ends, + to_ints(this->ends), this->axes); }); } @@ -438,7 +446,7 @@ struct slice args[1].visit([&](auto input_ends) { norm_inputs = normalize_starts_ends_axes(input_shape, - this->starts, + to_ints(this->starts), input_ends.template to_vector(), this->axes); }); @@ -449,8 +457,8 @@ struct slice args[1].visit([&](auto input_axes) { norm_inputs = normalize_starts_ends_axes(input_shape, - this->starts, - this->ends, + to_ints(this->starts), + to_ints(this->ends), input_axes.template to_vector()); }); } @@ -472,7 +480,7 @@ struct slice norm_inputs = normalize_starts_ends_axes(input_shape, input_starts.template to_vector(), - this->ends, + to_ints(this->ends), input_axes.template to_vector()); }); } @@ -482,7 +490,7 @@ struct slice visit_all(args[1], args[2])([&](auto input_ends, auto input_axes) { norm_inputs = normalize_starts_ends_axes(input_shape, - this->starts, + to_ints(this->starts), input_ends.template to_vector(), input_axes.template to_vector()); }); diff --git a/src/normalize_attributes.cpp b/src/normalize_attributes.cpp index 48804c9034f..7acca62fc34 100644 --- a/src/normalize_attributes.cpp +++ b/src/normalize_attributes.cpp @@ -230,6 +230,10 @@ bool normalize_attributes(operation& op, const shape& input_shape) auto vv = val.at(key).without_key(); if(vv.is_array()) { + // Symbolic (dim_like) bounds serialize as objects and cannot be + // clamped against the input length at compile time; leave them as-is. + if(std::any_of(vv.begin(), vv.end(), [](const auto& e) { return e.is_object(); })) + continue; std::vector axes; if(val.contains("axes")) { diff --git a/src/onnx/parse_slice.cpp b/src/onnx/parse_slice.cpp index 57e546694b2..e26ca624449 100644 --- a/src/onnx/parse_slice.cpp +++ b/src/onnx/parse_slice.cpp @@ -118,22 +118,32 @@ struct parse_slice : op_parser if(args.size() >= 3) { - sd.op.ends = sd.insert(args.at(2)); + auto ends = sd.insert(args.at(2)); + sd.op.ends.assign(ends.begin(), ends.end()); } else if(contains(info.attributes, "ends")) { literal s = parser.parse_value(info.attributes.at("ends")); - s.visit([&](auto v) { copy(v, std::back_inserter(sd.op.ends)); }); + s.visit([&](auto v) { + std::transform(v.begin(), v.end(), std::back_inserter(sd.op.ends), [](auto e) { + return static_cast(e); + }); + }); } if(args.size() >= 2) { - sd.op.starts = sd.insert(args.at(1)); + auto starts = sd.insert(args.at(1)); + sd.op.starts.assign(starts.begin(), starts.end()); } else if(contains(info.attributes, "starts")) { literal s = parser.parse_value(info.attributes.at("starts")); - s.visit([&](auto v) { copy(v, std::back_inserter(sd.op.starts)); }); + s.visit([&](auto v) { + std::transform(v.begin(), v.end(), std::back_inserter(sd.op.starts), [](auto e) { + return static_cast(e); + }); + }); } // data input argument @@ -161,10 +171,11 @@ struct parse_slice : op_parser { if(sd.steps[i] >= 0) continue; - sd.op.starts[i] += 1; - if(sd.op.starts[i] == 0) - sd.op.starts[i] = INT_MAX; - sd.op.ends[i] += 1; + auto start = std::get(sd.op.starts[i]) + 1; + if(start == 0) + start = INT_MAX; + sd.op.starts[i] = start; + sd.op.ends[i] = std::get(sd.op.ends[i]) + 1; sd.raxes.push_back(sd.op.axes[i]); std::swap(sd.op.starts[i], sd.op.ends[i]); } diff --git a/src/simplify_algebra.cpp b/src/simplify_algebra.cpp index c01c3d1924a..683d93d710c 100644 --- a/src/simplify_algebra.cpp +++ b/src/simplify_algebra.cpp @@ -203,8 +203,10 @@ struct find_mul_slice_conv auto sop = any_cast(i->get_operator()); if(sop.axes != slice_op.axes) return true; - if(std::max(sop.starts.front(), slice_op.starts.front()) < - std::min(sop.ends.front(), slice_op.ends.front())) + if(std::max(std::get(sop.starts.front()), + std::get(slice_op.starts.front())) < + std::min(std::get(sop.ends.front()), + std::get(slice_op.ends.front()))) return true; return false; })) @@ -221,17 +223,19 @@ struct find_mul_slice_conv auto new_mul = m.insert_instruction(ins, make_op("mul"), new_a, slice_w_ins); std::vector sliced_weights; - if(slice_op.starts.front() != 0) + if(std::get(slice_op.starts.front()) != 0) sliced_weights.push_back(m.insert_instruction( ins, - make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", slice_op.starts}}), + make_op("slice", + {{"axes", {0}}, {"starts", {0}}, {"ends", to_ints(slice_op.starts)}}), w_ins)); sliced_weights.push_back(new_mul); int64_t end_axis = w_ins->get_shape().lens().at(0); - if(slice_op.ends.front() != end_axis) + if(std::get(slice_op.ends.front()) != end_axis) sliced_weights.push_back(m.insert_instruction( ins, - make_op("slice", {{"axes", {0}}, {"starts", slice_op.ends}, {"ends", {end_axis}}}), + make_op("slice", + {{"axes", {0}}, {"starts", to_ints(slice_op.ends)}, {"ends", {end_axis}}}), w_ins)); auto new_weights = @@ -1378,11 +1382,12 @@ static std::vector get_splits(instruction_ref ins) auto get_end = [&](auto& i) -> auto& { return get_slice(i).ends; }; // Sort the "slice" instructions in order of starts - std::sort( - result.begin(), result.end(), [&](auto x, auto y) { return get_start(x) < get_start(y); }); - if(std::any_of(get_start(result.front()).begin(), get_start(result.front()).end(), [&](auto i) { - return i != 0; - })) + std::sort(result.begin(), result.end(), [&](auto x, auto y) { + return to_ints(get_start(x)) < to_ints(get_start(y)); + }); + if(std::any_of(get_start(result.front()).begin(), + get_start(result.front()).end(), + [&](const auto& i) { return std::get(i) != 0; })) return {}; // one slice must "start" where the last slice "end" @@ -1393,7 +1398,7 @@ static std::vector get_splits(instruction_ref ins) for(std::size_t i = 0; i < axes.size(); i++) { auto axis = axes[i]; - if(ins->get_shape().lens()[axis] != get_slice(result.back()).ends[i]) + if(ins->get_shape().lens()[axis] != std::get(get_slice(result.back()).ends[i])) return {}; } return result; @@ -1768,7 +1773,11 @@ struct find_split_concat if(not std::is_sorted(it, it + splits.size(), [](instruction_ref x, instruction_ref y) { auto xop = any_cast(x->get_operator()); auto yop = any_cast(y->get_operator()); - return std::tie(xop.starts, xop.ends) < std::tie(yop.starts, yop.ends); + auto xs = to_ints(xop.starts); + auto xe = to_ints(xop.ends); + auto ys = to_ints(yop.starts); + auto ye = to_ints(yop.ends); + return std::tie(xs, xe) < std::tie(ys, ye); })) return; @@ -2324,16 +2333,16 @@ struct find_split_reshape std::transform(vec_rsp.begin(), vec_rsp.end(), new_starts.begin(), [&](auto is) { auto cont = is->inputs().front(); auto og_slc = cont->inputs().front(); - return any_cast(og_slc->get_operator()).starts[0] * rsp_axis_len / - slc_axis_len; + return std::get(any_cast(og_slc->get_operator()).starts[0]) * + rsp_axis_len / slc_axis_len; }); std::vector new_ends(vec_rsp.size()); std::transform(vec_rsp.begin(), vec_rsp.end(), new_ends.begin(), [&](auto is) { auto cont = is->inputs().front(); auto og_slc = cont->inputs().front(); - return any_cast(og_slc->get_operator()).ends[0] * rsp_axis_len / - slc_axis_len; + return std::get(any_cast(og_slc->get_operator()).ends[0]) * + rsp_axis_len / slc_axis_len; }); auto rsp_ins = m.insert_instruction( @@ -2401,8 +2410,8 @@ struct find_split_transpose for(auto in : split_outputs) { auto oper = any_cast(in->get_operator()); - auto starts = oper.starts; - auto ends = oper.ends; + auto starts = to_ints(oper.starts); + auto ends = to_ints(oper.ends); auto tr_orig = in->outputs().front(); m.replace_instruction( tr_orig, diff --git a/src/simplify_dyn_ops.cpp b/src/simplify_dyn_ops.cpp index 6a90d97fe70..b57488c1928 100644 --- a/src/simplify_dyn_ops.cpp +++ b/src/simplify_dyn_ops.cpp @@ -182,7 +182,7 @@ struct find_const_2in_slice : match::supports_dynamic_shapes // slice(data, starts) inputs.at(1)->eval().visit( [&](auto output) { starts_vec.assign(output.begin(), output.end()); }); - ends_vec = slice_op.ends; + ends_vec = to_ints(slice_op.ends); axes_vec = slice_op.axes; } else if(set_attrs == op::slice::starts_axes) @@ -190,7 +190,7 @@ struct find_const_2in_slice : match::supports_dynamic_shapes // slice(data, ends) inputs.at(1)->eval().visit( [&](auto output) { ends_vec.assign(output.begin(), output.end()); }); - starts_vec = slice_op.starts; + starts_vec = to_ints(slice_op.starts); axes_vec = slice_op.axes; } else @@ -198,8 +198,8 @@ struct find_const_2in_slice : match::supports_dynamic_shapes // slice(data, axes) inputs.at(1)->eval().visit( [&](auto output) { axes_vec.assign(output.begin(), output.end()); }); - starts_vec = slice_op.starts; - ends_vec = slice_op.ends; + starts_vec = to_ints(slice_op.starts); + ends_vec = to_ints(slice_op.ends); } m.replace_instruction( ins, @@ -249,7 +249,7 @@ struct find_const_3in_slice : match::supports_dynamic_shapes [&](auto output) { starts_vec.assign(output.begin(), output.end()); }); inputs.at(2)->eval().visit( [&](auto output) { axes_vec.assign(output.begin(), output.end()); }); - ends_vec = slice_op.ends; + ends_vec = to_ints(slice_op.ends); } else { @@ -258,7 +258,7 @@ struct find_const_3in_slice : match::supports_dynamic_shapes [&](auto output) { ends_vec.assign(output.begin(), output.end()); }); inputs.at(2)->eval().visit( [&](auto output) { axes_vec.assign(output.begin(), output.end()); }); - starts_vec = slice_op.starts; + starts_vec = to_ints(slice_op.starts); } m.replace_instruction( ins, diff --git a/src/simplify_reshapes.cpp b/src/simplify_reshapes.cpp index d5920a01814..87eb04e1a59 100644 --- a/src/simplify_reshapes.cpp +++ b/src/simplify_reshapes.cpp @@ -663,7 +663,8 @@ struct find_nested_slice auto op = any_cast(ins->get_operator()); for(std::size_t i = 0; i < op.axes.size(); i++) { - result[op.axes[i]] = std::make_pair(op.starts[i], op.ends[i]); + result[op.axes[i]] = + std::make_pair(std::get(op.starts[i]), std::get(op.ends[i])); } return result; } @@ -861,8 +862,8 @@ struct find_concat_slice for(const auto& sins : slice_candidates) { auto sop = any_cast(sins->get_operator()); - size_t slice_start = sop.starts.front(); - size_t slice_len = sop.ends.front() - slice_start; + size_t slice_start = std::get(sop.starts.front()); + size_t slice_len = std::get(sop.ends.front()) - slice_start; auto fii = std::find_if(prefix_scan.begin(), prefix_scan.end(), [&](const auto& j) { return j == slice_start; }); @@ -1806,8 +1807,13 @@ struct find_transpose_slice { assert(op.starts.size() == op.ends.size()); std::vector result(op.starts.size()); - std::transform( - op.ends.begin(), op.ends.end(), op.starts.begin(), result.begin(), std::minus<>{}); + std::transform(op.ends.begin(), + op.ends.end(), + op.starts.begin(), + result.begin(), + [](const auto& e, const auto& s) { + return std::get(e) - std::get(s); + }); return result; } @@ -1838,8 +1844,9 @@ struct find_transpose_slice return f(x) % d; }); }; + auto get_int = [](const auto& x) { return std::get(x); }; if(mod_by_distance(slice.axes, [&](auto x) { return ins->get_shape().lens()[x]; }) != 0 or - mod_by_distance(slice.starts, id{}) != 0 or mod_by_distance(slice.ends, id{}) != 0) + mod_by_distance(slice.starts, get_int) != 0 or mod_by_distance(slice.ends, get_int) != 0) return; // TODO: Handle multiple axes if(sdistance.size() != 1) @@ -1877,8 +1884,8 @@ struct find_transpose_slice { auto op = any_cast(s->get_operator()); op.axes = {0}; - op.starts = {op.starts.front() / sdistance.front()}; - op.ends = {op.ends.front() / sdistance.front()}; + op.starts = {std::get(op.starts.front()) / sdistance.front()}; + op.ends = {std::get(op.ends.front()) / sdistance.front()}; auto slice_ins = m.insert_instruction(ins, op, transpose); auto squeeze = m.insert_instruction(ins, make_op("squeeze", {{"axes", {0}}}), slice_ins); diff --git a/test/op_shape_test.cpp b/test/op_shape_test.cpp index d4c521f7369..0b37077c523 100644 --- a/test/op_shape_test.cpp +++ b/test/op_shape_test.cpp @@ -5329,12 +5329,18 @@ TEST_CASE(slice_sym_fixed_bound_var) EXPECT(sout.to_static(sym_map) == op.compute_shape({sin.to_static(sym_map)})); } -TEST_CASE(slice_sym_non_fixed_throws) +TEST_CASE(slice_sym_non_fixed_axis) { - // Slicing on a non-fixed symbolic axis is rejected (same contract as range). - auto n = var("n", {1, 8}); - migraphx::shape sin{migraphx::shape::float_type, {dd{lit(4)}, dd{n}, dd{lit(8)}}}; - throws_shape(migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {2}}}), sin); + // Slicing a non-fixed symbolic axis is allowed: the bounds are trusted to be valid at + // runtime, so the extent is just ends - starts (no clamping). The other symbol is kept. + auto n = var("n", {1, 8}); + auto m = var("m", {1, 8}); + std::unordered_map sym_map = {{n, 5}, {m, 3}}; + migraphx::shape sin{migraphx::shape::float_type, {dd{n}, dd{m}}}; + auto op = migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {2}}}); + migraphx::shape sout{migraphx::shape::float_type, {dd{lit(2)}, dd{m}}, sin.dyn_strides()}; + expect_shape(sout, op, sin); + EXPECT(sout.to_static(sym_map) == op.compute_shape({sin.to_static(sym_map)})); } TEST_CASE(slice_sym_nonstandard_layout) @@ -5350,6 +5356,107 @@ TEST_CASE(slice_sym_nonstandard_layout) EXPECT(sout.to_static(sym_map) == op.compute_shape({sin.to_static(sym_map)})); } +TEST_CASE(slice_sym_clamped_and_negative_bounds) +{ + // Concrete bounds on a symbolic input are clipped/resolved against the fixed sliced axis + // like the static path; the symbolic axis is untouched. + auto n = var("n", {1, 8}); + std::unordered_map sym_map = {{n, 5}}; + migraphx::shape sin{migraphx::shape::float_type, {dd{n}, dd{lit(2)}, dd{lit(3)}}}; + + { + // end clipped to len 3: dim = 3 - 2 = 1. + auto op = migraphx::make_op("slice", {{"axes", {2}}, {"starts", {2}}, {"ends", {10}}}); + migraphx::shape sout{ + migraphx::shape::float_type, {dd{n}, dd{lit(2)}, dd{lit(1)}}, sin.dyn_strides()}; + expect_shape(sout, op, sin); + EXPECT(sout.to_static(sym_map) == op.compute_shape({sin.to_static(sym_map)})); + } + { + // negative start: -1 -> 2, dim = 3 - 2 = 1. + auto op = migraphx::make_op("slice", {{"axes", {2}}, {"starts", {-1}}, {"ends", {10}}}); + migraphx::shape sout{ + migraphx::shape::float_type, {dd{n}, dd{lit(2)}, dd{lit(1)}}, sin.dyn_strides()}; + expect_shape(sout, op, sin); + EXPECT(sout.to_static(sym_map) == op.compute_shape({sin.to_static(sym_map)})); + } + { + // negative end on axis 1 (len 2): -1 -> 1, dim = 1 - 0 = 1. + auto op = migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {-1}}}); + migraphx::shape sout{ + migraphx::shape::float_type, {dd{n}, dd{lit(1)}, dd{lit(3)}}, sin.dyn_strides()}; + expect_shape(sout, op, sin); + EXPECT(sout.to_static(sym_map) == op.compute_shape({sin.to_static(sym_map)})); + } +} + +TEST_CASE(slice_sym_symbolic_end_static_input) +{ + // Static input + symbolic end bound: output is symbolic (not fixed -> not demoted to static). + auto n = var("n", {1, 16}); + auto op = migraphx::make_op("slice", + {{"axes", {0}}, + {"starts", {0}}, + {"ends", migraphx::value::array{migraphx::to_value(dd{n})}}}); + + migraphx::shape sin{migraphx::shape::float_type, {10}}; + migraphx::shape sout{migraphx::shape::float_type, {dd{n}}, {lit(1)}}; + expect_shape(sout, op, sin); + EXPECT(sout.symbolic()); + EXPECT(not sout.is_fixed()); + EXPECT(sout.to_static({{n, 7}}) == migraphx::shape{migraphx::shape::float_type, {7}, {1}}); + EXPECT(sout.to_static({{n, 10}}) == migraphx::shape{migraphx::shape::float_type, {10}, {1}}); +} + +TEST_CASE(slice_sym_symbolic_bounds) +{ + auto m = var("m", {1, 16}); + auto n = var("n", {1, 16}); + + { + // Symbolic end on a fixed axis: dim = n - 2; leading axis and strides preserved. + auto op = migraphx::make_op("slice", + {{"axes", {1}}, + {"starts", {2}}, + {"ends", migraphx::value::array{migraphx::to_value(dd{n})}}}); + migraphx::shape sin{migraphx::shape::float_type, {dd{m}, dd{lit(12)}}}; + migraphx::shape sout{ + migraphx::shape::float_type, {dd{m}, dd{n - lit(2)}}, sin.dyn_strides()}; + expect_shape(sout, op, sin); + EXPECT(sout.symbolic()); + EXPECT(not sout.is_fixed()); + EXPECT(sout.to_static({{m, 4}, {n, 9}}) == + migraphx::shape{migraphx::shape::float_type, {4, 7}, {12, 1}}); + } + { + // Symbolic start: dim = 8 - n. + auto op = migraphx::make_op("slice", + {{"axes", {0}}, + {"starts", migraphx::value::array{migraphx::to_value(dd{n})}}, + {"ends", {8}}}); + migraphx::shape sin{migraphx::shape::float_type, {dd{lit(10)}, dd{lit(4)}}}; + migraphx::shape sout{ + migraphx::shape::float_type, {dd{lit(8) - n}, dd{lit(4)}}, sin.dyn_strides()}; + expect_shape(sout, op, sin); + EXPECT(sout.symbolic()); + EXPECT(sout.to_static({{n, 3}}) == + migraphx::shape{migraphx::shape::float_type, {5, 4}, {4, 1}}); + } + { + // Both bounds symbolic: dim = n - m. + auto op = migraphx::make_op("slice", + {{"axes", {0}}, + {"starts", migraphx::value::array{migraphx::to_value(dd{m})}}, + {"ends", migraphx::value::array{migraphx::to_value(dd{n})}}}); + migraphx::shape sin{migraphx::shape::float_type, {dd{lit(20)}}}; + migraphx::shape sout{migraphx::shape::float_type, {dd{n - m}}, sin.dyn_strides()}; + expect_shape(sout, op, sin); + EXPECT(sout.symbolic()); + EXPECT(sout.to_static({{m, 3}, {n, 9}}) == + migraphx::shape{migraphx::shape::float_type, {6}, {1}}); + } +} + TEST_CASE(test_scan_slice1) { migraphx::shape input{migraphx::shape::float_type, {2, 3, 4}}; From bbf66e96e489374e093f294dc1c60361af0c58f3 Mon Sep 17 00:00:00 2001 From: Shiv Date: Wed, 8 Jul 2026 10:03:16 -0700 Subject: [PATCH 02/42] fix tests --- test/op_shape_test.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/test/op_shape_test.cpp b/test/op_shape_test.cpp index fbd8107b97d..036be526124 100644 --- a/test/op_shape_test.cpp +++ b/test/op_shape_test.cpp @@ -5410,11 +5410,12 @@ TEST_CASE(slice_sym_symbolic_end_static_input) TEST_CASE(slice_sym_symbolic_bounds) { - auto m = var("m", {1, 16}); - auto n = var("n", {1, 16}); - + // The sliced extent (ends - starts) must be non-negative across the whole variable + // range, so each var range is chosen to keep end >= start. { // Symbolic end on a fixed axis: dim = n - 2; leading axis and strides preserved. + auto m = var("m", {1, 16}); + auto n = var("n", {2, 16}); auto op = migraphx::make_op("slice", {{"axes", {1}}, {"starts", {2}}, @@ -5429,7 +5430,8 @@ TEST_CASE(slice_sym_symbolic_bounds) migraphx::shape{migraphx::shape::float_type, {4, 7}, {12, 1}}); } { - // Symbolic start: dim = 8 - n. + // Symbolic start: dim = 8 - n (n <= 8 keeps the extent non-negative). + auto n = var("n", {1, 8}); auto op = migraphx::make_op("slice", {{"axes", {0}}, {"starts", migraphx::value::array{migraphx::to_value(dd{n})}}, @@ -5443,7 +5445,9 @@ TEST_CASE(slice_sym_symbolic_bounds) migraphx::shape{migraphx::shape::float_type, {5, 4}, {4, 1}}); } { - // Both bounds symbolic: dim = n - m. + // Both bounds symbolic: dim = n - m (ranges disjoint so n >= m always). + auto m = var("m", {1, 5}); + auto n = var("n", {5, 16}); auto op = migraphx::make_op("slice", {{"axes", {0}}, {"starts", migraphx::value::array{migraphx::to_value(dd{m})}}, From 5e7995589e4c401c2f35a978fa1f8e92f7b3a9bd Mon Sep 17 00:00:00 2001 From: Shiv Date: Wed, 8 Jul 2026 10:40:44 -0700 Subject: [PATCH 03/42] licensing and copilot comments --- src/onnx/parse_slice.cpp | 2 +- src/simplify_dyn_ops.cpp | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/onnx/parse_slice.cpp b/src/onnx/parse_slice.cpp index e26ca624449..f09f032c6ac 100644 --- a/src/onnx/parse_slice.cpp +++ b/src/onnx/parse_slice.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 diff --git a/src/simplify_dyn_ops.cpp b/src/simplify_dyn_ops.cpp index b57488c1928..ef82a0a9801 100644 --- a/src/simplify_dyn_ops.cpp +++ b/src/simplify_dyn_ops.cpp @@ -154,6 +154,18 @@ struct find_static_2in_broadcasts : match::supports_dynamic_shapes } }; +// Matches a slice whose set starts/ends bounds are all concrete ints (no +// symbolic dim_like bounds), so the const-input rewrites can read them as ints. +MIGRAPHX_PRED_MATCHER(slice_concrete_bounds, instruction_ref ins) +{ + if(ins->name() != "slice") + return false; + auto slice_op = any_cast(ins->get_operator()); + auto is_int = [](const dim_like& d) { return std::holds_alternative(d); }; + return std::all_of(slice_op.starts.begin(), slice_op.starts.end(), is_int) and + std::all_of(slice_op.ends.begin(), slice_op.ends.end(), is_int); +} + /** * Simplify slice with 2 inputs to the 1 input version if inputs[1] is constant. * From: @@ -165,7 +177,8 @@ struct find_const_2in_slice : match::supports_dynamic_shapes { auto matcher() const { - return match::name("slice")(match::nargs(2), match::arg(1)(match::is_constant())); + return match::name("slice")( + match::nargs(2), match::arg(1)(match::is_constant()), slice_concrete_bounds()); } void apply(module& m, const match::matcher_result& mr) const @@ -221,7 +234,8 @@ struct find_const_3in_slice : match::supports_dynamic_shapes { return match::name("slice")(match::nargs(3), match::arg(1)(match::is_constant()), - match::arg(2)(match::is_constant())); + match::arg(2)(match::is_constant()), + slice_concrete_bounds()); } void apply(module& m, const match::matcher_result& mr) const From ed88b6a15172af131de2771eb44e7d95db1899cc Mon Sep 17 00:00:00 2001 From: Shiv Date: Wed, 8 Jul 2026 13:41:17 -0700 Subject: [PATCH 04/42] tidy --- src/simplify_reshapes.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/simplify_reshapes.cpp b/src/simplify_reshapes.cpp index 5502abd6c05..310dd6dedd8 100644 --- a/src/simplify_reshapes.cpp +++ b/src/simplify_reshapes.cpp @@ -1292,7 +1292,7 @@ struct find_gather return; const std::size_t axis_index = tune_axis(dlens.size(), gather_op.axis, gather_op.name()); - const auto axis_len = dlens.at(axis_index); + const auto axis_len = dlens.at(axis_index); if(axis_len == 0) return; @@ -1852,7 +1852,7 @@ struct find_transpose_slice sdistance.begin(), 0, std::plus<>{}, - [&](auto x, auto d) -> uint64_t { + [&](const auto& x, auto d) -> uint64_t { if(d == 0) return 1; return f(x) % d; From 786f9941e491419c31c91162cc4362b9344da631 Mon Sep 17 00:00:00 2001 From: Shiv Date: Thu, 16 Jul 2026 14:48:53 -0700 Subject: [PATCH 05/42] add attribure normalization for symbolic --- src/include/migraphx/dim_like.hpp | 12 +++++ src/include/migraphx/op/slice.hpp | 50 +++++++----------- src/normalize_attributes.cpp | 88 +++++++++++++++++++++++++++++-- test/op_shape_test.cpp | 22 ++++++-- 4 files changed, 132 insertions(+), 40 deletions(-) diff --git a/src/include/migraphx/dim_like.hpp b/src/include/migraphx/dim_like.hpp index 094e475b1ac..5b89cc4ac90 100644 --- a/src/include/migraphx/dim_like.hpp +++ b/src/include/migraphx/dim_like.hpp @@ -34,6 +34,7 @@ #include #include #include +#include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { @@ -72,6 +73,17 @@ inline std::vector to_ints(const std::vector& dims) return result; } +inline std::vector to_sym_exprs(const std::vector& dims) +{ + std::vector result(dims.size()); + std::transform(dims.begin(), dims.end(), result.begin(), [](const dim_like& d) -> sym::expr { + if(std::holds_alternative(d)) + return std::get(d).sym_expr; + return sym::lit(std::get(d)); + }); + return result; +} + MIGRAPHX_EXPORT void migraphx_to_value(value& v, const dim_like& d); MIGRAPHX_EXPORT void migraphx_from_value(const value& v, dim_like& d); diff --git a/src/include/migraphx/op/slice.hpp b/src/include/migraphx/op/slice.hpp index b8bd1f2806e..d204a79de90 100644 --- a/src/include/migraphx/op/slice.hpp +++ b/src/include/migraphx/op/slice.hpp @@ -255,23 +255,18 @@ struct slice return shape{input_shape.type(), dds}; } - static sym::expr bound_expr(const dim_like& d) - { - if(std::holds_alternative(d)) - return std::get(d).sym_expr; - return sym::lit(std::get(d)); - } - - // Static and symbolic input share one path: promote to a symbolic shape, set - // each sliced axis to the extent ends - starts, carry the kept strides through, - // then demote back to static when the result is fully fixed (slice is a view). + // Static and symbolic inputs share this path; the result is demoted back to + // static when fully fixed (slice is a view). shape symbolic_compute_shape(const shape& s) const { - auto sym_in = s.to_symbolic(); - auto dds = sym_in.dyn_dims(); + assert(starts.size() == axes.size() and ends.size() == axes.size()); + auto sym_in = s.to_symbolic(); + auto dds = sym_in.dyn_dims(); + auto start_exprs = to_sym_exprs(starts); + auto end_exprs = to_sym_exprs(ends); for(std::size_t i = 0; i < axes.size(); ++i) - dds[axes[i]] = shape::dynamic_dimension{bound_expr(ends[i]) - bound_expr(starts[i])}; - shape result{s.type(), dds, sym_in.dyn_strides()}; + dds[axes[i]] = shape::dynamic_dimension{end_exprs[i] - start_exprs[i]}; + shape result{s.type(), std::move(dds), sym_in.dyn_strides()}; if(not s.symbolic() and result.is_fixed()) return result.to_static(); return result; @@ -291,26 +286,17 @@ struct slice if(input_shape.dynamic() and not input_shape.symbolic()) { - if(input_shape.symbolic()) - { - MIGRAPHX_THROW( - "SLICE 1_arg: slicing is not allowed on non-fixed symbolic input axis "); - } - // Attributes are not normalized for this case, so they can be negative or - // out-of-bounds. Using a relaxed dimension bound for now instead of calculating the - // tightest possible bound. - auto dds = input_shape.dyn_dims(); - for(auto axis : this->axes) + // Non-fixed sliced axis: bounds aren't normalized (can be negative or + // out-of-bounds), so use a relaxed [0, max] bound. (#5015) + if(std::any_of(axes.begin(), axes.end(), [&](auto axis) { + return not input_shape.dyn_dims()[axis].is_fixed(); + })) { - dds[axis] = {0, dds[axis].get_interval().max}; + auto dds = input_shape.dyn_dims(); + for(auto axis : axes) + dds[axis] = {0, dds[axis].get_interval().max}; + return shape{input_shape.type(), dds}; } - return shape{input_shape.type(), dds}; - } - - auto new_lens = lens_calc(input_shape.max_lens(), this->starts, this->ends, this->axes); - - if(not input_shape.dynamic()) - return shape{input_shape.type(), new_lens, input_shape.strides()}; auto new_lens = lens_calc(input_shape.max_lens(), to_ints(starts), to_ints(ends), axes); auto dds = input_shape.dyn_dims(); diff --git a/src/normalize_attributes.cpp b/src/normalize_attributes.cpp index 7acca62fc34..7f34340e9c2 100644 --- a/src/normalize_attributes.cpp +++ b/src/normalize_attributes.cpp @@ -25,11 +25,79 @@ #include #include #include +#include +#include +#include #include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { +// min/max that fold to one operand when the ordering is provable via intervals, +// and only fall back to a symbolic min/max node when it is indeterminate. +static sym::expr fold_min(const sym::expr& a, const sym::expr& b) +{ + auto lt = sym::strict_less(a, b); + if(lt.has_value()) + return *lt ? a : b; + return sym::min(a, b); +} +static sym::expr fold_max(const sym::expr& a, const sym::expr& b) +{ + auto lt = sym::strict_less(a, b); + if(lt.has_value()) + return *lt ? b : a; + return sym::max(a, b); +} + +static sym::expr axis_len_expr(const shape& s, int64_t axis) +{ + if(not s.dynamic()) + return sym::lit(static_cast(s.lens().at(axis))); + const auto& dd = s.dyn_dims().at(axis); + if(dd.is_symbolic()) + return dd.sym_expr; + if(dd.is_fixed()) + return sym::lit(static_cast(dd.get_interval().max)); + MIGRAPHX_THROW("normalize_attributes: cannot normalize a symbolic bound on a non-fixed axis"); +} + +static dim_like to_dim_like(const sym::expr& e) +{ + if(e.name() == "literal") + return sym::to(e.eval({})); + return shape::dynamic_dimension{e}; +} + +// Symbolic analog of tune_attribute for dim_like bounds (clip_min/clip_max + +// use_len). Applies the ONNX clamp norm(v) = clamp(v < 0 ? v + D : v, 0, D) +// symbolically, folding against the interval bounds where provable. +template +static value tune_attribute_sym(const std::vector& dims, + const std::vector& axes, + const std::vector& attrs, + const shape& input_shape, + Message m) +{ + if(not contains(attrs, op::normalize_attribute::use_len)) + MIGRAPHX_THROW(m() + "symbolic bounds are only supported with use_len normalization"); + if(axes.size() != dims.size()) + MIGRAPHX_THROW(m() + "symbolic bounds require one axis per bound"); + auto zero = sym::lit(std::int64_t{0}); + auto exprs = to_sym_exprs(dims); + std::vector result(dims.size()); + std::transform( + exprs.begin(), exprs.end(), axes.begin(), result.begin(), [&](const auto& v, auto axis) { + auto len = axis_len_expr(input_shape, axis); + auto neg = sym::strict_less(v, zero); // from-the-end (negative) index? + if(not neg.has_value()) + MIGRAPHX_THROW(m() + "symbolic bound of indeterminate sign cannot be normalized"); + auto abs_v = *neg ? v + len : v; + return to_dim_like(fold_min(fold_max(abs_v, zero), len)); + }); + return migraphx::to_value(result); +} + /** * Parameters: * vec: the vector attribute to normalize @@ -230,15 +298,27 @@ bool normalize_attributes(operation& op, const shape& input_shape) auto vv = val.at(key).without_key(); if(vv.is_array()) { - // Symbolic (dim_like) bounds serialize as objects and cannot be - // clamped against the input length at compile time; leave them as-is. - if(std::any_of(vv.begin(), vv.end(), [](const auto& e) { return e.is_object(); })) - continue; std::vector axes; if(val.contains("axes")) { axes = val.at("axes").without_key().to_vector(); } + // Symbolic (dim_like) bounds serialize as objects; clamp them + // symbolically rather than against a compile-time length. + if(std::any_of(vv.begin(), vv.end(), [](const auto& e) { return e.is_object(); })) + { + auto dims = migraphx::from_value>(vv); + val[key] = + tune_attribute_sym(dims, + axes, + rv.without_key().to_vector(), + input_shape, + message); + op.from_value(val); + val = op.to_value(); + tuned = true; + continue; + } auto vec = vv.to_vector(); auto result = tune_attribute(vec, axes, rv.without_key(), input_shape, message); val[key] = result; diff --git a/test/op_shape_test.cpp b/test/op_shape_test.cpp index 67067922395..ffc2263bfff 100644 --- a/test/op_shape_test.cpp +++ b/test/op_shape_test.cpp @@ -5436,8 +5436,9 @@ TEST_CASE(slice_sym_symbolic_end_static_input) {"starts", {0}}, {"ends", migraphx::value::array{migraphx::to_value(dd{n})}}}); + // end=n is clamped to the axis length 10: dim = min(n, 10). migraphx::shape sin{migraphx::shape::float_type, {10}}; - migraphx::shape sout{migraphx::shape::float_type, {dd{n}}, {lit(1)}}; + migraphx::shape sout{migraphx::shape::float_type, {dd{migraphx::sym::min(n, lit(10))}}, {lit(1)}}; expect_shape(sout, op, sin); EXPECT(sout.symbolic()); EXPECT(not sout.is_fixed()); @@ -5450,7 +5451,7 @@ TEST_CASE(slice_sym_symbolic_bounds) // The sliced extent (ends - starts) must be non-negative across the whole variable // range, so each var range is chosen to keep end >= start. { - // Symbolic end on a fixed axis: dim = n - 2; leading axis and strides preserved. + // Symbolic end clamped to the axis length 12: dim = min(n, 12) - 2. auto m = var("m", {1, 16}); auto n = var("n", {2, 16}); auto op = migraphx::make_op("slice", @@ -5458,14 +5459,27 @@ TEST_CASE(slice_sym_symbolic_bounds) {"starts", {2}}, {"ends", migraphx::value::array{migraphx::to_value(dd{n})}}}); migraphx::shape sin{migraphx::shape::float_type, {dd{m}, dd{lit(12)}}}; - migraphx::shape sout{ - migraphx::shape::float_type, {dd{m}, dd{n - lit(2)}}, sin.dyn_strides()}; + migraphx::shape sout{migraphx::shape::float_type, + {dd{m}, dd{migraphx::sym::min(n, lit(12)) - lit(2)}}, + sin.dyn_strides()}; expect_shape(sout, op, sin); EXPECT(sout.symbolic()); EXPECT(not sout.is_fixed()); EXPECT(sout.to_static({{m, 4}, {n, 9}}) == migraphx::shape{migraphx::shape::float_type, {4, 7}, {12, 1}}); } + { + // Symbolic end provably >= the axis length collapses to the length: extent is concrete. + auto n = var("n", {13, 20}); + auto op = migraphx::make_op("slice", + {{"axes", {0}}, + {"starts", {2}}, + {"ends", migraphx::value::array{migraphx::to_value(dd{n})}}}); + migraphx::shape sin{migraphx::shape::float_type, {dd{lit(12)}, dd{lit(4)}}}; + migraphx::shape sout{ + migraphx::shape::float_type, {dd{lit(10)}, dd{lit(4)}}, sin.dyn_strides()}; + expect_shape(sout, op, sin); + } { // Symbolic start: dim = 8 - n (n <= 8 keeps the extent non-negative). auto n = var("n", {1, 8}); From 13dc8fe69cf51bce811a1eb666a853560b6e5659 Mon Sep 17 00:00:00 2001 From: shivadbhavsar <105248561+shivadbhavsar@users.noreply.github.com> Date: Fri, 17 Jul 2026 08:56:47 -0700 Subject: [PATCH 06/42] Update test/op_shape_test.cpp format Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- test/op_shape_test.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/op_shape_test.cpp b/test/op_shape_test.cpp index ffc2263bfff..2ab738789ba 100644 --- a/test/op_shape_test.cpp +++ b/test/op_shape_test.cpp @@ -5438,7 +5438,8 @@ TEST_CASE(slice_sym_symbolic_end_static_input) // end=n is clamped to the axis length 10: dim = min(n, 10). migraphx::shape sin{migraphx::shape::float_type, {10}}; - migraphx::shape sout{migraphx::shape::float_type, {dd{migraphx::sym::min(n, lit(10))}}, {lit(1)}}; + migraphx::shape sout{ + migraphx::shape::float_type, {dd{migraphx::sym::min(n, lit(10))}}, {lit(1)}}; expect_shape(sout, op, sin); EXPECT(sout.symbolic()); EXPECT(not sout.is_fixed()); From aafa767114c59fd9fe0cd31076b702c204f96769 Mon Sep 17 00:00:00 2001 From: charlie Date: Fri, 17 Jul 2026 13:10:31 -0500 Subject: [PATCH 07/42] inital --- src/include/migraphx/op/slice.hpp | 206 ++++++++++-------------------- 1 file changed, 70 insertions(+), 136 deletions(-) diff --git a/src/include/migraphx/op/slice.hpp b/src/include/migraphx/op/slice.hpp index d204a79de90..80af240b5fa 100644 --- a/src/include/migraphx/op/slice.hpp +++ b/src/include/migraphx/op/slice.hpp @@ -40,52 +40,52 @@ namespace op { /** * Slice operator that accepts variable axes, starts and ends. - * All of `starts`, `ends`, and `axes` must be supplied by either - * their attribute or an input (but not both). + * All of `starts`, `ends`, and `axes` attributes must be supplied. * - * Valid calls: - * slice(input); axes, starts, ends set - * slice(input, starts); axes, ends set - * slice(input, ends); starts, axes set - * slice(input, axes); starts, ends set - * slice(input, starts, ends); axes set - * slice(input, starts, axes); ends set - * slice(input, ends, axes); starts set - * slice(input, start, ends, axes); none set + * `mode` specifies what the inputs to slice are: + * one_input: slice(input); + * starts_input: slice(input, starts); + * ends_input: slice(input, ends); + * axes_input: slice(input, axes); + * starts_ends_input: slice(input, starts, ends); + * starts_axes_input: slice(input, starts, axes); + * ends_axes_input: slice(input, ends, axes); + * starts_ends_axes_input: slice(input, start, ends, axes); * * Attributes: - * axes: constant axes to slice over (optional) - * starts: slice starting indices, constant or symbolic (optional) - * ends: slice ending indices, constant or symbolic (optional) + * axes: axes to slice over + * starts: slice starting indices + * ends: slice ending indices * * Parameters: * data: the input tensor to slice (dynamic or static shape) - * input_starts: starting indices of slice (optional, static shape) - * input_ends: ending indices of slice (optional, static shape) - * input_axes: axes to slice over (optional, static shape) + * starts_input: starting indices of slice (optional, static shape) + * ends_input: ending indices of slice (optional, static shape) + * axes_input: axes to slice over (optional, static shape) */ struct slice { - std::vector axes{}; + enum class slice_mode + { + one_input, + starts_input, + ends_input, + axes_input, + starts_ends_input, + starts_axes_input, + ends_axes_input, + starts_ends_axes_input + }; + + std::vector axes{}; std::vector starts{}; std::vector ends{}; - - /** - * Named arrays for the set attribute possibilities. - */ - static constexpr std::array all_set = {true, true, true}; - static constexpr std::array ends_axes = {false, true, true}; - static constexpr std::array starts_axes = {true, false, true}; - static constexpr std::array starts_ends = {true, true, false}; - static constexpr std::array axes_only = {false, false, true}; - static constexpr std::array ends_only = {false, true, false}; - static constexpr std::array starts_only = {true, false, false}; - static constexpr std::array none_set = {false, false, false}; + slice_mode mode = slice_mode::one_input; template static auto reflect(Self& self, F f) { - return pack(f(self.axes, "axes"), f(self.starts, "starts"), f(self.ends, "ends")); + return pack(f(self.axes, "axes"), f(self.starts, "starts"), f(self.ends, "ends"), f(self.mode, "mode")); } /** @@ -132,17 +132,19 @@ struct slice return new_lens; } - /// Get the attributes that are non-empty - std::array get_set_attributes() const - { - return {not starts.empty(), not ends.empty(), not axes.empty()}; - } - /// Helper function for normalize_compute_shape() - shape compute_two_or_more(std::vector inputs) const + void check_inputs_and_attributes(std::vector inputs) const { - auto input_shape = inputs[0]; - auto set_attributes = get_set_attributes(); + auto input_shape = inputs[0]; + if(axes.size() != starts.size() or starts.size() != ends.size()) + MIGRAPHX_THROW("SLICE: Invalid attributes configuration. Not the same number of dimensions. axes: " + migraphx::to_string(axes.size()) + " starts: " + migraphx::to_string(starts.size() + " ends: " + migraphx::to_string(ends.size()))); + + if(inputs.size() == 1) + { + if(mode != slice_mode::one_input) + MIGRAPHX_THROW("SLICE: Invalid mode for 1 input"); + return; + } // check that inputs [1, end) are all 1D, have the same // dimension, and are static check_shapes{inputs.begin() + 1, @@ -151,108 +153,36 @@ struct slice false} .only_dims(1) .same_dims(); - auto dds = input_shape.to_dynamic().dyn_dims(); + if(inputs.at(1).lens().at(0) != axes.size()) + { + MIGRAPHX_THROW("SLICE: varable input and attributes mismatch: input[1] length (" + + to_string(inputs[1].lens().at(0)) + ") != attribute number of dimensions (" + + to_string(axes.size()) + ")"); + } if(inputs.size() == 2) { - if(set_attributes == ends_axes) + std::vector two_input_modes = {slice_mode::starts_input, slice_mode::ends_input, slice_mode::axes_input}; + if(not contains(two_input_modes, mode)) { - // attr ends and axes set; inputs are (data, input_starts) - if(inputs[1].lens().at(0) != axes.size()) - { - MIGRAPHX_THROW("SLICE: 2 input and attributes mismatch: input_starts length (" + - to_string(inputs[1].lens().at(0)) + ") != number of axes (" + - to_string(axes.size()) + ")"); - } - std::for_each(axes.cbegin(), axes.cend(), [&](const auto& axis) { - dds.at(axis) = {0, dds.at(axis).get_interval().max}; - }); - } - else if(set_attributes == starts_axes) - { - // attr starts and axes set; inputs are (data, input_ends) - if(inputs[1].lens().at(0) != axes.size()) - { - MIGRAPHX_THROW("SLICE: 2 input and attributes mismatch: input_ends length (" + - to_string(inputs[1].lens().at(0)) + ") != number of axes (" + - to_string(axes.size()) + ")"); - } - std::for_each(axes.cbegin(), axes.cend(), [&](const auto& axis) { - dds.at(axis) = {0, dds.at(axis).get_interval().max}; - }); - } - else if(set_attributes == starts_ends) - { - // attr starts and ends set; inputs are (data, input_axes) - if(inputs[1].lens().at(0) != starts.size()) - { - MIGRAPHX_THROW("SLICE: 2 input and attributes mismatch: input_axes length (" + - to_string(inputs[1].lens().at(0)) + ") != number of starts (" + - to_string(starts.size()) + ")"); - } - std::transform(dds.begin(), dds.end(), dds.begin(), [](const auto& dd) { - return shape::dynamic_dimension{0, dd.get_interval().max}; - }); - } - else - { - MIGRAPHX_THROW("SLICE: Invalid 2 input and attributes configuration"); + MIGRAPHX_THROW("SLICE: Invalid mode for 2 inputs"); } } else if(inputs.size() == 3) { - if(set_attributes == axes_only) - { - // attr axes set; inputs are (data, input_starts, input_ends) - if(inputs[1].lens().at(0) != axes.size()) - { - MIGRAPHX_THROW("SLICE: 3 input and attributes mismatch: input_starts length (" + - to_string(inputs[1].lens().at(0)) + ") != number of axes (" + - to_string(axes.size()) + ")"); - } - std::for_each(axes.cbegin(), axes.cend(), [&](const auto& axis) { - dds.at(axis) = {0, dds.at(axis).get_interval().max}; - }); - } - else if(set_attributes == ends_only) - { - // attr ends set; inputs are (data, input_starts, input_axes) - if(inputs[1].lens().at(0) != ends.size()) - { - MIGRAPHX_THROW("SLICE: 3 input and attributes mismatch: input_starts length (" + - to_string(inputs[1].lens().at(0)) + ") != number of ends (" + - to_string(ends.size()) + ")"); - } - std::transform(dds.begin(), dds.end(), dds.begin(), [](const auto& dd) { - return shape::dynamic_dimension{0, dd.get_interval().max}; - }); - } - else if(set_attributes == starts_only) - + std::vector three_input_modes = {slice_mode::starts_ends_input, slice_mode::starts_axes_input, slice_mode::ends_axes_input}; + if(not contains(three_input_modes, mode)) { - // attr starts set; inputs are (data, input_ends, input_axes) - if(inputs[1].lens().at(0) != starts.size()) - { - MIGRAPHX_THROW("SLICE: 3 input and attributes mismatch: input_ends length (" + - to_string(inputs[1].lens().at(0)) + ") != number of starts (" + - to_string(starts.size()) + ")"); - } - std::transform(dds.begin(), dds.end(), dds.begin(), [](const auto& dd) { - return shape::dynamic_dimension{0, dd.get_interval().max}; - }); - } - else - { - MIGRAPHX_THROW("Invalid 3 input and attributes configuration"); + MIGRAPHX_THROW("SLICE: Invalid mode for 3 inputs"); } } else { - // all 4 inputs (data, inputs_starts, input_ends, input_axes) - std::transform(dds.begin(), dds.end(), dds.begin(), [](const auto& dd) { - return shape::dynamic_dimension{0, dd.get_interval().max}; - }); + if(mode != slice_mode::starts_ends_axes_input) + { + MIGRAPHX_THROW("SLICE: Invalid mode for 4 inputs"); + } } - return shape{input_shape.type(), dds}; + return; } // Static and symbolic inputs share this path; the result is demoted back to @@ -276,16 +206,14 @@ struct slice shape normalize_compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1, 2, 3, 4); - if(inputs.size() != 1) - return compute_two_or_more(inputs); - - auto input_shape = inputs[0]; - auto set_attributes = get_set_attributes(); - if(set_attributes != all_set) - MIGRAPHX_THROW("SLICE 1_arg: Invalid 1 input and attributes configuration"); + check_inputs_and_attributes(inputs); if(input_shape.dynamic() and not input_shape.symbolic()) { + if(inputs.size() != 1) + { + MIGRAPHX_THROW("SLICE: range-based dynamic shapes with variable inputs unsupported."); + } // Non-fixed sliced axis: bounds aren't normalized (can be negative or // out-of-bounds), so use a relaxed [0, max] bound. (#5015) if(std::any_of(axes.begin(), axes.end(), [&](auto axis) { @@ -415,6 +343,12 @@ struct slice return {{"norm_starts", norm_starts}, {"norm_ends", norm_ends}, {"norm_axes", norm_axes}}; } + void resolve_data_dependent_symbolics(args, &symbolic_map) + { + // Use data from input arguments to fill in the runtime static dimension for data-dependent symbolic dimensions. + // Call from dyn_output? + } + argument compute(const dyn_output& dyn_out, std::vector args) const { auto input = args[0]; From 37b21c0595ac7c6d487ed36b16bc6a408af85ee0 Mon Sep 17 00:00:00 2001 From: charlie Date: Fri, 17 Jul 2026 15:46:52 -0500 Subject: [PATCH 08/42] Update to use bind_symbolic --- src/include/migraphx/op/bind_symbolic.hpp | 81 +++++++++ src/include/migraphx/op/slice.hpp | 191 +--------------------- src/onnx/parse_nonmaxsuppression.cpp | 18 +- 3 files changed, 93 insertions(+), 197 deletions(-) create mode 100644 src/include/migraphx/op/bind_symbolic.hpp diff --git a/src/include/migraphx/op/bind_symbolic.hpp b/src/include/migraphx/op/bind_symbolic.hpp new file mode 100644 index 00000000000..5cb8a5037e7 --- /dev/null +++ b/src/include/migraphx/op/bind_symbolic.hpp @@ -0,0 +1,81 @@ +/* + * 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_OPERATORS_BIND_SYMBOLIC_HPP +#define MIGRAPHX_GUARD_OPERATORS_BIND_SYMBOLIC_HPP + +#include +#include +#include + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { +namespace op { + +/// Operator used to bind a symbolic variable to input data. Such that +/// at runtime the symbolic variable map will be updated with the value +/// of the input. The operator only updates the symbolic variable map, it +/// is an identity operator otherwise. Used for data-dependent dimensions. +/// +/// bind_symbolic(input) symbols = var("x"), var("y") +/// where input is tensor of shape [2]. +/// at runtime set: +/// var("x") = input.at(0) +/// var("y") = input.at(1) +/// +/// input must be a 1D static tensor and its dimension must match the number of symbols. +struct bind_symbolic +{ + std::vector symbols{}; + + template + static auto reflect(Self& self, F f) + { + return pack(f(self.symbols, "symbols")); + } + + std::string name() const { return "bind_symbolic"; } + + shape compute_shape(std::vector inputs) const + { + check_shapes{inputs, *this}.has(1).only_dims(1); + if(symbols.size() != inputs.lens().at(0)) + MIGRAPHX_THROW("BIND_SYMBOLIC: dimension of input does not match number of symbols."); + return inputs.at(0); + } + + //TODO: have a function that tells how to link up the symbols to the inputs? Or keep it a simple 1 to 1? + + argument compute(shape, std::vector args) const + { + return args[0]; + } + + std::vector output_alias(const std::vector&) const { return {0}; } +}; + +} // namespace op +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx + +#endif diff --git a/src/include/migraphx/op/slice.hpp b/src/include/migraphx/op/slice.hpp index 80af240b5fa..f7d01812ff1 100644 --- a/src/include/migraphx/op/slice.hpp +++ b/src/include/migraphx/op/slice.hpp @@ -208,11 +208,12 @@ struct slice check_shapes{inputs, *this, true}.has(1, 2, 3, 4); check_inputs_and_attributes(inputs); + // fallback for range-based dynamic shapes. Only handling 1 arg case. if(input_shape.dynamic() and not input_shape.symbolic()) { if(inputs.size() != 1) { - MIGRAPHX_THROW("SLICE: range-based dynamic shapes with variable inputs unsupported."); + MIGRAPHX_THROW("SLICE: range-based dynamic input shapes with variable inputs unsupported."); } // Non-fixed sliced axis: bounds aren't normalized (can be negative or // out-of-bounds), so use a relaxed [0, max] bound. (#5015) @@ -265,194 +266,14 @@ struct slice return offset * s.type_size(); } - /** - * Calculates the starting offset for the sliced tensor (for aliasing). - * Used for 2-4 inputs to `slice. - * - * \param s static input shape - * \param input_starts starting indices of slice - * \param ax_vec axes to slice on - */ - template - auto compute_offset(const shape& s, const T& input_starts, const T& ax_vec) const - { - auto ret = 0; - for(std::size_t i = 0; i < ax_vec.size(); ++i) - { - auto axis = ax_vec[i]; - ret += input_starts[i] * s.strides().at(axis); - } - return ret * s.type_size(); - } - - /** - * If given, normalize the inputs. Otherwise get from operator attributes. - * Return the values in a map. - * - * Parameters - * input_shape: static shape of the input - * input_starts: optional - * input_ends: optional - * input_ends: optional - */ - std::unordered_map> - normalize_starts_ends_axes(shape input_shape, - const optional>& input_starts, - const optional>& input_ends, - const optional>& input_axes) const - { - auto axes_attrs = this->attributes().at("normalize_axes"); - std::vector norm_starts; - std::vector norm_ends; - std::vector norm_axes; - if(input_axes) - { - norm_axes = normalize_axes(input_axes.value(), - input_shape, - axes_attrs.at("axes"), - "Slice variable input_axes"); - } - else - { - norm_axes = this->axes; - } - if(input_starts) - { - norm_starts = normalize_indices(input_starts.value(), - norm_axes, - input_shape, - axes_attrs.at("starts"), - "Slice variable input_starts"); - } - else - { - norm_starts = to_ints(this->starts); - } - if(input_ends) - { - norm_ends = normalize_indices(input_ends.value(), - norm_axes, - input_shape, - axes_attrs.at("ends"), - "Slice variable input ends"); - } - else - { - norm_ends = to_ints(this->ends); - } - return {{"norm_starts", norm_starts}, {"norm_ends", norm_ends}, {"norm_axes", norm_axes}}; - } - - void resolve_data_dependent_symbolics(args, &symbolic_map) - { - // Use data from input arguments to fill in the runtime static dimension for data-dependent symbolic dimensions. - // Call from dyn_output? - } - argument compute(const dyn_output& dyn_out, std::vector args) const { auto input = args[0]; auto input_shape = input.get_shape(); - if(args.size() == 1) - { - std::size_t offset = compute_offset(input_shape); - return {dyn_out.computed_shape, [=] { return input.data() + offset; }}; - } - else - { - // Note that we re-normalize both the attributes and inputs because of the non-fixed - // dynamic input shape case. It's possible to only re-normalize if slicing over - // non-fixed dynamic_dimensions. - auto set_attributes = get_set_attributes(); - std::unordered_map> norm_inputs; - if(set_attributes == ends_axes) - { - // attr ends and axes set; inputs are (data, input_starts) - args[1].visit([&](auto input_starts) { - norm_inputs = - normalize_starts_ends_axes(input_shape, - input_starts.template to_vector(), - to_ints(this->ends), - this->axes); - }); - } - else if(set_attributes == starts_axes) - { - // attr starts and axes set; inputs are (data, input_ends) - args[1].visit([&](auto input_ends) { - norm_inputs = - normalize_starts_ends_axes(input_shape, - to_ints(this->starts), - input_ends.template to_vector(), - this->axes); - }); - } - else if(set_attributes == starts_ends) - { - // attr starts and ends set; inputs are (data, input_axes) - args[1].visit([&](auto input_axes) { - norm_inputs = - normalize_starts_ends_axes(input_shape, - to_ints(this->starts), - to_ints(this->ends), - input_axes.template to_vector()); - }); - } - else if(set_attributes == axes_only) - { - // attr axes set; inputs are (data, input_starts, input_ends) - visit_all(args[1], args[2])([&](auto input_starts, auto input_ends) { - norm_inputs = - normalize_starts_ends_axes(input_shape, - input_starts.template to_vector(), - input_ends.template to_vector(), - this->axes); - }); - } - else if(set_attributes == ends_only) - { - // attr ends set; inputs are (data, input_starts, input_axes) - visit_all(args[1], args[2])([&](auto input_starts, auto input_axes) { - norm_inputs = - normalize_starts_ends_axes(input_shape, - input_starts.template to_vector(), - to_ints(this->ends), - input_axes.template to_vector()); - }); - } - else if(set_attributes == starts_only) - { - // attr starts set; inputs are (data, input_ends, input_axes) - visit_all(args[1], args[2])([&](auto input_ends, auto input_axes) { - norm_inputs = - normalize_starts_ends_axes(input_shape, - to_ints(this->starts), - input_ends.template to_vector(), - input_axes.template to_vector()); - }); - } - else - { - // no attr set, all inputs - visit_all(args[1], args[2], args[3])( - [&](auto input_starts, auto input_ends, auto input_axes) { - norm_inputs = - normalize_starts_ends_axes(input_shape, - input_starts.template to_vector(), - input_ends.template to_vector(), - input_axes.template to_vector()); - }); - } - auto offset = compute_offset( - input_shape, norm_inputs.at("norm_starts"), norm_inputs.at("norm_axes")); - shape calc_shape = shape{input_shape.type(), - lens_calc(input_shape.lens(), - norm_inputs.at("norm_starts"), - norm_inputs.at("norm_ends"), - norm_inputs.at("norm_axes")), - input_shape.strides()}; - return {calc_shape, [=] { return input.data() + offset; }}; - } + std::size_t offset = compute_offset(input_shape); + // For dynamic shapes, attributes will be normalized and symbolic dimensions resolved. + // Rerunning comput_shape() from dyn_output should give a static computed_shape. + return {dyn_out.computed_shape, [=] { return input.data() + offset; }}; } std::vector output_alias(const std::vector&) const { return {0}; } diff --git a/src/onnx/parse_nonmaxsuppression.cpp b/src/onnx/parse_nonmaxsuppression.cpp index eb422329f67..42d8b6981a0 100644 --- a/src/onnx/parse_nonmaxsuppression.cpp +++ b/src/onnx/parse_nonmaxsuppression.cpp @@ -45,18 +45,12 @@ struct parse_nonmaxsuppression : op_parser auto nms_ins = info.add_instruction(op, args); // slice with variable ends to handle dynamic shape output. auto indices = info.add_instruction(make_op("get_tuple_elem", {{"index", 0}}), nms_ins); - if(enabled(MIGRAPHX_USE_DYNAMIC_NMS{})) - { - // TODO: planning to make this the default behavior and removing the env var. - auto num_selected = - info.add_instruction(make_op("get_tuple_elem", {{"index", 1}}), nms_ins); - return info.add_instruction( - make_op("slice", {{"axes", {0}}, {"starts", {0}}}), indices, num_selected); - } - else - { - return indices; - } + auto num_selected = + info.add_instruction(make_op("get_tuple_elem", {{"index", 1}}), nms_ins); + // create unique symbolic using node_name from onnx_parser. + auto num_selected_bind = info.add_instruction(make_op("bind_symbolic", {{"symbols", node_name}}), num_selected); + return info.add_instruction( + make_op("slice", {{"axes", {0}}, {"starts", {0}}}), indices, num_selected_bind); } }; From 704874955a9ffe809c795605f9dbb08d42fd8a00 Mon Sep 17 00:00:00 2001 From: charlie Date: Fri, 17 Jul 2026 16:43:10 -0500 Subject: [PATCH 09/42] Add bind_symbolic op and update NMS and slice --- src/CMakeLists.txt | 1 + src/include/migraphx/op/bind_symbolic.hpp | 3 ++- src/include/migraphx/op/slice.hpp | 9 +++++---- src/include/migraphx/operators.hpp | 1 + src/onnx/parse_nonmaxsuppression.cpp | 10 ++++------ test/multi_target/multitarget_test.cpp | 17 +++++++++++++++-- test/onnx/parse/nms_dynamic_batch_test.cpp | 16 +++++++++++++++- test/onnx/parse/nms_dynamic_boxes_test.cpp | 16 +++++++++++++++- test/onnx/parse/nms_dynamic_classes_test.cpp | 16 +++++++++++++++- test/onnx/parse/nms_test.cpp | 16 +++++++++++++++- test/ref/nonmaxsuppression.cpp | 16 +++++++++++++++- 11 files changed, 103 insertions(+), 18 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e1bc74e6a1f..160f95282eb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -194,6 +194,7 @@ register_migraphx_ops( as_shape atanh atan + bind_symbolic bit_cast bitwise_and broadcast diff --git a/src/include/migraphx/op/bind_symbolic.hpp b/src/include/migraphx/op/bind_symbolic.hpp index 5cb8a5037e7..82e7f2beb08 100644 --- a/src/include/migraphx/op/bind_symbolic.hpp +++ b/src/include/migraphx/op/bind_symbolic.hpp @@ -25,6 +25,7 @@ #define MIGRAPHX_GUARD_OPERATORS_BIND_SYMBOLIC_HPP #include +#include #include #include @@ -59,7 +60,7 @@ struct bind_symbolic shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this}.has(1).only_dims(1); - if(symbols.size() != inputs.lens().at(0)) + if(symbols.size() != inputs.at(0).lens().at(0)) MIGRAPHX_THROW("BIND_SYMBOLIC: dimension of input does not match number of symbols."); return inputs.at(0); } diff --git a/src/include/migraphx/op/slice.hpp b/src/include/migraphx/op/slice.hpp index f7d01812ff1..536554dd1a2 100644 --- a/src/include/migraphx/op/slice.hpp +++ b/src/include/migraphx/op/slice.hpp @@ -32,6 +32,7 @@ #include #include #include +#include #include namespace migraphx { @@ -65,8 +66,8 @@ namespace op { */ struct slice { - enum class slice_mode - { + MIGRAPHX_NESTED_ENUM_CLASS( + slice_mode, one_input, starts_input, ends_input, @@ -75,7 +76,7 @@ struct slice starts_axes_input, ends_axes_input, starts_ends_axes_input - }; + ); std::vector axes{}; std::vector starts{}; @@ -207,7 +208,7 @@ struct slice { check_shapes{inputs, *this, true}.has(1, 2, 3, 4); check_inputs_and_attributes(inputs); - + auto input_shape = inputs[0]; // fallback for range-based dynamic shapes. Only handling 1 arg case. if(input_shape.dynamic() and not input_shape.symbolic()) { diff --git a/src/include/migraphx/operators.hpp b/src/include/migraphx/operators.hpp index 1e527c762ff..47683dbc6a5 100644 --- a/src/include/migraphx/operators.hpp +++ b/src/include/migraphx/operators.hpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include diff --git a/src/onnx/parse_nonmaxsuppression.cpp b/src/onnx/parse_nonmaxsuppression.cpp index 42d8b6981a0..30a25e70638 100644 --- a/src/onnx/parse_nonmaxsuppression.cpp +++ b/src/onnx/parse_nonmaxsuppression.cpp @@ -24,9 +24,7 @@ #include #include #include -#include - -MIGRAPHX_DECLARE_ENV_VAR(MIGRAPHX_USE_DYNAMIC_NMS) +#include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { @@ -47,10 +45,10 @@ struct parse_nonmaxsuppression : op_parser auto indices = info.add_instruction(make_op("get_tuple_elem", {{"index", 0}}), nms_ins); auto num_selected = info.add_instruction(make_op("get_tuple_elem", {{"index", 1}}), nms_ins); - // create unique symbolic using node_name from onnx_parser. - auto num_selected_bind = info.add_instruction(make_op("bind_symbolic", {{"symbols", node_name}}), num_selected); + auto num_selected_var = shape::dynamic_dimension{sym::var(info.name)}; + auto num_selected_bind = info.add_instruction(make_op("bind_symbolic", {{"symbols", {to_value(num_selected_var)}}}), num_selected); return info.add_instruction( - make_op("slice", {{"axes", {0}}, {"starts", {0}}}), indices, num_selected_bind); + make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {to_value(num_selected_var)}}, {"mode", "ends_input"}}), indices, num_selected_bind); } }; diff --git a/test/multi_target/multitarget_test.cpp b/test/multi_target/multitarget_test.cpp index 04ba53901d3..c258d0e5563 100644 --- a/test/multi_target/multitarget_test.cpp +++ b/test/multi_target/multitarget_test.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -57,6 +58,7 @@ static auto nonprefixed_ops() "nonmaxsuppression", "multibroadcast", "slice", + "bind_symbolic", "get_tuple_elem"}; return op_map; } @@ -230,8 +232,19 @@ TEST_CASE(single_target_multi_compile) score_threshold); auto idx = gpu_mod->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), nms); auto cnt = gpu_mod->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), nms); - auto r = gpu_mod->add_instruction( - migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}}), idx, cnt); + auto num_selected_var = + migraphx::shape::dynamic_dimension{migraphx::sym::var("nms_num_selected")}; + auto bind = gpu_mod->add_instruction( + migraphx::make_op("bind_symbolic", {{"symbols", {migraphx::to_value(num_selected_var)}}}), + cnt); + auto r = gpu_mod->add_instruction( + migraphx::make_op("slice", + {{"axes", {0}}, + {"starts", {0}}, + {"ends", {migraphx::to_value(num_selected_var)}}, + {"mode", "ends_input"}}), + idx, + bind); gpu_mod->add_return({r}); auto run_on_gpu = mm->add_instruction( diff --git a/test/onnx/parse/nms_dynamic_batch_test.cpp b/test/onnx/parse/nms_dynamic_batch_test.cpp index f9ac10fa4aa..49b460fe582 100644 --- a/test/onnx/parse/nms_dynamic_batch_test.cpp +++ b/test/onnx/parse/nms_dynamic_batch_test.cpp @@ -40,7 +40,21 @@ TEST_CASE(nms_dynamic_batch_test) auto st = mm->add_parameter("score_threshold", sst); auto nms = mm->add_instruction( migraphx::make_op("nonmaxsuppression", {{"center_point_box", true}}), b, s, mo, iou, st); - auto ret = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), nms); + auto idx = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), nms); + auto cnt = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), nms); + auto num_selected_var = + migraphx::shape::dynamic_dimension{migraphx::sym::var("NonMaxSuppression_5")}; + auto bind = mm->add_instruction( + migraphx::make_op("bind_symbolic", {{"symbols", {migraphx::to_value(num_selected_var)}}}), + cnt); + auto ret = mm->add_instruction( + migraphx::make_op("slice", + {{"axes", {0}}, + {"starts", {0}}, + {"ends", {migraphx::to_value(num_selected_var)}}, + {"mode", "ends_input"}}), + idx, + bind); mm->add_return({ret}); migraphx::onnx_options options; diff --git a/test/onnx/parse/nms_dynamic_boxes_test.cpp b/test/onnx/parse/nms_dynamic_boxes_test.cpp index 2b11265d00c..eb84863db70 100644 --- a/test/onnx/parse/nms_dynamic_boxes_test.cpp +++ b/test/onnx/parse/nms_dynamic_boxes_test.cpp @@ -39,7 +39,21 @@ TEST_CASE(nms_dynamic_boxes_test) migraphx::shape sst{migraphx::shape::float_type, {1}}; auto st = mm->add_parameter("score_threshold", sst); auto nms = mm->add_instruction(migraphx::make_op("nonmaxsuppression"), b, s, mo, iou, st); - auto ret = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), nms); + auto idx = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), nms); + auto cnt = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), nms); + auto num_selected_var = + migraphx::shape::dynamic_dimension{migraphx::sym::var("NonMaxSuppression_5")}; + auto bind = mm->add_instruction( + migraphx::make_op("bind_symbolic", {{"symbols", {migraphx::to_value(num_selected_var)}}}), + cnt); + auto ret = mm->add_instruction( + migraphx::make_op("slice", + {{"axes", {0}}, + {"starts", {0}}, + {"ends", {migraphx::to_value(num_selected_var)}}, + {"mode", "ends_input"}}), + idx, + bind); mm->add_return({ret}); migraphx::onnx_options options; diff --git a/test/onnx/parse/nms_dynamic_classes_test.cpp b/test/onnx/parse/nms_dynamic_classes_test.cpp index 8f8a3abd9d2..b0b851193cf 100644 --- a/test/onnx/parse/nms_dynamic_classes_test.cpp +++ b/test/onnx/parse/nms_dynamic_classes_test.cpp @@ -39,7 +39,21 @@ TEST_CASE(nms_dynamic_classes_test) migraphx::shape sst{migraphx::shape::float_type, {1}}; auto st = mm->add_parameter("score_threshold", sst); auto nms = mm->add_instruction(migraphx::make_op("nonmaxsuppression"), b, s, mo, iou, st); - auto ret = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), nms); + auto idx = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), nms); + auto cnt = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), nms); + auto num_selected_var = + migraphx::shape::dynamic_dimension{migraphx::sym::var("NonMaxSuppression_5")}; + auto bind = mm->add_instruction( + migraphx::make_op("bind_symbolic", {{"symbols", {migraphx::to_value(num_selected_var)}}}), + cnt); + auto ret = mm->add_instruction( + migraphx::make_op("slice", + {{"axes", {0}}, + {"starts", {0}}, + {"ends", {migraphx::to_value(num_selected_var)}}, + {"mode", "ends_input"}}), + idx, + bind); mm->add_return({ret}); migraphx::onnx_options options; diff --git a/test/onnx/parse/nms_test.cpp b/test/onnx/parse/nms_test.cpp index 3dbf522b504..5b7c497ab7f 100644 --- a/test/onnx/parse/nms_test.cpp +++ b/test/onnx/parse/nms_test.cpp @@ -45,7 +45,21 @@ TEST_CASE(nms_test) auto nms = mm->add_instruction( migraphx::make_op("nonmaxsuppression", {{"center_point_box", true}}), b, s, mo, iou, st); - auto ret = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), nms); + auto idx = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), nms); + auto cnt = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), nms); + auto num_selected_var = + migraphx::shape::dynamic_dimension{migraphx::sym::var("NonMaxSuppression_5")}; + auto bind = mm->add_instruction( + migraphx::make_op("bind_symbolic", {{"symbols", {migraphx::to_value(num_selected_var)}}}), + cnt); + auto ret = mm->add_instruction( + migraphx::make_op("slice", + {{"axes", {0}}, + {"starts", {0}}, + {"ends", {migraphx::to_value(num_selected_var)}}, + {"mode", "ends_input"}}), + idx, + bind); mm->add_return({ret}); auto prog = read_onnx("nms_test.onnx"); diff --git a/test/ref/nonmaxsuppression.cpp b/test/ref/nonmaxsuppression.cpp index 552f3e2b816..cd9fed0c332 100644 --- a/test/ref/nonmaxsuppression.cpp +++ b/test/ref/nonmaxsuppression.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -35,8 +36,21 @@ static migraphx::instruction_ref add_nms_dynamic_slice(migraphx::module* mm, { auto idx = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), nms); auto cnt = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), nms); + // Bind the runtime num_selected value to a symbolic variable and slice the padded indices + // down to it, matching the IR the NonMaxSuppression ONNX parser emits. + auto num_selected_var = + migraphx::shape::dynamic_dimension{migraphx::sym::var("nms_num_selected")}; + auto bind = mm->add_instruction( + migraphx::make_op("bind_symbolic", {{"symbols", {migraphx::to_value(num_selected_var)}}}), + cnt); return mm->add_instruction( - migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}}), idx, cnt); + migraphx::make_op("slice", + {{"axes", {0}}, + {"starts", {0}}, + {"ends", {migraphx::to_value(num_selected_var)}}, + {"mode", "ends_input"}}), + idx, + bind); } TEST_CASE(nms_dyn_out_test) From f6654e1e4ce089964f770f1e6f220a8949c9a14f Mon Sep 17 00:00:00 2001 From: charlie Date: Mon, 20 Jul 2026 13:19:35 -0500 Subject: [PATCH 10/42] Comment update --- src/include/migraphx/op/slice.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/include/migraphx/op/slice.hpp b/src/include/migraphx/op/slice.hpp index 536554dd1a2..b8ef28e8b11 100644 --- a/src/include/migraphx/op/slice.hpp +++ b/src/include/migraphx/op/slice.hpp @@ -133,7 +133,7 @@ struct slice return new_lens; } - /// Helper function for normalize_compute_shape() + /// Check that the inputs, attributes, and mode are valid. void check_inputs_and_attributes(std::vector inputs) const { auto input_shape = inputs[0]; From 860242a1e6fb813427bd3e971e3599e0420ac72e Mon Sep 17 00:00:00 2001 From: charlie Date: Tue, 21 Jul 2026 13:52:16 -0500 Subject: [PATCH 11/42] TopK changes --- src/include/migraphx/op/topk.hpp | 26 ++++--- src/onnx/parse_topk.cpp | 78 ++++++++++++------- src/rewrite_topk.cpp | 5 +- .../kernels/include/migraphx/kernels/topk.hpp | 5 +- src/targets/gpu/topk.cpp | 8 +- test/onnx/parse/topk_attrk_test.cpp | 4 +- test/onnx/parse/topk_neg_axis_test.cpp | 4 +- test/onnx/parse/topk_test.cpp | 4 +- test/onnx/parse/topk_var_k_test.cpp | 54 ++++++++++--- test/op/builder/torch_kit_test.cpp | 3 +- test/quantization.cpp | 10 ++- test/ref/topk.cpp | 11 ++- test/rewrite_topk.cpp | 42 ++++++---- test/verify/test_topk.cpp | 6 +- test/verify/test_topk_0.cpp | 6 +- test/verify/test_topk_1.cpp | 6 +- test/verify/test_topk_2.cpp | 6 +- test/verify/test_topk_3.cpp | 6 +- test/verify/test_topk_dynamic.cpp | 6 +- 19 files changed, 193 insertions(+), 97 deletions(-) diff --git a/src/include/migraphx/op/topk.hpp b/src/include/migraphx/op/topk.hpp index 521004b170d..e2877f89561 100644 --- a/src/include/migraphx/op/topk.hpp +++ b/src/include/migraphx/op/topk.hpp @@ -33,19 +33,22 @@ #include #include #include +#include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { /** - * TopK with constant `k` value. Significantly different from ONNX spec's TopK. + * TopK with additional input compared to ONNX spec's TopK. + * Valid calls topk(x, k), topk(x, k, indexing); * arg[0]: input data - * arg[1]: optional indexing information used for rewrite_topk + * arg[1]: k value input + * arg[2]: optional indexing information used for rewrite_topk */ struct topk { - int64_t k = 1; + dim_like k = 1; int64_t axis = 0; bool largest = true; @@ -66,16 +69,17 @@ struct topk shape normalize_compute_shape(std::vector inputs) const { - check_shapes{inputs, *this, true}.has(1, 2); - auto type = inputs.at(0).type(); + check_shapes{inputs, *this, true}.has(2, 3); + auto type = inputs.at(0).type(); + auto k_val = std::get(k); if(inputs.at(0).dynamic()) { auto dyn_dims = inputs.at(0).dyn_dims(); auto min_lens_vec = inputs.at(0).min_lens(); auto max_lens_vec = inputs.at(0).max_lens(); - auto min_kk = std::min(k, min_lens_vec[axis]); - auto max_kk = std::min(k, max_lens_vec[axis]); + auto min_kk = std::min(k_val, min_lens_vec[axis]); + auto max_kk = std::min(k_val, max_lens_vec[axis]); dyn_dims[axis] = {min_kk, max_kk}; shape s_val{type, dyn_dims}; @@ -85,7 +89,7 @@ struct topk else { auto lens = inputs.at(0).lens(); - auto kk = std::min(k, lens[axis]); + auto kk = std::min(k_val, lens[axis]); lens[axis] = kk; shape s_val{type, lens}; @@ -114,14 +118,14 @@ struct topk argument res_ind{vec_ss.back()}; auto in_val = args.front(); auto relements = in_val.get_shape().lens()[axis]; - auto actual_k = std::min(k, relements); + auto actual_k = std::min(std::get(k), relements); auto make_indices = [&](const auto& m_idx) { return [&](int64_t i) { - if(args.size() < 2) + if(args.size() < 3) return i; auto j = m_idx; j[axis] = i; - return args[1].at(j); + return args.back().at(j); }; }; auto outer_lens = in_val.get_shape().lens(); diff --git a/src/onnx/parse_topk.cpp b/src/onnx/parse_topk.cpp index 7481ddcb5e7..bb55b7608e6 100644 --- a/src/onnx/parse_topk.cpp +++ b/src/onnx/parse_topk.cpp @@ -27,6 +27,7 @@ #include #include #include +#include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { @@ -53,47 +54,68 @@ struct parse_topk : op_parser axis = parser.parse_value(info.attributes.at("axis")).at(); } - bool var_k = false; - int64_t k = 0; - if(args.size() == 2) + // opset-1 form: `k` is an attribute. Synthesize a constant `k` input so the topk + // operator always has (x, k) inputs. + if(args.size() == 1) { - auto arg_k = args.at(1)->eval(); - if(not arg_k.empty()) + int64_t k = 0; + if(contains(info.attributes, "k")) { - k = arg_k.at(); + k = info.attributes.at("k").i(); } - else - { - var_k = true; - } - } - else if(contains(info.attributes, "k")) - { - k = info.attributes.at("k").i(); + const shape k_shape{shape::int64_type, {1}}; + auto k_lit = info.add_literal(literal{k_shape, {k}}); + auto topk_ret = info.add_instruction( + make_op("topk", {{"k", k}, {"axis", axis}, {"largest", largest}}), args.at(0), k_lit); + + auto ret_val = info.add_instruction(make_op("get_tuple_elem", {{"index", 0}}), topk_ret); + auto ret_ind = info.add_instruction(make_op("get_tuple_elem", {{"index", 1}}), topk_ret); + return {ret_val, ret_ind}; } - if(var_k) + // opset-10+ form: `k` is a runtime input. + auto arg_k = args.at(1)->eval(); + if(not arg_k.empty()) { - // set `k` to axis dimension - auto input_shape = args.at(0)->get_shape(); - auto norm_axis = axis < 0 ? axis + input_shape.ndim() : axis; - k = input_shape.max_lens().at(norm_axis); + // Constant `k`: use its value for the attribute; topk output is already the exact size. + int64_t k = arg_k.at(); + auto topk_ret = info.add_instruction( + make_op("topk", {{"k", k}, {"axis", axis}, {"largest", largest}}), args.at(0), args.at(1)); + + auto ret_val = info.add_instruction(make_op("get_tuple_elem", {{"index", 0}}), topk_ret); + auto ret_ind = info.add_instruction(make_op("get_tuple_elem", {{"index", 1}}), topk_ret); + return {ret_val, ret_ind}; } + // Variable (data-dependent) `k`: run topk over the whole axis dimension, bind the runtime + // `k` to a symbolic dimension, then slice the outputs down to it. + auto input_shape = args.at(0)->get_shape(); + auto norm_axis = axis < 0 ? axis + input_shape.ndim() : axis; + int64_t k = input_shape.max_lens().at(norm_axis); + auto topk_ret = info.add_instruction( - make_op("topk", {{"k", k}, {"axis", axis}, {"largest", largest}}), args.at(0)); + make_op("topk", {{"k", k}, {"axis", axis}, {"largest", largest}}), args.at(0), args.at(1)); auto ret_val = info.add_instruction(make_op("get_tuple_elem", {{"index", 0}}), topk_ret); auto ret_ind = info.add_instruction(make_op("get_tuple_elem", {{"index", 1}}), topk_ret); - if(var_k) - { - // dynamic slice on outputs of `topk` - ret_val = info.add_instruction( - make_op("slice", {{"starts", {0}}, {"axes", {axis}}}), ret_val, args.at(1)); - ret_ind = info.add_instruction( - make_op("slice", {{"starts", {0}}, {"axes", {axis}}}), ret_ind, args.at(1)); - } + auto k_var = shape::dynamic_dimension{sym::var(info.name)}; + auto k_bind = info.add_instruction( + make_op("bind_symbolic", {{"symbols", {to_value(k_var)}}}), args.at(1)); + ret_val = info.add_instruction(make_op("slice", + {{"axes", {axis}}, + {"starts", {0}}, + {"ends", {to_value(k_var)}}, + {"mode", "ends_input"}}), + ret_val, + k_bind); + ret_ind = info.add_instruction(make_op("slice", + {{"axes", {axis}}, + {"starts", {0}}, + {"ends", {to_value(k_var)}}, + {"mode", "ends_input"}}), + ret_ind, + k_bind); return {ret_val, ret_ind}; } diff --git a/src/rewrite_topk.cpp b/src/rewrite_topk.cpp index 8597d55c331..dfca3db0b6d 100644 --- a/src/rewrite_topk.cpp +++ b/src/rewrite_topk.cpp @@ -43,6 +43,7 @@ struct find_large_topk { auto ins = r.result; auto input = ins->inputs().front(); + auto k_ins = ins->inputs().at(1); auto op = ins->get_operator().to_value(); auto axis = op["axis"].to(); auto dims = input->get_shape().lens(); @@ -75,10 +76,10 @@ struct find_large_topk ins, make_op("broadcast", {{"axis", axis}, {"out_lens", dims}}), indices_lit); auto gindices = m.insert_instruction(ins, make_op("reshape", {{"dims", gdims}}), indices); auto ginput = m.insert_instruction(ins, make_op("reshape", {{"dims", gdims}}), input); - auto topk1 = m.insert_instruction(ins, make_op("topk", op), ginput, gindices); + auto topk1 = m.insert_instruction(ins, make_op("topk", op), ginput, k_ins, gindices); auto finput = insert_final(topk1, 0); auto findices = insert_final(topk1, 1); - m.replace_instruction(ins, ins->get_operator(), finput, findices); + m.replace_instruction(ins, ins->get_operator(), finput, k_ins, findices); } }; diff --git a/src/targets/gpu/kernels/include/migraphx/kernels/topk.hpp b/src/targets/gpu/kernels/include/migraphx/kernels/topk.hpp index d771fab6b65..fac609e26b3 100644 --- a/src/targets/gpu/kernels/include/migraphx/kernels/topk.hpp +++ b/src/targets/gpu/kernels/include/migraphx/kernels/topk.hpp @@ -209,7 +209,10 @@ topk_impl(index idx, Compare compare, T init, Y y, YIndex y_idx, X x, XIndices.. template __device__ auto topk(Compare compare, T init) { - return [=](auto output, auto out_indices, auto input, auto... in_indices) { + // The `k` input is consumed positionally and ignored here: compile_ops bakes a constant + // `k` attribute into the output shape, so the trailing `in_indices` (if any) is the + // optional indexing input used by rewrite_topk. + return [=](auto output, auto out_indices, auto input, auto, auto... in_indices) { auto idx = make_index(); slice_schedule(idx, slice_axes())(output, out_indices, input, in_indices...)( diff --git a/src/targets/gpu/topk.cpp b/src/targets/gpu/topk.cpp index 2e799c650af..05466a8aca1 100644 --- a/src/targets/gpu/topk.cpp +++ b/src/targets/gpu/topk.cpp @@ -31,23 +31,25 @@ namespace gpu { shape hip_topk::compute_shape(std::vector inputs) const { - return op.normalize_compute_shape({inputs.front()}); + // Drop the trailing output-allocation input; topk needs the (x, k, [indexing]) inputs. + return op.normalize_compute_shape({inputs.begin(), inputs.end() - 1}); } argument hip_topk::compute(context& ctx, const shape&, const std::vector& args) const { auto outputs = args.back().get_sub_objects(); + auto k_val = std::get(op.k); return op.largest ? device::topk_largest(ctx.get_stream().get(), outputs.front(), outputs.back(), args[0], - op.k, + k_val, op.axis) : device::topk_smallest(ctx.get_stream().get(), outputs.front(), outputs.back(), args[0], - op.k, + k_val, op.axis); } diff --git a/test/onnx/parse/topk_attrk_test.cpp b/test/onnx/parse/topk_attrk_test.cpp index 6f8f6f3b9fc..d8a3fd62cd9 100644 --- a/test/onnx/parse/topk_attrk_test.cpp +++ b/test/onnx/parse/topk_attrk_test.cpp @@ -30,7 +30,9 @@ TEST_CASE(topk_attrk_test) auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {2, 5, 3, 2}}; auto data = mm->add_parameter("data", s); - auto out = mm->add_instruction(migraphx::make_op("topk", {{"k", 2}, {"axis", -1}}), data); + auto k = mm->add_literal( + migraphx::literal{migraphx::shape{migraphx::shape::int64_type, {1}}, {2}}); + auto out = mm->add_instruction(migraphx::make_op("topk", {{"k", 2}, {"axis", -1}}), data, k); auto val = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), out); auto ind = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), out); mm->add_return({val, ind}); diff --git a/test/onnx/parse/topk_neg_axis_test.cpp b/test/onnx/parse/topk_neg_axis_test.cpp index 882bf43ecca..99ef3ab93ab 100644 --- a/test/onnx/parse/topk_neg_axis_test.cpp +++ b/test/onnx/parse/topk_neg_axis_test.cpp @@ -29,11 +29,11 @@ TEST_CASE(topk_neg_axis_test) migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sk{migraphx::shape::int64_type, {1}}; - mm->add_literal(migraphx::literal(sk, {3})); + auto k = mm->add_literal(migraphx::literal(sk, {3})); migraphx::shape s{migraphx::shape::float_type, {3, 4, 5, 6}}; auto data = mm->add_parameter("data", s); auto out = mm->add_instruction( - migraphx::make_op("topk", {{"k", 3}, {"axis", -2}, {"largest", 1}}), data); + migraphx::make_op("topk", {{"k", 3}, {"axis", -2}, {"largest", 1}}), data, k); auto val = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), out); auto ind = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), out); mm->add_return({val, ind}); diff --git a/test/onnx/parse/topk_test.cpp b/test/onnx/parse/topk_test.cpp index 4ac45c73df2..9af389a8d78 100644 --- a/test/onnx/parse/topk_test.cpp +++ b/test/onnx/parse/topk_test.cpp @@ -29,11 +29,11 @@ TEST_CASE(topk_test) migraphx::program p; auto* mm = p.get_main_module(); migraphx::shape sk{migraphx::shape::int64_type, {1}}; - mm->add_literal(migraphx::literal(sk, {4})); + auto k = mm->add_literal(migraphx::literal(sk, {4})); migraphx::shape s{migraphx::shape::float_type, {2, 5, 3, 2}}; auto data = mm->add_parameter("data", s); auto out = mm->add_instruction( - migraphx::make_op("topk", {{"k", 4}, {"axis", 1}, {"largest", 0}}), data); + migraphx::make_op("topk", {{"k", 4}, {"axis", 1}, {"largest", 0}}), data, k); auto val = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), out); auto ind = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), out); mm->add_return({val, ind}); diff --git a/test/onnx/parse/topk_var_k_test.cpp b/test/onnx/parse/topk_var_k_test.cpp index 2338236abe0..49716089825 100644 --- a/test/onnx/parse/topk_var_k_test.cpp +++ b/test/onnx/parse/topk_var_k_test.cpp @@ -25,8 +25,8 @@ #include // `k` is a runtime input (graph input, not an initializer), so the parser takes the var_k -// path: topk runs with k set to the axis dimension, then the outputs are sliced down to the -// runtime `k`. +// path: topk runs with k set to the axis dimension's max length, the runtime `k` is bound to a +// symbolic dimension, then the outputs are sliced down to it. TEST_CASE(topk_var_k_test) { migraphx::program p; @@ -34,11 +34,26 @@ TEST_CASE(topk_var_k_test) auto data = mm->add_parameter("data", {migraphx::shape::float_type, {2, 4}}); auto k = mm->add_parameter("k", {migraphx::shape::int64_type, {1}}); auto out = mm->add_instruction( - migraphx::make_op("topk", {{"k", 4}, {"axis", 1}, {"largest", 1}}), data); - auto val = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), out); - auto ind = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), out); - val = mm->add_instruction(migraphx::make_op("slice", {{"starts", {0}}, {"axes", {1}}}), val, k); - ind = mm->add_instruction(migraphx::make_op("slice", {{"starts", {0}}, {"axes", {1}}}), ind, k); + migraphx::make_op("topk", {{"k", 4}, {"axis", 1}, {"largest", 1}}), data, k); + auto val = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), out); + auto ind = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), out); + auto k_var = migraphx::shape::dynamic_dimension{migraphx::sym::var("TopK_2")}; + auto bind = mm->add_instruction( + migraphx::make_op("bind_symbolic", {{"symbols", {migraphx::to_value(k_var)}}}), k); + val = mm->add_instruction(migraphx::make_op("slice", + {{"axes", {1}}, + {"starts", {0}}, + {"ends", {migraphx::to_value(k_var)}}, + {"mode", "ends_input"}}), + val, + bind); + ind = mm->add_instruction(migraphx::make_op("slice", + {{"axes", {1}}, + {"starts", {0}}, + {"ends", {migraphx::to_value(k_var)}}, + {"mode", "ends_input"}}), + ind, + bind); mm->add_return({val, ind}); auto prog = read_onnx("topk_var_k_test.onnx"); @@ -55,11 +70,26 @@ TEST_CASE(topk_var_k_dynamic_test) auto data = mm->add_parameter("data", {migraphx::shape::float_type, {{1, 4}, {2, 4}}}); auto k = mm->add_parameter("k", {migraphx::shape::int64_type, {1}}); auto out = mm->add_instruction( - migraphx::make_op("topk", {{"k", 4}, {"axis", 1}, {"largest", 1}}), data); - auto val = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), out); - auto ind = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), out); - val = mm->add_instruction(migraphx::make_op("slice", {{"starts", {0}}, {"axes", {1}}}), val, k); - ind = mm->add_instruction(migraphx::make_op("slice", {{"starts", {0}}, {"axes", {1}}}), ind, k); + migraphx::make_op("topk", {{"k", 4}, {"axis", 1}, {"largest", 1}}), data, k); + auto val = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), out); + auto ind = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), out); + auto k_var = migraphx::shape::dynamic_dimension{migraphx::sym::var("TopK_2")}; + auto bind = mm->add_instruction( + migraphx::make_op("bind_symbolic", {{"symbols", {migraphx::to_value(k_var)}}}), k); + val = mm->add_instruction(migraphx::make_op("slice", + {{"axes", {1}}, + {"starts", {0}}, + {"ends", {migraphx::to_value(k_var)}}, + {"mode", "ends_input"}}), + val, + bind); + ind = mm->add_instruction(migraphx::make_op("slice", + {{"axes", {1}}, + {"starts", {0}}, + {"ends", {migraphx::to_value(k_var)}}, + {"mode", "ends_input"}}), + ind, + bind); mm->add_return({val, ind}); migraphx::onnx_options options; diff --git a/test/op/builder/torch_kit_test.cpp b/test/op/builder/torch_kit_test.cpp index ea207e86f0b..fec515c9ab9 100644 --- a/test/op/builder/torch_kit_test.cpp +++ b/test/op/builder/torch_kit_test.cpp @@ -278,7 +278,8 @@ TEST_CASE(torch_kit_ops_op_builder_test) EXPECT(check_plain_op("softmax", {{"axis", 1}}, {{"a", {f, {4, 6}}}})); EXPECT(check_plain_op("squeeze", {{"axes", {0}}}, {{"a", {f, {1, 4, 6}}}})); EXPECT(check_plain_op("step", {{"axes", {0}}, {"steps", {2}}}, {{"a", {f, {4, 6}}}})); - EXPECT(check_plain_op("topk", {{"k", 2}, {"axis", 0}}, {{"a", {f, {4, 6}}}})); + EXPECT( + check_plain_op("topk", {{"k", 2}, {"axis", 0}}, {{"a", {f, {4, 6}}}, {"k", {i64, {1}}}})); EXPECT(check_plain_op("transpose", {{"permutation", {1, 0}}}, {{"a", {f, {4, 6}}}})); EXPECT(check_plain_op("undefined", obj, {})); EXPECT(check_plain_op("unsqueeze", {{"axes", {0}}}, {{"a", {f, {4, 6}}}})); diff --git a/test/quantization.cpp b/test/quantization.cpp index a33c88badec..e12c2de44ac 100644 --- a/test/quantization.cpp +++ b/test/quantization.cpp @@ -446,8 +446,10 @@ TEST_CASE(topk) auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 5}}; auto data = mm->add_parameter("data", s); - auto r = mm->add_instruction( - migraphx::make_op("topk", {{"axis", 0}, {"k", 3}, {"largest", 0}}), data); + auto kk = mm->add_literal( + migraphx::literal{migraphx::shape{migraphx::shape::int64_type, {1}}, {3}}); + auto r = mm->add_instruction( + migraphx::make_op("topk", {{"axis", 0}, {"k", 3}, {"largest", 0}}), data, kk); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); auto r1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r); mm->add_return({r0, r1}); @@ -460,10 +462,12 @@ TEST_CASE(topk) auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 5}}; auto fdata = mm->add_parameter("data", s); + auto kk = mm->add_literal( + migraphx::literal{migraphx::shape{migraphx::shape::int64_type, {1}}, {3}}); auto hdata = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), fdata); auto r = mm->add_instruction( - migraphx::make_op("topk", {{"axis", 0}, {"k", 3}, {"largest", 0}}), hdata); + migraphx::make_op("topk", {{"axis", 0}, {"k", 3}, {"largest", 0}}), hdata, kk); auto hr0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); auto fr0 = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::float_type}}), hr0); diff --git a/test/ref/topk.cpp b/test/ref/topk.cpp index 3f71f4f8879..535bb6ecd05 100644 --- a/test/ref/topk.cpp +++ b/test/ref/topk.cpp @@ -37,6 +37,9 @@ static auto run_program(const migraphx::value& op, bool custom_idx = false, bool migraphx::shape s{migraphx::shape::float_type, {3, 5}}; auto data = mm->add_parameter("data", s); std::vector topk_inputs = {data}; + auto k_val = op.at("k").to(); + topk_inputs.push_back( + mm->add_literal(migraphx::literal{migraphx::shape{migraphx::shape::int64_type, {1}}, {k_val}})); if(custom_idx) { migraphx::shape is{migraphx::shape::uint16_type, {3, 5}}; @@ -155,9 +158,11 @@ TEST_CASE(topk_k_greater_than_n_dynamic) std::vector dds = {{1, 100}}; migraphx::shape s{migraphx::shape::float_type, dds}; auto data = mm->add_parameter("data", s); + auto kk = mm->add_literal( + migraphx::literal{migraphx::shape{migraphx::shape::int64_type, {1}}, {100}}); // k=100 is the max placeholder from parse time auto r = mm->add_instruction( - migraphx::make_op("topk", {{"axis", 0}, {"k", 100}, {"largest", 1}}), data); + migraphx::make_op("topk", {{"axis", 0}, {"k", 100}, {"largest", 1}}), data, kk); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); auto r1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r); mm->add_return({r0, r1}); @@ -191,9 +196,11 @@ TEST_CASE(topk_k_equals_n) auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 5}}; auto data = mm->add_parameter("data", s); + auto kk = mm->add_literal( + migraphx::literal{migraphx::shape{migraphx::shape::int64_type, {1}}, {5}}); // k=5 equals axis=1 dimension of 5 auto r = mm->add_instruction(migraphx::make_op("topk", {{"axis", 1}, {"k", 5}, {"largest", 0}}), - data); + data, kk); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); auto r1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r); mm->add_return({r0, r1}); diff --git a/test/rewrite_topk.cpp b/test/rewrite_topk.cpp index 4d29aac52b5..22a338a7a3a 100644 --- a/test/rewrite_topk.cpp +++ b/test/rewrite_topk.cpp @@ -39,7 +39,8 @@ TEST_CASE(small_topk) migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {300}}); - auto r = m1.add_instruction(migraphx::make_op("topk", {{"k", 8}}), x); + auto k = m1.add_literal(migraphx::literal{{migraphx::shape::int64_type, {1}}, {8}}); + auto r = m1.add_instruction(migraphx::make_op("topk", {{"k", 8}}), x, k); m1.add_return({r}); } migraphx::module m2 = m1; @@ -52,7 +53,8 @@ TEST_CASE(large_topk_no_split) migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {240000}}); - auto r = m1.add_instruction(migraphx::make_op("topk", {{"k", 120000}}), x); + auto k = m1.add_literal(migraphx::literal{{migraphx::shape::int64_type, {1}}, {120000}}); + auto r = m1.add_instruction(migraphx::make_op("topk", {{"k", 120000}}), x, k); m1.add_return({r}); } migraphx::module m2 = m1; @@ -66,7 +68,8 @@ TEST_CASE(split_topk_batch_1) migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {n}}); - auto r = m1.add_instruction(migraphx::make_op("topk", {{"k", 8}}), x); + auto k = m1.add_literal(migraphx::literal{{migraphx::shape::int64_type, {1}}, {8}}); + auto r = m1.add_instruction(migraphx::make_op("topk", {{"k", 8}}), x, k); m1.add_return({r}); } run_pass(m1); @@ -76,6 +79,7 @@ TEST_CASE(split_topk_batch_1) std::vector indices(n); std::iota(indices.begin(), indices.end(), 0); auto x = m2.add_parameter("x", {migraphx::shape::float_type, {n}}); + auto k = m2.add_literal(migraphx::literal{{migraphx::shape::int64_type, {1}}, {8}}); auto input_idx = m2.add_literal(migraphx::literal{{migraphx::shape::uint32_type, {n}}, indices}); auto input_idxb = m2.add_instruction( @@ -84,15 +88,15 @@ TEST_CASE(split_topk_batch_1) migraphx::make_op("reshape", {{"dims", {group, n / group}}}), input_idxb); auto xr = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {group, n / group}}}), x); - auto r1 = - m2.add_instruction(migraphx::make_op("topk", {{"k", 8}, {"axis", 1}}), xr, input_idxr); + auto r1 = m2.add_instruction( + migraphx::make_op("topk", {{"k", 8}, {"axis", 1}}), xr, k, input_idxr); auto value1 = m2.add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r1); auto idx1 = m2.add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r1); auto valuer = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {8 * group}}}), value1); auto idxr = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {8 * group}}}), idx1); - auto r2 = - m2.add_instruction(migraphx::make_op("topk", {{"k", 8}, {"axis", 0}}), valuer, idxr); + auto r2 = m2.add_instruction( + migraphx::make_op("topk", {{"k", 8}, {"axis", 0}}), valuer, k, idxr); m2.add_return({r2}); } EXPECT(m1.sort() == m2.sort()); @@ -105,7 +109,8 @@ TEST_CASE(split_topk_batch_64) migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {batch, n}}); - auto r = m1.add_instruction(migraphx::make_op("topk", {{"k", 8}, {"axis", 1}}), x); + auto k = m1.add_literal(migraphx::literal{{migraphx::shape::int64_type, {1}}, {8}}); + auto r = m1.add_instruction(migraphx::make_op("topk", {{"k", 8}, {"axis", 1}}), x, k); m1.add_return({r}); } run_pass(m1); @@ -115,6 +120,7 @@ TEST_CASE(split_topk_batch_64) std::vector indices(n); std::iota(indices.begin(), indices.end(), 0); auto x = m2.add_parameter("x", {migraphx::shape::float_type, {batch, n}}); + auto k = m2.add_literal(migraphx::literal{{migraphx::shape::int64_type, {1}}, {8}}); auto input_idx = m2.add_literal(migraphx::literal{{migraphx::shape::uint32_type, {n}}, indices}); auto input_idxb = m2.add_instruction( @@ -123,16 +129,16 @@ TEST_CASE(split_topk_batch_64) migraphx::make_op("reshape", {{"dims", {batch, group, n / group}}}), input_idxb); auto xr = m2.add_instruction( migraphx::make_op("reshape", {{"dims", {batch, group, n / group}}}), x); - auto r1 = - m2.add_instruction(migraphx::make_op("topk", {{"k", 8}, {"axis", 2}}), xr, input_idxr); + auto r1 = m2.add_instruction( + migraphx::make_op("topk", {{"k", 8}, {"axis", 2}}), xr, k, input_idxr); auto value1 = m2.add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r1); auto idx1 = m2.add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r1); auto valuer = m2.add_instruction( migraphx::make_op("reshape", {{"dims", {batch, 8 * group}}}), value1); auto idxr = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {batch, 8 * group}}}), idx1); - auto r2 = - m2.add_instruction(migraphx::make_op("topk", {{"k", 8}, {"axis", 1}}), valuer, idxr); + auto r2 = m2.add_instruction( + migraphx::make_op("topk", {{"k", 8}, {"axis", 1}}), valuer, k, idxr); m2.add_return({r2}); } EXPECT(m1.sort() == m2.sort()); @@ -145,7 +151,8 @@ TEST_CASE(split_topk_batch_64_last) migraphx::module m1; { auto x = m1.add_parameter("x", {migraphx::shape::float_type, {n, batch}}); - auto r = m1.add_instruction(migraphx::make_op("topk", {{"k", 8}, {"axis", 0}}), x); + auto k = m1.add_literal(migraphx::literal{{migraphx::shape::int64_type, {1}}, {8}}); + auto r = m1.add_instruction(migraphx::make_op("topk", {{"k", 8}, {"axis", 0}}), x, k); m1.add_return({r}); } run_pass(m1); @@ -155,6 +162,7 @@ TEST_CASE(split_topk_batch_64_last) std::vector indices(n); std::iota(indices.begin(), indices.end(), 0); auto x = m2.add_parameter("x", {migraphx::shape::float_type, {n, batch}}); + auto k = m2.add_literal(migraphx::literal{{migraphx::shape::int64_type, {1}}, {8}}); auto input_idx = m2.add_literal(migraphx::literal{{migraphx::shape::uint32_type, {n}}, indices}); auto input_idxb = m2.add_instruction( @@ -163,16 +171,16 @@ TEST_CASE(split_topk_batch_64_last) migraphx::make_op("reshape", {{"dims", {group, n / group, batch}}}), input_idxb); auto xr = m2.add_instruction( migraphx::make_op("reshape", {{"dims", {group, n / group, batch}}}), x); - auto r1 = - m2.add_instruction(migraphx::make_op("topk", {{"k", 8}, {"axis", 1}}), xr, input_idxr); + auto r1 = m2.add_instruction( + migraphx::make_op("topk", {{"k", 8}, {"axis", 1}}), xr, k, input_idxr); auto value1 = m2.add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r1); auto idx1 = m2.add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r1); auto valuer = m2.add_instruction( migraphx::make_op("reshape", {{"dims", {8 * group, batch}}}), value1); auto idxr = m2.add_instruction(migraphx::make_op("reshape", {{"dims", {8 * group, batch}}}), idx1); - auto r2 = - m2.add_instruction(migraphx::make_op("topk", {{"k", 8}, {"axis", 0}}), valuer, idxr); + auto r2 = m2.add_instruction( + migraphx::make_op("topk", {{"k", 8}, {"axis", 0}}), valuer, k, idxr); m2.add_return({r2}); } EXPECT(m1.sort() == m2.sort()); diff --git a/test/verify/test_topk.cpp b/test/verify/test_topk.cpp index af001797aa8..92809e2a99a 100644 --- a/test/verify/test_topk.cpp +++ b/test/verify/test_topk.cpp @@ -39,8 +39,10 @@ struct test_topk : verify_program> unsigned int batch = 3; migraphx::shape s{DType, {batch, N}}; auto x1 = mm->add_parameter("x1", s); - auto r = mm->add_instruction( - migraphx::make_op("topk", {{"axis", 1}, {"k", K}, {"largest", 1}}), x1); + auto kk = mm->add_literal( + migraphx::literal{migraphx::shape{migraphx::shape::int64_type, {1}}, {K}}); + auto r = mm->add_instruction( + migraphx::make_op("topk", {{"axis", 1}, {"k", K}, {"largest", 1}}), x1, kk); auto values = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); auto indices = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r); mm->add_return({values, indices}); diff --git a/test/verify/test_topk_0.cpp b/test/verify/test_topk_0.cpp index 675d67efbcd..ed1cd51354d 100644 --- a/test/verify/test_topk_0.cpp +++ b/test/verify/test_topk_0.cpp @@ -36,8 +36,10 @@ struct test_topk_0 : verify_program> auto* mm = p.get_main_module(); migraphx::shape s{DType, {3, 5}}; auto data = mm->add_parameter("data", s); - auto r = mm->add_instruction( - migraphx::make_op("topk", {{"axis", 1}, {"k", 4}, {"largest", 1}}), data); + auto kk = mm->add_literal( + migraphx::literal{migraphx::shape{migraphx::shape::int64_type, {1}}, {4}}); + auto r = mm->add_instruction( + migraphx::make_op("topk", {{"axis", 1}, {"k", 4}, {"largest", 1}}), data, kk); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); mm->add_return({r0}); diff --git a/test/verify/test_topk_1.cpp b/test/verify/test_topk_1.cpp index 3e5d0bf331f..9d98131a23d 100644 --- a/test/verify/test_topk_1.cpp +++ b/test/verify/test_topk_1.cpp @@ -35,8 +35,10 @@ struct test_topk_1 : verify_program auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 5}}; auto data = mm->add_parameter("data", s); - auto r = mm->add_instruction( - migraphx::make_op("topk", {{"axis", -2}, {"k", 3}, {"largest", 1}}), data); + auto kk = mm->add_literal( + migraphx::literal{migraphx::shape{migraphx::shape::int64_type, {1}}, {3}}); + auto r = mm->add_instruction( + migraphx::make_op("topk", {{"axis", -2}, {"k", 3}, {"largest", 1}}), data, kk); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); auto r1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r); mm->add_return({r0, r1}); diff --git a/test/verify/test_topk_2.cpp b/test/verify/test_topk_2.cpp index 44f0283579e..ebde18d88b1 100644 --- a/test/verify/test_topk_2.cpp +++ b/test/verify/test_topk_2.cpp @@ -35,8 +35,10 @@ struct test_topk_2 : verify_program auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 5}}; auto data = mm->add_parameter("data", s); - auto r = mm->add_instruction( - migraphx::make_op("topk", {{"axis", 1}, {"k", 4}, {"largest", 0}}), data); + auto kk = mm->add_literal( + migraphx::literal{migraphx::shape{migraphx::shape::int64_type, {1}}, {4}}); + auto r = mm->add_instruction( + migraphx::make_op("topk", {{"axis", 1}, {"k", 4}, {"largest", 0}}), data, kk); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); mm->add_return({r0}); diff --git a/test/verify/test_topk_3.cpp b/test/verify/test_topk_3.cpp index 44d15f9b22d..26ec829e17a 100644 --- a/test/verify/test_topk_3.cpp +++ b/test/verify/test_topk_3.cpp @@ -35,8 +35,10 @@ struct test_topk_3 : verify_program auto* mm = p.get_main_module(); migraphx::shape s{migraphx::shape::float_type, {3, 5}}; auto data = mm->add_parameter("data", s); - auto r = mm->add_instruction( - migraphx::make_op("topk", {{"axis", -2}, {"k", 3}, {"largest", 0}}), data); + auto kk = mm->add_literal( + migraphx::literal{migraphx::shape{migraphx::shape::int64_type, {1}}, {3}}); + auto r = mm->add_instruction( + migraphx::make_op("topk", {{"axis", -2}, {"k", 3}, {"largest", 0}}), data, kk); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); auto r1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r); mm->add_return({r0, r1}); diff --git a/test/verify/test_topk_dynamic.cpp b/test/verify/test_topk_dynamic.cpp index 25b0836df00..0bdb4a853dd 100644 --- a/test/verify/test_topk_dynamic.cpp +++ b/test/verify/test_topk_dynamic.cpp @@ -38,8 +38,10 @@ struct test_topk_dynamic : verify_program> std::vector dds = {{1, 100}}; migraphx::shape s{migraphx::shape::float_type, dds}; auto data = mm->add_parameter("data", s); - auto r = mm->add_instruction( - migraphx::make_op("topk", {{"axis", 0}, {"k", 100}, {"largest", 1}}), data); + auto kk = mm->add_literal( + migraphx::literal{migraphx::shape{migraphx::shape::int64_type, {1}}, {100}}); + auto r = mm->add_instruction( + migraphx::make_op("topk", {{"axis", 0}, {"k", 100}, {"largest", 1}}), data, kk); auto r0 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), r); auto r1 = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), r); mm->add_return({r0, r1}); From 806f28b575ac0993476ceb1dd10214d191d72719 Mon Sep 17 00:00:00 2001 From: charlie Date: Tue, 21 Jul 2026 14:55:31 -0500 Subject: [PATCH 12/42] Remove bind_symbolic --- src/CMakeLists.txt | 1 - src/include/migraphx/op/bind_symbolic.hpp | 82 -------------------- src/include/migraphx/operators.hpp | 1 - src/onnx/parse_nonmaxsuppression.cpp | 3 +- src/onnx/parse_topk.cpp | 16 ++-- test/multi_target/multitarget_test.cpp | 6 +- test/onnx/parse/nms_dynamic_batch_test.cpp | 5 +- test/onnx/parse/nms_dynamic_boxes_test.cpp | 5 +- test/onnx/parse/nms_dynamic_classes_test.cpp | 5 +- test/onnx/parse/nms_test.cpp | 5 +- test/onnx/parse/topk_var_k_test.cpp | 16 ++-- test/ref/nonmaxsuppression.cpp | 9 +-- 12 files changed, 22 insertions(+), 132 deletions(-) delete mode 100644 src/include/migraphx/op/bind_symbolic.hpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 160f95282eb..e1bc74e6a1f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -194,7 +194,6 @@ register_migraphx_ops( as_shape atanh atan - bind_symbolic bit_cast bitwise_and broadcast diff --git a/src/include/migraphx/op/bind_symbolic.hpp b/src/include/migraphx/op/bind_symbolic.hpp deleted file mode 100644 index 82e7f2beb08..00000000000 --- a/src/include/migraphx/op/bind_symbolic.hpp +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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_OPERATORS_BIND_SYMBOLIC_HPP -#define MIGRAPHX_GUARD_OPERATORS_BIND_SYMBOLIC_HPP - -#include -#include -#include -#include - -namespace migraphx { -inline namespace MIGRAPHX_INLINE_NS { -namespace op { - -/// Operator used to bind a symbolic variable to input data. Such that -/// at runtime the symbolic variable map will be updated with the value -/// of the input. The operator only updates the symbolic variable map, it -/// is an identity operator otherwise. Used for data-dependent dimensions. -/// -/// bind_symbolic(input) symbols = var("x"), var("y") -/// where input is tensor of shape [2]. -/// at runtime set: -/// var("x") = input.at(0) -/// var("y") = input.at(1) -/// -/// input must be a 1D static tensor and its dimension must match the number of symbols. -struct bind_symbolic -{ - std::vector symbols{}; - - template - static auto reflect(Self& self, F f) - { - return pack(f(self.symbols, "symbols")); - } - - std::string name() const { return "bind_symbolic"; } - - shape compute_shape(std::vector inputs) const - { - check_shapes{inputs, *this}.has(1).only_dims(1); - if(symbols.size() != inputs.at(0).lens().at(0)) - MIGRAPHX_THROW("BIND_SYMBOLIC: dimension of input does not match number of symbols."); - return inputs.at(0); - } - - //TODO: have a function that tells how to link up the symbols to the inputs? Or keep it a simple 1 to 1? - - argument compute(shape, std::vector args) const - { - return args[0]; - } - - std::vector output_alias(const std::vector&) const { return {0}; } -}; - -} // namespace op -} // namespace MIGRAPHX_INLINE_NS -} // namespace migraphx - -#endif diff --git a/src/include/migraphx/operators.hpp b/src/include/migraphx/operators.hpp index 47683dbc6a5..1e527c762ff 100644 --- a/src/include/migraphx/operators.hpp +++ b/src/include/migraphx/operators.hpp @@ -36,7 +36,6 @@ #include #include #include -#include #include #include #include diff --git a/src/onnx/parse_nonmaxsuppression.cpp b/src/onnx/parse_nonmaxsuppression.cpp index 30a25e70638..7bbd801716d 100644 --- a/src/onnx/parse_nonmaxsuppression.cpp +++ b/src/onnx/parse_nonmaxsuppression.cpp @@ -46,9 +46,8 @@ struct parse_nonmaxsuppression : op_parser auto num_selected = info.add_instruction(make_op("get_tuple_elem", {{"index", 1}}), nms_ins); auto num_selected_var = shape::dynamic_dimension{sym::var(info.name)}; - auto num_selected_bind = info.add_instruction(make_op("bind_symbolic", {{"symbols", {to_value(num_selected_var)}}}), num_selected); return info.add_instruction( - make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {to_value(num_selected_var)}}, {"mode", "ends_input"}}), indices, num_selected_bind); + make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {to_value(num_selected_var)}}, {"mode", "ends_input"}}), indices, num_selected); } }; diff --git a/src/onnx/parse_topk.cpp b/src/onnx/parse_topk.cpp index bb55b7608e6..74cc4042262 100644 --- a/src/onnx/parse_topk.cpp +++ b/src/onnx/parse_topk.cpp @@ -87,8 +87,8 @@ struct parse_topk : op_parser return {ret_val, ret_ind}; } - // Variable (data-dependent) `k`: run topk over the whole axis dimension, bind the runtime - // `k` to a symbolic dimension, then slice the outputs down to it. + // Variable (data-dependent) `k`: run topk over the whole axis dimension, then slice the + // outputs down to the runtime `k` using a symbolic dimension. auto input_shape = args.at(0)->get_shape(); auto norm_axis = axis < 0 ? axis + input_shape.ndim() : axis; int64_t k = input_shape.max_lens().at(norm_axis); @@ -99,23 +99,21 @@ struct parse_topk : op_parser auto ret_val = info.add_instruction(make_op("get_tuple_elem", {{"index", 0}}), topk_ret); auto ret_ind = info.add_instruction(make_op("get_tuple_elem", {{"index", 1}}), topk_ret); - auto k_var = shape::dynamic_dimension{sym::var(info.name)}; - auto k_bind = info.add_instruction( - make_op("bind_symbolic", {{"symbols", {to_value(k_var)}}}), args.at(1)); - ret_val = info.add_instruction(make_op("slice", + auto k_var = shape::dynamic_dimension{sym::var(info.name)}; + ret_val = info.add_instruction(make_op("slice", {{"axes", {axis}}, {"starts", {0}}, {"ends", {to_value(k_var)}}, {"mode", "ends_input"}}), ret_val, - k_bind); - ret_ind = info.add_instruction(make_op("slice", + args.at(1)); + ret_ind = info.add_instruction(make_op("slice", {{"axes", {axis}}, {"starts", {0}}, {"ends", {to_value(k_var)}}, {"mode", "ends_input"}}), ret_ind, - k_bind); + args.at(1)); return {ret_val, ret_ind}; } diff --git a/test/multi_target/multitarget_test.cpp b/test/multi_target/multitarget_test.cpp index c258d0e5563..fe7eaca19fc 100644 --- a/test/multi_target/multitarget_test.cpp +++ b/test/multi_target/multitarget_test.cpp @@ -58,7 +58,6 @@ static auto nonprefixed_ops() "nonmaxsuppression", "multibroadcast", "slice", - "bind_symbolic", "get_tuple_elem"}; return op_map; } @@ -234,9 +233,6 @@ TEST_CASE(single_target_multi_compile) auto cnt = gpu_mod->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), nms); auto num_selected_var = migraphx::shape::dynamic_dimension{migraphx::sym::var("nms_num_selected")}; - auto bind = gpu_mod->add_instruction( - migraphx::make_op("bind_symbolic", {{"symbols", {migraphx::to_value(num_selected_var)}}}), - cnt); auto r = gpu_mod->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, @@ -244,7 +240,7 @@ TEST_CASE(single_target_multi_compile) {"ends", {migraphx::to_value(num_selected_var)}}, {"mode", "ends_input"}}), idx, - bind); + cnt); gpu_mod->add_return({r}); auto run_on_gpu = mm->add_instruction( diff --git a/test/onnx/parse/nms_dynamic_batch_test.cpp b/test/onnx/parse/nms_dynamic_batch_test.cpp index 49b460fe582..d51bea88fd8 100644 --- a/test/onnx/parse/nms_dynamic_batch_test.cpp +++ b/test/onnx/parse/nms_dynamic_batch_test.cpp @@ -44,9 +44,6 @@ TEST_CASE(nms_dynamic_batch_test) auto cnt = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), nms); auto num_selected_var = migraphx::shape::dynamic_dimension{migraphx::sym::var("NonMaxSuppression_5")}; - auto bind = mm->add_instruction( - migraphx::make_op("bind_symbolic", {{"symbols", {migraphx::to_value(num_selected_var)}}}), - cnt); auto ret = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, @@ -54,7 +51,7 @@ TEST_CASE(nms_dynamic_batch_test) {"ends", {migraphx::to_value(num_selected_var)}}, {"mode", "ends_input"}}), idx, - bind); + cnt); mm->add_return({ret}); migraphx::onnx_options options; diff --git a/test/onnx/parse/nms_dynamic_boxes_test.cpp b/test/onnx/parse/nms_dynamic_boxes_test.cpp index eb84863db70..bed2b5d4810 100644 --- a/test/onnx/parse/nms_dynamic_boxes_test.cpp +++ b/test/onnx/parse/nms_dynamic_boxes_test.cpp @@ -43,9 +43,6 @@ TEST_CASE(nms_dynamic_boxes_test) auto cnt = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), nms); auto num_selected_var = migraphx::shape::dynamic_dimension{migraphx::sym::var("NonMaxSuppression_5")}; - auto bind = mm->add_instruction( - migraphx::make_op("bind_symbolic", {{"symbols", {migraphx::to_value(num_selected_var)}}}), - cnt); auto ret = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, @@ -53,7 +50,7 @@ TEST_CASE(nms_dynamic_boxes_test) {"ends", {migraphx::to_value(num_selected_var)}}, {"mode", "ends_input"}}), idx, - bind); + cnt); mm->add_return({ret}); migraphx::onnx_options options; diff --git a/test/onnx/parse/nms_dynamic_classes_test.cpp b/test/onnx/parse/nms_dynamic_classes_test.cpp index b0b851193cf..4ed9ac79b53 100644 --- a/test/onnx/parse/nms_dynamic_classes_test.cpp +++ b/test/onnx/parse/nms_dynamic_classes_test.cpp @@ -43,9 +43,6 @@ TEST_CASE(nms_dynamic_classes_test) auto cnt = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), nms); auto num_selected_var = migraphx::shape::dynamic_dimension{migraphx::sym::var("NonMaxSuppression_5")}; - auto bind = mm->add_instruction( - migraphx::make_op("bind_symbolic", {{"symbols", {migraphx::to_value(num_selected_var)}}}), - cnt); auto ret = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, @@ -53,7 +50,7 @@ TEST_CASE(nms_dynamic_classes_test) {"ends", {migraphx::to_value(num_selected_var)}}, {"mode", "ends_input"}}), idx, - bind); + cnt); mm->add_return({ret}); migraphx::onnx_options options; diff --git a/test/onnx/parse/nms_test.cpp b/test/onnx/parse/nms_test.cpp index 5b7c497ab7f..49980b71317 100644 --- a/test/onnx/parse/nms_test.cpp +++ b/test/onnx/parse/nms_test.cpp @@ -49,9 +49,6 @@ TEST_CASE(nms_test) auto cnt = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), nms); auto num_selected_var = migraphx::shape::dynamic_dimension{migraphx::sym::var("NonMaxSuppression_5")}; - auto bind = mm->add_instruction( - migraphx::make_op("bind_symbolic", {{"symbols", {migraphx::to_value(num_selected_var)}}}), - cnt); auto ret = mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, @@ -59,7 +56,7 @@ TEST_CASE(nms_test) {"ends", {migraphx::to_value(num_selected_var)}}, {"mode", "ends_input"}}), idx, - bind); + cnt); mm->add_return({ret}); auto prog = read_onnx("nms_test.onnx"); diff --git a/test/onnx/parse/topk_var_k_test.cpp b/test/onnx/parse/topk_var_k_test.cpp index 49716089825..3bdbb79cd46 100644 --- a/test/onnx/parse/topk_var_k_test.cpp +++ b/test/onnx/parse/topk_var_k_test.cpp @@ -25,8 +25,8 @@ #include // `k` is a runtime input (graph input, not an initializer), so the parser takes the var_k -// path: topk runs with k set to the axis dimension's max length, the runtime `k` is bound to a -// symbolic dimension, then the outputs are sliced down to it. +// path: topk runs with k set to the axis dimension's max length, then the outputs are sliced +// down to the runtime `k` using a symbolic dimension. TEST_CASE(topk_var_k_test) { migraphx::program p; @@ -38,22 +38,20 @@ TEST_CASE(topk_var_k_test) auto val = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), out); auto ind = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), out); auto k_var = migraphx::shape::dynamic_dimension{migraphx::sym::var("TopK_2")}; - auto bind = mm->add_instruction( - migraphx::make_op("bind_symbolic", {{"symbols", {migraphx::to_value(k_var)}}}), k); val = mm->add_instruction(migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {migraphx::to_value(k_var)}}, {"mode", "ends_input"}}), val, - bind); + k); ind = mm->add_instruction(migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {migraphx::to_value(k_var)}}, {"mode", "ends_input"}}), ind, - bind); + k); mm->add_return({val, ind}); auto prog = read_onnx("topk_var_k_test.onnx"); @@ -74,22 +72,20 @@ TEST_CASE(topk_var_k_dynamic_test) auto val = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), out); auto ind = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), out); auto k_var = migraphx::shape::dynamic_dimension{migraphx::sym::var("TopK_2")}; - auto bind = mm->add_instruction( - migraphx::make_op("bind_symbolic", {{"symbols", {migraphx::to_value(k_var)}}}), k); val = mm->add_instruction(migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {migraphx::to_value(k_var)}}, {"mode", "ends_input"}}), val, - bind); + k); ind = mm->add_instruction(migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {migraphx::to_value(k_var)}}, {"mode", "ends_input"}}), ind, - bind); + k); mm->add_return({val, ind}); migraphx::onnx_options options; diff --git a/test/ref/nonmaxsuppression.cpp b/test/ref/nonmaxsuppression.cpp index cd9fed0c332..ad4513f9bc2 100644 --- a/test/ref/nonmaxsuppression.cpp +++ b/test/ref/nonmaxsuppression.cpp @@ -36,13 +36,10 @@ static migraphx::instruction_ref add_nms_dynamic_slice(migraphx::module* mm, { auto idx = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), nms); auto cnt = mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 1}}), nms); - // Bind the runtime num_selected value to a symbolic variable and slice the padded indices - // down to it, matching the IR the NonMaxSuppression ONNX parser emits. + // Slice the padded indices down to the runtime num_selected value using a symbolic + // dimension, matching the IR the NonMaxSuppression ONNX parser emits. auto num_selected_var = migraphx::shape::dynamic_dimension{migraphx::sym::var("nms_num_selected")}; - auto bind = mm->add_instruction( - migraphx::make_op("bind_symbolic", {{"symbols", {migraphx::to_value(num_selected_var)}}}), - cnt); return mm->add_instruction( migraphx::make_op("slice", {{"axes", {0}}, @@ -50,7 +47,7 @@ static migraphx::instruction_ref add_nms_dynamic_slice(migraphx::module* mm, {"ends", {migraphx::to_value(num_selected_var)}}, {"mode", "ends_input"}}), idx, - bind); + cnt); } TEST_CASE(nms_dyn_out_test) From 3e4b25e7cbe252b44f63907586030a74661aa076 Mon Sep 17 00:00:00 2001 From: charlie Date: Tue, 21 Jul 2026 14:55:39 -0500 Subject: [PATCH 13/42] Slice to use 2+ inputs for all symbolics --- src/include/migraphx/op/slice.hpp | 302 +++++++++++++++++++++++------- 1 file changed, 233 insertions(+), 69 deletions(-) diff --git a/src/include/migraphx/op/slice.hpp b/src/include/migraphx/op/slice.hpp index b8ef28e8b11..6cfbad225d0 100644 --- a/src/include/migraphx/op/slice.hpp +++ b/src/include/migraphx/op/slice.hpp @@ -39,46 +39,42 @@ namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { -/** - * Slice operator that accepts variable axes, starts and ends. - * All of `starts`, `ends`, and `axes` attributes must be supplied. - * - * `mode` specifies what the inputs to slice are: - * one_input: slice(input); - * starts_input: slice(input, starts); - * ends_input: slice(input, ends); - * axes_input: slice(input, axes); - * starts_ends_input: slice(input, starts, ends); - * starts_axes_input: slice(input, starts, axes); - * ends_axes_input: slice(input, ends, axes); - * starts_ends_axes_input: slice(input, start, ends, axes); - * - * Attributes: - * axes: axes to slice over - * starts: slice starting indices - * ends: slice ending indices - * - * Parameters: - * data: the input tensor to slice (dynamic or static shape) - * starts_input: starting indices of slice (optional, static shape) - * ends_input: ending indices of slice (optional, static shape) - * axes_input: axes to slice over (optional, static shape) - */ +/// Slice operator that accepts variable axes, starts and ends. +/// All of `starts`, `ends`, and `axes` attributes must be supplied. +/// +/// `mode` specifies what the inputs to slice are: +/// one_input: slice(input); +/// starts_input: slice(input, starts); +/// ends_input: slice(input, ends); +/// axes_input: slice(input, axes); +/// starts_ends_input: slice(input, starts, ends); +/// starts_axes_input: slice(input, starts, axes); +/// ends_axes_input: slice(input, ends, axes); +/// starts_ends_axes_input: slice(input, start, ends, axes); +/// +/// Attributes: +/// axes: axes to slice over +/// starts: slice starting indices +/// ends: slice ending indices +/// +/// Parameters: +/// data: the input tensor to slice (dynamic or static shape) +/// starts_input: starting indices of slice (optional, static shape) +/// ends_input: ending indices of slice (optional, static shape) +/// axes_input: axes to slice over (optional, static shape) struct slice { - MIGRAPHX_NESTED_ENUM_CLASS( - slice_mode, - one_input, - starts_input, - ends_input, - axes_input, - starts_ends_input, - starts_axes_input, - ends_axes_input, - starts_ends_axes_input - ); + MIGRAPHX_NESTED_ENUM_CLASS(slice_mode, + one_input, + starts_input, + ends_input, + axes_input, + starts_ends_input, + starts_axes_input, + ends_axes_input, + starts_ends_axes_input); - std::vector axes{}; + std::vector axes{}; std::vector starts{}; std::vector ends{}; slice_mode mode = slice_mode::one_input; @@ -86,15 +82,16 @@ struct slice template static auto reflect(Self& self, F f) { - return pack(f(self.axes, "axes"), f(self.starts, "starts"), f(self.ends, "ends"), f(self.mode, "mode")); + return pack(f(self.axes, "axes"), + f(self.starts, "starts"), + f(self.ends, "ends"), + f(self.mode, "mode")); } - /** - * Ensure that attribute axes is within limits. - * Will attempt to normalize starts and ends; but will use the dynamic_dimension.max - * values for dynamic shapes. This makes it so you have to renormalize for - * non-fixed dynamic_dimensions. - */ + /// Ensure that attribute axes is within limits. + /// Will attempt to normalize starts and ends; but will use the dynamic_dimension.max + /// values for dynamic shapes. This makes it so you have to renormalize for + /// non-fixed dynamic_dimensions. value attributes() const { value normalize_axes = value::object{}; @@ -114,12 +111,10 @@ struct slice std::string name() const { return "slice"; } - /** - * Computes the slice output shape dimensions for given starts, ends,and axes. - * Templated to also handle tensor views. - * Possibly different type between [in_starts, in_ends] and [in_axes] if in_axes is this - * object's axes attribute. Assumes in_starts and in_ends are normalized; in_axes are valid. - */ + /// Computes the slice output shape dimensions for given starts, ends,and axes. + /// Templated to also handle tensor views. + /// Possibly different type between [in_starts, in_ends] and [in_axes] if in_axes is this + /// object's axes attribute. Assumes in_starts and in_ends are normalized; in_axes are valid. template std::vector lens_calc(const std::vector& lengths, A in_starts, A in_ends, B in_axes) const @@ -138,7 +133,11 @@ struct slice { auto input_shape = inputs[0]; if(axes.size() != starts.size() or starts.size() != ends.size()) - MIGRAPHX_THROW("SLICE: Invalid attributes configuration. Not the same number of dimensions. axes: " + migraphx::to_string(axes.size()) + " starts: " + migraphx::to_string(starts.size() + " ends: " + migraphx::to_string(ends.size()))); + MIGRAPHX_THROW("SLICE: Invalid attributes configuration. Not the same number of " + "dimensions. axes: " + + migraphx::to_string(axes.size()) + + " starts: " + migraphx::to_string(starts.size()) + + " ends: " + migraphx::to_string(ends.size())); if(inputs.size() == 1) { @@ -146,23 +145,23 @@ struct slice MIGRAPHX_THROW("SLICE: Invalid mode for 1 input"); return; } - // check that inputs [1, end) are all 1D, have the same - // dimension, and are static + // check that inputs [1, end) are all 1D, have the same dimension, and are static check_shapes{inputs.begin() + 1, inputs.end(), - std::string("SLICE: inputs (starts, ends, and input_axes)"), + std::string("SLICE: inputs (starts_input, ends_input, axes_input)"), false} .only_dims(1) .same_dims(); if(inputs.at(1).lens().at(0) != axes.size()) { MIGRAPHX_THROW("SLICE: varable input and attributes mismatch: input[1] length (" + - to_string(inputs[1].lens().at(0)) + ") != attribute number of dimensions (" + - to_string(axes.size()) + ")"); + to_string(inputs[1].lens().at(0)) + + ") != attribute number of dimensions (" + to_string(axes.size()) + ")"); } if(inputs.size() == 2) { - std::vector two_input_modes = {slice_mode::starts_input, slice_mode::ends_input, slice_mode::axes_input}; + std::vector two_input_modes = { + slice_mode::starts_input, slice_mode::ends_input, slice_mode::axes_input}; if(not contains(two_input_modes, mode)) { MIGRAPHX_THROW("SLICE: Invalid mode for 2 inputs"); @@ -170,7 +169,9 @@ struct slice } else if(inputs.size() == 3) { - std::vector three_input_modes = {slice_mode::starts_ends_input, slice_mode::starts_axes_input, slice_mode::ends_axes_input}; + std::vector three_input_modes = {slice_mode::starts_ends_input, + slice_mode::starts_axes_input, + slice_mode::ends_axes_input}; if(not contains(three_input_modes, mode)) { MIGRAPHX_THROW("SLICE: Invalid mode for 3 inputs"); @@ -214,7 +215,8 @@ struct slice { if(inputs.size() != 1) { - MIGRAPHX_THROW("SLICE: range-based dynamic input shapes with variable inputs unsupported."); + MIGRAPHX_THROW( + "SLICE: range-based dynamic input shapes with variable inputs unsupported."); } // Non-fixed sliced axis: bounds aren't normalized (can be negative or // out-of-bounds), so use a relaxed [0, max] bound. (#5015) @@ -238,12 +240,10 @@ struct slice return symbolic_compute_shape(input_shape); } - /** - * Calculates the starting offset for the sliced tensor. - * Used in compute when only data input and all other information are in the attributes. - * - * \param s static input shape - */ + /// Calculates the starting offset for the sliced tensor. + /// Used in compute when only data input and all other information are in the attributes. + /// + /// s: static input shape auto compute_offset(const shape& s) const { const std::vector& lens = s.lens(); @@ -267,14 +267,178 @@ struct slice return offset * s.type_size(); } + /// Calculates the starting offset for the sliced tensor (for aliasing). + /// Used for 2-4 inputs to `slice. + /// + /// s: static input shape + /// starts_input: starting indices of slice + /// ax_vec: axes to slice on + template + auto compute_offset(const shape& s, const T& starts_input, const T& ax_vec) const + { + auto ret = 0; + for(std::size_t i = 0; i < ax_vec.size(); ++i) + { + auto axis = ax_vec[i]; + ret += starts_input[i] * s.strides().at(axis); + } + return ret * s.type_size(); + } + + /// Used to normalize starts/ends/axes at runtime. + /// If given, normalize the starts/ends/axes inputs. Otherwise get from operator attributes. + /// If starts_input or ends_input is not given, assuming starts/ends attributes are only + /// integers. If starts/ends have symbolics, they should go through the starts_input and + /// ends_input instead. `axes` attribute should always be correctly normalized at compile-time + /// because shapes with dynamic rank are not supported. + /// + /// input_shape: static shape of the input + /// starts_input: optional + /// ends_input: optional + /// axes_input: optional + std::unordered_map> + normalize_starts_ends_axes(shape input_shape, + const optional>& starts_input, + const optional>& ends_input, + const optional>& axes_input) const + { + assert(not input_shape.dynamic()); + auto axes_attrs = this->attributes().at("normalize_axes"); + std::vector norm_starts; + std::vector norm_ends; + std::vector norm_axes; + if(axes_input) + { + norm_axes = normalize_axes(axes_input.value(), + input_shape, + axes_attrs.at("axes"), + "Slice variable axes_input"); + } + else + { + norm_axes = this->axes; + } + if(starts_input) + { + norm_starts = normalize_indices(starts_input.value(), + norm_axes, + input_shape, + axes_attrs.at("starts"), + "Slice variable starts_input"); + } + else + { + norm_starts = to_ints(this->starts); + } + if(ends_input) + { + norm_ends = normalize_indices(ends_input.value(), + norm_axes, + input_shape, + axes_attrs.at("ends"), + "Slice variable input ends"); + } + else + { + norm_ends = to_ints(this->ends); + } + return {{"norm_starts", norm_starts}, {"norm_ends", norm_ends}, {"norm_axes", norm_axes}}; + } + argument compute(const dyn_output& dyn_out, std::vector args) const { auto input = args[0]; auto input_shape = input.get_shape(); - std::size_t offset = compute_offset(input_shape); - // For dynamic shapes, attributes will be normalized and symbolic dimensions resolved. - // Rerunning comput_shape() from dyn_output should give a static computed_shape. - return {dyn_out.computed_shape, [=] { return input.data() + offset; }}; + if(args.size() == 1) + { + std::size_t offset = compute_offset(input_shape); + return {dyn_out.computed_shape, [=] { return input.data() + offset; }}; + } + else + { + std::unordered_map> norm_inputs; + if(mode == slice_mode::starts_input) + { + args[1].visit([&](auto starts_input) { + norm_inputs = + normalize_starts_ends_axes(input_shape, + starts_input.template to_vector(), + this->ends, + this->axes); + }); + } + else if(mode == slice_mode::ends_input) + { + args[1].visit([&](auto ends_input) { + norm_inputs = + normalize_starts_ends_axes(input_shape, + this->starts, + ends_input.template to_vector(), + this->axes); + }); + } + else if(mode == slice_mode::axes_input) + { + args[1].visit([&](auto axes_input) { + norm_inputs = + normalize_starts_ends_axes(input_shape, + this->starts, + this->ends, + axes_input.template to_vector()); + }); + } + else if(mode == slice_mode::starts_ends_input) + { + // attr axes set; inputs are (data, starts_input, ends_input) + visit_all(args[1], args[2])([&](auto starts_input, auto ends_input) { + norm_inputs = + normalize_starts_ends_axes(input_shape, + starts_input.template to_vector(), + ends_input.template to_vector(), + this->axes); + }); + } + else if(mode == slice_mode::starts_axes_input) + { + visit_all(args[1], args[2])([&](auto starts_input, auto axes_input) { + norm_inputs = + normalize_starts_ends_axes(input_shape, + starts_input.template to_vector(), + this->ends, + axes_input.template to_vector()); + }); + } + else if(mode == slice_mode::ends_axes_input) + { + visit_all(args[1], args[2])([&](auto ends_input, auto axes_input) { + norm_inputs = + normalize_starts_ends_axes(input_shape, + this->starts, + ends_input.template to_vector(), + axes_input.template to_vector()); + }); + } + else // mode == slice_mode::starts_ends_axes_input + { + visit_all(args[1], args[2], args[3])( + [&](auto starts_input, auto ends_input, auto axes_input) { + norm_inputs = + normalize_starts_ends_axes(input_shape, + starts_input.template to_vector(), + ends_input.template to_vector(), + axes_input.template to_vector()); + }); + } + auto offset = compute_offset( + input_shape, norm_inputs.at("norm_starts"), norm_inputs.at("norm_axes")); + shape calc_shape = shape{input_shape.type(), + lens_calc(input_shape.lens(), + norm_inputs.at("norm_starts"), + norm_inputs.at("norm_ends"), + norm_inputs.at("norm_axes")), + input_shape.strides()}; + return {calc_shape, [=] { return input.data() + offset; }}; + } } std::vector output_alias(const std::vector&) const { return {0}; } From aa7ce6de0c8a4c42d5877a7ae6c6d74dedb8dd91 Mon Sep 17 00:00:00 2001 From: charlie Date: Tue, 21 Jul 2026 15:54:43 -0500 Subject: [PATCH 14/42] Refine constraints on symbolic slice attributes --- src/include/migraphx/dim_like.hpp | 8 ++++++++ src/include/migraphx/op/slice.hpp | 2 ++ 2 files changed, 10 insertions(+) diff --git a/src/include/migraphx/dim_like.hpp b/src/include/migraphx/dim_like.hpp index 5b89cc4ac90..cb8a008e02d 100644 --- a/src/include/migraphx/dim_like.hpp +++ b/src/include/migraphx/dim_like.hpp @@ -84,6 +84,14 @@ inline std::vector to_sym_exprs(const std::vector& dims) return result; } +// Check if any of the dim_like are a shape::dynamic_dimension. +inline bool any_sym(const std::vector& dims) +{ + return std::any_of(dims.begin(), dims.end(), [](const dim_like& d) { + return std::holds_alternative(d); + }); +} + MIGRAPHX_EXPORT void migraphx_to_value(value& v, const dim_like& d); MIGRAPHX_EXPORT void migraphx_from_value(const value& v, dim_like& d); diff --git a/src/include/migraphx/op/slice.hpp b/src/include/migraphx/op/slice.hpp index 6cfbad225d0..c2d2e5abb21 100644 --- a/src/include/migraphx/op/slice.hpp +++ b/src/include/migraphx/op/slice.hpp @@ -141,6 +141,8 @@ struct slice if(inputs.size() == 1) { + if(any_sym(starts) or any_sym(ends)) + MIGRAPHX_THROW("SLICE: Invalid attributes: symbolic in attribute for 1 input slice"); if(mode != slice_mode::one_input) MIGRAPHX_THROW("SLICE: Invalid mode for 1 input"); return; From 82f335f42647468822308ed0e766dde3a54baca4 Mon Sep 17 00:00:00 2001 From: Shiv Date: Tue, 21 Jul 2026 15:03:09 -0700 Subject: [PATCH 15/42] add runtime symbol resolution op --- src/CMakeLists.txt | 1 + src/include/migraphx/op/resolve_sym_expr.hpp | 95 +++++++++++++++++++ test/op_shape_test.cpp | 29 ++++++ test/ref/resolve_sym_expr.cpp | 98 ++++++++++++++++++++ test/ref/slice.cpp | 45 ++++++++- 5 files changed, 267 insertions(+), 1 deletion(-) create mode 100644 src/include/migraphx/op/resolve_sym_expr.hpp create mode 100644 test/ref/resolve_sym_expr.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e1bc74e6a1f..0327965a3d4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -286,6 +286,7 @@ register_migraphx_ops( reshape reshape_lazy resize + resolve_sym_expr reverse rnn rnn_last_cell_output diff --git a/src/include/migraphx/op/resolve_sym_expr.hpp b/src/include/migraphx/op/resolve_sym_expr.hpp new file mode 100644 index 00000000000..b4507cb348a --- /dev/null +++ b/src/include/migraphx/op/resolve_sym_expr.hpp @@ -0,0 +1,95 @@ +/* + * 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_OPERATORS_RESOLVE_SYM_EXPR_HPP +#define MIGRAPHX_GUARD_OPERATORS_RESOLVE_SYM_EXPR_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { +namespace op { + +/** + * Evaluate symbolic dimension expressions at runtime. Dynamic ops (e.g. slice) keep their + * symbolic dim_like bounds as attributes for compile-time shape inference; resolve_sym_expr turns + * those expressions into the concrete values fed to the op's runtime-tensor inputs. + * + * exprs: symbolic expressions to evaluate. symbols: the root variables they reference. + * Inputs: one scalar int per symbol, in `symbols` order (symbols[i] = args[i]); each is a single + * root-dimension value, e.g. an element of a `dimensions_of` output. + * Output: a tuple with one 1-D int64 element per expr, element i = eval(exprs[i]), unclamped. + */ +struct resolve_sym_expr +{ + std::vector exprs{}; + std::vector symbols{}; + + template + static auto reflect(Self& self, F f) + { + return pack(f(self.exprs, "exprs"), f(self.symbols, "symbols")); + } + + std::string name() const { return "resolve_sym_expr"; } + + shape compute_shape(std::vector inputs) const + { + check_shapes{inputs, *this}.has(symbols.size()).nelements(1); + return shape{std::vector(exprs.size(), shape{shape::int64_type, {1}})}; + } + + argument compute(const shape& output_shape, std::vector args) const + { + assert(args.size() == symbols.size()); + std::unordered_map smap; + for(std::size_t i = 0; i < symbols.size(); ++i) + smap[symbols[i]] = args[i].at(); + const auto& sub_shapes = output_shape.sub_shapes(); + assert(sub_shapes.size() == exprs.size()); + std::vector results(exprs.size()); + std::transform(exprs.begin(), + exprs.end(), + sub_shapes.begin(), + results.begin(), + [&](const sym::expr& e, const shape& s) { + argument r{s}; + r.visit([&](auto out) { out[0] = e.eval_uint(smap); }); + return r; + }); + return argument{results}; + } +}; + +} // namespace op +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx + +#endif diff --git a/test/op_shape_test.cpp b/test/op_shape_test.cpp index 12369334843..8fa0e20fd76 100644 --- a/test/op_shape_test.cpp +++ b/test/op_shape_test.cpp @@ -5295,6 +5295,35 @@ TEST_CASE(slice_dyn_nonfixed_keeps_other_optimals) input); } +TEST_CASE(resolve_sym_expr_shape) +{ + // Output is a tuple with one 1-D int64 element per expr, regardless of the symbolic exprs. + auto n = var("n", {1, 16}); + migraphx::shape sv{migraphx::shape::int64_type, {1}}; + migraphx::shape elem{migraphx::shape::int64_type, {1}}; + expect_shape( + migraphx::shape{std::vector{elem, elem}}, + migraphx::make_op( + "resolve_sym_expr", + {{"exprs", migraphx::value::array{migraphx::to_value(n), migraphx::to_value(n)}}, + {"symbols", migraphx::value::array{migraphx::to_value(n)}}}), + sv); +} + +TEST_CASE(resolve_sym_expr_bad_input) +{ + // One scalar value input is required per symbol; here 2 symbols but only 1 input. + auto m = var("m", {1, 16}); + auto n = var("n", {1, 16}); + migraphx::shape sv{migraphx::shape::int64_type, {1}}; + throws_shape( + migraphx::make_op( + "resolve_sym_expr", + {{"exprs", migraphx::value::array{migraphx::to_value(m)}}, + {"symbols", migraphx::value::array{migraphx::to_value(m), migraphx::to_value(n)}}}), + sv); +} + TEST_CASE(slice_sym) { auto n = var("n", {1, 8}); diff --git a/test/ref/resolve_sym_expr.cpp b/test/ref/resolve_sym_expr.cpp new file mode 100644 index 00000000000..5d1179b761d --- /dev/null +++ b/test/ref/resolve_sym_expr.cpp @@ -0,0 +1,98 @@ +/* + * 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 + +TEST_CASE(resolve_sym_expr_single_symbol) +{ + // Evaluate two exprs (n and floor(n/2)) of one root symbol n from its runtime value. + auto n = migraphx::sym::var("n", {1, 16}); + auto half = n / migraphx::sym::lit(2); + + migraphx::program p; + auto* mm = p.get_main_module(); + migraphx::shape sv_shape{migraphx::shape::int64_type, {1}}; + auto sv = mm->add_parameter("sym_vals", sv_shape); + mm->add_instruction( + migraphx::make_op( + "resolve_sym_expr", + {{"exprs", migraphx::value::array{migraphx::to_value(n), migraphx::to_value(half)}}, + {"symbols", migraphx::value::array{migraphx::to_value(n)}}}), + sv); + p.compile(migraphx::make_target("ref")); + + migraphx::parameter_map params; + std::vector sv_data = {7}; + params["sym_vals"] = migraphx::argument(sv_shape, sv_data.data()); + auto result = p.eval(params).back(); + + // Output is a tuple: element i = eval(exprs[i]). n = 7, floor(7 / 2) = 3. + migraphx::shape elem{migraphx::shape::int64_type, {1}}; + EXPECT(result.get_shape() == migraphx::shape{std::vector{elem, elem}}); + auto subs = result.get_sub_objects(); + EXPECT(subs.size() == 2); + EXPECT(subs[0].at() == 7); + EXPECT(subs[1].at() == 3); +} + +TEST_CASE(resolve_sym_expr_multi_symbol) +{ + // Two root symbols; one scalar value input per symbol, in `symbols` order. + auto m = migraphx::sym::var("m", {1, 16}); + auto n = migraphx::sym::var("n", {1, 16}); + auto sum = m + n; + + migraphx::program p; + auto* mm = p.get_main_module(); + migraphx::shape val{migraphx::shape::int64_type, {1}}; + auto mv = mm->add_parameter("m_val", val); + auto nv = mm->add_parameter("n_val", val); + mm->add_instruction( + migraphx::make_op( + "resolve_sym_expr", + {{"exprs", migraphx::value::array{migraphx::to_value(sum)}}, + {"symbols", migraphx::value::array{migraphx::to_value(m), migraphx::to_value(n)}}}), + mv, + nv); + p.compile(migraphx::make_target("ref")); + + migraphx::parameter_map params; + std::vector m_data = {3}; + std::vector n_data = {4}; + params["m_val"] = migraphx::argument(val, m_data.data()); + params["n_val"] = migraphx::argument(val, n_data.data()); + auto result = p.eval(params).back(); + + // Single-element tuple: m + n = 3 + 4. + auto subs = result.get_sub_objects(); + EXPECT(subs.size() == 1); + EXPECT(subs[0].at() == 7); +} diff --git a/test/ref/slice.cpp b/test/ref/slice.cpp index efde93f1ab0..7acc9ef87e2 100644 --- a/test/ref/slice.cpp +++ b/test/ref/slice.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 @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include #include @@ -409,3 +411,44 @@ TEST_CASE(slice_dyn_test1) EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); EXPECT(result.get_shape() == sresult); } + +TEST_CASE(slice_sym_resolved_input) +{ + // A late pass lowers `slice[axes=2, starts=0, ends=n]` to this: the symbolic end becomes a + // runtime input from resolve_sym_expr, the concrete start stays an attribute, and slice runs as + // a plain dynamic multi-input slice. + auto n = migraphx::sym::var("n", {1, 3}); + + migraphx::program p; + auto* mm = p.get_main_module(); + std::vector data(2 * 2 * 3); + std::iota(data.begin(), data.end(), 0); + migraphx::shape s{migraphx::shape::int32_type, {2, 2, 3}}; + auto l0 = mm->add_literal(migraphx::literal{s, data}); + + migraphx::shape val{migraphx::shape::int64_type, {1}}; + auto nv = mm->add_parameter("n_val", val); + auto end_tuple = mm->add_instruction( + migraphx::make_op("resolve_sym_expr", + {{"exprs", migraphx::value::array{migraphx::to_value(n)}}, + {"symbols", migraphx::value::array{migraphx::to_value(n)}}}), + nv); + // resolve_sym_expr returns a tuple; extract the single end value to feed slice's runtime input. + auto end_vals = + mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), end_tuple); + // starts_axes config: attrs starts + axes set, ends arrives as the runtime input. + mm->add_instruction(migraphx::make_op("slice", {{"axes", {2}}, {"starts", {0}}}), l0, end_vals); + p.compile(migraphx::make_target("ref")); + + migraphx::parameter_map params; + std::vector n_data = {2}; // n = 2 -> slice axis 2 as [0, 2) + params["n_val"] = migraphx::argument(val, n_data.data()); + auto result = p.eval(params).back(); + + std::vector gold = {0, 1, 3, 4, 6, 7, 9, 10}; + std::vector results_vector; + result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); + EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); + EXPECT(result.get_shape() == + migraphx::shape{migraphx::shape::int32_type, {2, 2, 2}, {6, 3, 1}}); +} From 30b3e0747de38297c6e68c6f3080aed4ee4827f0 Mon Sep 17 00:00:00 2001 From: Shiv Date: Tue, 21 Jul 2026 16:22:01 -0700 Subject: [PATCH 16/42] copilot review --- src/include/migraphx/op/resolve_sym_expr.hpp | 1 + test/ref/resolve_sym_expr.cpp | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/include/migraphx/op/resolve_sym_expr.hpp b/src/include/migraphx/op/resolve_sym_expr.hpp index b4507cb348a..a2c315181db 100644 --- a/src/include/migraphx/op/resolve_sym_expr.hpp +++ b/src/include/migraphx/op/resolve_sym_expr.hpp @@ -70,6 +70,7 @@ struct resolve_sym_expr { assert(args.size() == symbols.size()); std::unordered_map smap; + smap.reserve(symbols.size()); for(std::size_t i = 0; i < symbols.size(); ++i) smap[symbols[i]] = args[i].at(); const auto& sub_shapes = output_shape.sub_shapes(); diff --git a/test/ref/resolve_sym_expr.cpp b/test/ref/resolve_sym_expr.cpp index 5d1179b761d..8d9b817aecd 100644 --- a/test/ref/resolve_sym_expr.cpp +++ b/test/ref/resolve_sym_expr.cpp @@ -21,8 +21,6 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -#include -#include #include #include #include From f21aa3ded8df81870d23eb36b19f7688ea38b8b8 Mon Sep 17 00:00:00 2001 From: charlie Date: Tue, 21 Jul 2026 18:48:52 -0500 Subject: [PATCH 17/42] Add bit flag enum and update parse_slice to set slice_mode --- src/include/migraphx/enum.hpp | 110 +++++++++++++++++++++++++++++++++ src/onnx/parse_slice.cpp | 113 ++++++++++++++++++++++++---------- test/enum.cpp | 58 +++++++++++++++++ 3 files changed, 247 insertions(+), 34 deletions(-) diff --git a/src/include/migraphx/enum.hpp b/src/include/migraphx/enum.hpp index a2a59e7a42d..19bc7171a4c 100644 --- a/src/include/migraphx/enum.hpp +++ b/src/include/migraphx/enum.hpp @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -141,6 +142,76 @@ struct is_named_enum +struct is_bit_flag : std::false_type +{ +}; + +template +struct is_bit_flag()))>> + : std::true_type +{ +}; + +// Type-safe bitwise operators for enums declared with MIGRAPHX_BIT_FLAG_ENUM. They operate on the +// enum's underlying integer and return the enum type, so combining values never leaks to a raw +// integer and different flag enums cannot be mixed. +template {})> +constexpr E operator|(E lhs, E rhs) +{ + using U = std::underlying_type_t; + return static_cast(static_cast(lhs) | static_cast(rhs)); +} + +template {})> +constexpr E operator&(E lhs, E rhs) +{ + using U = std::underlying_type_t; + return static_cast(static_cast(lhs) & static_cast(rhs)); +} + +template {})> +constexpr E operator^(E lhs, E rhs) +{ + using U = std::underlying_type_t; + return static_cast(static_cast(lhs) ^ static_cast(rhs)); +} + +template {})> +constexpr E operator~(E val) +{ + using U = std::underlying_type_t; + return static_cast(~static_cast(val)); +} + +template {})> +constexpr E& operator|=(E& lhs, E rhs) +{ + return lhs = lhs | rhs; +} + +template {})> +constexpr E& operator&=(E& lhs, E rhs) +{ + return lhs = lhs & rhs; +} + +template {})> +constexpr E& operator^=(E& lhs, E rhs) +{ + return lhs = lhs ^ rhs; +} + +// Returns true when every bit set in flag is also set in val. +template {})> +constexpr bool has_flag(E val, E flag) +{ + using U = std::underlying_type_t; + return (static_cast(val) & static_cast(flag)) == static_cast(flag); +} + // Returns the array of enumerator values for an enum declared with MIGRAPHX_ENUM. template {})> auto enum_entries() @@ -285,4 +356,43 @@ Enum from_string(const std::string& name) MIGRAPHX_DETAIL_ENUM_HELPERS( \ friend, name, MIGRAPHX_DETAIL_ENUM_CLASS_CAPTURE, using enum_scope = name;, __VA_ARGS__) +// Emits the ADL hook that marks an enum as a bit flag, which is_bit_flag detects to enable the +// bitwise operators. `linkage` is `inline` at namespace scope or `friend` at class scope, mirroring +// MIGRAPHX_DETAIL_ENUM_HELPERS. +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define MIGRAPHX_DETAIL_BIT_FLAG_ENUM(linkage, name) \ + linkage constexpr bool migraphx_is_bit_flag(name) { return true; } + +// Declares a scoped enum (enum class) with the given underlying type and enables the type-safe +// bitwise operators |, &, ^, ~, |=, &=, ^= and has_flag() on it. Use it at namespace scope: +// +// MIGRAPHX_BIT_FLAG_ENUM(access, std::uint8_t, +// none = 0, +// read = 1 << 0, +// write = 1 << 1) +// +// auto rw = access::read | access::write; +// if(has_flag(rw, access::read)) { /* ... */ } +// +// Unlike MIGRAPHX_ENUM_CLASS, no to_string/from_string helpers are generated, so enumerator values +// should be self-contained bit masks. +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define MIGRAPHX_BIT_FLAG_ENUM(name, type, ...) \ + enum class name : type \ + { \ + __VA_ARGS__ \ + }; \ + MIGRAPHX_DETAIL_BIT_FLAG_ENUM(inline, name) + +// Like MIGRAPHX_BIT_FLAG_ENUM, but for a scoped enum declared inside a class or struct; the hook is +// generated as a hidden friend so argument-dependent lookup still finds it. Use it inside the +// class/struct body. +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define MIGRAPHX_NESTED_BIT_FLAG_ENUM(name, type, ...) \ + enum class name : type \ + { \ + __VA_ARGS__ \ + }; \ + MIGRAPHX_DETAIL_BIT_FLAG_ENUM(friend, name) + #endif // MIGRAPHX_GUARD_MIGRAPHX_ENUM_HPP diff --git a/src/onnx/parse_slice.cpp b/src/onnx/parse_slice.cpp index f09f032c6ac..6c486bbdfd5 100644 --- a/src/onnx/parse_slice.cpp +++ b/src/onnx/parse_slice.cpp @@ -27,11 +27,20 @@ #include #include #include +#include +#include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace onnx { +MIGRAPHX_BIT_FLAG_ENUM(slice_input_flags, + std::uint8_t, + none = 0, + starts_input = 1 << 0, + ends_input = 1 << 1, + axes_input = 1 << 2) + struct parse_slice : op_parser { @@ -39,17 +48,18 @@ struct parse_slice : op_parser struct slice_desc { - op::slice op; std::vector op_args; + std::vector axes; + std::vector starts; + std::vector ends; std::vector steps; std::vector raxes; + slice_input_flags flags; void always_insert(instruction_ref arg) { op_args.insert(op_args.begin(), arg); } - /** - * Either insert argument into `this->op_args` or return the constant value of the argument - */ - std::vector insert(instruction_ref arg) + // Either insert argument into `this->op_args` or return the constant value of the argument + std::vector try_insert(instruction_ref arg) { std::vector result; migraphx::argument arg_value = arg->eval(); @@ -63,6 +73,41 @@ struct parse_slice : op_parser } return result; } + + op::slice::slice_mode get_slice_mode(slice_input_flags slice_flags) + { + switch (slice_flags) + { + case slice_input_flags::none: + return op::slice::slice_mode::one_input; + case slice_input_flags::starts_input: + return op::slice::slice_mode::starts_input; + case slice_input_flags::ends_input: + return op::slice::slice_mode::ends_input; + case slice_input_flags::axes_input: + return op::slice::slice_mode::axes_input; + case (slice_input_flags::starts_input | slice_input_flags::ends_input): + return op::slice::slice_mode::starts_ends_input; + case (slice_input_flags::starts_input | slice_input_flags::axes_input): + return op::slice::slice_mode::starts_axes_input; + case (slice_input_flags::ends_input | slice_input_flags::axes_input): + return op::slice::slice_mode::ends_axes_input; + case (slice_input_flags::starts_input | slice_input_flags::ends_input | slice_input_flags::axes_input): + return op::slice::slice_mode::starts_ends_axes_input; + default: + MIGRAPHX_THROW("PARSE_SLICE: invalid slice_mode"); + } + } + + op::slice create_slice_operator() + { + op::slice slice_op; + slice_op.axes = axes; + slice_op.starts = starts; + slice_op.ends = ends; + slice_op.mode = get_slice_mode(flags); + return slice_op; + } }; instruction_ref parse(const op_desc& /*opd*/, @@ -71,7 +116,7 @@ struct parse_slice : op_parser const std::vector& args) const { auto sd = construct_slice_desc(parser, info, args); - auto ins = info.add_instruction(sd.op, sd.op_args); + auto ins = info.add_instruction(sd.create_slice_operator(), sd.op_args); if(not sd.raxes.empty()) { ins = info.add_instruction(make_op("reverse", {{"axes", sd.raxes}}), ins); @@ -85,7 +130,7 @@ struct parse_slice : op_parser std::back_inserter(nsteps), [](auto s) { return std::abs(s); }); return ins = info.add_instruction( - make_op("step", {{"axes", sd.op.axes}, {"steps", nsteps}}), ins); + make_op("step", {{"axes", sd.axes}, {"steps", nsteps}}), ins); } else return ins; @@ -97,8 +142,8 @@ struct parse_slice : op_parser { slice_desc sd; - // slice can have up to 5 inputs, we first check the 5th one - // to decide whether MIGRAPHX can handle this slice. + // ONNX Slice can have up to 5 inputs, we first check the 5th one + // to decide whether MIGX can handle this slice. if(args.size() == 5) { migraphx::argument step_arg = args.back()->eval(); @@ -108,61 +153,61 @@ struct parse_slice : op_parser if(args.size() >= 4) { - sd.op.axes = sd.insert(args.at(3)); + auto _axes = sd.try_insert(args.at(3)); + if(_axes.empty()) + sd.flags |= slice_input_flags::axes_input; + sd.axes = _axes; } else if(contains(info.attributes, "axes")) { literal s = parser.parse_value(info.attributes.at("axes")); - s.visit([&](auto v) { copy(v, std::back_inserter(sd.op.axes)); }); + s.visit([&](auto v) { copy(v, std::back_inserter(sd.axes)); }); } + // NOTE: goes through range-based dynamic shapes pathway only if(args.size() >= 3) { - auto ends = sd.insert(args.at(2)); - sd.op.ends.assign(ends.begin(), ends.end()); + auto _ends = sd.try_insert(args.at(2)); + if(_ends.empty()) + sd.flags |= slice_input_flags::ends_input; + sd.ends.assign(_ends.begin(), _ends.end()); } else if(contains(info.attributes, "ends")) { literal s = parser.parse_value(info.attributes.at("ends")); - s.visit([&](auto v) { - std::transform(v.begin(), v.end(), std::back_inserter(sd.op.ends), [](auto e) { - return static_cast(e); - }); - }); + s.visit([&](auto v) { copy(v, std::back_inserter(sd.starts)); }); } if(args.size() >= 2) { - auto starts = sd.insert(args.at(1)); - sd.op.starts.assign(starts.begin(), starts.end()); + auto _starts = sd.try_insert(args.at(1)); + if(_starts.empty()) + sd.flags |= slice_input_flags::starts_input; + sd.starts.assign(_starts.begin(), _starts.end()); } else if(contains(info.attributes, "starts")) { literal s = parser.parse_value(info.attributes.at("starts")); - s.visit([&](auto v) { - std::transform(v.begin(), v.end(), std::back_inserter(sd.op.starts), [](auto e) { - return static_cast(e); - }); - }); + s.visit([&](auto v) { copy(v, std::back_inserter(sd.starts)); }); } // data input argument sd.always_insert(args.at(0)); // If axes arg is not given, the default is all of them. - if(sd.op.axes.empty() and sd.op_args.size() <= 3) + if(sd.axes.empty() and sd.op_args.size() <= 3) { std::vector axes(args[0]->get_shape().ndim()); std::iota(axes.begin(), axes.end(), int64_t{0}); - sd.op.axes = axes; + sd.axes = axes; } if(std::any_of(sd.steps.begin(), sd.steps.end(), [](auto s) { return s != 1; })) { - if(sd.op.starts.empty() or sd.op.ends.empty()) + if(sd.starts.empty() or sd.ends.empty()) MIGRAPHX_THROW( "PARSE_SLICE: steps and variable starts and/or ends is not supported"); - if(sd.op.axes.empty()) + if(sd.axes.empty()) MIGRAPHX_THROW("PARSE_SLICE: steps and variable axes is not supported"); } @@ -171,13 +216,13 @@ struct parse_slice : op_parser { if(sd.steps[i] >= 0) continue; - auto start = std::get(sd.op.starts[i]) + 1; + auto start = std::get(sd.starts[i]) + 1; if(start == 0) start = INT_MAX; - sd.op.starts[i] = start; - sd.op.ends[i] = std::get(sd.op.ends[i]) + 1; - sd.raxes.push_back(sd.op.axes[i]); - std::swap(sd.op.starts[i], sd.op.ends[i]); + sd.starts[i] = start; + sd.ends[i] = std::get(sd.ends[i]) + 1; + sd.raxes.push_back(sd.axes[i]); + std::swap(sd.starts[i], sd.ends[i]); } return sd; } diff --git a/test/enum.cpp b/test/enum.cpp index 14c9f313c3d..74a455442de 100644 --- a/test/enum.cpp +++ b/test/enum.cpp @@ -148,6 +148,14 @@ namespace migraphx { // check that the generated to_string(status) is not ambiguous with migraphx::to_string(const T&). // NOLINTNEXTLINE(misc-use-internal-linkage) MIGRAPHX_ENUM(status, ok, busy = 4, done) + +// Bit-flag enums. Declared in the migraphx namespace so that argument-dependent lookup finds the +// operators (which also live in migraphx) from the global-scope test cases below. Two widths +// exercise the selectable underlying type. +// NOLINTNEXTLINE(misc-use-internal-linkage) +MIGRAPHX_BIT_FLAG_ENUM(access, std::uint8_t, none = 0, read = 1 << 0, write = 1 << 1, exec = 1 << 2) +// NOLINTNEXTLINE(misc-use-internal-linkage) +MIGRAPHX_BIT_FLAG_ENUM(wide_flag, std::uint16_t, none = 0, lo = 1 << 0, hi = 1 << 15) } // namespace migraphx TEST_CASE(underlying_values) @@ -307,6 +315,56 @@ TEST_CASE(is_named_enum_trait) EXPECT(not migraphx::is_named_enum{}); } +TEST_CASE(bit_flag_operators) +{ + auto rw = migraphx::access::read | migraphx::access::write; + EXPECT(has_flag(rw, migraphx::access::read)); + EXPECT(has_flag(rw, migraphx::access::write)); + EXPECT(not has_flag(rw, migraphx::access::exec)); + + // & masks, and the result stays the enum type. + EXPECT((rw & migraphx::access::read) == migraphx::access::read); + EXPECT((rw & migraphx::access::exec) == migraphx::access::none); + + rw |= migraphx::access::exec; + EXPECT(has_flag(rw, migraphx::access::exec)); + + // ~ and &= clear a bit. + rw &= ~migraphx::access::write; + EXPECT(not has_flag(rw, migraphx::access::write)); + EXPECT(has_flag(rw, migraphx::access::read)); + + // ^ and ^= toggle a bit. + EXPECT((migraphx::access::read ^ migraphx::access::read) == migraphx::access::none); + auto toggled = migraphx::access::none; + toggled ^= migraphx::access::write; + EXPECT(toggled == migraphx::access::write); +} + +TEST_CASE(bit_flag_width_and_scoped) +{ + // The underlying type is honored, so the storage width is selectable. + EXPECT(sizeof(migraphx::access) == 1); + EXPECT(sizeof(migraphx::wide_flag) == 2); + // A bit that only fits in the wider type round-trips through the underlying value. + EXPECT(static_cast(migraphx::wide_flag::hi) == (1 << 15)); + // It is still a real scoped enum: no implicit conversion to its underlying type. + EXPECT(not std::is_convertible{}); +} + +TEST_CASE(is_bit_flag_trait) +{ + EXPECT(migraphx::is_bit_flag{}); + EXPECT(migraphx::is_bit_flag{}); + // False for named enums, plain enums, and non-enum types: the operators do not leak to them. + EXPECT(not migraphx::is_bit_flag{}); + EXPECT(not migraphx::is_bit_flag{}); + EXPECT(not migraphx::is_bit_flag{}); + EXPECT(not migraphx::is_bit_flag{}); + // And a bit-flag enum does not get the named-enum string helpers. + EXPECT(not migraphx::is_named_enum{}); +} + TEST_CASE(value_stores_named_enum_as_string) { migraphx::value v = green; From 9db601b3571625346713b3ea5534214582cba097 Mon Sep 17 00:00:00 2001 From: charlie Date: Tue, 21 Jul 2026 19:51:58 -0500 Subject: [PATCH 18/42] Retain old behavior but add symbolics support --- src/include/migraphx/op/slice.hpp | 85 ++++++++++++++++++++++--------- src/onnx/parse_slice.cpp | 2 +- 2 files changed, 62 insertions(+), 25 deletions(-) diff --git a/src/include/migraphx/op/slice.hpp b/src/include/migraphx/op/slice.hpp index c2d2e5abb21..e516e2991d6 100644 --- a/src/include/migraphx/op/slice.hpp +++ b/src/include/migraphx/op/slice.hpp @@ -132,13 +132,6 @@ struct slice void check_inputs_and_attributes(std::vector inputs) const { auto input_shape = inputs[0]; - if(axes.size() != starts.size() or starts.size() != ends.size()) - MIGRAPHX_THROW("SLICE: Invalid attributes configuration. Not the same number of " - "dimensions. axes: " + - migraphx::to_string(axes.size()) + - " starts: " + migraphx::to_string(starts.size()) + - " ends: " + migraphx::to_string(ends.size())); - if(inputs.size() == 1) { if(any_sym(starts) or any_sym(ends)) @@ -147,19 +140,13 @@ struct slice MIGRAPHX_THROW("SLICE: Invalid mode for 1 input"); return; } - // check that inputs [1, end) are all 1D, have the same dimension, and are static + // Check that inputs [1, end) are all 1D, have the same dimension, and are static shape. check_shapes{inputs.begin() + 1, inputs.end(), std::string("SLICE: inputs (starts_input, ends_input, axes_input)"), false} .only_dims(1) .same_dims(); - if(inputs.at(1).lens().at(0) != axes.size()) - { - MIGRAPHX_THROW("SLICE: varable input and attributes mismatch: input[1] length (" + - to_string(inputs[1].lens().at(0)) + - ") != attribute number of dimensions (" + to_string(axes.size()) + ")"); - } if(inputs.size() == 2) { std::vector two_input_modes = { @@ -189,6 +176,53 @@ struct slice return; } + // TODO: remove this once range-based dynamic shapes are deprecated + bool use_range_based_logic() const + { + if(mode == slice_mode::starts_input and starts.empty()) + return true; + else if(mode == slice_mode::ends_input and ends.empty()) + return true; + else if(mode == slice_mode::axes_input and axes.empty()) + return true; + else if(mode == slice_mode::starts_ends_input and starts.empty() and ends.empty()) + return true; + else if(mode == slice_mode::starts_axes_input and starts.empty() and axes.empty()) + return true; + else if(mode == slice_mode::ends_axes_input and ends.empty() and axes.empty()) + return true; + else if(mode == slice_mode::starts_ends_axes_input and starts.empty() and ends.empty() and axes.empty()) + return true; + else + return false; + } + + // For when there is a variable input and the associated attribute is not set. + // ex: slice(data, starts) starts = {}, ends = {2, 3}, axes = {0, 1} + // TODO: remove this once range-based dynamic shapes are deprecated + shape range_based_compute_shape_for_two_or_more(shape input_shape) const + { + auto dds = input_shape.to_dynamic().dyn_dims(); + static std::vector has_axes_input = { + slice_mode::axes_input, + slice_mode::starts_axes_input, + slice_mode::ends_axes_input, + slice_mode::starts_ends_axes_input + }; + if(contains(has_axes_input, mode)) + { + std::transform(dds.begin(), dds.end(), dds.begin(), [](const auto& dd) { + return shape::dynamic_dimension{0, dd.get_interval().max}; + }); + } + + std::for_each(axes.cbegin(), axes.cend(), [&](const auto& axis) { + dds.at(axis) = {0, dds.at(axis).get_interval().max}; + }); + return shape{input_shape.type(), dds}; + } + + // Static and symbolic inputs share this path; the result is demoted back to // static when fully fixed (slice is a view). shape symbolic_compute_shape(const shape& s) const @@ -211,17 +245,13 @@ struct slice { check_shapes{inputs, *this, true}.has(1, 2, 3, 4); check_inputs_and_attributes(inputs); - auto input_shape = inputs[0]; - // fallback for range-based dynamic shapes. Only handling 1 arg case. - if(input_shape.dynamic() and not input_shape.symbolic()) + auto input_shape = inputs[0]; + if(inputs.size() == 1 and input_shape.dynamic() and not input_shape.symbolic()) { - if(inputs.size() != 1) - { - MIGRAPHX_THROW( - "SLICE: range-based dynamic input shapes with variable inputs unsupported."); - } + // Fallback for range-based dynamic shapes. // Non-fixed sliced axis: bounds aren't normalized (can be negative or // out-of-bounds), so use a relaxed [0, max] bound. (#5015) + // TODO: remove this once range-based dynamic shapes are deprecated if(std::any_of(axes.begin(), axes.end(), [&](auto axis) { return not input_shape.dyn_dims()[axis].is_fixed(); })) @@ -238,8 +268,15 @@ struct slice dds[axis] = shape::dynamic_dimension{new_lens[axis], new_lens[axis]}; return shape{input_shape.type(), dds}; } - - return symbolic_compute_shape(input_shape); + else if(inputs.size() > 1 and use_range_based_logic()) + { + // TODO: remove this once range-based dynamic shapes are deprecated + return range_based_compute_shape_for_two_or_more(input_shape); + } + else + { + return symbolic_compute_shape(input_shape); + } } /// Calculates the starting offset for the sliced tensor. diff --git a/src/onnx/parse_slice.cpp b/src/onnx/parse_slice.cpp index 6c486bbdfd5..179ab3dcdc3 100644 --- a/src/onnx/parse_slice.cpp +++ b/src/onnx/parse_slice.cpp @@ -175,7 +175,7 @@ struct parse_slice : op_parser else if(contains(info.attributes, "ends")) { literal s = parser.parse_value(info.attributes.at("ends")); - s.visit([&](auto v) { copy(v, std::back_inserter(sd.starts)); }); + s.visit([&](auto v) { copy(v, std::back_inserter(sd.ends)); }); } if(args.size() >= 2) From 7c31007e74d1d13667e14f5c6eabe958f92044d4 Mon Sep 17 00:00:00 2001 From: Shiv Date: Tue, 21 Jul 2026 18:13:25 -0700 Subject: [PATCH 19/42] tidy --- src/include/migraphx/op/resolve_sym_expr.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/include/migraphx/op/resolve_sym_expr.hpp b/src/include/migraphx/op/resolve_sym_expr.hpp index a2c315181db..0966a505761 100644 --- a/src/include/migraphx/op/resolve_sym_expr.hpp +++ b/src/include/migraphx/op/resolve_sym_expr.hpp @@ -60,7 +60,7 @@ struct resolve_sym_expr std::string name() const { return "resolve_sym_expr"; } - shape compute_shape(std::vector inputs) const + shape compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this}.has(symbols.size()).nelements(1); return shape{std::vector(exprs.size(), shape{shape::int64_type, {1}})}; From 0e0afa82125026ad21b84469bf06dfc473d18596 Mon Sep 17 00:00:00 2001 From: charlie Date: Wed, 22 Jul 2026 13:27:10 -0500 Subject: [PATCH 20/42] Slice updates and tests --- src/include/migraphx/op/slice.hpp | 67 +++- src/onnx/parse_slice.cpp | 14 +- test/gpu/dyn_slice_lowering.cpp | 14 +- .../parse/slice_var_input_default_steps.cpp | 3 +- test/onnx/parse/slice_var_input_dyn0.cpp | 7 +- test/onnx/parse/slice_var_input_dyn1.cpp | 3 +- test/onnx/parse/slice_var_input_static0.cpp | 6 +- test/onnx/parse/slice_var_input_static1.cpp | 3 +- test/op_shape_test.cpp | 352 +++++++++++------- test/ref/slice.cpp | 58 ++- 10 files changed, 345 insertions(+), 182 deletions(-) diff --git a/src/include/migraphx/op/slice.hpp b/src/include/migraphx/op/slice.hpp index e516e2991d6..34dc867f544 100644 --- a/src/include/migraphx/op/slice.hpp +++ b/src/include/migraphx/op/slice.hpp @@ -74,6 +74,8 @@ struct slice ends_axes_input, starts_ends_axes_input); + friend std::ostream& operator<<(std::ostream& os, slice_mode v) { return os << to_string(v); } + std::vector axes{}; std::vector starts{}; std::vector ends{}; @@ -132,6 +134,18 @@ struct slice void check_inputs_and_attributes(std::vector inputs) const { auto input_shape = inputs[0]; + // All set (non-empty) bound attributes must agree on the number of sliced axes. + // A variable (input-provided) bound leaves its attribute empty, so empty attrs are skipped. + std::size_t attr_size = 0; + for(auto s : {axes.size(), starts.size(), ends.size()}) + { + if(s == 0) + continue; + if(attr_size == 0) + attr_size = s; + else if(s != attr_size) + MIGRAPHX_THROW("SLICE: set starts/ends/axes attributes must have the same length"); + } if(inputs.size() == 1) { if(any_sym(starts) or any_sym(ends)) @@ -149,19 +163,40 @@ struct slice .same_dims(); if(inputs.size() == 2) { - std::vector two_input_modes = { - slice_mode::starts_input, slice_mode::ends_input, slice_mode::axes_input}; - if(not contains(two_input_modes, mode)) + std::vector two_input_modes_not_axes = {slice_mode::starts_input, slice_mode::ends_input}; + if(contains(two_input_modes_not_axes, mode)) + { + if(inputs[1].lens()[0] != axes.size()) + MIGRAPHX_THROW("SLICE: input length (" + migraphx::to_string(inputs[1].lens()[0]) + ") does not match attribute length (" + migraphx::to_string(axes.size()) + ")"); + } + else if(mode == slice_mode::axes_input) + { + if(inputs[1].lens()[0] != starts.size()) + MIGRAPHX_THROW("SLICE: input length (" + migraphx::to_string(inputs[1].lens()[0]) + ") does not match attribute length (" + migraphx::to_string(starts.size()) + ")"); + } + else { MIGRAPHX_THROW("SLICE: Invalid mode for 2 inputs"); } } else if(inputs.size() == 3) { - std::vector three_input_modes = {slice_mode::starts_ends_input, - slice_mode::starts_axes_input, - slice_mode::ends_axes_input}; - if(not contains(three_input_modes, mode)) + if(mode == slice_mode::starts_ends_input) + { + if(inputs[1].lens()[0] != axes.size()) + MIGRAPHX_THROW("SLICE: input length (" + migraphx::to_string(inputs[1].lens()[0]) + ") does not match attribute length (" + migraphx::to_string(axes.size()) + ")"); + } + else if(mode == slice_mode::starts_axes_input) + { + if(inputs[1].lens()[0] != ends.size()) + MIGRAPHX_THROW("SLICE: input length (" + migraphx::to_string(inputs[1].lens()[0]) + ") does not match attribute length (" + migraphx::to_string(ends.size()) + ")"); + } + else if(mode == slice_mode::ends_axes_input) + { + if(inputs[1].lens()[0] != starts.size()) + MIGRAPHX_THROW("SLICE: input length (" + migraphx::to_string(inputs[1].lens()[0]) + ") does not match attribute length (" + migraphx::to_string(starts.size()) + ")"); + } + else { MIGRAPHX_THROW("SLICE: Invalid mode for 3 inputs"); } @@ -227,7 +262,8 @@ struct slice // static when fully fixed (slice is a view). shape symbolic_compute_shape(const shape& s) const { - assert(starts.size() == axes.size() and ends.size() == axes.size()); + if(starts.size() != axes.size() or ends.size() != axes.size()) + MIGRAPHX_THROW("SLICE: Attribute sizes do not match for symbolic_compute_shape()"); auto sym_in = s.to_symbolic(); auto dds = sym_in.dyn_dims(); auto start_exprs = to_sym_exprs(starts); @@ -396,13 +432,16 @@ struct slice else { std::unordered_map> norm_inputs; + // Attribute-provided dims are passed as their concrete int values (via to_ints) so they + // are re-normalized/clamped against the runtime input shape, just like the runtime + // inputs. Symbolic bounds only ever appear on the input-provided dims. if(mode == slice_mode::starts_input) { args[1].visit([&](auto starts_input) { norm_inputs = normalize_starts_ends_axes(input_shape, starts_input.template to_vector(), - this->ends, + to_ints(this->ends), this->axes); }); } @@ -411,7 +450,7 @@ struct slice args[1].visit([&](auto ends_input) { norm_inputs = normalize_starts_ends_axes(input_shape, - this->starts, + to_ints(this->starts), ends_input.template to_vector(), this->axes); }); @@ -421,8 +460,8 @@ struct slice args[1].visit([&](auto axes_input) { norm_inputs = normalize_starts_ends_axes(input_shape, - this->starts, - this->ends, + to_ints(this->starts), + to_ints(this->ends), axes_input.template to_vector()); }); } @@ -443,7 +482,7 @@ struct slice norm_inputs = normalize_starts_ends_axes(input_shape, starts_input.template to_vector(), - this->ends, + to_ints(this->ends), axes_input.template to_vector()); }); } @@ -452,7 +491,7 @@ struct slice visit_all(args[1], args[2])([&](auto ends_input, auto axes_input) { norm_inputs = normalize_starts_ends_axes(input_shape, - this->starts, + to_ints(this->starts), ends_input.template to_vector(), axes_input.template to_vector()); }); diff --git a/src/onnx/parse_slice.cpp b/src/onnx/parse_slice.cpp index 179ab3dcdc3..72f071fb54b 100644 --- a/src/onnx/parse_slice.cpp +++ b/src/onnx/parse_slice.cpp @@ -54,7 +54,7 @@ struct parse_slice : op_parser std::vector ends; std::vector steps; std::vector raxes; - slice_input_flags flags; + slice_input_flags flags = slice_input_flags::none; void always_insert(instruction_ref arg) { op_args.insert(op_args.begin(), arg); } @@ -175,7 +175,11 @@ struct parse_slice : op_parser else if(contains(info.attributes, "ends")) { literal s = parser.parse_value(info.attributes.at("ends")); - s.visit([&](auto v) { copy(v, std::back_inserter(sd.ends)); }); + s.visit([&](auto v) { + std::transform(v.begin(), v.end(), std::back_inserter(sd.ends), [](auto e) { + return static_cast(e); + }); + }); } if(args.size() >= 2) @@ -188,7 +192,11 @@ struct parse_slice : op_parser else if(contains(info.attributes, "starts")) { literal s = parser.parse_value(info.attributes.at("starts")); - s.visit([&](auto v) { copy(v, std::back_inserter(sd.starts)); }); + s.visit([&](auto v) { + std::transform(v.begin(), v.end(), std::back_inserter(sd.starts), [](auto e) { + return static_cast(e); + }); + }); } // data input argument diff --git a/test/gpu/dyn_slice_lowering.cpp b/test/gpu/dyn_slice_lowering.cpp index 11d743012af..32c55950635 100644 --- a/test/gpu/dyn_slice_lowering.cpp +++ b/test/gpu/dyn_slice_lowering.cpp @@ -49,8 +49,11 @@ TEST_CASE(dyn_slice_lowering_runtime_inputs) auto data = m1.add_parameter("data", data_s); auto starts = m1.add_parameter("starts", idx_s); auto ends = m1.add_parameter("ends", idx_s); - auto sl = - m1.add_instruction(migraphx::make_op("slice", {{"axes", {2}}}), data, starts, ends); + auto sl = m1.add_instruction( + migraphx::make_op("slice", {{"axes", {2}}, {"mode", "starts_ends_input"}}), + data, + starts, + ends); m1.add_return({sl}); } run_lowering(m1); @@ -64,8 +67,11 @@ TEST_CASE(dyn_slice_lowering_runtime_inputs) auto copy_ends = m2.add_instruction(migraphx::make_op("hip::copy_from_gpu"), ends); auto sync = m2.add_instruction(migraphx::make_op("hip::sync_stream"), copy_starts, copy_ends); - auto sl = - m2.add_instruction(migraphx::make_op("slice", {{"axes", {2}}}), data, sync, copy_ends); + auto sl = m2.add_instruction( + migraphx::make_op("slice", {{"axes", {2}}, {"mode", "starts_ends_input"}}), + data, + sync, + copy_ends); m2.add_return({sl}); } EXPECT(m1 == m2); diff --git a/test/onnx/parse/slice_var_input_default_steps.cpp b/test/onnx/parse/slice_var_input_default_steps.cpp index e432b124f27..d3caa52e22e 100644 --- a/test/onnx/parse/slice_var_input_default_steps.cpp +++ b/test/onnx/parse/slice_var_input_default_steps.cpp @@ -34,7 +34,8 @@ TEST_CASE(slice_var_input_default_steps) auto ends = mm->add_parameter("ends", migraphx::shape{migraphx::shape::int64_type, {2}}); auto axes = mm->add_parameter("axes", migraphx::shape{migraphx::shape::int64_type, {2}}); mm->add_literal({{migraphx::shape::int64_type, {2}}, {1, 1}}); - auto ret = mm->add_instruction(migraphx::make_op("slice"), data, starts, ends, axes); + auto ret = mm->add_instruction( + migraphx::make_op("slice", {{"mode", "starts_ends_axes_input"}}), data, starts, ends, axes); mm->add_return({ret}); migraphx::onnx_options options; diff --git a/test/onnx/parse/slice_var_input_dyn0.cpp b/test/onnx/parse/slice_var_input_dyn0.cpp index ae4ab5739f8..11f64ef960a 100644 --- a/test/onnx/parse/slice_var_input_dyn0.cpp +++ b/test/onnx/parse/slice_var_input_dyn0.cpp @@ -32,8 +32,11 @@ TEST_CASE(slice_var_input_dyn0) mm->add_parameter("data", migraphx::shape{migraphx::shape::float_type, {{3, 8}, {2, 2}}}); auto starts = mm->add_parameter("starts", migraphx::shape{migraphx::shape::int32_type, {2}}); auto ends = mm->add_parameter("ends", migraphx::shape{migraphx::shape::int32_type, {2}}); - auto ret = - mm->add_instruction(migraphx::make_op("slice", {{"axes", {0, 1}}}), data, starts, ends); + auto ret = mm->add_instruction( + migraphx::make_op("slice", {{"axes", {0, 1}}, {"mode", "starts_ends_input"}}), + data, + starts, + ends); mm->add_return({ret}); migraphx::onnx_options options; diff --git a/test/onnx/parse/slice_var_input_dyn1.cpp b/test/onnx/parse/slice_var_input_dyn1.cpp index 1fe4681cb57..c660924a622 100644 --- a/test/onnx/parse/slice_var_input_dyn1.cpp +++ b/test/onnx/parse/slice_var_input_dyn1.cpp @@ -33,7 +33,8 @@ TEST_CASE(slice_var_input_dyn1) auto starts = mm->add_parameter("starts", migraphx::shape{migraphx::shape::int32_type, {2}}); auto ends = mm->add_parameter("ends", migraphx::shape{migraphx::shape::int32_type, {2}}); auto axes = mm->add_parameter("axes", migraphx::shape{migraphx::shape::int32_type, {2}}); - auto ret = mm->add_instruction(migraphx::make_op("slice"), data, starts, ends, axes); + auto ret = mm->add_instruction( + migraphx::make_op("slice", {{"mode", "starts_ends_axes_input"}}), data, starts, ends, axes); mm->add_return({ret}); migraphx::onnx_options options; diff --git a/test/onnx/parse/slice_var_input_static0.cpp b/test/onnx/parse/slice_var_input_static0.cpp index 2bea481379f..650a0c5783c 100644 --- a/test/onnx/parse/slice_var_input_static0.cpp +++ b/test/onnx/parse/slice_var_input_static0.cpp @@ -31,7 +31,11 @@ TEST_CASE(slice_var_input_static0) auto data = mm->add_parameter("data", migraphx::shape{migraphx::shape::float_type, {3, 2}}); auto starts = mm->add_parameter("starts", migraphx::shape{migraphx::shape::int32_type, {2}}); auto ends = mm->add_parameter("ends", migraphx::shape{migraphx::shape::int32_type, {2}}); - mm->add_instruction(migraphx::make_op("slice", {{"axes", {0, 1}}}), data, starts, ends); + mm->add_instruction( + migraphx::make_op("slice", {{"axes", {0, 1}}, {"mode", "starts_ends_input"}}), + data, + starts, + ends); auto prog = optimize_onnx("slice_var_input_static0.onnx"); EXPECT(p == prog); diff --git a/test/onnx/parse/slice_var_input_static1.cpp b/test/onnx/parse/slice_var_input_static1.cpp index d98a1429499..f9024cecaf6 100644 --- a/test/onnx/parse/slice_var_input_static1.cpp +++ b/test/onnx/parse/slice_var_input_static1.cpp @@ -32,7 +32,8 @@ TEST_CASE(slice_var_input_static1) auto starts = mm->add_parameter("starts", migraphx::shape{migraphx::shape::int64_type, {2}}); auto ends = mm->add_parameter("ends", migraphx::shape{migraphx::shape::int64_type, {2}}); auto axes = mm->add_parameter("axes", migraphx::shape{migraphx::shape::int64_type, {2}}); - mm->add_instruction(migraphx::make_op("slice"), data, starts, ends, axes); + mm->add_instruction( + migraphx::make_op("slice", {{"mode", "starts_ends_axes_input"}}), data, starts, ends, axes); auto prog = optimize_onnx("slice_var_input_static1.onnx"); EXPECT(p == prog); diff --git a/test/op_shape_test.cpp b/test/op_shape_test.cpp index 2ab738789ba..387fceddeaf 100644 --- a/test/op_shape_test.cpp +++ b/test/op_shape_test.cpp @@ -4906,18 +4906,11 @@ TEST_CASE(slice_var_inputs_static_shape0) // attr ends and axes set; inputs are (data, input_starts) migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; migraphx::shape starts{migraphx::shape::int64_type, {2}}; - expect_shape(migraphx::shape{migraphx::shape::float_type, {{3, 3}, {0, 4}, {0, 4}}}, - migraphx::make_op("slice", {{"ends", {2, 3}}, {"axes", {1, 2}}}), - input, - starts); -} - -TEST_CASE(slice_var_inputs_static_mismatch_error0) -{ - migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; - migraphx::shape starts{migraphx::shape::int64_type, {2}}; - throws_shape( - migraphx::make_op("slice", {{"ends", {2, 3, 4}}, {"axes", {0, 1, 2}}}), input, starts); + expect_shape( + migraphx::shape{migraphx::shape::float_type, {{3, 3}, {0, 4}, {0, 4}}}, + migraphx::make_op("slice", {{"ends", {2, 3}}, {"axes", {1, 2}}, {"mode", "starts_input"}}), + input, + starts); } TEST_CASE(slice_var_inputs_static_shape1) @@ -4925,18 +4918,11 @@ TEST_CASE(slice_var_inputs_static_shape1) // attr starts and axes set; inputs are (data, input_ends) migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; migraphx::shape ends{migraphx::shape::int64_type, {2}}; - expect_shape(migraphx::shape{migraphx::shape::float_type, {{3, 3}, {0, 4}, {0, 4}}}, - migraphx::make_op("slice", {{"starts", {0, 1}}, {"axes", {1, 2}}}), - input, - ends); -} - -TEST_CASE(slice_var_inputs_static_mismatch_error1) -{ - migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; - migraphx::shape ends{migraphx::shape::int64_type, {2}}; - throws_shape( - migraphx::make_op("slice", {{"starts", {0, 1, 2}}, {"axes", {0, 1, 2}}}), input, ends); + expect_shape( + migraphx::shape{migraphx::shape::float_type, {{3, 3}, {0, 4}, {0, 4}}}, + migraphx::make_op("slice", {{"starts", {0, 1}}, {"axes", {1, 2}}, {"mode", "ends_input"}}), + input, + ends); } TEST_CASE(slice_var_inputs_static_shape2) @@ -4944,18 +4930,11 @@ TEST_CASE(slice_var_inputs_static_shape2) // attr starts and ends set; inputs are (data, input_axes) migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; migraphx::shape axes{migraphx::shape::int64_type, {2}}; - expect_shape(migraphx::shape{migraphx::shape::float_type, {{0, 3}, {0, 4}, {0, 4}}}, - migraphx::make_op("slice", {{"starts", {0, 1}}, {"ends", {1, 2}}}), - input, - axes); -} - -TEST_CASE(slice_var_inputs_static_mismatch_error2) -{ - migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; - migraphx::shape axes{migraphx::shape::int64_type, {2}}; - throws_shape( - migraphx::make_op("slice", {{"starts", {0, 1, 2}}, {"ends", {3, 4, 4}}}), input, axes); + expect_shape( + migraphx::shape{migraphx::shape::float_type, {{0, 3}, {0, 4}, {0, 4}}}, + migraphx::make_op("slice", {{"starts", {0, 1}}, {"ends", {1, 2}}, {"mode", "axes_input"}}), + input, + axes); } TEST_CASE(slice_var_inputs_static_shape3) @@ -4965,20 +4944,12 @@ TEST_CASE(slice_var_inputs_static_shape3) migraphx::shape starts{migraphx::shape::int64_type, {2}}; migraphx::shape ends{migraphx::shape::int64_type, {2}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{3, 3}, {0, 4}, {0, 4}}}, - migraphx::make_op("slice", {{"axes", {1, 2}}}), + migraphx::make_op("slice", {{"axes", {1, 2}}, {"mode", "starts_ends_input"}}), input, starts, ends); } -TEST_CASE(slice_var_inputs_static_mismatch_error3) -{ - migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; - migraphx::shape starts{migraphx::shape::int64_type, {2}}; - migraphx::shape ends{migraphx::shape::int64_type, {2}}; - throws_shape(migraphx::make_op("slice", {{"axes", {0, 1, 2}}}), input, starts, ends); -} - TEST_CASE(slice_var_inputs_static_shape4) { // attr ends set; inputs are (data, input_starts, input_axes) @@ -4986,20 +4957,12 @@ TEST_CASE(slice_var_inputs_static_shape4) migraphx::shape starts{migraphx::shape::int64_type, {2}}; migraphx::shape axes{migraphx::shape::int64_type, {2}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{0, 3}, {0, 4}, {0, 4}}}, - migraphx::make_op("slice", {{"ends", {3, 4}}}), + migraphx::make_op("slice", {{"ends", {3, 4}}, {"mode", "starts_axes_input"}}), input, starts, axes); } -TEST_CASE(slice_var_inputs_static_mismatch_error4) -{ - migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; - migraphx::shape starts{migraphx::shape::int64_type, {2}}; - migraphx::shape axes{migraphx::shape::int64_type, {2}}; - throws_shape(migraphx::make_op("slice", {{"ends", {3, 3, 3}}}), input, starts, axes); -} - TEST_CASE(slice_var_inputs_static_shape5) { // attr starts set; inputs are (data, input_ends, input_axes) @@ -5007,20 +4970,12 @@ TEST_CASE(slice_var_inputs_static_shape5) migraphx::shape ends{migraphx::shape::int64_type, {2}}; migraphx::shape axes{migraphx::shape::int64_type, {2}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{0, 3}, {0, 4}, {0, 4}}}, - migraphx::make_op("slice", {{"starts", {0, 2}}}), + migraphx::make_op("slice", {{"starts", {0, 2}}, {"mode", "ends_axes_input"}}), input, ends, axes); } -TEST_CASE(slice_var_inputs_static_mismatch_error5) -{ - migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; - migraphx::shape ends{migraphx::shape::int64_type, {2}}; - migraphx::shape axes{migraphx::shape::int64_type, {2}}; - throws_shape(migraphx::make_op("slice", {{"starts", {0, 1, 2}}}), input, ends, axes); -} - TEST_CASE(slice_var_inputs_static_shape6) { migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; @@ -5028,154 +4983,251 @@ TEST_CASE(slice_var_inputs_static_shape6) migraphx::shape ends{migraphx::shape::int64_type, {2}}; migraphx::shape axes{migraphx::shape::int64_type, {2}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{0, 3}, {0, 4}, {0, 4}}}, - migraphx::make_op("slice"), + migraphx::make_op("slice", {{"mode", "starts_ends_axes_input"}}), input, starts, ends, axes); } -TEST_CASE(slice_var_inputs_static_mismatch_error6) +TEST_CASE(slice_var_inputs_dyn_shape0) { - migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; + // attr ends and axes set; inputs are (data, input_starts) + migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {4, 6}, {4, 6}}}; migraphx::shape starts{migraphx::shape::int64_type, {2}}; - migraphx::shape ends{migraphx::shape::int64_type, {2}}; - migraphx::shape axes{migraphx::shape::int64_type, {3}}; - throws_shape(migraphx::make_op("slice"), input, starts, ends, axes); + expect_shape( + migraphx::shape{migraphx::shape::float_type, {{3, 6}, {0, 6}, {0, 6}}}, + migraphx::make_op("slice", {{"ends", {2, 3}}, {"axes", {1, 2}}, {"mode", "starts_input"}}), + input, + starts); } -TEST_CASE(slice_var_inputs_dyn_shape0) +TEST_CASE(slice_var_inputs_dyn_shape1) { - // attr ends and axes set; inputs are (data, input_starts) + // attr starts and axes set; inputs are (data, input_ends) migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {4, 6}, {4, 6}}}; - migraphx::shape starts{migraphx::shape::int64_type, {2}}; - expect_shape(migraphx::shape{migraphx::shape::float_type, {{3, 6}, {0, 6}, {0, 6}}}, - migraphx::make_op("slice", {{"ends", {2, 3}}, {"axes", {1, 2}}}), - input, - starts); + migraphx::shape ends{migraphx::shape::int64_type, {2}}; + expect_shape( + migraphx::shape{migraphx::shape::float_type, {{3, 6}, {0, 6}, {0, 6}}}, + migraphx::make_op("slice", {{"starts", {0, 1}}, {"axes", {1, 2}}, {"mode", "ends_input"}}), + input, + ends); } -TEST_CASE(slice_var_inputs_dyn_mismatch_error0) +TEST_CASE(slice_var_inputs_dyn_shape2) { + // attr starts and ends set; inputs are (data, input_axes) migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {4, 6}, {4, 6}}}; - migraphx::shape starts{migraphx::shape::int64_type, {2}}; - throws_shape( - migraphx::make_op("slice", {{"ends", {2, 3, 4}}, {"axes", {0, 1, 2}}}), input, starts); + migraphx::shape axes{migraphx::shape::int64_type, {2}}; + expect_shape( + migraphx::shape{migraphx::shape::float_type, {{0, 6}, {0, 6}, {0, 6}}}, + migraphx::make_op("slice", {{"starts", {0, 1}}, {"ends", {8, 8}}, {"mode", "axes_input"}}), + input, + axes); } -TEST_CASE(slice_var_inputs_dyn_shape1) +TEST_CASE(slice_var_inputs_dyn_shape3) { - // attr starts and axes set; inputs are (data, input_ends) + // attr axes set; inputs are (data, input_starts, input_ends) migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {4, 6}, {4, 6}}}; + migraphx::shape starts{migraphx::shape::int64_type, {2}}; migraphx::shape ends{migraphx::shape::int64_type, {2}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{3, 6}, {0, 6}, {0, 6}}}, - migraphx::make_op("slice", {{"starts", {0, 1}}, {"axes", {1, 2}}}), + migraphx::make_op("slice", {{"axes", {1, 2}}, {"mode", "starts_ends_input"}}), input, + starts, ends); } -TEST_CASE(slice_var_inputs_dyn_mismatch_error1) +TEST_CASE(slice_var_inputs_dyn_shape4) { + // attr ends set; inputs are (data, input_starts, input_axes) migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {4, 6}, {4, 6}}}; - migraphx::shape ends{migraphx::shape::int64_type, {2}}; - throws_shape( - migraphx::make_op("slice", {{"starts", {0, 1, 2}}, {"axes", {0, 1, 2}}}), input, ends); + migraphx::shape starts{migraphx::shape::int64_type, {2}}; + migraphx::shape axes{migraphx::shape::int64_type, {2}}; + expect_shape(migraphx::shape{migraphx::shape::float_type, {{0, 6}, {0, 6}, {0, 6}}}, + migraphx::make_op("slice", {{"ends", {3, 4}}, {"mode", "starts_axes_input"}}), + input, + starts, + axes); } -TEST_CASE(slice_var_inputs_dyn_shape2) +TEST_CASE(slice_var_inputs_dyn_shape5) { - // attr starts and ends set; inputs are (data, input_axes) + // attr starts set; inputs are (data, input_ends, input_axes) migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {4, 6}, {4, 6}}}; + migraphx::shape ends{migraphx::shape::int64_type, {2}}; migraphx::shape axes{migraphx::shape::int64_type, {2}}; expect_shape(migraphx::shape{migraphx::shape::float_type, {{0, 6}, {0, 6}, {0, 6}}}, - migraphx::make_op("slice", {{"starts", {0, 1}}, {"ends", {8, 8}}}), + migraphx::make_op("slice", {{"starts", {0, 2}}, {"mode", "ends_axes_input"}}), input, + ends, axes); } -TEST_CASE(slice_var_inputs_dyn_mismatch_error2) +TEST_CASE(slice_var_inputs_dyn_shape6) { - migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {4, 6}, {4, 6}}}; + migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {2, 4, {2, 4}}, {2, 4, {2, 4}}}}; + migraphx::shape starts{migraphx::shape::int64_type, {2}}; + migraphx::shape ends{migraphx::shape::int64_type, {2}}; migraphx::shape axes{migraphx::shape::int64_type, {2}}; - throws_shape( - migraphx::make_op("slice", {{"starts", {0, 1, 2}}, {"ends", {3, 4, 4}}}), input, axes); + expect_shape(migraphx::shape{migraphx::shape::float_type, {{0, 6}, {0, 4}, {0, 4}}}, + migraphx::make_op("slice", {{"mode", "starts_ends_axes_input"}}), + input, + starts, + ends, + axes); } -TEST_CASE(slice_var_inputs_dyn_shape3) +TEST_CASE(slice_var_inputs_static_mismatch_error0) { - // attr axes set; inputs are (data, input_starts, input_ends) - migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {4, 6}, {4, 6}}}; + migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; migraphx::shape starts{migraphx::shape::int64_type, {2}}; + throws_shape( + migraphx::make_op("slice", + {{"ends", {2, 3, 4}}, {"axes", {0, 1, 2}}, {"mode", "starts_input"}}), + input, + starts); +} + +TEST_CASE(slice_var_inputs_static_mismatch_error1) +{ + migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; migraphx::shape ends{migraphx::shape::int64_type, {2}}; - expect_shape(migraphx::shape{migraphx::shape::float_type, {{3, 6}, {0, 6}, {0, 6}}}, - migraphx::make_op("slice", {{"axes", {1, 2}}}), - input, - starts, - ends); + throws_shape( + migraphx::make_op("slice", + {{"starts", {0, 1, 2}}, {"axes", {0, 1, 2}}, {"mode", "ends_input"}}), + input, + ends); } -TEST_CASE(slice_var_inputs_dyn_mismatch_error3) +TEST_CASE(slice_var_inputs_static_mismatch_error2) { - migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {4, 6}, {4, 6}}}; + migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; + migraphx::shape axes{migraphx::shape::int64_type, {2}}; + throws_shape( + migraphx::make_op("slice", + {{"starts", {0, 1, 2}}, {"ends", {3, 4, 4}}, {"mode", "axes_input"}}), + input, + axes); +} + +TEST_CASE(slice_var_inputs_static_mismatch_error3) +{ + migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; migraphx::shape starts{migraphx::shape::int64_type, {2}}; migraphx::shape ends{migraphx::shape::int64_type, {2}}; - throws_shape(migraphx::make_op("slice", {{"axes", {0, 1, 2}}}), input, starts, ends); + throws_shape( + migraphx::make_op("slice", {{"axes", {0, 1, 2}}, {"mode", "starts_ends_input"}}), + input, + starts, + ends); } -TEST_CASE(slice_var_inputs_dyn_shape4) +TEST_CASE(slice_var_inputs_static_mismatch_error4) { - // attr ends set; inputs are (data, input_starts, input_axes) - migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {4, 6}, {4, 6}}}; + migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; migraphx::shape starts{migraphx::shape::int64_type, {2}}; migraphx::shape axes{migraphx::shape::int64_type, {2}}; - expect_shape(migraphx::shape{migraphx::shape::float_type, {{0, 6}, {0, 6}, {0, 6}}}, - migraphx::make_op("slice", {{"ends", {3, 4}}}), + throws_shape( + migraphx::make_op("slice", {{"ends", {3, 3, 3}}, {"mode", "starts_axes_input"}}), + input, + starts, + axes); +} + +TEST_CASE(slice_var_inputs_static_mismatch_error5) +{ + migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; + migraphx::shape ends{migraphx::shape::int64_type, {2}}; + migraphx::shape axes{migraphx::shape::int64_type, {2}}; + throws_shape( + migraphx::make_op("slice", {{"starts", {0, 1, 2}}, {"mode", "ends_axes_input"}}), + input, + ends, + axes); +} + +TEST_CASE(slice_var_inputs_static_mismatch_error6) +{ + migraphx::shape input{migraphx::shape::float_type, {3, 4, 4}}; + migraphx::shape starts{migraphx::shape::int64_type, {2}}; + migraphx::shape ends{migraphx::shape::int64_type, {2}}; + migraphx::shape axes{migraphx::shape::int64_type, {3}}; + throws_shape(migraphx::make_op("slice", {{"mode", "starts_ends_axes_input"}}), input, starts, + ends, axes); } -TEST_CASE(slice_var_inputs_dyn_mismatch_error4) +TEST_CASE(slice_var_inputs_dyn_mismatch_error0) { migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {4, 6}, {4, 6}}}; migraphx::shape starts{migraphx::shape::int64_type, {2}}; - migraphx::shape axes{migraphx::shape::int64_type, {2}}; - throws_shape(migraphx::make_op("slice", {{"ends", {3, 3, 3}}}), input, starts, axes); + throws_shape( + migraphx::make_op("slice", + {{"ends", {2, 3, 4}}, {"axes", {0, 1, 2}}, {"mode", "starts_input"}}), + input, + starts); } -TEST_CASE(slice_var_inputs_dyn_shape5) +TEST_CASE(slice_var_inputs_dyn_mismatch_error1) { - // attr starts set; inputs are (data, input_ends, input_axes) migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {4, 6}, {4, 6}}}; migraphx::shape ends{migraphx::shape::int64_type, {2}}; + throws_shape( + migraphx::make_op("slice", + {{"starts", {0, 1, 2}}, {"axes", {0, 1, 2}}, {"mode", "ends_input"}}), + input, + ends); +} + +TEST_CASE(slice_var_inputs_dyn_mismatch_error2) +{ + migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {4, 6}, {4, 6}}}; migraphx::shape axes{migraphx::shape::int64_type, {2}}; - expect_shape(migraphx::shape{migraphx::shape::float_type, {{0, 6}, {0, 6}, {0, 6}}}, - migraphx::make_op("slice", {{"starts", {0, 2}}}), - input, - ends, - axes); + throws_shape( + migraphx::make_op("slice", + {{"starts", {0, 1, 2}}, {"ends", {3, 4, 4}}, {"mode", "axes_input"}}), + input, + axes); } -TEST_CASE(slice_var_inputs_dyn_mismatch_error5) +TEST_CASE(slice_var_inputs_dyn_mismatch_error3) { migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {4, 6}, {4, 6}}}; + migraphx::shape starts{migraphx::shape::int64_type, {2}}; migraphx::shape ends{migraphx::shape::int64_type, {2}}; - migraphx::shape axes{migraphx::shape::int64_type, {2}}; - throws_shape(migraphx::make_op("slice", {{"starts", {0, 1, 2}}}), input, ends, axes); + throws_shape( + migraphx::make_op("slice", {{"axes", {0, 1, 2}}, {"mode", "starts_ends_input"}}), + input, + starts, + ends); } -TEST_CASE(slice_var_inputs_dyn_shape6) +TEST_CASE(slice_var_inputs_dyn_mismatch_error4) { - migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {2, 4, {2, 4}}, {2, 4, {2, 4}}}}; + migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {4, 6}, {4, 6}}}; migraphx::shape starts{migraphx::shape::int64_type, {2}}; + migraphx::shape axes{migraphx::shape::int64_type, {2}}; + throws_shape( + migraphx::make_op("slice", {{"ends", {3, 3, 3}}, {"mode", "starts_axes_input"}}), + input, + starts, + axes); +} + +TEST_CASE(slice_var_inputs_dyn_mismatch_error5) +{ + migraphx::shape input{migraphx::shape::float_type, {{3, 6}, {4, 6}, {4, 6}}}; migraphx::shape ends{migraphx::shape::int64_type, {2}}; migraphx::shape axes{migraphx::shape::int64_type, {2}}; - expect_shape(migraphx::shape{migraphx::shape::float_type, {{0, 6}, {0, 4}, {0, 4}}}, - migraphx::make_op("slice"), - input, - starts, - ends, - axes); + throws_shape( + migraphx::make_op("slice", {{"starts", {0, 1, 2}}, {"mode", "ends_axes_input"}}), + input, + ends, + axes); } TEST_CASE(slice_var_inputs_dyn_mismatch_error6) @@ -5184,7 +5236,11 @@ TEST_CASE(slice_var_inputs_dyn_mismatch_error6) migraphx::shape starts{migraphx::shape::int64_type, {2}}; migraphx::shape ends{migraphx::shape::int64_type, {2}}; migraphx::shape axes{migraphx::shape::int64_type, {3}}; - throws_shape(migraphx::make_op("slice"), input, starts, ends, axes); + throws_shape(migraphx::make_op("slice", {{"mode", "starts_ends_axes_input"}}), + input, + starts, + ends, + axes); } TEST_CASE(slice_dyn_shape0) @@ -5430,17 +5486,20 @@ TEST_CASE(slice_sym_clamped_and_negative_bounds) TEST_CASE(slice_sym_symbolic_end_static_input) { // Static input + symbolic end bound: output is symbolic (not fixed -> not demoted to static). + // Symbolic attributes require 2+ inputs, so the runtime end value is supplied as an input. auto n = var("n", {1, 16}); auto op = migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, - {"ends", migraphx::value::array{migraphx::to_value(dd{n})}}}); + {"ends", migraphx::value::array{migraphx::to_value(dd{n})}}, + {"mode", "ends_input"}}); // end=n is clamped to the axis length 10: dim = min(n, 10). migraphx::shape sin{migraphx::shape::float_type, {10}}; + migraphx::shape ends_in{migraphx::shape::int64_type, {1}}; migraphx::shape sout{ migraphx::shape::float_type, {dd{migraphx::sym::min(n, lit(10))}}, {lit(1)}}; - expect_shape(sout, op, sin); + expect_shape(sout, op, sin, ends_in); EXPECT(sout.symbolic()); EXPECT(not sout.is_fixed()); EXPECT(sout.to_static({{n, 7}}) == migraphx::shape{migraphx::shape::float_type, {7}, {1}}); @@ -5458,12 +5517,14 @@ TEST_CASE(slice_sym_symbolic_bounds) auto op = migraphx::make_op("slice", {{"axes", {1}}, {"starts", {2}}, - {"ends", migraphx::value::array{migraphx::to_value(dd{n})}}}); + {"ends", migraphx::value::array{migraphx::to_value(dd{n})}}, + {"mode", "ends_input"}}); migraphx::shape sin{migraphx::shape::float_type, {dd{m}, dd{lit(12)}}}; + migraphx::shape ends_in{migraphx::shape::int64_type, {1}}; migraphx::shape sout{migraphx::shape::float_type, {dd{m}, dd{migraphx::sym::min(n, lit(12)) - lit(2)}}, sin.dyn_strides()}; - expect_shape(sout, op, sin); + expect_shape(sout, op, sin, ends_in); EXPECT(sout.symbolic()); EXPECT(not sout.is_fixed()); EXPECT(sout.to_static({{m, 4}, {n, 9}}) == @@ -5475,11 +5536,13 @@ TEST_CASE(slice_sym_symbolic_bounds) auto op = migraphx::make_op("slice", {{"axes", {0}}, {"starts", {2}}, - {"ends", migraphx::value::array{migraphx::to_value(dd{n})}}}); + {"ends", migraphx::value::array{migraphx::to_value(dd{n})}}, + {"mode", "ends_input"}}); migraphx::shape sin{migraphx::shape::float_type, {dd{lit(12)}, dd{lit(4)}}}; + migraphx::shape ends_in{migraphx::shape::int64_type, {1}}; migraphx::shape sout{ migraphx::shape::float_type, {dd{lit(10)}, dd{lit(4)}}, sin.dyn_strides()}; - expect_shape(sout, op, sin); + expect_shape(sout, op, sin, ends_in); } { // Symbolic start: dim = 8 - n (n <= 8 keeps the extent non-negative). @@ -5487,11 +5550,13 @@ TEST_CASE(slice_sym_symbolic_bounds) auto op = migraphx::make_op("slice", {{"axes", {0}}, {"starts", migraphx::value::array{migraphx::to_value(dd{n})}}, - {"ends", {8}}}); + {"ends", {8}}, + {"mode", "starts_input"}}); migraphx::shape sin{migraphx::shape::float_type, {dd{lit(10)}, dd{lit(4)}}}; + migraphx::shape starts_in{migraphx::shape::int64_type, {1}}; migraphx::shape sout{ migraphx::shape::float_type, {dd{lit(8) - n}, dd{lit(4)}}, sin.dyn_strides()}; - expect_shape(sout, op, sin); + expect_shape(sout, op, sin, starts_in); EXPECT(sout.symbolic()); EXPECT(sout.to_static({{n, 3}}) == migraphx::shape{migraphx::shape::float_type, {5, 4}, {4, 1}}); @@ -5503,10 +5568,13 @@ TEST_CASE(slice_sym_symbolic_bounds) auto op = migraphx::make_op("slice", {{"axes", {0}}, {"starts", migraphx::value::array{migraphx::to_value(dd{m})}}, - {"ends", migraphx::value::array{migraphx::to_value(dd{n})}}}); + {"ends", migraphx::value::array{migraphx::to_value(dd{n})}}, + {"mode", "starts_ends_input"}}); migraphx::shape sin{migraphx::shape::float_type, {dd{lit(20)}}}; + migraphx::shape starts_in{migraphx::shape::int64_type, {1}}; + migraphx::shape ends_in{migraphx::shape::int64_type, {1}}; migraphx::shape sout{migraphx::shape::float_type, {dd{n - m}}, sin.dyn_strides()}; - expect_shape(sout, op, sin); + expect_shape(sout, op, sin, starts_in, ends_in); EXPECT(sout.symbolic()); EXPECT(sout.to_static({{m, 3}, {n, 9}}) == migraphx::shape{migraphx::shape::float_type, {6}, {1}}); diff --git a/test/ref/slice.cpp b/test/ref/slice.cpp index efde93f1ab0..0550434f870 100644 --- a/test/ref/slice.cpp +++ b/test/ref/slice.cpp @@ -87,7 +87,11 @@ TEST_CASE(slice_var_inputs_static0) migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto starts = mm->add_parameter("starts", s1); auto ends = mm->add_parameter("ends", s1); - mm->add_instruction(migraphx::make_op("slice", {{"axes", {2}}}), l0, starts, ends); + mm->add_instruction( + migraphx::make_op("slice", {{"axes", {2}}, {"mode", "starts_ends_input"}}), + l0, + starts, + ends); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; @@ -113,7 +117,11 @@ TEST_CASE(slice_var_inputs_static1) migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto starts = mm->add_parameter("starts", s1); auto ends = mm->add_parameter("ends", s1); - mm->add_instruction(migraphx::make_op("slice", {{"axes", {2}}}), l0, starts, ends); + mm->add_instruction( + migraphx::make_op("slice", {{"axes", {2}}, {"mode", "starts_ends_input"}}), + l0, + starts, + ends); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; @@ -140,7 +148,8 @@ TEST_CASE(slice_var_inputs_static2) auto starts = mm->add_parameter("starts", s1); auto ends = mm->add_parameter("ends", s1); auto axes = mm->add_parameter("axes", s1); - mm->add_instruction(migraphx::make_op("slice"), l0, starts, ends, axes); + mm->add_instruction( + migraphx::make_op("slice", {{"mode", "starts_ends_axes_input"}}), l0, starts, ends, axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; @@ -165,7 +174,10 @@ TEST_CASE(slice_var_inputs_dyn0) auto input = mm->add_parameter("input", s0); migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto starts = mm->add_parameter("starts", s1); - mm->add_instruction(migraphx::make_op("slice", {{"axes", {2}}, {"ends", {10}}}), input, starts); + mm->add_instruction( + migraphx::make_op("slice", {{"axes", {2}}, {"ends", {10}}, {"mode", "starts_input"}}), + input, + starts); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; @@ -190,7 +202,10 @@ TEST_CASE(slice_var_inputs_dyn1) auto input = mm->add_parameter("input", s0); migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto ends = mm->add_parameter("ends", s1); - mm->add_instruction(migraphx::make_op("slice", {{"axes", {2}}, {"starts", {-5}}}), input, ends); + mm->add_instruction( + migraphx::make_op("slice", {{"axes", {2}}, {"starts", {-5}}, {"mode", "ends_input"}}), + input, + ends); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; @@ -215,7 +230,10 @@ TEST_CASE(slice_var_inputs_dyn2) auto input = mm->add_parameter("input", s0); migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto axes = mm->add_parameter("axes", s1); - mm->add_instruction(migraphx::make_op("slice", {{"starts", {1}}, {"ends", {-1}}}), input, axes); + mm->add_instruction( + migraphx::make_op("slice", {{"starts", {1}}, {"ends", {-1}}, {"mode", "axes_input"}}), + input, + axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; @@ -241,7 +259,11 @@ TEST_CASE(slice_var_inputs_dyn3) migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto starts = mm->add_parameter("starts", s1); auto ends = mm->add_parameter("ends", s1); - mm->add_instruction(migraphx::make_op("slice", {{"axes", {2}}}), input, starts, ends); + mm->add_instruction( + migraphx::make_op("slice", {{"axes", {2}}, {"mode", "starts_ends_input"}}), + input, + starts, + ends); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; @@ -269,10 +291,12 @@ TEST_CASE(slice_var_inputs_dyn4) migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto starts = mm->add_parameter("starts", s1); auto axes = mm->add_parameter("axes", s1); - mm->add_instruction(migraphx::make_op("slice", {{"ends", {std::numeric_limits::max()}}}), - input, - starts, - axes); + mm->add_instruction( + migraphx::make_op( + "slice", {{"ends", {std::numeric_limits::max()}}, {"mode", "starts_axes_input"}}), + input, + starts, + axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; @@ -300,7 +324,11 @@ TEST_CASE(slice_var_inputs_dyn5) migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto ends = mm->add_parameter("ends", s1); auto axes = mm->add_parameter("axes", s1); - mm->add_instruction(migraphx::make_op("slice", {{"starts", {-4}}}), input, ends, axes); + mm->add_instruction( + migraphx::make_op("slice", {{"starts", {-4}}, {"mode", "ends_axes_input"}}), + input, + ends, + axes); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; @@ -328,7 +356,11 @@ TEST_CASE(slice_var_inputs_dyn6) migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto starts = mm->add_parameter("starts", s1); auto ends = mm->add_parameter("ends", s1); - mm->add_instruction(migraphx::make_op("slice", {{"axes", {2}}}), input, starts, ends); + mm->add_instruction( + migraphx::make_op("slice", {{"axes", {2}}, {"mode", "starts_ends_input"}}), + input, + starts, + ends); p.compile(migraphx::make_target("ref")); migraphx::parameter_map params; From 9a224a2976178fe68a07a46d6ede2fb837ab5c1f Mon Sep 17 00:00:00 2001 From: charlie Date: Wed, 22 Jul 2026 13:28:47 -0500 Subject: [PATCH 21/42] Other changes around slice --- src/onnx/parse_nonmaxsuppression.cpp | 9 ++++- src/onnx/parse_topk.cpp | 4 +- src/simplify_dyn_ops.cpp | 22 +++++----- test/multi_target/multitarget_test.cpp | 2 +- test/onnx/parse/nms_dynamic_batch_test.cpp | 2 +- test/onnx/parse/nms_dynamic_boxes_test.cpp | 2 +- test/onnx/parse/nms_dynamic_classes_test.cpp | 2 +- test/onnx/parse/nms_test.cpp | 2 +- test/onnx/parse/topk_var_k_test.cpp | 8 ++-- test/ref/nonmaxsuppression.cpp | 2 +- test/simplify_dyn_ops_test.cpp | 42 +++++++++++++++----- 11 files changed, 63 insertions(+), 34 deletions(-) diff --git a/src/onnx/parse_nonmaxsuppression.cpp b/src/onnx/parse_nonmaxsuppression.cpp index 7bbd801716d..6387f855af4 100644 --- a/src/onnx/parse_nonmaxsuppression.cpp +++ b/src/onnx/parse_nonmaxsuppression.cpp @@ -47,7 +47,14 @@ struct parse_nonmaxsuppression : op_parser info.add_instruction(make_op("get_tuple_elem", {{"index", 1}}), nms_ins); auto num_selected_var = shape::dynamic_dimension{sym::var(info.name)}; return info.add_instruction( - make_op("slice", {{"axes", {0}}, {"starts", {0}}, {"ends", {to_value(num_selected_var)}}, {"mode", "ends_input"}}), indices, num_selected); + make_op( + "slice", + {{"axes", {0}}, + {"starts", {0}}, + {"ends", value::array{to_value(num_selected_var)}}, + {"mode", "ends_input"}}), + indices, + num_selected); } }; diff --git a/src/onnx/parse_topk.cpp b/src/onnx/parse_topk.cpp index 74cc4042262..277c61981f1 100644 --- a/src/onnx/parse_topk.cpp +++ b/src/onnx/parse_topk.cpp @@ -103,14 +103,14 @@ struct parse_topk : op_parser ret_val = info.add_instruction(make_op("slice", {{"axes", {axis}}, {"starts", {0}}, - {"ends", {to_value(k_var)}}, + {"ends", value::array{to_value(k_var)}}, {"mode", "ends_input"}}), ret_val, args.at(1)); ret_ind = info.add_instruction(make_op("slice", {{"axes", {axis}}, {"starts", {0}}, - {"ends", {to_value(k_var)}}, + {"ends", value::array{to_value(k_var)}}, {"mode", "ends_input"}}), ret_ind, args.at(1)); diff --git a/src/simplify_dyn_ops.cpp b/src/simplify_dyn_ops.cpp index ef82a0a9801..ea196740370 100644 --- a/src/simplify_dyn_ops.cpp +++ b/src/simplify_dyn_ops.cpp @@ -183,14 +183,13 @@ struct find_const_2in_slice : match::supports_dynamic_shapes void apply(module& m, const match::matcher_result& mr) const { - auto ins = mr.result; - auto inputs = ins->inputs(); - auto slice_op = any_cast(ins->get_operator()); - auto set_attrs = slice_op.get_set_attributes(); + auto ins = mr.result; + auto inputs = ins->inputs(); + auto slice_op = any_cast(ins->get_operator()); std::vector starts_vec; std::vector ends_vec; std::vector axes_vec; - if(set_attrs == op::slice::ends_axes) + if(slice_op.mode == op::slice::slice_mode::starts_input) { // slice(data, starts) inputs.at(1)->eval().visit( @@ -198,7 +197,7 @@ struct find_const_2in_slice : match::supports_dynamic_shapes ends_vec = to_ints(slice_op.ends); axes_vec = slice_op.axes; } - else if(set_attrs == op::slice::starts_axes) + else if(slice_op.mode == op::slice::slice_mode::ends_input) { // slice(data, ends) inputs.at(1)->eval().visit( @@ -240,14 +239,13 @@ struct find_const_3in_slice : match::supports_dynamic_shapes void apply(module& m, const match::matcher_result& mr) const { - auto ins = mr.result; - auto inputs = ins->inputs(); - auto slice_op = any_cast(ins->get_operator()); - auto set_attrs = slice_op.get_set_attributes(); + auto ins = mr.result; + auto inputs = ins->inputs(); + auto slice_op = any_cast(ins->get_operator()); std::vector starts_vec; std::vector ends_vec; std::vector axes_vec; - if(set_attrs == op::slice::axes_only) + if(slice_op.mode == op::slice::slice_mode::starts_ends_input) { // slice(data, starts, ends) inputs.at(1)->eval().visit( @@ -256,7 +254,7 @@ struct find_const_3in_slice : match::supports_dynamic_shapes [&](auto output) { ends_vec.assign(output.begin(), output.end()); }); axes_vec = slice_op.axes; } - else if(set_attrs == op::slice::ends_only) + else if(slice_op.mode == op::slice::slice_mode::starts_axes_input) { // slice(data, starts, axes) inputs.at(1)->eval().visit( diff --git a/test/multi_target/multitarget_test.cpp b/test/multi_target/multitarget_test.cpp index fe7eaca19fc..d944b37b582 100644 --- a/test/multi_target/multitarget_test.cpp +++ b/test/multi_target/multitarget_test.cpp @@ -237,7 +237,7 @@ TEST_CASE(single_target_multi_compile) migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, - {"ends", {migraphx::to_value(num_selected_var)}}, + {"ends", migraphx::value::array{migraphx::to_value(num_selected_var)}}, {"mode", "ends_input"}}), idx, cnt); diff --git a/test/onnx/parse/nms_dynamic_batch_test.cpp b/test/onnx/parse/nms_dynamic_batch_test.cpp index d51bea88fd8..98f9964820c 100644 --- a/test/onnx/parse/nms_dynamic_batch_test.cpp +++ b/test/onnx/parse/nms_dynamic_batch_test.cpp @@ -48,7 +48,7 @@ TEST_CASE(nms_dynamic_batch_test) migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, - {"ends", {migraphx::to_value(num_selected_var)}}, + {"ends", migraphx::value::array{migraphx::to_value(num_selected_var)}}, {"mode", "ends_input"}}), idx, cnt); diff --git a/test/onnx/parse/nms_dynamic_boxes_test.cpp b/test/onnx/parse/nms_dynamic_boxes_test.cpp index bed2b5d4810..17d5de4d674 100644 --- a/test/onnx/parse/nms_dynamic_boxes_test.cpp +++ b/test/onnx/parse/nms_dynamic_boxes_test.cpp @@ -47,7 +47,7 @@ TEST_CASE(nms_dynamic_boxes_test) migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, - {"ends", {migraphx::to_value(num_selected_var)}}, + {"ends", migraphx::value::array{migraphx::to_value(num_selected_var)}}, {"mode", "ends_input"}}), idx, cnt); diff --git a/test/onnx/parse/nms_dynamic_classes_test.cpp b/test/onnx/parse/nms_dynamic_classes_test.cpp index 4ed9ac79b53..adaf189baec 100644 --- a/test/onnx/parse/nms_dynamic_classes_test.cpp +++ b/test/onnx/parse/nms_dynamic_classes_test.cpp @@ -47,7 +47,7 @@ TEST_CASE(nms_dynamic_classes_test) migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, - {"ends", {migraphx::to_value(num_selected_var)}}, + {"ends", migraphx::value::array{migraphx::to_value(num_selected_var)}}, {"mode", "ends_input"}}), idx, cnt); diff --git a/test/onnx/parse/nms_test.cpp b/test/onnx/parse/nms_test.cpp index 49980b71317..beefd297652 100644 --- a/test/onnx/parse/nms_test.cpp +++ b/test/onnx/parse/nms_test.cpp @@ -53,7 +53,7 @@ TEST_CASE(nms_test) migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, - {"ends", {migraphx::to_value(num_selected_var)}}, + {"ends", migraphx::value::array{migraphx::to_value(num_selected_var)}}, {"mode", "ends_input"}}), idx, cnt); diff --git a/test/onnx/parse/topk_var_k_test.cpp b/test/onnx/parse/topk_var_k_test.cpp index 3bdbb79cd46..a7c7f7f9cc1 100644 --- a/test/onnx/parse/topk_var_k_test.cpp +++ b/test/onnx/parse/topk_var_k_test.cpp @@ -41,14 +41,14 @@ TEST_CASE(topk_var_k_test) val = mm->add_instruction(migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, - {"ends", {migraphx::to_value(k_var)}}, + {"ends", migraphx::value::array{migraphx::to_value(k_var)}}, {"mode", "ends_input"}}), val, k); ind = mm->add_instruction(migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, - {"ends", {migraphx::to_value(k_var)}}, + {"ends", migraphx::value::array{migraphx::to_value(k_var)}}, {"mode", "ends_input"}}), ind, k); @@ -75,14 +75,14 @@ TEST_CASE(topk_var_k_dynamic_test) val = mm->add_instruction(migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, - {"ends", {migraphx::to_value(k_var)}}, + {"ends", migraphx::value::array{migraphx::to_value(k_var)}}, {"mode", "ends_input"}}), val, k); ind = mm->add_instruction(migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, - {"ends", {migraphx::to_value(k_var)}}, + {"ends", migraphx::value::array{migraphx::to_value(k_var)}}, {"mode", "ends_input"}}), ind, k); diff --git a/test/ref/nonmaxsuppression.cpp b/test/ref/nonmaxsuppression.cpp index ad4513f9bc2..3ca60073fc8 100644 --- a/test/ref/nonmaxsuppression.cpp +++ b/test/ref/nonmaxsuppression.cpp @@ -44,7 +44,7 @@ static migraphx::instruction_ref add_nms_dynamic_slice(migraphx::module* mm, migraphx::make_op("slice", {{"axes", {0}}, {"starts", {0}}, - {"ends", {migraphx::to_value(num_selected_var)}}, + {"ends", migraphx::value::array{migraphx::to_value(num_selected_var)}}, {"mode", "ends_input"}}), idx, cnt); diff --git a/test/simplify_dyn_ops_test.cpp b/test/simplify_dyn_ops_test.cpp index b6a770df194..85d1895a53e 100644 --- a/test/simplify_dyn_ops_test.cpp +++ b/test/simplify_dyn_ops_test.cpp @@ -308,7 +308,9 @@ TEST_CASE(const_slice_2input_ends_axes) migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto input_starts = m0.add_literal(migraphx::literal{s1, {0}}); auto slice_ins = m0.add_instruction( - migraphx::make_op("slice", {{"ends", {3}}, {"axes", {0}}}), input, input_starts); + migraphx::make_op("slice", {{"ends", {3}}, {"axes", {0}}, {"mode", "starts_input"}}), + input, + input_starts); m0.add_return({slice_ins}); } run_pass(m0); @@ -333,7 +335,9 @@ TEST_CASE(const_slice_2input_starts_axes) migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto input_ends = m0.add_literal(migraphx::literal{s1, {3}}); auto slice_ins = m0.add_instruction( - migraphx::make_op("slice", {{"starts", {0}}, {"axes", {0}}}), input, input_ends); + migraphx::make_op("slice", {{"starts", {0}}, {"axes", {0}}, {"mode", "ends_input"}}), + input, + input_ends); m0.add_return({slice_ins}); } run_pass(m0); @@ -358,7 +362,9 @@ TEST_CASE(const_slice_2input_starts_ends) migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto input_axes = m0.add_literal(migraphx::literal{s1, {0}}); auto slice_ins = m0.add_instruction( - migraphx::make_op("slice", {{"starts", {0}}, {"ends", {3}}}), input, input_axes); + migraphx::make_op("slice", {{"starts", {0}}, {"ends", {3}}, {"mode", "axes_input"}}), + input, + input_axes); m0.add_return({slice_ins}); } run_pass(m0); @@ -384,7 +390,10 @@ TEST_CASE(const_slice_3input_axes_only) auto input_starts = m0.add_literal(migraphx::literal{s1, {0}}); auto input_ends = m0.add_literal(migraphx::literal{s1, {3}}); auto slice_ins = m0.add_instruction( - migraphx::make_op("slice", {{"axes", {0}}}), input, input_starts, input_ends); + migraphx::make_op("slice", {{"axes", {0}}, {"mode", "starts_ends_input"}}), + input, + input_starts, + input_ends); m0.add_return({slice_ins}); } run_pass(m0); @@ -410,7 +419,10 @@ TEST_CASE(const_slice_3input_ends_only) auto input_starts = m0.add_literal(migraphx::literal{s1, {0}}); auto input_axes = m0.add_literal(migraphx::literal{s1, {0}}); auto slice_ins = m0.add_instruction( - migraphx::make_op("slice", {{"ends", {3}}}), input, input_starts, input_axes); + migraphx::make_op("slice", {{"ends", {3}}, {"mode", "starts_axes_input"}}), + input, + input_starts, + input_axes); m0.add_return({slice_ins}); } run_pass(m0); @@ -436,7 +448,10 @@ TEST_CASE(const_slice_3inputs_starts_only) auto input_ends = m0.add_literal(migraphx::literal{s1, {3}}); auto input_axes = m0.add_literal(migraphx::literal{s1, {0}}); auto slice_ins = m0.add_instruction( - migraphx::make_op("slice", {{"starts", {0}}}), input, input_ends, input_axes); + migraphx::make_op("slice", {{"starts", {0}}, {"mode", "ends_axes_input"}}), + input, + input_ends, + input_axes); m0.add_return({slice_ins}); } run_pass(m0); @@ -461,7 +476,9 @@ TEST_CASE(const_slice_2input_ends_axes_dyn) migraphx::shape s1{migraphx::shape::int32_type, {1}}; auto input_starts = m0.add_literal(migraphx::literal{s1, {0}}); auto slice_ins = m0.add_instruction( - migraphx::make_op("slice", {{"ends", {3}}, {"axes", {0}}}), input, input_starts); + migraphx::make_op("slice", {{"ends", {3}}, {"axes", {0}}, {"mode", "starts_input"}}), + input, + input_starts); m0.add_return({slice_ins}); } run_pass(m0); @@ -488,7 +505,10 @@ TEST_CASE(const_slice_3input_dyn) auto input_starts = m0.add_literal(migraphx::literal{s1, {0}}); auto input_ends = m0.add_literal(migraphx::literal{s1, {3}}); auto slice_ins = m0.add_instruction( - migraphx::make_op("slice", {{"axes", {0}}}), input, input_starts, input_ends); + migraphx::make_op("slice", {{"axes", {0}}, {"mode", "starts_ends_input"}}), + input, + input_starts, + input_ends); m0.add_return({slice_ins}); } run_pass(m0); @@ -515,7 +535,11 @@ TEST_CASE(const_slice_4input) auto input_ends = m0.add_literal(migraphx::literal{s1, {3}}); auto input_axes = m0.add_literal(migraphx::literal{s1, {0}}); auto slice_ins = m0.add_instruction( - migraphx::make_op("slice"), input, input_starts, input_ends, input_axes); + migraphx::make_op("slice", {{"mode", "starts_ends_axes_input"}}), + input, + input_starts, + input_ends, + input_axes); m0.add_return({slice_ins}); } run_pass(m0); From e92d11a96469965f6908cd345db15a76efcfe154 Mon Sep 17 00:00:00 2001 From: Matt Van Horn Date: Wed, 22 Jul 2026 11:34:57 -0700 Subject: [PATCH 22/42] NMS: Early exit ref 0 boxes, add tests for 0 and 1 box edge cases (#4999) --- src/onnx/parse_nonmaxsuppression.cpp | 7 ++ src/targets/gpu/lowering.cpp | 4 +- test/gpu/nonmaxsuppression.cpp | 77 ++++++++++++++++++ test/onnx/gen_onnx.py | 74 +++++++++++++++++ .../nonmaxsuppression_zero_boxes_test.onnx | Bin 0 -> 759 bytes test/onnx/parse/nonmaxsuppression_test.cpp | 52 ++++++++++++ 6 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 test/onnx/nonmaxsuppression_zero_boxes_test.onnx create mode 100644 test/onnx/parse/nonmaxsuppression_test.cpp diff --git a/src/onnx/parse_nonmaxsuppression.cpp b/src/onnx/parse_nonmaxsuppression.cpp index eb422329f67..bf2196edd3b 100644 --- a/src/onnx/parse_nonmaxsuppression.cpp +++ b/src/onnx/parse_nonmaxsuppression.cpp @@ -22,6 +22,7 @@ * THE SOFTWARE. */ #include +#include #include #include #include @@ -41,6 +42,12 @@ struct parse_nonmaxsuppression : op_parser const onnx_parser::node_info& info, const std::vector& args) const { + if(any_of(args, [](const auto& arg) { + const auto& s = arg->get_shape(); + return not s.dynamic() and s.elements() == 0; + })) + return info.add_instruction(make_op("undefined")); + auto op = parser.load(opd.op_name, info); auto nms_ins = info.add_instruction(op, args); // slice with variable ends to handle dynamic shape output. diff --git a/src/targets/gpu/lowering.cpp b/src/targets/gpu/lowering.cpp index 8aa711a3f3c..a0a6dc67afa 100644 --- a/src/targets/gpu/lowering.cpp +++ b/src/targets/gpu/lowering.cpp @@ -461,7 +461,9 @@ struct miopen_apply return lower_nms_to_ref(ins); const auto num_boxes = boxes_s.lens().at(1); const auto num_bc = boxes_s.lens().at(0) * scores_s.lens().at(1); - // bound on (batch, class) from shared memory limit on compact kernel + // Route to ref (CPU) when: + // - num_boxes < 2: Single box or no boxes, no sort or IoU comparison needed. + // - num_bc > 8192: shared-memory limit on the compact kernel. if(num_boxes < 2 or num_bc > 8192) return lower_nms_to_ref(ins); return lower_nms_to_gpu_pipeline(ins); diff --git a/test/gpu/nonmaxsuppression.cpp b/test/gpu/nonmaxsuppression.cpp index 42f999d83ed..02370f7f245 100644 --- a/test/gpu/nonmaxsuppression.cpp +++ b/test/gpu/nonmaxsuppression.cpp @@ -1345,4 +1345,81 @@ TEST_CASE(nms_quantized_ties_test) EXPECT(num_selected == 10); } +// Edge case: 1 box with score above score_threshold. The single box should be +// selected. Routes through lower_nms_to_ref because num_boxes < 2. +TEST_CASE(nms_one_box_above_threshold_test) +{ + migraphx::program p; + auto* mm = p.get_main_module(); + migraphx::shape boxes_s{migraphx::shape::float_type, {1, 1, 4}}; + migraphx::shape scores_s{migraphx::shape::float_type, {1, 1, 1}}; + + auto boxes_p = mm->add_parameter("boxes", boxes_s); + auto scores_p = mm->add_parameter("scores", scores_s); + auto max_out_l = mm->add_literal(int64_t{10}); + auto iou_threshold = mm->add_literal(0.5f); + auto score_threshold = mm->add_literal(0.3f); + + auto nms = mm->add_instruction(migraphx::make_op("nonmaxsuppression"), + boxes_p, + scores_p, + max_out_l, + iou_threshold, + score_threshold); + add_nms_return(mm, nms); + + // single box: [y1=0, x1=0, y2=1, x2=1], score 0.9 > threshold 0.3 + std::vector boxes_vec = {0.0f, 0.0f, 1.0f, 1.0f}; + std::vector scores_vec = {0.9f}; + + migraphx::parameter_map host_params; + host_params["boxes"] = migraphx::argument(boxes_s, boxes_vec.data()); + host_params["scores"] = migraphx::argument(scores_s, scores_vec.data()); + + auto [indices, num_selected] = run_gpu_nms(std::move(p), host_params); + indices.resize(static_cast(num_selected) * 3); + // batch_idx=0, class_idx=0, box_idx=0 + std::vector gold = {0, 0, 0}; + EXPECT(indices == gold); + EXPECT(num_selected == 1); +} + +// Edge case: 1 box with score below score_threshold. No boxes should be +// selected. Routes through lower_nms_to_ref because num_boxes < 2. +TEST_CASE(nms_one_box_below_threshold_test) +{ + migraphx::program p; + auto* mm = p.get_main_module(); + migraphx::shape boxes_s{migraphx::shape::float_type, {1, 1, 4}}; + migraphx::shape scores_s{migraphx::shape::float_type, {1, 1, 1}}; + + auto boxes_p = mm->add_parameter("boxes", boxes_s); + auto scores_p = mm->add_parameter("scores", scores_s); + auto max_out_l = mm->add_literal(int64_t{10}); + auto iou_threshold = mm->add_literal(0.5f); + auto score_threshold = mm->add_literal(0.5f); + + auto nms = mm->add_instruction(migraphx::make_op("nonmaxsuppression"), + boxes_p, + scores_p, + max_out_l, + iou_threshold, + score_threshold); + add_nms_return(mm, nms); + + // single box: score 0.2 < threshold 0.5 + std::vector boxes_vec = {0.0f, 0.0f, 1.0f, 1.0f}; + std::vector scores_vec = {0.2f}; + + migraphx::parameter_map host_params; + host_params["boxes"] = migraphx::argument(boxes_s, boxes_vec.data()); + host_params["scores"] = migraphx::argument(scores_s, scores_vec.data()); + + auto [indices, num_selected] = run_gpu_nms(std::move(p), host_params); + indices.resize(static_cast(num_selected) * 3); + std::vector gold = {}; + EXPECT(indices == gold); + EXPECT(num_selected == 0); +} + int main(int argc, const char* argv[]) { test::run(argc, argv); } diff --git a/test/onnx/gen_onnx.py b/test/onnx/gen_onnx.py index 1ea70a09792..30f20e8ea9a 100644 --- a/test/onnx/gen_onnx.py +++ b/test/onnx/gen_onnx.py @@ -11545,6 +11545,80 @@ def nms_dynamic_classes_test(): return ([node], [b, s, mo, iou, st], [out]) +@onnx_test() +def nonmaxsuppression_zero_boxes_test(): + b = helper.make_tensor_value_info('boxes', TensorProto.FLOAT, [1, 6, 4]) + s = helper.make_tensor_value_info('scores', TensorProto.FLOAT, [1, 1, 6]) + mo = helper.make_tensor_value_info('max_output_boxes_per_class', + TensorProto.INT64, [1]) + iou = helper.make_tensor_value_info('iou_threshold', TensorProto.FLOAT, + [1]) + st = helper.make_tensor_value_info('score_threshold', TensorProto.FLOAT, + [1]) + out = helper.make_tensor_value_info('selected_indices', TensorProto.INT64, + [None, 3]) + + start = np.array([0]) + start_tensor = helper.make_tensor(name='start', + data_type=TensorProto.INT64, + dims=start.shape, + vals=start.astype(int)) + arg_start = helper.make_node('Constant', + inputs=[], + outputs=['arg_start'], + value=start_tensor) + + end = np.array([0]) + end_tensor = helper.make_tensor(name='end', + data_type=TensorProto.INT64, + dims=end.shape, + vals=end.astype(int)) + arg_end = helper.make_node('Constant', + inputs=[], + outputs=['arg_end'], + value=end_tensor) + + boxes_axis = np.array([1]) + boxes_axis_tensor = helper.make_tensor(name='boxes_axis', + data_type=TensorProto.INT64, + dims=boxes_axis.shape, + vals=boxes_axis.astype(int)) + arg_boxes_axis = helper.make_node('Constant', + inputs=[], + outputs=['arg_boxes_axis'], + value=boxes_axis_tensor) + + scores_axis = np.array([2]) + scores_axis_tensor = helper.make_tensor(name='scores_axis', + data_type=TensorProto.INT64, + dims=scores_axis.shape, + vals=scores_axis.astype(int)) + arg_scores_axis = helper.make_node('Constant', + inputs=[], + outputs=['arg_scores_axis'], + value=scores_axis_tensor) + + slice_boxes = onnx.helper.make_node( + 'Slice', + inputs=['boxes', 'arg_start', 'arg_end', 'arg_boxes_axis'], + outputs=['sliced_boxes']) + slice_scores = onnx.helper.make_node( + 'Slice', + inputs=['scores', 'arg_start', 'arg_end', 'arg_scores_axis'], + outputs=['sliced_scores']) + + node = onnx.helper.make_node('NonMaxSuppression', + inputs=[ + 'sliced_boxes', 'sliced_scores', + 'max_output_boxes_per_class', + 'iou_threshold', 'score_threshold' + ], + outputs=['selected_indices']) + + return ([arg_start, arg_end, arg_boxes_axis, arg_scores_axis, + slice_boxes, slice_scores, node], [b, s, mo, iou, st], [out]) + + @onnx_test() def not_test(): x = helper.make_tensor_value_info('0', TensorProto.INT32, [4]) diff --git a/test/onnx/nonmaxsuppression_zero_boxes_test.onnx b/test/onnx/nonmaxsuppression_zero_boxes_test.onnx new file mode 100644 index 0000000000000000000000000000000000000000..3ff3d28a541f65333451859579bb8550608a5dfe GIT binary patch literal 759 zcma))yH3L}7=`P$X~Iu0!7!i#qI5!)7+8ytP&&iXt&8O*7L63_D0Wc9z_ai^ycBjW zN|PeQU@6IezT zSJY`iArESufgkZCkLL^BfwbsZMV}RP*E^QA(BMyC<$h3?gep9=V&S2TLZ1ai(z&%Z zZ?J+v;rXjYI~*RB9tBgGd_uz;nkty;Ya$|5ajME=idjPZh)M~gP^3idbo-r%0+{5+ z^#|-q7BOGx*dY&eU^zc|5&W5E>-ygR@hfaD%9FwvTnFJHbRDGEK(GGhvO0v;G*@hH s|0;k19LL6e + +TEST_CASE(nonmaxsuppression_zero_boxes_test) +{ + migraphx::program p; + auto* mm = p.get_main_module(); + auto boxes = + mm->add_parameter("boxes", migraphx::shape{migraphx::shape::float_type, {1, 6, 4}}); + auto scores = + mm->add_parameter("scores", migraphx::shape{migraphx::shape::float_type, {1, 1, 6}}); + mm->add_parameter("max_output_boxes_per_class", + migraphx::shape{migraphx::shape::int64_type, {1}}); + mm->add_parameter("iou_threshold", migraphx::shape{migraphx::shape::float_type, {1}}); + mm->add_parameter("score_threshold", migraphx::shape{migraphx::shape::float_type, {1}}); + mm->add_literal({{migraphx::shape::int64_type, {1}}, {0}}); + mm->add_literal({{migraphx::shape::int64_type, {1}}, {0}}); + mm->add_literal({{migraphx::shape::int64_type, {1}}, {1}}); + mm->add_literal({{migraphx::shape::int64_type, {1}}, {2}}); + mm->add_instruction(migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {0}}}), + boxes); + mm->add_instruction(migraphx::make_op("slice", {{"axes", {2}}, {"starts", {0}}, {"ends", {0}}}), + scores); + auto ret = mm->add_instruction(migraphx::make_op("undefined")); + mm->add_return({ret}); + + auto prog = read_onnx("nonmaxsuppression_zero_boxes_test.onnx"); + EXPECT(p == prog); +} From 9b410a242cdb2246ef0b6274349baf53eff2cc33 Mon Sep 17 00:00:00 2001 From: Eddie Liao <54926923+eddieliao@users.noreply.github.com> Date: Wed, 22 Jul 2026 12:37:31 -0700 Subject: [PATCH 23/42] [AIMIGRAPHX-1151] Add pytest bridge for unit tests (#5006) Adds a pytest bridge to support running of CTest unit tests with pytest. --- requirements.txt | 2 +- test/CMakeLists.txt | 3 ++ test/test_pytest_bridge.py | 91 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 test/test_pytest_bridge.py diff --git a/requirements.txt b/requirements.txt index fd69ce8d3e0..a6f9b32a3f3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,7 +27,7 @@ nlohmann/json@v3.8.0 -DCMAKE_POLICY_VERSION_MINIMUM=3.5 pybind/pybind11@3e9dfa2866941655c56877882565e7577de6fc7b --build msgpack/msgpack-c@cpp-3.3.0 -DMSGPACK_BUILD_TESTS=Off -DMSGPACK_BUILD_EXAMPLES=Off -DCMAKE_POLICY_VERSION_MINIMUM=3.5 sqlite3@3.50.4 -DCMAKE_POSITION_INDEPENDENT_CODE=On -ROCm/rocm-cmake@1d4652ae2ec0e44a67a7c415dd7e51c88a6aa68d --build +ROCm/rocm-cmake@6a7c5b73b8882c74f8f7060e2633f230dabb7b63 --build ROCm/composable_kernel@ad0db05b040bacda751c65c705261b8a0a7ed25d --cmake subdir -DCMAKE_DIR=codegen -DCMAKE_POSITION_INDEPENDENT_CODE=On -DBUILD_TESTING=Off -DCMAKE_POLICY_VERSION_MINIMUM=3.5 https://gitlab.com/libeigen/eigen/-/archive/5.0.1/eigen-5.0.1.tar.gz -DBUILD_TESTING=Off -DEIGEN_BUILD_DOC=Off -DEIGEN_BUILD_LAPACK=Off ROCm/rocMLIR@eccd4d712fb1b0729622690945ab0760f6ca8d6f -DBUILD_FAT_LIBROCKCOMPILER=On -DLLVM_INCLUDE_TESTS=Off diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index dca7e30adc7..366ab5c019d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -117,6 +117,9 @@ if(MIGRAPHX_ENABLE_PYTHON) add_subdirectory(py) endif() +# Install a pytest bridge next to the installed C++ tests +rocm_install_test(FILES ${CMAKE_CURRENT_SOURCE_DIR}/test_pytest_bridge.py) + # Op builder test set(TEST_OP_BUILDER_DIR ${CMAKE_CURRENT_SOURCE_DIR}/op) add_subdirectory(op) diff --git a/test/test_pytest_bridge.py b/test/test_pytest_bridge.py new file mode 100644 index 00000000000..0c4738528a9 --- /dev/null +++ b/test/test_pytest_bridge.py @@ -0,0 +1,91 @@ +##################################################################################### +# 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. +##################################################################################### +"""Pytest bridge for the MIGraphX test suite + +NOTE: the file is named ``test_*`` on purpose so pytest's default discovery can find it +""" +import os +import shutil +import subprocess + +import pytest + +_HERE = os.path.dirname(os.path.abspath(__file__)) + + +def _resolve_test_dir(): + env_dir = os.environ.get("MIGRAPHX_TEST_DIR") + if env_dir: + return env_dir + for candidate in (_HERE, os.getcwd()): + if os.path.exists(os.path.join(candidate, "CTestTestfile.cmake")): + return candidate + return _HERE + + +def _migraphx_lib_dir(): + try: + import migraphx + except ImportError: + return None + return os.path.dirname(os.path.abspath(migraphx.__file__)) + + +def _ctest_env(test_dir): + env = dict(os.environ) + lib_dirs = [d for d in (os.path.join(test_dir, "lib"), _migraphx_lib_dir()) + if d and os.path.isdir(d)] + if lib_dirs: + existing = env.get("LD_LIBRARY_PATH", "") + env["LD_LIBRARY_PATH"] = os.pathsep.join( + lib_dirs + ([existing] if existing else [])) + return env + + +def _ensure_executable(test_dir): + bin_dir = os.path.join(test_dir, "bin") + if not os.path.isdir(bin_dir): + return + for name in os.listdir(bin_dir): + try: + os.chmod(os.path.join(bin_dir, name), 0o755) + except OSError: + pass + + +@pytest.mark.skipif(shutil.which("ctest") is None, + reason="ctest not found; install CMake to run the suite") +def test_migraphx(): + test_dir = _resolve_test_dir() + if not os.path.exists(os.path.join(test_dir, "CTestTestfile.cmake")): + pytest.skip( + f"No CTestTestfile.cmake in {test_dir}; set MIGRAPHX_TEST_DIR to a " + "build or installed-tests directory.") + _ensure_executable(test_dir) + result = subprocess.run( + ["ctest", "--test-dir", test_dir, "-j", str(os.cpu_count() or 1), + "--timeout", "5000", "--output-on-failure"], + env=_ctest_env(test_dir), + ) + assert result.returncode == 0, f"ctest reported failures (exit {result.returncode})" From 5c50d0cb1d2743e6c2ed063a17dd3f0e8d7c2f3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilija=20Kalini=C4=87?= Date: Wed, 22 Jul 2026 21:38:46 +0200 Subject: [PATCH 24/42] Fix nonzero for non-standard input layouts (#5046) Fix the reference nonzero operator so it accepts and correctly reads non-standard input layouts such as transposed and broadcasted tensors. --- CHANGELOG.md | 1 + src/include/migraphx/op/nonzero.hpp | 28 ++++++++------------- test/ref/nonzero.cpp | 39 ++++++++++++++++++++++++++++- test/verify/test_nonzero.cpp | 21 ++++++++++++++++ 4 files changed, 71 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2215654dbfe..dcbc8fadaa0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ Full documentation for MIGraphX is available at ### Resolved issues +* Fixed the reference `nonzero` operator to handle non-standard input layouts such as transposed or broadcasted tensors. * Restored support for the documented flat {min,max,optimals} JSON format in migraphx-driver's --default-dyn-dim and --dyn-input-dim flags (#4926). * Fixed ONNX `Where` parsing for dynamic-shape inputs that require broadcasting (including mixed static and dynamic inputs), which previously threw `same_dims: where: Dimensions do not match` (#4925). * Fixed a regression in `simplify_algebra` where `find_conv_broadcast_input` could trigger `Dimensions do not match` for padded broadcast-convolution rewrites in no-interior spatial cases (#4738). diff --git a/src/include/migraphx/op/nonzero.hpp b/src/include/migraphx/op/nonzero.hpp index e14d62a05b9..06283a4e51e 100644 --- a/src/include/migraphx/op/nonzero.hpp +++ b/src/include/migraphx/op/nonzero.hpp @@ -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 @@ -31,6 +31,7 @@ #include #include #include +#include #include namespace migraphx { @@ -43,7 +44,7 @@ struct nonzero shape compute_shape(std::vector inputs) const { - check_shapes{inputs, *this}.has(1).standard(); + check_shapes{inputs, *this}.has(1); auto elem_num = inputs[0].elements(); auto dim_num = inputs[0].lens().size(); std::vector out_lens = {dim_num, elem_num}; @@ -53,24 +54,17 @@ struct nonzero argument compute(const shape& output_shape, std::vector args) const { - std::vector> vec_idx; auto s = args.front().get_shape(); - args.front().visit([&](auto v) { - shape_for_each(s, [&](const auto& idx_v, size_t idx) { - if(not float_equal(v[idx], 0)) - { - vec_idx.push_back(idx_v); - } - }); - }); - argument result{output_shape}; - result.visit([&](auto output) { - std::fill(output.begin(), output.end(), 0); - par_for(vec_idx.size(), [&](auto i) { - for(std::size_t j = 0; j < vec_idx.front().size(); ++j) + auto output = result.get(); + std::fill(output.begin(), output.end(), 0); + args.front().visit([&](auto v) { + std::size_t nonzero_idx = 0; + shape_for_each(s, [&](const auto& idx_v) { + if(not float_equal(v[idx_v], 0)) { - output[output_shape.index({j, i})] = vec_idx[i][j]; + auto out_idx = nonzero_idx++; + par_for(idx_v.size(), [&](auto i) { output(i, out_idx) = idx_v[i]; }); } }); }); diff --git a/test/ref/nonzero.cpp b/test/ref/nonzero.cpp index b6a534e0ea1..20cc0d94890 100644 --- a/test/ref/nonzero.cpp +++ b/test/ref/nonzero.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 @@ -48,3 +48,40 @@ TEST_CASE(nonzero_test) 1, 1, 0, 0, 0, 0, 0, 1, 0, 2, 0, 2, 0, 2, 0, 0, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } + +TEST_CASE(nonzero_transposed_input) +{ + migraphx::program p; + auto* mm = p.get_main_module(); + migraphx::shape s{migraphx::shape::float_type, {2, 3}}; + std::vector data = {1.0f, 0.0f, 2.0f, 0.0f, 3.0f, 4.0f}; + auto input = mm->add_literal(migraphx::literal(s, data)); + auto transposed = + mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), input); + auto ret = mm->add_instruction(migraphx::make_op("nonzero"), transposed); + mm->add_return({ret}); + p.compile(migraphx::make_target("ref")); + auto result = p.eval({}).back(); + std::vector result_vector; + result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); + // np.nonzero(data.reshape(2, 3).T), padded to nonzero output shape {2, 6}. + std::vector gold = {0, 1, 2, 2, 0, 0, 0, 1, 0, 1, 0, 0}; + EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); +} + +TEST_CASE(nonzero_broadcasted_input) +{ + migraphx::program p; + auto* mm = p.get_main_module(); + auto input = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {1.0f}}); + auto broadcasted = + mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 3}}}), input); + auto ret = mm->add_instruction(migraphx::make_op("nonzero"), broadcasted); + mm->add_return({ret}); + p.compile(migraphx::make_target("ref")); + auto result = p.eval({}).back(); + std::vector result_vector; + result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); + std::vector gold = {0, 0, 0, 1, 1, 1, 0, 1, 2, 0, 1, 2}; + EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); +} diff --git a/test/verify/test_nonzero.cpp b/test/verify/test_nonzero.cpp index dbbbad4a548..ac894195bb8 100644 --- a/test/verify/test_nonzero.cpp +++ b/test/verify/test_nonzero.cpp @@ -51,3 +51,24 @@ template struct test_nonzero; template struct test_nonzero; template struct test_nonzero; template struct test_nonzero; + +template +struct test_nonzero_transpose : verify_program> +{ + migraphx::program create_program() const + { + migraphx::program p; + auto* mm = p.get_main_module(); + migraphx::shape s{DType, {2, 3}}; + auto x = mm->add_parameter("data", s); + auto transposed = + mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), x); + auto r = mm->add_instruction(migraphx::make_op("nonzero"), transposed); + mm->add_return({r}); + + return p; + } +}; + +template struct test_nonzero_transpose; +template struct test_nonzero_transpose; From e4736b3a64364edeeaf415f008e5368d28cea5ce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Jul 2026 14:40:40 -0400 Subject: [PATCH 25/42] Bump gitpython from 3.1.50 to 3.1.52 in /docs/sphinx (#5091) --- docs/sphinx/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sphinx/requirements.txt b/docs/sphinx/requirements.txt index 9cd2e8fd23a..943b4a7081c 100644 --- a/docs/sphinx/requirements.txt +++ b/docs/sphinx/requirements.txt @@ -87,7 +87,7 @@ fastjsonschema==2.20.0 # rocm-docs-core gitdb==4.0.11 # via gitpython -gitpython==3.1.50 +gitpython==3.1.52 # via rocm-docs-core greenlet==3.1.1 # via sqlalchemy From 82f2328d10230c4182c80e7bfbb08bba2ad2e623 Mon Sep 17 00:00:00 2001 From: Chris Austen Date: Thu, 23 Jul 2026 14:41:19 -0400 Subject: [PATCH 26/42] Update MIGraphX version number to 2.17 (#5080) --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0989ccab1c2..26ba9aaaef0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -144,7 +144,7 @@ include(ROCMSetupVersion) option(BUILD_DEV "Build for development purpose only" OFF) -rocm_setup_version(VERSION 2.16.0) +rocm_setup_version(VERSION 2.17.0) math(EXPR MIGRAPHX_SO_MAJOR_VERSION "(${PROJECT_VERSION_MAJOR} * 1000 * 1000) + (${PROJECT_VERSION_MINOR} * 1000) + ${PROJECT_VERSION_PATCH}") set(MIGRAPHX_SO_VERSION ${MIGRAPHX_SO_MAJOR_VERSION}.0) From 8c5dbe2a0a14f673e09b107c2e8feea547b9944e Mon Sep 17 00:00:00 2001 From: Paul Fultz II Date: Thu, 23 Jul 2026 13:44:47 -0500 Subject: [PATCH 27/42] Add missing hsa-amd-aqlprofile package (#5083) --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 5b3c2e798d1..367f60b6e99 100644 --- a/Dockerfile +++ b/Dockerfile @@ -65,6 +65,7 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \ hipify-clang \ hiprand-dev \ hipsparselt \ + hsa-amd-aqlprofile \ half \ libssl-dev \ zlib1g-dev && \ From 6b9fc74d439d283182595f27529f44c76592d53a Mon Sep 17 00:00:00 2001 From: Shiv Date: Thu, 23 Jul 2026 13:04:24 -0700 Subject: [PATCH 28/42] update to simplify IR --- src/CMakeLists.txt | 2 +- src/eliminate_contiguous.cpp | 3 +- src/include/migraphx/dyn_output.hpp | 20 +- src/include/migraphx/op/eval_expr.hpp | 144 +++++++ src/include/migraphx/op/resolve_sym_expr.hpp | 96 ----- src/include/migraphx/operation.hpp | 425 +++++++++++++++++-- src/instruction.cpp | 3 +- src/program.cpp | 14 +- src/targets/cpu/lowering.cpp | 4 +- src/targets/ref/lowering.cpp | 4 +- test/op_shape_test.cpp | 36 +- test/ref/eval_expr.cpp | 84 ++++ test/ref/resolve_sym_expr.cpp | 96 ----- test/ref/slice.cpp | 42 +- tools/include/operation.hpp | 177 ++++++-- 15 files changed, 838 insertions(+), 312 deletions(-) create mode 100644 src/include/migraphx/op/eval_expr.hpp delete mode 100644 src/include/migraphx/op/resolve_sym_expr.hpp create mode 100644 test/ref/eval_expr.cpp delete mode 100644 test/ref/resolve_sym_expr.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0327965a3d4..16b0260b004 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -218,6 +218,7 @@ register_migraphx_ops( elu equal erf + eval_expr exp fill fixed_pad @@ -286,7 +287,6 @@ register_migraphx_ops( reshape reshape_lazy resize - resolve_sym_expr reverse rnn rnn_last_cell_output diff --git a/src/eliminate_contiguous.cpp b/src/eliminate_contiguous.cpp index 04667233b27..f8c94753523 100644 --- a/src/eliminate_contiguous.cpp +++ b/src/eliminate_contiguous.cpp @@ -171,7 +171,8 @@ static void remove_contiguous(const std::string& op_name, module& m, F f) shape computed_shape = c.compute_shape({prev->get_shape()}); const std::vector& prev_eval = {prev->eval()}; // prev_eval should not be used in make_compute_output_shape() as computed_shape is static - auto co_shape = make_compute_output_shape(pack(c, computed_shape, prev_eval)); + auto co_shape = make_compute_output_shape( + pack(c, computed_shape, std::vector{prev->get_shape()}, prev_eval)); literals[i] = c.compute(co_shape, prev_eval); }); diff --git a/src/include/migraphx/dyn_output.hpp b/src/include/migraphx/dyn_output.hpp index 2ce0ade4217..be71e2e5e52 100644 --- a/src/include/migraphx/dyn_output.hpp +++ b/src/include/migraphx/dyn_output.hpp @@ -27,6 +27,7 @@ #include #include #include +#include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { @@ -37,6 +38,8 @@ struct dyn_output shape ins_shape; // shape computed at eval time using input arguments shape computed_shape; + // original shapes of the instruction inputs + std::vector input_shapes; }; /** @@ -51,18 +54,25 @@ struct compute_output_shape operator dyn_output() const { - return ins_inputs([](const auto& x, shape ins_shape, const std::vector& inputs) { + return ins_inputs([](const auto& x, + shape ins_shape, + const std::vector& input_shapes, + const std::vector& inputs) { + auto original_inputs = input_shapes.empty() ? to_shapes(inputs) : input_shapes; // some op returns a tuple shape e.g. TopK if(ins_shape.any_of_dynamic()) - return dyn_output{ins_shape, compute_shape(x, to_shapes(inputs))}; - return dyn_output{ins_shape, ins_shape}; + return dyn_output{ + ins_shape, compute_shape(x, to_shapes(inputs)), std::move(original_inputs)}; + return dyn_output{ins_shape, ins_shape, std::move(original_inputs)}; }); } operator shape() const { - return ins_inputs( - [](const auto&, shape ins_shape, const std::vector&) { return ins_shape; }); + return ins_inputs([](const auto&, + shape ins_shape, + const std::vector&, + const std::vector&) { return ins_shape; }); } }; diff --git a/src/include/migraphx/op/eval_expr.hpp b/src/include/migraphx/op/eval_expr.hpp new file mode 100644 index 00000000000..bfe91ffbea0 --- /dev/null +++ b/src/include/migraphx/op/eval_expr.hpp @@ -0,0 +1,144 @@ +/* + * 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_OPERATORS_EVAL_EXPR_HPP +#define MIGRAPHX_GUARD_OPERATORS_EVAL_EXPR_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { +namespace op { + +struct eval_expr +{ + std::vector expressions{}; + + template + static auto reflect(Self& self, F f) + { + return pack(f(self.expressions, "expressions")); + } + + std::string name() const { return "eval_expr"; } + + static void collect_variables(const sym::expr& e, std::vector& variables) + { + if(e.name() == "variable") + { + auto variable = sym::as_symbol(e); + if(std::none_of(variables.begin(), variables.end(), [&](const auto& v) { + return sym::same_symbol(v, variable); + })) + variables.push_back(std::move(variable)); + return; + } + for(const auto& child : e.children()) + collect_variables(child, variables); + } + + static std::vector direct_variables(const shape& s) + { + std::vector result; + if(not s.symbolic()) + return result; + for(const auto& d : s.dyn_dims()) + { + if(d.sym_expr.name() != "variable") + continue; + auto variable = sym::as_symbol(d.sym_expr); + if(std::none_of(result.begin(), result.end(), [&](const auto& v) { + return sym::same_symbol(v, variable); + })) + result.push_back(std::move(variable)); + } + return result; + } + + shape compute_shape(const std::vector& inputs) const + { + check_shapes{inputs, *this, true}.has(1); + std::vector required; + for(const auto& expression : expressions) + collect_variables(expression, required); + auto available = direct_variables(inputs.front()); + for(const auto& variable : required) + { + if(std::none_of(available.begin(), available.end(), [&](const auto& v) { + return sym::same_symbol(v, variable); + })) + MIGRAPHX_THROW("EVAL_EXPR: Symbol '" + variable.to_string() + + "' is not a direct input dimension"); + } + return shape{shape::int64_type, {expressions.size()}}; + } + + argument compute(const dyn_output& dyn_out, std::vector args) const + { + assert(args.size() == 1); + assert(dyn_out.input_shapes.size() == 1); + const auto& input_shape = dyn_out.input_shapes.front(); + auto lens = args.front().get_shape().lens(); + if(input_shape.ndim() != lens.size()) + MIGRAPHX_THROW("EVAL_EXPR: Runtime input rank does not match its symbolic shape"); + + std::unordered_map values; + if(input_shape.symbolic()) + { + const auto& dims = input_shape.dyn_dims(); + for(std::size_t axis = 0; axis < dims.size(); ++axis) + { + if(dims[axis].sym_expr.name() != "variable") + continue; + auto variable = sym::as_symbol(dims[axis].sym_expr); + auto result = values.emplace(variable, lens[axis]); + if(not result.second and result.first->second != lens[axis]) + MIGRAPHX_THROW( + "EVAL_EXPR: Repeated symbol has inconsistent runtime dimensions"); + } + } + + argument result{shape{shape::int64_type, {expressions.size()}}}; + result.visit([&](auto output) { + std::transform(expressions.begin(), + expressions.end(), + output.begin(), + [&](const auto& e) { return e.eval_uint(values); }); + }); + return result; + } +}; + +} // namespace op +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx + +#endif diff --git a/src/include/migraphx/op/resolve_sym_expr.hpp b/src/include/migraphx/op/resolve_sym_expr.hpp deleted file mode 100644 index 0966a505761..00000000000 --- a/src/include/migraphx/op/resolve_sym_expr.hpp +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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_OPERATORS_RESOLVE_SYM_EXPR_HPP -#define MIGRAPHX_GUARD_OPERATORS_RESOLVE_SYM_EXPR_HPP - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace migraphx { -inline namespace MIGRAPHX_INLINE_NS { -namespace op { - -/** - * Evaluate symbolic dimension expressions at runtime. Dynamic ops (e.g. slice) keep their - * symbolic dim_like bounds as attributes for compile-time shape inference; resolve_sym_expr turns - * those expressions into the concrete values fed to the op's runtime-tensor inputs. - * - * exprs: symbolic expressions to evaluate. symbols: the root variables they reference. - * Inputs: one scalar int per symbol, in `symbols` order (symbols[i] = args[i]); each is a single - * root-dimension value, e.g. an element of a `dimensions_of` output. - * Output: a tuple with one 1-D int64 element per expr, element i = eval(exprs[i]), unclamped. - */ -struct resolve_sym_expr -{ - std::vector exprs{}; - std::vector symbols{}; - - template - static auto reflect(Self& self, F f) - { - return pack(f(self.exprs, "exprs"), f(self.symbols, "symbols")); - } - - std::string name() const { return "resolve_sym_expr"; } - - shape compute_shape(const std::vector& inputs) const - { - check_shapes{inputs, *this}.has(symbols.size()).nelements(1); - return shape{std::vector(exprs.size(), shape{shape::int64_type, {1}})}; - } - - argument compute(const shape& output_shape, std::vector args) const - { - assert(args.size() == symbols.size()); - std::unordered_map smap; - smap.reserve(symbols.size()); - for(std::size_t i = 0; i < symbols.size(); ++i) - smap[symbols[i]] = args[i].at(); - const auto& sub_shapes = output_shape.sub_shapes(); - assert(sub_shapes.size() == exprs.size()); - std::vector results(exprs.size()); - std::transform(exprs.begin(), - exprs.end(), - sub_shapes.begin(), - results.begin(), - [&](const sym::expr& e, const shape& s) { - argument r{s}; - r.visit([&](auto out) { out[0] = e.eval_uint(smap); }); - return r; - }); - return argument{results}; - } -}; - -} // namespace op -} // namespace MIGRAPHX_INLINE_NS -} // namespace migraphx - -#endif diff --git a/src/include/migraphx/operation.hpp b/src/include/migraphx/operation.hpp index 395c0942dd4..81a813ca401 100644 --- a/src/include/migraphx/operation.hpp +++ b/src/include/migraphx/operation.hpp @@ -79,6 +79,10 @@ struct operation * the same the `output` shape. */ argument compute(context& ctx, const shape& output, const std::vector& input) const; + argument compute(context& ctx, + const shape& output, + const std::vector& input_shapes, + const std::vector& input) const; /// An optional method to return which arguments the output will alias. If /// there is no aliased output then an empty vector can be returned. std::vector output_alias(const std::vector& input) const; @@ -202,78 +206,126 @@ auto compute_op(rank<1>, const T& x, context& ctx, const shape& output_shape, + const std::vector& input_shapes, const std::vector& input) -> decltype(x.compute(auto_any_cast(ctx), - make_compute_output_shape(pack(x, output_shape, input)), + make_compute_output_shape(pack(x, output_shape, input_shapes, input)), input)) { - return x.compute( - auto_any_cast(ctx), make_compute_output_shape(pack(x, output_shape, input)), input); + return x.compute(auto_any_cast(ctx), + make_compute_output_shape(pack(x, output_shape, input_shapes, input)), + input); } template -argument compute_op(rank<0>, const T& x, context&, const shape&, const std::vector&) +argument compute_op(rank<0>, + const T& x, + context&, + const shape&, + const std::vector&, + const std::vector&) { std::string name = x.name(); MIGRAPHX_THROW("Not computable: " + name); } +template +argument compute_op(const T& x, + context& ctx, + const shape& output_shape, + const std::vector& input_shapes, + const std::vector& input) +{ + return compute_op(rank<1>{}, x, ctx, output_shape, input_shapes, input); +} + template argument compute_op(const T& x, context& ctx, const shape& output_shape, const std::vector& input) { - return compute_op(rank<1>{}, x, ctx, output_shape, input); + return compute_op(x, ctx, output_shape, std::vector{}, input); } template -auto compute_op(rank<1>, const T& x, const shape& output_shape, const std::vector& input) - -> decltype(x.compute(make_compute_output_shape(pack(x, output_shape, input)), input)) +auto compute_op(rank<1>, + const T& x, + const shape& output_shape, + const std::vector& input_shapes, + const std::vector& input) + -> decltype(x.compute(make_compute_output_shape(pack(x, output_shape, input_shapes, input)), + input)) { - return x.compute(make_compute_output_shape(pack(x, output_shape, input)), input); + return x.compute(make_compute_output_shape(pack(x, output_shape, input_shapes, input)), input); } template -argument compute_op(rank<0>, const T& x, const shape&, const std::vector&) +argument compute_op( + rank<0>, const T& x, const shape&, const std::vector&, const std::vector&) { std::string name = x.name(); MIGRAPHX_THROW("Not computable: " + name); } +template +argument compute_op(const T& x, + const shape& output_shape, + const std::vector& input_shapes, + const std::vector& input) +{ + return compute_op(rank<1>{}, x, output_shape, input_shapes, input); +} + template argument compute_op(const T& x, const shape& output_shape, const std::vector& input) { - return compute_op(rank<1>{}, x, output_shape, input); + return compute_op(x, output_shape, std::vector{}, input); } template auto compute_op(rank<1>, const T& x, const shape& output, + const std::vector& input_shapes, const std::vector& inputs, const std::vector& module_args, - F f) -> decltype(x.compute(make_compute_output_shape(pack(x, output, inputs)), - inputs, - module_args, - std::move(f))) + F f) + -> decltype(x.compute(make_compute_output_shape(pack(x, output, input_shapes, inputs)), + inputs, + module_args, + std::move(f))) { - return x.compute( - make_compute_output_shape(pack(x, output, inputs)), inputs, module_args, std::move(f)); + return x.compute(make_compute_output_shape(pack(x, output, input_shapes, inputs)), + inputs, + module_args, + std::move(f)); } template argument compute_op(rank<0>, const T& x, const shape& output, + const std::vector& input_shapes, const std::vector& inputs, const std::vector& module_args, F) // NOLINT { if(module_args.empty()) - return compute_op(x, output, inputs); + return compute_op(x, output, input_shapes, inputs); std::string name = x.name(); MIGRAPHX_THROW("Not computable: " + name); } +template +argument compute_op(const T& x, + const shape& output, + const std::vector& input_shapes, + const std::vector& inputs, + const std::vector& module_args, + F f) +{ + return compute_op(rank<1>{}, x, output, input_shapes, inputs, module_args, std::move(f)); +} + template argument compute_op(const T& x, const shape& output, @@ -281,7 +333,7 @@ argument compute_op(const T& x, const std::vector& module_args, F f) { - return compute_op(rank<1>{}, x, output, inputs, module_args, std::move(f)); + return compute_op(x, output, std::vector{}, inputs, module_args, std::move(f)); } template @@ -289,17 +341,18 @@ auto compute_op(rank<4>, const T& x, context& ctx, const shape& output, + const std::vector& input_shapes, const std::vector& inputs, const std::vector& module_args, F f) // NOLINT -> decltype(x.compute(auto_any_cast(ctx), - make_compute_output_shape(pack(x, output, inputs)), + make_compute_output_shape(pack(x, output, input_shapes, inputs)), inputs, module_args, std::move(f))) { return x.compute(auto_any_cast(ctx), - make_compute_output_shape(pack(x, output, inputs)), + make_compute_output_shape(pack(x, output, input_shapes, inputs)), inputs, module_args, std::move(f)); @@ -310,14 +363,19 @@ auto compute_op(rank<3>, const T& x, context&, const shape& output, + const std::vector& input_shapes, const std::vector& inputs, const std::vector& module_args, F f) // NOLINT - -> decltype(x.compute( - make_compute_output_shape(pack(x, output, inputs)), inputs, module_args, std::move(f))) + -> decltype(x.compute(make_compute_output_shape(pack(x, output, input_shapes, inputs)), + inputs, + module_args, + std::move(f))) { - return x.compute( - make_compute_output_shape(pack(x, output, inputs)), inputs, module_args, std::move(f)); + return x.compute(make_compute_output_shape(pack(x, output, input_shapes, inputs)), + inputs, + module_args, + std::move(f)); } template @@ -325,12 +383,13 @@ auto compute_op(rank<2>, const T& x, context&, const shape& output, + const std::vector& input_shapes, const std::vector& inputs, const std::vector&, F) // NOLINT - -> decltype(x.compute(make_compute_output_shape(pack(x, output, inputs)), inputs)) + -> decltype(x.compute(make_compute_output_shape(pack(x, output, input_shapes, inputs)), inputs)) { - return x.compute(make_compute_output_shape(pack(x, output, inputs)), inputs); + return x.compute(make_compute_output_shape(pack(x, output, input_shapes, inputs)), inputs); } template @@ -338,15 +397,17 @@ auto compute_op(rank<1>, const T& x, context& ctx, const shape& output, + const std::vector& input_shapes, const std::vector& inputs, const std::vector&, F) // NOLINT -> decltype(x.compute(auto_any_cast(ctx), - make_compute_output_shape(pack(x, output, inputs)), + make_compute_output_shape(pack(x, output, input_shapes, inputs)), inputs)) { - return x.compute( - auto_any_cast(ctx), make_compute_output_shape(pack(x, output, inputs)), inputs); + return x.compute(auto_any_cast(ctx), + make_compute_output_shape(pack(x, output, input_shapes, inputs)), + inputs); } template @@ -354,6 +415,7 @@ argument compute_op(rank<0>, const T& x, context&, const shape&, + const std::vector&, const std::vector&, const std::vector&, F) // NOLINT @@ -362,6 +424,18 @@ argument compute_op(rank<0>, MIGRAPHX_THROW("Not computable: " + name); } +template +argument compute_op(const T& x, + context& ctx, + const shape& output, + const std::vector& input_shapes, + const std::vector& inputs, + const std::vector& module_args, + F f) +{ + return compute_op(rank<4>{}, x, ctx, output, input_shapes, inputs, module_args, std::move(f)); +} + template argument compute_op(const T& x, context& ctx, @@ -370,7 +444,7 @@ argument compute_op(const T& x, const std::vector& module_args, F f) { - return compute_op(rank<4>{}, x, ctx, output, inputs, module_args, std::move(f)); + return compute_op(x, ctx, output, std::vector{}, inputs, module_args, std::move(f)); } template @@ -378,7 +452,9 @@ auto is_context_free_op(rank<1>, const T& x, const shape& output_shape, const std::vector& input) - -> decltype(x.compute(make_compute_output_shape(pack(x, output_shape, input)), input), + -> decltype(x.compute( + make_compute_output_shape(pack(x, output_shape, std::vector{}, input)), + input), std::true_type{}); template @@ -530,9 +606,25 @@ struct MIGRAPHX_EXPORT operation // (optional) argument compute(context& ctx, const shape& output, const std::vector& input) const; // (optional) + argument compute(context& ctx, + const shape& output, + const std::vector& input_shapes, + const std::vector& input) const; + // (optional) argument compute(const shape& output, const std::vector& input) const; // (optional) argument compute(const shape& output, + const std::vector& input_shapes, + const std::vector& input) const; + // (optional) + argument compute(const shape& output, + const std::vector& input, + const std::vector& module_args, + std::function( + module_ref&, const std::unordered_map&)> run) const; + // (optional) + argument compute(const shape& output, + const std::vector& input_shapes, const std::vector& input, const std::vector& module_args, std::function( @@ -545,6 +637,14 @@ struct MIGRAPHX_EXPORT operation std::function( module_ref&, const std::unordered_map&)> run) const; // (optional) + argument compute(context& ctx, + const shape& output, + const std::vector& input_shapes, + const std::vector& input, + const std::vector& module_args, + std::function( + module_ref&, const std::unordered_map&)> run) const; + // (optional) value to_value() const; // (optional) void from_value(const value& v); @@ -728,6 +828,29 @@ struct operation return detail::compute_op(private_detail_te_self, ctx, output, input); } + template + static auto private_detail_te_default_compute(char, + T&& private_detail_te_self, + context& ctx, + const shape& output, + const std::vector& input_shapes, + const std::vector& input) + -> decltype(private_detail_te_self.compute(ctx, output, input_shapes, input)) + { + return private_detail_te_self.compute(ctx, output, input_shapes, input); + } + + template + static argument private_detail_te_default_compute(float, + T&& private_detail_te_self, + context& ctx, + const shape& output, + const std::vector& input_shapes, + const std::vector& input) + { + return detail::compute_op(private_detail_te_self, ctx, output, input_shapes, input); + } + template static auto private_detail_te_default_compute(char, T&& private_detail_te_self, @@ -747,6 +870,27 @@ struct operation return detail::compute_op(private_detail_te_self, output, input); } + template + static auto private_detail_te_default_compute(char, + T&& private_detail_te_self, + const shape& output, + const std::vector& input_shapes, + const std::vector& input) + -> decltype(private_detail_te_self.compute(output, input_shapes, input)) + { + return private_detail_te_self.compute(output, input_shapes, input); + } + + template + static argument private_detail_te_default_compute(float, + T&& private_detail_te_self, + const shape& output, + const std::vector& input_shapes, + const std::vector& input) + { + return detail::compute_op(private_detail_te_self, output, input_shapes, input); + } + template static auto private_detail_te_default_compute( char, @@ -775,6 +919,38 @@ struct operation private_detail_te_self, output, input, module_args, std::move(run)); } + template + static auto private_detail_te_default_compute( + char, + T&& private_detail_te_self, + const shape& output, + const std::vector& input_shapes, + const std::vector& input, + const std::vector& module_args, + std::function(module_ref&, + const std::unordered_map&)> run) + -> decltype(private_detail_te_self.compute( + output, input_shapes, input, module_args, std::move(run))) + { + return private_detail_te_self.compute( + output, input_shapes, input, module_args, std::move(run)); + } + + template + static argument private_detail_te_default_compute( + float, + T&& private_detail_te_self, + const shape& output, + const std::vector& input_shapes, + const std::vector& input, + const std::vector& module_args, + std::function(module_ref&, + const std::unordered_map&)> run) + { + return detail::compute_op( + private_detail_te_self, output, input_shapes, input, module_args, std::move(run)); + } + template static auto private_detail_te_default_compute( char, @@ -805,6 +981,40 @@ struct operation private_detail_te_self, ctx, output, input, module_args, std::move(run)); } + template + static auto private_detail_te_default_compute( + char, + T&& private_detail_te_self, + context& ctx, + const shape& output, + const std::vector& input_shapes, + const std::vector& input, + const std::vector& module_args, + std::function(module_ref&, + const std::unordered_map&)> run) + -> decltype(private_detail_te_self.compute( + ctx, output, input_shapes, input, module_args, std::move(run))) + { + return private_detail_te_self.compute( + ctx, output, input_shapes, input, module_args, std::move(run)); + } + + template + static argument private_detail_te_default_compute( + float, + T&& private_detail_te_self, + context& ctx, + const shape& output, + const std::vector& input_shapes, + const std::vector& input, + const std::vector& module_args, + std::function(module_ref&, + const std::unordered_map&)> run) + { + return detail::compute_op( + private_detail_te_self, ctx, output, input_shapes, input, module_args, std::move(run)); + } + template static auto private_detail_te_default_to_value(char, T&& private_detail_te_self) -> decltype(private_detail_te_self.to_value()) @@ -897,10 +1107,21 @@ struct operation std::declval(), std::declval(), std::declval&>()), + private_detail_te_default_compute(char(0), + std::declval(), + std::declval(), + std::declval(), + std::declval&>(), + std::declval&>()), private_detail_te_default_compute(char(0), std::declval(), std::declval(), std::declval&>()), + private_detail_te_default_compute(char(0), + std::declval(), + std::declval(), + std::declval&>(), + std::declval&>()), private_detail_te_default_compute( char(0), std::declval(), @@ -909,6 +1130,15 @@ struct operation std::declval&>(), std::declval( module_ref&, const std::unordered_map&)>>()), + private_detail_te_default_compute( + char(0), + std::declval(), + std::declval(), + std::declval&>(), + std::declval&>(), + std::declval&>(), + std::declval( + module_ref&, const std::unordered_map&)>>()), private_detail_te_default_compute( char(0), std::declval(), @@ -918,6 +1148,16 @@ struct operation std::declval&>(), std::declval( module_ref&, const std::unordered_map&)>>()), + private_detail_te_default_compute( + char(0), + std::declval(), + std::declval(), + std::declval(), + std::declval&>(), + std::declval&>(), + std::declval&>(), + std::declval( + module_ref&, const std::unordered_map&)>>()), private_detail_te_default_to_value(char(0), std::declval()), private_detail_te_default_from_value(char(0), @@ -952,7 +1192,7 @@ struct operation typename = private_te_constraints, typename = typename std::enable_if< not std::is_same, operation>{}>::type> - operation& operator=(PrivateDetailTypeErasedT && value) + operation& operator=(PrivateDetailTypeErasedT&& value) { using std::swap; auto* derived = this->any_cast>(); @@ -1066,12 +1306,29 @@ struct operation return (*this).private_detail_te_get_handle().compute(ctx, output, input); } + argument compute(context& ctx, + const shape& output, + const std::vector& input_shapes, + const std::vector& input) const + { + assert((*this).private_detail_te_handle_mem_var); + return (*this).private_detail_te_get_handle().compute(ctx, output, input_shapes, input); + } + argument compute(const shape& output, const std::vector& input) const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().compute(output, input); } + argument compute(const shape& output, + const std::vector& input_shapes, + const std::vector& input) const + { + assert((*this).private_detail_te_handle_mem_var); + return (*this).private_detail_te_get_handle().compute(output, input_shapes, input); + } + argument compute(const shape& output, const std::vector& input, const std::vector& module_args, @@ -1083,6 +1340,18 @@ struct operation output, input, module_args, std::move(run)); } + argument compute(const shape& output, + const std::vector& input_shapes, + const std::vector& input, + const std::vector& module_args, + std::function( + module_ref&, const std::unordered_map&)> run) const + { + assert((*this).private_detail_te_handle_mem_var); + return (*this).private_detail_te_get_handle().compute( + output, input_shapes, input, module_args, std::move(run)); + } + argument compute(context& ctx, const shape& output, const std::vector& input, @@ -1095,6 +1364,19 @@ struct operation ctx, output, input, module_args, std::move(run)); } + argument compute(context& ctx, + const shape& output, + const std::vector& input_shapes, + const std::vector& input, + const std::vector& module_args, + std::function( + module_ref&, const std::unordered_map&)> run) const + { + assert((*this).private_detail_te_handle_mem_var); + return (*this).private_detail_te_get_handle().compute( + ctx, output, input_shapes, input, module_args, std::move(run)); + } + value to_value() const { assert((*this).private_detail_te_handle_mem_var); @@ -1153,7 +1435,14 @@ struct operation const std::vector& mod_args) const = 0; virtual argument compute(context& ctx, const shape& output, const std::vector& input) const = 0; + virtual argument compute(context& ctx, + const shape& output, + const std::vector& input_shapes, + const std::vector& input) const = 0; virtual argument compute(const shape& output, const std::vector& input) const = 0; + virtual argument compute(const shape& output, + const std::vector& input_shapes, + const std::vector& input) const = 0; virtual argument compute(const shape& output, const std::vector& input, @@ -1161,12 +1450,27 @@ struct operation std::function( module_ref&, const std::unordered_map&)> run) const = 0; virtual argument + compute(const shape& output, + const std::vector& input_shapes, + const std::vector& input, + const std::vector& module_args, + std::function( + module_ref&, const std::unordered_map&)> run) const = 0; + virtual argument compute(context& ctx, const shape& output, const std::vector& input, const std::vector& module_args, std::function( module_ref&, const std::unordered_map&)> run) const = 0; + virtual argument + compute(context& ctx, + const shape& output, + const std::vector& input_shapes, + const std::vector& input, + const std::vector& module_args, + std::function( + module_ref&, const std::unordered_map&)> run) const = 0; virtual value to_value() const = 0; virtual void from_value(const value& v) = 0; virtual value attributes() const = 0; @@ -1270,6 +1574,16 @@ struct operation char(0), private_detail_te_value, ctx, output, input); } + argument compute(context& ctx, + const shape& output, + const std::vector& input_shapes, + const std::vector& input) const override + { + + return private_detail_te_default_compute( + char(0), private_detail_te_value, ctx, output, input_shapes, input); + } + argument compute(const shape& output, const std::vector& input) const override { @@ -1277,6 +1591,15 @@ struct operation char(0), private_detail_te_value, output, input); } + argument compute(const shape& output, + const std::vector& input_shapes, + const std::vector& input) const override + { + + return private_detail_te_default_compute( + char(0), private_detail_te_value, output, input_shapes, input); + } + argument compute( const shape& output, const std::vector& input, @@ -1289,6 +1612,24 @@ struct operation char(0), private_detail_te_value, output, input, module_args, std::move(run)); } + argument compute( + const shape& output, + const std::vector& input_shapes, + const std::vector& input, + const std::vector& module_args, + std::function( + module_ref&, const std::unordered_map&)> run) const override + { + + return private_detail_te_default_compute(char(0), + private_detail_te_value, + output, + input_shapes, + input, + module_args, + std::move(run)); + } + argument compute( context& ctx, const shape& output, @@ -1302,6 +1643,26 @@ struct operation char(0), private_detail_te_value, ctx, output, input, module_args, std::move(run)); } + argument compute( + context& ctx, + const shape& output, + const std::vector& input_shapes, + const std::vector& input, + const std::vector& module_args, + std::function( + module_ref&, const std::unordered_map&)> run) const override + { + + return private_detail_te_default_compute(char(0), + private_detail_te_value, + ctx, + output, + input_shapes, + input, + module_args, + std::move(run)); + } + value to_value() const override { diff --git a/src/instruction.cpp b/src/instruction.cpp index 94028b7ee69..04d13e68555 100644 --- a/src/instruction.cpp +++ b/src/instruction.cpp @@ -429,7 +429,8 @@ argument instruction::eval(bool check_eval) const ins.inputs().end(), std::back_inserter(args), [&](auto arg) { return self(*arg); }); - auto value = ins.normalized_operator().compute(ins.get_shape(), args); + auto value = + ins.normalized_operator().compute(ins.get_shape(), to_shapes(ins.inputs()), args); cache.emplace(&ins, value); return value; })(*this); diff --git a/src/program.cpp b/src/program.cpp index 5fc1dc3ec75..0f865a16c90 100644 --- a/src/program.cpp +++ b/src/program.cpp @@ -580,13 +580,19 @@ static std::vector generic_eval(const module* mod, results.insert_or_assign( ins, trace(ins, [&] { - auto op = ins->normalized_operator(); + auto op = ins->normalized_operator(); + auto input_shapes = to_shapes(ins->inputs()); if(op.is_context_free()) - return op.compute(ins->get_shape(), values, mod_args, module_eval); + return op.compute( + ins->get_shape(), input_shapes, values, mod_args, module_eval); if(ins->get_target_id() >= ctx.size()) MIGRAPHX_THROW("No context available for " + op.name()); - return op.compute( - ctx[ins->get_target_id()], ins->get_shape(), values, mod_args, module_eval); + return op.compute(ctx[ins->get_target_id()], + ins->get_shape(), + input_shapes, + values, + mod_args, + module_eval); })); } assert(results.find(ins) != results.end()); diff --git a/src/targets/cpu/lowering.cpp b/src/targets/cpu/lowering.cpp index fdea6202a4b..b8a2f68e2f2 100644 --- a/src/targets/cpu/lowering.cpp +++ b/src/targets/cpu/lowering.cpp @@ -142,9 +142,9 @@ struct cpu_op } std::string name() const { return "cpu::op"; } shape compute_shape(const std::vector& inputs) const { return op.compute_shape(inputs); } - argument compute(context&, const shape& output_shape, const std::vector& args) const + argument compute(context&, const dyn_output& dyn_out, const std::vector& args) const { - return op.compute(output_shape, args); + return op.compute(dyn_out.ins_shape, dyn_out.input_shapes, args); } value to_value() const { diff --git a/src/targets/ref/lowering.cpp b/src/targets/ref/lowering.cpp index 3e28071ea3b..2a8536fe3b6 100644 --- a/src/targets/ref/lowering.cpp +++ b/src/targets/ref/lowering.cpp @@ -179,9 +179,9 @@ struct ref_op } std::string name() const { return "ref::op"; } shape compute_shape(const std::vector& inputs) const { return op.compute_shape(inputs); } - argument compute(context&, const shape& output_shape, const std::vector& args) const + argument compute(context&, const dyn_output& dyn_out, const std::vector& args) const { - return op.compute(output_shape, args); + return op.compute(dyn_out.ins_shape, dyn_out.input_shapes, args); } value to_value() const { diff --git a/test/op_shape_test.cpp b/test/op_shape_test.cpp index 8fa0e20fd76..b9cf19823f2 100644 --- a/test/op_shape_test.cpp +++ b/test/op_shape_test.cpp @@ -5295,33 +5295,29 @@ TEST_CASE(slice_dyn_nonfixed_keeps_other_optimals) input); } -TEST_CASE(resolve_sym_expr_shape) +TEST_CASE(eval_expr_shape) { - // Output is a tuple with one 1-D int64 element per expr, regardless of the symbolic exprs. auto n = var("n", {1, 16}); - migraphx::shape sv{migraphx::shape::int64_type, {1}}; - migraphx::shape elem{migraphx::shape::int64_type, {1}}; - expect_shape( - migraphx::shape{std::vector{elem, elem}}, - migraphx::make_op( - "resolve_sym_expr", - {{"exprs", migraphx::value::array{migraphx::to_value(n), migraphx::to_value(n)}}, - {"symbols", migraphx::value::array{migraphx::to_value(n)}}}), - sv); + auto h = var("h", {1, 32}); + auto w = var("w", {1, 32}); + migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(3)}, dd{h}, dd{w}}}; + expect_shape(migraphx::shape{migraphx::shape::int64_type, {3}}, + migraphx::make_op("eval_expr", + {{"expressions", + migraphx::value::array{migraphx::to_value(n), + migraphx::to_value(h / lit(2)), + migraphx::to_value(w / lit(2))}}}), + input); } -TEST_CASE(resolve_sym_expr_bad_input) +TEST_CASE(eval_expr_missing_symbol) { - // One scalar value input is required per symbol; here 2 symbols but only 1 input. auto m = var("m", {1, 16}); auto n = var("n", {1, 16}); - migraphx::shape sv{migraphx::shape::int64_type, {1}}; - throws_shape( - migraphx::make_op( - "resolve_sym_expr", - {{"exprs", migraphx::value::array{migraphx::to_value(m)}}, - {"symbols", migraphx::value::array{migraphx::to_value(m), migraphx::to_value(n)}}}), - sv); + migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(3)}}}; + throws_shape(migraphx::make_op( + "eval_expr", {{"expressions", migraphx::value::array{migraphx::to_value(m)}}}), + input); } TEST_CASE(slice_sym) diff --git a/test/ref/eval_expr.cpp b/test/ref/eval_expr.cpp new file mode 100644 index 00000000000..66ab538001a --- /dev/null +++ b/test/ref/eval_expr.cpp @@ -0,0 +1,84 @@ +/* + * 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 + +TEST_CASE(eval_expr_input_shape) +{ + using dd = migraphx::shape::dynamic_dimension; + auto n = migraphx::sym::var("N", {1, 16}); + auto h = migraphx::sym::var("H", {1, 32}); + auto w = migraphx::sym::var("W", {1, 32}); + + migraphx::program p; + auto* mm = p.get_main_module(); + auto x = mm->add_parameter("x", + migraphx::shape{migraphx::shape::float_type, + {dd{n}, dd{migraphx::sym::lit(3)}, dd{h}, dd{w}}}); + mm->add_instruction(migraphx::make_op("eval_expr", + {{"expressions", + migraphx::value::array{ + migraphx::to_value(n), + migraphx::to_value(h / migraphx::sym::lit(2)), + migraphx::to_value(w / migraphx::sym::lit(2))}}}), + x); + p.compile(migraphx::make_target("ref")); + + migraphx::shape input_shape{migraphx::shape::float_type, {7, 3, 10, 12}}; + std::vector data(input_shape.elements()); + auto result = p.eval({{"x", migraphx::argument{input_shape, data.data()}}}).back(); + + std::vector values; + result.visit([&](auto output) { values.assign(output.begin(), output.end()); }); + EXPECT(result.get_shape() == migraphx::shape{migraphx::shape::int64_type, {3}}); + EXPECT(values == std::vector{7, 5, 6}); +} + +TEST_CASE(eval_expr_multi_symbol) +{ + using dd = migraphx::shape::dynamic_dimension; + auto m = migraphx::sym::var("M", {1, 16}); + auto n = migraphx::sym::var("N", {1, 16}); + + migraphx::program p; + auto* mm = p.get_main_module(); + auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {dd{m}, dd{n}}}); + mm->add_instruction( + migraphx::make_op("eval_expr", + {{"expressions", migraphx::value::array{migraphx::to_value(m + n)}}}), + x); + p.compile(migraphx::make_target("ref")); + + migraphx::shape input_shape{migraphx::shape::float_type, {3, 4}}; + std::vector data(input_shape.elements()); + auto result = p.eval({{"x", migraphx::argument{input_shape, data.data()}}}).back(); + + EXPECT(result.get_shape() == migraphx::shape{migraphx::shape::int64_type, {1}}); + EXPECT(result.at() == 7); +} diff --git a/test/ref/resolve_sym_expr.cpp b/test/ref/resolve_sym_expr.cpp deleted file mode 100644 index 8d9b817aecd..00000000000 --- a/test/ref/resolve_sym_expr.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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 - -TEST_CASE(resolve_sym_expr_single_symbol) -{ - // Evaluate two exprs (n and floor(n/2)) of one root symbol n from its runtime value. - auto n = migraphx::sym::var("n", {1, 16}); - auto half = n / migraphx::sym::lit(2); - - migraphx::program p; - auto* mm = p.get_main_module(); - migraphx::shape sv_shape{migraphx::shape::int64_type, {1}}; - auto sv = mm->add_parameter("sym_vals", sv_shape); - mm->add_instruction( - migraphx::make_op( - "resolve_sym_expr", - {{"exprs", migraphx::value::array{migraphx::to_value(n), migraphx::to_value(half)}}, - {"symbols", migraphx::value::array{migraphx::to_value(n)}}}), - sv); - p.compile(migraphx::make_target("ref")); - - migraphx::parameter_map params; - std::vector sv_data = {7}; - params["sym_vals"] = migraphx::argument(sv_shape, sv_data.data()); - auto result = p.eval(params).back(); - - // Output is a tuple: element i = eval(exprs[i]). n = 7, floor(7 / 2) = 3. - migraphx::shape elem{migraphx::shape::int64_type, {1}}; - EXPECT(result.get_shape() == migraphx::shape{std::vector{elem, elem}}); - auto subs = result.get_sub_objects(); - EXPECT(subs.size() == 2); - EXPECT(subs[0].at() == 7); - EXPECT(subs[1].at() == 3); -} - -TEST_CASE(resolve_sym_expr_multi_symbol) -{ - // Two root symbols; one scalar value input per symbol, in `symbols` order. - auto m = migraphx::sym::var("m", {1, 16}); - auto n = migraphx::sym::var("n", {1, 16}); - auto sum = m + n; - - migraphx::program p; - auto* mm = p.get_main_module(); - migraphx::shape val{migraphx::shape::int64_type, {1}}; - auto mv = mm->add_parameter("m_val", val); - auto nv = mm->add_parameter("n_val", val); - mm->add_instruction( - migraphx::make_op( - "resolve_sym_expr", - {{"exprs", migraphx::value::array{migraphx::to_value(sum)}}, - {"symbols", migraphx::value::array{migraphx::to_value(m), migraphx::to_value(n)}}}), - mv, - nv); - p.compile(migraphx::make_target("ref")); - - migraphx::parameter_map params; - std::vector m_data = {3}; - std::vector n_data = {4}; - params["m_val"] = migraphx::argument(val, m_data.data()); - params["n_val"] = migraphx::argument(val, n_data.data()); - auto result = p.eval(params).back(); - - // Single-element tuple: m + n = 3 + 4. - auto subs = result.get_sub_objects(); - EXPECT(subs.size() == 1); - EXPECT(subs[0].at() == 7); -} diff --git a/test/ref/slice.cpp b/test/ref/slice.cpp index 7acc9ef87e2..de953f435e9 100644 --- a/test/ref/slice.cpp +++ b/test/ref/slice.cpp @@ -412,38 +412,30 @@ TEST_CASE(slice_dyn_test1) EXPECT(result.get_shape() == sresult); } -TEST_CASE(slice_sym_resolved_input) +TEST_CASE(slice_eval_expr_input) { - // A late pass lowers `slice[axes=2, starts=0, ends=n]` to this: the symbolic end becomes a - // runtime input from resolve_sym_expr, the concrete start stays an attribute, and slice runs as - // a plain dynamic multi-input slice. - auto n = migraphx::sym::var("n", {1, 3}); + using dd = migraphx::shape::dynamic_dimension; + auto n = migraphx::sym::var("n", {1, 3}); migraphx::program p; auto* mm = p.get_main_module(); - std::vector data(2 * 2 * 3); - std::iota(data.begin(), data.end(), 0); - migraphx::shape s{migraphx::shape::int32_type, {2, 2, 3}}; - auto l0 = mm->add_literal(migraphx::literal{s, data}); + migraphx::shape s{migraphx::shape::int32_type, + {dd{migraphx::sym::lit(2)}, dd{migraphx::sym::lit(2)}, dd{n}}}; + auto x = mm->add_parameter("x", s); - migraphx::shape val{migraphx::shape::int64_type, {1}}; - auto nv = mm->add_parameter("n_val", val); - auto end_tuple = mm->add_instruction( - migraphx::make_op("resolve_sym_expr", - {{"exprs", migraphx::value::array{migraphx::to_value(n)}}, - {"symbols", migraphx::value::array{migraphx::to_value(n)}}}), - nv); - // resolve_sym_expr returns a tuple; extract the single end value to feed slice's runtime input. - auto end_vals = - mm->add_instruction(migraphx::make_op("get_tuple_elem", {{"index", 0}}), end_tuple); - // starts_axes config: attrs starts + axes set, ends arrives as the runtime input. - mm->add_instruction(migraphx::make_op("slice", {{"axes", {2}}, {"starts", {0}}}), l0, end_vals); + auto end_vals = mm->add_instruction( + migraphx::make_op( + "eval_expr", + {{"expressions", + migraphx::value::array{migraphx::to_value(n - migraphx::sym::lit(1))}}}), + x); + mm->add_instruction(migraphx::make_op("slice", {{"axes", {2}}, {"starts", {0}}}), x, end_vals); p.compile(migraphx::make_target("ref")); - migraphx::parameter_map params; - std::vector n_data = {2}; // n = 2 -> slice axis 2 as [0, 2) - params["n_val"] = migraphx::argument(val, n_data.data()); - auto result = p.eval(params).back(); + std::vector data(2 * 2 * 3); + std::iota(data.begin(), data.end(), 0); + migraphx::shape input_shape{migraphx::shape::int32_type, {2, 2, 3}}; + auto result = p.eval({{"x", migraphx::argument{input_shape, data.data()}}}).back(); std::vector gold = {0, 1, 3, 4, 6, 7, 9, 10}; std::vector results_vector; diff --git a/tools/include/operation.hpp b/tools/include/operation.hpp index a2e73c79940..86c24ae890e 100644 --- a/tools/include/operation.hpp +++ b/tools/include/operation.hpp @@ -79,6 +79,10 @@ struct operation * the same the `output` shape. */ argument compute(context& ctx, const shape& output, const std::vector& input) const; + argument compute(context& ctx, + const shape& output, + const std::vector& input_shapes, + const std::vector& input) const; /// An optional method to return which arguments the output will alias. If /// there is no aliased output then an empty vector can be returned. std::vector output_alias(const std::vector& input) const; @@ -204,78 +208,129 @@ auto compute_op(rank<1>, const T& x, context& ctx, const shape& output_shape, + const std::vector& input_shapes, const std::vector& input) -> decltype(x.compute(auto_any_cast(ctx), - make_compute_output_shape(pack(x, output_shape, input)), + make_compute_output_shape(pack(x, output_shape, input_shapes, input)), input)) { - return x.compute( - auto_any_cast(ctx), make_compute_output_shape(pack(x, output_shape, input)), input); + return x.compute(auto_any_cast(ctx), + make_compute_output_shape(pack(x, output_shape, input_shapes, input)), + input); } template -argument compute_op(rank<0>, const T& x, context&, const shape&, const std::vector&) +argument compute_op(rank<0>, + const T& x, + context&, + const shape&, + const std::vector&, + const std::vector&) { std::string name = x.name(); MIGRAPHX_THROW("Not computable: " + name); } +template +argument compute_op(const T& x, + context& ctx, + const shape& output_shape, + const std::vector& input_shapes, + const std::vector& input) +{ + return compute_op(rank<1>{}, x, ctx, output_shape, input_shapes, input); +} + template argument compute_op(const T& x, context& ctx, const shape& output_shape, const std::vector& input) { - return compute_op(rank<1>{}, x, ctx, output_shape, input); + return compute_op(x, ctx, output_shape, std::vector{}, input); } template -auto compute_op(rank<1>, const T& x, const shape& output_shape, const std::vector& input) - -> decltype(x.compute(make_compute_output_shape(pack(x, output_shape, input)), input)) +auto compute_op(rank<1>, + const T& x, + const shape& output_shape, + const std::vector& input_shapes, + const std::vector& input) + -> decltype( + x.compute(make_compute_output_shape(pack(x, output_shape, input_shapes, input)), input)) { - return x.compute(make_compute_output_shape(pack(x, output_shape, input)), input); + return x.compute(make_compute_output_shape(pack(x, output_shape, input_shapes, input)), input); } template -argument compute_op(rank<0>, const T& x, const shape&, const std::vector&) +argument compute_op(rank<0>, + const T& x, + const shape&, + const std::vector&, + const std::vector&) { std::string name = x.name(); MIGRAPHX_THROW("Not computable: " + name); } +template +argument compute_op(const T& x, + const shape& output_shape, + const std::vector& input_shapes, + const std::vector& input) +{ + return compute_op(rank<1>{}, x, output_shape, input_shapes, input); +} + template argument compute_op(const T& x, const shape& output_shape, const std::vector& input) { - return compute_op(rank<1>{}, x, output_shape, input); + return compute_op(x, output_shape, std::vector{}, input); } template auto compute_op(rank<1>, const T& x, const shape& output, + const std::vector& input_shapes, const std::vector& inputs, const std::vector& module_args, - F f) -> decltype(x.compute(make_compute_output_shape(pack(x, output, inputs)), + F f) -> decltype(x.compute(make_compute_output_shape( + pack(x, output, input_shapes, inputs)), inputs, module_args, std::move(f))) { - return x.compute( - make_compute_output_shape(pack(x, output, inputs)), inputs, module_args, std::move(f)); + return x.compute(make_compute_output_shape(pack(x, output, input_shapes, inputs)), + inputs, + module_args, + std::move(f)); } template argument compute_op(rank<0>, const T& x, const shape& output, + const std::vector& input_shapes, const std::vector& inputs, const std::vector& module_args, F) // NOLINT { if(module_args.empty()) - return compute_op(x, output, inputs); + return compute_op(x, output, input_shapes, inputs); std::string name = x.name(); MIGRAPHX_THROW("Not computable: " + name); } +template +argument compute_op(const T& x, + const shape& output, + const std::vector& input_shapes, + const std::vector& inputs, + const std::vector& module_args, + F f) +{ + return compute_op(rank<1>{}, x, output, input_shapes, inputs, module_args, std::move(f)); +} + template argument compute_op(const T& x, const shape& output, @@ -283,7 +338,8 @@ argument compute_op(const T& x, const std::vector& module_args, F f) { - return compute_op(rank<1>{}, x, output, inputs, module_args, std::move(f)); + return compute_op( + x, output, std::vector{}, inputs, module_args, std::move(f)); } template @@ -291,17 +347,18 @@ auto compute_op(rank<4>, const T& x, context& ctx, const shape& output, + const std::vector& input_shapes, const std::vector& inputs, const std::vector& module_args, F f) // NOLINT -> decltype(x.compute(auto_any_cast(ctx), - make_compute_output_shape(pack(x, output, inputs)), + make_compute_output_shape(pack(x, output, input_shapes, inputs)), inputs, module_args, std::move(f))) { return x.compute(auto_any_cast(ctx), - make_compute_output_shape(pack(x, output, inputs)), + make_compute_output_shape(pack(x, output, input_shapes, inputs)), inputs, module_args, std::move(f)); @@ -312,14 +369,20 @@ auto compute_op(rank<3>, const T& x, context&, const shape& output, + const std::vector& input_shapes, const std::vector& inputs, const std::vector& module_args, F f) // NOLINT -> decltype(x.compute( - make_compute_output_shape(pack(x, output, inputs)), inputs, module_args, std::move(f))) + make_compute_output_shape(pack(x, output, input_shapes, inputs)), + inputs, + module_args, + std::move(f))) { - return x.compute( - make_compute_output_shape(pack(x, output, inputs)), inputs, module_args, std::move(f)); + return x.compute(make_compute_output_shape(pack(x, output, input_shapes, inputs)), + inputs, + module_args, + std::move(f)); } template @@ -327,12 +390,14 @@ auto compute_op(rank<2>, const T& x, context&, const shape& output, + const std::vector& input_shapes, const std::vector& inputs, const std::vector&, F) // NOLINT - -> decltype(x.compute(make_compute_output_shape(pack(x, output, inputs)), inputs)) + -> decltype( + x.compute(make_compute_output_shape(pack(x, output, input_shapes, inputs)), inputs)) { - return x.compute(make_compute_output_shape(pack(x, output, inputs)), inputs); + return x.compute(make_compute_output_shape(pack(x, output, input_shapes, inputs)), inputs); } template @@ -340,15 +405,17 @@ auto compute_op(rank<1>, const T& x, context& ctx, const shape& output, + const std::vector& input_shapes, const std::vector& inputs, const std::vector&, F) // NOLINT -> decltype(x.compute(auto_any_cast(ctx), - make_compute_output_shape(pack(x, output, inputs)), + make_compute_output_shape(pack(x, output, input_shapes, inputs)), inputs)) { - return x.compute( - auto_any_cast(ctx), make_compute_output_shape(pack(x, output, inputs)), inputs); + return x.compute(auto_any_cast(ctx), + make_compute_output_shape(pack(x, output, input_shapes, inputs)), + inputs); } template @@ -356,6 +423,7 @@ argument compute_op(rank<0>, const T& x, context&, const shape&, + const std::vector&, const std::vector&, const std::vector&, F) // NOLINT @@ -364,6 +432,19 @@ argument compute_op(rank<0>, MIGRAPHX_THROW("Not computable: " + name); } +template +argument compute_op(const T& x, + context& ctx, + const shape& output, + const std::vector& input_shapes, + const std::vector& inputs, + const std::vector& module_args, + F f) +{ + return compute_op( + rank<4>{}, x, ctx, output, input_shapes, inputs, module_args, std::move(f)); +} + template argument compute_op(const T& x, context& ctx, @@ -372,7 +453,8 @@ argument compute_op(const T& x, const std::vector& module_args, F f) { - return compute_op(rank<4>{}, x, ctx, output, inputs, module_args, std::move(f)); + return compute_op( + x, ctx, output, std::vector{}, inputs, module_args, std::move(f)); } template @@ -380,7 +462,10 @@ auto is_context_free_op(rank<1>, const T& x, const shape& output_shape, const std::vector& input) - -> decltype(x.compute(make_compute_output_shape(pack(x, output_shape, input)), input), + -> decltype(x.compute( + make_compute_output_shape( + pack(x, output_shape, std::vector{}, input)), + input), std::true_type{}); template @@ -556,12 +641,27 @@ lifetime get_lifetime_op(const T&) input = 'const std::vector&', const = True, default = 'detail::compute_op'), + virtual('compute', + returns = 'argument', + ctx = 'context&', + output = 'const shape&', + input_shapes = 'const std::vector&', + input = 'const std::vector&', + const = True, + default = 'detail::compute_op'), virtual('compute', returns = 'argument', output = 'const shape&', input = 'const std::vector&', const = True, default = 'detail::compute_op'), + virtual('compute', + returns = 'argument', + output = 'const shape&', + input_shapes = 'const std::vector&', + input = 'const std::vector&', + const = True, + default = 'detail::compute_op'), virtual( 'compute', returns = 'argument', @@ -572,6 +672,17 @@ lifetime get_lifetime_op(const T&) 'std::function(module_ref&, const std::unordered_map&)>', const = True, default = 'detail::compute_op'), + virtual( + 'compute', + returns = 'argument', + output = 'const shape&', + input_shapes = 'const std::vector&', + input = 'const std::vector&', + module_args = 'const std::vector&', + run = + 'std::function(module_ref&, const std::unordered_map&)>', + const = True, + default = 'detail::compute_op'), virtual( 'compute', returns = 'argument', @@ -583,6 +694,18 @@ lifetime get_lifetime_op(const T&) 'std::function(module_ref&, const std::unordered_map&)>', const = True, default = 'detail::compute_op'), + virtual( + 'compute', + returns = 'argument', + ctx = 'context&', + output = 'const shape&', + input_shapes = 'const std::vector&', + input = 'const std::vector&', + module_args = 'const std::vector&', + run = + 'std::function(module_ref&, const std::unordered_map&)>', + const = True, + default = 'detail::compute_op'), virtual('to_value', returns = 'value', const = True, default = 'detail::to_value_op'), virtual('from_value', v = 'const value&', default = 'detail::from_value_op'), virtual('attributes', returns = 'value', const = True, default = 'detail::attributes_op'), From a0803a123a2a65a6695035740d5392f4ac37f4d2 Mon Sep 17 00:00:00 2001 From: Shiv Date: Thu, 23 Jul 2026 13:51:02 -0700 Subject: [PATCH 29/42] ci fix --- src/include/migraphx/operation.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/include/migraphx/operation.hpp b/src/include/migraphx/operation.hpp index 81a813ca401..0b122718a84 100644 --- a/src/include/migraphx/operation.hpp +++ b/src/include/migraphx/operation.hpp @@ -1192,7 +1192,7 @@ struct operation typename = private_te_constraints, typename = typename std::enable_if< not std::is_same, operation>{}>::type> - operation& operator=(PrivateDetailTypeErasedT&& value) + operation& operator=(PrivateDetailTypeErasedT && value) { using std::swap; auto* derived = this->any_cast>(); From 14163910ba03616d07b2e14217234c3db3a03643 Mon Sep 17 00:00:00 2001 From: Shiv Date: Thu, 23 Jul 2026 14:41:19 -0700 Subject: [PATCH 30/42] format --- tools/include/operation.hpp | 45 +++++++++++++++---------------------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/tools/include/operation.hpp b/tools/include/operation.hpp index 86c24ae890e..0ef5347badd 100644 --- a/tools/include/operation.hpp +++ b/tools/include/operation.hpp @@ -254,18 +254,15 @@ auto compute_op(rank<1>, const shape& output_shape, const std::vector& input_shapes, const std::vector& input) - -> decltype( - x.compute(make_compute_output_shape(pack(x, output_shape, input_shapes, input)), input)) + -> decltype(x.compute(make_compute_output_shape(pack(x, output_shape, input_shapes, input)), + input)) { return x.compute(make_compute_output_shape(pack(x, output_shape, input_shapes, input)), input); } template -argument compute_op(rank<0>, - const T& x, - const shape&, - const std::vector&, - const std::vector&) +argument compute_op( + rank<0>, const T& x, const shape&, const std::vector&, const std::vector&) { std::string name = x.name(); MIGRAPHX_THROW("Not computable: " + name); @@ -293,11 +290,11 @@ auto compute_op(rank<1>, const std::vector& input_shapes, const std::vector& inputs, const std::vector& module_args, - F f) -> decltype(x.compute(make_compute_output_shape( - pack(x, output, input_shapes, inputs)), - inputs, - module_args, - std::move(f))) + F f) + -> decltype(x.compute(make_compute_output_shape(pack(x, output, input_shapes, inputs)), + inputs, + module_args, + std::move(f))) { return x.compute(make_compute_output_shape(pack(x, output, input_shapes, inputs)), inputs, @@ -338,8 +335,7 @@ argument compute_op(const T& x, const std::vector& module_args, F f) { - return compute_op( - x, output, std::vector{}, inputs, module_args, std::move(f)); + return compute_op(x, output, std::vector{}, inputs, module_args, std::move(f)); } template @@ -373,11 +369,10 @@ auto compute_op(rank<3>, const std::vector& inputs, const std::vector& module_args, F f) // NOLINT - -> decltype(x.compute( - make_compute_output_shape(pack(x, output, input_shapes, inputs)), - inputs, - module_args, - std::move(f))) + -> decltype(x.compute(make_compute_output_shape(pack(x, output, input_shapes, inputs)), + inputs, + module_args, + std::move(f))) { return x.compute(make_compute_output_shape(pack(x, output, input_shapes, inputs)), inputs, @@ -394,8 +389,7 @@ auto compute_op(rank<2>, const std::vector& inputs, const std::vector&, F) // NOLINT - -> decltype( - x.compute(make_compute_output_shape(pack(x, output, input_shapes, inputs)), inputs)) + -> decltype(x.compute(make_compute_output_shape(pack(x, output, input_shapes, inputs)), inputs)) { return x.compute(make_compute_output_shape(pack(x, output, input_shapes, inputs)), inputs); } @@ -441,8 +435,7 @@ argument compute_op(const T& x, const std::vector& module_args, F f) { - return compute_op( - rank<4>{}, x, ctx, output, input_shapes, inputs, module_args, std::move(f)); + return compute_op(rank<4>{}, x, ctx, output, input_shapes, inputs, module_args, std::move(f)); } template @@ -453,8 +446,7 @@ argument compute_op(const T& x, const std::vector& module_args, F f) { - return compute_op( - x, ctx, output, std::vector{}, inputs, module_args, std::move(f)); + return compute_op(x, ctx, output, std::vector{}, inputs, module_args, std::move(f)); } template @@ -463,8 +455,7 @@ auto is_context_free_op(rank<1>, const shape& output_shape, const std::vector& input) -> decltype(x.compute( - make_compute_output_shape( - pack(x, output_shape, std::vector{}, input)), + make_compute_output_shape(pack(x, output_shape, std::vector{}, input)), input), std::true_type{}); From 6e490a991d85dc9af0633ccac1e7f215700b860f Mon Sep 17 00:00:00 2001 From: Shiv Date: Thu, 23 Jul 2026 14:59:00 -0700 Subject: [PATCH 31/42] tidy --- src/include/migraphx/operation.hpp | 24 ++++++++++-------------- tools/include/operation.hpp | 24 ++++++++++-------------- 2 files changed, 20 insertions(+), 28 deletions(-) diff --git a/src/include/migraphx/operation.hpp b/src/include/migraphx/operation.hpp index 0b122718a84..fd2d4483699 100644 --- a/src/include/migraphx/operation.hpp +++ b/src/include/migraphx/operation.hpp @@ -288,16 +288,12 @@ auto compute_op(rank<1>, const std::vector& input_shapes, const std::vector& inputs, const std::vector& module_args, - F f) - -> decltype(x.compute(make_compute_output_shape(pack(x, output, input_shapes, inputs)), - inputs, - module_args, - std::move(f))) + const F& f) + -> decltype(x.compute( + make_compute_output_shape(pack(x, output, input_shapes, inputs)), inputs, module_args, f)) { - return x.compute(make_compute_output_shape(pack(x, output, input_shapes, inputs)), - inputs, - module_args, - std::move(f)); + return x.compute( + make_compute_output_shape(pack(x, output, input_shapes, inputs)), inputs, module_args, f); } template @@ -307,7 +303,7 @@ argument compute_op(rank<0>, const std::vector& input_shapes, const std::vector& inputs, const std::vector& module_args, - F) // NOLINT + const F&) { if(module_args.empty()) return compute_op(x, output, input_shapes, inputs); @@ -321,9 +317,9 @@ argument compute_op(const T& x, const std::vector& input_shapes, const std::vector& inputs, const std::vector& module_args, - F f) + const F& f) { - return compute_op(rank<1>{}, x, output, input_shapes, inputs, module_args, std::move(f)); + return compute_op(rank<1>{}, x, output, input_shapes, inputs, module_args, f); } template @@ -331,9 +327,9 @@ argument compute_op(const T& x, const shape& output, const std::vector& inputs, const std::vector& module_args, - F f) + const F& f) { - return compute_op(x, output, std::vector{}, inputs, module_args, std::move(f)); + return compute_op(x, output, std::vector{}, inputs, module_args, f); } template diff --git a/tools/include/operation.hpp b/tools/include/operation.hpp index 0ef5347badd..90d7bc09686 100644 --- a/tools/include/operation.hpp +++ b/tools/include/operation.hpp @@ -290,16 +290,12 @@ auto compute_op(rank<1>, const std::vector& input_shapes, const std::vector& inputs, const std::vector& module_args, - F f) - -> decltype(x.compute(make_compute_output_shape(pack(x, output, input_shapes, inputs)), - inputs, - module_args, - std::move(f))) + const F& f) + -> decltype(x.compute( + make_compute_output_shape(pack(x, output, input_shapes, inputs)), inputs, module_args, f)) { - return x.compute(make_compute_output_shape(pack(x, output, input_shapes, inputs)), - inputs, - module_args, - std::move(f)); + return x.compute( + make_compute_output_shape(pack(x, output, input_shapes, inputs)), inputs, module_args, f); } template @@ -309,7 +305,7 @@ argument compute_op(rank<0>, const std::vector& input_shapes, const std::vector& inputs, const std::vector& module_args, - F) // NOLINT + const F&) { if(module_args.empty()) return compute_op(x, output, input_shapes, inputs); @@ -323,9 +319,9 @@ argument compute_op(const T& x, const std::vector& input_shapes, const std::vector& inputs, const std::vector& module_args, - F f) + const F& f) { - return compute_op(rank<1>{}, x, output, input_shapes, inputs, module_args, std::move(f)); + return compute_op(rank<1>{}, x, output, input_shapes, inputs, module_args, f); } template @@ -333,9 +329,9 @@ argument compute_op(const T& x, const shape& output, const std::vector& inputs, const std::vector& module_args, - F f) + const F& f) { - return compute_op(x, output, std::vector{}, inputs, module_args, std::move(f)); + return compute_op(x, output, std::vector{}, inputs, module_args, f); } template From f6f858bb72e061363aa17c02b93365e72c296caf Mon Sep 17 00:00:00 2001 From: Shiv Date: Thu, 23 Jul 2026 15:50:52 -0700 Subject: [PATCH 32/42] fix lowering --- src/targets/cpu/lowering.cpp | 4 ++-- src/targets/ref/lowering.cpp | 27 +++++++++++++++++++++++++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/targets/cpu/lowering.cpp b/src/targets/cpu/lowering.cpp index b8a2f68e2f2..fdea6202a4b 100644 --- a/src/targets/cpu/lowering.cpp +++ b/src/targets/cpu/lowering.cpp @@ -142,9 +142,9 @@ struct cpu_op } std::string name() const { return "cpu::op"; } shape compute_shape(const std::vector& inputs) const { return op.compute_shape(inputs); } - argument compute(context&, const dyn_output& dyn_out, const std::vector& args) const + argument compute(context&, const shape& output_shape, const std::vector& args) const { - return op.compute(dyn_out.ins_shape, dyn_out.input_shapes, args); + return op.compute(output_shape, args); } value to_value() const { diff --git a/src/targets/ref/lowering.cpp b/src/targets/ref/lowering.cpp index 2a8536fe3b6..cb976709755 100644 --- a/src/targets/ref/lowering.cpp +++ b/src/targets/ref/lowering.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -179,9 +180,9 @@ struct ref_op } std::string name() const { return "ref::op"; } shape compute_shape(const std::vector& inputs) const { return op.compute_shape(inputs); } - argument compute(context&, const dyn_output& dyn_out, const std::vector& args) const + argument compute(context&, const shape& output_shape, const std::vector& args) const { - return op.compute(dyn_out.ins_shape, dyn_out.input_shapes, args); + return op.compute(output_shape, args); } value to_value() const { @@ -202,6 +203,27 @@ struct ref_op }; MIGRAPHX_REGISTER_OP(ref_op) +struct ref_eval_expr +{ + op::eval_expr op; + + template + static auto reflect(Self& self, F f) + { + return migraphx::reflect(self.op, f); + } + + std::string name() const { return "ref::eval_expr"; } + + shape compute_shape(const std::vector& inputs) const { return op.compute_shape(inputs); } + + argument compute(context&, const dyn_output& dyn_out, std::vector args) const + { + return op.compute(dyn_out, std::move(args)); + } +}; +MIGRAPHX_REGISTER_OP(ref_eval_expr) + struct ref_quant_gemm { op::quant_dot op; @@ -385,6 +407,7 @@ struct ref_apply void init() { + apply_map["eval_expr"] = extend_op(); apply_map["quant_dot"] = extend_op(); apply_map["im2col"] = extend_op(); apply_map["logsoftmax"] = extend_op, op::logsoftmax>(); From e4981d312a9b2cabc523a87e396633a4c2d00ea8 Mon Sep 17 00:00:00 2001 From: Paul Fultz II Date: Fri, 24 Jul 2026 09:34:47 -0500 Subject: [PATCH 33/42] Generate API sources in the build directory (#4961) --- .github/workflows/ci.yaml | 2 +- src/api/CMakeLists.txt | 45 +++++++++++++++++++++++-- tools/generate.py | 70 ++++++++++++++++++++++++++++----------- 3 files changed, 95 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9804fb298d0..0654b9f320b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -144,7 +144,7 @@ jobs: -DCLANG_TIDY_CACHE=/data/tidy-cache \ -DGPU_TARGETS=gfx908 \ .. - make -j$(nproc) -k onnx-proto tf-proto tidy + make -j$(nproc) -k onnx-proto tf-proto generate_api tidy # GH actions can not update existing cache, as a workaround clear cache and then save it - name: Clear tidy cache before saving diff --git a/src/api/CMakeLists.txt b/src/api/CMakeLists.txt index 93b85bfd65b..645f8709e6c 100644 --- a/src/api/CMakeLists.txt +++ b/src/api/CMakeLists.txt @@ -22,9 +22,50 @@ # THE SOFTWARE. ##################################################################################### +# The C API (migraphx.h and api.cpp) is generated from the templates in +# tools/api. `make generate` still writes them into the source tree so the +# output can be reviewed, but migraphx_c is built from a freshly generated copy +# placed in the build directory instead of the checked-in src/api copies. +find_package(Python 3 COMPONENTS Interpreter REQUIRED) + +set(MIGRAPHX_API_TOOLS_DIR ${PROJECT_SOURCE_DIR}/tools) +set(MIGRAPHX_API_GEN_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/include) +set(MIGRAPHX_API_GEN_HEADER ${MIGRAPHX_API_GEN_INCLUDE_DIR}/migraphx/migraphx.h) +set(MIGRAPHX_API_GEN_SOURCE ${CMAKE_CURRENT_BINARY_DIR}/api.cpp) + +# generate.py writes migraphx.h under include/migraphx/ and api.cpp at the top +# level of the output dir, mirroring the source-tree layout. The build copy is +# only a compiler input, so it is generated without clang-format; `make +# generate` still formats the reviewable source-tree copy. +add_custom_command( + OUTPUT ${MIGRAPHX_API_GEN_HEADER} ${MIGRAPHX_API_GEN_SOURCE} + COMMAND ${Python_EXECUTABLE} ${MIGRAPHX_API_TOOLS_DIR}/generate.py + --api-only + --api-output-dir ${CMAKE_CURRENT_BINARY_DIR} + WORKING_DIRECTORY ${MIGRAPHX_API_TOOLS_DIR} + DEPENDS + ${MIGRAPHX_API_TOOLS_DIR}/generate.py + ${MIGRAPHX_API_TOOLS_DIR}/api.py + ${MIGRAPHX_API_TOOLS_DIR}/api/migraphx.h + ${MIGRAPHX_API_TOOLS_DIR}/api/api.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/migraphx.py + COMMENT "Generating C API (migraphx.h and api.cpp) in build directory" + VERBATIM +) + +add_custom_target(generate_api DEPENDS ${MIGRAPHX_API_GEN_HEADER} ${MIGRAPHX_API_GEN_SOURCE}) + +# migraphx.hpp is hand-written (not generated); copy it next to the generated +# migraphx.h so the build include directory is a complete public API. +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/include/migraphx/migraphx.hpp + ${MIGRAPHX_API_GEN_INCLUDE_DIR}/migraphx/migraphx.hpp + COPYONLY) + add_library(migraphx_c - api.cpp + ${MIGRAPHX_API_GEN_SOURCE} ) +add_dependencies(migraphx_c generate_api) set_target_properties(migraphx_c PROPERTIES EXPORT_NAME c) migraphx_generate_export_header(migraphx_c DIRECTORY migraphx/api) @@ -44,5 +85,5 @@ target_link_libraries(migraphx_c PUBLIC migraphx_version) rocm_install_targets( TARGETS migraphx_c INCLUDE - ${CMAKE_CURRENT_SOURCE_DIR}/include + ${MIGRAPHX_API_GEN_INCLUDE_DIR} ) diff --git a/tools/generate.py b/tools/generate.py index c776cbc0399..7d9d11105b1 100644 --- a/tools/generate.py +++ b/tools/generate.py @@ -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 @@ -41,40 +41,72 @@ def clang_format(buffer, **kwargs): **kwargs).stdout.decode('utf-8') -def api_generate(input_path: Path, output_path: Path): +def maybe_format(buffer, do_format=True): + return clang_format(buffer) if do_format else buffer + + +def api_generate(input_path: Path, output_path: Path, do_format=True): + output_path.parent.mkdir(parents=True, exist_ok=True) with open(output_path, 'w') as f: - f.write(clang_format(api.run(input_path))) + f.write(maybe_format(api.run(input_path), do_format)) -def te_generate(input_path: Path, output_path: Path): +def te_generate(input_path: Path, output_path: Path, do_format=True): with open(output_path, 'w') as f: - f.write(clang_format(te.run(input_path))) + f.write(maybe_format(te.run(input_path), do_format)) + + +def generate_api(output_dir: Path, do_format=True): + runpy.run_path(str(migraphx_py_path)) + header_path = output_dir / 'include/migraphx/migraphx.h' + source_path = output_dir / 'api.cpp' + api_generate(work_dir / 'api/migraphx.h', header_path, do_format) + print(f'Finished generating header {header_path}') + api_generate(work_dir / 'api/api.cpp', source_path, do_format) + print(f'Finished generating source {source_path}') + + +def generate_all(do_format=True): + files = Path('include').absolute().iterdir() + for f in [f for f in files if f.is_file()]: + te_generate(f, src_dir / f'include/migraphx/{f.name}', do_format) + generate_api(src_dir / 'api', do_format) def main(): parser = argparse.ArgumentParser() parser.add_argument('-f', '--clang-format', type=Path) + parser.add_argument('--api-only', + action='store_true', + help='Only generate the C API files (migraphx.h and ' + 'api.cpp) under the directory given by ' + '--api-output-dir instead of writing into the source ' + 'tree') + parser.add_argument('--api-output-dir', + type=Path, + help='Base output directory for the generated C API ' + 'files: migraphx.h is written under include/migraphx/ ' + 'and api.cpp at the top level') args = parser.parse_args() + if args.api_only and not args.api_output_dir: + parser.error('--api-only requires --api-output-dir') + global clang_format_path if args.clang_format: clang_format_path = args.clang_format - if not clang_format_path.is_file(): - print(f"{clang_format_path}: invalid path or not installed", - file=sys.stderr) - return - try: - files = Path('include').absolute().iterdir() - for f in [f for f in files if f.is_file()]: - te_generate(f, src_dir / f'include/migraphx/{f.name}') - runpy.run_path(str(migraphx_py_path)) - api_generate(work_dir / 'api/migraphx.h', - src_dir / 'api/include/migraphx/migraphx.h') - print('Finished generating header migraphx.h') - api_generate(work_dir / 'api/api.cpp', src_dir / 'api/api.cpp') - print('Finished generating source api.cpp') + if args.api_only: + # These files are only consumed by the compiler, so skip + # clang-format; only `make generate` formats them for review. + generate_api(args.api_output_dir, do_format=False) + else: + if not clang_format_path.is_file(): + print(f"{clang_format_path}: invalid path or not installed", + file=sys.stderr) + return + generate_all() except subprocess.CalledProcessError as ex: if ex.stdout: print(ex.stdout.decode('utf-8')) From a73a3aa0ee135aa632ce6dae2f94d34cf0b4d90e Mon Sep 17 00:00:00 2001 From: shivadbhavsar <105248561+shivadbhavsar@users.noreply.github.com> Date: Fri, 24 Jul 2026 07:37:28 -0700 Subject: [PATCH 34/42] symbolic reshape ops (#4977) --- .clang-tidy | 6 +- src/include/migraphx/dim_like.hpp | 6 + src/include/migraphx/op/flatten.hpp | 36 +- src/include/migraphx/op/layout.hpp | 15 +- src/include/migraphx/op/reshape.hpp | 107 +++-- src/include/migraphx/op/reshape_lazy.hpp | 109 +++--- src/include/migraphx/op/squeeze.hpp | 82 ++-- src/include/migraphx/op/unsqueeze.hpp | 144 ++++--- src/include/migraphx/reshape_dims.hpp | 19 +- src/include/migraphx/shape.hpp | 7 + src/include/migraphx/sym.hpp | 3 + src/reshape_dims.cpp | 194 ++++++--- src/shape.cpp | 2 + src/sym.cpp | 16 + .../gpu/include/migraphx/gpu/contiguous.hpp | 14 +- src/targets/gpu/propagate_reshape_layout.cpp | 27 +- test/gpu/propagate_reshape_layout.cpp | 66 ++++ test/op_shape_test.cpp | 368 ++++++++++++++++++ 18 files changed, 903 insertions(+), 318 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index bd8a554307e..a8e17670c85 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -14,11 +14,11 @@ CheckOptions: - key: misc-const-correctness.AnalyzeValues value: 'false' - key: performance-for-range-copy.AllowedTypes - value: 'shape;operation;iterator;literal;tensor_view;match' + value: 'shape;operation;iterator;literal;tensor_view;match;sym::expr' - key: performance-unnecessary-copy-initialization.AllowedTypes - value: 'shape;operation;iterator;literal;tensor_view;match' + value: 'shape;operation;iterator;literal;tensor_view;match;sym::expr' - key: performance-unnecessary-value-param.AllowedTypes - value: 'shape;operation;iterator;literal;tensor_view;match;__amdgpu_buffer_rsrc_t' + value: 'shape;operation;iterator;literal;tensor_view;match;sym::expr;__amdgpu_buffer_rsrc_t' - key: readability-function-size.BranchThreshold value: '15' - key: readability-function-size.LineThreshold diff --git a/src/include/migraphx/dim_like.hpp b/src/include/migraphx/dim_like.hpp index d3933fe2a40..1a90b6f9ed7 100644 --- a/src/include/migraphx/dim_like.hpp +++ b/src/include/migraphx/dim_like.hpp @@ -53,6 +53,12 @@ struct dim_like_picker // A dim attribute entry that may be either a plain int64_t or a dynamic_dimension. using dim_like = picked_variant; +inline bool is_symbolic(const dim_like& d) +{ + return std::holds_alternative(d) and + std::get(d).is_symbolic(); +} + inline std::ostream& operator<<(std::ostream& os, const dim_like& d) { visit([&](const auto& x) { os << x; }, d); diff --git a/src/include/migraphx/op/flatten.hpp b/src/include/migraphx/op/flatten.hpp index ab23bc60f6b..045d43b5651 100644 --- a/src/include/migraphx/op/flatten.hpp +++ b/src/include/migraphx/op/flatten.hpp @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2015-2025 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 @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -54,11 +55,32 @@ struct flatten } std::string name() const { return "flatten"; } + + // Collapses the dims on each side of axis into a standard 2D shape, for + // static and symbolic input through one path. + shape symbolic_compute_shape(const shape& s) const + { + auto sym_in = s.to_symbolic(); + const auto& dds = sym_in.dyn_dims(); + auto x = std::accumulate(dds.begin(), + dds.begin() + axis, + shape::dynamic_dimension{sym::lit(1)}, + std::multiplies<>{}); + auto y = std::accumulate(dds.begin() + axis, + dds.end(), + shape::dynamic_dimension{sym::lit(1)}, + std::multiplies<>{}); + shape result{s.type(), {x, y}}; + if(not s.symbolic()) + return result.to_static(); + return result; + } + shape normalize_compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1); const auto& s = inputs[0]; - if(s.dynamic()) + if(s.dynamic() and not s.symbolic()) { // Doesn't handle optimals auto min_lens = s.min_lens(); @@ -78,15 +100,7 @@ struct flatten {}}; return {s.type(), {x, y}}; } - else - { - auto&& lens = s.lens(); - auto x = std::accumulate( - lens.begin(), lens.begin() + axis, std::size_t{1}, std::multiplies<>{}); - auto y = std::accumulate( - lens.begin() + axis, lens.end(), std::size_t{1}, std::multiplies<>{}); - return {s.type(), {x, y}}; - } + return symbolic_compute_shape(s); } argument compute(const dyn_output& dyn_out, std::vector args) const { diff --git a/src/include/migraphx/op/layout.hpp b/src/include/migraphx/op/layout.hpp index 16372af6826..557a6abd85d 100644 --- a/src/include/migraphx/op/layout.hpp +++ b/src/include/migraphx/op/layout.hpp @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2015-2025 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 @@ -58,10 +58,15 @@ struct layout : unary shape compute_shape(std::vector inputs) const { - check_shapes{inputs, *this}.has(1).only_dims(permutation.size()); - auto lens = inputs.at(0).lens(); - auto t = inputs.at(0).type(); - return shape::from_permutation(t, lens, permutation); + check_shapes{inputs, *this, true}.has(1).only_dims(permutation.size()); + const auto& input = inputs.at(0); + auto t = input.type(); + // A range-based dynamic shape has no strides, so a permuted layout is not representable. + if(input.symbolic()) + return shape::from_permutation(t, input.dyn_dims(), permutation); + if(input.dynamic()) + MIGRAPHX_THROW("LAYOUT: non-symbolic dynamic shapes are not supported"); + return shape::from_permutation(t, input.lens(), permutation); } auto apply() const diff --git a/src/include/migraphx/op/reshape.hpp b/src/include/migraphx/op/reshape.hpp index 52b298f8f82..454f6cf7307 100644 --- a/src/include/migraphx/op/reshape.hpp +++ b/src/include/migraphx/op/reshape.hpp @@ -24,7 +24,6 @@ #ifndef MIGRAPHX_GUARD_OPERATORS_RESHAPE_HPP #define MIGRAPHX_GUARD_OPERATORS_RESHAPE_HPP -#include #include #include #include @@ -136,82 +135,64 @@ struct reshape return {s0.type(), output_dyn_dims}; } - shape static_compute_shape(std::vector inputs, std::size_t n_neg_dims) const + // Resolves the output dims for static and symbolic input through one path. + shape symbolic_compute_shape(const shape& s0) const { - check_shapes{inputs, *this}.has(1); - auto&& idims = inputs.front().lens(); - 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++) + // Lift static input to symbolic literals so the same dd arithmetic resolves both. + auto sym_in = s0.to_symbolic(); + auto output_dyn_dims = resolve_reshape_dims(sym_in, dims); + const bool has_inferred_dim = + std::find(dims.begin(), dims.end(), dim_like{-1}) != dims.end(); + const bool dims_have_symbolic = std::any_of(dims.begin(), dims.end(), is_symbolic); + + // Preserve the input layout when reshape_dims can derive it; else standard. + std::vector target(output_dyn_dims.size()); + std::transform(output_dyn_dims.begin(), + output_dyn_dims.end(), + target.begin(), + [](const auto& dd) { return dd.sym_expr; }); + auto result = reshape_dims(sym_in, target, {.lazy = false}) + .value_or(shape{s0.type(), output_dyn_dims}); + + // An inferred -1 over a symbolic input is a floor division, so its element + // count is only resolvable at runtime; otherwise throw on a provably + // mismatched count (strict_less either way), letting indeterminate ones pass. + if(not(s0.symbolic() and has_inferred_dim)) { - 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] == dim_like{-1}) - rdims[i] = 1; - } - - if(n_neg_dims > 0) - { - size_t missing_dim = - inputs.front().elements() / - std::accumulate(rdims.begin(), rdims.end(), 1, std::multiplies()); - for(std::size_t i = 0; i < rdims.size(); i++) - { - if(dims[i] == dim_like{-1}) - rdims[i] = missing_dim; - } + auto out_elems = result.sym_elements(); + auto in_elems = s0.sym_elements(); + if(sym::strict_less(out_elems, in_elems).value_or(false) or + sym::strict_less(in_elems, out_elems).value_or(false)) + MIGRAPHX_THROW("Reshape: Wrong number of elements for reshape: reshape has " + + to_string(out_elems) + " elements whereas the input has " + + to_string(in_elems)); } - auto nelements = - std::accumulate(rdims.begin(), rdims.end(), std::size_t{1}, std::multiplies<>{}); - - if(nelements != inputs.front().elements()) - MIGRAPHX_THROW("Reshape: Wrong number of elements for reshape: reshape has " + - std::to_string(nelements) + " elements whereas the input has " + - std::to_string(inputs.front().elements())); - - auto s = reshape_dims(inputs.front(), rdims, {.lazy = false}); - if(not s.has_value()) - return shape{inputs.front().type(), rdims}; - - return s.value(); + // Only a static input with integer dims is fully literal; evaluate it back to + // the concrete layout. Anything symbolic stays symbolic. + if(not s0.symbolic() and not dims_have_symbolic) + return result.to_static(); + return result; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1, 2); + if(inputs.size() == 2) + return inputs.back(); - 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 but given {" + - to_string_range(dims) + "} with " + to_string(n_neg_dims) + " -1 dims"); + validate_reshape_dims(name(), dims); const auto& s0 = inputs.front(); - if(inputs.size() == 1) - { - if(s0.dynamic()) - { - return dyn_1arg_compute_shape(s0); - } - else - { - return static_compute_shape(inputs, n_neg_dims); - } - } - else + if(s0.dynamic() and not s0.symbolic()) { - return inputs.back(); + // A symbolic dim has no range interpretation, so it cannot target a + // range-based input. + if(std::any_of(dims.begin(), dims.end(), is_symbolic)) + MIGRAPHX_THROW("reshape: range-based input only supports int64 dim entries"); + return dyn_1arg_compute_shape(s0); } + return symbolic_compute_shape(s0); } argument compute(const dyn_output& dyn_out, std::vector args) const diff --git a/src/include/migraphx/op/reshape_lazy.hpp b/src/include/migraphx/op/reshape_lazy.hpp index 40be0ac6c1e..23e81e55e8f 100644 --- a/src/include/migraphx/op/reshape_lazy.hpp +++ b/src/include/migraphx/op/reshape_lazy.hpp @@ -101,75 +101,74 @@ struct reshape_lazy return {s0.type(), output_dyn_dims}; } - shape static_compute_shape(std::vector inputs, std::size_t n_neg_dims) const + // Resolves the output layout for static and symbolic input through one path. + shape symbolic_compute_shape(const shape& s0) const { - check_shapes{inputs, *this}.has(1); - auto&& idims = inputs.front().lens(); - 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] == dim_like{0}) - rdims[i] = idims[i]; + // Lift static input to symbolic literals so the same dd arithmetic resolves both. + auto sym_in = s0.to_symbolic(); + auto output_dyn_dims = resolve_reshape_dims(sym_in, dims); + const bool has_inferred_dim = + std::find(dims.begin(), dims.end(), dim_like{-1}) != dims.end(); + + std::vector target(output_dyn_dims.size()); + std::transform(output_dyn_dims.begin(), + output_dyn_dims.end(), + target.begin(), + [](const auto& dd) { return dd.sym_expr; }); + + // Lazy reshape is a no-copy view: when the permutation can't be preserved we + // cannot fall back to a repacked standard layout the way reshape does. + auto s = reshape_dims(sym_in, target, {.lazy = true}); + if(not s.has_value()) + MIGRAPHX_THROW("reshape_lazy on axis that is not packed."); - // since rdims using size_t type, -1 is the max value - // is size_t that cause later compuation incorrect - if(dims[i] == dim_like{-1}) - rdims[i] = 1; + const bool dims_have_symbolic = std::any_of(dims.begin(), dims.end(), is_symbolic); + // Only a static input with integer dims is fully literal; evaluate it back to + // the concrete layout (static results stay byte-identical). Else stays symbolic. + if(not s0.symbolic() and not dims_have_symbolic) + { + auto result = s->to_static(); + if(result.elements() != s0.elements()) + MIGRAPHX_THROW( + "reshape_lazy: Wrong number of elements for reshape_lazy: reshape_lazy has " + + std::to_string(result.elements()) + " elements whereas the input has " + + std::to_string(s0.elements())); + assert(result.bytes() == s0.bytes()); + return result; } - - if(n_neg_dims > 0) + // An inferred -1 over a symbolic input is a floor division, so its element + // count is only resolvable at runtime; otherwise throw on a provably + // mismatched count (strict_less either way), letting indeterminate ones pass. + if(not(s0.symbolic() and has_inferred_dim)) { - size_t missing_dim = - inputs.front().elements() / - std::accumulate(rdims.begin(), rdims.end(), 1, std::multiplies()); - for(std::size_t i = 0; i < rdims.size(); i++) - { - if(dims[i] == dim_like{-1}) - rdims[i] = missing_dim; - } + auto out_elems = s->sym_elements(); + auto in_elems = s0.sym_elements(); + if(sym::strict_less(out_elems, in_elems).value_or(false) or + sym::strict_less(in_elems, out_elems).value_or(false)) + MIGRAPHX_THROW( + "reshape_lazy: Wrong number of elements for reshape_lazy: reshape_lazy has " + + to_string(out_elems) + " elements whereas the input has " + + to_string(in_elems)); } - - auto s = reshape_dims(inputs.front(), rdims, {.lazy = true}); - if(not s.has_value()) - MIGRAPHX_THROW("reshape_lazy on axis that is not packed."); - - if(s->elements() != inputs.front().elements()) - MIGRAPHX_THROW( - "reshape_lazy: Wrong number of elements for reshape_lazy: reshape_lazy has " + - std::to_string(s->elements()) + " elements whereas the input has " + - std::to_string(inputs.front().elements())); - - assert(s->bytes() == inputs.front().bytes()); return *s; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(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 but " - "given {" + - to_string_range(dims) + "} with " + to_string(n_neg_dims) + " -1 dims"); - const auto& s0 = inputs[0]; - if(s0.dynamic()) + + validate_reshape_dims(name(), dims); + + const auto& s0 = inputs.front(); + if(s0.dynamic() and not s0.symbolic()) { + // A symbolic dim has no range interpretation, so it cannot target a + // range-based input. + if(std::any_of(dims.begin(), dims.end(), is_symbolic)) + MIGRAPHX_THROW("reshape_lazy: range-based input only supports int64 dim entries"); return dyn_compute_shape(s0); } - else - { - return static_compute_shape(inputs, n_neg_dims); - } + return symbolic_compute_shape(s0); } argument compute(const dyn_output& dyn_out, std::vector args) const diff --git a/src/include/migraphx/op/squeeze.hpp b/src/include/migraphx/op/squeeze.hpp index 49f1dc2873f..c6020b4826a 100644 --- a/src/include/migraphx/op/squeeze.hpp +++ b/src/include/migraphx/op/squeeze.hpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -53,11 +54,44 @@ struct squeeze } std::string name() const { return "squeeze"; } + + // Drops the size-1 axes, preserving the kept axes' strides, for static and + // symbolic input through one path. + shape symbolic_compute_shape(const shape& s) const + { + auto sym_in = s.to_symbolic(); + const auto& dds = sym_in.dyn_dims(); + const auto& strds = sym_in.dyn_strides(); + auto one = sym::lit(1); + // A dropped axis must be provably 1. + if(std::any_of(axes.begin(), axes.end(), [&](auto axis) { + return not(dds.at(axis).sym_expr == one); + })) + MIGRAPHX_THROW("SQUEEZE: axis dimension should be equal to 1; axes {" + + to_string_range(axes) + "} of input " + to_string(s)); + std::vector new_dds; + std::vector new_strides; + for(auto i : range(dds.size())) + { + const bool drop = axes.empty() ? (dds[i].sym_expr == one) + : (std::find(axes.begin(), axes.end(), i) != axes.end()); + if(not drop) + { + new_dds.push_back(dds[i]); + new_strides.push_back(strds[i]); + } + } + shape result = new_dds.empty() ? shape{s.type()} : shape{s.type(), new_dds, new_strides}; + if(not s.symbolic()) + return result.to_static(); + return result; + } + shape normalize_compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1); auto input_shape = inputs[0]; - if(input_shape.dynamic()) + if(input_shape.dynamic() and not input_shape.symbolic()) { // Allow for any dynamic_dimension that intersects with {1, 1}. // Assuming that the shape at run-time will be compatible. @@ -93,51 +127,7 @@ struct squeeze } return {input_shape.type(), dyn_dims}; } - else - { - auto type = input_shape.type(); - auto old_lens = input_shape.lens(); - const auto& old_strides = input_shape.strides(); - if(std::any_of( - axes.begin(), axes.end(), [&](auto axis) { return old_lens[axis] != 1; })) - { - MIGRAPHX_THROW("SQUEEZE: static axis dimension should be equal to 1; axes {" + - to_string_range(axes) + "} of input dims {" + - to_string_range(old_lens) + "}"); - } - std::vector new_lens; - std::vector new_strides; - if(axes.empty()) - { - for(auto i : range(old_lens.size())) - { - if(old_lens[i] != 1) - { - new_lens.push_back(old_lens[i]); - new_strides.push_back(old_strides[i]); - } - } - } - else - { - for(auto i : range(old_lens.size())) - { - if(std::find(axes.begin(), axes.end(), i) == axes.end()) - { - new_lens.push_back(old_lens[i]); - new_strides.push_back(old_strides[i]); - } - } - } - if(new_lens.empty()) - { - return shape{type}; - } - else - { - return shape{type, new_lens, new_strides}; - } - } + return symbolic_compute_shape(input_shape); } argument compute(const dyn_output& dyn_out, std::vector args) const diff --git a/src/include/migraphx/op/unsqueeze.hpp b/src/include/migraphx/op/unsqueeze.hpp index 9de33a5aea8..4bee53aee26 100644 --- a/src/include/migraphx/op/unsqueeze.hpp +++ b/src/include/migraphx/op/unsqueeze.hpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -63,12 +64,92 @@ struct unsqueeze } std::string name() const { return "unsqueeze"; } + + // Inserts the axes (sized by step), carrying the kept axes' strides through, + // for static and symbolic input through one path. + shape symbolic_compute_shape(const shape& s) const + { + auto sym_in = s.to_symbolic(); + auto type = s.type(); + std::vector old_lens(sym_in.ndim()); + std::transform(sym_in.dyn_dims().begin(), + sym_in.dyn_dims().end(), + old_lens.begin(), + [](const auto& dd) { return dd.sym_expr; }); + const auto& old_strides = sym_in.dyn_strides(); + auto is_scalar = sym_in.scalar(); + auto one = sym::lit(1); + + if(is_scalar and old_lens.size() == 1 and old_lens.front() == one) + { + shape result{type, {shape::dynamic_dimension{one}}}; + return s.symbolic() ? result : result.to_static(); + } + + if(steps.size() > axes.size()) + MIGRAPHX_THROW("UNSQUEEZE: Steps provided with no axis: " + to_string(steps.size()) + + " steps but only " + to_string(axes.size()) + " axes"); + + std::size_t new_size = old_lens.size() + axes.size(); + std::vector new_lens(new_size); + std::vector new_strides(new_size); + std::size_t p = 0; + for(auto i : range(new_size)) + { + auto axis_idx = std::find(axes.begin(), axes.end(), i) - axes.begin(); + if(axis_idx < axes.size()) + { + std::int64_t step = 1; + if(axis_idx < steps.size()) + step = steps[axis_idx]; + if(step == 0) + MIGRAPHX_THROW("UNSQUEEZE: step must be non-zero at axis " + to_string(i)); + if(is_scalar and step != 1) + MIGRAPHX_THROW("UNSQUEEZE: step must be 1 when input is scalar but step is " + + to_string(step) + " at axis " + to_string(i)); + new_lens[i] = sym::lit(step); + if(p < old_strides.size()) + { + // Only a literal dim can be proven indivisible; a symbolic + // dim is trusted and propagated as a tdiv. + auto rem = old_lens[p] % sym::lit(step); + if(rem.name() == "literal" and not(rem == sym::lit(0))) + MIGRAPHX_THROW("UNSQUEEZE: Axis dimension (" + old_lens[p].to_string() + + ") is not divisible by step (" + to_string(step) + + ") at axis " + to_string(i)); + old_lens[p] = old_lens[p] / sym::lit(step); + new_strides[i] = is_scalar ? one : old_strides[p] * old_lens[p]; + } + else + { + if(step != 1) + MIGRAPHX_THROW("UNSQUEEZE: Step must be 1 for extra axes but step is " + + to_string(step) + " at axis " + to_string(i)); + new_strides[i] = one; + } + } + else + { + new_lens[i] = old_lens[p]; + new_strides[i] = old_strides[p++]; + } + } + std::vector new_dds(new_size); + std::transform(new_lens.begin(), new_lens.end(), new_dds.begin(), [](const auto& e) { + return shape::dynamic_dimension{e}; + }); + shape result{type, new_dds, new_strides}; + if(not s.symbolic()) + return result.to_static(); + return result; + } + shape normalize_compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1); const auto& input_shape = inputs[0]; - if(input_shape.dynamic()) + if(input_shape.dynamic() and not input_shape.symbolic()) { if(not steps.empty()) { @@ -91,66 +172,7 @@ struct unsqueeze } return {input_shape.type(), dyn_dims}; } - else - { - auto type = input_shape.type(); - auto old_lens = input_shape.lens(); - const auto& old_strides = input_shape.strides(); - auto is_scalar = input_shape.scalar(); - - if(is_scalar and old_lens.size() == 1 and old_lens.front() == 1) - return shape{type, old_lens}; - - if(steps.size() > axes.size()) - MIGRAPHX_THROW( - "UNSQUEEZE: Steps provided with no axis: " + to_string(steps.size()) + - " steps but only " + to_string(axes.size()) + " axes"); - - std::size_t new_size = old_lens.size() + axes.size(); - - std::vector new_lens(new_size); - std::vector new_strides(new_size); - std::size_t p = 0; - for(auto i : range(new_size)) - { - auto axis_idx = std::find(axes.begin(), axes.end(), i) - axes.begin(); - if(axis_idx < axes.size()) - { - std::int64_t step = 1; - if(axis_idx < steps.size()) - step = steps[axis_idx]; - if(step == 0) - MIGRAPHX_THROW("UNSQUEEZE: step must be non-zero at axis " + to_string(i)); - if(is_scalar and step != 1) - MIGRAPHX_THROW( - "UNSQUEEZE: step must be 1 when input is scalar but step is " + - to_string(step) + " at axis " + to_string(i)); - new_lens[i] = step; - if(p < old_strides.size()) - { - if((old_lens[p] % step) != 0) - MIGRAPHX_THROW("UNSQUEEZE: Axis dimension (" + to_string(old_lens[p]) + - ") is not divisible by step (" + to_string(step) + - ") at axis " + to_string(i)); - old_lens[p] /= step; - new_strides[i] = is_scalar ? 1 : old_strides[p] * old_lens[p]; - } - else - { - if(step != 1) - MIGRAPHX_THROW("UNSQUEEZE: Step must be 1 for extra axes but step is " + - to_string(step) + " at axis " + to_string(i)); - new_strides[i] = 1; - } - } - else - { - new_lens[i] = old_lens[p]; - new_strides[i] = old_strides[p++]; - } - } - return shape{type, new_lens, new_strides}; - } + return symbolic_compute_shape(input_shape); } argument compute(const dyn_output& dyn_out, std::vector args) const { diff --git a/src/include/migraphx/reshape_dims.hpp b/src/include/migraphx/reshape_dims.hpp index 8acf6ac1cf2..dc552942027 100644 --- a/src/include/migraphx/reshape_dims.hpp +++ b/src/include/migraphx/reshape_dims.hpp @@ -27,22 +27,37 @@ #include #include +#include +#include +#include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { -struct shape; - struct reshape_dims_options { bool lazy = false; }; +// nullopt when the layout can't be proven; the caller falls back to standard. +MIGRAPHX_EXPORT optional +reshape_dims(const shape& input, const std::vector& rdims, reshape_dims_options options); + +// Convenience overload for concrete dims; lifts each to a literal sym::expr. MIGRAPHX_EXPORT optional reshape_dims(const shape& input, const std::vector& rdims, reshape_dims_options options); +// Resolve reshape `dims` entries against a symbolic input: 0 copies the input dim, +// -1 is inferred as the leftover element count, literals/symbols are taken as-is. +MIGRAPHX_EXPORT std::vector +resolve_reshape_dims(const shape& sym_in, const std::vector& dims); + +// Throws if `dims` holds a range-based dynamic_dimension or more than one -1 entry. +MIGRAPHX_EXPORT void validate_reshape_dims(const std::string& name, + const std::vector& dims); + } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_RESHAPE_DIMS_HPP diff --git a/src/include/migraphx/shape.hpp b/src/include/migraphx/shape.hpp index 9fca95bec28..3e9b68b8f0d 100644 --- a/src/include/migraphx/shape.hpp +++ b/src/include/migraphx/shape.hpp @@ -317,6 +317,13 @@ struct MIGRAPHX_EXPORT shape */ sym::expr sym_elements() const; + /*! + * Return each dimension as a symbolic expression. Works for any shape kind: + * static dimensions become literals; symbolic dimensions return their + * expression. + */ + std::vector sym_dims() const; + /*! * Return the number of total bytes used for storage of the tensor data; includes subshapes. * For dynamic shape, returns the maximum number of bytes presuming a packed shape. diff --git a/src/include/migraphx/sym.hpp b/src/include/migraphx/sym.hpp index dd487e53949..7e229895dd3 100644 --- a/src/include/migraphx/sym.hpp +++ b/src/include/migraphx/sym.hpp @@ -254,6 +254,9 @@ MIGRAPHX_EXPORT expr var(std::string name, interval constraint, std::set MIGRAPHX_EXPORT expr as_symbol(const expr& e, int max_depth = -1); MIGRAPHX_EXPORT bool same_symbol(const expr& a, const expr& b); +// Whether dividend is evenly divisible by divisor (integral operands only). +MIGRAPHX_EXPORT bool is_divisible(const expr& dividend, const expr& divisor); + MIGRAPHX_EXPORT expr arg(expr x); template {})> diff --git a/src/reshape_dims.cpp b/src/reshape_dims.cpp index 588486f419b..41d24103878 100644 --- a/src/reshape_dims.cpp +++ b/src/reshape_dims.cpp @@ -25,25 +25,34 @@ #include #include #include +#include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template -static auto compute_end_dim(Iterator start, Iterator last, std::size_t dim) +static Iterator compute_end_dim(Iterator start, Iterator last, const sym::expr& dim) { - std::size_t x = 1; - auto it = std::find_if(start, last, [&](auto i) { + auto x = sym::lit(std::int64_t{1}); + bool indeterminate = false; + auto it = std::find_if(start, last, [&](const auto& i) { x *= i; - return x >= dim; + auto x_lt = sym::strict_less(x, dim); + if(not x_lt.has_value()) + { + indeterminate = true; + return true; + } + return not *x_lt; }); - if(x != dim) + if(indeterminate or not sym::same_symbol(x, dim)) return start; return it; } -template -static OptionalPair try_merge_pairs(OptionalPair p2, OptionalPair p1) +static optional> +try_merge_pairs(optional> p2, + optional> p1) { if(not p1.has_value()) return nullopt; @@ -54,30 +63,32 @@ static OptionalPair try_merge_pairs(OptionalPair p2, OptionalPair p1) auto stride1 = p1->second; auto stride2 = p2->second; auto elements = dim1 * dim2; + auto zero = sym::lit(std::int64_t{0}); // Transposed - if(stride2 > stride1) + auto order = sym::strict_less(stride1, stride2); + if(not order.has_value() or *order) return nullopt; // Broadcasted check to avoid division by zero - if(stride2 == 0) + if(sym::same_symbol(stride2, zero)) { - if(stride1 == 0) - return {{elements, 0}}; + if(sym::same_symbol(stride1, zero)) + return {{elements, zero}}; return nullopt; } - if(stride1 % stride2 != 0) + if(not sym::is_divisible(stride1, stride2)) return nullopt; auto space = (stride1 * dim1 + stride2 * dim2 - stride1) / stride2; // Nonpacked - if(space != elements) + if(not sym::same_symbol(space, elements)) return nullopt; return {{elements, stride2}}; } template -static optional merge_strides(DimIterator dim_start, - DimIterator dim_last, - StrideIterator stride_start, - StrideIterator stride_last) +static optional merge_strides(DimIterator dim_start, + DimIterator dim_last, + StrideIterator stride_start, + StrideIterator stride_last) { if(dim_start == dim_last) return nullopt; @@ -107,64 +118,139 @@ static auto can_strides_merge(DimIterator dim_start, return merge_strides(dim_start, dim_last, stride_start, stride_last).has_value(); } +std::vector resolve_reshape_dims(const shape& sym_in, + const std::vector& dims) +{ + const auto& input_dds = sym_in.dyn_dims(); + std::vector output_dyn_dims(dims.size()); + shape::dynamic_dimension known_elements{sym::lit(1)}; + std::size_t neg_dim_num = dims.size(); + for(std::size_t i = 0; i < dims.size(); ++i) + { + const auto& d = dims[i]; + // Defer -1; it needs the product of every other axis. + if(d == dim_like{-1}) + { + neg_dim_num = i; + continue; + } + if(d == dim_like{0}) + output_dyn_dims[i] = input_dds.at(i); + else if(is_symbolic(d)) + output_dyn_dims[i] = std::get(d); + else + output_dyn_dims[i] = shape::dynamic_dimension{sym::lit(std::get(d))}; + known_elements = known_elements * output_dyn_dims[i]; + } + if(neg_dim_num < dims.size()) + { + auto total_elements = std::accumulate(input_dds.begin(), + input_dds.end(), + shape::dynamic_dimension{sym::lit(1)}, + std::multiplies<>{}); + output_dyn_dims[neg_dim_num] = total_elements / known_elements; + } + return output_dyn_dims; +} + +void validate_reshape_dims(const std::string& name, const std::vector& dims) +{ + if(std::any_of(dims.begin(), dims.end(), [](const dim_like& d) { + return std::holds_alternative(d) and not is_symbolic(d); + })) + MIGRAPHX_THROW(name + ": dim entries must be int64 or symbolic"); + + auto n_neg_dims = std::count(dims.begin(), dims.end(), dim_like{-1}); + if(n_neg_dims > 1) + MIGRAPHX_THROW(name + ": Dimensions for " + name + " can only have one -1 dim but given {" + + to_string_range(dims) + "} with " + to_string(n_neg_dims) + " -1 dims"); +} + optional reshape_dims(const shape& input, const std::vector& rdims, reshape_dims_options options) { + std::vector sym_rdims(rdims.size()); + std::transform( + rdims.begin(), rdims.end(), sym_rdims.begin(), [](std::size_t d) { return sym::lit(d); }); + return reshape_dims(input, sym_rdims, options); +} + +optional +reshape_dims(const shape& input, const std::vector& rdims, reshape_dims_options options) +{ + const std::vector rdds(rdims.begin(), rdims.end()); + if(input.standard()) - return shape{input.type(), rdims}; + return shape{input.type(), rdds}; // Broadcasts have ambiguous permutations (multiple axes share stride 0), so // for non-lazy reshape fall back to a standard layout. Sliced (non-packed) // inputs still propagate the permutation via the algorithm + with_lens below. if(not options.lazy and input.broadcasted()) - return shape{input.type(), rdims}; + return shape{input.type(), rdds}; - const auto& idims = input.lens(); - const auto& istrides = input.strides(); + std::vector idims(input.dyn_dims().size()); + std::transform(input.dyn_dims().begin(), + input.dyn_dims().end(), + idims.begin(), + [](const auto& dd) { return dd.sym_expr; }); + const auto& istrides = input.dyn_strides(); - std::vector rstrides; + std::vector rstrides; std::size_t i = 0; std::size_t r = 0; while(i < idims.size() and r < rdims.size()) { auto idim = idims[i]; auto rdim = rdims[r]; - if(rdim == idim) + if(sym::same_symbol(rdim, idim)) { rstrides.push_back(istrides[i]); } - // squeeze - else if(rdim > idim) + else { - auto start = idims.begin() + i; - auto it = compute_end_dim(start, idims.end(), rdim); - if(it == start) - return nullopt; - auto n = it - start; - assert((i + n) <= istrides.size()); - if(options.lazy and - not can_strides_merge( - start, it + 1, istrides.begin() + i, istrides.begin() + i + n + 1)) + // equality handled above; an unprovable ordering bails. + auto rdim_gt = sym::strict_less(idim, rdim); + auto rdim_lt = sym::strict_less(rdim, idim); + if(not rdim_gt.has_value() or not rdim_lt.has_value()) return nullopt; - i += n; - rstrides.push_back(istrides[i]); - } - // unsqueeze - else // if(rdim < idim) - { - auto start = rdims.begin() + r; - auto it = compute_end_dim(start, rdims.end(), idim); - if(it == start) + // squeeze + if(*rdim_gt) + { + auto start = idims.begin() + i; + auto it = compute_end_dim(start, idims.end(), rdim); + if(it == start) + return nullopt; + auto n = it - start; + assert((i + n) <= istrides.size()); + if(options.lazy and + not can_strides_merge( + start, it + 1, istrides.begin() + i, istrides.begin() + i + n + 1)) + return nullopt; + i += n; + rstrides.push_back(istrides[i]); + } + // unsqueeze + else if(*rdim_lt) + { + auto start = rdims.begin() + r; + auto it = compute_end_dim(start, rdims.end(), idim); + if(it == start) + return nullopt; + auto n = it - start; + assert((r + n) <= rdims.size()); + auto stride = istrides[i] * idim; + std::for_each(start, it + 1, [&](auto dim) { + stride /= dim; + rstrides.push_back(stride); + }); + r += n; + } + else + { return nullopt; - auto n = it - start; - assert((r + n) <= rdims.size()); - auto stride = istrides[i] * idim; - std::for_each(start, it + 1, [&](auto dim) { - stride /= dim; - rstrides.push_back(stride); - }); - r += n; + } } i++; r++; @@ -176,7 +262,7 @@ optional reshape_dims(const shape& input, auto stride = rstrides.back(); for(auto d : range(rdims.begin() + rstrides.size(), rdims.end())) { - if(d != 1) + if(d != sym::lit(std::int64_t{1})) return nullopt; rstrides.push_back(stride); } @@ -185,11 +271,11 @@ optional reshape_dims(const shape& input, if(rdims.size() != rstrides.size()) return nullopt; - auto result = shape{input.type(), rdims, rstrides}; + auto result = shape{input.type(), rdds, rstrides}; if(options.lazy or result.packed()) return result; // TODO: Add as_packed to shape class - return result.with_lens(result.type(), result.lens()); + return result.with_lens(result.type(), result.dyn_dims()); } } // namespace MIGRAPHX_INLINE_NS diff --git a/src/shape.cpp b/src/shape.cpp index b3449556909..3b9dfab890b 100644 --- a/src/shape.cpp +++ b/src/shape.cpp @@ -662,6 +662,8 @@ std::size_t shape::elements() const { return impl->elements(); } sym::expr shape::sym_elements() const { return impl->sym_elements(); } +std::vector shape::sym_dims() const { return impl->sym_dims(); } + std::size_t shape::bytes() const { if(this->sub_shapes().empty()) diff --git a/src/sym.cpp b/src/sym.cpp index 3f84e41d393..bf299ad3456 100644 --- a/src/sym.cpp +++ b/src/sym.cpp @@ -1623,6 +1623,22 @@ bool same_symbol(const expr& a, const expr& b) }); } +[[maybe_unused]] static bool has_float_literal(const expr& e) +{ + if(e.empty()) + return false; + if(const auto* n = std::get_if(&get_node(e))) + return std::holds_alternative(n->val); + return std::any_of(e.children().begin(), e.children().end(), has_float_literal); +} + +bool is_divisible(const expr& dividend, const expr& divisor) +{ + // Float literals make the /-reconstruction rounding-dependent. + assert(not has_float_literal(dividend) and not has_float_literal(divisor)); + return same_symbol((dividend / divisor) * divisor, dividend); +} + // Number of levels in e: a leaf (literal/variable) is depth 1, empty is 0. static int expr_depth(const expr& e) { diff --git a/src/targets/gpu/include/migraphx/gpu/contiguous.hpp b/src/targets/gpu/include/migraphx/gpu/contiguous.hpp index f689ea9cfdd..b169babd09f 100644 --- a/src/targets/gpu/include/migraphx/gpu/contiguous.hpp +++ b/src/targets/gpu/include/migraphx/gpu/contiguous.hpp @@ -41,13 +41,13 @@ struct miopen_contiguous : unary_device shape compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this, true}.has(2); - if(inputs.at(0).dynamic()) - { - return inputs.at(0); - } - auto lens = inputs.at(0).lens(); - auto t = inputs.at(0).type(); - return {t, lens}; + const auto& input = inputs.at(0); + // Packing yields a standard layout; a range-only dynamic shape has no strides to pack. + if(input.symbolic()) + return {input.type(), input.dyn_dims()}; + if(input.dynamic()) + return input; + return {input.type(), input.lens()}; } }; diff --git a/src/targets/gpu/propagate_reshape_layout.cpp b/src/targets/gpu/propagate_reshape_layout.cpp index 4764ec3afed..0c2de9699f7 100644 --- a/src/targets/gpu/propagate_reshape_layout.cpp +++ b/src/targets/gpu/propagate_reshape_layout.cpp @@ -35,7 +35,7 @@ inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace { -struct find_reshape_lazy_contiguous +struct find_reshape_lazy_contiguous : match::supports_dynamic_shapes { // eliminate_contiguous only leaves a standardizing gpu::contiguous in front of a // reshape_lazy when it could not alias the input directly; that is the only case where a @@ -52,19 +52,24 @@ struct find_reshape_lazy_contiguous auto cont = r.instructions["contiguous"]; auto input = cont->inputs().front(); const auto& s = input->get_shape(); - // A standard input carries no permutation to propagate. - if(s.dynamic() or s.standard()) + // A standard input has no permutation to propagate; a range-based dynamic input has + // no symbolic dims for reshape_dims/find_permutation to work with. + if(s.standard() or (s.dynamic() and not s.symbolic())) return; - const auto& rdims = rl->get_shape().lens(); - // The permuted, packed output the original reshape would have produced from the real - // (non-standard) input. reshape_dims does not verify the element count, so guard it here - // the same way reshape_lazy::compute_shape does. - auto permuted = reshape_dims(s, rdims, {.lazy = false}); - if(not permuted or permuted->standard() or permuted->elements() != s.elements()) + auto sym_in = s.to_symbolic(); + + auto permuted = reshape_dims(sym_in, rl->get_shape().sym_dims(), {.lazy = false}); + if(not permuted or permuted->standard()) + return; + // reshape_dims does not check the element count; bail when it provably differs, + // matching reshape_lazy::compute_shape (an indeterminate count is allowed through). + auto out_elems = permuted->sym_elements(); + auto in_elems = sym_in.sym_elements(); + if(sym::strict_less(out_elems, in_elems).value_or(false) or + sym::strict_less(in_elems, out_elems).value_or(false)) return; - // The packed layout that reshape_lazy can alias straight to that output. - auto relayout = reshape_dims(*permuted, s.lens(), {.lazy = true}); + auto relayout = reshape_dims(*permuted, s.sym_dims(), {.lazy = true}); if(not relayout) return; diff --git a/test/gpu/propagate_reshape_layout.cpp b/test/gpu/propagate_reshape_layout.cpp index a7ed506492d..fa238c20495 100644 --- a/test/gpu/propagate_reshape_layout.cpp +++ b/test/gpu/propagate_reshape_layout.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include "make_precompile_op.hpp" @@ -92,6 +93,71 @@ TEST_CASE(propagate_permutation) EXPECT(rl1->get_shape().lens() == std::vector{1, 16, 256, 256}); } +// Symbolic analog of propagate_permutation: the leading batch dimension is a symbol that +// threads through the reshape/transpose/reshape_lazy chain. The pass must propagate the +// permutation just as in the static case, producing the same layout/allocate structure. +TEST_CASE(propagate_permutation_symbolic) +{ + using dd = migraphx::shape::dynamic_dimension; + using migraphx::sym::lit; + + auto n = migraphx::sym::var("N", {1, 8}); + migraphx::shape in{migraphx::shape::float_type, + {dd{n}, dd{lit(1)}, dd{lit(1024)}, dd{lit(1024)}}}; + + migraphx::module m1; + { + auto x = m1.add_parameter("x", in); + // 0 copies the symbolic batch dim; the spatial dims split into blocks. + auto r = + m1.add_instruction(migraphx::make_op("reshape", {{"dims", {0, 256, 4, 256, 4}}}), x); + auto t = m1.add_instruction( + migraphx::make_op("transpose", {{"permutation", {0, 2, 4, 1, 3}}}), r); + // post-eliminate_contiguous state: a standardizing gpu::contiguous feeds reshape_lazy + auto alloc = m1.add_instruction( + migraphx::make_op("allocate", + {{"shape", + migraphx::to_value(migraphx::shape{migraphx::shape::float_type, + t->get_shape().dyn_dims()})}})); + auto c = m1.add_instruction(migraphx::make_op("gpu::contiguous"), t, alloc); + auto rl = + m1.add_instruction(migraphx::make_op("reshape_lazy", {{"dims", {0, 16, 256, 256}}}), c); + m1.add_return({rl}); + } + run_pass(m1); + + migraphx::module m2; + { + auto x = m2.add_parameter("x", in); + auto r = + m2.add_instruction(migraphx::make_op("reshape", {{"dims", {0, 256, 4, 256, 4}}}), x); + auto t = m2.add_instruction( + migraphx::make_op("transpose", {{"permutation", {0, 2, 4, 1, 3}}}), r); + // layout repacks the transpose into the packed memory order reshape_lazy can alias + auto l_shape = migraphx::shape::from_permutation( + migraphx::shape::float_type, + {dd{n}, dd{lit(4)}, dd{lit(4)}, dd{lit(256)}, dd{lit(256)}}, + {0, 3, 4, 1, 2}); + auto alloc = m2.add_instruction( + migraphx::make_op("allocate", {{"shape", migraphx::to_value(l_shape)}})); + auto layout = m2.add_instruction( + make_precompile_op(migraphx::make_op("layout", {{"permutation", {0, 3, 4, 1, 2}}})), + t, + alloc); + auto rl = m2.add_instruction( + migraphx::make_op("reshape_lazy", {{"dims", {0, 16, 256, 256}}}), layout); + m2.add_return({rl}); + } + + EXPECT(m1 == m2); + // reshape_lazy now produces the permuted (NHWC-like) symbolic output rather than a standard one + auto rl1 = std::prev(m1.end())->inputs().front(); + EXPECT(rl1->name() == "reshape_lazy"); + EXPECT(not rl1->get_shape().standard()); + EXPECT(rl1->get_shape().sym_dims() == + std::vector{n, lit(16), lit(256), lit(256)}); +} + // When the reshape collapses the non-standard input back to a standard layout there is no // permutation to propagate, so the pass must leave the graph unchanged. TEST_CASE(no_permutation_noop) diff --git a/test/op_shape_test.cpp b/test/op_shape_test.cpp index 12369334843..ae60d28d96c 100644 --- a/test/op_shape_test.cpp +++ b/test/op_shape_test.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -1615,6 +1616,41 @@ TEST_CASE(flatten_dyn_axis4) input); } +TEST_CASE(flatten_sym_axis1) +{ + auto n = var("N", {1, 8}); + migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(4)}, dd{lit(6)}}}; + migraphx::shape output{migraphx::shape::float_type, {dd{n}, dd{lit(24)}}}; + expect_shape(output, migraphx::make_op("flatten", {{"axis", 1}}), input); +} + +TEST_CASE(flatten_sym_multi) +{ + auto n = var("N", {1, 8}); + auto m = var("M", {1, 8}); + auto k = var("K", {1, 8}); + migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{m}, dd{k}}}; + migraphx::shape output{migraphx::shape::float_type, {dd{n}, dd{m * k}}}; + expect_shape(output, migraphx::make_op("flatten", {{"axis", 1}}), input); +} + +TEST_CASE(flatten_sym_axis0) +{ + auto n = var("N", {1, 8}); + auto m = var("M", {1, 8}); + migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{m}}}; + migraphx::shape output{migraphx::shape::float_type, {dd{lit(1)}, dd{n * m}}}; + expect_shape(output, migraphx::make_op("flatten", {{"axis", 0}}), input); +} + +TEST_CASE(flatten_sym_negative_axis) +{ + auto n = var("N", {1, 8}); + migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(4)}, dd{lit(6)}}}; + migraphx::shape output{migraphx::shape::float_type, {dd{lit(4) * n}, dd{lit(6)}}}; + expect_shape(output, migraphx::make_op("flatten", {{"axis", -1}}), input); +} + TEST_CASE(fill_static_int) { migraphx::shape default_value{migraphx::shape::int64_type, {1}, {0}}; @@ -4332,6 +4368,194 @@ TEST_CASE(reshape_dyn_1in_multiple_non_fixed1) expect_shape(output, migraphx::make_op("reshape", {{"dims", new_shape}}), input); } +TEST_CASE(reshape_sym_zero_marker) +{ + auto n = var("N", {1, 8}); + migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(6)}}}; + migraphx::shape output{migraphx::shape::float_type, {dd{n}, dd{lit(2)}, dd{lit(3)}}}; + expect_shape(output, migraphx::make_op("reshape", {{"dims", {0, 2, 3}}}), input); +} + +TEST_CASE(reshape_sym_negative_1_int_missing) +{ + auto n = var("N", {1, 8}); + migraphx::shape input{migraphx::shape::float_type, {dd{lit(2)}, dd{n}, dd{lit(3)}}}; + migraphx::shape output{migraphx::shape::float_type, {dd{lit(2)}, dd{n}, dd{lit(3)}}}; + expect_shape(output, migraphx::make_op("reshape", {{"dims", {2, 0, -1}}}), input); +} + +TEST_CASE(reshape_sym_minus1_first) +{ + auto n = var("N", {1, 8}); + migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(6)}}}; + migraphx::shape output{migraphx::shape::float_type, {dd{lit(3) * n}, dd{lit(2)}}}; + expect_shape(output, migraphx::make_op("reshape", {{"dims", {-1, 2}}}), input); +} + +TEST_CASE(reshape_sym_minus1_distributes_over_sum) +{ + auto n = var("N", {1, 8}); + migraphx::shape input{migraphx::shape::float_type, {dd{n + lit(1)}, dd{lit(6)}}}; + migraphx::shape output{migraphx::shape::float_type, {dd{lit(3) * n + lit(3)}, dd{lit(2)}}}; + expect_shape(output, migraphx::make_op("reshape", {{"dims", {-1, 2}}}), input); +} + +TEST_CASE(reshape_sym_dims_smaller_than_input) +{ + auto n = var("N", {1, 8}); + auto m = var("M", {1, 8}); + migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(2)}, dd{m}}}; + migraphx::shape output{migraphx::shape::float_type, {dd{n}, dd{lit(2) * m}}}; + expect_shape(output, migraphx::make_op("reshape", {{"dims", {0, -1}}}), input); +} + +TEST_CASE(reshape_sym_broadcast_input_standard) +{ + auto n = var("N", {1, 8}); + migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(6)}}, {lit(0), lit(1)}}; + migraphx::shape output{migraphx::shape::float_type, {dd{n}, dd{lit(2)}, dd{lit(3)}}}; + expect_shape(output, migraphx::make_op("reshape", {{"dims", {0, 2, 3}}}), input); + EXPECT(output.standard()); +} + +TEST_CASE(reshape_sym_transposed_literal_unsqueeze) +{ + auto n = var("N", {1, 8}); + migraphx::shape input{migraphx::shape::float_type, {dd{lit(6)}, dd{n}}, {lit(1), lit(6)}}; + std::vector dims = {2, 3, dd{n}}; + migraphx::shape output{ + migraphx::shape::float_type, {dd{lit(2)}, dd{lit(3)}, dd{n}}, {lit(3), lit(1), lit(6)}}; + expect_shape(output, migraphx::make_op("reshape", {{"dims", migraphx::to_value(dims)}}), input); + EXPECT(not output.standard()); +} + +TEST_CASE(reshape_sym_transposed_symbolic_strides) +{ + auto n = var("N", {1, 8}); + migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(6)}}, {lit(1), n}}; + migraphx::shape output{ + migraphx::shape::float_type, {dd{n}, dd{lit(2)}, dd{lit(3)}}, {lit(1), lit(3) * n, n}}; + expect_shape(output, migraphx::make_op("reshape", {{"dims", {0, 2, 3}}}), input); + EXPECT(not output.standard()); +} + +TEST_CASE(reshape_sym_nonstandard_indeterminate_falls_back) +{ + auto n = var("N", {1, 8}); + migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(6)}}, {lit(1), n}}; + std::vector dims = {6, dd{n}}; + migraphx::shape output{migraphx::shape::float_type, {dd{lit(6)}, dd{n}}}; + expect_shape(output, migraphx::make_op("reshape", {{"dims", migraphx::to_value(dims)}}), input); + EXPECT(output.standard()); +} + +TEST_CASE(reshape_sym_nonpacked_unsqueeze) +{ + auto n = var("N", {1, 8}); + migraphx::shape input{ + migraphx::shape::float_type, {dd{n}, dd{lit(4)}, dd{lit(16)}}, {lit(128), lit(32), lit(2)}}; + std::vector dims = {0, 4, 2, 8}; + migraphx::shape output{migraphx::shape::float_type, + {dd{n}, dd{lit(4)}, dd{lit(2)}, dd{lit(8)}}}; + expect_shape(output, migraphx::make_op("reshape", {{"dims", migraphx::to_value(dims)}}), input); + EXPECT(output.standard()); +} + +TEST_CASE(reshape_sym_target_dim_negative_1) +{ + auto n = var("N", {1, 8}); + migraphx::shape input = {migraphx::shape::float_type, {6}}; + std::vector dims = {dd{n}, -1}; + migraphx::shape output{migraphx::shape::float_type, {dd{n}, dd{lit(6) / n}}}; + expect_shape(output, migraphx::make_op("reshape", {{"dims", migraphx::to_value(dims)}}), input); +} + +TEST_CASE(reshape_sym_minus1_non_exact_div) +{ + auto n = var("N", {1, 8}); + auto m = var("M", {1, 8}); + migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{m}, dd{lit(6)}}}; + migraphx::shape output{migraphx::shape::float_type, + {dd{(lit(2) * m * n) / lit(3)}, dd{lit(3)}, dd{lit(3)}}}; + expect_shape(output, migraphx::make_op("reshape", {{"dims", {-1, 3, 3}}}), input); +} + +TEST_CASE(reshape_sym_target_middle_axis) +{ + auto n = var("N", {1, 8}); + migraphx::shape input = {migraphx::shape::float_type, {24}}; + std::vector dims = {2, dd{n}, dd{lit(12) / n}}; + migraphx::shape output{migraphx::shape::float_type, {dd{lit(2)}, dd{n}, dd{lit(12) / n}}}; + expect_shape(output, migraphx::make_op("reshape", {{"dims", migraphx::to_value(dims)}}), input); +} + +TEST_CASE(reshape_sym_target_minus1_cancels_to_literal) +{ + auto n = var("N", {1, 8}); + migraphx::shape input = {migraphx::shape::float_type, {dd{lit(12)}, dd{n}}}; + std::vector dims = {-1, dd{lit(2) * n}}; + migraphx::shape output{migraphx::shape::float_type, {dd{lit(6)}, dd{lit(2) * n}}}; + expect_shape(output, migraphx::make_op("reshape", {{"dims", migraphx::to_value(dims)}}), input); +} + +TEST_CASE(reshape_sym_target_minus1_symbolic_missing) +{ + auto n = var("N", {1, 8}); + auto m = var("M", {1, 8}); + migraphx::shape input = {migraphx::shape::float_type, {dd{n}, dd{m}}}; + std::vector dims = {dd{lit(2) * n}, -1}; + migraphx::shape output{migraphx::shape::float_type, {dd{lit(2) * n}, dd{m / lit(2)}}}; + expect_shape(output, migraphx::make_op("reshape", {{"dims", migraphx::to_value(dims)}}), input); +} + +TEST_CASE(reshape_sym_target_collapse_axes) +{ + auto n = var("N", {1, 8}); + auto m = var("M", {1, 8}); + auto k = var("K", {1, 8}); + migraphx::shape input = {migraphx::shape::float_type, {dd{n}, dd{m}, dd{k}}}; + std::vector dims = {dd{n * m}, dd{k}}; + migraphx::shape output{migraphx::shape::float_type, {dd{n * m}, dd{k}}}; + expect_shape(output, migraphx::make_op("reshape", {{"dims", migraphx::to_value(dims)}}), input); +} + +TEST_CASE(reshape_sym_element_mismatch_throws) +{ + auto n = var("N", {1, 8}); + migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(6)}}}; + throws_shape(migraphx::make_op("reshape", {{"dims", {0, 2, 2}}}), input); +} + +TEST_CASE(reshape_sym_symbolic_dim_mismatch_throws) +{ + auto n = var("N", {1, 8}); + migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(6)}}}; + std::vector dims = {dd{n}, dd{lit(7)}}; + throws_shape(migraphx::make_op("reshape", {{"dims", migraphx::to_value(dims)}}), input); +} + +TEST_CASE(reshape_range_input_sym_dim_throws) +{ + auto n = var("N", {1, 8}); + migraphx::shape input{migraphx::shape::float_type, {{1, 8}, {6, 6}}}; + std::vector dims = {dd{n}, dd{lit(2)}, dd{lit(3)}}; + throws_shape(migraphx::make_op("reshape", {{"dims", migraphx::to_value(dims)}}), input); +} + +TEST_CASE(reshape_range_dim_like_throws) +{ + migraphx::shape input{migraphx::shape::float_type, {6}}; + std::vector dims = {dd{1, 4}, dd{1, 6}}; + throws_shape(migraphx::make_op("reshape", {{"dims", migraphx::to_value(dims)}}), input); +} + +TEST_CASE(reshape_sym_multiple_neg_throws) +{ + auto n = var("N", {1, 8}); + migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(6)}}}; + throws_shape(migraphx::make_op("reshape", {{"dims", {1, -1, -1}}}), input); +} + TEST_CASE(reshape_lazy_shape) { migraphx::shape input{migraphx::shape::float_type, {24, 1, 1, 1}}; @@ -4464,6 +4688,72 @@ TEST_CASE(reshape_lazy_nonpacked_squeeze2) throws_shape(migraphx::make_op("reshape_lazy", {{"dims", {64}}}), input); } +TEST_CASE(reshape_lazy_sym_nonpacked_squeeze) +{ + auto n = var("N", {2, 8}); + migraphx::shape input{migraphx::shape::float_type, {dd{lit(4)}, dd{n}}, {lit(2) * n, lit(2)}}; + std::vector dims = {dd{lit(4) * n}}; + migraphx::shape output{migraphx::shape::float_type, {dd{lit(4) * n}}, {lit(2)}}; + expect_shape( + output, migraphx::make_op("reshape_lazy", {{"dims", migraphx::to_value(dims)}}), input); +} + +TEST_CASE(reshape_lazy_sym_nonpacked_unsqueeze) +{ + auto n = var("N", {2, 8}); + migraphx::shape input{migraphx::shape::float_type, {dd{lit(4)}, dd{n}}, {lit(2) * n, lit(2)}}; + std::vector dims = {2, 2, dd{n}}; + migraphx::shape output{migraphx::shape::float_type, + {dd{lit(2)}, dd{lit(2)}, dd{n}}, + {lit(4) * n, lit(2) * n, lit(2)}}; + expect_shape( + output, migraphx::make_op("reshape_lazy", {{"dims", migraphx::to_value(dims)}}), input); +} + +TEST_CASE(reshape_lazy_sym_transposed_squeeze_throws) +{ + auto n = var("N", {2, 8}); + migraphx::shape input{migraphx::shape::float_type, {dd{lit(4)}, dd{n}}, {lit(1), lit(4)}}; + std::vector dims = {dd{lit(4) * n}}; + throws_shape(migraphx::make_op("reshape_lazy", {{"dims", migraphx::to_value(dims)}}), input); +} + +TEST_CASE(reshape_lazy_sym_broadcast_squeeze) +{ + auto n = var("N", {2, 8}); + migraphx::shape input{migraphx::shape::float_type, + {dd{lit(2)}, dd{n}, dd{lit(16)}, dd{lit(1280)}}, + {lit(0), lit(0), lit(0), lit(1)}}; + std::vector dims = {2, dd{lit(16) * n}, 1280}; + migraphx::shape output{migraphx::shape::float_type, + {dd{lit(2)}, dd{lit(16) * n}, dd{lit(1280)}}, + {lit(0), lit(0), lit(1)}}; + expect_shape( + output, migraphx::make_op("reshape_lazy", {{"dims", migraphx::to_value(dims)}}), input); +} + +TEST_CASE(reshape_lazy_sym_broadcast_unsqueeze) +{ + auto n = var("N", {2, 8}); + migraphx::shape input{migraphx::shape::float_type, + {dd{lit(2)}, dd{lit(16) * n}, dd{lit(1280)}}, + {lit(0), lit(0), lit(1)}}; + std::vector dims = {2, dd{n}, 16, 1280}; + migraphx::shape output{migraphx::shape::float_type, + {dd{lit(2)}, dd{n}, dd{lit(16)}, dd{lit(1280)}}, + {lit(0), lit(0), lit(0), lit(1)}}; + expect_shape( + output, migraphx::make_op("reshape_lazy", {{"dims", migraphx::to_value(dims)}}), input); +} + +TEST_CASE(reshape_lazy_sym_element_mismatch_throws) +{ + auto n = var("N", {2, 8}); + migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(6)}}}; + std::vector dims = {dd{n}, 2, 2}; + throws_shape(migraphx::make_op("reshape_lazy", {{"dims", migraphx::to_value(dims)}}), input); +} + TEST_CASE(reshape_lazy_broadcast_unsqueeze1) { migraphx::shape input{migraphx::shape::float_type, {2, 256, 1280}, {0, 0, 1}}; @@ -6272,6 +6562,41 @@ TEST_CASE(test_squeeze_wrong_axis) throws_shape(migraphx::make_op("squeeze", {{"axes", {0}}}), s1); } +TEST_CASE(test_squeeze_sym) +{ + auto n = var("N", {1, 8}); + auto m = var("M", {1, 8}); + migraphx::shape s1{migraphx::shape::float_type, {dd{n}, dd{lit(1)}, dd{m}}}; + migraphx::shape s2{migraphx::shape::float_type, {dd{n}, dd{m}}}; + expect_shape(s2, migraphx::make_op("squeeze", {{"axes", {1}}}), s1); +} + +TEST_CASE(test_squeeze_sym_transpose) +{ + auto n = var("N", {1, 8}); + migraphx::shape s1{ + migraphx::shape::float_type, {dd{n}, dd{lit(4)}, dd{lit(1)}}, {lit(4), lit(1), lit(4)}}; + migraphx::shape s2{migraphx::shape::float_type, {dd{n}, dd{lit(4)}}, {lit(4), lit(1)}}; + expect_shape(s2, migraphx::make_op("squeeze", {{"axes", {2}}}), s1); +} + +TEST_CASE(test_squeeze_sym_empty_axes) +{ + auto n = var("N", {1, 8}); + auto m = var("M", {1, 8}); + migraphx::shape s1{migraphx::shape::float_type, {dd{n}, dd{lit(1)}, dd{m}, dd{lit(1)}}}; + migraphx::shape s2{migraphx::shape::float_type, {dd{n}, dd{m}}}; + expect_shape(s2, migraphx::make_op("squeeze"), s1); +} + +TEST_CASE(test_squeeze_sym_symbolic_axis_throws) +{ + auto n = var("N", {1, 8}); + auto m = var("M", {1, 8}); + migraphx::shape s1{migraphx::shape::float_type, {dd{n}, dd{lit(1)}, dd{m}}}; + throws_shape(migraphx::make_op("squeeze", {{"axes", {0}}}), s1); +} + TEST_CASE(test_unique_axis_invalid) { migraphx::shape x_shape{migraphx::shape::float_type, {10, 4, 3}}; @@ -6492,6 +6817,49 @@ TEST_CASE(test_unsqueeze_multiple_axes_step) expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {2, 4, 5}}, {"steps", {2}}}), s1); } +TEST_CASE(test_unsqueeze_sym) +{ + auto n = var("N", {1, 8}); + migraphx::shape s1{migraphx::shape::float_type, {dd{n}, dd{lit(5)}, dd{lit(3)}}}; + migraphx::shape s2{migraphx::shape::float_type, {dd{n}, dd{lit(5)}, dd{lit(1)}, dd{lit(3)}}}; + expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {2}}}), s1); +} + +TEST_CASE(test_unsqueeze_sym_symbolic_stride) +{ + auto n = var("N", {1, 8}); + migraphx::shape s1{migraphx::shape::float_type, {dd{lit(6)}, dd{n}}, {lit(1), lit(6)}}; + migraphx::shape s2{ + migraphx::shape::float_type, {dd{lit(1)}, dd{lit(6)}, dd{n}}, {lit(6), lit(1), lit(6)}}; + expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {0}}}), s1); + EXPECT(not s2.standard()); +} + +TEST_CASE(test_unsqueeze_sym_step) +{ + auto n = var("N", {1, 8}); + migraphx::shape s1{migraphx::shape::float_type, {dd{n}, dd{lit(5)}, dd{lit(12)}}}; + migraphx::shape s2{migraphx::shape::float_type, {dd{n}, dd{lit(5)}, dd{lit(2)}, dd{lit(6)}}}; + expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {2}}, {"steps", {2}}}), s1); +} + +TEST_CASE(test_unsqueeze_sym_step_symbolic_divisor) +{ + auto n = var("N", {1, 8}); + auto m = var("M", {1, 8}); + migraphx::shape s1{migraphx::shape::float_type, {dd{n}, dd{m}}}; + migraphx::shape s2{ + migraphx::shape::float_type, {dd{n}, dd{lit(2)}, dd{m / lit(2)}}, {m, m / lit(2), lit(1)}}; + expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {1}}, {"steps", {2}}}), s1); +} + +TEST_CASE(test_unsqueeze_sym_step_non_divisible_throws) +{ + auto n = var("N", {1, 8}); + migraphx::shape s1{migraphx::shape::float_type, {dd{n}, dd{lit(5)}, dd{lit(3)}}}; + throws_shape(migraphx::make_op("unsqueeze", {{"axes", {2}}, {"steps", {2}}}), s1); +} + TEST_CASE(transpose_shape) { migraphx::shape input{migraphx::shape::float_type, {2, 2}}; From ae1b9c37f876467d773d57ee3ba4cfe967d122a8 Mon Sep 17 00:00:00 2001 From: shivadbhavsar <105248561+shivadbhavsar@users.noreply.github.com> Date: Fri, 24 Jul 2026 07:52:07 -0700 Subject: [PATCH 35/42] Update torchkit to support full converter refactor (#5060) Macro builders were recently added to better manage composed ops when parsing from outside libraries. This extends the torch kit with a number of missing ops required to fully migrate torch_migraphx to use this builder framework. --- src/CMakeLists.txt | 2 +- src/op/builder/floor_div.cpp | 49 +++++++ src/op/builder/gather_elements.cpp | 95 ++++++++++++++ src/op/builder/glu.cpp | 67 ++++++++++ src/op/builder/group_norm.cpp | 80 +++++++++++ src/op/builder/hardsigmoid.cpp | 67 ++++++++++ .../include/migraphx/op/builder/kit.hpp | 12 +- src/op/builder/instance_norm.cpp | 83 ++++++++++++ src/op/builder/layer_norm.cpp | 59 +++++++++ src/op/builder/normalize.cpp | 69 ++++++++++ src/op/builder/selu.cpp | 71 ++++++++++ src/op/builder/softsign.cpp | 53 ++++++++ src/op/builder/torch/as_strided.cpp | 81 ++++++++++++ src/op/builder/torch/conv_transpose.cpp | 103 +++++++++++++++ src/op/builder/torch/index_copy.cpp | 72 ++++++++++ src/op/builder/torch/linear.cpp | 68 ++++++++++ src/op/builder/torch/lstm.cpp | 84 ++++++++++++ src/op/builder/torch/nan_to_num.cpp | 87 ++++++++++++ src/op/builder/torch/scatter_reduce.cpp | 106 +++++++++++++++ src/op/builder/torch/slice_scatter.cpp | 76 +++++++++++ src/op/builder/torch/std.cpp | 85 ++++++++++++ src/op/builder/torch_kit.cpp | 76 ++++------- src/op/builder/vector_norm.cpp | 92 +++++++++++++ src/py/migraphx_py.cpp | 8 ++ test/op/CMakeLists.txt | 4 +- test/op/builder/gather_elements_test.cpp | 59 +++++++++ test/op/builder/torch/as_strided_test.cpp | 64 +++++++++ test/op/builder/torch/batchnorm_test.cpp | 42 ++++++ test/op/builder/torch/clip_test.cpp | 95 ++++++++++++++ .../common_ops_test.cpp} | 124 +++--------------- test/op/builder/torch/conv_transpose_test.cpp | 89 +++++++++++++ test/op/builder/torch/convolution_test.cpp | 46 +++++++ test/op/builder/torch/dot_test.cpp | 38 ++++++ test/op/builder/torch/floor_div_test.cpp | 39 ++++++ test/op/builder/torch/gelu_test.cpp | 37 ++++++ test/op/builder/torch/glu_test.cpp | 43 ++++++ test/op/builder/torch/group_norm_test.cpp | 72 ++++++++++ test/op/builder/torch/hardsigmoid_test.cpp | 43 ++++++ test/op/builder/torch/index_copy_test.cpp | 46 +++++++ test/op/builder/torch/instance_norm_test.cpp | 67 ++++++++++ test/op/builder/torch/layer_norm_test.cpp | 55 ++++++++ test/op/builder/torch/linear_test.cpp | 53 ++++++++ test/op/builder/torch/lstm_test.cpp | 113 ++++++++++++++++ test/op/builder/torch/nan_to_num_test.cpp | 57 ++++++++ test/op/builder/torch/scatter_reduce_test.cpp | 87 ++++++++++++ test/op/builder/torch/selu_test.cpp | 48 +++++++ test/op/builder/torch/slice_scatter_test.cpp | 51 +++++++ test/op/builder/torch/softsign_test.cpp | 40 ++++++ test/op/builder/torch/std_test.cpp | 47 +++++++ test/op/builder/torch/vector_norm_test.cpp | 89 +++++++++++++ 50 files changed, 3037 insertions(+), 156 deletions(-) create mode 100644 src/op/builder/floor_div.cpp create mode 100644 src/op/builder/gather_elements.cpp create mode 100644 src/op/builder/glu.cpp create mode 100644 src/op/builder/group_norm.cpp create mode 100644 src/op/builder/hardsigmoid.cpp create mode 100644 src/op/builder/instance_norm.cpp create mode 100644 src/op/builder/layer_norm.cpp create mode 100644 src/op/builder/normalize.cpp create mode 100644 src/op/builder/selu.cpp create mode 100644 src/op/builder/softsign.cpp create mode 100644 src/op/builder/torch/as_strided.cpp create mode 100644 src/op/builder/torch/conv_transpose.cpp create mode 100644 src/op/builder/torch/index_copy.cpp create mode 100644 src/op/builder/torch/linear.cpp create mode 100644 src/op/builder/torch/lstm.cpp create mode 100644 src/op/builder/torch/nan_to_num.cpp create mode 100644 src/op/builder/torch/scatter_reduce.cpp create mode 100644 src/op/builder/torch/slice_scatter.cpp create mode 100644 src/op/builder/torch/std.cpp create mode 100644 src/op/builder/vector_norm.cpp create mode 100644 test/op/builder/gather_elements_test.cpp create mode 100644 test/op/builder/torch/as_strided_test.cpp create mode 100644 test/op/builder/torch/batchnorm_test.cpp create mode 100644 test/op/builder/torch/clip_test.cpp rename test/op/builder/{torch_kit_test.cpp => torch/common_ops_test.cpp} (61%) create mode 100644 test/op/builder/torch/conv_transpose_test.cpp create mode 100644 test/op/builder/torch/convolution_test.cpp create mode 100644 test/op/builder/torch/dot_test.cpp create mode 100644 test/op/builder/torch/floor_div_test.cpp create mode 100644 test/op/builder/torch/gelu_test.cpp create mode 100644 test/op/builder/torch/glu_test.cpp create mode 100644 test/op/builder/torch/group_norm_test.cpp create mode 100644 test/op/builder/torch/hardsigmoid_test.cpp create mode 100644 test/op/builder/torch/index_copy_test.cpp create mode 100644 test/op/builder/torch/instance_norm_test.cpp create mode 100644 test/op/builder/torch/layer_norm_test.cpp create mode 100644 test/op/builder/torch/linear_test.cpp create mode 100644 test/op/builder/torch/lstm_test.cpp create mode 100644 test/op/builder/torch/nan_to_num_test.cpp create mode 100644 test/op/builder/torch/scatter_reduce_test.cpp create mode 100644 test/op/builder/torch/selu_test.cpp create mode 100644 test/op/builder/torch/slice_scatter_test.cpp create mode 100644 test/op/builder/torch/softsign_test.cpp create mode 100644 test/op/builder/torch/std_test.cpp create mode 100644 test/op/builder/torch/vector_norm_test.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e1bc74e6a1f..4b18a01803d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -155,7 +155,7 @@ add_library(migraphx enable_static_init(migraphx) -file(GLOB BUILDER_SRCS CONFIGURE_DEPENDS op/builder/*.cpp) +file(GLOB BUILDER_SRCS CONFIGURE_DEPENDS op/builder/*.cpp op/builder/torch/*.cpp) target_sources(migraphx PRIVATE ${BUILDER_SRCS}) if(WIN32) diff --git a/src/op/builder/floor_div.cpp b/src/op/builder/floor_div.cpp new file mode 100644 index 00000000000..f5afde2a97d --- /dev/null +++ b/src/op/builder/floor_div.cpp @@ -0,0 +1,49 @@ +/* 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 + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { +namespace op { +namespace builder { + +// floor_div has no native op: floor(a / b) over common operands. +struct floor_div : op_builder +{ + std::vector + insert(module& m, instruction_ref ins, const std::vector& args) const + { + auto quotient = insert_common_op(m, ins, make_op("div"), args); + return {m.insert_instruction(ins, make_op("floor"), quotient)}; + } +}; + +} // namespace builder +} // namespace op +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx diff --git a/src/op/builder/gather_elements.cpp b/src/op/builder/gather_elements.cpp new file mode 100644 index 00000000000..44088266122 --- /dev/null +++ b/src/op/builder/gather_elements.cpp @@ -0,0 +1,95 @@ +/* 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 + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { +namespace op { +namespace builder { + +// gather_elements has no native op: flatten the input and gather element-wise using per-element +// offsets built from the input strides. +struct gather_elements : op_builder +{ + int64_t axis = 0; + + template + static auto reflect(Self& self, F f) + { + return pack(f(self.axis, "axis")); + } + + std::vector + insert(module& m, instruction_ref ins, const std::vector& args) const + { + auto arg_data = m.insert_instruction(ins, make_op("contiguous"), args[0]); + auto arg_ind = m.insert_instruction(ins, make_op("contiguous"), args[1]); + + auto data_s = arg_data->get_shape(); + auto ind_s = arg_ind->get_shape(); + if(data_s.lens().size() != ind_s.lens().size()) + MIGRAPHX_THROW("gather_elements: input data and index must have the same rank"); + + int n_rank = data_s.lens().size(); + int tuned_axis = tune_axis(n_rank, axis, "gather_elements"); + auto axis_stride = data_s.strides()[tuned_axis]; + + int64_t data_elem_num = data_s.elements(); + arg_data = + m.insert_instruction(ins, make_op("reshape", {{"dims", {data_elem_num}}}), arg_data); + + // flat offset of every index position, and its coordinate along the gathered axis + std::size_t elem_num = ind_s.elements(); + std::vector data_indices(elem_num); + std::vector axis_indices(elem_num); + for(std::size_t i = 0; i < elem_num; ++i) + { + auto multi = ind_s.multi(i); + data_indices[i] = data_s.index(multi); + axis_indices[i] = multi[tuned_axis]; + } + + auto l_shape_idx = m.add_literal(literal(ind_s, data_indices.begin(), data_indices.end())); + auto l_dim_idx = m.add_literal(literal(ind_s, axis_indices.begin(), axis_indices.end())); + auto l_stride = m.add_literal(literal{{ind_s.type(), {1}}, {axis_stride}}); + l_stride = m.insert_instruction( + ins, make_op("multibroadcast", {{"out_lens", ind_s.lens()}}), l_stride); + + auto dim_diff = m.insert_instruction(ins, make_op("sub"), arg_ind, l_dim_idx); + auto delta = m.insert_instruction(ins, make_op("mul"), dim_diff, l_stride); + auto ind = m.insert_instruction(ins, make_op("add"), l_shape_idx, delta); + return {m.insert_instruction(ins, make_op("gather", {{"axis", 0}}), arg_data, ind)}; + } +}; + +} // namespace builder +} // namespace op +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx diff --git a/src/op/builder/glu.cpp b/src/op/builder/glu.cpp new file mode 100644 index 00000000000..68fd4a16b64 --- /dev/null +++ b/src/op/builder/glu.cpp @@ -0,0 +1,67 @@ +/* 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 + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { +namespace op { +namespace builder { + +// glu has no native op: split in half along `axis`, gate the first half by sigmoid(second). +struct glu : op_builder +{ + int64_t axis = -1; + + template + static auto reflect(Self& self, F f) + { + return pack(f(self.axis, "axis")); + } + + std::vector + insert(module& m, instruction_ref ins, const std::vector& args) const + { + auto x = args[0]; + auto lens = x->get_shape().lens(); + auto ax = tune_axis(lens.size(), axis, "glu"); + int64_t len = lens[ax]; + + auto first = m.insert_instruction( + ins, make_op("slice", {{"axes", {ax}}, {"starts", {0}}, {"ends", {len / 2}}}), x); + auto second = m.insert_instruction( + ins, make_op("slice", {{"axes", {ax}}, {"starts", {len / 2}}, {"ends", {len}}}), x); + auto gate = m.insert_instruction(ins, make_op("sigmoid"), second); + return {m.insert_instruction(ins, make_op("mul"), first, gate)}; + } +}; + +} // namespace builder +} // namespace op +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx diff --git a/src/op/builder/group_norm.cpp b/src/op/builder/group_norm.cpp new file mode 100644 index 00000000000..4049ee3e2fe --- /dev/null +++ b/src/op/builder/group_norm.cpp @@ -0,0 +1,80 @@ +/* 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 + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { +namespace op { +namespace builder { + +// group_norm has no native op: normalize each channel group, then the affine. +struct group_norm : op_builder +{ + float epsilon = 1e-5f; + int64_t num_groups = 1; + + template + static auto reflect(Self& self, F f) + { + return pack(f(self.epsilon, "epsilon"), f(self.num_groups, "num_groups")); + } + + std::vector + insert(module& m, instruction_ref ins, const std::vector& args) const + { + auto x = args[0]; + auto lens = x->get_shape().lens(); + if(lens.size() <= 2 or lens[1] % num_groups != 0) + MIGRAPHX_THROW("group_norm op_builder: input rank must be > 2 and num_groups must " + "divide the channel dim"); + + std::vector grouped_dims = {static_cast(lens[0]), num_groups, -1}; + auto grouped = m.insert_instruction(ins, make_op("reshape", {{"dims", grouped_dims}}), x); + auto norm = op::builder::insert( + "normalize", m, ins, {grouped}, {{"axes", {-1}}, {"epsilon", epsilon}}) + .front(); + + std::vector out_dims(lens.begin(), lens.end()); + auto norm_r = m.insert_instruction(ins, make_op("reshape", {{"dims", out_dims}}), norm); + + // unsqueeze the per-channel scale/bias to broadcast over the spatial dims + std::vector unsqueeze_axes(lens.size() - 2); + std::iota(unsqueeze_axes.begin(), unsqueeze_axes.end(), 1); + auto scale = + m.insert_instruction(ins, make_op("unsqueeze", {{"axes", unsqueeze_axes}}), args[1]); + auto bias = + m.insert_instruction(ins, make_op("unsqueeze", {{"axes", unsqueeze_axes}}), args[2]); + auto scaled = insert_common_op(m, ins, "mul", norm_r, scale); + return {insert_common_op(m, ins, "add", scaled, bias)}; + } +}; + +} // namespace builder +} // namespace op +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx diff --git a/src/op/builder/hardsigmoid.cpp b/src/op/builder/hardsigmoid.cpp new file mode 100644 index 00000000000..f396f06829e --- /dev/null +++ b/src/op/builder/hardsigmoid.cpp @@ -0,0 +1,67 @@ +/* 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 + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { +namespace op { +namespace builder { + +// hardsigmoid has no native op: clip(alpha * x + beta, 0, 1). +struct hardsigmoid : op_builder +{ + float alpha = 1.0f / 6.0f; + float beta = 0.5f; + + template + static auto reflect(Self& self, F f) + { + return pack(f(self.alpha, "alpha"), f(self.beta, "beta")); + } + + std::vector + insert(module& m, instruction_ref ins, const std::vector& args) const + { + auto x = args[0]; + auto type = x->get_shape().type(); + + auto alpha_lit = m.add_literal({type, {alpha}}); + auto beta_lit = m.add_literal({type, {beta}}); + auto lo = m.add_literal({type, {0.0f}}); + auto hi = m.add_literal({type, {1.0f}}); + + auto scaled = insert_common_op(m, ins, "mul", alpha_lit, x); + auto shifted = insert_common_op(m, ins, "add", beta_lit, scaled); + return {insert_common_op(m, ins, "clip", shifted, lo, hi)}; + } +}; + +} // namespace builder +} // namespace op +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx diff --git a/src/op/builder/include/migraphx/op/builder/kit.hpp b/src/op/builder/include/migraphx/op/builder/kit.hpp index 21f362f8b52..ef93f8ae482 100644 --- a/src/op/builder/include/migraphx/op/builder/kit.hpp +++ b/src/op/builder/include/migraphx/op/builder/kit.hpp @@ -66,7 +66,17 @@ struct kit : auto_register op_builder_if from_builder(const std::string& op_builder) const { - return get_op_builder_if(op_builder); + // Resolve lazily: the target builder may register after this kit's apply() + // runs during static initialization. + return op_builder_if{ + [=](module& m, + instruction_ref ins, + const std::vector& args, + const std::vector& module_args, + const value& options) { + return get_op_builder_if(op_builder).bld_func(m, ins, args, module_args, options); + }, + [=] { return get_op_builder_if(op_builder).to_val_func(); }}; } op_builder_if with_common(const op_builder_if& obi, common_options coptions = {}) const diff --git a/src/op/builder/instance_norm.cpp b/src/op/builder/instance_norm.cpp new file mode 100644 index 00000000000..774b851f5be --- /dev/null +++ b/src/op/builder/instance_norm.cpp @@ -0,0 +1,83 @@ +/* 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 + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { +namespace op { +namespace builder { + +// instance_norm has no native op: normalize from input stats, then the affine. +struct instance_norm : op_builder +{ + float epsilon = 1e-5f; + + template + static auto reflect(Self& self, F f) + { + return pack(f(self.epsilon, "epsilon")); + } + + std::vector + insert(module& m, instruction_ref ins, const std::vector& args) const + { + auto x = args[0]; + int64_t rank = x->get_shape().ndim(); + if(rank < 2) + MIGRAPHX_THROW("instance_norm op_builder: input rank must be at least 2"); + + // reduce over the batch and spatial dims, keeping the channel dim + std::vector axes = {0}; + for(int64_t i = 2; i < rank; ++i) + axes.push_back(i); + + auto norm = + op::builder::insert("normalize", m, ins, {x}, {{"axes", axes}, {"epsilon", epsilon}}) + .front(); + + // unsqueeze the per-channel scale/bias to broadcast over the spatial dims + auto scale = args[1]; + auto bias = args[2]; + if(rank > 2) + { + std::vector unsqueeze_axes(rank - 2); + std::iota(unsqueeze_axes.begin(), unsqueeze_axes.end(), 1); + scale = + m.insert_instruction(ins, make_op("unsqueeze", {{"axes", unsqueeze_axes}}), scale); + bias = + m.insert_instruction(ins, make_op("unsqueeze", {{"axes", unsqueeze_axes}}), bias); + } + auto scaled = insert_common_op(m, ins, "mul", norm, scale); + return {insert_common_op(m, ins, "add", scaled, bias)}; + } +}; + +} // namespace builder +} // namespace op +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx diff --git a/src/op/builder/layer_norm.cpp b/src/op/builder/layer_norm.cpp new file mode 100644 index 00000000000..c74045fdf30 --- /dev/null +++ b/src/op/builder/layer_norm.cpp @@ -0,0 +1,59 @@ +/* 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 { +namespace op { +namespace builder { + +// layer_norm has no native op: normalize over axes, then the affine scale/bias. +struct layer_norm : op_builder +{ + float epsilon = 1e-5f; + std::vector axes = {-1}; + + template + static auto reflect(Self& self, F f) + { + return pack(f(self.epsilon, "epsilon"), f(self.axes, "axes")); + } + + std::vector + insert(module& m, instruction_ref ins, const std::vector& args) const + { + auto norm = op::builder::insert( + "normalize", m, ins, {args[0]}, {{"axes", axes}, {"epsilon", epsilon}}) + .front(); + auto scaled = insert_common_op(m, ins, "mul", norm, args[1]); + return {insert_common_op(m, ins, "add", scaled, args[2])}; + } +}; + +} // namespace builder +} // namespace op +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx diff --git a/src/op/builder/normalize.cpp b/src/op/builder/normalize.cpp new file mode 100644 index 00000000000..802e4e8c6c7 --- /dev/null +++ b/src/op/builder/normalize.cpp @@ -0,0 +1,69 @@ +/* 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 + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { +namespace op { +namespace builder { + +// (x - mean) * rsqrt(var + epsilon) reduced over `axes`, with a biased variance. Shared by the +// normalization builders (layer_norm, group_norm, instance_norm). +struct normalize : op_builder +{ + std::vector axes = {}; + float epsilon = 1e-5f; + + template + static auto reflect(Self& self, F f) + { + return pack(f(self.axes, "axes"), f(self.epsilon, "epsilon")); + } + + std::vector + insert(module& m, instruction_ref ins, const std::vector& args) const + { + auto x = args[0]; + auto x_type = x->get_shape().type(); + auto mean = m.insert_instruction(ins, make_op("reduce_mean", {{"axes", axes}}), x); + auto x_sub = insert_common_op(m, ins, "sub", x, mean); + auto sqdiff = insert_common_op(m, ins, "sqdiff", x, mean); + auto variance = m.insert_instruction(ins, make_op("reduce_mean", {{"axes", axes}}), sqdiff); + auto eps = m.add_literal(literal{shape{x_type}, {epsilon}}); + auto var_eps = insert_common_op(m, ins, "add", variance, eps); + auto rsqrt = m.insert_instruction(ins, make_op("rsqrt"), var_eps); + return {insert_common_op(m, ins, "mul", x_sub, rsqrt)}; + } +}; + +} // namespace builder +} // namespace op +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx diff --git a/src/op/builder/selu.cpp b/src/op/builder/selu.cpp new file mode 100644 index 00000000000..f92103d1c66 --- /dev/null +++ b/src/op/builder/selu.cpp @@ -0,0 +1,71 @@ +/* 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 + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { +namespace op { +namespace builder { + +// selu has no native op: gamma * (max(0, x) + min(0, alpha * (exp(x) - 1))). +struct selu : op_builder +{ + float alpha = 1.6732632423543772f; + float gamma = 1.0507009873554805f; + + template + static auto reflect(Self& self, F f) + { + return pack(f(self.alpha, "alpha"), f(self.gamma, "gamma")); + } + + std::vector + insert(module& m, instruction_ref ins, const std::vector& args) const + { + auto x = args[0]; + auto type = x->get_shape().type(); + + auto zero = m.add_literal({type, {0.0f}}); + auto one = m.add_literal({type, {1.0f}}); + auto alpha_lit = m.add_literal({type, {alpha}}); + auto gamma_lit = m.add_literal({type, {gamma}}); + + auto linear = insert_common_op(m, ins, "max", zero, x); + auto exp_x = m.insert_instruction(ins, make_op("exp"), x); + auto exp_sub = insert_common_op(m, ins, "sub", exp_x, one); + auto exp_mul = insert_common_op(m, ins, "mul", alpha_lit, exp_sub); + auto exp_part = insert_common_op(m, ins, "min", zero, exp_mul); + auto sum = insert_common_op(m, ins, "add", linear, exp_part); + return {insert_common_op(m, ins, "mul", gamma_lit, sum)}; + } +}; + +} // namespace builder +} // namespace op +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx diff --git a/src/op/builder/softsign.cpp b/src/op/builder/softsign.cpp new file mode 100644 index 00000000000..866ec723bd2 --- /dev/null +++ b/src/op/builder/softsign.cpp @@ -0,0 +1,53 @@ +/* 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 + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { +namespace op { +namespace builder { + +// softsign has no native op: x / (1 + |x|). +struct softsign : op_builder +{ + std::vector + insert(module& m, instruction_ref ins, const std::vector& args) const + { + auto x = args[0]; + auto type = x->get_shape().type(); + auto one = m.add_literal({type, {1.0f}}); + auto abs_x = m.insert_instruction(ins, make_op("abs"), x); + auto denom = insert_common_op(m, ins, "add", abs_x, one); + return {insert_common_op(m, ins, "div", x, denom)}; + } +}; + +} // namespace builder +} // namespace op +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx diff --git a/src/op/builder/torch/as_strided.cpp b/src/op/builder/torch/as_strided.cpp new file mode 100644 index 00000000000..dc131724205 --- /dev/null +++ b/src/op/builder/torch/as_strided.cpp @@ -0,0 +1,81 @@ +/* + * 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 + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { +namespace op { +namespace builder { + +// as_strided has no native op: gather each element from its strided storage offset. +struct torch_as_strided : op_builder +{ + std::vector size; + std::vector stride; + int64_t storage_offset = 0; + + template + static auto reflect(Self& self, F f) + { + return pack(f(self.size, "size"), + f(self.stride, "stride"), + f(self.storage_offset, "storage_offset")); + } + + static std::vector names() { return {"tm::as_strided"}; } + + std::vector + insert(module& m, instruction_ref ins, const std::vector& args) const + { + if(size.size() != stride.size()) + MIGRAPHX_THROW("as_strided: size and stride must have the same length"); + shape strided{shape::int64_type, + std::vector(size.begin(), size.end()), + std::vector(stride.begin(), stride.end())}; + std::vector data(strided.elements()); + for(std::size_t i = 0; i < data.size(); ++i) + data[i] = storage_offset + strided.index(i); + auto indices = m.add_literal( + literal{shape{shape::int64_type, {data.size()}}, data.begin(), data.end()}); + + auto flat_inp = m.insert_instruction(ins, make_op("contiguous"), args[0]); + flat_inp = m.insert_instruction(ins, make_op("reshape", {{"dims", {-1}}}), flat_inp); + auto gathered = + m.insert_instruction(ins, make_op("gather", {{"axis", 0}}), flat_inp, indices); + return {m.insert_instruction(ins, make_op("reshape", {{"dims", size}}), gathered)}; + } +}; + +} // namespace builder +} // namespace op +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx diff --git a/src/op/builder/torch/conv_transpose.cpp b/src/op/builder/torch/conv_transpose.cpp new file mode 100644 index 00000000000..f7bbb92fa0f --- /dev/null +++ b/src/op/builder/torch/conv_transpose.cpp @@ -0,0 +1,103 @@ +/* + * 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 + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { +namespace op { +namespace builder { + +// conv_transpose has no native op: convolution_backwards + output_padding crop + channel bias. +struct torch_conv_transpose : op_builder +{ + std::vector stride; + std::vector padding; + std::vector dilation; + std::vector output_padding; + int group = 1; + + template + static auto reflect(Self& self, F f) + { + return pack(f(self.stride, "stride"), + f(self.padding, "padding"), + f(self.dilation, "dilation"), + f(self.output_padding, "output_padding"), + f(self.group, "group")); + } + + static std::vector names() { return {"tm::conv_transpose"}; } + + std::vector + insert(module& m, instruction_ref ins, const std::vector& args) const + { + // output_padding cannot be expressed by the op: run it unpadded, then crop + bool crop = std::any_of( + output_padding.begin(), output_padding.end(), [](std::size_t o) { return o != 0; }); + auto pad = crop ? std::vector(padding.size(), 0) : padding; + auto out = m.insert_instruction( + ins, + make_op( + "convolution_backwards", + {{"stride", stride}, {"padding", pad}, {"dilation", dilation}, {"group", group}}), + args[0], + args[1]); + + if(crop) + { + auto spatial = out->get_shape().lens(); + std::vector axes(output_padding.size()); + std::vector starts(output_padding.size()); + std::vector ends(output_padding.size()); + for(std::size_t i = 0; i < output_padding.size(); ++i) + { + axes[i] = static_cast(i + 2); + starts[i] = static_cast(padding[i]); + ends[i] = static_cast(spatial[i + 2] - padding[i] + output_padding[i]); + } + out = m.insert_instruction( + ins, make_op("slice", {{"axes", axes}, {"starts", starts}, {"ends", ends}}), out); + } + + if(args.size() < 3) + return {out}; + + auto out_lens = out->get_shape().lens(); + auto bias = m.insert_instruction( + ins, make_op("broadcast", {{"axis", 1}, {"out_lens", out_lens}}), args[2]); + return {m.insert_instruction(ins, make_op("add"), out, bias)}; + } +}; + +} // namespace builder +} // namespace op +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx diff --git a/src/op/builder/torch/index_copy.cpp b/src/op/builder/torch/index_copy.cpp new file mode 100644 index 00000000000..c050212561b --- /dev/null +++ b/src/op/builder/torch/index_copy.cpp @@ -0,0 +1,72 @@ +/* + * 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 + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { +namespace op { +namespace builder { + +// index_copy has no native op: scatter src into the rows of `dim` listed in the 1-D index. +struct torch_index_copy : op_builder +{ + int64_t dim = 0; + + template + static auto reflect(Self& self, F f) + { + return pack(f(self.dim, "dim")); + } + + static std::vector names() { return {"tm::index_copy"}; } + + std::vector + insert(module& m, instruction_ref ins, const std::vector& args) const + { + auto inp = args[0]; + auto idx = args[1]; + auto src = args[2]; + auto src_lens = src->get_shape().lens(); + auto axis = tune_axis(src_lens.size(), dim, "index_copy"); + + std::vector rsp(src_lens.size(), 1); + rsp[axis] = idx->get_shape().lens().at(0); + auto scatter_idx = m.insert_instruction(ins, make_op("reshape", {{"dims", rsp}}), idx); + scatter_idx = m.insert_instruction( + ins, make_op("multibroadcast", {{"out_lens", src_lens}}), scatter_idx); + return {m.insert_instruction( + ins, make_op("scatter_none", {{"axis", axis}}), {inp, scatter_idx, src})}; + } +}; + +} // namespace builder +} // namespace op +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx diff --git a/src/op/builder/torch/linear.cpp b/src/op/builder/torch/linear.cpp new file mode 100644 index 00000000000..1a66a7d81a1 --- /dev/null +++ b/src/op/builder/torch/linear.cpp @@ -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. + */ + +#include +#include +#include +#include +#include +#include +#include + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { +namespace op { +namespace builder { + +// linear reuses the gemm builder; ND inputs are flattened to rank 2. +struct torch_linear : op_builder +{ + static std::vector names() { return {"tm::linear"}; } + + std::vector + insert(module& m, instruction_ref ins, const std::vector& args) const + { + const value gemm_opts{{"transB", true}}; + auto lens = args[0]->get_shape().lens(); + if(lens.size() == 2) + return op::builder::insert("gemm", m, ins, args, gemm_opts); + + auto rows = args[0]->get_shape().elements() / lens.back(); + std::vector flat = {static_cast(rows), static_cast(lens.back())}; + auto x2d = m.insert_instruction(ins, make_op("reshape", {{"dims", flat}}), args[0]); + + auto gemm_args = args; + gemm_args[0] = x2d; + auto out = op::builder::insert("gemm", m, ins, gemm_args, gemm_opts).front(); + + std::vector out_dims(lens.begin(), lens.end() - 1); + out_dims.push_back(static_cast(out->get_shape().lens().back())); + return {m.insert_instruction(ins, make_op("reshape", {{"dims", out_dims}}), out)}; + } +}; + +} // namespace builder +} // namespace op +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx diff --git a/src/op/builder/torch/lstm.cpp b/src/op/builder/torch/lstm.cpp new file mode 100644 index 00000000000..23c39cc5bc7 --- /dev/null +++ b/src/op/builder/torch/lstm.cpp @@ -0,0 +1,84 @@ +/* + * 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 + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { +namespace op { +namespace builder { + +// lstm expands into an lstm op plus its last hidden-state and last cell-state outputs. +struct torch_lstm : op_builder +{ + std::size_t hidden_size = 1; + std::vector actv_funcs{}; + rnn_direction direction = rnn_direction::forward; + float clip = 0.0f; + int input_forget = 0; + + template + static auto reflect(Self& self, F f) + { + return pack(f(self.hidden_size, "hidden_size"), + f(self.actv_funcs, "actv_func"), + f(self.direction, "direction"), + f(self.clip, "clip"), + f(self.input_forget, "input_forget")); + } + + static std::vector names() { return {"tm::lstm"}; } + + std::vector + insert(module& m, instruction_ref ins, const std::vector& args) const + { + auto self = *this; + if(self.actv_funcs.empty()) + { + self.actv_funcs = {make_op("sigmoid"), make_op("tanh"), make_op("tanh")}; + if(self.direction == rnn_direction::bidirectional) + { + self.actv_funcs.insert(self.actv_funcs.end(), + {make_op("sigmoid"), make_op("tanh"), make_op("tanh")}); + } + } + auto hidden_states = + m.insert_instruction(ins, make_op("lstm", migraphx::to_value(self)), args); + auto last_hs = m.insert_instruction(ins, make_op("rnn_last_hs_output"), hidden_states); + auto last_cell = m.insert_instruction(ins, make_op("rnn_last_cell_output"), hidden_states); + return {hidden_states, last_hs, last_cell}; + } +}; + +} // namespace builder +} // namespace op +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx diff --git a/src/op/builder/torch/nan_to_num.cpp b/src/op/builder/torch/nan_to_num.cpp new file mode 100644 index 00000000000..6feb144886b --- /dev/null +++ b/src/op/builder/torch/nan_to_num.cpp @@ -0,0 +1,87 @@ +/* + * 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 + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { +namespace op { +namespace builder { + +// nan_to_num has no native op: replace NaN/+inf/-inf with the given values. +struct torch_nan_to_num : op_builder +{ + float nan = 0.0f; + float posinf = std::numeric_limits::max(); + float neginf = std::numeric_limits::lowest(); + + template + static auto reflect(Self& self, F f) + { + return pack(f(self.nan, "nan"), f(self.posinf, "posinf"), f(self.neginf, "neginf")); + } + + static std::vector names() { return {"tm::nan_to_num"}; } + + std::vector + insert(module& m, instruction_ref ins, const std::vector& args) const + { + auto x = args[0]; + auto type = x->get_shape().type(); + + auto nan_lit = m.add_literal({type, {nan}}); + auto zero = m.add_literal({type, {0.0f}}); + auto posinf_lit = m.add_literal({type, {posinf}}); + auto neginf_lit = m.add_literal({type, {neginf}}); + + // where selects per-element, so inputs are broadcast but not type-promoted + const common_options no_promote{.common_type = false}; + const auto where = make_op("where"); + auto select = [&](instruction_ref cond, instruction_ref val, instruction_ref other) { + return insert_common_op(m, ins, where, {cond, val, other}, no_promote); + }; + + auto is_nan = m.insert_instruction(ins, make_op("isnan"), x); + auto result = select(is_nan, nan_lit, x); + auto is_inf = m.insert_instruction(ins, make_op("isinf"), x); + auto less = insert_common_op(m, ins, "less", x, zero); + auto greater = insert_common_op(m, ins, "greater", x, zero); + auto neg_mask = insert_common_op(m, ins, "logical_and", less, is_inf); + auto pos_mask = insert_common_op(m, ins, "logical_and", greater, is_inf); + result = select(neg_mask, neginf_lit, result); + return {select(pos_mask, posinf_lit, result)}; + } +}; + +} // namespace builder +} // namespace op +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx diff --git a/src/op/builder/torch/scatter_reduce.cpp b/src/op/builder/torch/scatter_reduce.cpp new file mode 100644 index 00000000000..403db2f1a05 --- /dev/null +++ b/src/op/builder/torch/scatter_reduce.cpp @@ -0,0 +1,106 @@ +/* + * 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 +#include +#include +#include +#include + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { +namespace op { +namespace builder { + +// scatter_reduce has no native op: use the matching reduction scatter op; for include_self=false +// the target positions are first overwritten with the reduction identity so they drop out. +struct torch_scatter_reduce : op_builder +{ + int64_t dim = 0; + std::string reduce = "sum"; + bool include_self = true; + + template + static auto reflect(Self& self, F f) + { + return pack( + f(self.dim, "dim"), f(self.reduce, "reduce"), f(self.include_self, "include_self")); + } + + static std::vector names() { return {"tm::scatter_reduce"}; } + + std::vector + insert(module& m, instruction_ref ins, const std::vector& args) const + { + const std::unordered_map reduce_map = {{"mean", "scatter_none"}, + {"sum", "scatter_add"}, + {"prod", "scatter_mul"}, + {"amax", "scatter_max"}, + {"amin", "scatter_min"}}; + if(reduce_map.count(reduce) == 0) + MIGRAPHX_THROW("scatter_reduce: unsupported reduction '" + reduce + "'"); + + auto inp = args[0]; + auto idx = args[1]; + auto src = args[2]; + auto axis = tune_axis(inp->get_shape().ndim(), dim, "scatter_reduce"); + + if(not include_self and reduce != "mean") + { + argument id_arg{shape{inp->get_shape().type(), {1}}}; + id_arg.visit([&](auto v) { + using type = std::remove_cv_t; + if(reduce == "sum") + v.front() = type(0); + else if(reduce == "prod") + v.front() = type(1); + else if(reduce == "amax") + v.front() = std::numeric_limits::lowest(); + else + v.front() = std::numeric_limits::max(); + }); + auto identity = m.add_literal(id_arg.get_shape(), id_arg.data()); + identity = m.insert_instruction( + ins, make_op("multibroadcast", {{"out_lens", idx->get_shape().lens()}}), identity); + inp = m.insert_instruction( + ins, make_op("scatter_none", {{"axis", axis}}), {inp, idx, identity}); + } + + return {m.insert_instruction( + ins, make_op(reduce_map.at(reduce), {{"axis", axis}}), {inp, idx, src})}; + } +}; + +} // namespace builder +} // namespace op +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx diff --git a/src/op/builder/torch/slice_scatter.cpp b/src/op/builder/torch/slice_scatter.cpp new file mode 100644 index 00000000000..5f60f4386ef --- /dev/null +++ b/src/op/builder/torch/slice_scatter.cpp @@ -0,0 +1,76 @@ +/* + * 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 + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { +namespace op { +namespace builder { + +// slice_scatter has no native op: scatter src into the [start:end:step] slice along `dim`. +struct torch_slice_scatter : op_builder +{ + int64_t dim = 0; + int64_t start = 0; + int64_t end = 0; + int64_t step = 1; + + template + static auto reflect(Self& self, F f) + { + return pack( + f(self.dim, "dim"), f(self.start, "start"), f(self.end, "end"), f(self.step, "step")); + } + + static std::vector names() { return {"tm::slice_scatter"}; } + + std::vector + insert(module& m, instruction_ref ins, const std::vector& args) const + { + shape idx_shape{shape::int64_type, args[1]->get_shape().lens()}; + auto axis = tune_axis(idx_shape.ndim(), dim, "slice_scatter"); + std::vector data(idx_shape.elements()); + for(std::size_t i = 0; i < data.size(); ++i) + data[i] = start + step * idx_shape.multi(i)[axis]; + auto indices = m.add_literal(literal{idx_shape, data.begin(), data.end()}); + + auto std_input = m.insert_instruction(ins, make_op("contiguous"), args[0]); + auto std_src = m.insert_instruction(ins, make_op("contiguous"), args[1]); + return {m.insert_instruction( + ins, make_op("scatter_none", {{"axis", axis}}), {std_input, indices, std_src})}; + } +}; + +} // namespace builder +} // namespace op +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx diff --git a/src/op/builder/torch/std.cpp b/src/op/builder/torch/std.cpp new file mode 100644 index 00000000000..18265b956e6 --- /dev/null +++ b/src/op/builder/torch/std.cpp @@ -0,0 +1,85 @@ +/* + * 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 +#include + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { +namespace op { +namespace builder { + +// std has no native op: sqrt of the corrected variance reduced over axes. +struct torch_std : op_builder +{ + std::vector axes = {}; + bool keepdim = false; + float correction = 1.0f; + + template + static auto reflect(Self& self, F f) + { + return pack( + f(self.axes, "axes"), f(self.keepdim, "keepdim"), f(self.correction, "correction")); + } + + static std::vector names() { return {"tm::std"}; } + + std::vector + insert(module& m, instruction_ref ins, const std::vector& args) const + { + auto x = args[0]; + auto lens = x->get_shape().lens(); + auto type = x->get_shape().type(); + + auto n = std::accumulate( + axes.begin(), axes.end(), std::size_t{1}, [&](std::size_t acc, int64_t a) { + return acc * lens[tune_axis(lens.size(), a, "std")]; + }); + + auto mean = m.insert_instruction(ins, make_op("reduce_mean", {{"axes", axes}}), x); + auto sub = insert_common_op(m, ins, "sub", x, mean); + auto sq = insert_common_op(m, ins, "mul", sub, sub); + auto sum = m.insert_instruction(ins, make_op("reduce_sum", {{"axes", axes}}), sq); + auto denom = m.add_literal({type, {static_cast(n) - correction}}); + auto var = insert_common_op(m, ins, "div", sum, denom); + auto out = m.insert_instruction(ins, make_op("sqrt"), var); + if(not keepdim) + out = m.insert_instruction(ins, make_op("squeeze", {{"axes", axes}}), out); + return {out}; + } +}; + +} // namespace builder +} // namespace op +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx diff --git a/src/op/builder/torch_kit.cpp b/src/op/builder/torch_kit.cpp index b13d9cc37cf..ca9535b6638 100644 --- a/src/op/builder/torch_kit.cpp +++ b/src/op/builder/torch_kit.cpp @@ -23,66 +23,29 @@ * */ +#include #include -#include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { namespace builder { -struct torch_lstm : op_builder -{ - std::size_t hidden_size = 1; - std::vector actv_funcs{}; - rnn_direction direction = rnn_direction::forward; - float clip = 0.0f; - int input_forget = 0; - - template - static auto reflect(Self& self, F f) - { - return pack(f(self.hidden_size, "hidden_size"), - f(self.actv_funcs, "actv_func"), - f(self.direction, "direction"), - f(self.clip, "clip"), - f(self.input_forget, "input_forget")); - } - - static std::vector names() { return {"tm::lstm"}; } - - std::vector - insert(module& m, instruction_ref ins, const std::vector& args) const - { - auto self = *this; - if(self.actv_funcs.empty()) - { - self.actv_funcs = {make_op("sigmoid"), make_op("tanh"), make_op("tanh")}; - if(self.direction == rnn_direction::bidirectional) - { - self.actv_funcs.insert(self.actv_funcs.end(), - {make_op("sigmoid"), make_op("tanh"), make_op("tanh")}); - } - } - auto hidden_states = - m.insert_instruction(ins, make_op("lstm", migraphx::to_value(self)), args); - auto last_hs = m.insert_instruction(ins, make_op("rnn_last_hs_output"), hidden_states); - auto last_cell = m.insert_instruction(ins, make_op("rnn_last_cell_output"), hidden_states); - return {hidden_states, last_hs, last_cell}; - } -}; - +// Registration point for the torch kit. The tm:: composite builders are defined in +// op/builder/torch/*.cpp and auto-register themselves; this only wires up the passthrough +// ops and the aliases to existing shared builders. struct torch_kit : kit { std::string prefix() const { return "tm::"; } void apply() const { this->common_ops({ - "ceil", "convert", "cos", "cosh", "div", "dot", "elu", "equal", - "erf", "exp", "floor", "fmod", "greater", "isinf", "isnan", "leaky_relu", - "less", "log", "log2", "logical_and", "max", "min", "mul", "neg", - "not", "pow", "recip", "relu", "rsqrt", "sigmoid", "sign", "sin", - "sinh", "sqrt", "sub", "tan", "tanh", + "abs", "acos", "add", "asin", "atan", "bitwise_and", "ceil", + "convert", "cos", "cosh", "div", "elu", "equal", "erf", + "exp", "floor", "fmod", "greater", "isinf", "isnan", "leaky_relu", + "less", "log", "log2", "logical_and", "max", "min", "mul", + "neg", "not", "pow", "recip", "relu", "rsqrt", "sigmoid", + "sign", "sin", "sinh", "sqrt", "sub", "tan", "tanh", }); this->common_ops({"where"}, {.common_type = false}); @@ -92,12 +55,12 @@ struct torch_kit : kit "broadcast", "concat", "contiguous", - "convolution", "convolution_backwards", "dequantizelinear", "gather", "gathernd", "get_tuple_elem", + "logsoftmax", "multibroadcast", "pad", "pooling", @@ -121,6 +84,23 @@ struct torch_kit : kit "undefined", "unsqueeze", }); + + // Composite builders (bias fusion, broadcasting, etc.), not plain ops. + this->builders({"batchnorm", + "clip", + "convolution", + "dot", + "floor_div", + "gather_elements", + "gelu_erf", + "glu", + "group_norm", + "hardsigmoid", + "instance_norm", + "layer_norm", + "selu", + "softsign", + "vector_norm"}); } }; diff --git a/src/op/builder/vector_norm.cpp b/src/op/builder/vector_norm.cpp new file mode 100644 index 00000000000..7c5fa7975ef --- /dev/null +++ b/src/op/builder/vector_norm.cpp @@ -0,0 +1,92 @@ +/* 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 + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { +namespace op { +namespace builder { + +// vector_norm has no native op: reduce abs(x) over axes per the ord-specific formula. +struct vector_norm : op_builder +{ + float ord = 2.0f; + std::vector axes = {}; + bool keepdim = false; + + template + static auto reflect(Self& self, F f) + { + return pack(f(self.ord, "ord"), f(self.axes, "axes"), f(self.keepdim, "keepdim")); + } + + std::vector + insert(module& m, instruction_ref ins, const std::vector& args) const + { + auto x = args[0]; + auto x_type = x->get_shape().type(); + auto abs_x = m.insert_instruction(ins, make_op("abs"), x); + + instruction_ref out; + if(float_equal(ord, 0.0f)) + { + // count of nonzero elements: sum(abs(x) > 0) + auto zero = m.add_literal(migraphx::literal{migraphx::shape{x_type}, {0.0f}}); + auto nonzero = insert_common_op(m, ins, "greater", abs_x, zero); + auto counts = + m.insert_instruction(ins, make_op("convert", {{"target_type", x_type}}), nonzero); + out = m.insert_instruction(ins, make_op("reduce_sum", {{"axes", axes}}), counts); + } + else if(std::isinf(ord)) + { + // +inf -> max(abs(x)), -inf -> min(abs(x)) + const auto* reduce = ord > 0 ? "reduce_max" : "reduce_min"; + out = m.insert_instruction(ins, make_op(reduce, {{"axes", axes}}), abs_x); + } + else + { + // sum(abs(x) ^ ord) ^ (1 / ord) + auto ord_lit = m.add_literal(migraphx::literal{migraphx::shape{x_type}, {ord}}); + auto pow_x = insert_common_op(m, ins, "pow", abs_x, ord_lit); + auto sum_pow = + m.insert_instruction(ins, make_op("reduce_sum", {{"axes", axes}}), pow_x); + auto recip = m.insert_instruction(ins, make_op("recip"), ord_lit); + out = insert_common_op(m, ins, "pow", sum_pow, recip); + } + + if(not keepdim) + out = m.insert_instruction(ins, make_op("squeeze", {{"axes", axes}}), out); + return {out}; + } +}; + +} // namespace builder +} // namespace op +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx diff --git a/src/py/migraphx_py.cpp b/src/py/migraphx_py.cpp index 06d7cc94969..517c2982e69 100644 --- a/src/py/migraphx_py.cpp +++ b/src/py/migraphx_py.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -389,6 +390,7 @@ MIGRAPHX_PYBIND11_MODULE(migraphx, m) .def("type_string", &migraphx::shape::type_string) .def("type_size", &migraphx::shape::type_size) .def("dyn_dims", &migraphx::shape::dyn_dims) + .def("sub_shapes", &migraphx::shape::sub_shapes) .def("packed", &migraphx::shape::packed) .def("transposed", &migraphx::shape::transposed) .def("broadcasted", &migraphx::shape::broadcasted) @@ -734,6 +736,12 @@ MIGRAPHX_PYBIND11_MODULE(migraphx, m) .def("options", [](const py_macro& mac) -> py::object { return to_py_object(mac.options); }); + m.def( + "has_op_builder", + [](const std::string& name) { return migraphx::op::builder::has_op_builder(name); }, + py::arg("name"), + "Whether an op-builder (e.g. a \"tm::\" kit builder) is registered."); + m.def( "argument_from_pointer", [](const migraphx::shape shape, const int64_t address) { diff --git a/test/op/CMakeLists.txt b/test/op/CMakeLists.txt index e850f58b94f..72cb1175504 100644 --- a/test/op/CMakeLists.txt +++ b/test/op/CMakeLists.txt @@ -1,7 +1,7 @@ ##################################################################################### # The MIT License (MIT) # -# Copyright (c) 2015-2025 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 @@ -22,7 +22,7 @@ # THE SOFTWARE. ##################################################################################### -file(GLOB OP_BUILDER_TESTS CONFIGURE_DEPENDS builder/*.cpp) +file(GLOB OP_BUILDER_TESTS CONFIGURE_DEPENDS builder/*.cpp builder/torch/*.cpp) rocm_add_test_executable(test_op_builder_test ${OP_BUILDER_TESTS}) target_include_directories(test_op_builder_test PUBLIC ../include include) diff --git a/test/op/builder/gather_elements_test.cpp b/test/op/builder/gather_elements_test.cpp new file mode 100644 index 00000000000..aadff09d1da --- /dev/null +++ b/test/op/builder/gather_elements_test.cpp @@ -0,0 +1,59 @@ +/* + * 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 + +// gather_elements flattens the data and gathers element-wise using per-element flat +// offsets: shape_index + (index - axis_coord) * axis_stride, evaluated over the index shape. +TEST_CASE(gather_elements_op_builder_test) +{ + const auto f = migraphx::shape::float_type; + const auto i = migraphx::shape::int32_type; + + migraphx::module mm; + auto data = mm.add_parameter("data", {f, {2, 3}}); + auto ind = mm.add_parameter("ind", {i, {2, 3}}); + + auto arg_data = mm.add_instruction(migraphx::make_op("contiguous"), data); + auto arg_ind = mm.add_instruction(migraphx::make_op("contiguous"), ind); + arg_data = mm.add_instruction(migraphx::make_op("reshape", {{"dims", {6}}}), arg_data); + + std::vector shape_idx = {0, 1, 2, 3, 4, 5}; + std::vector dim_idx = {0, 1, 2, 0, 1, 2}; + auto l_shape_idx = mm.add_literal(migraphx::literal{migraphx::shape{i, {2, 3}}, shape_idx}); + auto l_dim_idx = mm.add_literal(migraphx::literal{migraphx::shape{i, {2, 3}}, dim_idx}); + auto l_stride = mm.add_literal(migraphx::literal{migraphx::shape{i, {1}}, {1}}); + l_stride = + mm.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 3}}}), l_stride); + auto dim_diff = mm.add_instruction(migraphx::make_op("sub"), arg_ind, l_dim_idx); + auto delta = mm.add_instruction(migraphx::make_op("mul"), dim_diff, l_stride); + auto indices = mm.add_instruction(migraphx::make_op("add"), l_shape_idx, delta); + mm.add_instruction(migraphx::make_op("gather", {{"axis", 0}}), arg_data, indices); + + EXPECT(mm == make_op_module("gather_elements", {{"axis", 1}}, mm.get_parameters())); +} diff --git a/test/op/builder/torch/as_strided_test.cpp b/test/op/builder/torch/as_strided_test.cpp new file mode 100644 index 00000000000..b0a799df1f4 --- /dev/null +++ b/test/op/builder/torch/as_strided_test.cpp @@ -0,0 +1,64 @@ +/* + * 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 + +// tm::as_strided materializes a strided view by flattening the input and gathering +// the element at storage_offset + strided.index(i) for every output coordinate. +TEST_CASE(torch_kit_as_strided_op_builder_test) +{ + const auto f = migraphx::shape::float_type; + + migraphx::module mm; + auto x = mm.add_parameter("x", {f, {4}}); + + std::vector idx_data = {0, 1, 2, 3}; + auto indices = mm.add_literal( + migraphx::literal{migraphx::shape{migraphx::shape::int64_type, {4}}, idx_data}); + auto flat_inp = mm.add_instruction(migraphx::make_op("contiguous"), x); + flat_inp = mm.add_instruction(migraphx::make_op("reshape", {{"dims", {-1}}}), flat_inp); + auto gathered = + mm.add_instruction(migraphx::make_op("gather", {{"axis", 0}}), flat_inp, indices); + mm.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 2}}}), gathered); + + migraphx::value options{{"size", {2, 2}}, {"stride", {2, 1}}, {"storage_offset", 0}}; + EXPECT(mm == make_op_module("tm::as_strided", options, mm.get_parameters())); +} + +// size and stride must have matching lengths. +TEST_CASE(torch_kit_as_strided_size_stride_mismatch) +{ + const auto f = migraphx::shape::float_type; + migraphx::module mm; + mm.add_parameter("x", {f, {4}}); + EXPECT(test::throws([&] { + make_op_module("tm::as_strided", + {{"size", {2, 2}}, {"stride", {2}}, {"storage_offset", 0}}, + mm.get_parameters()); + })); +} diff --git a/test/op/builder/torch/batchnorm_test.cpp b/test/op/builder/torch/batchnorm_test.cpp new file mode 100644 index 00000000000..170fff9fa03 --- /dev/null +++ b/test/op/builder/torch/batchnorm_test.cpp @@ -0,0 +1,42 @@ +/* + * 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 + +// tm::batchnorm is a thin re-export of the global "batchnorm" builder, so the "tm::"-prefixed +// form must match the un-prefixed builder exactly. +TEST_CASE(torch_kit_batchnorm_op_builder_test) +{ + const auto f = migraphx::shape::float_type; + migraphx::value options{{"epsilon", 1e-5f}}; + migraphx::module mm; + auto x = mm.add_parameter("x", {f, {2, 3, 4, 4}}); + auto scale = mm.add_parameter("scale", {f, {3}}); + auto bias = mm.add_parameter("bias", {f, {3}}); + auto mean = mm.add_parameter("mean", {f, {3}}); + auto var = mm.add_parameter("var", {f, {3}}); + migraphx::op::builder::add("batchnorm", mm, {x, scale, bias, mean, var}, options); + + EXPECT(mm == make_op_module("tm::batchnorm", options, mm.get_parameters())); +} diff --git a/test/op/builder/torch/clip_test.cpp b/test/op/builder/torch/clip_test.cpp new file mode 100644 index 00000000000..99ebc9c29c3 --- /dev/null +++ b/test/op/builder/torch/clip_test.cpp @@ -0,0 +1,95 @@ +/* + * 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 + +// tm::clip lowers to clip/min/max/identity based on which optional bounds are given +// (an undefined arg means "absent"). + +TEST_CASE(torch_kit_clip_min_and_max_op_builder_test) +{ + const auto f = migraphx::shape::float_type; + migraphx::module mm; + auto x = mm.add_parameter("x", {f, {2, 3}}); + auto lo = mm.add_parameter("lo", {f, {2, 3}}); + auto hi = mm.add_parameter("hi", {f, {2, 3}}); + add_common_op(mm, migraphx::make_op("clip"), {x, lo, hi}); + + EXPECT(mm == make_op_module("tm::clip", mm.get_parameters())); +} + +TEST_CASE(torch_kit_clip_min_only_op_builder_test) +{ + // max is undefined -> lowers to max(x, lo). + const auto f = migraphx::shape::float_type; + migraphx::module mm; + auto x = mm.add_parameter("x", {f, {2, 3}}); + auto lo = mm.add_parameter("lo", {f, {2, 3}}); + mm.add_instruction(migraphx::make_op("undefined")); + add_common_op(mm, migraphx::make_op("max"), {x, lo}); + + migraphx::module mm_op_built; + auto x_op = mm_op_built.add_parameter("x", {f, {2, 3}}); + auto lo_op = mm_op_built.add_parameter("lo", {f, {2, 3}}); + auto hi_op = mm_op_built.add_instruction(migraphx::make_op("undefined")); + migraphx::op::builder::add("tm::clip", mm_op_built, {x_op, lo_op, hi_op}); + EXPECT(mm == mm_op_built); +} + +TEST_CASE(torch_kit_clip_max_only_op_builder_test) +{ + // min is undefined -> lowers to min(x, hi). + const auto f = migraphx::shape::float_type; + migraphx::module mm; + auto x = mm.add_parameter("x", {f, {2, 3}}); + mm.add_instruction(migraphx::make_op("undefined")); + auto hi = mm.add_parameter("hi", {f, {2, 3}}); + add_common_op(mm, migraphx::make_op("min"), {x, hi}); + + migraphx::module mm_op_built; + auto x_op = mm_op_built.add_parameter("x", {f, {2, 3}}); + auto lo_op = mm_op_built.add_instruction(migraphx::make_op("undefined")); + auto hi_op = mm_op_built.add_parameter("hi", {f, {2, 3}}); + migraphx::op::builder::add("tm::clip", mm_op_built, {x_op, lo_op, hi_op}); + EXPECT(mm == mm_op_built); +} + +TEST_CASE(torch_kit_clip_none_op_builder_test) +{ + // Neither bound supplied -> identity(x). + const auto f = migraphx::shape::float_type; + migraphx::module mm; + auto x = mm.add_parameter("x", {f, {2, 3}}); + mm.add_instruction(migraphx::make_op("undefined")); + mm.add_instruction(migraphx::make_op("undefined")); + mm.add_instruction(migraphx::make_op("identity"), x); + + migraphx::module mm_op_built; + auto x_op = mm_op_built.add_parameter("x", {f, {2, 3}}); + auto lo_op = mm_op_built.add_instruction(migraphx::make_op("undefined")); + auto hi_op = mm_op_built.add_instruction(migraphx::make_op("undefined")); + migraphx::op::builder::add("tm::clip", mm_op_built, {x_op, lo_op, hi_op}); + EXPECT(mm == mm_op_built); +} diff --git a/test/op/builder/torch_kit_test.cpp b/test/op/builder/torch/common_ops_test.cpp similarity index 61% rename from test/op/builder/torch_kit_test.cpp rename to test/op/builder/torch/common_ops_test.cpp index ea207e86f0b..6ecea314791 100644 --- a/test/op/builder/torch_kit_test.cpp +++ b/test/op/builder/torch/common_ops_test.cpp @@ -22,17 +22,17 @@ * THE SOFTWARE. */ +#include +#include +#include +#include #include #include #include #include -#include -#include -// The torch_kit registers builders under the "tm::" prefix. The custom builder -// "tm::lstm" expands into an lstm op plus the rnn_last_hs_output and -// rnn_last_cell_output ops; the remaining builders are thin wrappers around -// native ops, either with common (broadcast/convert) handling or without. +// The torch kit registers the common (broadcast/convert) ops and a set of plain passthrough +// ops under the "tm::" prefix. Each builder must insert exactly its wrapped op over the args. namespace { struct param_spec @@ -41,11 +41,9 @@ struct param_spec migraphx::shape shape; }; -// Verifies that a plain (non-common) builder inserts exactly the wrapped op over -// the given args, unchanged. Builds the expected module by hand and compares it -// to what the kit's "tm::"-prefixed builder produces. Returns the comparison so -// the caller can EXPECT() it with the op name as a literal -- that way a failure -// message identifies which op did not match. +// Verifies a plain (non-common) builder inserts exactly the wrapped op over the given args. +// Returns the comparison so the caller can EXPECT() it with the op name as a literal -- a failure +// message then identifies which op did not match. bool check_plain_op(const std::string& op_name, const migraphx::value& options, const std::vector& params) @@ -61,92 +59,12 @@ bool check_plain_op(const std::string& op_name, } } // namespace -TEST_CASE(torch_lstm_forward_op_builder_test) -{ - const std::size_t hidden_size = 2; - - migraphx::module mm; - auto x = mm.add_parameter("x", {migraphx::shape::float_type, {3, 4, 5}}); - auto w = mm.add_parameter("w", {migraphx::shape::float_type, {1, 8, 5}}); - auto r = mm.add_parameter("r", {migraphx::shape::float_type, {1, 8, 2}}); - - // A forward lstm defaults to the {sigmoid, tanh, tanh} activation set. - std::vector actv_funcs{ - migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")}; - - auto hs = mm.add_instruction( - migraphx::make_op( - "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(actv_funcs)}}), - x, - w, - r); - mm.add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); - mm.add_instruction(migraphx::make_op("rnn_last_cell_output"), hs); - - EXPECT(mm == make_op_module("tm::lstm", {{"hidden_size", hidden_size}}, mm.get_parameters())); -} - -TEST_CASE(torch_lstm_bidirectional_op_builder_test) -{ - const std::size_t hidden_size = 2; - - migraphx::module mm; - auto x = mm.add_parameter("x", {migraphx::shape::float_type, {3, 4, 5}}); - auto w = mm.add_parameter("w", {migraphx::shape::float_type, {2, 8, 5}}); - auto r = mm.add_parameter("r", {migraphx::shape::float_type, {2, 8, 2}}); - - // A bidirectional lstm needs the activation set duplicated (6 functions). - std::vector actv_funcs{migraphx::make_op("sigmoid"), - migraphx::make_op("tanh"), - migraphx::make_op("tanh"), - migraphx::make_op("sigmoid"), - migraphx::make_op("tanh"), - migraphx::make_op("tanh")}; - - auto hs = mm.add_instruction( - migraphx::make_op("lstm", - {{"hidden_size", hidden_size}, - {"actv_func", migraphx::to_value(actv_funcs)}, - {"direction", migraphx::op::rnn_direction::bidirectional}}), - x, - w, - r); - mm.add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); - mm.add_instruction(migraphx::make_op("rnn_last_cell_output"), hs); - - migraphx::value options{{"hidden_size", hidden_size}, - {"direction", migraphx::op::rnn_direction::bidirectional}}; - EXPECT(mm == make_op_module("tm::lstm", options, mm.get_parameters())); -} - -TEST_CASE(torch_lstm_custom_actv_funcs_op_builder_test) -{ - const std::size_t hidden_size = 2; - - // Explicitly provided activation functions should be used as-is and not be - // overridden with the defaults. - std::vector actv_funcs{ - migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("sigmoid")}; - migraphx::value options{{"hidden_size", hidden_size}, - {"actv_func", migraphx::to_value(actv_funcs)}}; - - migraphx::module mm; - auto x = mm.add_parameter("x", {migraphx::shape::float_type, {3, 4, 5}}); - auto w = mm.add_parameter("w", {migraphx::shape::float_type, {1, 8, 5}}); - auto r = mm.add_parameter("r", {migraphx::shape::float_type, {1, 8, 2}}); - auto hs = mm.add_instruction(migraphx::make_op("lstm", options), x, w, r); - mm.add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); - mm.add_instruction(migraphx::make_op("rnn_last_cell_output"), hs); - - EXPECT(mm == make_op_module("tm::lstm", options, mm.get_parameters())); -} - TEST_CASE(torch_kit_common_unary_op_builder_test) { const std::vector unary_ops{ - "ceil", "cos", "cosh", "elu", "erf", "exp", "floor", "isinf", - "isnan", "log", "log2", "leaky_relu", "neg", "recip", "relu", "rsqrt", - "sigmoid", "sign", "sin", "sinh", "sqrt", "tan", "tanh"}; + "abs", "acos", "asin", "atan", "ceil", "cos", "cosh", "elu", "erf", + "exp", "floor", "isinf", "isnan", "log", "log2", "leaky_relu", "neg", "recip", + "relu", "rsqrt", "sigmoid", "sign", "sin", "sinh", "sqrt", "tan", "tanh"}; std::for_each(unary_ops.begin(), unary_ops.end(), [&](const std::string& op_name) { migraphx::module mm; @@ -160,7 +78,7 @@ TEST_CASE(torch_kit_common_unary_op_builder_test) TEST_CASE(torch_kit_common_binary_op_builder_test) { const std::vector binary_ops{ - "div", "equal", "fmod", "greater", "less", "max", "min", "mul", "pow", "sub"}; + "add", "div", "equal", "fmod", "greater", "less", "max", "min", "mul", "pow", "sub"}; std::for_each(binary_ops.begin(), binary_ops.end(), [&](const std::string& op_name) { migraphx::module mm; @@ -193,16 +111,15 @@ TEST_CASE(torch_kit_common_not_op_builder_test) EXPECT(mm == make_op_module("tm::not", mm.get_parameters())); } -TEST_CASE(torch_kit_common_dot_op_builder_test) +TEST_CASE(torch_kit_common_bitwise_and_op_builder_test) { - // "dot" is registered as a common op, so its inputs go through common - // broadcasting; use matching square shapes that survive it. + // bitwise_and needs integral types; different ranks exercise common broadcasting. migraphx::module mm; - auto a = mm.add_parameter("a", {migraphx::shape::float_type, {4, 4}}); - auto b = mm.add_parameter("b", {migraphx::shape::float_type, {4, 4}}); - add_common_op(mm, migraphx::make_op("dot"), {a, b}); + auto a = mm.add_parameter("a", {migraphx::shape::int32_type, {2, 3, 4}}); + auto b = mm.add_parameter("b", {migraphx::shape::int32_type, {4}}); + add_common_op(mm, migraphx::make_op("bitwise_and"), {a, b}); - EXPECT(mm == make_op_module("tm::dot", mm.get_parameters())); + EXPECT(mm == make_op_module("tm::bitwise_and", mm.get_parameters())); } TEST_CASE(torch_kit_where_op_builder_test) @@ -247,14 +164,13 @@ TEST_CASE(torch_kit_ops_op_builder_test) EXPECT(check_plain_op("broadcast", {{"axis", 1}, {"out_lens", {2, 4, 6}}}, {{"a", {f, {4}}}})); EXPECT(check_plain_op("concat", {{"axis", 0}}, {{"a", {f, {4, 6}}}, {"b", {f, {4, 6}}}})); EXPECT(check_plain_op("contiguous", obj, {{"a", {f, {4, 6}}}})); - EXPECT( - check_plain_op("convolution", obj, {{"x", {f, {1, 3, 8, 8}}}, {"w", {f, {4, 3, 3, 3}}}})); EXPECT(check_plain_op( "convolution_backwards", obj, {{"x", {f, {1, 3, 8, 8}}}, {"w", {f, {3, 4, 3, 3}}}})); EXPECT(check_plain_op("dequantizelinear", obj, {{"x", {i8, {4, 6}}}, {"scale", {f, {4, 6}}}})); EXPECT(check_plain_op("gather", {{"axis", 0}}, {{"data", {f, {4, 6}}}, {"ind", {i64, {2}}}})); EXPECT(check_plain_op("gathernd", obj, {{"data", {f, {4, 6}}}, {"ind", {i64, {2, 1}}}})); EXPECT(check_plain_op("get_tuple_elem", {{"index", 0}}, {{"a", tuple_s}})); + EXPECT(check_plain_op("logsoftmax", {{"axis", 1}}, {{"a", {f, {4, 6}}}})); EXPECT(check_plain_op("multibroadcast", {{"out_lens", {2, 4, 6}}}, {{"a", {f, {4, 6}}}})); EXPECT(check_plain_op("pad", {{"pads", {0, 0, 1, 1}}}, {{"a", {f, {4, 6}}}})); EXPECT(check_plain_op("pooling", diff --git a/test/op/builder/torch/conv_transpose_test.cpp b/test/op/builder/torch/conv_transpose_test.cpp new file mode 100644 index 00000000000..53892d9d45a --- /dev/null +++ b/test/op/builder/torch/conv_transpose_test.cpp @@ -0,0 +1,89 @@ +/* + * 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 + +// tm::conv_transpose runs convolution_backwards unpadded, crops off the symmetric +// padding while keeping the output_padding elements, then adds the channel bias. +TEST_CASE(torch_kit_conv_transpose_op_builder_test) +{ + const auto f = migraphx::shape::float_type; + std::vector stride = {2, 2}; + std::vector padding = {1, 1}; + std::vector dilation = {1, 1}; + std::vector output_padding = {1, 1}; + migraphx::value options{{"stride", stride}, + {"padding", padding}, + {"dilation", dilation}, + {"output_padding", output_padding}, + {"group", 1}}; + + migraphx::module mm; + auto x = mm.add_parameter("x", {f, {1, 3, 4, 4}}); + auto w = mm.add_parameter("w", {f, {3, 4, 3, 3}}); + auto bias = mm.add_parameter("bias", {f, {4}}); + auto out = mm.add_instruction( + migraphx::make_op( + "convolution_backwards", + {{"stride", stride}, {"padding", {0, 0}}, {"dilation", dilation}, {"group", 1}}), + x, + w); + auto cropped = mm.add_instruction( + migraphx::make_op("slice", {{"axes", {2, 3}}, {"starts", {1, 1}}, {"ends", {9, 9}}}), out); + auto b = mm.add_instruction( + migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 4, 8, 8}}}), bias); + mm.add_instruction(migraphx::make_op("add"), cropped, b); + + EXPECT(mm == make_op_module("tm::conv_transpose", options, mm.get_parameters())); +} + +// tm::conv_transpose with no output_padding passes padding straight to the op. +TEST_CASE(torch_kit_conv_transpose_no_crop_op_builder_test) +{ + const auto f = migraphx::shape::float_type; + std::vector stride = {1, 1}; + std::vector padding = {1, 1}; + std::vector dilation = {1, 1}; + std::vector output_padding = {0, 0}; + migraphx::value options{{"stride", stride}, + {"padding", padding}, + {"dilation", dilation}, + {"output_padding", output_padding}, + {"group", 1}}; + + migraphx::module mm; + auto x = mm.add_parameter("x", {f, {1, 3, 4, 4}}); + auto w = mm.add_parameter("w", {f, {3, 4, 3, 3}}); + mm.add_instruction( + migraphx::make_op( + "convolution_backwards", + {{"stride", stride}, {"padding", padding}, {"dilation", dilation}, {"group", 1}}), + x, + w); + + EXPECT(mm == make_op_module("tm::conv_transpose", options, mm.get_parameters())); +} diff --git a/test/op/builder/torch/convolution_test.cpp b/test/op/builder/torch/convolution_test.cpp new file mode 100644 index 00000000000..70c1cf54c9e --- /dev/null +++ b/test/op/builder/torch/convolution_test.cpp @@ -0,0 +1,46 @@ +/* + * 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 + +// tm::convolution aliases the shared convolution builder (conv + fused channel bias). Note the +// builder's plural attribute names. +TEST_CASE(torch_kit_convolution_op_builder_test) +{ + const auto f = migraphx::shape::float_type; + std::vector strides = {1, 1}; + std::vector paddings = {0, 0}; + std::vector dilations = {1, 1}; + migraphx::value options{ + {"strides", strides}, {"paddings", paddings}, {"dilations", dilations}, {"group", 1}}; + migraphx::module mm; + auto x = mm.add_parameter("x", {f, {1, 3, 8, 8}}); + auto w = mm.add_parameter("w", {f, {4, 3, 3, 3}}); + auto bias = mm.add_parameter("bias", {f, {4}}); + migraphx::op::builder::add("convolution", mm, {x, w, bias}, options); + + EXPECT(mm == make_op_module("tm::convolution", options, mm.get_parameters())); +} diff --git a/test/op/builder/torch/dot_test.cpp b/test/op/builder/torch/dot_test.cpp new file mode 100644 index 00000000000..a2c958e46c6 --- /dev/null +++ b/test/op/builder/torch/dot_test.cpp @@ -0,0 +1,38 @@ +/* + * 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 + +// tm::dot aliases the shared dot builder (numpy batch-broadcast + dot). Mixed batch ranks +// exercise the broadcasting. +TEST_CASE(torch_kit_dot_op_builder_test) +{ + const auto f = migraphx::shape::float_type; + migraphx::module mm; + auto a = mm.add_parameter("a", {f, {2, 1, 3, 4}}); + auto b = mm.add_parameter("b", {f, {5, 4, 6}}); + migraphx::op::builder::add("dot", mm, {a, b}); + + EXPECT(mm == make_op_module("tm::dot", mm.get_parameters())); +} diff --git a/test/op/builder/torch/floor_div_test.cpp b/test/op/builder/torch/floor_div_test.cpp new file mode 100644 index 00000000000..8e047fd661c --- /dev/null +++ b/test/op/builder/torch/floor_div_test.cpp @@ -0,0 +1,39 @@ +/* + * 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 + +// tm::floor_div == floor(common div); different ranks exercise broadcasting. +TEST_CASE(torch_kit_floor_div_op_builder_test) +{ + const auto f = migraphx::shape::float_type; + migraphx::module mm; + auto a = mm.add_parameter("a", {f, {2, 3, 4}}); + auto b = mm.add_parameter("b", {f, {4}}); + auto quotient = add_common_op(mm, migraphx::make_op("div"), {a, b}); + mm.add_instruction(migraphx::make_op("floor"), quotient); + + EXPECT(mm == make_op_module("tm::floor_div", mm.get_parameters())); +} diff --git a/test/op/builder/torch/gelu_test.cpp b/test/op/builder/torch/gelu_test.cpp new file mode 100644 index 00000000000..791ab64e172 --- /dev/null +++ b/test/op/builder/torch/gelu_test.cpp @@ -0,0 +1,37 @@ +/* + * 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 + +// tm::gelu_erf is a thin re-export of the global "gelu_erf" builder, so the "tm::"-prefixed +// form must match it exactly. +TEST_CASE(torch_kit_gelu_erf_op_builder_test) +{ + const auto f = migraphx::shape::float_type; + migraphx::module mm; + auto x = mm.add_parameter("x", {f, {2, 3}}); + migraphx::op::builder::add("gelu_erf", mm, {x}); + + EXPECT(mm == make_op_module("tm::gelu_erf", mm.get_parameters())); +} diff --git a/test/op/builder/torch/glu_test.cpp b/test/op/builder/torch/glu_test.cpp new file mode 100644 index 00000000000..2fabb5b07c7 --- /dev/null +++ b/test/op/builder/torch/glu_test.cpp @@ -0,0 +1,43 @@ +/* + * 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 + +// tm::glu splits the input in half along `axis` and gates the first half by +// sigmoid of the second: glu(x) = x1 * sigmoid(x2). +TEST_CASE(torch_kit_glu_op_builder_test) +{ + const auto f = migraphx::shape::float_type; + migraphx::module mm; + auto x = mm.add_parameter("x", {f, {2, 4}}); + auto first = mm.add_instruction( + migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {2}}}), x); + auto second = mm.add_instruction( + migraphx::make_op("slice", {{"axes", {1}}, {"starts", {2}}, {"ends", {4}}}), x); + auto gate = mm.add_instruction(migraphx::make_op("sigmoid"), second); + add_common_op(mm, migraphx::make_op("mul"), {first, gate}); + + EXPECT(mm == make_op_module("tm::glu", {{"axis", -1}}, mm.get_parameters())); +} diff --git a/test/op/builder/torch/group_norm_test.cpp b/test/op/builder/torch/group_norm_test.cpp new file mode 100644 index 00000000000..5cdc9270bdf --- /dev/null +++ b/test/op/builder/torch/group_norm_test.cpp @@ -0,0 +1,72 @@ +/* + * 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 + +// tm::group_norm reshapes to (N, num_groups, -1), normalizes over the trailing axis, +// reshapes back, then applies the per-channel affine. +TEST_CASE(torch_kit_group_norm_op_builder_test) +{ + const auto f = migraphx::shape::float_type; + const float eps = 1e-5f; + + migraphx::module mm; + auto x = mm.add_parameter("x", {f, {2, 4, 3}}); + auto scale = mm.add_parameter("scale", {f, {4}}); + auto bias = mm.add_parameter("bias", {f, {4}}); + + std::vector axes = {-1}; + auto grouped = mm.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 2, -1}}}), x); + auto mean = mm.add_instruction(migraphx::make_op("reduce_mean", {{"axes", axes}}), grouped); + auto x_sub = add_common_op(mm, migraphx::make_op("sub"), {grouped, mean}); + auto sqdiff = add_common_op(mm, migraphx::make_op("sqdiff"), {grouped, mean}); + auto variance = mm.add_instruction(migraphx::make_op("reduce_mean", {{"axes", axes}}), sqdiff); + auto eps_lit = mm.add_literal(migraphx::literal{migraphx::shape{f}, {eps}}); + auto var_eps = add_common_op(mm, migraphx::make_op("add"), {variance, eps_lit}); + auto rsqrt = mm.add_instruction(migraphx::make_op("rsqrt"), var_eps); + auto norm = add_common_op(mm, migraphx::make_op("mul"), {x_sub, rsqrt}); + auto norm_r = mm.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 4, 3}}}), norm); + auto scale_u = mm.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), scale); + auto bias_u = mm.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), bias); + auto scaled = add_common_op(mm, migraphx::make_op("mul"), {norm_r, scale_u}); + add_common_op(mm, migraphx::make_op("add"), {scaled, bias_u}); + + EXPECT(mm == make_op_module( + "tm::group_norm", {{"epsilon", eps}, {"num_groups", 2}}, mm.get_parameters())); +} + +// num_groups must divide the channel dim and the input must have spatial dims. +TEST_CASE(torch_kit_group_norm_bad_input_op_builder_test) +{ + const auto f = migraphx::shape::float_type; + migraphx::module mm; + mm.add_parameter("x", {f, {2, 3, 4}}); // 3 channels not divisible by num_groups = 2 + EXPECT(test::throws([&] { + make_op_module( + "tm::group_norm", {{"epsilon", 1e-5f}, {"num_groups", 2}}, mm.get_parameters()); + })); +} diff --git a/test/op/builder/torch/hardsigmoid_test.cpp b/test/op/builder/torch/hardsigmoid_test.cpp new file mode 100644 index 00000000000..fcccc7bed17 --- /dev/null +++ b/test/op/builder/torch/hardsigmoid_test.cpp @@ -0,0 +1,43 @@ +/* + * 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 + +// tm::hardsigmoid == clip(alpha * x + beta, 0, 1) with alpha = 1/6, beta = 1/2. +TEST_CASE(torch_kit_hardsigmoid_op_builder_test) +{ + const auto f = migraphx::shape::float_type; + migraphx::module mm; + auto x = mm.add_parameter("x", {f, {2, 3}}); + auto alpha = mm.add_literal(migraphx::literal{migraphx::shape{f}, {1.0f / 6.0f}}); + auto beta = mm.add_literal(migraphx::literal{migraphx::shape{f}, {0.5f}}); + auto lo = mm.add_literal(migraphx::literal{migraphx::shape{f}, {0.0f}}); + auto hi = mm.add_literal(migraphx::literal{migraphx::shape{f}, {1.0f}}); + auto scaled = add_common_op(mm, migraphx::make_op("mul"), {alpha, x}); + auto shifted = add_common_op(mm, migraphx::make_op("add"), {beta, scaled}); + add_common_op(mm, migraphx::make_op("clip"), {shifted, lo, hi}); + + EXPECT(mm == make_op_module("tm::hardsigmoid", mm.get_parameters())); +} diff --git a/test/op/builder/torch/index_copy_test.cpp b/test/op/builder/torch/index_copy_test.cpp new file mode 100644 index 00000000000..62faad933b2 --- /dev/null +++ b/test/op/builder/torch/index_copy_test.cpp @@ -0,0 +1,46 @@ +/* + * 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 + +// tm::index_copy reshapes the 1-D index to the src rank, broadcasts it to the src +// shape, and scatters src into the rows of `dim` it selects. +TEST_CASE(torch_kit_index_copy_op_builder_test) +{ + const auto f = migraphx::shape::float_type; + const auto i = migraphx::shape::int32_type; + + migraphx::module mm; + auto inp = mm.add_parameter("inp", {f, {5, 4}}); + auto idx = mm.add_parameter("idx", {i, {2}}); + auto src = mm.add_parameter("src", {f, {2, 4}}); + + auto scatter_idx = mm.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 1}}}), idx); + scatter_idx = mm.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 4}}}), + scatter_idx); + mm.add_instruction(migraphx::make_op("scatter_none", {{"axis", 0}}), inp, scatter_idx, src); + + EXPECT(mm == make_op_module("tm::index_copy", {{"dim", 0}}, mm.get_parameters())); +} diff --git a/test/op/builder/torch/instance_norm_test.cpp b/test/op/builder/torch/instance_norm_test.cpp new file mode 100644 index 00000000000..7150c49e294 --- /dev/null +++ b/test/op/builder/torch/instance_norm_test.cpp @@ -0,0 +1,67 @@ +/* + * 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 + +// tm::instance_norm computes stats from the input over the batch and spatial dims +// (every dim except channel dim 1), then applies the per-channel affine. +TEST_CASE(torch_kit_instance_norm_op_builder_test) +{ + const auto f = migraphx::shape::float_type; + const float eps = 1e-5f; + + migraphx::module mm; + auto x = mm.add_parameter("x", {f, {2, 3, 4, 4}}); + auto scale = mm.add_parameter("scale", {f, {3}}); + auto bias = mm.add_parameter("bias", {f, {3}}); + + std::vector axes = {0, 2, 3}; + auto mean = mm.add_instruction(migraphx::make_op("reduce_mean", {{"axes", axes}}), x); + auto x_sub = add_common_op(mm, migraphx::make_op("sub"), {x, mean}); + auto sqdiff = add_common_op(mm, migraphx::make_op("sqdiff"), {x, mean}); + auto variance = mm.add_instruction(migraphx::make_op("reduce_mean", {{"axes", axes}}), sqdiff); + auto eps_lit = mm.add_literal(migraphx::literal{migraphx::shape{f}, {eps}}); + auto var_eps = add_common_op(mm, migraphx::make_op("add"), {variance, eps_lit}); + auto rsqrt = mm.add_instruction(migraphx::make_op("rsqrt"), var_eps); + auto norm = add_common_op(mm, migraphx::make_op("mul"), {x_sub, rsqrt}); + auto scale_u = mm.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), scale); + auto bias_u = mm.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), bias); + auto scaled = add_common_op(mm, migraphx::make_op("mul"), {norm, scale_u}); + add_common_op(mm, migraphx::make_op("add"), {scaled, bias_u}); + + EXPECT(mm == make_op_module("tm::instance_norm", {{"epsilon", eps}}, mm.get_parameters())); +} + +// input must be at least rank 2. +TEST_CASE(torch_kit_instance_norm_low_rank_op_builder_test) +{ + const auto f = migraphx::shape::float_type; + migraphx::module mm; + mm.add_parameter("x", {f, {4}}); + EXPECT(test::throws( + [&] { make_op_module("tm::instance_norm", {{"epsilon", 1e-5f}}, mm.get_parameters()); })); +} diff --git a/test/op/builder/torch/layer_norm_test.cpp b/test/op/builder/torch/layer_norm_test.cpp new file mode 100644 index 00000000000..1140ff34522 --- /dev/null +++ b/test/op/builder/torch/layer_norm_test.cpp @@ -0,0 +1,55 @@ +/* + * 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 + +// tm::layer_norm == (x - mean) * rsqrt(var + eps) * scale + bias, reduced over `axes`, +// with the affine params broadcast right-aligned against the input. +TEST_CASE(torch_kit_layer_norm_op_builder_test) +{ + const auto f = migraphx::shape::float_type; + const float eps = 1e-5f; + std::vector axes = {-1}; + + migraphx::module mm; + auto x = mm.add_parameter("x", {f, {2, 3, 4}}); + auto scale = mm.add_parameter("scale", {f, {4}}); + auto bias = mm.add_parameter("bias", {f, {4}}); + auto mean = mm.add_instruction(migraphx::make_op("reduce_mean", {{"axes", axes}}), x); + auto x_sub = add_common_op(mm, migraphx::make_op("sub"), {x, mean}); + auto sqdiff = add_common_op(mm, migraphx::make_op("sqdiff"), {x, mean}); + auto variance = mm.add_instruction(migraphx::make_op("reduce_mean", {{"axes", axes}}), sqdiff); + auto eps_lit = mm.add_literal(migraphx::literal{migraphx::shape{f}, {eps}}); + auto var_eps = add_common_op(mm, migraphx::make_op("add"), {variance, eps_lit}); + auto rsqrt = mm.add_instruction(migraphx::make_op("rsqrt"), var_eps); + auto norm = add_common_op(mm, migraphx::make_op("mul"), {x_sub, rsqrt}); + auto scaled = add_common_op(mm, migraphx::make_op("mul"), {norm, scale}); + add_common_op(mm, migraphx::make_op("add"), {scaled, bias}); + + EXPECT(mm == make_op_module( + "tm::layer_norm", {{"epsilon", eps}, {"axes", axes}}, mm.get_parameters())); +} diff --git a/test/op/builder/torch/linear_test.cpp b/test/op/builder/torch/linear_test.cpp new file mode 100644 index 00000000000..a5b6c8e5714 --- /dev/null +++ b/test/op/builder/torch/linear_test.cpp @@ -0,0 +1,53 @@ +/* + * 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 + +// ND linear flattens to rank 2, delegates to gemm, then reshapes back. +TEST_CASE(torch_kit_linear_op_builder_test) +{ + const auto f = migraphx::shape::float_type; + migraphx::module mm; + auto x = mm.add_parameter("x", {f, {2, 3, 4}}); + auto w = mm.add_parameter("w", {f, {5, 4}}); + auto bias = mm.add_parameter("bias", {f, {5}}); + auto x2d = mm.add_instruction(migraphx::make_op("reshape", {{"dims", {6, 4}}}), x); + auto out = migraphx::op::builder::add("gemm", mm, {x2d, w, bias}, {{"transB", true}}).front(); + mm.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 3, 5}}}), out); + + EXPECT(mm == make_op_module("tm::linear", mm.get_parameters())); +} + +// rank-2 linear is exactly the gemm builder. +TEST_CASE(torch_kit_linear_no_bias_op_builder_test) +{ + const auto f = migraphx::shape::float_type; + migraphx::module mm; + auto x = mm.add_parameter("x", {f, {3, 4}}); + auto w = mm.add_parameter("w", {f, {5, 4}}); + migraphx::op::builder::add("gemm", mm, {x, w}, {{"transB", true}}); + + EXPECT(mm == make_op_module("tm::linear", mm.get_parameters())); +} diff --git a/test/op/builder/torch/lstm_test.cpp b/test/op/builder/torch/lstm_test.cpp new file mode 100644 index 00000000000..b247fedb73c --- /dev/null +++ b/test/op/builder/torch/lstm_test.cpp @@ -0,0 +1,113 @@ +/* + * 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 + +// The tm::lstm builder expands into an lstm op plus the rnn_last_hs_output and +// rnn_last_cell_output ops. + +TEST_CASE(torch_kit_lstm_forward_op_builder_test) +{ + const std::size_t hidden_size = 2; + + migraphx::module mm; + auto x = mm.add_parameter("x", {migraphx::shape::float_type, {3, 4, 5}}); + auto w = mm.add_parameter("w", {migraphx::shape::float_type, {1, 8, 5}}); + auto r = mm.add_parameter("r", {migraphx::shape::float_type, {1, 8, 2}}); + + // A forward lstm defaults to the {sigmoid, tanh, tanh} activation set. + std::vector actv_funcs{ + migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")}; + + auto hs = mm.add_instruction( + migraphx::make_op( + "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(actv_funcs)}}), + x, + w, + r); + mm.add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); + mm.add_instruction(migraphx::make_op("rnn_last_cell_output"), hs); + + EXPECT(mm == make_op_module("tm::lstm", {{"hidden_size", hidden_size}}, mm.get_parameters())); +} + +TEST_CASE(torch_kit_lstm_bidirectional_op_builder_test) +{ + const std::size_t hidden_size = 2; + + migraphx::module mm; + auto x = mm.add_parameter("x", {migraphx::shape::float_type, {3, 4, 5}}); + auto w = mm.add_parameter("w", {migraphx::shape::float_type, {2, 8, 5}}); + auto r = mm.add_parameter("r", {migraphx::shape::float_type, {2, 8, 2}}); + + // A bidirectional lstm needs the activation set duplicated (6 functions). + std::vector actv_funcs{migraphx::make_op("sigmoid"), + migraphx::make_op("tanh"), + migraphx::make_op("tanh"), + migraphx::make_op("sigmoid"), + migraphx::make_op("tanh"), + migraphx::make_op("tanh")}; + + auto hs = mm.add_instruction( + migraphx::make_op("lstm", + {{"hidden_size", hidden_size}, + {"actv_func", migraphx::to_value(actv_funcs)}, + {"direction", migraphx::op::rnn_direction::bidirectional}}), + x, + w, + r); + mm.add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); + mm.add_instruction(migraphx::make_op("rnn_last_cell_output"), hs); + + migraphx::value options{{"hidden_size", hidden_size}, + {"direction", migraphx::op::rnn_direction::bidirectional}}; + EXPECT(mm == make_op_module("tm::lstm", options, mm.get_parameters())); +} + +TEST_CASE(torch_kit_lstm_custom_actv_funcs_op_builder_test) +{ + const std::size_t hidden_size = 2; + + // Explicitly provided activation functions should be used as-is and not be + // overridden with the defaults. + std::vector actv_funcs{ + migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("sigmoid")}; + migraphx::value options{{"hidden_size", hidden_size}, + {"actv_func", migraphx::to_value(actv_funcs)}}; + + migraphx::module mm; + auto x = mm.add_parameter("x", {migraphx::shape::float_type, {3, 4, 5}}); + auto w = mm.add_parameter("w", {migraphx::shape::float_type, {1, 8, 5}}); + auto r = mm.add_parameter("r", {migraphx::shape::float_type, {1, 8, 2}}); + auto hs = mm.add_instruction(migraphx::make_op("lstm", options), x, w, r); + mm.add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); + mm.add_instruction(migraphx::make_op("rnn_last_cell_output"), hs); + + EXPECT(mm == make_op_module("tm::lstm", options, mm.get_parameters())); +} diff --git a/test/op/builder/torch/nan_to_num_test.cpp b/test/op/builder/torch/nan_to_num_test.cpp new file mode 100644 index 00000000000..b635ffa27de --- /dev/null +++ b/test/op/builder/torch/nan_to_num_test.cpp @@ -0,0 +1,57 @@ +/* + * 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 + +// tm::nan_to_num replaces NaN with `nan`, +inf with `posinf`, -inf with `neginf`; +// the inf sign is recovered by comparing the input against 0. where broadcasts its +// operands but does not promote the boolean condition. +TEST_CASE(torch_kit_nan_to_num_op_builder_test) +{ + const auto f = migraphx::shape::float_type; + migraphx::value options{{"nan", 0.0f}, {"posinf", 1e4f}, {"neginf", -1e4f}}; + + migraphx::module mm; + auto x = mm.add_parameter("x", {f, {2, 3}}); + auto nan_lit = mm.add_literal(migraphx::literal{migraphx::shape{f}, {0.0f}}); + auto zero = mm.add_literal(migraphx::literal{migraphx::shape{f}, {0.0f}}); + auto posinf_lit = mm.add_literal(migraphx::literal{migraphx::shape{f}, {1e4f}}); + auto neginf_lit = mm.add_literal(migraphx::literal{migraphx::shape{f}, {-1e4f}}); + + auto is_nan = mm.add_instruction(migraphx::make_op("isnan"), x); + auto result = + add_common_op(mm, migraphx::make_op("where"), {is_nan, nan_lit, x}, {.common_type = false}); + auto is_inf = mm.add_instruction(migraphx::make_op("isinf"), x); + auto less = add_common_op(mm, migraphx::make_op("less"), {x, zero}); + auto greater = add_common_op(mm, migraphx::make_op("greater"), {x, zero}); + auto neg_mask = add_common_op(mm, migraphx::make_op("logical_and"), {less, is_inf}); + auto pos_mask = add_common_op(mm, migraphx::make_op("logical_and"), {greater, is_inf}); + result = add_common_op( + mm, migraphx::make_op("where"), {neg_mask, neginf_lit, result}, {.common_type = false}); + add_common_op( + mm, migraphx::make_op("where"), {pos_mask, posinf_lit, result}, {.common_type = false}); + + EXPECT(mm == make_op_module("tm::nan_to_num", options, mm.get_parameters())); +} diff --git a/test/op/builder/torch/scatter_reduce_test.cpp b/test/op/builder/torch/scatter_reduce_test.cpp new file mode 100644 index 00000000000..171d116ab37 --- /dev/null +++ b/test/op/builder/torch/scatter_reduce_test.cpp @@ -0,0 +1,87 @@ +/* + * 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 + +// tm::scatter_reduce maps the torch reduction onto the matching scatter op. When +// include_self is false the target positions are first overwritten with the +// reduction identity (via scatter_none) so they drop out of the reduction. +static void check_scatter_reduce(const std::string& reduce, + const std::string& scatter_op, + float identity, + bool include_self) +{ + const auto f = migraphx::shape::float_type; + const auto i = migraphx::shape::int32_type; + + migraphx::module mm; + auto inp = mm.add_parameter("inp", {f, {4, 4}}); + auto idx = mm.add_parameter("idx", {i, {2, 4}}); + auto src = mm.add_parameter("src", {f, {2, 4}}); + auto data = inp; + if(not include_self) + { + auto id = mm.add_literal(migraphx::literal{migraphx::shape{f, {1}}, {identity}}); + id = mm.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 4}}}), id); + data = mm.add_instruction(migraphx::make_op("scatter_none", {{"axis", 0}}), inp, idx, id); + } + mm.add_instruction(migraphx::make_op(scatter_op, {{"axis", 0}}), data, idx, src); + + migraphx::value options{{"dim", 0}, {"reduce", reduce}, {"include_self", include_self}}; + EXPECT(mm == make_op_module("tm::scatter_reduce", options, mm.get_parameters())); +} + +TEST_CASE(torch_kit_scatter_reduce_sum_include_self) +{ + check_scatter_reduce("sum", "scatter_add", 0.0f, true); +} + +TEST_CASE(torch_kit_scatter_reduce_sum) { check_scatter_reduce("sum", "scatter_add", 0.0f, false); } + +TEST_CASE(torch_kit_scatter_reduce_prod) +{ + check_scatter_reduce("prod", "scatter_mul", 1.0f, false); +} + +TEST_CASE(torch_kit_scatter_reduce_amax) +{ + check_scatter_reduce("amax", "scatter_max", std::numeric_limits::lowest(), false); +} + +TEST_CASE(torch_kit_scatter_reduce_amin) +{ + check_scatter_reduce("amin", "scatter_min", std::numeric_limits::max(), false); +} + +TEST_CASE(torch_kit_scatter_reduce_unsupported_reduce) +{ + EXPECT(test::throws([&] { + make_op_module( + "tm::scatter_reduce", {{"dim", 0}, {"reduce", "bogus"}, {"include_self", true}}, {}); + })); +} diff --git a/test/op/builder/torch/selu_test.cpp b/test/op/builder/torch/selu_test.cpp new file mode 100644 index 00000000000..8b8b1fc2295 --- /dev/null +++ b/test/op/builder/torch/selu_test.cpp @@ -0,0 +1,48 @@ +/* + * 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 + +// tm::selu == gamma * (max(0, x) + min(0, alpha * (exp(x) - 1))) with the SELU +// constants; literals are created in the builder's order so the modules match. +TEST_CASE(torch_kit_selu_op_builder_test) +{ + const auto f = migraphx::shape::float_type; + migraphx::module mm; + auto x = mm.add_parameter("x", {f, {2, 3}}); + auto zero = mm.add_literal(migraphx::literal{migraphx::shape{f}, {0.0f}}); + auto one = mm.add_literal(migraphx::literal{migraphx::shape{f}, {1.0f}}); + auto alpha = mm.add_literal(migraphx::literal{migraphx::shape{f}, {1.6732632423543772f}}); + auto gamma = mm.add_literal(migraphx::literal{migraphx::shape{f}, {1.0507009873554805f}}); + auto linear = add_common_op(mm, migraphx::make_op("max"), {zero, x}); + auto exp_x = mm.add_instruction(migraphx::make_op("exp"), x); + auto exp_sub = add_common_op(mm, migraphx::make_op("sub"), {exp_x, one}); + auto exp_mul = add_common_op(mm, migraphx::make_op("mul"), {alpha, exp_sub}); + auto exp_part = add_common_op(mm, migraphx::make_op("min"), {zero, exp_mul}); + auto sum = add_common_op(mm, migraphx::make_op("add"), {linear, exp_part}); + add_common_op(mm, migraphx::make_op("mul"), {gamma, sum}); + + EXPECT(mm == make_op_module("tm::selu", mm.get_parameters())); +} diff --git a/test/op/builder/torch/slice_scatter_test.cpp b/test/op/builder/torch/slice_scatter_test.cpp new file mode 100644 index 00000000000..3fd4e683943 --- /dev/null +++ b/test/op/builder/torch/slice_scatter_test.cpp @@ -0,0 +1,51 @@ +/* + * 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 + +// tm::slice_scatter scatters src into the [start:end:step] slice along `dim`; the +// scatter indices carry the resolved position of each src element along that dim. +TEST_CASE(torch_kit_slice_scatter_op_builder_test) +{ + const auto f = migraphx::shape::float_type; + + migraphx::module mm; + auto input = mm.add_parameter("input", {f, {4, 3}}); + auto src = mm.add_parameter("src", {f, {2, 3}}); + + std::vector idx_data = {0, 0, 0, 1, 1, 1}; + auto indices = mm.add_literal( + migraphx::literal{migraphx::shape{migraphx::shape::int64_type, {2, 3}}, idx_data}); + auto std_input = mm.add_instruction(migraphx::make_op("contiguous"), input); + auto std_src = mm.add_instruction(migraphx::make_op("contiguous"), src); + mm.add_instruction( + migraphx::make_op("scatter_none", {{"axis", 0}}), std_input, indices, std_src); + + migraphx::value options{{"dim", 0}, {"start", 0}, {"end", 2}, {"step", 1}}; + EXPECT(mm == make_op_module("tm::slice_scatter", options, mm.get_parameters())); +} diff --git a/test/op/builder/torch/softsign_test.cpp b/test/op/builder/torch/softsign_test.cpp new file mode 100644 index 00000000000..f552139d65c --- /dev/null +++ b/test/op/builder/torch/softsign_test.cpp @@ -0,0 +1,40 @@ +/* + * 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 + +// tm::softsign == x / (1 + |x|). +TEST_CASE(torch_kit_softsign_op_builder_test) +{ + const auto f = migraphx::shape::float_type; + migraphx::module mm; + auto x = mm.add_parameter("x", {f, {2, 3}}); + auto one = mm.add_literal(migraphx::literal{migraphx::shape{f}, {1.0f}}); + auto abs_x = mm.add_instruction(migraphx::make_op("abs"), x); + auto denom = add_common_op(mm, migraphx::make_op("add"), {abs_x, one}); + add_common_op(mm, migraphx::make_op("div"), {x, denom}); + + EXPECT(mm == make_op_module("tm::softsign", mm.get_parameters())); +} diff --git a/test/op/builder/torch/std_test.cpp b/test/op/builder/torch/std_test.cpp new file mode 100644 index 00000000000..3edd2c7a4ad --- /dev/null +++ b/test/op/builder/torch/std_test.cpp @@ -0,0 +1,47 @@ +/* + * 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 + +// tm::std == sqrt(sum((x - mean)^2) / (N - correction)) reduced over the axes, +// squeezing them out unless keepdim. Here N == 4 and correction == 1, so N - 1 == 3. +TEST_CASE(torch_kit_std_op_builder_test) +{ + const auto f = migraphx::shape::float_type; + migraphx::value options{{"axes", {1}}, {"keepdim", false}, {"correction", 1.0f}}; + + migraphx::module mm; + auto x = mm.add_parameter("x", {f, {2, 4}}); + auto mean = mm.add_instruction(migraphx::make_op("reduce_mean", {{"axes", {1}}}), x); + auto sub = add_common_op(mm, migraphx::make_op("sub"), {x, mean}); + auto sq = add_common_op(mm, migraphx::make_op("mul"), {sub, sub}); + auto sum = mm.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), sq); + auto denom = mm.add_literal(migraphx::literal{migraphx::shape{f}, {3.0f}}); + auto var = add_common_op(mm, migraphx::make_op("div"), {sum, denom}); + auto out = mm.add_instruction(migraphx::make_op("sqrt"), var); + mm.add_instruction(migraphx::make_op("squeeze", {{"axes", {1}}}), out); + + EXPECT(mm == make_op_module("tm::std", options, mm.get_parameters())); +} diff --git a/test/op/builder/torch/vector_norm_test.cpp b/test/op/builder/torch/vector_norm_test.cpp new file mode 100644 index 00000000000..5d55029997a --- /dev/null +++ b/test/op/builder/torch/vector_norm_test.cpp @@ -0,0 +1,89 @@ +/* + * 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 + +// tm::vector_norm reduces abs(x) over axes with the ord-specific formula, then +// squeezes the reduced axes unless keepdim. General p-norm: sum(abs(x)^ord)^(1/ord). +TEST_CASE(torch_kit_vector_norm_p_op_builder_test) +{ + const auto f = migraphx::shape::float_type; + std::vector axes = {1}; + + migraphx::module mm; + auto x = mm.add_parameter("x", {f, {2, 3}}); + auto abs_x = mm.add_instruction(migraphx::make_op("abs"), x); + auto ord_lit = mm.add_literal(migraphx::literal{migraphx::shape{f}, {2.0f}}); + auto pow_x = add_common_op(mm, migraphx::make_op("pow"), {abs_x, ord_lit}); + auto sum_pow = mm.add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), pow_x); + auto recip = mm.add_instruction(migraphx::make_op("recip"), ord_lit); + auto out = add_common_op(mm, migraphx::make_op("pow"), {sum_pow, recip}); + mm.add_instruction(migraphx::make_op("squeeze", {{"axes", axes}}), out); + + EXPECT(mm == make_op_module("tm::vector_norm", + {{"ord", 2.0f}, {"axes", axes}, {"keepdim", false}}, + mm.get_parameters())); +} + +// ord = +inf -> max(abs(x)); keepdim = true leaves the reduced axis in place. +TEST_CASE(torch_kit_vector_norm_inf_op_builder_test) +{ + const auto f = migraphx::shape::float_type; + std::vector axes = {1}; + + migraphx::module mm; + auto x = mm.add_parameter("x", {f, {2, 3}}); + auto abs_x = mm.add_instruction(migraphx::make_op("abs"), x); + mm.add_instruction(migraphx::make_op("reduce_max", {{"axes", axes}}), abs_x); + + EXPECT(mm == + make_op_module( + "tm::vector_norm", + {{"ord", std::numeric_limits::infinity()}, {"axes", axes}, {"keepdim", true}}, + mm.get_parameters())); +} + +// ord = 0 -> count of nonzero elements: sum(abs(x) > 0). +TEST_CASE(torch_kit_vector_norm_zero_op_builder_test) +{ + const auto f = migraphx::shape::float_type; + std::vector axes = {1}; + + migraphx::module mm; + auto x = mm.add_parameter("x", {f, {2, 3}}); + auto abs_x = mm.add_instruction(migraphx::make_op("abs"), x); + auto zero = mm.add_literal(migraphx::literal{migraphx::shape{f}, {0.0f}}); + auto nonzero = add_common_op(mm, migraphx::make_op("greater"), {abs_x, zero}); + auto counts = mm.add_instruction(migraphx::make_op("convert", {{"target_type", f}}), nonzero); + auto out = mm.add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), counts); + mm.add_instruction(migraphx::make_op("squeeze", {{"axes", axes}}), out); + + EXPECT(mm == make_op_module("tm::vector_norm", + {{"ord", 0.0f}, {"axes", axes}, {"keepdim", false}}, + mm.get_parameters())); +} From 3f80d98d8a89ebdb76da409fc61a4a6290f00256 Mon Sep 17 00:00:00 2001 From: Shiv Date: Fri, 24 Jul 2026 09:05:49 -0700 Subject: [PATCH 36/42] cppcheck --- src/include/migraphx/op/eval_expr.hpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/include/migraphx/op/eval_expr.hpp b/src/include/migraphx/op/eval_expr.hpp index bfe91ffbea0..fdc4426e8a2 100644 --- a/src/include/migraphx/op/eval_expr.hpp +++ b/src/include/migraphx/op/eval_expr.hpp @@ -90,14 +90,14 @@ struct eval_expr for(const auto& expression : expressions) collect_variables(expression, required); auto available = direct_variables(inputs.front()); - for(const auto& variable : required) - { - if(std::none_of(available.begin(), available.end(), [&](const auto& v) { - return sym::same_symbol(v, variable); - })) - MIGRAPHX_THROW("EVAL_EXPR: Symbol '" + variable.to_string() + - "' is not a direct input dimension"); - } + auto missing = std::find_if(required.begin(), required.end(), [&](const auto& variable) { + return std::none_of(available.begin(), available.end(), [&](const auto& v) { + return sym::same_symbol(v, variable); + }); + }); + if(missing != required.end()) + MIGRAPHX_THROW("EVAL_EXPR: Symbol '" + missing->to_string() + + "' is not a direct input dimension"); return shape{shape::int64_type, {expressions.size()}}; } From c488cd124e05000ecc3d1c7b5c5dccf0d3ae3b70 Mon Sep 17 00:00:00 2001 From: jomohamm Date: Sat, 25 Jul 2026 00:23:29 +0530 Subject: [PATCH 37/42] Enable hipBLASLt GEMM for gfx115x (#5082) Adds gfx115 to hipblaslt_supported_impl() so gfx1150/1151/1152/1153 (Strix Halo/Point, RDNA3.5) use the hipBLASLt GEMM path. --- src/targets/gpu/device_name.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/targets/gpu/device_name.cpp b/src/targets/gpu/device_name.cpp index 2b825ea062f..70aa0f40a36 100644 --- a/src/targets/gpu/device_name.cpp +++ b/src/targets/gpu/device_name.cpp @@ -141,7 +141,8 @@ static bool hipblaslt_supported_impl(const std::string& gfx_name) { return (gfx_name == "gfx90a" or (starts_with(gfx_name, "gfx94") and gfx_name >= "gfx942") or (starts_with(gfx_name, "gfx95") and gfx_name >= "gfx950") or - starts_with(gfx_name, "gfx110") or starts_with(gfx_name, "gfx120")); + starts_with(gfx_name, "gfx110") or starts_with(gfx_name, "gfx115") or + starts_with(gfx_name, "gfx120")); } #endif From 0c17c0acbcd865115076749d07bee11571a2bb6f Mon Sep 17 00:00:00 2001 From: Paul Fultz II Date: Fri, 24 Jul 2026 15:39:19 -0500 Subject: [PATCH 38/42] Fix bug in reshape_dims when taking a static shape (#5093) --- src/reshape_dims.cpp | 58 ++++-- test/reshape_dims_test.cpp | 395 +++++++++++++++++++++++++++++++++++++ 2 files changed, 432 insertions(+), 21 deletions(-) create mode 100644 test/reshape_dims_test.cpp diff --git a/src/reshape_dims.cpp b/src/reshape_dims.cpp index 41d24103878..461e996496a 100644 --- a/src/reshape_dims.cpp +++ b/src/reshape_dims.cpp @@ -176,27 +176,15 @@ optional reshape_dims(const shape& input, return reshape_dims(input, sym_rdims, options); } -optional -reshape_dims(const shape& input, const std::vector& rdims, reshape_dims_options options) +// Walk the input dims and the requested dims together, squeezing a run of input axes whose +// product is a requested dim and unsqueezing an input axis into a run of requested dims, deriving +// the stride of each requested dim as it goes. nullopt when the layout can't be proven. +static optional> +compute_reshape_strides(const std::vector& idims, + const std::vector& istrides, + const std::vector& rdims, + reshape_dims_options options) { - const std::vector rdds(rdims.begin(), rdims.end()); - - if(input.standard()) - return shape{input.type(), rdds}; - - // Broadcasts have ambiguous permutations (multiple axes share stride 0), so - // for non-lazy reshape fall back to a standard layout. Sliced (non-packed) - // inputs still propagate the permutation via the algorithm + with_lens below. - if(not options.lazy and input.broadcasted()) - return shape{input.type(), rdds}; - - std::vector idims(input.dyn_dims().size()); - std::transform(input.dyn_dims().begin(), - input.dyn_dims().end(), - idims.begin(), - [](const auto& dd) { return dd.sym_expr; }); - const auto& istrides = input.dyn_strides(); - std::vector rstrides; std::size_t i = 0; std::size_t r = 0; @@ -271,7 +259,35 @@ reshape_dims(const shape& input, const std::vector& rdims, reshape_di if(rdims.size() != rstrides.size()) return nullopt; - auto result = shape{input.type(), rdds, rstrides}; + return rstrides; +} + +optional +reshape_dims(const shape& input, const std::vector& rdims, reshape_dims_options options) +{ + const std::vector rdds(rdims.begin(), rdims.end()); + + if(input.standard()) + return shape{input.type(), rdds}; + + // Broadcasts have ambiguous permutations (multiple axes share stride 0), so + // for non-lazy reshape fall back to a standard layout. Sliced (non-packed) + // inputs still propagate the permutation via the algorithm + with_lens below. + if(not options.lazy and input.broadcasted()) + return shape{input.type(), rdds}; + + // Range-based dynamic dimensions carry no stride expressions to merge or split. + if(input.dynamic() and not input.symbolic()) + return nullopt; + + // Lift a static input to symbolic literals so one algorithm resolves both kinds. + const auto sym_in = input.to_symbolic(); + auto rstrides = + compute_reshape_strides(sym_in.sym_dims(), sym_in.dyn_strides(), rdims, options); + if(not rstrides.has_value()) + return nullopt; + + auto result = shape{input.type(), rdds, *rstrides}; if(options.lazy or result.packed()) return result; // TODO: Add as_packed to shape class diff --git a/test/reshape_dims_test.cpp b/test/reshape_dims_test.cpp new file mode 100644 index 00000000000..ef5d8a5c550 --- /dev/null +++ b/test/reshape_dims_test.cpp @@ -0,0 +1,395 @@ +/* + * 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 + +using dd = migraphx::shape::dynamic_dimension; +using se = migraphx::sym::expr; +using migraphx::sym::lit; +using migraphx::sym::var; + +static const auto ftype = migraphx::shape::float_type; + +// reshape_dims always answers in the symbolic domain, so evaluate the result back to a concrete +// shape for comparison. nullopt means the layout could not be proven. +static migraphx::optional +static_reshape(const migraphx::shape& input, const std::vector& rdims, bool lazy) +{ + auto r = migraphx::reshape_dims(input, rdims, {.lazy = lazy}); + if(not r.has_value()) + return migraphx::nullopt; + return r->to_static(); +} + +static migraphx::optional +sym_reshape(const migraphx::shape& input, + const std::vector& rdims, + bool lazy, + const std::unordered_map& sym_map) +{ + auto r = migraphx::reshape_dims(input, rdims, {.lazy = lazy}); + if(not r.has_value()) + return migraphx::nullopt; + return r->to_static(sym_map); +} + +//////////////////////////////////////////////////////////////////////////////// +// reshape_dims: static inputs +//////////////////////////////////////////////////////////////////////////////// + +TEST_CASE(standard_merge) +{ + migraphx::shape s{ftype, {2, 3, 4}}; + migraphx::shape expected{ftype, {2, 12}}; + EXPECT(static_reshape(s, {2, 12}, true) == expected); + EXPECT(static_reshape(s, {2, 12}, false) == expected); +} + +TEST_CASE(standard_split) +{ + migraphx::shape s{ftype, {2, 12}}; + migraphx::shape expected{ftype, {2, 3, 4}}; + EXPECT(static_reshape(s, {2, 3, 4}, true) == expected); + EXPECT(static_reshape(s, {2, 3, 4}, false) == expected); +} + +TEST_CASE(standard_identity) +{ + migraphx::shape s{ftype, {2, 3, 4}}; + EXPECT(static_reshape(s, {2, 3, 4}, true) == s); +} + +TEST_CASE(standard_flatten) +{ + migraphx::shape s{ftype, {2, 3, 4}}; + migraphx::shape expected{ftype, {24}}; + EXPECT(static_reshape(s, {24}, true) == expected); +} + +// Merging axes that are not adjacent in memory cannot be expressed as a view, so lazy reshape +// declines while a copy-permitting reshape repacks to a standard layout. +TEST_CASE(transposed_unmergeable) +{ + migraphx::shape s{ftype, {2, 3, 4}, {12, 1, 3}}; + migraphx::shape expected{ftype, {2, 12}}; + EXPECT(static_reshape(s, {2, 12}, true) == migraphx::nullopt); + EXPECT(static_reshape(s, {2, 12}, false) == expected); +} + +// The trailing axes of this permutation are adjacent, so the merge holds as a view and the +// permutation carries through to the result. +TEST_CASE(transposed_mergeable) +{ + migraphx::shape s{ftype, {2, 3, 4}, {1, 8, 2}}; + migraphx::shape expected{ftype, {2, 12}, {1, 2}}; + EXPECT(static_reshape(s, {2, 12}, true) == expected); + EXPECT(static_reshape(s, {2, 12}, false) == expected); +} + +// Splitting an axis is the inverse of merging it and recovers the original strides. +TEST_CASE(transposed_split) +{ + migraphx::shape s{ftype, {2, 12}, {1, 2}}; + migraphx::shape expected{ftype, {2, 3, 4}, {1, 8, 2}}; + EXPECT(static_reshape(s, {2, 3, 4}, true) == expected); +} + +// A broadcasted axis keeps its zero stride through a lazy merge of the packed trailing axes. +// Without a view requirement the ambiguous permutation falls back to a standard layout instead. +TEST_CASE(broadcasted) +{ + migraphx::shape s{ftype, {2, 3, 4}, {0, 4, 1}}; + migraphx::shape lazy_expected{ftype, {2, 12}, {0, 1}}; + migraphx::shape copy_expected{ftype, {2, 12}}; + EXPECT(static_reshape(s, {2, 12}, true) == lazy_expected); + EXPECT(static_reshape(s, {2, 12}, false) == copy_expected); +} + +TEST_CASE(broadcasted_scalar) +{ + migraphx::shape s{ftype, {2, 3}, {0, 0}}; + migraphx::shape expected{ftype, {6}, {0}}; + EXPECT(static_reshape(s, {6}, true) == expected); +} + +// A broadcast axis cannot merge into a non-broadcast one, since the result would need two +// different strides for one axis. +TEST_CASE(broadcasted_unmergeable) +{ + migraphx::shape s{ftype, {2, 3}, {0, 1}}; + EXPECT(static_reshape(s, {6}, true) == migraphx::nullopt); +} + +// A sliced shape has gaps between its axes, so merging them loses the gap and needs a copy. +TEST_CASE(nonpacked) +{ + migraphx::shape s{ftype, {2, 2}, {4, 1}}; + migraphx::shape expected{ftype, {4}}; + EXPECT(static_reshape(s, {4}, true) == migraphx::nullopt); + EXPECT(static_reshape(s, {4}, false) == expected); +} + +// Axes of length 1 past the end of the walk inherit the last stride. +TEST_CASE(trailing_ones) +{ + migraphx::shape s{ftype, {2, 3, 4}, {1, 8, 2}}; + migraphx::shape expected{ftype, {2, 12, 1, 1}, {1, 2, 2, 2}}; + EXPECT(static_reshape(s, {2, 12, 1, 1}, true) == expected); +} + +// A trailing axis that is not 1 would change the element count. +TEST_CASE(trailing_non_one) +{ + migraphx::shape s{ftype, {2, 3, 4}, {1, 8, 2}}; + EXPECT(static_reshape(s, {2, 12, 2}, true) == migraphx::nullopt); +} + +// No run of input axes multiplies to 5, so the walk cannot line the two shapes up. +TEST_CASE(mismatched_elements) +{ + migraphx::shape s{ftype, {2, 3, 4}, {12, 1, 3}}; + EXPECT(static_reshape(s, {5, 5}, true) == migraphx::nullopt); + EXPECT(static_reshape(s, {5, 5}, false) == migraphx::nullopt); +} + +// Range-based dynamic dimensions have no stride expressions to reason about, so the layout is +// unprovable rather than an error. +TEST_CASE(range_dynamic) +{ + migraphx::shape s{ftype, {{1, 4}, {3, 3}, {4, 4}}}; + EXPECT(static_reshape(s, {2, 12}, true) == migraphx::nullopt); + EXPECT(static_reshape(s, {2, 12}, false) == migraphx::nullopt); +} + +// A static input and its symbolic lift must resolve through the same path. Static shapes carry no +// dyn_dims()/dyn_strides(), so they have to be lifted internally rather than read as if they were +// already symbolic. +TEST_CASE(static_matches_symbolic_lift) +{ + const std::vector inputs = {{ftype, {2, 3, 4}}, + {ftype, {2, 3, 4}, {12, 1, 3}}, + {ftype, {2, 3, 4}, {1, 8, 2}}, + {ftype, {2, 3, 4}, {0, 4, 1}}, + {ftype, {2, 3, 4}, {24, 8, 2}}}; + const std::vector> targets = { + {2, 12}, {24}, {2, 3, 4}, {6, 4}, {2, 2, 6}}; + for(const auto& s : inputs) + { + for(const auto& target : targets) + { + for(bool lazy : {true, false}) + { + EXPECT(static_reshape(s, target, lazy) == + static_reshape(s.to_symbolic(), target, lazy)); + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// reshape_dims: symbolic inputs +//////////////////////////////////////////////////////////////////////////////// + +TEST_CASE(symbolic_standard) +{ + auto n = var("n", {1, 8}); + std::unordered_map sym_map = {{n, 2}}; + migraphx::shape s{ftype, {dd{n}, dd{lit(3)}, dd{lit(4)}}}; + migraphx::shape expected{ftype, {2, 12}}; + EXPECT(sym_reshape(s, {n, lit(12)}, true, sym_map) == expected); +} + +// A symbolic stride merges the same way a literal one does when the ratio is provable. +TEST_CASE(symbolic_transposed_mergeable) +{ + auto n = var("n", {1, 8}); + std::unordered_map sym_map = {{n, 2}}; + migraphx::shape s{ftype, {dd{n}, dd{lit(3)}, dd{lit(4)}}, {lit(1), n * 4, n}}; + migraphx::shape expected{ftype, {2, 12}, {1, 2}}; + EXPECT(sym_reshape(s, {n, lit(12)}, true, sym_map) == expected); +} + +TEST_CASE(symbolic_broadcasted) +{ + auto n = var("n", {1, 8}); + std::unordered_map sym_map = {{n, 2}}; + migraphx::shape s{ftype, {dd{n}, dd{lit(3)}, dd{lit(4)}}, {lit(0), lit(4), lit(1)}}; + migraphx::shape lazy_expected{ftype, {2, 12}, {0, 1}}; + migraphx::shape copy_expected{ftype, {2, 12}}; + EXPECT(sym_reshape(s, {n, lit(12)}, true, sym_map) == lazy_expected); + EXPECT(sym_reshape(s, {n, lit(12)}, false, sym_map) == copy_expected); +} + +// n ranges over [1, 8], so neither n < 8 nor 8 < n holds for every value and the walk cannot pick +// between squeezing and unsqueezing. +TEST_CASE(symbolic_unprovable_ordering) +{ + auto n = var("n", {1, 8}); + migraphx::shape s{ftype, {dd{n}, dd{lit(4)}}, {lit(1), n}}; + EXPECT(migraphx::reshape_dims(s, {lit(8), lit(4)}, {.lazy = true}) == migraphx::nullopt); +} + +// Merging a literal axis with a symbolic one yields a symbolic output dim. n is bounded below by +// 2 so that 4 < 4n is provable; at n == 1 the ordering would be indeterminate. +TEST_CASE(symbolic_merge_into_symbol) +{ + auto n = var("n", {2, 8}); + std::unordered_map sym_map = {{n, 2}}; + migraphx::shape s{ftype, {dd{lit(3)}, dd{lit(4)}, dd{n}}, {lit(1), n * 3, lit(3)}}; + migraphx::shape expected{ftype, {3, 8}, {1, 3}}; + EXPECT(sym_reshape(s, {lit(3), n * 4}, true, sym_map) == expected); +} + +// Column-major strides make the outer axis the denser one, so merging the two axes is a +// transposed merge that no view can express. A copy repacks to a standard layout. +TEST_CASE(symbolic_transposed_unmergeable) +{ + auto n = var("n", {1, 8}); + std::unordered_map sym_map = {{n, 3}}; + migraphx::shape s{ftype, {dd{n}, dd{lit(4)}}, {lit(1), n}}; + migraphx::shape expected{ftype, {12}}; + EXPECT(sym_reshape(s, {n * 4}, true, sym_map) == migraphx::nullopt); + EXPECT(sym_reshape(s, {n * 4}, false, sym_map) == expected); +} + +// Resolving the symbols first and reshaping the concrete shape must agree with reshaping +// symbolically and resolving afterwards. +TEST_CASE(symbolic_matches_static_eval) +{ + auto n = var("n", {1, 8}); + std::unordered_map sym_map = {{n, 2}}; + const std::vector inputs = { + {ftype, {dd{n}, dd{lit(3)}, dd{lit(4)}}}, + {ftype, {dd{n}, dd{lit(3)}, dd{lit(4)}}, {lit(1), n * 4, n}}, + {ftype, {dd{n}, dd{lit(3)}, dd{lit(4)}}, {lit(0), lit(4), lit(1)}}, + {ftype, {dd{n}, dd{lit(3)}, dd{lit(4)}}, {lit(12), lit(1), lit(3)}}}; + for(const auto& s : inputs) + { + for(bool lazy : {true, false}) + { + auto from_sym = sym_reshape(s, {n, lit(12)}, lazy, sym_map); + auto from_static = static_reshape(s.to_static(sym_map), {2, 12}, lazy); + EXPECT(from_sym == from_static); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// resolve_reshape_dims +//////////////////////////////////////////////////////////////////////////////// + +TEST_CASE(resolve_literals) +{ + migraphx::shape s{ftype, {dd{lit(2)}, dd{lit(3)}, dd{lit(4)}}}; + std::vector
expected = {dd{lit(2)}, dd{lit(12)}}; + EXPECT(migraphx::resolve_reshape_dims(s, {2, 12}) == expected); +} + +// A 0 entry copies the input dim at that index. +TEST_CASE(resolve_zero_copies_input_dim) +{ + migraphx::shape s{ftype, {dd{lit(2)}, dd{lit(3)}, dd{lit(4)}}}; + std::vector
expected = {dd{lit(2)}, dd{lit(12)}}; + EXPECT(migraphx::resolve_reshape_dims(s, {0, 12}) == expected); +} + +TEST_CASE(resolve_zero_copies_symbol) +{ + auto n = var("n", {1, 8}); + migraphx::shape s{ftype, {dd{n}, dd{lit(12)}}}; + std::vector
expected = {dd{n}, dd{lit(12)}}; + EXPECT(migraphx::resolve_reshape_dims(s, {0, 12}) == expected); +} + +// A -1 entry is the leftover element count after the explicit dims. +TEST_CASE(resolve_infers_negative_one) +{ + migraphx::shape s{ftype, {dd{lit(2)}, dd{lit(3)}, dd{lit(4)}}}; + std::vector
expected = {dd{lit(2)}, dd{lit(12)}}; + EXPECT(migraphx::resolve_reshape_dims(s, {2, -1}) == expected); + EXPECT(migraphx::resolve_reshape_dims(s, {-1, 12}) == expected); +} + +TEST_CASE(resolve_infers_negative_one_over_symbol) +{ + auto n = var("n", {1, 8}); + migraphx::shape s{ftype, {dd{n}, dd{lit(3)}, dd{lit(4)}}}; + auto result = migraphx::resolve_reshape_dims(s, {-1, 12}); + EXPECT(result.size() == 2); + EXPECT(result[0] == dd{n}); + EXPECT(result[1] == dd{lit(12)}); +} + +// A symbolic dim entry is taken as-is. +TEST_CASE(resolve_symbolic_entry) +{ + auto n = var("n", {1, 8}); + migraphx::shape s{ftype, {dd{n}, dd{lit(3)}, dd{lit(4)}}}; + std::vector
expected = {dd{n}, dd{lit(12)}}; + EXPECT(migraphx::resolve_reshape_dims(s, {migraphx::dim_like{dd{n}}, 12}) == expected); +} + +TEST_CASE(resolve_rank_change) +{ + migraphx::shape s{ftype, {dd{lit(24)}}}; + std::vector
expected = {dd{lit(2)}, dd{lit(3)}, dd{lit(4)}}; + EXPECT(migraphx::resolve_reshape_dims(s, {2, 3, -1}) == expected); +} + +//////////////////////////////////////////////////////////////////////////////// +// validate_reshape_dims +//////////////////////////////////////////////////////////////////////////////// + +TEST_CASE(validate_accepts_literals) +{ + migraphx::validate_reshape_dims("reshape", {1, 2, 3}); + migraphx::validate_reshape_dims("reshape", {0, 2, -1}); + migraphx::validate_reshape_dims("reshape", {}); +} + +TEST_CASE(validate_accepts_symbolic) +{ + auto n = var("n", {1, 8}); + migraphx::validate_reshape_dims("reshape", {migraphx::dim_like{dd{n}}, 12}); +} + +TEST_CASE(validate_rejects_range_dim) +{ + EXPECT(test::throws( + [&] { migraphx::validate_reshape_dims("reshape", {migraphx::dim_like{dd{1, 4}}, 12}); }, + "dim entries must be int64 or symbolic")); +} + +TEST_CASE(validate_rejects_multiple_inferred_dims) +{ + EXPECT(test::throws( + [&] { migraphx::validate_reshape_dims("reshape", {-1, 2, -1}); }, + "can only have one -1 dim")); +} + +int main(int argc, const char* argv[]) { test::run(argc, argv); } From aff1a33ce2d0dac7694df1a123c564394c51f9f9 Mon Sep 17 00:00:00 2001 From: Shiv Date: Fri, 24 Jul 2026 14:31:53 -0700 Subject: [PATCH 39/42] refactor to update input_shape attr through finalize --- src/CMakeLists.txt | 2 +- src/eliminate_contiguous.cpp | 3 +- src/include/migraphx/dyn_output.hpp | 20 +- ...eval_expr.hpp => eval_expr_from_shape.hpp} | 97 ++-- src/include/migraphx/operation.hpp | 421 ++---------------- src/include/migraphx/sym.hpp | 3 + src/instruction.cpp | 7 +- src/program.cpp | 14 +- src/sym.cpp | 21 + src/targets/cpu/lowering.cpp | 5 + src/targets/ref/lowering.cpp | 28 +- test/instruction.cpp | 33 ++ test/op_shape_test.cpp | 42 +- ...eval_expr.cpp => eval_expr_from_shape.cpp} | 43 +- test/ref/slice.cpp | 4 +- test/sym.cpp | 33 ++ tools/include/operation.hpp | 174 ++------ 17 files changed, 295 insertions(+), 655 deletions(-) rename src/include/migraphx/op/{eval_expr.hpp => eval_expr_from_shape.hpp} (52%) rename test/ref/{eval_expr.cpp => eval_expr_from_shape.cpp} (65%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 16b0260b004..70beedc51a3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -218,7 +218,7 @@ register_migraphx_ops( elu equal erf - eval_expr + eval_expr_from_shape exp fill fixed_pad diff --git a/src/eliminate_contiguous.cpp b/src/eliminate_contiguous.cpp index f8c94753523..04667233b27 100644 --- a/src/eliminate_contiguous.cpp +++ b/src/eliminate_contiguous.cpp @@ -171,8 +171,7 @@ static void remove_contiguous(const std::string& op_name, module& m, F f) shape computed_shape = c.compute_shape({prev->get_shape()}); const std::vector& prev_eval = {prev->eval()}; // prev_eval should not be used in make_compute_output_shape() as computed_shape is static - auto co_shape = make_compute_output_shape( - pack(c, computed_shape, std::vector{prev->get_shape()}, prev_eval)); + auto co_shape = make_compute_output_shape(pack(c, computed_shape, prev_eval)); literals[i] = c.compute(co_shape, prev_eval); }); diff --git a/src/include/migraphx/dyn_output.hpp b/src/include/migraphx/dyn_output.hpp index be71e2e5e52..2ce0ade4217 100644 --- a/src/include/migraphx/dyn_output.hpp +++ b/src/include/migraphx/dyn_output.hpp @@ -27,7 +27,6 @@ #include #include #include -#include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { @@ -38,8 +37,6 @@ struct dyn_output shape ins_shape; // shape computed at eval time using input arguments shape computed_shape; - // original shapes of the instruction inputs - std::vector input_shapes; }; /** @@ -54,25 +51,18 @@ struct compute_output_shape operator dyn_output() const { - return ins_inputs([](const auto& x, - shape ins_shape, - const std::vector& input_shapes, - const std::vector& inputs) { - auto original_inputs = input_shapes.empty() ? to_shapes(inputs) : input_shapes; + return ins_inputs([](const auto& x, shape ins_shape, const std::vector& inputs) { // some op returns a tuple shape e.g. TopK if(ins_shape.any_of_dynamic()) - return dyn_output{ - ins_shape, compute_shape(x, to_shapes(inputs)), std::move(original_inputs)}; - return dyn_output{ins_shape, ins_shape, std::move(original_inputs)}; + return dyn_output{ins_shape, compute_shape(x, to_shapes(inputs))}; + return dyn_output{ins_shape, ins_shape}; }); } operator shape() const { - return ins_inputs([](const auto&, - shape ins_shape, - const std::vector&, - const std::vector&) { return ins_shape; }); + return ins_inputs( + [](const auto&, shape ins_shape, const std::vector&) { return ins_shape; }); } }; diff --git a/src/include/migraphx/op/eval_expr.hpp b/src/include/migraphx/op/eval_expr_from_shape.hpp similarity index 52% rename from src/include/migraphx/op/eval_expr.hpp rename to src/include/migraphx/op/eval_expr_from_shape.hpp index fdc4426e8a2..02c0e31d5e5 100644 --- a/src/include/migraphx/op/eval_expr.hpp +++ b/src/include/migraphx/op/eval_expr_from_shape.hpp @@ -21,98 +21,79 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -#ifndef MIGRAPHX_GUARD_OPERATORS_EVAL_EXPR_HPP -#define MIGRAPHX_GUARD_OPERATORS_EVAL_EXPR_HPP +#ifndef MIGRAPHX_GUARD_OPERATORS_EVAL_EXPR_FROM_SHAPE_HPP +#define MIGRAPHX_GUARD_OPERATORS_EVAL_EXPR_FROM_SHAPE_HPP #include #include #include -#include +#include +#include #include #include -#include #include +#include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { -struct eval_expr +struct eval_expr_from_shape { std::vector expressions{}; + std::vector input_shapes{}; template static auto reflect(Self& self, F f) { - return pack(f(self.expressions, "expressions")); + return pack(f(self.expressions, "expressions"), f(self.input_shapes, "input_shapes")); } - std::string name() const { return "eval_expr"; } + std::string name() const { return "eval_expr_from_shape"; } - static void collect_variables(const sym::expr& e, std::vector& variables) + shape compute_shape(const std::vector& inputs) const { - if(e.name() == "variable") - { - auto variable = sym::as_symbol(e); - if(std::none_of(variables.begin(), variables.end(), [&](const auto& v) { - return sym::same_symbol(v, variable); - })) - variables.push_back(std::move(variable)); - return; - } - for(const auto& child : e.children()) - collect_variables(child, variables); - } + check_shapes{inputs, *this, true}.has_at_least(1); - static std::vector direct_variables(const shape& s) - { - std::vector result; - if(not s.symbolic()) - return result; - for(const auto& d : s.dyn_dims()) + std::unordered_set available; + for(const auto& input : inputs) { - if(d.sym_expr.name() != "variable") + if(not input.symbolic()) continue; - auto variable = sym::as_symbol(d.sym_expr); - if(std::none_of(result.begin(), result.end(), [&](const auto& v) { - return sym::same_symbol(v, variable); - })) - result.push_back(std::move(variable)); + for(const auto& d : input.dyn_dims()) + if(d.sym_expr.name() == "variable") + available.insert(sym::as_symbol(d.sym_expr)); } - return result; + for(const auto& expression : expressions) + for(const auto& variable : sym::find_variables(expression)) + if(available.count(variable) == 0) + MIGRAPHX_THROW("EVAL_EXPR_FROM_SHAPE: Symbol '" + variable.to_string() + + "' is not a direct input dimension"); + + return shape{shape::int64_type, {expressions.size()}}; } - shape compute_shape(const std::vector& inputs) const + void finalize(context&, const shape&, const std::vector& inputs) { - check_shapes{inputs, *this, true}.has(1); - std::vector required; - for(const auto& expression : expressions) - collect_variables(expression, required); - auto available = direct_variables(inputs.front()); - auto missing = std::find_if(required.begin(), required.end(), [&](const auto& variable) { - return std::none_of(available.begin(), available.end(), [&](const auto& v) { - return sym::same_symbol(v, variable); - }); - }); - if(missing != required.end()) - MIGRAPHX_THROW("EVAL_EXPR: Symbol '" + missing->to_string() + - "' is not a direct input dimension"); - return shape{shape::int64_type, {expressions.size()}}; + input_shapes = inputs; } - argument compute(const dyn_output& dyn_out, std::vector args) const + argument compute(const shape&, std::vector args) const { - assert(args.size() == 1); - assert(dyn_out.input_shapes.size() == 1); - const auto& input_shape = dyn_out.input_shapes.front(); - auto lens = args.front().get_shape().lens(); - if(input_shape.ndim() != lens.size()) - MIGRAPHX_THROW("EVAL_EXPR: Runtime input rank does not match its symbolic shape"); + if(input_shapes.empty() or input_shapes.size() != args.size()) + MIGRAPHX_THROW("EVAL_EXPR_FROM_SHAPE: input shapes not captured; op was not finalized"); std::unordered_map values; - if(input_shape.symbolic()) + for(std::size_t i = 0; i < input_shapes.size(); ++i) { + const auto& input_shape = input_shapes[i]; + auto lens = args[i].get_shape().lens(); + if(input_shape.ndim() != lens.size()) + MIGRAPHX_THROW("EVAL_EXPR_FROM_SHAPE: Runtime input rank does not match its " + "symbolic shape"); + if(not input_shape.symbolic()) + continue; const auto& dims = input_shape.dyn_dims(); for(std::size_t axis = 0; axis < dims.size(); ++axis) { @@ -121,8 +102,8 @@ struct eval_expr auto variable = sym::as_symbol(dims[axis].sym_expr); auto result = values.emplace(variable, lens[axis]); if(not result.second and result.first->second != lens[axis]) - MIGRAPHX_THROW( - "EVAL_EXPR: Repeated symbol has inconsistent runtime dimensions"); + MIGRAPHX_THROW("EVAL_EXPR_FROM_SHAPE: Repeated symbol has inconsistent runtime " + "dimensions"); } } diff --git a/src/include/migraphx/operation.hpp b/src/include/migraphx/operation.hpp index fd2d4483699..395c0942dd4 100644 --- a/src/include/migraphx/operation.hpp +++ b/src/include/migraphx/operation.hpp @@ -79,10 +79,6 @@ struct operation * the same the `output` shape. */ argument compute(context& ctx, const shape& output, const std::vector& input) const; - argument compute(context& ctx, - const shape& output, - const std::vector& input_shapes, - const std::vector& input) const; /// An optional method to return which arguments the output will alias. If /// there is no aliased output then an empty vector can be returned. std::vector output_alias(const std::vector& input) const; @@ -206,130 +202,86 @@ auto compute_op(rank<1>, const T& x, context& ctx, const shape& output_shape, - const std::vector& input_shapes, const std::vector& input) -> decltype(x.compute(auto_any_cast(ctx), - make_compute_output_shape(pack(x, output_shape, input_shapes, input)), + make_compute_output_shape(pack(x, output_shape, input)), input)) { - return x.compute(auto_any_cast(ctx), - make_compute_output_shape(pack(x, output_shape, input_shapes, input)), - input); + return x.compute( + auto_any_cast(ctx), make_compute_output_shape(pack(x, output_shape, input)), input); } template -argument compute_op(rank<0>, - const T& x, - context&, - const shape&, - const std::vector&, - const std::vector&) +argument compute_op(rank<0>, const T& x, context&, const shape&, const std::vector&) { std::string name = x.name(); MIGRAPHX_THROW("Not computable: " + name); } -template -argument compute_op(const T& x, - context& ctx, - const shape& output_shape, - const std::vector& input_shapes, - const std::vector& input) -{ - return compute_op(rank<1>{}, x, ctx, output_shape, input_shapes, input); -} - template argument compute_op(const T& x, context& ctx, const shape& output_shape, const std::vector& input) { - return compute_op(x, ctx, output_shape, std::vector{}, input); + return compute_op(rank<1>{}, x, ctx, output_shape, input); } template -auto compute_op(rank<1>, - const T& x, - const shape& output_shape, - const std::vector& input_shapes, - const std::vector& input) - -> decltype(x.compute(make_compute_output_shape(pack(x, output_shape, input_shapes, input)), - input)) +auto compute_op(rank<1>, const T& x, const shape& output_shape, const std::vector& input) + -> decltype(x.compute(make_compute_output_shape(pack(x, output_shape, input)), input)) { - return x.compute(make_compute_output_shape(pack(x, output_shape, input_shapes, input)), input); + return x.compute(make_compute_output_shape(pack(x, output_shape, input)), input); } template -argument compute_op( - rank<0>, const T& x, const shape&, const std::vector&, const std::vector&) +argument compute_op(rank<0>, const T& x, const shape&, const std::vector&) { std::string name = x.name(); MIGRAPHX_THROW("Not computable: " + name); } -template -argument compute_op(const T& x, - const shape& output_shape, - const std::vector& input_shapes, - const std::vector& input) -{ - return compute_op(rank<1>{}, x, output_shape, input_shapes, input); -} - template argument compute_op(const T& x, const shape& output_shape, const std::vector& input) { - return compute_op(x, output_shape, std::vector{}, input); + return compute_op(rank<1>{}, x, output_shape, input); } template auto compute_op(rank<1>, const T& x, const shape& output, - const std::vector& input_shapes, const std::vector& inputs, const std::vector& module_args, - const F& f) - -> decltype(x.compute( - make_compute_output_shape(pack(x, output, input_shapes, inputs)), inputs, module_args, f)) + F f) -> decltype(x.compute(make_compute_output_shape(pack(x, output, inputs)), + inputs, + module_args, + std::move(f))) { return x.compute( - make_compute_output_shape(pack(x, output, input_shapes, inputs)), inputs, module_args, f); + make_compute_output_shape(pack(x, output, inputs)), inputs, module_args, std::move(f)); } template argument compute_op(rank<0>, const T& x, const shape& output, - const std::vector& input_shapes, const std::vector& inputs, const std::vector& module_args, - const F&) + F) // NOLINT { if(module_args.empty()) - return compute_op(x, output, input_shapes, inputs); + return compute_op(x, output, inputs); std::string name = x.name(); MIGRAPHX_THROW("Not computable: " + name); } -template -argument compute_op(const T& x, - const shape& output, - const std::vector& input_shapes, - const std::vector& inputs, - const std::vector& module_args, - const F& f) -{ - return compute_op(rank<1>{}, x, output, input_shapes, inputs, module_args, f); -} - template argument compute_op(const T& x, const shape& output, const std::vector& inputs, const std::vector& module_args, - const F& f) + F f) { - return compute_op(x, output, std::vector{}, inputs, module_args, f); + return compute_op(rank<1>{}, x, output, inputs, module_args, std::move(f)); } template @@ -337,18 +289,17 @@ auto compute_op(rank<4>, const T& x, context& ctx, const shape& output, - const std::vector& input_shapes, const std::vector& inputs, const std::vector& module_args, F f) // NOLINT -> decltype(x.compute(auto_any_cast(ctx), - make_compute_output_shape(pack(x, output, input_shapes, inputs)), + make_compute_output_shape(pack(x, output, inputs)), inputs, module_args, std::move(f))) { return x.compute(auto_any_cast(ctx), - make_compute_output_shape(pack(x, output, input_shapes, inputs)), + make_compute_output_shape(pack(x, output, inputs)), inputs, module_args, std::move(f)); @@ -359,19 +310,14 @@ auto compute_op(rank<3>, const T& x, context&, const shape& output, - const std::vector& input_shapes, const std::vector& inputs, const std::vector& module_args, F f) // NOLINT - -> decltype(x.compute(make_compute_output_shape(pack(x, output, input_shapes, inputs)), - inputs, - module_args, - std::move(f))) + -> decltype(x.compute( + make_compute_output_shape(pack(x, output, inputs)), inputs, module_args, std::move(f))) { - return x.compute(make_compute_output_shape(pack(x, output, input_shapes, inputs)), - inputs, - module_args, - std::move(f)); + return x.compute( + make_compute_output_shape(pack(x, output, inputs)), inputs, module_args, std::move(f)); } template @@ -379,13 +325,12 @@ auto compute_op(rank<2>, const T& x, context&, const shape& output, - const std::vector& input_shapes, const std::vector& inputs, const std::vector&, F) // NOLINT - -> decltype(x.compute(make_compute_output_shape(pack(x, output, input_shapes, inputs)), inputs)) + -> decltype(x.compute(make_compute_output_shape(pack(x, output, inputs)), inputs)) { - return x.compute(make_compute_output_shape(pack(x, output, input_shapes, inputs)), inputs); + return x.compute(make_compute_output_shape(pack(x, output, inputs)), inputs); } template @@ -393,17 +338,15 @@ auto compute_op(rank<1>, const T& x, context& ctx, const shape& output, - const std::vector& input_shapes, const std::vector& inputs, const std::vector&, F) // NOLINT -> decltype(x.compute(auto_any_cast(ctx), - make_compute_output_shape(pack(x, output, input_shapes, inputs)), + make_compute_output_shape(pack(x, output, inputs)), inputs)) { - return x.compute(auto_any_cast(ctx), - make_compute_output_shape(pack(x, output, input_shapes, inputs)), - inputs); + return x.compute( + auto_any_cast(ctx), make_compute_output_shape(pack(x, output, inputs)), inputs); } template @@ -411,7 +354,6 @@ argument compute_op(rank<0>, const T& x, context&, const shape&, - const std::vector&, const std::vector&, const std::vector&, F) // NOLINT @@ -424,23 +366,11 @@ template argument compute_op(const T& x, context& ctx, const shape& output, - const std::vector& input_shapes, const std::vector& inputs, const std::vector& module_args, F f) { - return compute_op(rank<4>{}, x, ctx, output, input_shapes, inputs, module_args, std::move(f)); -} - -template -argument compute_op(const T& x, - context& ctx, - const shape& output, - const std::vector& inputs, - const std::vector& module_args, - F f) -{ - return compute_op(x, ctx, output, std::vector{}, inputs, module_args, std::move(f)); + return compute_op(rank<4>{}, x, ctx, output, inputs, module_args, std::move(f)); } template @@ -448,9 +378,7 @@ auto is_context_free_op(rank<1>, const T& x, const shape& output_shape, const std::vector& input) - -> decltype(x.compute( - make_compute_output_shape(pack(x, output_shape, std::vector{}, input)), - input), + -> decltype(x.compute(make_compute_output_shape(pack(x, output_shape, input)), input), std::true_type{}); template @@ -602,25 +530,9 @@ struct MIGRAPHX_EXPORT operation // (optional) argument compute(context& ctx, const shape& output, const std::vector& input) const; // (optional) - argument compute(context& ctx, - const shape& output, - const std::vector& input_shapes, - const std::vector& input) const; - // (optional) argument compute(const shape& output, const std::vector& input) const; // (optional) argument compute(const shape& output, - const std::vector& input_shapes, - const std::vector& input) const; - // (optional) - argument compute(const shape& output, - const std::vector& input, - const std::vector& module_args, - std::function( - module_ref&, const std::unordered_map&)> run) const; - // (optional) - argument compute(const shape& output, - const std::vector& input_shapes, const std::vector& input, const std::vector& module_args, std::function( @@ -633,14 +545,6 @@ struct MIGRAPHX_EXPORT operation std::function( module_ref&, const std::unordered_map&)> run) const; // (optional) - argument compute(context& ctx, - const shape& output, - const std::vector& input_shapes, - const std::vector& input, - const std::vector& module_args, - std::function( - module_ref&, const std::unordered_map&)> run) const; - // (optional) value to_value() const; // (optional) void from_value(const value& v); @@ -824,29 +728,6 @@ struct operation return detail::compute_op(private_detail_te_self, ctx, output, input); } - template - static auto private_detail_te_default_compute(char, - T&& private_detail_te_self, - context& ctx, - const shape& output, - const std::vector& input_shapes, - const std::vector& input) - -> decltype(private_detail_te_self.compute(ctx, output, input_shapes, input)) - { - return private_detail_te_self.compute(ctx, output, input_shapes, input); - } - - template - static argument private_detail_te_default_compute(float, - T&& private_detail_te_self, - context& ctx, - const shape& output, - const std::vector& input_shapes, - const std::vector& input) - { - return detail::compute_op(private_detail_te_self, ctx, output, input_shapes, input); - } - template static auto private_detail_te_default_compute(char, T&& private_detail_te_self, @@ -866,27 +747,6 @@ struct operation return detail::compute_op(private_detail_te_self, output, input); } - template - static auto private_detail_te_default_compute(char, - T&& private_detail_te_self, - const shape& output, - const std::vector& input_shapes, - const std::vector& input) - -> decltype(private_detail_te_self.compute(output, input_shapes, input)) - { - return private_detail_te_self.compute(output, input_shapes, input); - } - - template - static argument private_detail_te_default_compute(float, - T&& private_detail_te_self, - const shape& output, - const std::vector& input_shapes, - const std::vector& input) - { - return detail::compute_op(private_detail_te_self, output, input_shapes, input); - } - template static auto private_detail_te_default_compute( char, @@ -915,38 +775,6 @@ struct operation private_detail_te_self, output, input, module_args, std::move(run)); } - template - static auto private_detail_te_default_compute( - char, - T&& private_detail_te_self, - const shape& output, - const std::vector& input_shapes, - const std::vector& input, - const std::vector& module_args, - std::function(module_ref&, - const std::unordered_map&)> run) - -> decltype(private_detail_te_self.compute( - output, input_shapes, input, module_args, std::move(run))) - { - return private_detail_te_self.compute( - output, input_shapes, input, module_args, std::move(run)); - } - - template - static argument private_detail_te_default_compute( - float, - T&& private_detail_te_self, - const shape& output, - const std::vector& input_shapes, - const std::vector& input, - const std::vector& module_args, - std::function(module_ref&, - const std::unordered_map&)> run) - { - return detail::compute_op( - private_detail_te_self, output, input_shapes, input, module_args, std::move(run)); - } - template static auto private_detail_te_default_compute( char, @@ -977,40 +805,6 @@ struct operation private_detail_te_self, ctx, output, input, module_args, std::move(run)); } - template - static auto private_detail_te_default_compute( - char, - T&& private_detail_te_self, - context& ctx, - const shape& output, - const std::vector& input_shapes, - const std::vector& input, - const std::vector& module_args, - std::function(module_ref&, - const std::unordered_map&)> run) - -> decltype(private_detail_te_self.compute( - ctx, output, input_shapes, input, module_args, std::move(run))) - { - return private_detail_te_self.compute( - ctx, output, input_shapes, input, module_args, std::move(run)); - } - - template - static argument private_detail_te_default_compute( - float, - T&& private_detail_te_self, - context& ctx, - const shape& output, - const std::vector& input_shapes, - const std::vector& input, - const std::vector& module_args, - std::function(module_ref&, - const std::unordered_map&)> run) - { - return detail::compute_op( - private_detail_te_self, ctx, output, input_shapes, input, module_args, std::move(run)); - } - template static auto private_detail_te_default_to_value(char, T&& private_detail_te_self) -> decltype(private_detail_te_self.to_value()) @@ -1103,21 +897,10 @@ struct operation std::declval(), std::declval(), std::declval&>()), - private_detail_te_default_compute(char(0), - std::declval(), - std::declval(), - std::declval(), - std::declval&>(), - std::declval&>()), private_detail_te_default_compute(char(0), std::declval(), std::declval(), std::declval&>()), - private_detail_te_default_compute(char(0), - std::declval(), - std::declval(), - std::declval&>(), - std::declval&>()), private_detail_te_default_compute( char(0), std::declval(), @@ -1126,15 +909,6 @@ struct operation std::declval&>(), std::declval( module_ref&, const std::unordered_map&)>>()), - private_detail_te_default_compute( - char(0), - std::declval(), - std::declval(), - std::declval&>(), - std::declval&>(), - std::declval&>(), - std::declval( - module_ref&, const std::unordered_map&)>>()), private_detail_te_default_compute( char(0), std::declval(), @@ -1144,16 +918,6 @@ struct operation std::declval&>(), std::declval( module_ref&, const std::unordered_map&)>>()), - private_detail_te_default_compute( - char(0), - std::declval(), - std::declval(), - std::declval(), - std::declval&>(), - std::declval&>(), - std::declval&>(), - std::declval( - module_ref&, const std::unordered_map&)>>()), private_detail_te_default_to_value(char(0), std::declval()), private_detail_te_default_from_value(char(0), @@ -1302,29 +1066,12 @@ struct operation return (*this).private_detail_te_get_handle().compute(ctx, output, input); } - argument compute(context& ctx, - const shape& output, - const std::vector& input_shapes, - const std::vector& input) const - { - assert((*this).private_detail_te_handle_mem_var); - return (*this).private_detail_te_get_handle().compute(ctx, output, input_shapes, input); - } - argument compute(const shape& output, const std::vector& input) const { assert((*this).private_detail_te_handle_mem_var); return (*this).private_detail_te_get_handle().compute(output, input); } - argument compute(const shape& output, - const std::vector& input_shapes, - const std::vector& input) const - { - assert((*this).private_detail_te_handle_mem_var); - return (*this).private_detail_te_get_handle().compute(output, input_shapes, input); - } - argument compute(const shape& output, const std::vector& input, const std::vector& module_args, @@ -1336,18 +1083,6 @@ struct operation output, input, module_args, std::move(run)); } - argument compute(const shape& output, - const std::vector& input_shapes, - const std::vector& input, - const std::vector& module_args, - std::function( - module_ref&, const std::unordered_map&)> run) const - { - assert((*this).private_detail_te_handle_mem_var); - return (*this).private_detail_te_get_handle().compute( - output, input_shapes, input, module_args, std::move(run)); - } - argument compute(context& ctx, const shape& output, const std::vector& input, @@ -1360,19 +1095,6 @@ struct operation ctx, output, input, module_args, std::move(run)); } - argument compute(context& ctx, - const shape& output, - const std::vector& input_shapes, - const std::vector& input, - const std::vector& module_args, - std::function( - module_ref&, const std::unordered_map&)> run) const - { - assert((*this).private_detail_te_handle_mem_var); - return (*this).private_detail_te_get_handle().compute( - ctx, output, input_shapes, input, module_args, std::move(run)); - } - value to_value() const { assert((*this).private_detail_te_handle_mem_var); @@ -1431,14 +1153,7 @@ struct operation const std::vector& mod_args) const = 0; virtual argument compute(context& ctx, const shape& output, const std::vector& input) const = 0; - virtual argument compute(context& ctx, - const shape& output, - const std::vector& input_shapes, - const std::vector& input) const = 0; virtual argument compute(const shape& output, const std::vector& input) const = 0; - virtual argument compute(const shape& output, - const std::vector& input_shapes, - const std::vector& input) const = 0; virtual argument compute(const shape& output, const std::vector& input, @@ -1446,27 +1161,12 @@ struct operation std::function( module_ref&, const std::unordered_map&)> run) const = 0; virtual argument - compute(const shape& output, - const std::vector& input_shapes, - const std::vector& input, - const std::vector& module_args, - std::function( - module_ref&, const std::unordered_map&)> run) const = 0; - virtual argument compute(context& ctx, const shape& output, const std::vector& input, const std::vector& module_args, std::function( module_ref&, const std::unordered_map&)> run) const = 0; - virtual argument - compute(context& ctx, - const shape& output, - const std::vector& input_shapes, - const std::vector& input, - const std::vector& module_args, - std::function( - module_ref&, const std::unordered_map&)> run) const = 0; virtual value to_value() const = 0; virtual void from_value(const value& v) = 0; virtual value attributes() const = 0; @@ -1570,16 +1270,6 @@ struct operation char(0), private_detail_te_value, ctx, output, input); } - argument compute(context& ctx, - const shape& output, - const std::vector& input_shapes, - const std::vector& input) const override - { - - return private_detail_te_default_compute( - char(0), private_detail_te_value, ctx, output, input_shapes, input); - } - argument compute(const shape& output, const std::vector& input) const override { @@ -1587,15 +1277,6 @@ struct operation char(0), private_detail_te_value, output, input); } - argument compute(const shape& output, - const std::vector& input_shapes, - const std::vector& input) const override - { - - return private_detail_te_default_compute( - char(0), private_detail_te_value, output, input_shapes, input); - } - argument compute( const shape& output, const std::vector& input, @@ -1608,24 +1289,6 @@ struct operation char(0), private_detail_te_value, output, input, module_args, std::move(run)); } - argument compute( - const shape& output, - const std::vector& input_shapes, - const std::vector& input, - const std::vector& module_args, - std::function( - module_ref&, const std::unordered_map&)> run) const override - { - - return private_detail_te_default_compute(char(0), - private_detail_te_value, - output, - input_shapes, - input, - module_args, - std::move(run)); - } - argument compute( context& ctx, const shape& output, @@ -1639,26 +1302,6 @@ struct operation char(0), private_detail_te_value, ctx, output, input, module_args, std::move(run)); } - argument compute( - context& ctx, - const shape& output, - const std::vector& input_shapes, - const std::vector& input, - const std::vector& module_args, - std::function( - module_ref&, const std::unordered_map&)> run) const override - { - - return private_detail_te_default_compute(char(0), - private_detail_te_value, - ctx, - output, - input_shapes, - input, - module_args, - std::move(run)); - } - value to_value() const override { diff --git a/src/include/migraphx/sym.hpp b/src/include/migraphx/sym.hpp index dd487e53949..2da0266e076 100644 --- a/src/include/migraphx/sym.hpp +++ b/src/include/migraphx/sym.hpp @@ -254,6 +254,9 @@ MIGRAPHX_EXPORT expr var(std::string name, interval constraint, std::set MIGRAPHX_EXPORT expr as_symbol(const expr& e, int max_depth = -1); MIGRAPHX_EXPORT bool same_symbol(const expr& a, const expr& b); +// Find distinct variables as metadata-free symbols in first-encounter order. +MIGRAPHX_EXPORT std::vector find_variables(const expr& e); + MIGRAPHX_EXPORT expr arg(expr x); template {})> diff --git a/src/instruction.cpp b/src/instruction.cpp index 04d13e68555..5c651221725 100644 --- a/src/instruction.cpp +++ b/src/instruction.cpp @@ -379,6 +379,8 @@ bool instruction::can_eval() const return true; if(not is_context_free(op)) return false; + if(has_finalize(op)) + return false; #if MIGRAPHX_HAS_PMR std::array storage; std::pmr::monotonic_buffer_resource resource{storage.data(), storage.size()}; @@ -393,7 +395,7 @@ bool instruction::can_eval() const bool evaluable = false; if(ins.name() == "@literal") evaluable = true; - else if(is_context_free(ins.get_operator())) + else if(is_context_free(ins.get_operator()) and not has_finalize(ins.get_operator())) evaluable = std::all_of( ins.inputs().begin(), ins.inputs().end(), [&](auto arg) { return self(*arg); }); cache.emplace(&ins, evaluable); @@ -429,8 +431,7 @@ argument instruction::eval(bool check_eval) const ins.inputs().end(), std::back_inserter(args), [&](auto arg) { return self(*arg); }); - auto value = - ins.normalized_operator().compute(ins.get_shape(), to_shapes(ins.inputs()), args); + auto value = ins.normalized_operator().compute(ins.get_shape(), args); cache.emplace(&ins, value); return value; })(*this); diff --git a/src/program.cpp b/src/program.cpp index 0f865a16c90..5fc1dc3ec75 100644 --- a/src/program.cpp +++ b/src/program.cpp @@ -580,19 +580,13 @@ static std::vector generic_eval(const module* mod, results.insert_or_assign( ins, trace(ins, [&] { - auto op = ins->normalized_operator(); - auto input_shapes = to_shapes(ins->inputs()); + auto op = ins->normalized_operator(); if(op.is_context_free()) - return op.compute( - ins->get_shape(), input_shapes, values, mod_args, module_eval); + return op.compute(ins->get_shape(), values, mod_args, module_eval); if(ins->get_target_id() >= ctx.size()) MIGRAPHX_THROW("No context available for " + op.name()); - return op.compute(ctx[ins->get_target_id()], - ins->get_shape(), - input_shapes, - values, - mod_args, - module_eval); + return op.compute( + ctx[ins->get_target_id()], ins->get_shape(), values, mod_args, module_eval); })); } assert(results.find(ins) != results.end()); diff --git a/src/sym.cpp b/src/sym.cpp index 3f84e41d393..56b61ee6f1a 100644 --- a/src/sym.cpp +++ b/src/sym.cpp @@ -1623,6 +1623,27 @@ bool same_symbol(const expr& a, const expr& b) }); } +std::vector find_variables(const expr& e) +{ + std::vector result; + std::unordered_set visited; + std::unordered_set seen_variables; + fix([&](auto self, const expr& x) { + if(x.empty() or not visited.insert(x).second) + return; + if(x.name() == "variable") + { + auto s = as_symbol(x); + if(seen_variables.insert(s).second) + result.push_back(std::move(s)); + return; + } + for(const auto& c : x.children()) + self(c); + })(e); + return result; +} + // Number of levels in e: a leaf (literal/variable) is depth 1, empty is 0. static int expr_depth(const expr& e) { diff --git a/src/targets/cpu/lowering.cpp b/src/targets/cpu/lowering.cpp index fdea6202a4b..bc135a0f54c 100644 --- a/src/targets/cpu/lowering.cpp +++ b/src/targets/cpu/lowering.cpp @@ -146,6 +146,11 @@ struct cpu_op { return op.compute(output_shape, args); } + void + finalize(migraphx::context& ctx, const shape& output_shape, const std::vector& inputs) + { + op.finalize(ctx, output_shape, inputs); + } value to_value() const { value v; diff --git a/src/targets/ref/lowering.cpp b/src/targets/ref/lowering.cpp index cb976709755..686a9ec77f3 100644 --- a/src/targets/ref/lowering.cpp +++ b/src/targets/ref/lowering.cpp @@ -25,7 +25,6 @@ #include #include #include -#include #include #include #include @@ -184,6 +183,11 @@ struct ref_op { return op.compute(output_shape, args); } + void + finalize(migraphx::context& ctx, const shape& output_shape, const std::vector& inputs) + { + op.finalize(ctx, output_shape, inputs); + } value to_value() const { value v; @@ -203,27 +207,6 @@ struct ref_op }; MIGRAPHX_REGISTER_OP(ref_op) -struct ref_eval_expr -{ - op::eval_expr op; - - template - static auto reflect(Self& self, F f) - { - return migraphx::reflect(self.op, f); - } - - std::string name() const { return "ref::eval_expr"; } - - shape compute_shape(const std::vector& inputs) const { return op.compute_shape(inputs); } - - argument compute(context&, const dyn_output& dyn_out, std::vector args) const - { - return op.compute(dyn_out, std::move(args)); - } -}; -MIGRAPHX_REGISTER_OP(ref_eval_expr) - struct ref_quant_gemm { op::quant_dot op; @@ -407,7 +390,6 @@ struct ref_apply void init() { - apply_map["eval_expr"] = extend_op(); apply_map["quant_dot"] = extend_op(); apply_map["im2col"] = extend_op(); apply_map["logsoftmax"] = extend_op, op::logsoftmax>(); diff --git a/test/instruction.cpp b/test/instruction.cpp index 6ff8dc35e66..a31031b01fa 100644 --- a/test/instruction.cpp +++ b/test/instruction.cpp @@ -28,6 +28,39 @@ #include "test.hpp" #include "rob.hpp" +struct can_eval_finalize_passthrough +{ + std::string name() const { return "can_eval_finalize_passthrough"; } + + migraphx::shape compute_shape(const std::vector& inputs) const + { + return inputs.at(0); + } + + migraphx::argument compute(const migraphx::shape&, + const std::vector& args) const + { + return args.at(0); + } + + void finalize(migraphx::context&, const migraphx::shape&, const std::vector&) + { + } +}; + +TEST_CASE(can_eval_rejects_finalize_op) +{ + migraphx::module m; + auto one = m.add_literal(1); + auto evaluable = m.add_instruction(migraphx::make_op("identity"), one); + auto finalized = m.add_instruction(can_eval_finalize_passthrough{}, one); + auto dependent = m.add_instruction(migraphx::make_op("identity"), finalized); + + EXPECT(evaluable->can_eval()); + EXPECT(not finalized->can_eval()); + EXPECT(not dependent->can_eval()); +} + TEST_CASE(check_undefined) { migraphx::module m; diff --git a/test/op_shape_test.cpp b/test/op_shape_test.cpp index b9cf19823f2..da274643273 100644 --- a/test/op_shape_test.cpp +++ b/test/op_shape_test.cpp @@ -5295,14 +5295,14 @@ TEST_CASE(slice_dyn_nonfixed_keeps_other_optimals) input); } -TEST_CASE(eval_expr_shape) +TEST_CASE(eval_expr_from_shape_shape) { auto n = var("n", {1, 16}); auto h = var("h", {1, 32}); auto w = var("w", {1, 32}); migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(3)}, dd{h}, dd{w}}}; expect_shape(migraphx::shape{migraphx::shape::int64_type, {3}}, - migraphx::make_op("eval_expr", + migraphx::make_op("eval_expr_from_shape", {{"expressions", migraphx::value::array{migraphx::to_value(n), migraphx::to_value(h / lit(2)), @@ -5310,14 +5310,44 @@ TEST_CASE(eval_expr_shape) input); } -TEST_CASE(eval_expr_missing_symbol) +TEST_CASE(eval_expr_from_shape_missing_symbol) { auto m = var("m", {1, 16}); auto n = var("n", {1, 16}); migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(3)}}}; - throws_shape(migraphx::make_op( - "eval_expr", {{"expressions", migraphx::value::array{migraphx::to_value(m)}}}), - input); + throws_shape( + migraphx::make_op("eval_expr_from_shape", + {{"expressions", migraphx::value::array{migraphx::to_value(m)}}}), + input); +} + +TEST_CASE(eval_expr_from_shape_multi_input) +{ + auto m = var("m", {1, 16}); + auto n = var("n", {1, 16}); + migraphx::shape a{migraphx::shape::float_type, {dd{m}, dd{lit(3)}}}; + migraphx::shape b{migraphx::shape::float_type, {dd{lit(2)}, dd{n}}}; + expect_shape(migraphx::shape{migraphx::shape::int64_type, {2}}, + migraphx::make_op( + "eval_expr_from_shape", + {{"expressions", + migraphx::value::array{migraphx::to_value(m + n), migraphx::to_value(m)}}}), + a, + b); +} + +TEST_CASE(eval_expr_from_shape_missing_symbol_multi_input) +{ + auto m = var("m", {1, 16}); + auto n = var("n", {1, 16}); + auto k = var("k", {1, 16}); + migraphx::shape a{migraphx::shape::float_type, {dd{m}, dd{lit(3)}}}; + migraphx::shape b{migraphx::shape::float_type, {dd{lit(2)}, dd{n}}}; + throws_shape( + migraphx::make_op("eval_expr_from_shape", + {{"expressions", migraphx::value::array{migraphx::to_value(m + k)}}}), + a, + b); } TEST_CASE(slice_sym) diff --git a/test/ref/eval_expr.cpp b/test/ref/eval_expr_from_shape.cpp similarity index 65% rename from test/ref/eval_expr.cpp rename to test/ref/eval_expr_from_shape.cpp index 66ab538001a..d4c48660851 100644 --- a/test/ref/eval_expr.cpp +++ b/test/ref/eval_expr_from_shape.cpp @@ -29,7 +29,7 @@ #include -TEST_CASE(eval_expr_input_shape) +TEST_CASE(eval_expr_from_shape_input) { using dd = migraphx::shape::dynamic_dimension; auto n = migraphx::sym::var("N", {1, 16}); @@ -41,7 +41,7 @@ TEST_CASE(eval_expr_input_shape) auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {dd{n}, dd{migraphx::sym::lit(3)}, dd{h}, dd{w}}}); - mm->add_instruction(migraphx::make_op("eval_expr", + mm->add_instruction(migraphx::make_op("eval_expr_from_shape", {{"expressions", migraphx::value::array{ migraphx::to_value(n), @@ -60,7 +60,7 @@ TEST_CASE(eval_expr_input_shape) EXPECT(values == std::vector{7, 5, 6}); } -TEST_CASE(eval_expr_multi_symbol) +TEST_CASE(eval_expr_from_shape_multi_symbol) { using dd = migraphx::shape::dynamic_dimension; auto m = migraphx::sym::var("M", {1, 16}); @@ -70,7 +70,7 @@ TEST_CASE(eval_expr_multi_symbol) auto* mm = p.get_main_module(); auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {dd{m}, dd{n}}}); mm->add_instruction( - migraphx::make_op("eval_expr", + migraphx::make_op("eval_expr_from_shape", {{"expressions", migraphx::value::array{migraphx::to_value(m + n)}}}), x); p.compile(migraphx::make_target("ref")); @@ -82,3 +82,38 @@ TEST_CASE(eval_expr_multi_symbol) EXPECT(result.get_shape() == migraphx::shape{migraphx::shape::int64_type, {1}}); EXPECT(result.at() == 7); } + +TEST_CASE(eval_expr_from_shape_cross_input) +{ + using dd = migraphx::shape::dynamic_dimension; + auto m = migraphx::sym::var("M", {1, 16}); + auto n = migraphx::sym::var("N", {1, 16}); + + migraphx::program p; + auto* mm = p.get_main_module(); + auto a = mm->add_parameter( + "a", migraphx::shape{migraphx::shape::float_type, {dd{m}, dd{migraphx::sym::lit(3)}}}); + auto b = mm->add_parameter( + "b", migraphx::shape{migraphx::shape::float_type, {dd{migraphx::sym::lit(2)}, dd{n}}}); + mm->add_instruction(migraphx::make_op("eval_expr_from_shape", + {{"expressions", + migraphx::value::array{migraphx::to_value(m + n), + migraphx::to_value(m), + migraphx::to_value(n)}}}), + a, + b); + p.compile(migraphx::make_target("ref")); + + migraphx::shape a_shape{migraphx::shape::float_type, {5, 3}}; + migraphx::shape b_shape{migraphx::shape::float_type, {2, 7}}; + std::vector a_data(a_shape.elements()); + std::vector b_data(b_shape.elements()); + auto result = p.eval({{"a", migraphx::argument{a_shape, a_data.data()}}, + {"b", migraphx::argument{b_shape, b_data.data()}}}) + .back(); + + std::vector values; + result.visit([&](auto output) { values.assign(output.begin(), output.end()); }); + EXPECT(result.get_shape() == migraphx::shape{migraphx::shape::int64_type, {3}}); + EXPECT(values == std::vector{12, 5, 7}); +} diff --git a/test/ref/slice.cpp b/test/ref/slice.cpp index de953f435e9..be4e9763a39 100644 --- a/test/ref/slice.cpp +++ b/test/ref/slice.cpp @@ -412,7 +412,7 @@ TEST_CASE(slice_dyn_test1) EXPECT(result.get_shape() == sresult); } -TEST_CASE(slice_eval_expr_input) +TEST_CASE(slice_eval_expr_from_shape_input) { using dd = migraphx::shape::dynamic_dimension; auto n = migraphx::sym::var("n", {1, 3}); @@ -425,7 +425,7 @@ TEST_CASE(slice_eval_expr_input) auto end_vals = mm->add_instruction( migraphx::make_op( - "eval_expr", + "eval_expr_from_shape", {{"expressions", migraphx::value::array{migraphx::to_value(n - migraphx::sym::lit(1))}}}), x); diff --git a/test/sym.cpp b/test/sym.cpp index e435b093ab8..9e4dc1d2fd0 100644 --- a/test/sym.cpp +++ b/test/sym.cpp @@ -831,6 +831,39 @@ TEST_CASE(expr_variable_constraint_equality) EXPECT(migraphx::sym::same_symbol(var("x"), var("x", c))); } +TEST_CASE(find_variables_collects_distinct) +{ + auto x = var("x"); + auto y = var("y"); + auto pair = call("find_variables_collects_distinct", [](auto a, auto b) { return a + b; }); + auto vars = migraphx::sym::find_variables(pair(x, pair(y, x))); + EXPECT(vars == std::vector{x, y}); +} + +TEST_CASE(find_variables_constant_is_empty) +{ + EXPECT(migraphx::sym::find_variables(lit(5)).empty()); + EXPECT(migraphx::sym::find_variables(lit(2) + lit(3)).empty()); + EXPECT(migraphx::sym::find_variables(expr{}).empty()); +} + +TEST_CASE(find_variables_strips_metadata) +{ + auto c = interval{int64_t{1}, int64_t{16}}; + auto pair = call("find_variables_strips_metadata", [](auto a, auto b) { return a + b; }); + auto vars = migraphx::sym::find_variables(pair(var("x", c), var("x"))); + EXPECT(vars == std::vector{var("x")}); +} + +TEST_CASE(find_variables_shared_subexpression) +{ + auto pair = call("find_variables_shared_subexpression", [](auto a, auto b) { return a + b; }); + auto e = pair(var("x"), lit(1)); + for(int i = 0; i < 20; ++i) + e = pair(e, e); + EXPECT(migraphx::sym::find_variables(e) == std::vector{var("x")}); +} + TEST_CASE(expr_equal_compound) { auto x = var("x"); diff --git a/tools/include/operation.hpp b/tools/include/operation.hpp index 90d7bc09686..a2e73c79940 100644 --- a/tools/include/operation.hpp +++ b/tools/include/operation.hpp @@ -79,10 +79,6 @@ struct operation * the same the `output` shape. */ argument compute(context& ctx, const shape& output, const std::vector& input) const; - argument compute(context& ctx, - const shape& output, - const std::vector& input_shapes, - const std::vector& input) const; /// An optional method to return which arguments the output will alias. If /// there is no aliased output then an empty vector can be returned. std::vector output_alias(const std::vector& input) const; @@ -208,107 +204,74 @@ auto compute_op(rank<1>, const T& x, context& ctx, const shape& output_shape, - const std::vector& input_shapes, const std::vector& input) -> decltype(x.compute(auto_any_cast(ctx), - make_compute_output_shape(pack(x, output_shape, input_shapes, input)), + make_compute_output_shape(pack(x, output_shape, input)), input)) { - return x.compute(auto_any_cast(ctx), - make_compute_output_shape(pack(x, output_shape, input_shapes, input)), - input); + return x.compute( + auto_any_cast(ctx), make_compute_output_shape(pack(x, output_shape, input)), input); } template -argument compute_op(rank<0>, - const T& x, - context&, - const shape&, - const std::vector&, - const std::vector&) +argument compute_op(rank<0>, const T& x, context&, const shape&, const std::vector&) { std::string name = x.name(); MIGRAPHX_THROW("Not computable: " + name); } -template -argument compute_op(const T& x, - context& ctx, - const shape& output_shape, - const std::vector& input_shapes, - const std::vector& input) -{ - return compute_op(rank<1>{}, x, ctx, output_shape, input_shapes, input); -} - template argument compute_op(const T& x, context& ctx, const shape& output_shape, const std::vector& input) { - return compute_op(x, ctx, output_shape, std::vector{}, input); + return compute_op(rank<1>{}, x, ctx, output_shape, input); } template -auto compute_op(rank<1>, - const T& x, - const shape& output_shape, - const std::vector& input_shapes, - const std::vector& input) - -> decltype(x.compute(make_compute_output_shape(pack(x, output_shape, input_shapes, input)), - input)) +auto compute_op(rank<1>, const T& x, const shape& output_shape, const std::vector& input) + -> decltype(x.compute(make_compute_output_shape(pack(x, output_shape, input)), input)) { - return x.compute(make_compute_output_shape(pack(x, output_shape, input_shapes, input)), input); + return x.compute(make_compute_output_shape(pack(x, output_shape, input)), input); } template -argument compute_op( - rank<0>, const T& x, const shape&, const std::vector&, const std::vector&) +argument compute_op(rank<0>, const T& x, const shape&, const std::vector&) { std::string name = x.name(); MIGRAPHX_THROW("Not computable: " + name); } -template -argument compute_op(const T& x, - const shape& output_shape, - const std::vector& input_shapes, - const std::vector& input) -{ - return compute_op(rank<1>{}, x, output_shape, input_shapes, input); -} - template argument compute_op(const T& x, const shape& output_shape, const std::vector& input) { - return compute_op(x, output_shape, std::vector{}, input); + return compute_op(rank<1>{}, x, output_shape, input); } template auto compute_op(rank<1>, const T& x, const shape& output, - const std::vector& input_shapes, const std::vector& inputs, const std::vector& module_args, - const F& f) - -> decltype(x.compute( - make_compute_output_shape(pack(x, output, input_shapes, inputs)), inputs, module_args, f)) + F f) -> decltype(x.compute(make_compute_output_shape(pack(x, output, inputs)), + inputs, + module_args, + std::move(f))) { return x.compute( - make_compute_output_shape(pack(x, output, input_shapes, inputs)), inputs, module_args, f); + make_compute_output_shape(pack(x, output, inputs)), inputs, module_args, std::move(f)); } template argument compute_op(rank<0>, const T& x, const shape& output, - const std::vector& input_shapes, const std::vector& inputs, const std::vector& module_args, - const F&) + F) // NOLINT { if(module_args.empty()) - return compute_op(x, output, input_shapes, inputs); + return compute_op(x, output, inputs); std::string name = x.name(); MIGRAPHX_THROW("Not computable: " + name); } @@ -316,22 +279,11 @@ argument compute_op(rank<0>, template argument compute_op(const T& x, const shape& output, - const std::vector& input_shapes, const std::vector& inputs, const std::vector& module_args, - const F& f) -{ - return compute_op(rank<1>{}, x, output, input_shapes, inputs, module_args, f); -} - -template -argument compute_op(const T& x, - const shape& output, - const std::vector& inputs, - const std::vector& module_args, - const F& f) + F f) { - return compute_op(x, output, std::vector{}, inputs, module_args, f); + return compute_op(rank<1>{}, x, output, inputs, module_args, std::move(f)); } template @@ -339,18 +291,17 @@ auto compute_op(rank<4>, const T& x, context& ctx, const shape& output, - const std::vector& input_shapes, const std::vector& inputs, const std::vector& module_args, F f) // NOLINT -> decltype(x.compute(auto_any_cast(ctx), - make_compute_output_shape(pack(x, output, input_shapes, inputs)), + make_compute_output_shape(pack(x, output, inputs)), inputs, module_args, std::move(f))) { return x.compute(auto_any_cast(ctx), - make_compute_output_shape(pack(x, output, input_shapes, inputs)), + make_compute_output_shape(pack(x, output, inputs)), inputs, module_args, std::move(f)); @@ -361,19 +312,14 @@ auto compute_op(rank<3>, const T& x, context&, const shape& output, - const std::vector& input_shapes, const std::vector& inputs, const std::vector& module_args, F f) // NOLINT - -> decltype(x.compute(make_compute_output_shape(pack(x, output, input_shapes, inputs)), - inputs, - module_args, - std::move(f))) + -> decltype(x.compute( + make_compute_output_shape(pack(x, output, inputs)), inputs, module_args, std::move(f))) { - return x.compute(make_compute_output_shape(pack(x, output, input_shapes, inputs)), - inputs, - module_args, - std::move(f)); + return x.compute( + make_compute_output_shape(pack(x, output, inputs)), inputs, module_args, std::move(f)); } template @@ -381,13 +327,12 @@ auto compute_op(rank<2>, const T& x, context&, const shape& output, - const std::vector& input_shapes, const std::vector& inputs, const std::vector&, F) // NOLINT - -> decltype(x.compute(make_compute_output_shape(pack(x, output, input_shapes, inputs)), inputs)) + -> decltype(x.compute(make_compute_output_shape(pack(x, output, inputs)), inputs)) { - return x.compute(make_compute_output_shape(pack(x, output, input_shapes, inputs)), inputs); + return x.compute(make_compute_output_shape(pack(x, output, inputs)), inputs); } template @@ -395,17 +340,15 @@ auto compute_op(rank<1>, const T& x, context& ctx, const shape& output, - const std::vector& input_shapes, const std::vector& inputs, const std::vector&, F) // NOLINT -> decltype(x.compute(auto_any_cast(ctx), - make_compute_output_shape(pack(x, output, input_shapes, inputs)), + make_compute_output_shape(pack(x, output, inputs)), inputs)) { - return x.compute(auto_any_cast(ctx), - make_compute_output_shape(pack(x, output, input_shapes, inputs)), - inputs); + return x.compute( + auto_any_cast(ctx), make_compute_output_shape(pack(x, output, inputs)), inputs); } template @@ -413,7 +356,6 @@ argument compute_op(rank<0>, const T& x, context&, const shape&, - const std::vector&, const std::vector&, const std::vector&, F) // NOLINT @@ -422,18 +364,6 @@ argument compute_op(rank<0>, MIGRAPHX_THROW("Not computable: " + name); } -template -argument compute_op(const T& x, - context& ctx, - const shape& output, - const std::vector& input_shapes, - const std::vector& inputs, - const std::vector& module_args, - F f) -{ - return compute_op(rank<4>{}, x, ctx, output, input_shapes, inputs, module_args, std::move(f)); -} - template argument compute_op(const T& x, context& ctx, @@ -442,7 +372,7 @@ argument compute_op(const T& x, const std::vector& module_args, F f) { - return compute_op(x, ctx, output, std::vector{}, inputs, module_args, std::move(f)); + return compute_op(rank<4>{}, x, ctx, output, inputs, module_args, std::move(f)); } template @@ -450,9 +380,7 @@ auto is_context_free_op(rank<1>, const T& x, const shape& output_shape, const std::vector& input) - -> decltype(x.compute( - make_compute_output_shape(pack(x, output_shape, std::vector{}, input)), - input), + -> decltype(x.compute(make_compute_output_shape(pack(x, output_shape, input)), input), std::true_type{}); template @@ -628,27 +556,12 @@ lifetime get_lifetime_op(const T&) input = 'const std::vector&', const = True, default = 'detail::compute_op'), - virtual('compute', - returns = 'argument', - ctx = 'context&', - output = 'const shape&', - input_shapes = 'const std::vector&', - input = 'const std::vector&', - const = True, - default = 'detail::compute_op'), virtual('compute', returns = 'argument', output = 'const shape&', input = 'const std::vector&', const = True, default = 'detail::compute_op'), - virtual('compute', - returns = 'argument', - output = 'const shape&', - input_shapes = 'const std::vector&', - input = 'const std::vector&', - const = True, - default = 'detail::compute_op'), virtual( 'compute', returns = 'argument', @@ -659,17 +572,6 @@ lifetime get_lifetime_op(const T&) 'std::function(module_ref&, const std::unordered_map&)>', const = True, default = 'detail::compute_op'), - virtual( - 'compute', - returns = 'argument', - output = 'const shape&', - input_shapes = 'const std::vector&', - input = 'const std::vector&', - module_args = 'const std::vector&', - run = - 'std::function(module_ref&, const std::unordered_map&)>', - const = True, - default = 'detail::compute_op'), virtual( 'compute', returns = 'argument', @@ -681,18 +583,6 @@ lifetime get_lifetime_op(const T&) 'std::function(module_ref&, const std::unordered_map&)>', const = True, default = 'detail::compute_op'), - virtual( - 'compute', - returns = 'argument', - ctx = 'context&', - output = 'const shape&', - input_shapes = 'const std::vector&', - input = 'const std::vector&', - module_args = 'const std::vector&', - run = - 'std::function(module_ref&, const std::unordered_map&)>', - const = True, - default = 'detail::compute_op'), virtual('to_value', returns = 'value', const = True, default = 'detail::to_value_op'), virtual('from_value', v = 'const value&', default = 'detail::from_value_op'), virtual('attributes', returns = 'value', const = True, default = 'detail::attributes_op'), From 49f842b387482e0df49b1f7b1b2c3a67856fd9a2 Mon Sep 17 00:00:00 2001 From: Breanna Devore-McDonald Date: Fri, 24 Jul 2026 19:06:38 -0400 Subject: [PATCH 40/42] [AIMIGRAPHX-1209] optimize kernel 2 for non kv cache flash decoding and update tests (#5090) Rewrites the flash decoding kernel 2 recombination step in find_flash_decoding to use the exp-normalize form: `O = sum(O' * exp(LSE - max)) / sum(exp(LSE - max))` instead of normalizing weights first, then scaling and summing partial outputs. The result is mathematically equivalent but produces IR that fuses more cleanly downstream (e.g. with rewrite_broadcast in a follow-up PR). --- CHANGELOG.md | 1 + src/fuse_attention.cpp | 56 ++++++++++++++-------------- test/fuse_attention.cpp | 81 ++++++++++++++++++----------------------- 3 files changed, 63 insertions(+), 75 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dcbc8fadaa0..ff0f4574e2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,6 +82,7 @@ Full documentation for MIGraphX is available at * Fixed `slice_concat_gather` matcher and interaction between same table and cross table gather fusions(#5038). ### Optimized +* Optimized flash decoding recombination in `fuse_attention` to use the exp-normalize form (#5090). * Reduced tuning time by scaling the per-candidate benchmark bundle to the candidate's op count (#4989). * Enabled tensor vectorization for GPU fused `argmin` and `argmax` (`gpu::arg_reduce`) (#4790). * Replaced Hillis-Steele scan algorithm with a wave-based hierarchical scan, reducing work complexity from O(N log N) to O(N) and synchronization from O(log N) to 2 `__syncthreads()` calls (#4720). diff --git a/src/fuse_attention.cpp b/src/fuse_attention.cpp index 470799c307d..b1ca9e7939b 100644 --- a/src/fuse_attention.cpp +++ b/src/fuse_attention.cpp @@ -755,51 +755,49 @@ struct find_flash_decoding auto lse = mm.insert_instruction( attn_group_ins, make_op("get_tuple_elem", {{"index", 1}}), new_group_ins); - // kernel 2 - // the partial outputs O'[g] are already weighted by their group's softmax, - // LSE[g] contains log(sum(exp(S[g]))) for each group - // To combine: weight by exp(LSE[g]) / sum_g(exp(LSE[g'])) - - // compute global max for numerical stability + // kernel 2: combine using exp-normalize trick + // O = sum(O' * exp(LSE - max)) / sum(exp(LSE - max)) + // find max LSE across groups for numerical stability auto lse_max = mm.insert_instruction(attn_group_ins, make_op("reduce_max", {{"axes", {g_axis}}}), lse); + auto lse_max_bcast = mm.insert_instruction( attn_group_ins, make_op("multibroadcast", {{"out_lens", lse->get_shape().lens()}}), lse_max); - // exp(LSE - max_LSE) + // compute unnormalized weights + // exp(LSE - max) auto lse_sub = mm.insert_instruction(attn_group_ins, make_op("sub"), lse, lse_max_bcast); + auto lse_exp = mm.insert_instruction(attn_group_ins, make_op("exp"), lse_sub); - // sum across groups - auto lse_sum = mm.insert_instruction( - attn_group_ins, make_op("reduce_sum", {{"axes", {g_axis}}}), lse_exp); - auto lse_sum_bcast = mm.insert_instruction( + // broadcast weights to match O' shape + // [B, G, M] -> [B, G, M, D] + auto lse_exp_bcast = mm.insert_instruction( attn_group_ins, - make_op("multibroadcast", {{"out_lens", lse_exp->get_shape().lens()}}), - lse_sum); + make_op("multibroadcast", {{"out_lens", partial_output_o_prime->get_shape().lens()}}), + lse_exp); - // scale factor: exp(LSE[g] - max_LSE) / sum(exp(LSE - max_LSE)) - auto scale = mm.insert_instruction(attn_group_ins, make_op("div"), lse_exp, lse_sum_bcast); + // convert weights to output type + auto output_type = partial_output_o_prime->get_shape().type(); + auto weights = mm.insert_instruction( + attn_group_ins, make_op("convert", {{"target_type", output_type}}), lse_exp_bcast); - auto scale_bcast = mm.insert_instruction( - attn_group_ins, - make_op("multibroadcast", {{"out_lens", partial_output_o_prime->get_shape().lens()}}), - scale); + // compute weighted sum: numerator = sum(O' * weights) + auto weighted_o = + mm.insert_instruction(attn_group_ins, make_op("mul"), partial_output_o_prime, weights); - // convert scale to match the type of partial_output_o_prime - auto output_type = partial_output_o_prime->get_shape().type(); - auto scale_converted = mm.insert_instruction( - attn_group_ins, make_op("convert", {{"target_type", output_type}}), scale_bcast); + auto numerator = mm.insert_instruction( + attn_group_ins, make_op("reduce_sum", {{"axes", {g_axis}}}), weighted_o); - // R = mul(O', broadcasted_scale) - auto scaled_r = mm.insert_instruction( - attn_group_ins, make_op("mul"), partial_output_o_prime, scale_converted); + // compute sum of weights: denominator = sum(weights) + auto denominator = mm.insert_instruction( + attn_group_ins, make_op("reduce_sum", {{"axes", {g_axis}}}), weights); - // O = sum(R, axis=G_axis) - auto final_output_o = mm.insert_instruction( - attn_group_ins, make_op("reduce_sum", {{"axes", {g_axis}}}), scaled_r); + // final division: O = numerator / denominator + auto final_output_o = + mm.insert_instruction(attn_group_ins, make_op("div"), numerator, denominator); // squeeze G to match the original output shape auto final_squeezed_o = mm.insert_instruction( diff --git a/test/fuse_attention.cpp b/test/fuse_attention.cpp index 74f1c610594..7031d342e60 100644 --- a/test/fuse_attention.cpp +++ b/test/fuse_attention.cpp @@ -902,21 +902,19 @@ TEST_CASE(gemm_softmax_gemm_flash_decoding) auto k2_broad1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 12, 2, 256, 1}}}), k2_rmax); auto k2_sub = mm->add_instruction(migraphx::make_op("sub"), lse, k2_broad1); - auto k2_exp = mm->add_instruction(migraphx::make_op("exp"), k2_sub); - auto k2_rsum1 = - mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), k2_exp); - auto k2_broad2 = mm->add_instruction( - migraphx::make_op("multibroadcast", {{"out_lens", {1, 12, 2, 256, 1}}}), k2_rsum1); - auto k2_div = mm->add_instruction(migraphx::make_op("div"), k2_exp, k2_broad2); + auto k2_exp = mm->add_instruction(migraphx::make_op("exp"), k2_sub); auto k2_broad3 = mm->add_instruction( - migraphx::make_op("multibroadcast", {{"out_lens", {1, 12, 2, 256, 256}}}), k2_div); + migraphx::make_op("multibroadcast", {{"out_lens", {1, 12, 2, 256, 256}}}), k2_exp); auto k2_convert = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), k2_broad3); auto k2_mul = mm->add_instruction(migraphx::make_op("mul"), o_p, k2_convert); - auto k2_rsum2 = + auto k2_rsum1 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), k2_mul); + auto k2_rsum2 = + mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), k2_convert); + auto k2_div = mm->add_instruction(migraphx::make_op("div"), k2_rsum1, k2_rsum2); auto k2_squeeze = - mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {2}}}), k2_rsum2); + mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {2}}}), k2_div); mm->add_return({k2_squeeze}); } EXPECT(p1.sort() == p2.sort()); @@ -1018,21 +1016,19 @@ TEST_CASE(flash_decoding_3d) auto k2_broad1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, num_splits, 256, 1}}}), k2_rmax); auto k2_sub = mm->add_instruction(migraphx::make_op("sub"), lse, k2_broad1); - auto k2_exp = mm->add_instruction(migraphx::make_op("exp"), k2_sub); - auto k2_rsum1 = - mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {g_axis}}}), k2_exp); - auto k2_broad2 = mm->add_instruction( - migraphx::make_op("multibroadcast", {{"out_lens", {1, num_splits, 256, 1}}}), k2_rsum1); - auto k2_div = mm->add_instruction(migraphx::make_op("div"), k2_exp, k2_broad2); + auto k2_exp = mm->add_instruction(migraphx::make_op("exp"), k2_sub); auto k2_broad3 = mm->add_instruction( - migraphx::make_op("multibroadcast", {{"out_lens", q_prime_shape}}), k2_div); + migraphx::make_op("multibroadcast", {{"out_lens", q_prime_shape}}), k2_exp); auto k2_convert = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), k2_broad3); auto k2_mul = mm->add_instruction(migraphx::make_op("mul"), o_p, k2_convert); - auto k2_rsum2 = + auto k2_rsum1 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {g_axis}}}), k2_mul); + auto k2_rsum2 = + mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {g_axis}}}), k2_convert); + auto k2_div = mm->add_instruction(migraphx::make_op("div"), k2_rsum1, k2_rsum2); auto k2_squeeze = - mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {g_axis}}}), k2_rsum2); + mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {g_axis}}}), k2_div); mm->add_return({k2_squeeze}); } EXPECT(p1.sort() == p2.sort()); @@ -1142,21 +1138,19 @@ TEST_CASE(flash_decoding_3d_rectangular) auto k2_broad1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, num_splits, 240, 1}}}), k2_rmax); auto k2_sub = mm->add_instruction(migraphx::make_op("sub"), lse, k2_broad1); - auto k2_exp = mm->add_instruction(migraphx::make_op("exp"), k2_sub); - auto k2_rsum1 = - mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {g_axis}}}), k2_exp); - auto k2_broad2 = mm->add_instruction( - migraphx::make_op("multibroadcast", {{"out_lens", {1, num_splits, 240, 1}}}), k2_rsum1); - auto k2_div = mm->add_instruction(migraphx::make_op("div"), k2_exp, k2_broad2); + auto k2_exp = mm->add_instruction(migraphx::make_op("exp"), k2_sub); auto k2_broad3 = mm->add_instruction( - migraphx::make_op("multibroadcast", {{"out_lens", q_prime_shape}}), k2_div); + migraphx::make_op("multibroadcast", {{"out_lens", q_prime_shape}}), k2_exp); auto k2_convert = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), k2_broad3); auto k2_mul = mm->add_instruction(migraphx::make_op("mul"), o_p, k2_convert); - auto k2_rsum2 = + auto k2_rsum1 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {g_axis}}}), k2_mul); + auto k2_rsum2 = + mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {g_axis}}}), k2_convert); + auto k2_div = mm->add_instruction(migraphx::make_op("div"), k2_rsum1, k2_rsum2); auto k2_squeeze = - mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {g_axis}}}), k2_rsum2); + mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {g_axis}}}), k2_div); mm->add_return({k2_squeeze}); } EXPECT(p1.sort() == p2.sort()); @@ -1274,21 +1268,19 @@ TEST_CASE(flash_decoding_3d_padding) auto k2_broad1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, num_splits, 242, 1}}}), k2_rmax); auto k2_sub = mm->add_instruction(migraphx::make_op("sub"), lse, k2_broad1); - auto k2_exp = mm->add_instruction(migraphx::make_op("exp"), k2_sub); - auto k2_rsum1 = - mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {g_axis}}}), k2_exp); - auto k2_broad2 = mm->add_instruction( - migraphx::make_op("multibroadcast", {{"out_lens", {1, num_splits, 242, 1}}}), k2_rsum1); - auto k2_div = mm->add_instruction(migraphx::make_op("div"), k2_exp, k2_broad2); + auto k2_exp = mm->add_instruction(migraphx::make_op("exp"), k2_sub); auto k2_broad3 = mm->add_instruction( - migraphx::make_op("multibroadcast", {{"out_lens", q_prime_shape}}), k2_div); + migraphx::make_op("multibroadcast", {{"out_lens", q_prime_shape}}), k2_exp); auto k2_convert = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), k2_broad3); auto k2_mul = mm->add_instruction(migraphx::make_op("mul"), o_p, k2_convert); - auto k2_rsum2 = + auto k2_rsum1 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {g_axis}}}), k2_mul); + auto k2_rsum2 = + mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {g_axis}}}), k2_convert); + auto k2_div = mm->add_instruction(migraphx::make_op("div"), k2_rsum1, k2_rsum2); auto k2_squeeze = - mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {g_axis}}}), k2_rsum2); + mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {g_axis}}}), k2_div); // Slice to remove padding: [1, 242, 256] -> [1, 241, 256] auto sliced = mm->add_instruction( @@ -1905,22 +1897,19 @@ TEST_CASE(flash_decoding_3d_auto_split_large_sequence) migraphx::make_op("multibroadcast", {{"out_lens", {1, expected_splits, 512, 1}}}), k2_rmax); auto k2_sub = mm->add_instruction(migraphx::make_op("sub"), lse, k2_broad1); - auto k2_exp = mm->add_instruction(migraphx::make_op("exp"), k2_sub); - auto k2_rsum1 = - mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {g_axis}}}), k2_exp); - auto k2_broad2 = mm->add_instruction( - migraphx::make_op("multibroadcast", {{"out_lens", {1, expected_splits, 512, 1}}}), - k2_rsum1); - auto k2_div = mm->add_instruction(migraphx::make_op("div"), k2_exp, k2_broad2); + auto k2_exp = mm->add_instruction(migraphx::make_op("exp"), k2_sub); auto k2_broad3 = mm->add_instruction( - migraphx::make_op("multibroadcast", {{"out_lens", q_prime_shape}}), k2_div); + migraphx::make_op("multibroadcast", {{"out_lens", q_prime_shape}}), k2_exp); auto k2_convert = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), k2_broad3); auto k2_mul = mm->add_instruction(migraphx::make_op("mul"), o_p, k2_convert); - auto k2_rsum2 = + auto k2_rsum1 = mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {g_axis}}}), k2_mul); + auto k2_rsum2 = + mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {g_axis}}}), k2_convert); + auto k2_div = mm->add_instruction(migraphx::make_op("div"), k2_rsum1, k2_rsum2); auto k2_squeeze = - mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {g_axis}}}), k2_rsum2); + mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {g_axis}}}), k2_div); mm->add_return({k2_squeeze}); } EXPECT(p1.sort() == p2.sort()); From 0910e3e7f2ced314414c27071e53b4aaffe81523 Mon Sep 17 00:00:00 2001 From: Shiv Date: Fri, 24 Jul 2026 16:50:40 -0700 Subject: [PATCH 41/42] fix merge error --- src/sym.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sym.cpp b/src/sym.cpp index 3f2ce10a55c..857c21a3c5f 100644 --- a/src/sym.cpp +++ b/src/sym.cpp @@ -1642,6 +1642,8 @@ std::vector find_variables(const expr& e) self(c); })(e); return result; +} + [[maybe_unused]] static bool has_float_literal(const expr& e) { if(e.empty()) From d1c9f3b2e61cfe7f67672dbe6c845cdc5ccfa1fc Mon Sep 17 00:00:00 2001 From: charlie Date: Thu, 30 Jul 2026 16:47:12 -0500 Subject: [PATCH 42/42] Revert "Merge branch 'sym_resolve_op' of github.com:ROCm/AMDMIGraphX into proto_data_dependent_symbolics" This reverts commit 872fefa1e88964128f6fa7d126836283e9e2e52f, reversing changes made to 9a224a2976178fe68a07a46d6ede2fb837ab5c1f. --- .clang-tidy | 6 +- .github/workflows/ci.yaml | 2 +- CHANGELOG.md | 2 - CMakeLists.txt | 2 +- Dockerfile | 1 - docs/sphinx/requirements.txt | 2 +- requirements.txt | 2 +- src/CMakeLists.txt | 3 +- src/api/CMakeLists.txt | 45 +- src/fuse_attention.cpp | 56 +-- src/include/migraphx/dim_like.hpp | 6 - .../migraphx/op/eval_expr_from_shape.hpp | 125 ------ src/include/migraphx/op/flatten.hpp | 36 +- src/include/migraphx/op/layout.hpp | 15 +- src/include/migraphx/op/nonzero.hpp | 28 +- src/include/migraphx/op/reshape.hpp | 107 +++-- src/include/migraphx/op/reshape_lazy.hpp | 109 ++--- src/include/migraphx/op/squeeze.hpp | 82 ++-- src/include/migraphx/op/unsqueeze.hpp | 144 +++--- src/include/migraphx/reshape_dims.hpp | 19 +- src/include/migraphx/shape.hpp | 7 - src/include/migraphx/sym.hpp | 5 - src/instruction.cpp | 4 +- src/onnx/parse_nonmaxsuppression.cpp | 7 - src/op/builder/floor_div.cpp | 49 -- src/op/builder/gather_elements.cpp | 95 ---- src/op/builder/glu.cpp | 67 --- src/op/builder/group_norm.cpp | 80 ---- src/op/builder/hardsigmoid.cpp | 67 --- .../include/migraphx/op/builder/kit.hpp | 12 +- src/op/builder/instance_norm.cpp | 83 ---- src/op/builder/layer_norm.cpp | 59 --- src/op/builder/normalize.cpp | 69 --- src/op/builder/selu.cpp | 71 --- src/op/builder/softsign.cpp | 53 --- src/op/builder/torch/as_strided.cpp | 81 ---- src/op/builder/torch/conv_transpose.cpp | 103 ----- src/op/builder/torch/index_copy.cpp | 72 --- src/op/builder/torch/linear.cpp | 68 --- src/op/builder/torch/lstm.cpp | 84 ---- src/op/builder/torch/nan_to_num.cpp | 87 ---- src/op/builder/torch/scatter_reduce.cpp | 106 ----- src/op/builder/torch/slice_scatter.cpp | 76 ---- src/op/builder/torch/std.cpp | 85 ---- src/op/builder/torch_kit.cpp | 76 ++-- src/op/builder/vector_norm.cpp | 92 ---- src/py/migraphx_py.cpp | 8 - src/reshape_dims.cpp | 224 +++------- src/shape.cpp | 2 - src/sym.cpp | 37 -- src/targets/cpu/lowering.cpp | 5 - src/targets/gpu/device_name.cpp | 3 +- .../gpu/include/migraphx/gpu/contiguous.hpp | 14 +- src/targets/gpu/lowering.cpp | 4 +- src/targets/gpu/propagate_reshape_layout.cpp | 27 +- src/targets/ref/lowering.cpp | 5 - test/CMakeLists.txt | 3 - test/fuse_attention.cpp | 81 ++-- test/gpu/nonmaxsuppression.cpp | 77 ---- test/gpu/propagate_reshape_layout.cpp | 66 --- test/instruction.cpp | 33 -- test/onnx/gen_onnx.py | 74 --- .../nonmaxsuppression_zero_boxes_test.onnx | Bin 759 -> 0 bytes test/onnx/parse/nonmaxsuppression_test.cpp | 52 --- test/op/CMakeLists.txt | 4 +- test/op/builder/gather_elements_test.cpp | 59 --- test/op/builder/torch/as_strided_test.cpp | 64 --- test/op/builder/torch/batchnorm_test.cpp | 42 -- test/op/builder/torch/clip_test.cpp | 95 ---- test/op/builder/torch/conv_transpose_test.cpp | 89 ---- test/op/builder/torch/convolution_test.cpp | 46 -- test/op/builder/torch/dot_test.cpp | 38 -- test/op/builder/torch/floor_div_test.cpp | 39 -- test/op/builder/torch/gelu_test.cpp | 37 -- test/op/builder/torch/glu_test.cpp | 43 -- test/op/builder/torch/group_norm_test.cpp | 72 --- test/op/builder/torch/hardsigmoid_test.cpp | 43 -- test/op/builder/torch/index_copy_test.cpp | 46 -- test/op/builder/torch/instance_norm_test.cpp | 67 --- test/op/builder/torch/layer_norm_test.cpp | 55 --- test/op/builder/torch/linear_test.cpp | 53 --- test/op/builder/torch/lstm_test.cpp | 113 ----- test/op/builder/torch/nan_to_num_test.cpp | 57 --- test/op/builder/torch/scatter_reduce_test.cpp | 87 ---- test/op/builder/torch/selu_test.cpp | 48 -- test/op/builder/torch/slice_scatter_test.cpp | 51 --- test/op/builder/torch/softsign_test.cpp | 40 -- test/op/builder/torch/std_test.cpp | 47 -- test/op/builder/torch/vector_norm_test.cpp | 89 ---- ...common_ops_test.cpp => torch_kit_test.cpp} | 124 ++++- test/op_shape_test.cpp | 423 ------------------ test/ref/eval_expr_from_shape.cpp | 119 ----- test/ref/nonzero.cpp | 39 +- test/ref/slice.cpp | 37 +- test/reshape_dims_test.cpp | 395 ---------------- test/sym.cpp | 33 -- test/test_pytest_bridge.py | 91 ---- test/verify/test_nonzero.cpp | 21 - tools/generate.py | 70 +-- 99 files changed, 603 insertions(+), 5338 deletions(-) delete mode 100644 src/include/migraphx/op/eval_expr_from_shape.hpp delete mode 100644 src/op/builder/floor_div.cpp delete mode 100644 src/op/builder/gather_elements.cpp delete mode 100644 src/op/builder/glu.cpp delete mode 100644 src/op/builder/group_norm.cpp delete mode 100644 src/op/builder/hardsigmoid.cpp delete mode 100644 src/op/builder/instance_norm.cpp delete mode 100644 src/op/builder/layer_norm.cpp delete mode 100644 src/op/builder/normalize.cpp delete mode 100644 src/op/builder/selu.cpp delete mode 100644 src/op/builder/softsign.cpp delete mode 100644 src/op/builder/torch/as_strided.cpp delete mode 100644 src/op/builder/torch/conv_transpose.cpp delete mode 100644 src/op/builder/torch/index_copy.cpp delete mode 100644 src/op/builder/torch/linear.cpp delete mode 100644 src/op/builder/torch/lstm.cpp delete mode 100644 src/op/builder/torch/nan_to_num.cpp delete mode 100644 src/op/builder/torch/scatter_reduce.cpp delete mode 100644 src/op/builder/torch/slice_scatter.cpp delete mode 100644 src/op/builder/torch/std.cpp delete mode 100644 src/op/builder/vector_norm.cpp delete mode 100644 test/onnx/nonmaxsuppression_zero_boxes_test.onnx delete mode 100644 test/onnx/parse/nonmaxsuppression_test.cpp delete mode 100644 test/op/builder/gather_elements_test.cpp delete mode 100644 test/op/builder/torch/as_strided_test.cpp delete mode 100644 test/op/builder/torch/batchnorm_test.cpp delete mode 100644 test/op/builder/torch/clip_test.cpp delete mode 100644 test/op/builder/torch/conv_transpose_test.cpp delete mode 100644 test/op/builder/torch/convolution_test.cpp delete mode 100644 test/op/builder/torch/dot_test.cpp delete mode 100644 test/op/builder/torch/floor_div_test.cpp delete mode 100644 test/op/builder/torch/gelu_test.cpp delete mode 100644 test/op/builder/torch/glu_test.cpp delete mode 100644 test/op/builder/torch/group_norm_test.cpp delete mode 100644 test/op/builder/torch/hardsigmoid_test.cpp delete mode 100644 test/op/builder/torch/index_copy_test.cpp delete mode 100644 test/op/builder/torch/instance_norm_test.cpp delete mode 100644 test/op/builder/torch/layer_norm_test.cpp delete mode 100644 test/op/builder/torch/linear_test.cpp delete mode 100644 test/op/builder/torch/lstm_test.cpp delete mode 100644 test/op/builder/torch/nan_to_num_test.cpp delete mode 100644 test/op/builder/torch/scatter_reduce_test.cpp delete mode 100644 test/op/builder/torch/selu_test.cpp delete mode 100644 test/op/builder/torch/slice_scatter_test.cpp delete mode 100644 test/op/builder/torch/softsign_test.cpp delete mode 100644 test/op/builder/torch/std_test.cpp delete mode 100644 test/op/builder/torch/vector_norm_test.cpp rename test/op/builder/{torch/common_ops_test.cpp => torch_kit_test.cpp} (61%) delete mode 100644 test/ref/eval_expr_from_shape.cpp delete mode 100644 test/reshape_dims_test.cpp delete mode 100644 test/test_pytest_bridge.py diff --git a/.clang-tidy b/.clang-tidy index a8e17670c85..bd8a554307e 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -14,11 +14,11 @@ CheckOptions: - key: misc-const-correctness.AnalyzeValues value: 'false' - key: performance-for-range-copy.AllowedTypes - value: 'shape;operation;iterator;literal;tensor_view;match;sym::expr' + value: 'shape;operation;iterator;literal;tensor_view;match' - key: performance-unnecessary-copy-initialization.AllowedTypes - value: 'shape;operation;iterator;literal;tensor_view;match;sym::expr' + value: 'shape;operation;iterator;literal;tensor_view;match' - key: performance-unnecessary-value-param.AllowedTypes - value: 'shape;operation;iterator;literal;tensor_view;match;sym::expr;__amdgpu_buffer_rsrc_t' + value: 'shape;operation;iterator;literal;tensor_view;match;__amdgpu_buffer_rsrc_t' - key: readability-function-size.BranchThreshold value: '15' - key: readability-function-size.LineThreshold diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0654b9f320b..9804fb298d0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -144,7 +144,7 @@ jobs: -DCLANG_TIDY_CACHE=/data/tidy-cache \ -DGPU_TARGETS=gfx908 \ .. - make -j$(nproc) -k onnx-proto tf-proto generate_api tidy + make -j$(nproc) -k onnx-proto tf-proto tidy # GH actions can not update existing cache, as a workaround clear cache and then save it - name: Clear tidy cache before saving diff --git a/CHANGELOG.md b/CHANGELOG.md index ff0f4574e2f..2215654dbfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,7 +62,6 @@ Full documentation for MIGraphX is available at ### Resolved issues -* Fixed the reference `nonzero` operator to handle non-standard input layouts such as transposed or broadcasted tensors. * Restored support for the documented flat {min,max,optimals} JSON format in migraphx-driver's --default-dyn-dim and --dyn-input-dim flags (#4926). * Fixed ONNX `Where` parsing for dynamic-shape inputs that require broadcasting (including mixed static and dynamic inputs), which previously threw `same_dims: where: Dimensions do not match` (#4925). * Fixed a regression in `simplify_algebra` where `find_conv_broadcast_input` could trigger `Dimensions do not match` for padded broadcast-convolution rewrites in no-interior spatial cases (#4738). @@ -82,7 +81,6 @@ Full documentation for MIGraphX is available at * Fixed `slice_concat_gather` matcher and interaction between same table and cross table gather fusions(#5038). ### Optimized -* Optimized flash decoding recombination in `fuse_attention` to use the exp-normalize form (#5090). * Reduced tuning time by scaling the per-candidate benchmark bundle to the candidate's op count (#4989). * Enabled tensor vectorization for GPU fused `argmin` and `argmax` (`gpu::arg_reduce`) (#4790). * Replaced Hillis-Steele scan algorithm with a wave-based hierarchical scan, reducing work complexity from O(N log N) to O(N) and synchronization from O(log N) to 2 `__syncthreads()` calls (#4720). diff --git a/CMakeLists.txt b/CMakeLists.txt index 26ba9aaaef0..0989ccab1c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -144,7 +144,7 @@ include(ROCMSetupVersion) option(BUILD_DEV "Build for development purpose only" OFF) -rocm_setup_version(VERSION 2.17.0) +rocm_setup_version(VERSION 2.16.0) math(EXPR MIGRAPHX_SO_MAJOR_VERSION "(${PROJECT_VERSION_MAJOR} * 1000 * 1000) + (${PROJECT_VERSION_MINOR} * 1000) + ${PROJECT_VERSION_PATCH}") set(MIGRAPHX_SO_VERSION ${MIGRAPHX_SO_MAJOR_VERSION}.0) diff --git a/Dockerfile b/Dockerfile index 367f60b6e99..5b3c2e798d1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -65,7 +65,6 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \ hipify-clang \ hiprand-dev \ hipsparselt \ - hsa-amd-aqlprofile \ half \ libssl-dev \ zlib1g-dev && \ diff --git a/docs/sphinx/requirements.txt b/docs/sphinx/requirements.txt index 943b4a7081c..9cd2e8fd23a 100644 --- a/docs/sphinx/requirements.txt +++ b/docs/sphinx/requirements.txt @@ -87,7 +87,7 @@ fastjsonschema==2.20.0 # rocm-docs-core gitdb==4.0.11 # via gitpython -gitpython==3.1.52 +gitpython==3.1.50 # via rocm-docs-core greenlet==3.1.1 # via sqlalchemy diff --git a/requirements.txt b/requirements.txt index a6f9b32a3f3..fd69ce8d3e0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,7 +27,7 @@ nlohmann/json@v3.8.0 -DCMAKE_POLICY_VERSION_MINIMUM=3.5 pybind/pybind11@3e9dfa2866941655c56877882565e7577de6fc7b --build msgpack/msgpack-c@cpp-3.3.0 -DMSGPACK_BUILD_TESTS=Off -DMSGPACK_BUILD_EXAMPLES=Off -DCMAKE_POLICY_VERSION_MINIMUM=3.5 sqlite3@3.50.4 -DCMAKE_POSITION_INDEPENDENT_CODE=On -ROCm/rocm-cmake@6a7c5b73b8882c74f8f7060e2633f230dabb7b63 --build +ROCm/rocm-cmake@1d4652ae2ec0e44a67a7c415dd7e51c88a6aa68d --build ROCm/composable_kernel@ad0db05b040bacda751c65c705261b8a0a7ed25d --cmake subdir -DCMAKE_DIR=codegen -DCMAKE_POSITION_INDEPENDENT_CODE=On -DBUILD_TESTING=Off -DCMAKE_POLICY_VERSION_MINIMUM=3.5 https://gitlab.com/libeigen/eigen/-/archive/5.0.1/eigen-5.0.1.tar.gz -DBUILD_TESTING=Off -DEIGEN_BUILD_DOC=Off -DEIGEN_BUILD_LAPACK=Off ROCm/rocMLIR@eccd4d712fb1b0729622690945ab0760f6ca8d6f -DBUILD_FAT_LIBROCKCOMPILER=On -DLLVM_INCLUDE_TESTS=Off diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bad7a47abb8..e1bc74e6a1f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -155,7 +155,7 @@ add_library(migraphx enable_static_init(migraphx) -file(GLOB BUILDER_SRCS CONFIGURE_DEPENDS op/builder/*.cpp op/builder/torch/*.cpp) +file(GLOB BUILDER_SRCS CONFIGURE_DEPENDS op/builder/*.cpp) target_sources(migraphx PRIVATE ${BUILDER_SRCS}) if(WIN32) @@ -218,7 +218,6 @@ register_migraphx_ops( elu equal erf - eval_expr_from_shape exp fill fixed_pad diff --git a/src/api/CMakeLists.txt b/src/api/CMakeLists.txt index 645f8709e6c..93b85bfd65b 100644 --- a/src/api/CMakeLists.txt +++ b/src/api/CMakeLists.txt @@ -22,50 +22,9 @@ # THE SOFTWARE. ##################################################################################### -# The C API (migraphx.h and api.cpp) is generated from the templates in -# tools/api. `make generate` still writes them into the source tree so the -# output can be reviewed, but migraphx_c is built from a freshly generated copy -# placed in the build directory instead of the checked-in src/api copies. -find_package(Python 3 COMPONENTS Interpreter REQUIRED) - -set(MIGRAPHX_API_TOOLS_DIR ${PROJECT_SOURCE_DIR}/tools) -set(MIGRAPHX_API_GEN_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/include) -set(MIGRAPHX_API_GEN_HEADER ${MIGRAPHX_API_GEN_INCLUDE_DIR}/migraphx/migraphx.h) -set(MIGRAPHX_API_GEN_SOURCE ${CMAKE_CURRENT_BINARY_DIR}/api.cpp) - -# generate.py writes migraphx.h under include/migraphx/ and api.cpp at the top -# level of the output dir, mirroring the source-tree layout. The build copy is -# only a compiler input, so it is generated without clang-format; `make -# generate` still formats the reviewable source-tree copy. -add_custom_command( - OUTPUT ${MIGRAPHX_API_GEN_HEADER} ${MIGRAPHX_API_GEN_SOURCE} - COMMAND ${Python_EXECUTABLE} ${MIGRAPHX_API_TOOLS_DIR}/generate.py - --api-only - --api-output-dir ${CMAKE_CURRENT_BINARY_DIR} - WORKING_DIRECTORY ${MIGRAPHX_API_TOOLS_DIR} - DEPENDS - ${MIGRAPHX_API_TOOLS_DIR}/generate.py - ${MIGRAPHX_API_TOOLS_DIR}/api.py - ${MIGRAPHX_API_TOOLS_DIR}/api/migraphx.h - ${MIGRAPHX_API_TOOLS_DIR}/api/api.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/migraphx.py - COMMENT "Generating C API (migraphx.h and api.cpp) in build directory" - VERBATIM -) - -add_custom_target(generate_api DEPENDS ${MIGRAPHX_API_GEN_HEADER} ${MIGRAPHX_API_GEN_SOURCE}) - -# migraphx.hpp is hand-written (not generated); copy it next to the generated -# migraphx.h so the build include directory is a complete public API. -configure_file( - ${CMAKE_CURRENT_SOURCE_DIR}/include/migraphx/migraphx.hpp - ${MIGRAPHX_API_GEN_INCLUDE_DIR}/migraphx/migraphx.hpp - COPYONLY) - add_library(migraphx_c - ${MIGRAPHX_API_GEN_SOURCE} + api.cpp ) -add_dependencies(migraphx_c generate_api) set_target_properties(migraphx_c PROPERTIES EXPORT_NAME c) migraphx_generate_export_header(migraphx_c DIRECTORY migraphx/api) @@ -85,5 +44,5 @@ target_link_libraries(migraphx_c PUBLIC migraphx_version) rocm_install_targets( TARGETS migraphx_c INCLUDE - ${MIGRAPHX_API_GEN_INCLUDE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/include ) diff --git a/src/fuse_attention.cpp b/src/fuse_attention.cpp index b1ca9e7939b..470799c307d 100644 --- a/src/fuse_attention.cpp +++ b/src/fuse_attention.cpp @@ -755,49 +755,51 @@ struct find_flash_decoding auto lse = mm.insert_instruction( attn_group_ins, make_op("get_tuple_elem", {{"index", 1}}), new_group_ins); - // kernel 2: combine using exp-normalize trick - // O = sum(O' * exp(LSE - max)) / sum(exp(LSE - max)) - // find max LSE across groups for numerical stability + // kernel 2 + // the partial outputs O'[g] are already weighted by their group's softmax, + // LSE[g] contains log(sum(exp(S[g]))) for each group + // To combine: weight by exp(LSE[g]) / sum_g(exp(LSE[g'])) + + // compute global max for numerical stability auto lse_max = mm.insert_instruction(attn_group_ins, make_op("reduce_max", {{"axes", {g_axis}}}), lse); - auto lse_max_bcast = mm.insert_instruction( attn_group_ins, make_op("multibroadcast", {{"out_lens", lse->get_shape().lens()}}), lse_max); - // compute unnormalized weights - // exp(LSE - max) + // exp(LSE - max_LSE) auto lse_sub = mm.insert_instruction(attn_group_ins, make_op("sub"), lse, lse_max_bcast); - auto lse_exp = mm.insert_instruction(attn_group_ins, make_op("exp"), lse_sub); - // broadcast weights to match O' shape - // [B, G, M] -> [B, G, M, D] - auto lse_exp_bcast = mm.insert_instruction( + // sum across groups + auto lse_sum = mm.insert_instruction( + attn_group_ins, make_op("reduce_sum", {{"axes", {g_axis}}}), lse_exp); + auto lse_sum_bcast = mm.insert_instruction( attn_group_ins, - make_op("multibroadcast", {{"out_lens", partial_output_o_prime->get_shape().lens()}}), - lse_exp); + make_op("multibroadcast", {{"out_lens", lse_exp->get_shape().lens()}}), + lse_sum); - // convert weights to output type - auto output_type = partial_output_o_prime->get_shape().type(); - auto weights = mm.insert_instruction( - attn_group_ins, make_op("convert", {{"target_type", output_type}}), lse_exp_bcast); + // scale factor: exp(LSE[g] - max_LSE) / sum(exp(LSE - max_LSE)) + auto scale = mm.insert_instruction(attn_group_ins, make_op("div"), lse_exp, lse_sum_bcast); - // compute weighted sum: numerator = sum(O' * weights) - auto weighted_o = - mm.insert_instruction(attn_group_ins, make_op("mul"), partial_output_o_prime, weights); + auto scale_bcast = mm.insert_instruction( + attn_group_ins, + make_op("multibroadcast", {{"out_lens", partial_output_o_prime->get_shape().lens()}}), + scale); - auto numerator = mm.insert_instruction( - attn_group_ins, make_op("reduce_sum", {{"axes", {g_axis}}}), weighted_o); + // convert scale to match the type of partial_output_o_prime + auto output_type = partial_output_o_prime->get_shape().type(); + auto scale_converted = mm.insert_instruction( + attn_group_ins, make_op("convert", {{"target_type", output_type}}), scale_bcast); - // compute sum of weights: denominator = sum(weights) - auto denominator = mm.insert_instruction( - attn_group_ins, make_op("reduce_sum", {{"axes", {g_axis}}}), weights); + // R = mul(O', broadcasted_scale) + auto scaled_r = mm.insert_instruction( + attn_group_ins, make_op("mul"), partial_output_o_prime, scale_converted); - // final division: O = numerator / denominator - auto final_output_o = - mm.insert_instruction(attn_group_ins, make_op("div"), numerator, denominator); + // O = sum(R, axis=G_axis) + auto final_output_o = mm.insert_instruction( + attn_group_ins, make_op("reduce_sum", {{"axes", {g_axis}}}), scaled_r); // squeeze G to match the original output shape auto final_squeezed_o = mm.insert_instruction( diff --git a/src/include/migraphx/dim_like.hpp b/src/include/migraphx/dim_like.hpp index 129650123fd..cb8a008e02d 100644 --- a/src/include/migraphx/dim_like.hpp +++ b/src/include/migraphx/dim_like.hpp @@ -56,12 +56,6 @@ struct dim_like_picker // A dim attribute entry that may be either a plain int64_t or a dynamic_dimension. using dim_like = picked_variant; -inline bool is_symbolic(const dim_like& d) -{ - return std::holds_alternative(d) and - std::get(d).is_symbolic(); -} - inline std::ostream& operator<<(std::ostream& os, const dim_like& d) { visit([&](const auto& x) { os << x; }, d); diff --git a/src/include/migraphx/op/eval_expr_from_shape.hpp b/src/include/migraphx/op/eval_expr_from_shape.hpp deleted file mode 100644 index 02c0e31d5e5..00000000000 --- a/src/include/migraphx/op/eval_expr_from_shape.hpp +++ /dev/null @@ -1,125 +0,0 @@ -/* - * 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_OPERATORS_EVAL_EXPR_FROM_SHAPE_HPP -#define MIGRAPHX_GUARD_OPERATORS_EVAL_EXPR_FROM_SHAPE_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace migraphx { -inline namespace MIGRAPHX_INLINE_NS { -namespace op { - -struct eval_expr_from_shape -{ - std::vector expressions{}; - std::vector input_shapes{}; - - template - static auto reflect(Self& self, F f) - { - return pack(f(self.expressions, "expressions"), f(self.input_shapes, "input_shapes")); - } - - std::string name() const { return "eval_expr_from_shape"; } - - shape compute_shape(const std::vector& inputs) const - { - check_shapes{inputs, *this, true}.has_at_least(1); - - std::unordered_set available; - for(const auto& input : inputs) - { - if(not input.symbolic()) - continue; - for(const auto& d : input.dyn_dims()) - if(d.sym_expr.name() == "variable") - available.insert(sym::as_symbol(d.sym_expr)); - } - for(const auto& expression : expressions) - for(const auto& variable : sym::find_variables(expression)) - if(available.count(variable) == 0) - MIGRAPHX_THROW("EVAL_EXPR_FROM_SHAPE: Symbol '" + variable.to_string() + - "' is not a direct input dimension"); - - return shape{shape::int64_type, {expressions.size()}}; - } - - void finalize(context&, const shape&, const std::vector& inputs) - { - input_shapes = inputs; - } - - argument compute(const shape&, std::vector args) const - { - if(input_shapes.empty() or input_shapes.size() != args.size()) - MIGRAPHX_THROW("EVAL_EXPR_FROM_SHAPE: input shapes not captured; op was not finalized"); - - std::unordered_map values; - for(std::size_t i = 0; i < input_shapes.size(); ++i) - { - const auto& input_shape = input_shapes[i]; - auto lens = args[i].get_shape().lens(); - if(input_shape.ndim() != lens.size()) - MIGRAPHX_THROW("EVAL_EXPR_FROM_SHAPE: Runtime input rank does not match its " - "symbolic shape"); - if(not input_shape.symbolic()) - continue; - const auto& dims = input_shape.dyn_dims(); - for(std::size_t axis = 0; axis < dims.size(); ++axis) - { - if(dims[axis].sym_expr.name() != "variable") - continue; - auto variable = sym::as_symbol(dims[axis].sym_expr); - auto result = values.emplace(variable, lens[axis]); - if(not result.second and result.first->second != lens[axis]) - MIGRAPHX_THROW("EVAL_EXPR_FROM_SHAPE: Repeated symbol has inconsistent runtime " - "dimensions"); - } - } - - argument result{shape{shape::int64_type, {expressions.size()}}}; - result.visit([&](auto output) { - std::transform(expressions.begin(), - expressions.end(), - output.begin(), - [&](const auto& e) { return e.eval_uint(values); }); - }); - return result; - } -}; - -} // namespace op -} // namespace MIGRAPHX_INLINE_NS -} // namespace migraphx - -#endif diff --git a/src/include/migraphx/op/flatten.hpp b/src/include/migraphx/op/flatten.hpp index 045d43b5651..ab23bc60f6b 100644 --- a/src/include/migraphx/op/flatten.hpp +++ b/src/include/migraphx/op/flatten.hpp @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2015-2026 Advanced Micro Devices, Inc. All rights reserved. + * Copyright (c) 2015-2025 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 @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -55,32 +54,11 @@ struct flatten } std::string name() const { return "flatten"; } - - // Collapses the dims on each side of axis into a standard 2D shape, for - // static and symbolic input through one path. - shape symbolic_compute_shape(const shape& s) const - { - auto sym_in = s.to_symbolic(); - const auto& dds = sym_in.dyn_dims(); - auto x = std::accumulate(dds.begin(), - dds.begin() + axis, - shape::dynamic_dimension{sym::lit(1)}, - std::multiplies<>{}); - auto y = std::accumulate(dds.begin() + axis, - dds.end(), - shape::dynamic_dimension{sym::lit(1)}, - std::multiplies<>{}); - shape result{s.type(), {x, y}}; - if(not s.symbolic()) - return result.to_static(); - return result; - } - shape normalize_compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1); const auto& s = inputs[0]; - if(s.dynamic() and not s.symbolic()) + if(s.dynamic()) { // Doesn't handle optimals auto min_lens = s.min_lens(); @@ -100,7 +78,15 @@ struct flatten {}}; return {s.type(), {x, y}}; } - return symbolic_compute_shape(s); + else + { + auto&& lens = s.lens(); + auto x = std::accumulate( + lens.begin(), lens.begin() + axis, std::size_t{1}, std::multiplies<>{}); + auto y = std::accumulate( + lens.begin() + axis, lens.end(), std::size_t{1}, std::multiplies<>{}); + return {s.type(), {x, y}}; + } } argument compute(const dyn_output& dyn_out, std::vector args) const { diff --git a/src/include/migraphx/op/layout.hpp b/src/include/migraphx/op/layout.hpp index 557a6abd85d..16372af6826 100644 --- a/src/include/migraphx/op/layout.hpp +++ b/src/include/migraphx/op/layout.hpp @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2015-2026 Advanced Micro Devices, Inc. All rights reserved. + * Copyright (c) 2015-2025 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 @@ -58,15 +58,10 @@ struct layout : unary shape compute_shape(std::vector inputs) const { - check_shapes{inputs, *this, true}.has(1).only_dims(permutation.size()); - const auto& input = inputs.at(0); - auto t = input.type(); - // A range-based dynamic shape has no strides, so a permuted layout is not representable. - if(input.symbolic()) - return shape::from_permutation(t, input.dyn_dims(), permutation); - if(input.dynamic()) - MIGRAPHX_THROW("LAYOUT: non-symbolic dynamic shapes are not supported"); - return shape::from_permutation(t, input.lens(), permutation); + check_shapes{inputs, *this}.has(1).only_dims(permutation.size()); + auto lens = inputs.at(0).lens(); + auto t = inputs.at(0).type(); + return shape::from_permutation(t, lens, permutation); } auto apply() const diff --git a/src/include/migraphx/op/nonzero.hpp b/src/include/migraphx/op/nonzero.hpp index 06283a4e51e..e14d62a05b9 100644 --- a/src/include/migraphx/op/nonzero.hpp +++ b/src/include/migraphx/op/nonzero.hpp @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2015-2026 Advanced Micro Devices, Inc. All rights reserved. + * Copyright (c) 2015-2023 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 @@ -31,7 +31,6 @@ #include #include #include -#include #include namespace migraphx { @@ -44,7 +43,7 @@ struct nonzero shape compute_shape(std::vector inputs) const { - check_shapes{inputs, *this}.has(1); + check_shapes{inputs, *this}.has(1).standard(); auto elem_num = inputs[0].elements(); auto dim_num = inputs[0].lens().size(); std::vector out_lens = {dim_num, elem_num}; @@ -54,17 +53,24 @@ struct nonzero argument compute(const shape& output_shape, std::vector args) const { + std::vector> vec_idx; auto s = args.front().get_shape(); - argument result{output_shape}; - auto output = result.get(); - std::fill(output.begin(), output.end(), 0); args.front().visit([&](auto v) { - std::size_t nonzero_idx = 0; - shape_for_each(s, [&](const auto& idx_v) { - if(not float_equal(v[idx_v], 0)) + shape_for_each(s, [&](const auto& idx_v, size_t idx) { + if(not float_equal(v[idx], 0)) + { + vec_idx.push_back(idx_v); + } + }); + }); + + argument result{output_shape}; + result.visit([&](auto output) { + std::fill(output.begin(), output.end(), 0); + par_for(vec_idx.size(), [&](auto i) { + for(std::size_t j = 0; j < vec_idx.front().size(); ++j) { - auto out_idx = nonzero_idx++; - par_for(idx_v.size(), [&](auto i) { output(i, out_idx) = idx_v[i]; }); + output[output_shape.index({j, i})] = vec_idx[i][j]; } }); }); diff --git a/src/include/migraphx/op/reshape.hpp b/src/include/migraphx/op/reshape.hpp index 454f6cf7307..52b298f8f82 100644 --- a/src/include/migraphx/op/reshape.hpp +++ b/src/include/migraphx/op/reshape.hpp @@ -24,6 +24,7 @@ #ifndef MIGRAPHX_GUARD_OPERATORS_RESHAPE_HPP #define MIGRAPHX_GUARD_OPERATORS_RESHAPE_HPP +#include #include #include #include @@ -135,64 +136,82 @@ struct reshape return {s0.type(), output_dyn_dims}; } - // Resolves the output dims for static and symbolic input through one path. - shape symbolic_compute_shape(const shape& s0) const + shape static_compute_shape(std::vector inputs, std::size_t n_neg_dims) const { - // Lift static input to symbolic literals so the same dd arithmetic resolves both. - auto sym_in = s0.to_symbolic(); - auto output_dyn_dims = resolve_reshape_dims(sym_in, dims); - const bool has_inferred_dim = - std::find(dims.begin(), dims.end(), dim_like{-1}) != dims.end(); - const bool dims_have_symbolic = std::any_of(dims.begin(), dims.end(), is_symbolic); - - // Preserve the input layout when reshape_dims can derive it; else standard. - std::vector target(output_dyn_dims.size()); - std::transform(output_dyn_dims.begin(), - output_dyn_dims.end(), - target.begin(), - [](const auto& dd) { return dd.sym_expr; }); - auto result = reshape_dims(sym_in, target, {.lazy = false}) - .value_or(shape{s0.type(), output_dyn_dims}); - - // An inferred -1 over a symbolic input is a floor division, so its element - // count is only resolvable at runtime; otherwise throw on a provably - // mismatched count (strict_less either way), letting indeterminate ones pass. - if(not(s0.symbolic() and has_inferred_dim)) + check_shapes{inputs, *this}.has(1); + auto&& idims = inputs.front().lens(); + 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++) { - auto out_elems = result.sym_elements(); - auto in_elems = s0.sym_elements(); - if(sym::strict_less(out_elems, in_elems).value_or(false) or - sym::strict_less(in_elems, out_elems).value_or(false)) - MIGRAPHX_THROW("Reshape: Wrong number of elements for reshape: reshape has " + - to_string(out_elems) + " elements whereas the input has " + - to_string(in_elems)); + 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] == dim_like{-1}) + rdims[i] = 1; + } + + if(n_neg_dims > 0) + { + size_t missing_dim = + inputs.front().elements() / + std::accumulate(rdims.begin(), rdims.end(), 1, std::multiplies()); + for(std::size_t i = 0; i < rdims.size(); i++) + { + if(dims[i] == dim_like{-1}) + rdims[i] = missing_dim; + } } - // Only a static input with integer dims is fully literal; evaluate it back to - // the concrete layout. Anything symbolic stays symbolic. - if(not s0.symbolic() and not dims_have_symbolic) - return result.to_static(); - return result; + auto nelements = + std::accumulate(rdims.begin(), rdims.end(), std::size_t{1}, std::multiplies<>{}); + + if(nelements != inputs.front().elements()) + MIGRAPHX_THROW("Reshape: Wrong number of elements for reshape: reshape has " + + std::to_string(nelements) + " elements whereas the input has " + + std::to_string(inputs.front().elements())); + + auto s = reshape_dims(inputs.front(), rdims, {.lazy = false}); + if(not s.has_value()) + return shape{inputs.front().type(), rdims}; + + return s.value(); } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1, 2); - if(inputs.size() == 2) - return inputs.back(); - validate_reshape_dims(name(), dims); + 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 but given {" + + to_string_range(dims) + "} with " + to_string(n_neg_dims) + " -1 dims"); const auto& s0 = inputs.front(); - if(s0.dynamic() and not s0.symbolic()) + if(inputs.size() == 1) + { + if(s0.dynamic()) + { + return dyn_1arg_compute_shape(s0); + } + else + { + return static_compute_shape(inputs, n_neg_dims); + } + } + else { - // A symbolic dim has no range interpretation, so it cannot target a - // range-based input. - if(std::any_of(dims.begin(), dims.end(), is_symbolic)) - MIGRAPHX_THROW("reshape: range-based input only supports int64 dim entries"); - return dyn_1arg_compute_shape(s0); + return inputs.back(); } - return symbolic_compute_shape(s0); } argument compute(const dyn_output& dyn_out, std::vector args) const diff --git a/src/include/migraphx/op/reshape_lazy.hpp b/src/include/migraphx/op/reshape_lazy.hpp index 23e81e55e8f..40be0ac6c1e 100644 --- a/src/include/migraphx/op/reshape_lazy.hpp +++ b/src/include/migraphx/op/reshape_lazy.hpp @@ -101,74 +101,75 @@ struct reshape_lazy return {s0.type(), output_dyn_dims}; } - // Resolves the output layout for static and symbolic input through one path. - shape symbolic_compute_shape(const shape& s0) const + shape static_compute_shape(std::vector inputs, std::size_t n_neg_dims) const { - // Lift static input to symbolic literals so the same dd arithmetic resolves both. - auto sym_in = s0.to_symbolic(); - auto output_dyn_dims = resolve_reshape_dims(sym_in, dims); - const bool has_inferred_dim = - std::find(dims.begin(), dims.end(), dim_like{-1}) != dims.end(); - - std::vector target(output_dyn_dims.size()); - std::transform(output_dyn_dims.begin(), - output_dyn_dims.end(), - target.begin(), - [](const auto& dd) { return dd.sym_expr; }); - - // Lazy reshape is a no-copy view: when the permutation can't be preserved we - // cannot fall back to a repacked standard layout the way reshape does. - auto s = reshape_dims(sym_in, target, {.lazy = true}); - if(not s.has_value()) - MIGRAPHX_THROW("reshape_lazy on axis that is not packed."); - - const bool dims_have_symbolic = std::any_of(dims.begin(), dims.end(), is_symbolic); - // Only a static input with integer dims is fully literal; evaluate it back to - // the concrete layout (static results stay byte-identical). Else stays symbolic. - if(not s0.symbolic() and not dims_have_symbolic) + check_shapes{inputs, *this}.has(1); + auto&& idims = inputs.front().lens(); + 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++) { - auto result = s->to_static(); - if(result.elements() != s0.elements()) - MIGRAPHX_THROW( - "reshape_lazy: Wrong number of elements for reshape_lazy: reshape_lazy has " + - std::to_string(result.elements()) + " elements whereas the input has " + - std::to_string(s0.elements())); - assert(result.bytes() == s0.bytes()); - return result; + 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] == dim_like{-1}) + rdims[i] = 1; } - // An inferred -1 over a symbolic input is a floor division, so its element - // count is only resolvable at runtime; otherwise throw on a provably - // mismatched count (strict_less either way), letting indeterminate ones pass. - if(not(s0.symbolic() and has_inferred_dim)) + + if(n_neg_dims > 0) { - auto out_elems = s->sym_elements(); - auto in_elems = s0.sym_elements(); - if(sym::strict_less(out_elems, in_elems).value_or(false) or - sym::strict_less(in_elems, out_elems).value_or(false)) - MIGRAPHX_THROW( - "reshape_lazy: Wrong number of elements for reshape_lazy: reshape_lazy has " + - to_string(out_elems) + " elements whereas the input has " + - to_string(in_elems)); + size_t missing_dim = + inputs.front().elements() / + std::accumulate(rdims.begin(), rdims.end(), 1, std::multiplies()); + for(std::size_t i = 0; i < rdims.size(); i++) + { + if(dims[i] == dim_like{-1}) + rdims[i] = missing_dim; + } } + + auto s = reshape_dims(inputs.front(), rdims, {.lazy = true}); + if(not s.has_value()) + MIGRAPHX_THROW("reshape_lazy on axis that is not packed."); + + if(s->elements() != inputs.front().elements()) + MIGRAPHX_THROW( + "reshape_lazy: Wrong number of elements for reshape_lazy: reshape_lazy has " + + std::to_string(s->elements()) + " elements whereas the input has " + + std::to_string(inputs.front().elements())); + + assert(s->bytes() == inputs.front().bytes()); return *s; } shape compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1); - - validate_reshape_dims(name(), dims); - - const auto& s0 = inputs.front(); - if(s0.dynamic() and not s0.symbolic()) + 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 but " + "given {" + + to_string_range(dims) + "} with " + to_string(n_neg_dims) + " -1 dims"); + const auto& s0 = inputs[0]; + if(s0.dynamic()) { - // A symbolic dim has no range interpretation, so it cannot target a - // range-based input. - if(std::any_of(dims.begin(), dims.end(), is_symbolic)) - MIGRAPHX_THROW("reshape_lazy: range-based input only supports int64 dim entries"); return dyn_compute_shape(s0); } - return symbolic_compute_shape(s0); + else + { + return static_compute_shape(inputs, n_neg_dims); + } } argument compute(const dyn_output& dyn_out, std::vector args) const diff --git a/src/include/migraphx/op/squeeze.hpp b/src/include/migraphx/op/squeeze.hpp index c6020b4826a..49f1dc2873f 100644 --- a/src/include/migraphx/op/squeeze.hpp +++ b/src/include/migraphx/op/squeeze.hpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -54,44 +53,11 @@ struct squeeze } std::string name() const { return "squeeze"; } - - // Drops the size-1 axes, preserving the kept axes' strides, for static and - // symbolic input through one path. - shape symbolic_compute_shape(const shape& s) const - { - auto sym_in = s.to_symbolic(); - const auto& dds = sym_in.dyn_dims(); - const auto& strds = sym_in.dyn_strides(); - auto one = sym::lit(1); - // A dropped axis must be provably 1. - if(std::any_of(axes.begin(), axes.end(), [&](auto axis) { - return not(dds.at(axis).sym_expr == one); - })) - MIGRAPHX_THROW("SQUEEZE: axis dimension should be equal to 1; axes {" + - to_string_range(axes) + "} of input " + to_string(s)); - std::vector new_dds; - std::vector new_strides; - for(auto i : range(dds.size())) - { - const bool drop = axes.empty() ? (dds[i].sym_expr == one) - : (std::find(axes.begin(), axes.end(), i) != axes.end()); - if(not drop) - { - new_dds.push_back(dds[i]); - new_strides.push_back(strds[i]); - } - } - shape result = new_dds.empty() ? shape{s.type()} : shape{s.type(), new_dds, new_strides}; - if(not s.symbolic()) - return result.to_static(); - return result; - } - shape normalize_compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1); auto input_shape = inputs[0]; - if(input_shape.dynamic() and not input_shape.symbolic()) + if(input_shape.dynamic()) { // Allow for any dynamic_dimension that intersects with {1, 1}. // Assuming that the shape at run-time will be compatible. @@ -127,7 +93,51 @@ struct squeeze } return {input_shape.type(), dyn_dims}; } - return symbolic_compute_shape(input_shape); + else + { + auto type = input_shape.type(); + auto old_lens = input_shape.lens(); + const auto& old_strides = input_shape.strides(); + if(std::any_of( + axes.begin(), axes.end(), [&](auto axis) { return old_lens[axis] != 1; })) + { + MIGRAPHX_THROW("SQUEEZE: static axis dimension should be equal to 1; axes {" + + to_string_range(axes) + "} of input dims {" + + to_string_range(old_lens) + "}"); + } + std::vector new_lens; + std::vector new_strides; + if(axes.empty()) + { + for(auto i : range(old_lens.size())) + { + if(old_lens[i] != 1) + { + new_lens.push_back(old_lens[i]); + new_strides.push_back(old_strides[i]); + } + } + } + else + { + for(auto i : range(old_lens.size())) + { + if(std::find(axes.begin(), axes.end(), i) == axes.end()) + { + new_lens.push_back(old_lens[i]); + new_strides.push_back(old_strides[i]); + } + } + } + if(new_lens.empty()) + { + return shape{type}; + } + else + { + return shape{type, new_lens, new_strides}; + } + } } argument compute(const dyn_output& dyn_out, std::vector args) const diff --git a/src/include/migraphx/op/unsqueeze.hpp b/src/include/migraphx/op/unsqueeze.hpp index 4bee53aee26..9de33a5aea8 100644 --- a/src/include/migraphx/op/unsqueeze.hpp +++ b/src/include/migraphx/op/unsqueeze.hpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -64,92 +63,12 @@ struct unsqueeze } std::string name() const { return "unsqueeze"; } - - // Inserts the axes (sized by step), carrying the kept axes' strides through, - // for static and symbolic input through one path. - shape symbolic_compute_shape(const shape& s) const - { - auto sym_in = s.to_symbolic(); - auto type = s.type(); - std::vector old_lens(sym_in.ndim()); - std::transform(sym_in.dyn_dims().begin(), - sym_in.dyn_dims().end(), - old_lens.begin(), - [](const auto& dd) { return dd.sym_expr; }); - const auto& old_strides = sym_in.dyn_strides(); - auto is_scalar = sym_in.scalar(); - auto one = sym::lit(1); - - if(is_scalar and old_lens.size() == 1 and old_lens.front() == one) - { - shape result{type, {shape::dynamic_dimension{one}}}; - return s.symbolic() ? result : result.to_static(); - } - - if(steps.size() > axes.size()) - MIGRAPHX_THROW("UNSQUEEZE: Steps provided with no axis: " + to_string(steps.size()) + - " steps but only " + to_string(axes.size()) + " axes"); - - std::size_t new_size = old_lens.size() + axes.size(); - std::vector new_lens(new_size); - std::vector new_strides(new_size); - std::size_t p = 0; - for(auto i : range(new_size)) - { - auto axis_idx = std::find(axes.begin(), axes.end(), i) - axes.begin(); - if(axis_idx < axes.size()) - { - std::int64_t step = 1; - if(axis_idx < steps.size()) - step = steps[axis_idx]; - if(step == 0) - MIGRAPHX_THROW("UNSQUEEZE: step must be non-zero at axis " + to_string(i)); - if(is_scalar and step != 1) - MIGRAPHX_THROW("UNSQUEEZE: step must be 1 when input is scalar but step is " + - to_string(step) + " at axis " + to_string(i)); - new_lens[i] = sym::lit(step); - if(p < old_strides.size()) - { - // Only a literal dim can be proven indivisible; a symbolic - // dim is trusted and propagated as a tdiv. - auto rem = old_lens[p] % sym::lit(step); - if(rem.name() == "literal" and not(rem == sym::lit(0))) - MIGRAPHX_THROW("UNSQUEEZE: Axis dimension (" + old_lens[p].to_string() + - ") is not divisible by step (" + to_string(step) + - ") at axis " + to_string(i)); - old_lens[p] = old_lens[p] / sym::lit(step); - new_strides[i] = is_scalar ? one : old_strides[p] * old_lens[p]; - } - else - { - if(step != 1) - MIGRAPHX_THROW("UNSQUEEZE: Step must be 1 for extra axes but step is " + - to_string(step) + " at axis " + to_string(i)); - new_strides[i] = one; - } - } - else - { - new_lens[i] = old_lens[p]; - new_strides[i] = old_strides[p++]; - } - } - std::vector new_dds(new_size); - std::transform(new_lens.begin(), new_lens.end(), new_dds.begin(), [](const auto& e) { - return shape::dynamic_dimension{e}; - }); - shape result{type, new_dds, new_strides}; - if(not s.symbolic()) - return result.to_static(); - return result; - } - shape normalize_compute_shape(std::vector inputs) const { check_shapes{inputs, *this, true}.has(1); const auto& input_shape = inputs[0]; - if(input_shape.dynamic() and not input_shape.symbolic()) + if(input_shape.dynamic()) { if(not steps.empty()) { @@ -172,7 +91,66 @@ struct unsqueeze } return {input_shape.type(), dyn_dims}; } - return symbolic_compute_shape(input_shape); + else + { + auto type = input_shape.type(); + auto old_lens = input_shape.lens(); + const auto& old_strides = input_shape.strides(); + auto is_scalar = input_shape.scalar(); + + if(is_scalar and old_lens.size() == 1 and old_lens.front() == 1) + return shape{type, old_lens}; + + if(steps.size() > axes.size()) + MIGRAPHX_THROW( + "UNSQUEEZE: Steps provided with no axis: " + to_string(steps.size()) + + " steps but only " + to_string(axes.size()) + " axes"); + + std::size_t new_size = old_lens.size() + axes.size(); + + std::vector new_lens(new_size); + std::vector new_strides(new_size); + std::size_t p = 0; + for(auto i : range(new_size)) + { + auto axis_idx = std::find(axes.begin(), axes.end(), i) - axes.begin(); + if(axis_idx < axes.size()) + { + std::int64_t step = 1; + if(axis_idx < steps.size()) + step = steps[axis_idx]; + if(step == 0) + MIGRAPHX_THROW("UNSQUEEZE: step must be non-zero at axis " + to_string(i)); + if(is_scalar and step != 1) + MIGRAPHX_THROW( + "UNSQUEEZE: step must be 1 when input is scalar but step is " + + to_string(step) + " at axis " + to_string(i)); + new_lens[i] = step; + if(p < old_strides.size()) + { + if((old_lens[p] % step) != 0) + MIGRAPHX_THROW("UNSQUEEZE: Axis dimension (" + to_string(old_lens[p]) + + ") is not divisible by step (" + to_string(step) + + ") at axis " + to_string(i)); + old_lens[p] /= step; + new_strides[i] = is_scalar ? 1 : old_strides[p] * old_lens[p]; + } + else + { + if(step != 1) + MIGRAPHX_THROW("UNSQUEEZE: Step must be 1 for extra axes but step is " + + to_string(step) + " at axis " + to_string(i)); + new_strides[i] = 1; + } + } + else + { + new_lens[i] = old_lens[p]; + new_strides[i] = old_strides[p++]; + } + } + return shape{type, new_lens, new_strides}; + } } argument compute(const dyn_output& dyn_out, std::vector args) const { diff --git a/src/include/migraphx/reshape_dims.hpp b/src/include/migraphx/reshape_dims.hpp index dc552942027..8acf6ac1cf2 100644 --- a/src/include/migraphx/reshape_dims.hpp +++ b/src/include/migraphx/reshape_dims.hpp @@ -27,37 +27,22 @@ #include #include -#include -#include -#include #include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { +struct shape; + struct reshape_dims_options { bool lazy = false; }; -// nullopt when the layout can't be proven; the caller falls back to standard. -MIGRAPHX_EXPORT optional -reshape_dims(const shape& input, const std::vector& rdims, reshape_dims_options options); - -// Convenience overload for concrete dims; lifts each to a literal sym::expr. MIGRAPHX_EXPORT optional reshape_dims(const shape& input, const std::vector& rdims, reshape_dims_options options); -// Resolve reshape `dims` entries against a symbolic input: 0 copies the input dim, -// -1 is inferred as the leftover element count, literals/symbols are taken as-is. -MIGRAPHX_EXPORT std::vector -resolve_reshape_dims(const shape& sym_in, const std::vector& dims); - -// Throws if `dims` holds a range-based dynamic_dimension or more than one -1 entry. -MIGRAPHX_EXPORT void validate_reshape_dims(const std::string& name, - const std::vector& dims); - } // namespace MIGRAPHX_INLINE_NS } // namespace migraphx #endif // MIGRAPHX_GUARD_MIGRAPHX_RESHAPE_DIMS_HPP diff --git a/src/include/migraphx/shape.hpp b/src/include/migraphx/shape.hpp index 3e9b68b8f0d..9fca95bec28 100644 --- a/src/include/migraphx/shape.hpp +++ b/src/include/migraphx/shape.hpp @@ -317,13 +317,6 @@ struct MIGRAPHX_EXPORT shape */ sym::expr sym_elements() const; - /*! - * Return each dimension as a symbolic expression. Works for any shape kind: - * static dimensions become literals; symbolic dimensions return their - * expression. - */ - std::vector sym_dims() const; - /*! * Return the number of total bytes used for storage of the tensor data; includes subshapes. * For dynamic shape, returns the maximum number of bytes presuming a packed shape. diff --git a/src/include/migraphx/sym.hpp b/src/include/migraphx/sym.hpp index 54c854e49a6..dd487e53949 100644 --- a/src/include/migraphx/sym.hpp +++ b/src/include/migraphx/sym.hpp @@ -254,11 +254,6 @@ MIGRAPHX_EXPORT expr var(std::string name, interval constraint, std::set MIGRAPHX_EXPORT expr as_symbol(const expr& e, int max_depth = -1); MIGRAPHX_EXPORT bool same_symbol(const expr& a, const expr& b); -// Find distinct variables as metadata-free symbols in first-encounter order. -MIGRAPHX_EXPORT std::vector find_variables(const expr& e); -// Whether dividend is evenly divisible by divisor (integral operands only). -MIGRAPHX_EXPORT bool is_divisible(const expr& dividend, const expr& divisor); - MIGRAPHX_EXPORT expr arg(expr x); template {})> diff --git a/src/instruction.cpp b/src/instruction.cpp index 5c651221725..94028b7ee69 100644 --- a/src/instruction.cpp +++ b/src/instruction.cpp @@ -379,8 +379,6 @@ bool instruction::can_eval() const return true; if(not is_context_free(op)) return false; - if(has_finalize(op)) - return false; #if MIGRAPHX_HAS_PMR std::array storage; std::pmr::monotonic_buffer_resource resource{storage.data(), storage.size()}; @@ -395,7 +393,7 @@ bool instruction::can_eval() const bool evaluable = false; if(ins.name() == "@literal") evaluable = true; - else if(is_context_free(ins.get_operator()) and not has_finalize(ins.get_operator())) + else if(is_context_free(ins.get_operator())) evaluable = std::all_of( ins.inputs().begin(), ins.inputs().end(), [&](auto arg) { return self(*arg); }); cache.emplace(&ins, evaluable); diff --git a/src/onnx/parse_nonmaxsuppression.cpp b/src/onnx/parse_nonmaxsuppression.cpp index 4d55d5c7959..6387f855af4 100644 --- a/src/onnx/parse_nonmaxsuppression.cpp +++ b/src/onnx/parse_nonmaxsuppression.cpp @@ -22,7 +22,6 @@ * THE SOFTWARE. */ #include -#include #include #include #include @@ -40,12 +39,6 @@ struct parse_nonmaxsuppression : op_parser const onnx_parser::node_info& info, const std::vector& args) const { - if(any_of(args, [](const auto& arg) { - const auto& s = arg->get_shape(); - return not s.dynamic() and s.elements() == 0; - })) - return info.add_instruction(make_op("undefined")); - auto op = parser.load(opd.op_name, info); auto nms_ins = info.add_instruction(op, args); // slice with variable ends to handle dynamic shape output. diff --git a/src/op/builder/floor_div.cpp b/src/op/builder/floor_div.cpp deleted file mode 100644 index f5afde2a97d..00000000000 --- a/src/op/builder/floor_div.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/* 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 - -namespace migraphx { -inline namespace MIGRAPHX_INLINE_NS { -namespace op { -namespace builder { - -// floor_div has no native op: floor(a / b) over common operands. -struct floor_div : op_builder -{ - std::vector - insert(module& m, instruction_ref ins, const std::vector& args) const - { - auto quotient = insert_common_op(m, ins, make_op("div"), args); - return {m.insert_instruction(ins, make_op("floor"), quotient)}; - } -}; - -} // namespace builder -} // namespace op -} // namespace MIGRAPHX_INLINE_NS -} // namespace migraphx diff --git a/src/op/builder/gather_elements.cpp b/src/op/builder/gather_elements.cpp deleted file mode 100644 index 44088266122..00000000000 --- a/src/op/builder/gather_elements.cpp +++ /dev/null @@ -1,95 +0,0 @@ -/* 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 - -namespace migraphx { -inline namespace MIGRAPHX_INLINE_NS { -namespace op { -namespace builder { - -// gather_elements has no native op: flatten the input and gather element-wise using per-element -// offsets built from the input strides. -struct gather_elements : op_builder -{ - int64_t axis = 0; - - template - static auto reflect(Self& self, F f) - { - return pack(f(self.axis, "axis")); - } - - std::vector - insert(module& m, instruction_ref ins, const std::vector& args) const - { - auto arg_data = m.insert_instruction(ins, make_op("contiguous"), args[0]); - auto arg_ind = m.insert_instruction(ins, make_op("contiguous"), args[1]); - - auto data_s = arg_data->get_shape(); - auto ind_s = arg_ind->get_shape(); - if(data_s.lens().size() != ind_s.lens().size()) - MIGRAPHX_THROW("gather_elements: input data and index must have the same rank"); - - int n_rank = data_s.lens().size(); - int tuned_axis = tune_axis(n_rank, axis, "gather_elements"); - auto axis_stride = data_s.strides()[tuned_axis]; - - int64_t data_elem_num = data_s.elements(); - arg_data = - m.insert_instruction(ins, make_op("reshape", {{"dims", {data_elem_num}}}), arg_data); - - // flat offset of every index position, and its coordinate along the gathered axis - std::size_t elem_num = ind_s.elements(); - std::vector data_indices(elem_num); - std::vector axis_indices(elem_num); - for(std::size_t i = 0; i < elem_num; ++i) - { - auto multi = ind_s.multi(i); - data_indices[i] = data_s.index(multi); - axis_indices[i] = multi[tuned_axis]; - } - - auto l_shape_idx = m.add_literal(literal(ind_s, data_indices.begin(), data_indices.end())); - auto l_dim_idx = m.add_literal(literal(ind_s, axis_indices.begin(), axis_indices.end())); - auto l_stride = m.add_literal(literal{{ind_s.type(), {1}}, {axis_stride}}); - l_stride = m.insert_instruction( - ins, make_op("multibroadcast", {{"out_lens", ind_s.lens()}}), l_stride); - - auto dim_diff = m.insert_instruction(ins, make_op("sub"), arg_ind, l_dim_idx); - auto delta = m.insert_instruction(ins, make_op("mul"), dim_diff, l_stride); - auto ind = m.insert_instruction(ins, make_op("add"), l_shape_idx, delta); - return {m.insert_instruction(ins, make_op("gather", {{"axis", 0}}), arg_data, ind)}; - } -}; - -} // namespace builder -} // namespace op -} // namespace MIGRAPHX_INLINE_NS -} // namespace migraphx diff --git a/src/op/builder/glu.cpp b/src/op/builder/glu.cpp deleted file mode 100644 index 68fd4a16b64..00000000000 --- a/src/op/builder/glu.cpp +++ /dev/null @@ -1,67 +0,0 @@ -/* 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 - -namespace migraphx { -inline namespace MIGRAPHX_INLINE_NS { -namespace op { -namespace builder { - -// glu has no native op: split in half along `axis`, gate the first half by sigmoid(second). -struct glu : op_builder -{ - int64_t axis = -1; - - template - static auto reflect(Self& self, F f) - { - return pack(f(self.axis, "axis")); - } - - std::vector - insert(module& m, instruction_ref ins, const std::vector& args) const - { - auto x = args[0]; - auto lens = x->get_shape().lens(); - auto ax = tune_axis(lens.size(), axis, "glu"); - int64_t len = lens[ax]; - - auto first = m.insert_instruction( - ins, make_op("slice", {{"axes", {ax}}, {"starts", {0}}, {"ends", {len / 2}}}), x); - auto second = m.insert_instruction( - ins, make_op("slice", {{"axes", {ax}}, {"starts", {len / 2}}, {"ends", {len}}}), x); - auto gate = m.insert_instruction(ins, make_op("sigmoid"), second); - return {m.insert_instruction(ins, make_op("mul"), first, gate)}; - } -}; - -} // namespace builder -} // namespace op -} // namespace MIGRAPHX_INLINE_NS -} // namespace migraphx diff --git a/src/op/builder/group_norm.cpp b/src/op/builder/group_norm.cpp deleted file mode 100644 index 4049ee3e2fe..00000000000 --- a/src/op/builder/group_norm.cpp +++ /dev/null @@ -1,80 +0,0 @@ -/* 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 - -namespace migraphx { -inline namespace MIGRAPHX_INLINE_NS { -namespace op { -namespace builder { - -// group_norm has no native op: normalize each channel group, then the affine. -struct group_norm : op_builder -{ - float epsilon = 1e-5f; - int64_t num_groups = 1; - - template - static auto reflect(Self& self, F f) - { - return pack(f(self.epsilon, "epsilon"), f(self.num_groups, "num_groups")); - } - - std::vector - insert(module& m, instruction_ref ins, const std::vector& args) const - { - auto x = args[0]; - auto lens = x->get_shape().lens(); - if(lens.size() <= 2 or lens[1] % num_groups != 0) - MIGRAPHX_THROW("group_norm op_builder: input rank must be > 2 and num_groups must " - "divide the channel dim"); - - std::vector grouped_dims = {static_cast(lens[0]), num_groups, -1}; - auto grouped = m.insert_instruction(ins, make_op("reshape", {{"dims", grouped_dims}}), x); - auto norm = op::builder::insert( - "normalize", m, ins, {grouped}, {{"axes", {-1}}, {"epsilon", epsilon}}) - .front(); - - std::vector out_dims(lens.begin(), lens.end()); - auto norm_r = m.insert_instruction(ins, make_op("reshape", {{"dims", out_dims}}), norm); - - // unsqueeze the per-channel scale/bias to broadcast over the spatial dims - std::vector unsqueeze_axes(lens.size() - 2); - std::iota(unsqueeze_axes.begin(), unsqueeze_axes.end(), 1); - auto scale = - m.insert_instruction(ins, make_op("unsqueeze", {{"axes", unsqueeze_axes}}), args[1]); - auto bias = - m.insert_instruction(ins, make_op("unsqueeze", {{"axes", unsqueeze_axes}}), args[2]); - auto scaled = insert_common_op(m, ins, "mul", norm_r, scale); - return {insert_common_op(m, ins, "add", scaled, bias)}; - } -}; - -} // namespace builder -} // namespace op -} // namespace MIGRAPHX_INLINE_NS -} // namespace migraphx diff --git a/src/op/builder/hardsigmoid.cpp b/src/op/builder/hardsigmoid.cpp deleted file mode 100644 index f396f06829e..00000000000 --- a/src/op/builder/hardsigmoid.cpp +++ /dev/null @@ -1,67 +0,0 @@ -/* 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 - -namespace migraphx { -inline namespace MIGRAPHX_INLINE_NS { -namespace op { -namespace builder { - -// hardsigmoid has no native op: clip(alpha * x + beta, 0, 1). -struct hardsigmoid : op_builder -{ - float alpha = 1.0f / 6.0f; - float beta = 0.5f; - - template - static auto reflect(Self& self, F f) - { - return pack(f(self.alpha, "alpha"), f(self.beta, "beta")); - } - - std::vector - insert(module& m, instruction_ref ins, const std::vector& args) const - { - auto x = args[0]; - auto type = x->get_shape().type(); - - auto alpha_lit = m.add_literal({type, {alpha}}); - auto beta_lit = m.add_literal({type, {beta}}); - auto lo = m.add_literal({type, {0.0f}}); - auto hi = m.add_literal({type, {1.0f}}); - - auto scaled = insert_common_op(m, ins, "mul", alpha_lit, x); - auto shifted = insert_common_op(m, ins, "add", beta_lit, scaled); - return {insert_common_op(m, ins, "clip", shifted, lo, hi)}; - } -}; - -} // namespace builder -} // namespace op -} // namespace MIGRAPHX_INLINE_NS -} // namespace migraphx diff --git a/src/op/builder/include/migraphx/op/builder/kit.hpp b/src/op/builder/include/migraphx/op/builder/kit.hpp index ef93f8ae482..21f362f8b52 100644 --- a/src/op/builder/include/migraphx/op/builder/kit.hpp +++ b/src/op/builder/include/migraphx/op/builder/kit.hpp @@ -66,17 +66,7 @@ struct kit : auto_register op_builder_if from_builder(const std::string& op_builder) const { - // Resolve lazily: the target builder may register after this kit's apply() - // runs during static initialization. - return op_builder_if{ - [=](module& m, - instruction_ref ins, - const std::vector& args, - const std::vector& module_args, - const value& options) { - return get_op_builder_if(op_builder).bld_func(m, ins, args, module_args, options); - }, - [=] { return get_op_builder_if(op_builder).to_val_func(); }}; + return get_op_builder_if(op_builder); } op_builder_if with_common(const op_builder_if& obi, common_options coptions = {}) const diff --git a/src/op/builder/instance_norm.cpp b/src/op/builder/instance_norm.cpp deleted file mode 100644 index 774b851f5be..00000000000 --- a/src/op/builder/instance_norm.cpp +++ /dev/null @@ -1,83 +0,0 @@ -/* 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 - -namespace migraphx { -inline namespace MIGRAPHX_INLINE_NS { -namespace op { -namespace builder { - -// instance_norm has no native op: normalize from input stats, then the affine. -struct instance_norm : op_builder -{ - float epsilon = 1e-5f; - - template - static auto reflect(Self& self, F f) - { - return pack(f(self.epsilon, "epsilon")); - } - - std::vector - insert(module& m, instruction_ref ins, const std::vector& args) const - { - auto x = args[0]; - int64_t rank = x->get_shape().ndim(); - if(rank < 2) - MIGRAPHX_THROW("instance_norm op_builder: input rank must be at least 2"); - - // reduce over the batch and spatial dims, keeping the channel dim - std::vector axes = {0}; - for(int64_t i = 2; i < rank; ++i) - axes.push_back(i); - - auto norm = - op::builder::insert("normalize", m, ins, {x}, {{"axes", axes}, {"epsilon", epsilon}}) - .front(); - - // unsqueeze the per-channel scale/bias to broadcast over the spatial dims - auto scale = args[1]; - auto bias = args[2]; - if(rank > 2) - { - std::vector unsqueeze_axes(rank - 2); - std::iota(unsqueeze_axes.begin(), unsqueeze_axes.end(), 1); - scale = - m.insert_instruction(ins, make_op("unsqueeze", {{"axes", unsqueeze_axes}}), scale); - bias = - m.insert_instruction(ins, make_op("unsqueeze", {{"axes", unsqueeze_axes}}), bias); - } - auto scaled = insert_common_op(m, ins, "mul", norm, scale); - return {insert_common_op(m, ins, "add", scaled, bias)}; - } -}; - -} // namespace builder -} // namespace op -} // namespace MIGRAPHX_INLINE_NS -} // namespace migraphx diff --git a/src/op/builder/layer_norm.cpp b/src/op/builder/layer_norm.cpp deleted file mode 100644 index c74045fdf30..00000000000 --- a/src/op/builder/layer_norm.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* 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 { -namespace op { -namespace builder { - -// layer_norm has no native op: normalize over axes, then the affine scale/bias. -struct layer_norm : op_builder -{ - float epsilon = 1e-5f; - std::vector axes = {-1}; - - template - static auto reflect(Self& self, F f) - { - return pack(f(self.epsilon, "epsilon"), f(self.axes, "axes")); - } - - std::vector - insert(module& m, instruction_ref ins, const std::vector& args) const - { - auto norm = op::builder::insert( - "normalize", m, ins, {args[0]}, {{"axes", axes}, {"epsilon", epsilon}}) - .front(); - auto scaled = insert_common_op(m, ins, "mul", norm, args[1]); - return {insert_common_op(m, ins, "add", scaled, args[2])}; - } -}; - -} // namespace builder -} // namespace op -} // namespace MIGRAPHX_INLINE_NS -} // namespace migraphx diff --git a/src/op/builder/normalize.cpp b/src/op/builder/normalize.cpp deleted file mode 100644 index 802e4e8c6c7..00000000000 --- a/src/op/builder/normalize.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/* 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 - -namespace migraphx { -inline namespace MIGRAPHX_INLINE_NS { -namespace op { -namespace builder { - -// (x - mean) * rsqrt(var + epsilon) reduced over `axes`, with a biased variance. Shared by the -// normalization builders (layer_norm, group_norm, instance_norm). -struct normalize : op_builder -{ - std::vector axes = {}; - float epsilon = 1e-5f; - - template - static auto reflect(Self& self, F f) - { - return pack(f(self.axes, "axes"), f(self.epsilon, "epsilon")); - } - - std::vector - insert(module& m, instruction_ref ins, const std::vector& args) const - { - auto x = args[0]; - auto x_type = x->get_shape().type(); - auto mean = m.insert_instruction(ins, make_op("reduce_mean", {{"axes", axes}}), x); - auto x_sub = insert_common_op(m, ins, "sub", x, mean); - auto sqdiff = insert_common_op(m, ins, "sqdiff", x, mean); - auto variance = m.insert_instruction(ins, make_op("reduce_mean", {{"axes", axes}}), sqdiff); - auto eps = m.add_literal(literal{shape{x_type}, {epsilon}}); - auto var_eps = insert_common_op(m, ins, "add", variance, eps); - auto rsqrt = m.insert_instruction(ins, make_op("rsqrt"), var_eps); - return {insert_common_op(m, ins, "mul", x_sub, rsqrt)}; - } -}; - -} // namespace builder -} // namespace op -} // namespace MIGRAPHX_INLINE_NS -} // namespace migraphx diff --git a/src/op/builder/selu.cpp b/src/op/builder/selu.cpp deleted file mode 100644 index f92103d1c66..00000000000 --- a/src/op/builder/selu.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/* 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 - -namespace migraphx { -inline namespace MIGRAPHX_INLINE_NS { -namespace op { -namespace builder { - -// selu has no native op: gamma * (max(0, x) + min(0, alpha * (exp(x) - 1))). -struct selu : op_builder -{ - float alpha = 1.6732632423543772f; - float gamma = 1.0507009873554805f; - - template - static auto reflect(Self& self, F f) - { - return pack(f(self.alpha, "alpha"), f(self.gamma, "gamma")); - } - - std::vector - insert(module& m, instruction_ref ins, const std::vector& args) const - { - auto x = args[0]; - auto type = x->get_shape().type(); - - auto zero = m.add_literal({type, {0.0f}}); - auto one = m.add_literal({type, {1.0f}}); - auto alpha_lit = m.add_literal({type, {alpha}}); - auto gamma_lit = m.add_literal({type, {gamma}}); - - auto linear = insert_common_op(m, ins, "max", zero, x); - auto exp_x = m.insert_instruction(ins, make_op("exp"), x); - auto exp_sub = insert_common_op(m, ins, "sub", exp_x, one); - auto exp_mul = insert_common_op(m, ins, "mul", alpha_lit, exp_sub); - auto exp_part = insert_common_op(m, ins, "min", zero, exp_mul); - auto sum = insert_common_op(m, ins, "add", linear, exp_part); - return {insert_common_op(m, ins, "mul", gamma_lit, sum)}; - } -}; - -} // namespace builder -} // namespace op -} // namespace MIGRAPHX_INLINE_NS -} // namespace migraphx diff --git a/src/op/builder/softsign.cpp b/src/op/builder/softsign.cpp deleted file mode 100644 index 866ec723bd2..00000000000 --- a/src/op/builder/softsign.cpp +++ /dev/null @@ -1,53 +0,0 @@ -/* 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 - -namespace migraphx { -inline namespace MIGRAPHX_INLINE_NS { -namespace op { -namespace builder { - -// softsign has no native op: x / (1 + |x|). -struct softsign : op_builder -{ - std::vector - insert(module& m, instruction_ref ins, const std::vector& args) const - { - auto x = args[0]; - auto type = x->get_shape().type(); - auto one = m.add_literal({type, {1.0f}}); - auto abs_x = m.insert_instruction(ins, make_op("abs"), x); - auto denom = insert_common_op(m, ins, "add", abs_x, one); - return {insert_common_op(m, ins, "div", x, denom)}; - } -}; - -} // namespace builder -} // namespace op -} // namespace MIGRAPHX_INLINE_NS -} // namespace migraphx diff --git a/src/op/builder/torch/as_strided.cpp b/src/op/builder/torch/as_strided.cpp deleted file mode 100644 index dc131724205..00000000000 --- a/src/op/builder/torch/as_strided.cpp +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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 - -namespace migraphx { -inline namespace MIGRAPHX_INLINE_NS { -namespace op { -namespace builder { - -// as_strided has no native op: gather each element from its strided storage offset. -struct torch_as_strided : op_builder -{ - std::vector size; - std::vector stride; - int64_t storage_offset = 0; - - template - static auto reflect(Self& self, F f) - { - return pack(f(self.size, "size"), - f(self.stride, "stride"), - f(self.storage_offset, "storage_offset")); - } - - static std::vector names() { return {"tm::as_strided"}; } - - std::vector - insert(module& m, instruction_ref ins, const std::vector& args) const - { - if(size.size() != stride.size()) - MIGRAPHX_THROW("as_strided: size and stride must have the same length"); - shape strided{shape::int64_type, - std::vector(size.begin(), size.end()), - std::vector(stride.begin(), stride.end())}; - std::vector data(strided.elements()); - for(std::size_t i = 0; i < data.size(); ++i) - data[i] = storage_offset + strided.index(i); - auto indices = m.add_literal( - literal{shape{shape::int64_type, {data.size()}}, data.begin(), data.end()}); - - auto flat_inp = m.insert_instruction(ins, make_op("contiguous"), args[0]); - flat_inp = m.insert_instruction(ins, make_op("reshape", {{"dims", {-1}}}), flat_inp); - auto gathered = - m.insert_instruction(ins, make_op("gather", {{"axis", 0}}), flat_inp, indices); - return {m.insert_instruction(ins, make_op("reshape", {{"dims", size}}), gathered)}; - } -}; - -} // namespace builder -} // namespace op -} // namespace MIGRAPHX_INLINE_NS -} // namespace migraphx diff --git a/src/op/builder/torch/conv_transpose.cpp b/src/op/builder/torch/conv_transpose.cpp deleted file mode 100644 index f7bbb92fa0f..00000000000 --- a/src/op/builder/torch/conv_transpose.cpp +++ /dev/null @@ -1,103 +0,0 @@ -/* - * 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 - -namespace migraphx { -inline namespace MIGRAPHX_INLINE_NS { -namespace op { -namespace builder { - -// conv_transpose has no native op: convolution_backwards + output_padding crop + channel bias. -struct torch_conv_transpose : op_builder -{ - std::vector stride; - std::vector padding; - std::vector dilation; - std::vector output_padding; - int group = 1; - - template - static auto reflect(Self& self, F f) - { - return pack(f(self.stride, "stride"), - f(self.padding, "padding"), - f(self.dilation, "dilation"), - f(self.output_padding, "output_padding"), - f(self.group, "group")); - } - - static std::vector names() { return {"tm::conv_transpose"}; } - - std::vector - insert(module& m, instruction_ref ins, const std::vector& args) const - { - // output_padding cannot be expressed by the op: run it unpadded, then crop - bool crop = std::any_of( - output_padding.begin(), output_padding.end(), [](std::size_t o) { return o != 0; }); - auto pad = crop ? std::vector(padding.size(), 0) : padding; - auto out = m.insert_instruction( - ins, - make_op( - "convolution_backwards", - {{"stride", stride}, {"padding", pad}, {"dilation", dilation}, {"group", group}}), - args[0], - args[1]); - - if(crop) - { - auto spatial = out->get_shape().lens(); - std::vector axes(output_padding.size()); - std::vector starts(output_padding.size()); - std::vector ends(output_padding.size()); - for(std::size_t i = 0; i < output_padding.size(); ++i) - { - axes[i] = static_cast(i + 2); - starts[i] = static_cast(padding[i]); - ends[i] = static_cast(spatial[i + 2] - padding[i] + output_padding[i]); - } - out = m.insert_instruction( - ins, make_op("slice", {{"axes", axes}, {"starts", starts}, {"ends", ends}}), out); - } - - if(args.size() < 3) - return {out}; - - auto out_lens = out->get_shape().lens(); - auto bias = m.insert_instruction( - ins, make_op("broadcast", {{"axis", 1}, {"out_lens", out_lens}}), args[2]); - return {m.insert_instruction(ins, make_op("add"), out, bias)}; - } -}; - -} // namespace builder -} // namespace op -} // namespace MIGRAPHX_INLINE_NS -} // namespace migraphx diff --git a/src/op/builder/torch/index_copy.cpp b/src/op/builder/torch/index_copy.cpp deleted file mode 100644 index c050212561b..00000000000 --- a/src/op/builder/torch/index_copy.cpp +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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 - -namespace migraphx { -inline namespace MIGRAPHX_INLINE_NS { -namespace op { -namespace builder { - -// index_copy has no native op: scatter src into the rows of `dim` listed in the 1-D index. -struct torch_index_copy : op_builder -{ - int64_t dim = 0; - - template - static auto reflect(Self& self, F f) - { - return pack(f(self.dim, "dim")); - } - - static std::vector names() { return {"tm::index_copy"}; } - - std::vector - insert(module& m, instruction_ref ins, const std::vector& args) const - { - auto inp = args[0]; - auto idx = args[1]; - auto src = args[2]; - auto src_lens = src->get_shape().lens(); - auto axis = tune_axis(src_lens.size(), dim, "index_copy"); - - std::vector rsp(src_lens.size(), 1); - rsp[axis] = idx->get_shape().lens().at(0); - auto scatter_idx = m.insert_instruction(ins, make_op("reshape", {{"dims", rsp}}), idx); - scatter_idx = m.insert_instruction( - ins, make_op("multibroadcast", {{"out_lens", src_lens}}), scatter_idx); - return {m.insert_instruction( - ins, make_op("scatter_none", {{"axis", axis}}), {inp, scatter_idx, src})}; - } -}; - -} // namespace builder -} // namespace op -} // namespace MIGRAPHX_INLINE_NS -} // namespace migraphx diff --git a/src/op/builder/torch/linear.cpp b/src/op/builder/torch/linear.cpp deleted file mode 100644 index 1a66a7d81a1..00000000000 --- a/src/op/builder/torch/linear.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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 - -namespace migraphx { -inline namespace MIGRAPHX_INLINE_NS { -namespace op { -namespace builder { - -// linear reuses the gemm builder; ND inputs are flattened to rank 2. -struct torch_linear : op_builder -{ - static std::vector names() { return {"tm::linear"}; } - - std::vector - insert(module& m, instruction_ref ins, const std::vector& args) const - { - const value gemm_opts{{"transB", true}}; - auto lens = args[0]->get_shape().lens(); - if(lens.size() == 2) - return op::builder::insert("gemm", m, ins, args, gemm_opts); - - auto rows = args[0]->get_shape().elements() / lens.back(); - std::vector flat = {static_cast(rows), static_cast(lens.back())}; - auto x2d = m.insert_instruction(ins, make_op("reshape", {{"dims", flat}}), args[0]); - - auto gemm_args = args; - gemm_args[0] = x2d; - auto out = op::builder::insert("gemm", m, ins, gemm_args, gemm_opts).front(); - - std::vector out_dims(lens.begin(), lens.end() - 1); - out_dims.push_back(static_cast(out->get_shape().lens().back())); - return {m.insert_instruction(ins, make_op("reshape", {{"dims", out_dims}}), out)}; - } -}; - -} // namespace builder -} // namespace op -} // namespace MIGRAPHX_INLINE_NS -} // namespace migraphx diff --git a/src/op/builder/torch/lstm.cpp b/src/op/builder/torch/lstm.cpp deleted file mode 100644 index 23c39cc5bc7..00000000000 --- a/src/op/builder/torch/lstm.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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 - -namespace migraphx { -inline namespace MIGRAPHX_INLINE_NS { -namespace op { -namespace builder { - -// lstm expands into an lstm op plus its last hidden-state and last cell-state outputs. -struct torch_lstm : op_builder -{ - std::size_t hidden_size = 1; - std::vector actv_funcs{}; - rnn_direction direction = rnn_direction::forward; - float clip = 0.0f; - int input_forget = 0; - - template - static auto reflect(Self& self, F f) - { - return pack(f(self.hidden_size, "hidden_size"), - f(self.actv_funcs, "actv_func"), - f(self.direction, "direction"), - f(self.clip, "clip"), - f(self.input_forget, "input_forget")); - } - - static std::vector names() { return {"tm::lstm"}; } - - std::vector - insert(module& m, instruction_ref ins, const std::vector& args) const - { - auto self = *this; - if(self.actv_funcs.empty()) - { - self.actv_funcs = {make_op("sigmoid"), make_op("tanh"), make_op("tanh")}; - if(self.direction == rnn_direction::bidirectional) - { - self.actv_funcs.insert(self.actv_funcs.end(), - {make_op("sigmoid"), make_op("tanh"), make_op("tanh")}); - } - } - auto hidden_states = - m.insert_instruction(ins, make_op("lstm", migraphx::to_value(self)), args); - auto last_hs = m.insert_instruction(ins, make_op("rnn_last_hs_output"), hidden_states); - auto last_cell = m.insert_instruction(ins, make_op("rnn_last_cell_output"), hidden_states); - return {hidden_states, last_hs, last_cell}; - } -}; - -} // namespace builder -} // namespace op -} // namespace MIGRAPHX_INLINE_NS -} // namespace migraphx diff --git a/src/op/builder/torch/nan_to_num.cpp b/src/op/builder/torch/nan_to_num.cpp deleted file mode 100644 index 6feb144886b..00000000000 --- a/src/op/builder/torch/nan_to_num.cpp +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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 - -namespace migraphx { -inline namespace MIGRAPHX_INLINE_NS { -namespace op { -namespace builder { - -// nan_to_num has no native op: replace NaN/+inf/-inf with the given values. -struct torch_nan_to_num : op_builder -{ - float nan = 0.0f; - float posinf = std::numeric_limits::max(); - float neginf = std::numeric_limits::lowest(); - - template - static auto reflect(Self& self, F f) - { - return pack(f(self.nan, "nan"), f(self.posinf, "posinf"), f(self.neginf, "neginf")); - } - - static std::vector names() { return {"tm::nan_to_num"}; } - - std::vector - insert(module& m, instruction_ref ins, const std::vector& args) const - { - auto x = args[0]; - auto type = x->get_shape().type(); - - auto nan_lit = m.add_literal({type, {nan}}); - auto zero = m.add_literal({type, {0.0f}}); - auto posinf_lit = m.add_literal({type, {posinf}}); - auto neginf_lit = m.add_literal({type, {neginf}}); - - // where selects per-element, so inputs are broadcast but not type-promoted - const common_options no_promote{.common_type = false}; - const auto where = make_op("where"); - auto select = [&](instruction_ref cond, instruction_ref val, instruction_ref other) { - return insert_common_op(m, ins, where, {cond, val, other}, no_promote); - }; - - auto is_nan = m.insert_instruction(ins, make_op("isnan"), x); - auto result = select(is_nan, nan_lit, x); - auto is_inf = m.insert_instruction(ins, make_op("isinf"), x); - auto less = insert_common_op(m, ins, "less", x, zero); - auto greater = insert_common_op(m, ins, "greater", x, zero); - auto neg_mask = insert_common_op(m, ins, "logical_and", less, is_inf); - auto pos_mask = insert_common_op(m, ins, "logical_and", greater, is_inf); - result = select(neg_mask, neginf_lit, result); - return {select(pos_mask, posinf_lit, result)}; - } -}; - -} // namespace builder -} // namespace op -} // namespace MIGRAPHX_INLINE_NS -} // namespace migraphx diff --git a/src/op/builder/torch/scatter_reduce.cpp b/src/op/builder/torch/scatter_reduce.cpp deleted file mode 100644 index 403db2f1a05..00000000000 --- a/src/op/builder/torch/scatter_reduce.cpp +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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 -#include -#include -#include -#include - -namespace migraphx { -inline namespace MIGRAPHX_INLINE_NS { -namespace op { -namespace builder { - -// scatter_reduce has no native op: use the matching reduction scatter op; for include_self=false -// the target positions are first overwritten with the reduction identity so they drop out. -struct torch_scatter_reduce : op_builder -{ - int64_t dim = 0; - std::string reduce = "sum"; - bool include_self = true; - - template - static auto reflect(Self& self, F f) - { - return pack( - f(self.dim, "dim"), f(self.reduce, "reduce"), f(self.include_self, "include_self")); - } - - static std::vector names() { return {"tm::scatter_reduce"}; } - - std::vector - insert(module& m, instruction_ref ins, const std::vector& args) const - { - const std::unordered_map reduce_map = {{"mean", "scatter_none"}, - {"sum", "scatter_add"}, - {"prod", "scatter_mul"}, - {"amax", "scatter_max"}, - {"amin", "scatter_min"}}; - if(reduce_map.count(reduce) == 0) - MIGRAPHX_THROW("scatter_reduce: unsupported reduction '" + reduce + "'"); - - auto inp = args[0]; - auto idx = args[1]; - auto src = args[2]; - auto axis = tune_axis(inp->get_shape().ndim(), dim, "scatter_reduce"); - - if(not include_self and reduce != "mean") - { - argument id_arg{shape{inp->get_shape().type(), {1}}}; - id_arg.visit([&](auto v) { - using type = std::remove_cv_t; - if(reduce == "sum") - v.front() = type(0); - else if(reduce == "prod") - v.front() = type(1); - else if(reduce == "amax") - v.front() = std::numeric_limits::lowest(); - else - v.front() = std::numeric_limits::max(); - }); - auto identity = m.add_literal(id_arg.get_shape(), id_arg.data()); - identity = m.insert_instruction( - ins, make_op("multibroadcast", {{"out_lens", idx->get_shape().lens()}}), identity); - inp = m.insert_instruction( - ins, make_op("scatter_none", {{"axis", axis}}), {inp, idx, identity}); - } - - return {m.insert_instruction( - ins, make_op(reduce_map.at(reduce), {{"axis", axis}}), {inp, idx, src})}; - } -}; - -} // namespace builder -} // namespace op -} // namespace MIGRAPHX_INLINE_NS -} // namespace migraphx diff --git a/src/op/builder/torch/slice_scatter.cpp b/src/op/builder/torch/slice_scatter.cpp deleted file mode 100644 index 5f60f4386ef..00000000000 --- a/src/op/builder/torch/slice_scatter.cpp +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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 - -namespace migraphx { -inline namespace MIGRAPHX_INLINE_NS { -namespace op { -namespace builder { - -// slice_scatter has no native op: scatter src into the [start:end:step] slice along `dim`. -struct torch_slice_scatter : op_builder -{ - int64_t dim = 0; - int64_t start = 0; - int64_t end = 0; - int64_t step = 1; - - template - static auto reflect(Self& self, F f) - { - return pack( - f(self.dim, "dim"), f(self.start, "start"), f(self.end, "end"), f(self.step, "step")); - } - - static std::vector names() { return {"tm::slice_scatter"}; } - - std::vector - insert(module& m, instruction_ref ins, const std::vector& args) const - { - shape idx_shape{shape::int64_type, args[1]->get_shape().lens()}; - auto axis = tune_axis(idx_shape.ndim(), dim, "slice_scatter"); - std::vector data(idx_shape.elements()); - for(std::size_t i = 0; i < data.size(); ++i) - data[i] = start + step * idx_shape.multi(i)[axis]; - auto indices = m.add_literal(literal{idx_shape, data.begin(), data.end()}); - - auto std_input = m.insert_instruction(ins, make_op("contiguous"), args[0]); - auto std_src = m.insert_instruction(ins, make_op("contiguous"), args[1]); - return {m.insert_instruction( - ins, make_op("scatter_none", {{"axis", axis}}), {std_input, indices, std_src})}; - } -}; - -} // namespace builder -} // namespace op -} // namespace MIGRAPHX_INLINE_NS -} // namespace migraphx diff --git a/src/op/builder/torch/std.cpp b/src/op/builder/torch/std.cpp deleted file mode 100644 index 18265b956e6..00000000000 --- a/src/op/builder/torch/std.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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 -#include - -namespace migraphx { -inline namespace MIGRAPHX_INLINE_NS { -namespace op { -namespace builder { - -// std has no native op: sqrt of the corrected variance reduced over axes. -struct torch_std : op_builder -{ - std::vector axes = {}; - bool keepdim = false; - float correction = 1.0f; - - template - static auto reflect(Self& self, F f) - { - return pack( - f(self.axes, "axes"), f(self.keepdim, "keepdim"), f(self.correction, "correction")); - } - - static std::vector names() { return {"tm::std"}; } - - std::vector - insert(module& m, instruction_ref ins, const std::vector& args) const - { - auto x = args[0]; - auto lens = x->get_shape().lens(); - auto type = x->get_shape().type(); - - auto n = std::accumulate( - axes.begin(), axes.end(), std::size_t{1}, [&](std::size_t acc, int64_t a) { - return acc * lens[tune_axis(lens.size(), a, "std")]; - }); - - auto mean = m.insert_instruction(ins, make_op("reduce_mean", {{"axes", axes}}), x); - auto sub = insert_common_op(m, ins, "sub", x, mean); - auto sq = insert_common_op(m, ins, "mul", sub, sub); - auto sum = m.insert_instruction(ins, make_op("reduce_sum", {{"axes", axes}}), sq); - auto denom = m.add_literal({type, {static_cast(n) - correction}}); - auto var = insert_common_op(m, ins, "div", sum, denom); - auto out = m.insert_instruction(ins, make_op("sqrt"), var); - if(not keepdim) - out = m.insert_instruction(ins, make_op("squeeze", {{"axes", axes}}), out); - return {out}; - } -}; - -} // namespace builder -} // namespace op -} // namespace MIGRAPHX_INLINE_NS -} // namespace migraphx diff --git a/src/op/builder/torch_kit.cpp b/src/op/builder/torch_kit.cpp index ca9535b6638..b13d9cc37cf 100644 --- a/src/op/builder/torch_kit.cpp +++ b/src/op/builder/torch_kit.cpp @@ -23,29 +23,66 @@ * */ -#include #include +#include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { namespace op { namespace builder { -// Registration point for the torch kit. The tm:: composite builders are defined in -// op/builder/torch/*.cpp and auto-register themselves; this only wires up the passthrough -// ops and the aliases to existing shared builders. +struct torch_lstm : op_builder +{ + std::size_t hidden_size = 1; + std::vector actv_funcs{}; + rnn_direction direction = rnn_direction::forward; + float clip = 0.0f; + int input_forget = 0; + + template + static auto reflect(Self& self, F f) + { + return pack(f(self.hidden_size, "hidden_size"), + f(self.actv_funcs, "actv_func"), + f(self.direction, "direction"), + f(self.clip, "clip"), + f(self.input_forget, "input_forget")); + } + + static std::vector names() { return {"tm::lstm"}; } + + std::vector + insert(module& m, instruction_ref ins, const std::vector& args) const + { + auto self = *this; + if(self.actv_funcs.empty()) + { + self.actv_funcs = {make_op("sigmoid"), make_op("tanh"), make_op("tanh")}; + if(self.direction == rnn_direction::bidirectional) + { + self.actv_funcs.insert(self.actv_funcs.end(), + {make_op("sigmoid"), make_op("tanh"), make_op("tanh")}); + } + } + auto hidden_states = + m.insert_instruction(ins, make_op("lstm", migraphx::to_value(self)), args); + auto last_hs = m.insert_instruction(ins, make_op("rnn_last_hs_output"), hidden_states); + auto last_cell = m.insert_instruction(ins, make_op("rnn_last_cell_output"), hidden_states); + return {hidden_states, last_hs, last_cell}; + } +}; + struct torch_kit : kit { std::string prefix() const { return "tm::"; } void apply() const { this->common_ops({ - "abs", "acos", "add", "asin", "atan", "bitwise_and", "ceil", - "convert", "cos", "cosh", "div", "elu", "equal", "erf", - "exp", "floor", "fmod", "greater", "isinf", "isnan", "leaky_relu", - "less", "log", "log2", "logical_and", "max", "min", "mul", - "neg", "not", "pow", "recip", "relu", "rsqrt", "sigmoid", - "sign", "sin", "sinh", "sqrt", "sub", "tan", "tanh", + "ceil", "convert", "cos", "cosh", "div", "dot", "elu", "equal", + "erf", "exp", "floor", "fmod", "greater", "isinf", "isnan", "leaky_relu", + "less", "log", "log2", "logical_and", "max", "min", "mul", "neg", + "not", "pow", "recip", "relu", "rsqrt", "sigmoid", "sign", "sin", + "sinh", "sqrt", "sub", "tan", "tanh", }); this->common_ops({"where"}, {.common_type = false}); @@ -55,12 +92,12 @@ struct torch_kit : kit "broadcast", "concat", "contiguous", + "convolution", "convolution_backwards", "dequantizelinear", "gather", "gathernd", "get_tuple_elem", - "logsoftmax", "multibroadcast", "pad", "pooling", @@ -84,23 +121,6 @@ struct torch_kit : kit "undefined", "unsqueeze", }); - - // Composite builders (bias fusion, broadcasting, etc.), not plain ops. - this->builders({"batchnorm", - "clip", - "convolution", - "dot", - "floor_div", - "gather_elements", - "gelu_erf", - "glu", - "group_norm", - "hardsigmoid", - "instance_norm", - "layer_norm", - "selu", - "softsign", - "vector_norm"}); } }; diff --git a/src/op/builder/vector_norm.cpp b/src/op/builder/vector_norm.cpp deleted file mode 100644 index 7c5fa7975ef..00000000000 --- a/src/op/builder/vector_norm.cpp +++ /dev/null @@ -1,92 +0,0 @@ -/* 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 - -namespace migraphx { -inline namespace MIGRAPHX_INLINE_NS { -namespace op { -namespace builder { - -// vector_norm has no native op: reduce abs(x) over axes per the ord-specific formula. -struct vector_norm : op_builder -{ - float ord = 2.0f; - std::vector axes = {}; - bool keepdim = false; - - template - static auto reflect(Self& self, F f) - { - return pack(f(self.ord, "ord"), f(self.axes, "axes"), f(self.keepdim, "keepdim")); - } - - std::vector - insert(module& m, instruction_ref ins, const std::vector& args) const - { - auto x = args[0]; - auto x_type = x->get_shape().type(); - auto abs_x = m.insert_instruction(ins, make_op("abs"), x); - - instruction_ref out; - if(float_equal(ord, 0.0f)) - { - // count of nonzero elements: sum(abs(x) > 0) - auto zero = m.add_literal(migraphx::literal{migraphx::shape{x_type}, {0.0f}}); - auto nonzero = insert_common_op(m, ins, "greater", abs_x, zero); - auto counts = - m.insert_instruction(ins, make_op("convert", {{"target_type", x_type}}), nonzero); - out = m.insert_instruction(ins, make_op("reduce_sum", {{"axes", axes}}), counts); - } - else if(std::isinf(ord)) - { - // +inf -> max(abs(x)), -inf -> min(abs(x)) - const auto* reduce = ord > 0 ? "reduce_max" : "reduce_min"; - out = m.insert_instruction(ins, make_op(reduce, {{"axes", axes}}), abs_x); - } - else - { - // sum(abs(x) ^ ord) ^ (1 / ord) - auto ord_lit = m.add_literal(migraphx::literal{migraphx::shape{x_type}, {ord}}); - auto pow_x = insert_common_op(m, ins, "pow", abs_x, ord_lit); - auto sum_pow = - m.insert_instruction(ins, make_op("reduce_sum", {{"axes", axes}}), pow_x); - auto recip = m.insert_instruction(ins, make_op("recip"), ord_lit); - out = insert_common_op(m, ins, "pow", sum_pow, recip); - } - - if(not keepdim) - out = m.insert_instruction(ins, make_op("squeeze", {{"axes", axes}}), out); - return {out}; - } -}; - -} // namespace builder -} // namespace op -} // namespace MIGRAPHX_INLINE_NS -} // namespace migraphx diff --git a/src/py/migraphx_py.cpp b/src/py/migraphx_py.cpp index 517c2982e69..06d7cc94969 100644 --- a/src/py/migraphx_py.cpp +++ b/src/py/migraphx_py.cpp @@ -43,7 +43,6 @@ #include #include #include -#include #include #include #include @@ -390,7 +389,6 @@ MIGRAPHX_PYBIND11_MODULE(migraphx, m) .def("type_string", &migraphx::shape::type_string) .def("type_size", &migraphx::shape::type_size) .def("dyn_dims", &migraphx::shape::dyn_dims) - .def("sub_shapes", &migraphx::shape::sub_shapes) .def("packed", &migraphx::shape::packed) .def("transposed", &migraphx::shape::transposed) .def("broadcasted", &migraphx::shape::broadcasted) @@ -736,12 +734,6 @@ MIGRAPHX_PYBIND11_MODULE(migraphx, m) .def("options", [](const py_macro& mac) -> py::object { return to_py_object(mac.options); }); - m.def( - "has_op_builder", - [](const std::string& name) { return migraphx::op::builder::has_op_builder(name); }, - py::arg("name"), - "Whether an op-builder (e.g. a \"tm::\" kit builder) is registered."); - m.def( "argument_from_pointer", [](const migraphx::shape shape, const int64_t address) { diff --git a/src/reshape_dims.cpp b/src/reshape_dims.cpp index 461e996496a..588486f419b 100644 --- a/src/reshape_dims.cpp +++ b/src/reshape_dims.cpp @@ -25,34 +25,25 @@ #include #include #include -#include namespace migraphx { inline namespace MIGRAPHX_INLINE_NS { template -static Iterator compute_end_dim(Iterator start, Iterator last, const sym::expr& dim) +static auto compute_end_dim(Iterator start, Iterator last, std::size_t dim) { - auto x = sym::lit(std::int64_t{1}); - bool indeterminate = false; - auto it = std::find_if(start, last, [&](const auto& i) { + std::size_t x = 1; + auto it = std::find_if(start, last, [&](auto i) { x *= i; - auto x_lt = sym::strict_less(x, dim); - if(not x_lt.has_value()) - { - indeterminate = true; - return true; - } - return not *x_lt; + return x >= dim; }); - if(indeterminate or not sym::same_symbol(x, dim)) + if(x != dim) return start; return it; } -static optional> -try_merge_pairs(optional> p2, - optional> p1) +template +static OptionalPair try_merge_pairs(OptionalPair p2, OptionalPair p1) { if(not p1.has_value()) return nullopt; @@ -63,32 +54,30 @@ try_merge_pairs(optional> p2, auto stride1 = p1->second; auto stride2 = p2->second; auto elements = dim1 * dim2; - auto zero = sym::lit(std::int64_t{0}); // Transposed - auto order = sym::strict_less(stride1, stride2); - if(not order.has_value() or *order) + if(stride2 > stride1) return nullopt; // Broadcasted check to avoid division by zero - if(sym::same_symbol(stride2, zero)) + if(stride2 == 0) { - if(sym::same_symbol(stride1, zero)) - return {{elements, zero}}; + if(stride1 == 0) + return {{elements, 0}}; return nullopt; } - if(not sym::is_divisible(stride1, stride2)) + if(stride1 % stride2 != 0) return nullopt; auto space = (stride1 * dim1 + stride2 * dim2 - stride1) / stride2; // Nonpacked - if(not sym::same_symbol(space, elements)) + if(space != elements) return nullopt; return {{elements, stride2}}; } template -static optional merge_strides(DimIterator dim_start, - DimIterator dim_last, - StrideIterator stride_start, - StrideIterator stride_last) +static optional merge_strides(DimIterator dim_start, + DimIterator dim_last, + StrideIterator stride_start, + StrideIterator stride_last) { if(dim_start == dim_last) return nullopt; @@ -118,127 +107,64 @@ static auto can_strides_merge(DimIterator dim_start, return merge_strides(dim_start, dim_last, stride_start, stride_last).has_value(); } -std::vector resolve_reshape_dims(const shape& sym_in, - const std::vector& dims) -{ - const auto& input_dds = sym_in.dyn_dims(); - std::vector output_dyn_dims(dims.size()); - shape::dynamic_dimension known_elements{sym::lit(1)}; - std::size_t neg_dim_num = dims.size(); - for(std::size_t i = 0; i < dims.size(); ++i) - { - const auto& d = dims[i]; - // Defer -1; it needs the product of every other axis. - if(d == dim_like{-1}) - { - neg_dim_num = i; - continue; - } - if(d == dim_like{0}) - output_dyn_dims[i] = input_dds.at(i); - else if(is_symbolic(d)) - output_dyn_dims[i] = std::get(d); - else - output_dyn_dims[i] = shape::dynamic_dimension{sym::lit(std::get(d))}; - known_elements = known_elements * output_dyn_dims[i]; - } - if(neg_dim_num < dims.size()) - { - auto total_elements = std::accumulate(input_dds.begin(), - input_dds.end(), - shape::dynamic_dimension{sym::lit(1)}, - std::multiplies<>{}); - output_dyn_dims[neg_dim_num] = total_elements / known_elements; - } - return output_dyn_dims; -} - -void validate_reshape_dims(const std::string& name, const std::vector& dims) -{ - if(std::any_of(dims.begin(), dims.end(), [](const dim_like& d) { - return std::holds_alternative(d) and not is_symbolic(d); - })) - MIGRAPHX_THROW(name + ": dim entries must be int64 or symbolic"); - - auto n_neg_dims = std::count(dims.begin(), dims.end(), dim_like{-1}); - if(n_neg_dims > 1) - MIGRAPHX_THROW(name + ": Dimensions for " + name + " can only have one -1 dim but given {" + - to_string_range(dims) + "} with " + to_string(n_neg_dims) + " -1 dims"); -} - optional reshape_dims(const shape& input, const std::vector& rdims, reshape_dims_options options) { - std::vector sym_rdims(rdims.size()); - std::transform( - rdims.begin(), rdims.end(), sym_rdims.begin(), [](std::size_t d) { return sym::lit(d); }); - return reshape_dims(input, sym_rdims, options); -} + if(input.standard()) + return shape{input.type(), rdims}; -// Walk the input dims and the requested dims together, squeezing a run of input axes whose -// product is a requested dim and unsqueezing an input axis into a run of requested dims, deriving -// the stride of each requested dim as it goes. nullopt when the layout can't be proven. -static optional> -compute_reshape_strides(const std::vector& idims, - const std::vector& istrides, - const std::vector& rdims, - reshape_dims_options options) -{ - std::vector rstrides; + // Broadcasts have ambiguous permutations (multiple axes share stride 0), so + // for non-lazy reshape fall back to a standard layout. Sliced (non-packed) + // inputs still propagate the permutation via the algorithm + with_lens below. + if(not options.lazy and input.broadcasted()) + return shape{input.type(), rdims}; + + const auto& idims = input.lens(); + const auto& istrides = input.strides(); + + std::vector rstrides; std::size_t i = 0; std::size_t r = 0; while(i < idims.size() and r < rdims.size()) { auto idim = idims[i]; auto rdim = rdims[r]; - if(sym::same_symbol(rdim, idim)) + if(rdim == idim) { rstrides.push_back(istrides[i]); } - else + // squeeze + else if(rdim > idim) { - // equality handled above; an unprovable ordering bails. - auto rdim_gt = sym::strict_less(idim, rdim); - auto rdim_lt = sym::strict_less(rdim, idim); - if(not rdim_gt.has_value() or not rdim_lt.has_value()) + auto start = idims.begin() + i; + auto it = compute_end_dim(start, idims.end(), rdim); + if(it == start) + return nullopt; + auto n = it - start; + assert((i + n) <= istrides.size()); + if(options.lazy and + not can_strides_merge( + start, it + 1, istrides.begin() + i, istrides.begin() + i + n + 1)) return nullopt; - // squeeze - if(*rdim_gt) - { - auto start = idims.begin() + i; - auto it = compute_end_dim(start, idims.end(), rdim); - if(it == start) - return nullopt; - auto n = it - start; - assert((i + n) <= istrides.size()); - if(options.lazy and - not can_strides_merge( - start, it + 1, istrides.begin() + i, istrides.begin() + i + n + 1)) - return nullopt; - i += n; - rstrides.push_back(istrides[i]); - } - // unsqueeze - else if(*rdim_lt) - { - auto start = rdims.begin() + r; - auto it = compute_end_dim(start, rdims.end(), idim); - if(it == start) - return nullopt; - auto n = it - start; - assert((r + n) <= rdims.size()); - auto stride = istrides[i] * idim; - std::for_each(start, it + 1, [&](auto dim) { - stride /= dim; - rstrides.push_back(stride); - }); - r += n; - } - else - { + i += n; + rstrides.push_back(istrides[i]); + } + // unsqueeze + else // if(rdim < idim) + { + auto start = rdims.begin() + r; + auto it = compute_end_dim(start, rdims.end(), idim); + if(it == start) return nullopt; - } + auto n = it - start; + assert((r + n) <= rdims.size()); + auto stride = istrides[i] * idim; + std::for_each(start, it + 1, [&](auto dim) { + stride /= dim; + rstrides.push_back(stride); + }); + r += n; } i++; r++; @@ -250,7 +176,7 @@ compute_reshape_strides(const std::vector& idims, auto stride = rstrides.back(); for(auto d : range(rdims.begin() + rstrides.size(), rdims.end())) { - if(d != sym::lit(std::int64_t{1})) + if(d != 1) return nullopt; rstrides.push_back(stride); } @@ -259,39 +185,11 @@ compute_reshape_strides(const std::vector& idims, if(rdims.size() != rstrides.size()) return nullopt; - return rstrides; -} - -optional -reshape_dims(const shape& input, const std::vector& rdims, reshape_dims_options options) -{ - const std::vector rdds(rdims.begin(), rdims.end()); - - if(input.standard()) - return shape{input.type(), rdds}; - - // Broadcasts have ambiguous permutations (multiple axes share stride 0), so - // for non-lazy reshape fall back to a standard layout. Sliced (non-packed) - // inputs still propagate the permutation via the algorithm + with_lens below. - if(not options.lazy and input.broadcasted()) - return shape{input.type(), rdds}; - - // Range-based dynamic dimensions carry no stride expressions to merge or split. - if(input.dynamic() and not input.symbolic()) - return nullopt; - - // Lift a static input to symbolic literals so one algorithm resolves both kinds. - const auto sym_in = input.to_symbolic(); - auto rstrides = - compute_reshape_strides(sym_in.sym_dims(), sym_in.dyn_strides(), rdims, options); - if(not rstrides.has_value()) - return nullopt; - - auto result = shape{input.type(), rdds, *rstrides}; + auto result = shape{input.type(), rdims, rstrides}; if(options.lazy or result.packed()) return result; // TODO: Add as_packed to shape class - return result.with_lens(result.type(), result.dyn_dims()); + return result.with_lens(result.type(), result.lens()); } } // namespace MIGRAPHX_INLINE_NS diff --git a/src/shape.cpp b/src/shape.cpp index 3b9dfab890b..b3449556909 100644 --- a/src/shape.cpp +++ b/src/shape.cpp @@ -662,8 +662,6 @@ std::size_t shape::elements() const { return impl->elements(); } sym::expr shape::sym_elements() const { return impl->sym_elements(); } -std::vector shape::sym_dims() const { return impl->sym_dims(); } - std::size_t shape::bytes() const { if(this->sub_shapes().empty()) diff --git a/src/sym.cpp b/src/sym.cpp index 857c21a3c5f..3f84e41d393 100644 --- a/src/sym.cpp +++ b/src/sym.cpp @@ -1623,43 +1623,6 @@ bool same_symbol(const expr& a, const expr& b) }); } -std::vector find_variables(const expr& e) -{ - std::vector result; - std::unordered_set visited; - std::unordered_set seen_variables; - fix([&](auto self, const expr& x) { - if(x.empty() or not visited.insert(x).second) - return; - if(x.name() == "variable") - { - auto s = as_symbol(x); - if(seen_variables.insert(s).second) - result.push_back(std::move(s)); - return; - } - for(const auto& c : x.children()) - self(c); - })(e); - return result; -} - -[[maybe_unused]] static bool has_float_literal(const expr& e) -{ - if(e.empty()) - return false; - if(const auto* n = std::get_if(&get_node(e))) - return std::holds_alternative(n->val); - return std::any_of(e.children().begin(), e.children().end(), has_float_literal); -} - -bool is_divisible(const expr& dividend, const expr& divisor) -{ - // Float literals make the /-reconstruction rounding-dependent. - assert(not has_float_literal(dividend) and not has_float_literal(divisor)); - return same_symbol((dividend / divisor) * divisor, dividend); -} - // Number of levels in e: a leaf (literal/variable) is depth 1, empty is 0. static int expr_depth(const expr& e) { diff --git a/src/targets/cpu/lowering.cpp b/src/targets/cpu/lowering.cpp index bc135a0f54c..fdea6202a4b 100644 --- a/src/targets/cpu/lowering.cpp +++ b/src/targets/cpu/lowering.cpp @@ -146,11 +146,6 @@ struct cpu_op { return op.compute(output_shape, args); } - void - finalize(migraphx::context& ctx, const shape& output_shape, const std::vector& inputs) - { - op.finalize(ctx, output_shape, inputs); - } value to_value() const { value v; diff --git a/src/targets/gpu/device_name.cpp b/src/targets/gpu/device_name.cpp index 70aa0f40a36..2b825ea062f 100644 --- a/src/targets/gpu/device_name.cpp +++ b/src/targets/gpu/device_name.cpp @@ -141,8 +141,7 @@ static bool hipblaslt_supported_impl(const std::string& gfx_name) { return (gfx_name == "gfx90a" or (starts_with(gfx_name, "gfx94") and gfx_name >= "gfx942") or (starts_with(gfx_name, "gfx95") and gfx_name >= "gfx950") or - starts_with(gfx_name, "gfx110") or starts_with(gfx_name, "gfx115") or - starts_with(gfx_name, "gfx120")); + starts_with(gfx_name, "gfx110") or starts_with(gfx_name, "gfx120")); } #endif diff --git a/src/targets/gpu/include/migraphx/gpu/contiguous.hpp b/src/targets/gpu/include/migraphx/gpu/contiguous.hpp index b169babd09f..f689ea9cfdd 100644 --- a/src/targets/gpu/include/migraphx/gpu/contiguous.hpp +++ b/src/targets/gpu/include/migraphx/gpu/contiguous.hpp @@ -41,13 +41,13 @@ struct miopen_contiguous : unary_device shape compute_shape(const std::vector& inputs) const { check_shapes{inputs, *this, true}.has(2); - const auto& input = inputs.at(0); - // Packing yields a standard layout; a range-only dynamic shape has no strides to pack. - if(input.symbolic()) - return {input.type(), input.dyn_dims()}; - if(input.dynamic()) - return input; - return {input.type(), input.lens()}; + if(inputs.at(0).dynamic()) + { + return inputs.at(0); + } + auto lens = inputs.at(0).lens(); + auto t = inputs.at(0).type(); + return {t, lens}; } }; diff --git a/src/targets/gpu/lowering.cpp b/src/targets/gpu/lowering.cpp index a0a6dc67afa..8aa711a3f3c 100644 --- a/src/targets/gpu/lowering.cpp +++ b/src/targets/gpu/lowering.cpp @@ -461,9 +461,7 @@ struct miopen_apply return lower_nms_to_ref(ins); const auto num_boxes = boxes_s.lens().at(1); const auto num_bc = boxes_s.lens().at(0) * scores_s.lens().at(1); - // Route to ref (CPU) when: - // - num_boxes < 2: Single box or no boxes, no sort or IoU comparison needed. - // - num_bc > 8192: shared-memory limit on the compact kernel. + // bound on (batch, class) from shared memory limit on compact kernel if(num_boxes < 2 or num_bc > 8192) return lower_nms_to_ref(ins); return lower_nms_to_gpu_pipeline(ins); diff --git a/src/targets/gpu/propagate_reshape_layout.cpp b/src/targets/gpu/propagate_reshape_layout.cpp index 0c2de9699f7..4764ec3afed 100644 --- a/src/targets/gpu/propagate_reshape_layout.cpp +++ b/src/targets/gpu/propagate_reshape_layout.cpp @@ -35,7 +35,7 @@ inline namespace MIGRAPHX_INLINE_NS { namespace gpu { namespace { -struct find_reshape_lazy_contiguous : match::supports_dynamic_shapes +struct find_reshape_lazy_contiguous { // eliminate_contiguous only leaves a standardizing gpu::contiguous in front of a // reshape_lazy when it could not alias the input directly; that is the only case where a @@ -52,24 +52,19 @@ struct find_reshape_lazy_contiguous : match::supports_dynamic_shapes auto cont = r.instructions["contiguous"]; auto input = cont->inputs().front(); const auto& s = input->get_shape(); - // A standard input has no permutation to propagate; a range-based dynamic input has - // no symbolic dims for reshape_dims/find_permutation to work with. - if(s.standard() or (s.dynamic() and not s.symbolic())) + // A standard input carries no permutation to propagate. + if(s.dynamic() or s.standard()) return; - auto sym_in = s.to_symbolic(); - - auto permuted = reshape_dims(sym_in, rl->get_shape().sym_dims(), {.lazy = false}); - if(not permuted or permuted->standard()) - return; - // reshape_dims does not check the element count; bail when it provably differs, - // matching reshape_lazy::compute_shape (an indeterminate count is allowed through). - auto out_elems = permuted->sym_elements(); - auto in_elems = sym_in.sym_elements(); - if(sym::strict_less(out_elems, in_elems).value_or(false) or - sym::strict_less(in_elems, out_elems).value_or(false)) + const auto& rdims = rl->get_shape().lens(); + // The permuted, packed output the original reshape would have produced from the real + // (non-standard) input. reshape_dims does not verify the element count, so guard it here + // the same way reshape_lazy::compute_shape does. + auto permuted = reshape_dims(s, rdims, {.lazy = false}); + if(not permuted or permuted->standard() or permuted->elements() != s.elements()) return; - auto relayout = reshape_dims(*permuted, s.sym_dims(), {.lazy = true}); + // The packed layout that reshape_lazy can alias straight to that output. + auto relayout = reshape_dims(*permuted, s.lens(), {.lazy = true}); if(not relayout) return; diff --git a/src/targets/ref/lowering.cpp b/src/targets/ref/lowering.cpp index 686a9ec77f3..3e28071ea3b 100644 --- a/src/targets/ref/lowering.cpp +++ b/src/targets/ref/lowering.cpp @@ -183,11 +183,6 @@ struct ref_op { return op.compute(output_shape, args); } - void - finalize(migraphx::context& ctx, const shape& output_shape, const std::vector& inputs) - { - op.finalize(ctx, output_shape, inputs); - } value to_value() const { value v; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 366ab5c019d..dca7e30adc7 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -117,9 +117,6 @@ if(MIGRAPHX_ENABLE_PYTHON) add_subdirectory(py) endif() -# Install a pytest bridge next to the installed C++ tests -rocm_install_test(FILES ${CMAKE_CURRENT_SOURCE_DIR}/test_pytest_bridge.py) - # Op builder test set(TEST_OP_BUILDER_DIR ${CMAKE_CURRENT_SOURCE_DIR}/op) add_subdirectory(op) diff --git a/test/fuse_attention.cpp b/test/fuse_attention.cpp index 7031d342e60..74f1c610594 100644 --- a/test/fuse_attention.cpp +++ b/test/fuse_attention.cpp @@ -902,19 +902,21 @@ TEST_CASE(gemm_softmax_gemm_flash_decoding) auto k2_broad1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, 12, 2, 256, 1}}}), k2_rmax); auto k2_sub = mm->add_instruction(migraphx::make_op("sub"), lse, k2_broad1); - auto k2_exp = mm->add_instruction(migraphx::make_op("exp"), k2_sub); + auto k2_exp = mm->add_instruction(migraphx::make_op("exp"), k2_sub); + auto k2_rsum1 = + mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), k2_exp); + auto k2_broad2 = mm->add_instruction( + migraphx::make_op("multibroadcast", {{"out_lens", {1, 12, 2, 256, 1}}}), k2_rsum1); + auto k2_div = mm->add_instruction(migraphx::make_op("div"), k2_exp, k2_broad2); auto k2_broad3 = mm->add_instruction( - migraphx::make_op("multibroadcast", {{"out_lens", {1, 12, 2, 256, 256}}}), k2_exp); + migraphx::make_op("multibroadcast", {{"out_lens", {1, 12, 2, 256, 256}}}), k2_div); auto k2_convert = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), k2_broad3); auto k2_mul = mm->add_instruction(migraphx::make_op("mul"), o_p, k2_convert); - auto k2_rsum1 = - mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), k2_mul); auto k2_rsum2 = - mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), k2_convert); - auto k2_div = mm->add_instruction(migraphx::make_op("div"), k2_rsum1, k2_rsum2); + mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {2}}}), k2_mul); auto k2_squeeze = - mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {2}}}), k2_div); + mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {2}}}), k2_rsum2); mm->add_return({k2_squeeze}); } EXPECT(p1.sort() == p2.sort()); @@ -1016,19 +1018,21 @@ TEST_CASE(flash_decoding_3d) auto k2_broad1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, num_splits, 256, 1}}}), k2_rmax); auto k2_sub = mm->add_instruction(migraphx::make_op("sub"), lse, k2_broad1); - auto k2_exp = mm->add_instruction(migraphx::make_op("exp"), k2_sub); + auto k2_exp = mm->add_instruction(migraphx::make_op("exp"), k2_sub); + auto k2_rsum1 = + mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {g_axis}}}), k2_exp); + auto k2_broad2 = mm->add_instruction( + migraphx::make_op("multibroadcast", {{"out_lens", {1, num_splits, 256, 1}}}), k2_rsum1); + auto k2_div = mm->add_instruction(migraphx::make_op("div"), k2_exp, k2_broad2); auto k2_broad3 = mm->add_instruction( - migraphx::make_op("multibroadcast", {{"out_lens", q_prime_shape}}), k2_exp); + migraphx::make_op("multibroadcast", {{"out_lens", q_prime_shape}}), k2_div); auto k2_convert = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), k2_broad3); auto k2_mul = mm->add_instruction(migraphx::make_op("mul"), o_p, k2_convert); - auto k2_rsum1 = - mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {g_axis}}}), k2_mul); auto k2_rsum2 = - mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {g_axis}}}), k2_convert); - auto k2_div = mm->add_instruction(migraphx::make_op("div"), k2_rsum1, k2_rsum2); + mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {g_axis}}}), k2_mul); auto k2_squeeze = - mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {g_axis}}}), k2_div); + mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {g_axis}}}), k2_rsum2); mm->add_return({k2_squeeze}); } EXPECT(p1.sort() == p2.sort()); @@ -1138,19 +1142,21 @@ TEST_CASE(flash_decoding_3d_rectangular) auto k2_broad1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, num_splits, 240, 1}}}), k2_rmax); auto k2_sub = mm->add_instruction(migraphx::make_op("sub"), lse, k2_broad1); - auto k2_exp = mm->add_instruction(migraphx::make_op("exp"), k2_sub); + auto k2_exp = mm->add_instruction(migraphx::make_op("exp"), k2_sub); + auto k2_rsum1 = + mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {g_axis}}}), k2_exp); + auto k2_broad2 = mm->add_instruction( + migraphx::make_op("multibroadcast", {{"out_lens", {1, num_splits, 240, 1}}}), k2_rsum1); + auto k2_div = mm->add_instruction(migraphx::make_op("div"), k2_exp, k2_broad2); auto k2_broad3 = mm->add_instruction( - migraphx::make_op("multibroadcast", {{"out_lens", q_prime_shape}}), k2_exp); + migraphx::make_op("multibroadcast", {{"out_lens", q_prime_shape}}), k2_div); auto k2_convert = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), k2_broad3); auto k2_mul = mm->add_instruction(migraphx::make_op("mul"), o_p, k2_convert); - auto k2_rsum1 = - mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {g_axis}}}), k2_mul); auto k2_rsum2 = - mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {g_axis}}}), k2_convert); - auto k2_div = mm->add_instruction(migraphx::make_op("div"), k2_rsum1, k2_rsum2); + mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {g_axis}}}), k2_mul); auto k2_squeeze = - mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {g_axis}}}), k2_div); + mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {g_axis}}}), k2_rsum2); mm->add_return({k2_squeeze}); } EXPECT(p1.sort() == p2.sort()); @@ -1268,19 +1274,21 @@ TEST_CASE(flash_decoding_3d_padding) auto k2_broad1 = mm->add_instruction( migraphx::make_op("multibroadcast", {{"out_lens", {1, num_splits, 242, 1}}}), k2_rmax); auto k2_sub = mm->add_instruction(migraphx::make_op("sub"), lse, k2_broad1); - auto k2_exp = mm->add_instruction(migraphx::make_op("exp"), k2_sub); + auto k2_exp = mm->add_instruction(migraphx::make_op("exp"), k2_sub); + auto k2_rsum1 = + mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {g_axis}}}), k2_exp); + auto k2_broad2 = mm->add_instruction( + migraphx::make_op("multibroadcast", {{"out_lens", {1, num_splits, 242, 1}}}), k2_rsum1); + auto k2_div = mm->add_instruction(migraphx::make_op("div"), k2_exp, k2_broad2); auto k2_broad3 = mm->add_instruction( - migraphx::make_op("multibroadcast", {{"out_lens", q_prime_shape}}), k2_exp); + migraphx::make_op("multibroadcast", {{"out_lens", q_prime_shape}}), k2_div); auto k2_convert = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), k2_broad3); auto k2_mul = mm->add_instruction(migraphx::make_op("mul"), o_p, k2_convert); - auto k2_rsum1 = - mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {g_axis}}}), k2_mul); auto k2_rsum2 = - mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {g_axis}}}), k2_convert); - auto k2_div = mm->add_instruction(migraphx::make_op("div"), k2_rsum1, k2_rsum2); + mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {g_axis}}}), k2_mul); auto k2_squeeze = - mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {g_axis}}}), k2_div); + mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {g_axis}}}), k2_rsum2); // Slice to remove padding: [1, 242, 256] -> [1, 241, 256] auto sliced = mm->add_instruction( @@ -1897,19 +1905,22 @@ TEST_CASE(flash_decoding_3d_auto_split_large_sequence) migraphx::make_op("multibroadcast", {{"out_lens", {1, expected_splits, 512, 1}}}), k2_rmax); auto k2_sub = mm->add_instruction(migraphx::make_op("sub"), lse, k2_broad1); - auto k2_exp = mm->add_instruction(migraphx::make_op("exp"), k2_sub); + auto k2_exp = mm->add_instruction(migraphx::make_op("exp"), k2_sub); + auto k2_rsum1 = + mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {g_axis}}}), k2_exp); + auto k2_broad2 = mm->add_instruction( + migraphx::make_op("multibroadcast", {{"out_lens", {1, expected_splits, 512, 1}}}), + k2_rsum1); + auto k2_div = mm->add_instruction(migraphx::make_op("div"), k2_exp, k2_broad2); auto k2_broad3 = mm->add_instruction( - migraphx::make_op("multibroadcast", {{"out_lens", q_prime_shape}}), k2_exp); + migraphx::make_op("multibroadcast", {{"out_lens", q_prime_shape}}), k2_div); auto k2_convert = mm->add_instruction( migraphx::make_op("convert", {{"target_type", migraphx::shape::half_type}}), k2_broad3); auto k2_mul = mm->add_instruction(migraphx::make_op("mul"), o_p, k2_convert); - auto k2_rsum1 = - mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {g_axis}}}), k2_mul); auto k2_rsum2 = - mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {g_axis}}}), k2_convert); - auto k2_div = mm->add_instruction(migraphx::make_op("div"), k2_rsum1, k2_rsum2); + mm->add_instruction(migraphx::make_op("reduce_sum", {{"axes", {g_axis}}}), k2_mul); auto k2_squeeze = - mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {g_axis}}}), k2_div); + mm->add_instruction(migraphx::make_op("squeeze", {{"axes", {g_axis}}}), k2_rsum2); mm->add_return({k2_squeeze}); } EXPECT(p1.sort() == p2.sort()); diff --git a/test/gpu/nonmaxsuppression.cpp b/test/gpu/nonmaxsuppression.cpp index 02370f7f245..42f999d83ed 100644 --- a/test/gpu/nonmaxsuppression.cpp +++ b/test/gpu/nonmaxsuppression.cpp @@ -1345,81 +1345,4 @@ TEST_CASE(nms_quantized_ties_test) EXPECT(num_selected == 10); } -// Edge case: 1 box with score above score_threshold. The single box should be -// selected. Routes through lower_nms_to_ref because num_boxes < 2. -TEST_CASE(nms_one_box_above_threshold_test) -{ - migraphx::program p; - auto* mm = p.get_main_module(); - migraphx::shape boxes_s{migraphx::shape::float_type, {1, 1, 4}}; - migraphx::shape scores_s{migraphx::shape::float_type, {1, 1, 1}}; - - auto boxes_p = mm->add_parameter("boxes", boxes_s); - auto scores_p = mm->add_parameter("scores", scores_s); - auto max_out_l = mm->add_literal(int64_t{10}); - auto iou_threshold = mm->add_literal(0.5f); - auto score_threshold = mm->add_literal(0.3f); - - auto nms = mm->add_instruction(migraphx::make_op("nonmaxsuppression"), - boxes_p, - scores_p, - max_out_l, - iou_threshold, - score_threshold); - add_nms_return(mm, nms); - - // single box: [y1=0, x1=0, y2=1, x2=1], score 0.9 > threshold 0.3 - std::vector boxes_vec = {0.0f, 0.0f, 1.0f, 1.0f}; - std::vector scores_vec = {0.9f}; - - migraphx::parameter_map host_params; - host_params["boxes"] = migraphx::argument(boxes_s, boxes_vec.data()); - host_params["scores"] = migraphx::argument(scores_s, scores_vec.data()); - - auto [indices, num_selected] = run_gpu_nms(std::move(p), host_params); - indices.resize(static_cast(num_selected) * 3); - // batch_idx=0, class_idx=0, box_idx=0 - std::vector gold = {0, 0, 0}; - EXPECT(indices == gold); - EXPECT(num_selected == 1); -} - -// Edge case: 1 box with score below score_threshold. No boxes should be -// selected. Routes through lower_nms_to_ref because num_boxes < 2. -TEST_CASE(nms_one_box_below_threshold_test) -{ - migraphx::program p; - auto* mm = p.get_main_module(); - migraphx::shape boxes_s{migraphx::shape::float_type, {1, 1, 4}}; - migraphx::shape scores_s{migraphx::shape::float_type, {1, 1, 1}}; - - auto boxes_p = mm->add_parameter("boxes", boxes_s); - auto scores_p = mm->add_parameter("scores", scores_s); - auto max_out_l = mm->add_literal(int64_t{10}); - auto iou_threshold = mm->add_literal(0.5f); - auto score_threshold = mm->add_literal(0.5f); - - auto nms = mm->add_instruction(migraphx::make_op("nonmaxsuppression"), - boxes_p, - scores_p, - max_out_l, - iou_threshold, - score_threshold); - add_nms_return(mm, nms); - - // single box: score 0.2 < threshold 0.5 - std::vector boxes_vec = {0.0f, 0.0f, 1.0f, 1.0f}; - std::vector scores_vec = {0.2f}; - - migraphx::parameter_map host_params; - host_params["boxes"] = migraphx::argument(boxes_s, boxes_vec.data()); - host_params["scores"] = migraphx::argument(scores_s, scores_vec.data()); - - auto [indices, num_selected] = run_gpu_nms(std::move(p), host_params); - indices.resize(static_cast(num_selected) * 3); - std::vector gold = {}; - EXPECT(indices == gold); - EXPECT(num_selected == 0); -} - int main(int argc, const char* argv[]) { test::run(argc, argv); } diff --git a/test/gpu/propagate_reshape_layout.cpp b/test/gpu/propagate_reshape_layout.cpp index fa238c20495..a7ed506492d 100644 --- a/test/gpu/propagate_reshape_layout.cpp +++ b/test/gpu/propagate_reshape_layout.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include "make_precompile_op.hpp" @@ -93,71 +92,6 @@ TEST_CASE(propagate_permutation) EXPECT(rl1->get_shape().lens() == std::vector{1, 16, 256, 256}); } -// Symbolic analog of propagate_permutation: the leading batch dimension is a symbol that -// threads through the reshape/transpose/reshape_lazy chain. The pass must propagate the -// permutation just as in the static case, producing the same layout/allocate structure. -TEST_CASE(propagate_permutation_symbolic) -{ - using dd = migraphx::shape::dynamic_dimension; - using migraphx::sym::lit; - - auto n = migraphx::sym::var("N", {1, 8}); - migraphx::shape in{migraphx::shape::float_type, - {dd{n}, dd{lit(1)}, dd{lit(1024)}, dd{lit(1024)}}}; - - migraphx::module m1; - { - auto x = m1.add_parameter("x", in); - // 0 copies the symbolic batch dim; the spatial dims split into blocks. - auto r = - m1.add_instruction(migraphx::make_op("reshape", {{"dims", {0, 256, 4, 256, 4}}}), x); - auto t = m1.add_instruction( - migraphx::make_op("transpose", {{"permutation", {0, 2, 4, 1, 3}}}), r); - // post-eliminate_contiguous state: a standardizing gpu::contiguous feeds reshape_lazy - auto alloc = m1.add_instruction( - migraphx::make_op("allocate", - {{"shape", - migraphx::to_value(migraphx::shape{migraphx::shape::float_type, - t->get_shape().dyn_dims()})}})); - auto c = m1.add_instruction(migraphx::make_op("gpu::contiguous"), t, alloc); - auto rl = - m1.add_instruction(migraphx::make_op("reshape_lazy", {{"dims", {0, 16, 256, 256}}}), c); - m1.add_return({rl}); - } - run_pass(m1); - - migraphx::module m2; - { - auto x = m2.add_parameter("x", in); - auto r = - m2.add_instruction(migraphx::make_op("reshape", {{"dims", {0, 256, 4, 256, 4}}}), x); - auto t = m2.add_instruction( - migraphx::make_op("transpose", {{"permutation", {0, 2, 4, 1, 3}}}), r); - // layout repacks the transpose into the packed memory order reshape_lazy can alias - auto l_shape = migraphx::shape::from_permutation( - migraphx::shape::float_type, - {dd{n}, dd{lit(4)}, dd{lit(4)}, dd{lit(256)}, dd{lit(256)}}, - {0, 3, 4, 1, 2}); - auto alloc = m2.add_instruction( - migraphx::make_op("allocate", {{"shape", migraphx::to_value(l_shape)}})); - auto layout = m2.add_instruction( - make_precompile_op(migraphx::make_op("layout", {{"permutation", {0, 3, 4, 1, 2}}})), - t, - alloc); - auto rl = m2.add_instruction( - migraphx::make_op("reshape_lazy", {{"dims", {0, 16, 256, 256}}}), layout); - m2.add_return({rl}); - } - - EXPECT(m1 == m2); - // reshape_lazy now produces the permuted (NHWC-like) symbolic output rather than a standard one - auto rl1 = std::prev(m1.end())->inputs().front(); - EXPECT(rl1->name() == "reshape_lazy"); - EXPECT(not rl1->get_shape().standard()); - EXPECT(rl1->get_shape().sym_dims() == - std::vector{n, lit(16), lit(256), lit(256)}); -} - // When the reshape collapses the non-standard input back to a standard layout there is no // permutation to propagate, so the pass must leave the graph unchanged. TEST_CASE(no_permutation_noop) diff --git a/test/instruction.cpp b/test/instruction.cpp index a31031b01fa..6ff8dc35e66 100644 --- a/test/instruction.cpp +++ b/test/instruction.cpp @@ -28,39 +28,6 @@ #include "test.hpp" #include "rob.hpp" -struct can_eval_finalize_passthrough -{ - std::string name() const { return "can_eval_finalize_passthrough"; } - - migraphx::shape compute_shape(const std::vector& inputs) const - { - return inputs.at(0); - } - - migraphx::argument compute(const migraphx::shape&, - const std::vector& args) const - { - return args.at(0); - } - - void finalize(migraphx::context&, const migraphx::shape&, const std::vector&) - { - } -}; - -TEST_CASE(can_eval_rejects_finalize_op) -{ - migraphx::module m; - auto one = m.add_literal(1); - auto evaluable = m.add_instruction(migraphx::make_op("identity"), one); - auto finalized = m.add_instruction(can_eval_finalize_passthrough{}, one); - auto dependent = m.add_instruction(migraphx::make_op("identity"), finalized); - - EXPECT(evaluable->can_eval()); - EXPECT(not finalized->can_eval()); - EXPECT(not dependent->can_eval()); -} - TEST_CASE(check_undefined) { migraphx::module m; diff --git a/test/onnx/gen_onnx.py b/test/onnx/gen_onnx.py index 30f20e8ea9a..1ea70a09792 100644 --- a/test/onnx/gen_onnx.py +++ b/test/onnx/gen_onnx.py @@ -11545,80 +11545,6 @@ def nms_dynamic_classes_test(): return ([node], [b, s, mo, iou, st], [out]) -@onnx_test() -def nonmaxsuppression_zero_boxes_test(): - b = helper.make_tensor_value_info('boxes', TensorProto.FLOAT, [1, 6, 4]) - s = helper.make_tensor_value_info('scores', TensorProto.FLOAT, [1, 1, 6]) - mo = helper.make_tensor_value_info('max_output_boxes_per_class', - TensorProto.INT64, [1]) - iou = helper.make_tensor_value_info('iou_threshold', TensorProto.FLOAT, - [1]) - st = helper.make_tensor_value_info('score_threshold', TensorProto.FLOAT, - [1]) - out = helper.make_tensor_value_info('selected_indices', TensorProto.INT64, - [None, 3]) - - start = np.array([0]) - start_tensor = helper.make_tensor(name='start', - data_type=TensorProto.INT64, - dims=start.shape, - vals=start.astype(int)) - arg_start = helper.make_node('Constant', - inputs=[], - outputs=['arg_start'], - value=start_tensor) - - end = np.array([0]) - end_tensor = helper.make_tensor(name='end', - data_type=TensorProto.INT64, - dims=end.shape, - vals=end.astype(int)) - arg_end = helper.make_node('Constant', - inputs=[], - outputs=['arg_end'], - value=end_tensor) - - boxes_axis = np.array([1]) - boxes_axis_tensor = helper.make_tensor(name='boxes_axis', - data_type=TensorProto.INT64, - dims=boxes_axis.shape, - vals=boxes_axis.astype(int)) - arg_boxes_axis = helper.make_node('Constant', - inputs=[], - outputs=['arg_boxes_axis'], - value=boxes_axis_tensor) - - scores_axis = np.array([2]) - scores_axis_tensor = helper.make_tensor(name='scores_axis', - data_type=TensorProto.INT64, - dims=scores_axis.shape, - vals=scores_axis.astype(int)) - arg_scores_axis = helper.make_node('Constant', - inputs=[], - outputs=['arg_scores_axis'], - value=scores_axis_tensor) - - slice_boxes = onnx.helper.make_node( - 'Slice', - inputs=['boxes', 'arg_start', 'arg_end', 'arg_boxes_axis'], - outputs=['sliced_boxes']) - slice_scores = onnx.helper.make_node( - 'Slice', - inputs=['scores', 'arg_start', 'arg_end', 'arg_scores_axis'], - outputs=['sliced_scores']) - - node = onnx.helper.make_node('NonMaxSuppression', - inputs=[ - 'sliced_boxes', 'sliced_scores', - 'max_output_boxes_per_class', - 'iou_threshold', 'score_threshold' - ], - outputs=['selected_indices']) - - return ([arg_start, arg_end, arg_boxes_axis, arg_scores_axis, - slice_boxes, slice_scores, node], [b, s, mo, iou, st], [out]) - - @onnx_test() def not_test(): x = helper.make_tensor_value_info('0', TensorProto.INT32, [4]) diff --git a/test/onnx/nonmaxsuppression_zero_boxes_test.onnx b/test/onnx/nonmaxsuppression_zero_boxes_test.onnx deleted file mode 100644 index 3ff3d28a541f65333451859579bb8550608a5dfe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 759 zcma))yH3L}7=`P$X~Iu0!7!i#qI5!)7+8ytP&&iXt&8O*7L63_D0Wc9z_ai^ycBjW zN|PeQU@6IezT zSJY`iArESufgkZCkLL^BfwbsZMV}RP*E^QA(BMyC<$h3?gep9=V&S2TLZ1ai(z&%Z zZ?J+v;rXjYI~*RB9tBgGd_uz;nkty;Ya$|5ajME=idjPZh)M~gP^3idbo-r%0+{5+ z^#|-q7BOGx*dY&eU^zc|5&W5E>-ygR@hfaD%9FwvTnFJHbRDGEK(GGhvO0v;G*@hH s|0;k19LL6e - -TEST_CASE(nonmaxsuppression_zero_boxes_test) -{ - migraphx::program p; - auto* mm = p.get_main_module(); - auto boxes = - mm->add_parameter("boxes", migraphx::shape{migraphx::shape::float_type, {1, 6, 4}}); - auto scores = - mm->add_parameter("scores", migraphx::shape{migraphx::shape::float_type, {1, 1, 6}}); - mm->add_parameter("max_output_boxes_per_class", - migraphx::shape{migraphx::shape::int64_type, {1}}); - mm->add_parameter("iou_threshold", migraphx::shape{migraphx::shape::float_type, {1}}); - mm->add_parameter("score_threshold", migraphx::shape{migraphx::shape::float_type, {1}}); - mm->add_literal({{migraphx::shape::int64_type, {1}}, {0}}); - mm->add_literal({{migraphx::shape::int64_type, {1}}, {0}}); - mm->add_literal({{migraphx::shape::int64_type, {1}}, {1}}); - mm->add_literal({{migraphx::shape::int64_type, {1}}, {2}}); - mm->add_instruction(migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {0}}}), - boxes); - mm->add_instruction(migraphx::make_op("slice", {{"axes", {2}}, {"starts", {0}}, {"ends", {0}}}), - scores); - auto ret = mm->add_instruction(migraphx::make_op("undefined")); - mm->add_return({ret}); - - auto prog = read_onnx("nonmaxsuppression_zero_boxes_test.onnx"); - EXPECT(p == prog); -} diff --git a/test/op/CMakeLists.txt b/test/op/CMakeLists.txt index 72cb1175504..e850f58b94f 100644 --- a/test/op/CMakeLists.txt +++ b/test/op/CMakeLists.txt @@ -1,7 +1,7 @@ ##################################################################################### # The MIT License (MIT) # -# Copyright (c) 2015-2026 Advanced Micro Devices, Inc. All rights reserved. +# Copyright (c) 2015-2025 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 @@ -22,7 +22,7 @@ # THE SOFTWARE. ##################################################################################### -file(GLOB OP_BUILDER_TESTS CONFIGURE_DEPENDS builder/*.cpp builder/torch/*.cpp) +file(GLOB OP_BUILDER_TESTS CONFIGURE_DEPENDS builder/*.cpp) rocm_add_test_executable(test_op_builder_test ${OP_BUILDER_TESTS}) target_include_directories(test_op_builder_test PUBLIC ../include include) diff --git a/test/op/builder/gather_elements_test.cpp b/test/op/builder/gather_elements_test.cpp deleted file mode 100644 index aadff09d1da..00000000000 --- a/test/op/builder/gather_elements_test.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 - -// gather_elements flattens the data and gathers element-wise using per-element flat -// offsets: shape_index + (index - axis_coord) * axis_stride, evaluated over the index shape. -TEST_CASE(gather_elements_op_builder_test) -{ - const auto f = migraphx::shape::float_type; - const auto i = migraphx::shape::int32_type; - - migraphx::module mm; - auto data = mm.add_parameter("data", {f, {2, 3}}); - auto ind = mm.add_parameter("ind", {i, {2, 3}}); - - auto arg_data = mm.add_instruction(migraphx::make_op("contiguous"), data); - auto arg_ind = mm.add_instruction(migraphx::make_op("contiguous"), ind); - arg_data = mm.add_instruction(migraphx::make_op("reshape", {{"dims", {6}}}), arg_data); - - std::vector shape_idx = {0, 1, 2, 3, 4, 5}; - std::vector dim_idx = {0, 1, 2, 0, 1, 2}; - auto l_shape_idx = mm.add_literal(migraphx::literal{migraphx::shape{i, {2, 3}}, shape_idx}); - auto l_dim_idx = mm.add_literal(migraphx::literal{migraphx::shape{i, {2, 3}}, dim_idx}); - auto l_stride = mm.add_literal(migraphx::literal{migraphx::shape{i, {1}}, {1}}); - l_stride = - mm.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 3}}}), l_stride); - auto dim_diff = mm.add_instruction(migraphx::make_op("sub"), arg_ind, l_dim_idx); - auto delta = mm.add_instruction(migraphx::make_op("mul"), dim_diff, l_stride); - auto indices = mm.add_instruction(migraphx::make_op("add"), l_shape_idx, delta); - mm.add_instruction(migraphx::make_op("gather", {{"axis", 0}}), arg_data, indices); - - EXPECT(mm == make_op_module("gather_elements", {{"axis", 1}}, mm.get_parameters())); -} diff --git a/test/op/builder/torch/as_strided_test.cpp b/test/op/builder/torch/as_strided_test.cpp deleted file mode 100644 index b0a799df1f4..00000000000 --- a/test/op/builder/torch/as_strided_test.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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 - -// tm::as_strided materializes a strided view by flattening the input and gathering -// the element at storage_offset + strided.index(i) for every output coordinate. -TEST_CASE(torch_kit_as_strided_op_builder_test) -{ - const auto f = migraphx::shape::float_type; - - migraphx::module mm; - auto x = mm.add_parameter("x", {f, {4}}); - - std::vector idx_data = {0, 1, 2, 3}; - auto indices = mm.add_literal( - migraphx::literal{migraphx::shape{migraphx::shape::int64_type, {4}}, idx_data}); - auto flat_inp = mm.add_instruction(migraphx::make_op("contiguous"), x); - flat_inp = mm.add_instruction(migraphx::make_op("reshape", {{"dims", {-1}}}), flat_inp); - auto gathered = - mm.add_instruction(migraphx::make_op("gather", {{"axis", 0}}), flat_inp, indices); - mm.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 2}}}), gathered); - - migraphx::value options{{"size", {2, 2}}, {"stride", {2, 1}}, {"storage_offset", 0}}; - EXPECT(mm == make_op_module("tm::as_strided", options, mm.get_parameters())); -} - -// size and stride must have matching lengths. -TEST_CASE(torch_kit_as_strided_size_stride_mismatch) -{ - const auto f = migraphx::shape::float_type; - migraphx::module mm; - mm.add_parameter("x", {f, {4}}); - EXPECT(test::throws([&] { - make_op_module("tm::as_strided", - {{"size", {2, 2}}, {"stride", {2}}, {"storage_offset", 0}}, - mm.get_parameters()); - })); -} diff --git a/test/op/builder/torch/batchnorm_test.cpp b/test/op/builder/torch/batchnorm_test.cpp deleted file mode 100644 index 170fff9fa03..00000000000 --- a/test/op/builder/torch/batchnorm_test.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 - -// tm::batchnorm is a thin re-export of the global "batchnorm" builder, so the "tm::"-prefixed -// form must match the un-prefixed builder exactly. -TEST_CASE(torch_kit_batchnorm_op_builder_test) -{ - const auto f = migraphx::shape::float_type; - migraphx::value options{{"epsilon", 1e-5f}}; - migraphx::module mm; - auto x = mm.add_parameter("x", {f, {2, 3, 4, 4}}); - auto scale = mm.add_parameter("scale", {f, {3}}); - auto bias = mm.add_parameter("bias", {f, {3}}); - auto mean = mm.add_parameter("mean", {f, {3}}); - auto var = mm.add_parameter("var", {f, {3}}); - migraphx::op::builder::add("batchnorm", mm, {x, scale, bias, mean, var}, options); - - EXPECT(mm == make_op_module("tm::batchnorm", options, mm.get_parameters())); -} diff --git a/test/op/builder/torch/clip_test.cpp b/test/op/builder/torch/clip_test.cpp deleted file mode 100644 index 99ebc9c29c3..00000000000 --- a/test/op/builder/torch/clip_test.cpp +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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 - -// tm::clip lowers to clip/min/max/identity based on which optional bounds are given -// (an undefined arg means "absent"). - -TEST_CASE(torch_kit_clip_min_and_max_op_builder_test) -{ - const auto f = migraphx::shape::float_type; - migraphx::module mm; - auto x = mm.add_parameter("x", {f, {2, 3}}); - auto lo = mm.add_parameter("lo", {f, {2, 3}}); - auto hi = mm.add_parameter("hi", {f, {2, 3}}); - add_common_op(mm, migraphx::make_op("clip"), {x, lo, hi}); - - EXPECT(mm == make_op_module("tm::clip", mm.get_parameters())); -} - -TEST_CASE(torch_kit_clip_min_only_op_builder_test) -{ - // max is undefined -> lowers to max(x, lo). - const auto f = migraphx::shape::float_type; - migraphx::module mm; - auto x = mm.add_parameter("x", {f, {2, 3}}); - auto lo = mm.add_parameter("lo", {f, {2, 3}}); - mm.add_instruction(migraphx::make_op("undefined")); - add_common_op(mm, migraphx::make_op("max"), {x, lo}); - - migraphx::module mm_op_built; - auto x_op = mm_op_built.add_parameter("x", {f, {2, 3}}); - auto lo_op = mm_op_built.add_parameter("lo", {f, {2, 3}}); - auto hi_op = mm_op_built.add_instruction(migraphx::make_op("undefined")); - migraphx::op::builder::add("tm::clip", mm_op_built, {x_op, lo_op, hi_op}); - EXPECT(mm == mm_op_built); -} - -TEST_CASE(torch_kit_clip_max_only_op_builder_test) -{ - // min is undefined -> lowers to min(x, hi). - const auto f = migraphx::shape::float_type; - migraphx::module mm; - auto x = mm.add_parameter("x", {f, {2, 3}}); - mm.add_instruction(migraphx::make_op("undefined")); - auto hi = mm.add_parameter("hi", {f, {2, 3}}); - add_common_op(mm, migraphx::make_op("min"), {x, hi}); - - migraphx::module mm_op_built; - auto x_op = mm_op_built.add_parameter("x", {f, {2, 3}}); - auto lo_op = mm_op_built.add_instruction(migraphx::make_op("undefined")); - auto hi_op = mm_op_built.add_parameter("hi", {f, {2, 3}}); - migraphx::op::builder::add("tm::clip", mm_op_built, {x_op, lo_op, hi_op}); - EXPECT(mm == mm_op_built); -} - -TEST_CASE(torch_kit_clip_none_op_builder_test) -{ - // Neither bound supplied -> identity(x). - const auto f = migraphx::shape::float_type; - migraphx::module mm; - auto x = mm.add_parameter("x", {f, {2, 3}}); - mm.add_instruction(migraphx::make_op("undefined")); - mm.add_instruction(migraphx::make_op("undefined")); - mm.add_instruction(migraphx::make_op("identity"), x); - - migraphx::module mm_op_built; - auto x_op = mm_op_built.add_parameter("x", {f, {2, 3}}); - auto lo_op = mm_op_built.add_instruction(migraphx::make_op("undefined")); - auto hi_op = mm_op_built.add_instruction(migraphx::make_op("undefined")); - migraphx::op::builder::add("tm::clip", mm_op_built, {x_op, lo_op, hi_op}); - EXPECT(mm == mm_op_built); -} diff --git a/test/op/builder/torch/conv_transpose_test.cpp b/test/op/builder/torch/conv_transpose_test.cpp deleted file mode 100644 index 53892d9d45a..00000000000 --- a/test/op/builder/torch/conv_transpose_test.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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 - -// tm::conv_transpose runs convolution_backwards unpadded, crops off the symmetric -// padding while keeping the output_padding elements, then adds the channel bias. -TEST_CASE(torch_kit_conv_transpose_op_builder_test) -{ - const auto f = migraphx::shape::float_type; - std::vector stride = {2, 2}; - std::vector padding = {1, 1}; - std::vector dilation = {1, 1}; - std::vector output_padding = {1, 1}; - migraphx::value options{{"stride", stride}, - {"padding", padding}, - {"dilation", dilation}, - {"output_padding", output_padding}, - {"group", 1}}; - - migraphx::module mm; - auto x = mm.add_parameter("x", {f, {1, 3, 4, 4}}); - auto w = mm.add_parameter("w", {f, {3, 4, 3, 3}}); - auto bias = mm.add_parameter("bias", {f, {4}}); - auto out = mm.add_instruction( - migraphx::make_op( - "convolution_backwards", - {{"stride", stride}, {"padding", {0, 0}}, {"dilation", dilation}, {"group", 1}}), - x, - w); - auto cropped = mm.add_instruction( - migraphx::make_op("slice", {{"axes", {2, 3}}, {"starts", {1, 1}}, {"ends", {9, 9}}}), out); - auto b = mm.add_instruction( - migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", {1, 4, 8, 8}}}), bias); - mm.add_instruction(migraphx::make_op("add"), cropped, b); - - EXPECT(mm == make_op_module("tm::conv_transpose", options, mm.get_parameters())); -} - -// tm::conv_transpose with no output_padding passes padding straight to the op. -TEST_CASE(torch_kit_conv_transpose_no_crop_op_builder_test) -{ - const auto f = migraphx::shape::float_type; - std::vector stride = {1, 1}; - std::vector padding = {1, 1}; - std::vector dilation = {1, 1}; - std::vector output_padding = {0, 0}; - migraphx::value options{{"stride", stride}, - {"padding", padding}, - {"dilation", dilation}, - {"output_padding", output_padding}, - {"group", 1}}; - - migraphx::module mm; - auto x = mm.add_parameter("x", {f, {1, 3, 4, 4}}); - auto w = mm.add_parameter("w", {f, {3, 4, 3, 3}}); - mm.add_instruction( - migraphx::make_op( - "convolution_backwards", - {{"stride", stride}, {"padding", padding}, {"dilation", dilation}, {"group", 1}}), - x, - w); - - EXPECT(mm == make_op_module("tm::conv_transpose", options, mm.get_parameters())); -} diff --git a/test/op/builder/torch/convolution_test.cpp b/test/op/builder/torch/convolution_test.cpp deleted file mode 100644 index 70c1cf54c9e..00000000000 --- a/test/op/builder/torch/convolution_test.cpp +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 - -// tm::convolution aliases the shared convolution builder (conv + fused channel bias). Note the -// builder's plural attribute names. -TEST_CASE(torch_kit_convolution_op_builder_test) -{ - const auto f = migraphx::shape::float_type; - std::vector strides = {1, 1}; - std::vector paddings = {0, 0}; - std::vector dilations = {1, 1}; - migraphx::value options{ - {"strides", strides}, {"paddings", paddings}, {"dilations", dilations}, {"group", 1}}; - migraphx::module mm; - auto x = mm.add_parameter("x", {f, {1, 3, 8, 8}}); - auto w = mm.add_parameter("w", {f, {4, 3, 3, 3}}); - auto bias = mm.add_parameter("bias", {f, {4}}); - migraphx::op::builder::add("convolution", mm, {x, w, bias}, options); - - EXPECT(mm == make_op_module("tm::convolution", options, mm.get_parameters())); -} diff --git a/test/op/builder/torch/dot_test.cpp b/test/op/builder/torch/dot_test.cpp deleted file mode 100644 index a2c958e46c6..00000000000 --- a/test/op/builder/torch/dot_test.cpp +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 - -// tm::dot aliases the shared dot builder (numpy batch-broadcast + dot). Mixed batch ranks -// exercise the broadcasting. -TEST_CASE(torch_kit_dot_op_builder_test) -{ - const auto f = migraphx::shape::float_type; - migraphx::module mm; - auto a = mm.add_parameter("a", {f, {2, 1, 3, 4}}); - auto b = mm.add_parameter("b", {f, {5, 4, 6}}); - migraphx::op::builder::add("dot", mm, {a, b}); - - EXPECT(mm == make_op_module("tm::dot", mm.get_parameters())); -} diff --git a/test/op/builder/torch/floor_div_test.cpp b/test/op/builder/torch/floor_div_test.cpp deleted file mode 100644 index 8e047fd661c..00000000000 --- a/test/op/builder/torch/floor_div_test.cpp +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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 - -// tm::floor_div == floor(common div); different ranks exercise broadcasting. -TEST_CASE(torch_kit_floor_div_op_builder_test) -{ - const auto f = migraphx::shape::float_type; - migraphx::module mm; - auto a = mm.add_parameter("a", {f, {2, 3, 4}}); - auto b = mm.add_parameter("b", {f, {4}}); - auto quotient = add_common_op(mm, migraphx::make_op("div"), {a, b}); - mm.add_instruction(migraphx::make_op("floor"), quotient); - - EXPECT(mm == make_op_module("tm::floor_div", mm.get_parameters())); -} diff --git a/test/op/builder/torch/gelu_test.cpp b/test/op/builder/torch/gelu_test.cpp deleted file mode 100644 index 791ab64e172..00000000000 --- a/test/op/builder/torch/gelu_test.cpp +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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 - -// tm::gelu_erf is a thin re-export of the global "gelu_erf" builder, so the "tm::"-prefixed -// form must match it exactly. -TEST_CASE(torch_kit_gelu_erf_op_builder_test) -{ - const auto f = migraphx::shape::float_type; - migraphx::module mm; - auto x = mm.add_parameter("x", {f, {2, 3}}); - migraphx::op::builder::add("gelu_erf", mm, {x}); - - EXPECT(mm == make_op_module("tm::gelu_erf", mm.get_parameters())); -} diff --git a/test/op/builder/torch/glu_test.cpp b/test/op/builder/torch/glu_test.cpp deleted file mode 100644 index 2fabb5b07c7..00000000000 --- a/test/op/builder/torch/glu_test.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 - -// tm::glu splits the input in half along `axis` and gates the first half by -// sigmoid of the second: glu(x) = x1 * sigmoid(x2). -TEST_CASE(torch_kit_glu_op_builder_test) -{ - const auto f = migraphx::shape::float_type; - migraphx::module mm; - auto x = mm.add_parameter("x", {f, {2, 4}}); - auto first = mm.add_instruction( - migraphx::make_op("slice", {{"axes", {1}}, {"starts", {0}}, {"ends", {2}}}), x); - auto second = mm.add_instruction( - migraphx::make_op("slice", {{"axes", {1}}, {"starts", {2}}, {"ends", {4}}}), x); - auto gate = mm.add_instruction(migraphx::make_op("sigmoid"), second); - add_common_op(mm, migraphx::make_op("mul"), {first, gate}); - - EXPECT(mm == make_op_module("tm::glu", {{"axis", -1}}, mm.get_parameters())); -} diff --git a/test/op/builder/torch/group_norm_test.cpp b/test/op/builder/torch/group_norm_test.cpp deleted file mode 100644 index 5cdc9270bdf..00000000000 --- a/test/op/builder/torch/group_norm_test.cpp +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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 - -// tm::group_norm reshapes to (N, num_groups, -1), normalizes over the trailing axis, -// reshapes back, then applies the per-channel affine. -TEST_CASE(torch_kit_group_norm_op_builder_test) -{ - const auto f = migraphx::shape::float_type; - const float eps = 1e-5f; - - migraphx::module mm; - auto x = mm.add_parameter("x", {f, {2, 4, 3}}); - auto scale = mm.add_parameter("scale", {f, {4}}); - auto bias = mm.add_parameter("bias", {f, {4}}); - - std::vector axes = {-1}; - auto grouped = mm.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 2, -1}}}), x); - auto mean = mm.add_instruction(migraphx::make_op("reduce_mean", {{"axes", axes}}), grouped); - auto x_sub = add_common_op(mm, migraphx::make_op("sub"), {grouped, mean}); - auto sqdiff = add_common_op(mm, migraphx::make_op("sqdiff"), {grouped, mean}); - auto variance = mm.add_instruction(migraphx::make_op("reduce_mean", {{"axes", axes}}), sqdiff); - auto eps_lit = mm.add_literal(migraphx::literal{migraphx::shape{f}, {eps}}); - auto var_eps = add_common_op(mm, migraphx::make_op("add"), {variance, eps_lit}); - auto rsqrt = mm.add_instruction(migraphx::make_op("rsqrt"), var_eps); - auto norm = add_common_op(mm, migraphx::make_op("mul"), {x_sub, rsqrt}); - auto norm_r = mm.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 4, 3}}}), norm); - auto scale_u = mm.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), scale); - auto bias_u = mm.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1}}}), bias); - auto scaled = add_common_op(mm, migraphx::make_op("mul"), {norm_r, scale_u}); - add_common_op(mm, migraphx::make_op("add"), {scaled, bias_u}); - - EXPECT(mm == make_op_module( - "tm::group_norm", {{"epsilon", eps}, {"num_groups", 2}}, mm.get_parameters())); -} - -// num_groups must divide the channel dim and the input must have spatial dims. -TEST_CASE(torch_kit_group_norm_bad_input_op_builder_test) -{ - const auto f = migraphx::shape::float_type; - migraphx::module mm; - mm.add_parameter("x", {f, {2, 3, 4}}); // 3 channels not divisible by num_groups = 2 - EXPECT(test::throws([&] { - make_op_module( - "tm::group_norm", {{"epsilon", 1e-5f}, {"num_groups", 2}}, mm.get_parameters()); - })); -} diff --git a/test/op/builder/torch/hardsigmoid_test.cpp b/test/op/builder/torch/hardsigmoid_test.cpp deleted file mode 100644 index fcccc7bed17..00000000000 --- a/test/op/builder/torch/hardsigmoid_test.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 - -// tm::hardsigmoid == clip(alpha * x + beta, 0, 1) with alpha = 1/6, beta = 1/2. -TEST_CASE(torch_kit_hardsigmoid_op_builder_test) -{ - const auto f = migraphx::shape::float_type; - migraphx::module mm; - auto x = mm.add_parameter("x", {f, {2, 3}}); - auto alpha = mm.add_literal(migraphx::literal{migraphx::shape{f}, {1.0f / 6.0f}}); - auto beta = mm.add_literal(migraphx::literal{migraphx::shape{f}, {0.5f}}); - auto lo = mm.add_literal(migraphx::literal{migraphx::shape{f}, {0.0f}}); - auto hi = mm.add_literal(migraphx::literal{migraphx::shape{f}, {1.0f}}); - auto scaled = add_common_op(mm, migraphx::make_op("mul"), {alpha, x}); - auto shifted = add_common_op(mm, migraphx::make_op("add"), {beta, scaled}); - add_common_op(mm, migraphx::make_op("clip"), {shifted, lo, hi}); - - EXPECT(mm == make_op_module("tm::hardsigmoid", mm.get_parameters())); -} diff --git a/test/op/builder/torch/index_copy_test.cpp b/test/op/builder/torch/index_copy_test.cpp deleted file mode 100644 index 62faad933b2..00000000000 --- a/test/op/builder/torch/index_copy_test.cpp +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 - -// tm::index_copy reshapes the 1-D index to the src rank, broadcasts it to the src -// shape, and scatters src into the rows of `dim` it selects. -TEST_CASE(torch_kit_index_copy_op_builder_test) -{ - const auto f = migraphx::shape::float_type; - const auto i = migraphx::shape::int32_type; - - migraphx::module mm; - auto inp = mm.add_parameter("inp", {f, {5, 4}}); - auto idx = mm.add_parameter("idx", {i, {2}}); - auto src = mm.add_parameter("src", {f, {2, 4}}); - - auto scatter_idx = mm.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 1}}}), idx); - scatter_idx = mm.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 4}}}), - scatter_idx); - mm.add_instruction(migraphx::make_op("scatter_none", {{"axis", 0}}), inp, scatter_idx, src); - - EXPECT(mm == make_op_module("tm::index_copy", {{"dim", 0}}, mm.get_parameters())); -} diff --git a/test/op/builder/torch/instance_norm_test.cpp b/test/op/builder/torch/instance_norm_test.cpp deleted file mode 100644 index 7150c49e294..00000000000 --- a/test/op/builder/torch/instance_norm_test.cpp +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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 - -// tm::instance_norm computes stats from the input over the batch and spatial dims -// (every dim except channel dim 1), then applies the per-channel affine. -TEST_CASE(torch_kit_instance_norm_op_builder_test) -{ - const auto f = migraphx::shape::float_type; - const float eps = 1e-5f; - - migraphx::module mm; - auto x = mm.add_parameter("x", {f, {2, 3, 4, 4}}); - auto scale = mm.add_parameter("scale", {f, {3}}); - auto bias = mm.add_parameter("bias", {f, {3}}); - - std::vector axes = {0, 2, 3}; - auto mean = mm.add_instruction(migraphx::make_op("reduce_mean", {{"axes", axes}}), x); - auto x_sub = add_common_op(mm, migraphx::make_op("sub"), {x, mean}); - auto sqdiff = add_common_op(mm, migraphx::make_op("sqdiff"), {x, mean}); - auto variance = mm.add_instruction(migraphx::make_op("reduce_mean", {{"axes", axes}}), sqdiff); - auto eps_lit = mm.add_literal(migraphx::literal{migraphx::shape{f}, {eps}}); - auto var_eps = add_common_op(mm, migraphx::make_op("add"), {variance, eps_lit}); - auto rsqrt = mm.add_instruction(migraphx::make_op("rsqrt"), var_eps); - auto norm = add_common_op(mm, migraphx::make_op("mul"), {x_sub, rsqrt}); - auto scale_u = mm.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), scale); - auto bias_u = mm.add_instruction(migraphx::make_op("unsqueeze", {{"axes", {1, 2}}}), bias); - auto scaled = add_common_op(mm, migraphx::make_op("mul"), {norm, scale_u}); - add_common_op(mm, migraphx::make_op("add"), {scaled, bias_u}); - - EXPECT(mm == make_op_module("tm::instance_norm", {{"epsilon", eps}}, mm.get_parameters())); -} - -// input must be at least rank 2. -TEST_CASE(torch_kit_instance_norm_low_rank_op_builder_test) -{ - const auto f = migraphx::shape::float_type; - migraphx::module mm; - mm.add_parameter("x", {f, {4}}); - EXPECT(test::throws( - [&] { make_op_module("tm::instance_norm", {{"epsilon", 1e-5f}}, mm.get_parameters()); })); -} diff --git a/test/op/builder/torch/layer_norm_test.cpp b/test/op/builder/torch/layer_norm_test.cpp deleted file mode 100644 index 1140ff34522..00000000000 --- a/test/op/builder/torch/layer_norm_test.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 - -// tm::layer_norm == (x - mean) * rsqrt(var + eps) * scale + bias, reduced over `axes`, -// with the affine params broadcast right-aligned against the input. -TEST_CASE(torch_kit_layer_norm_op_builder_test) -{ - const auto f = migraphx::shape::float_type; - const float eps = 1e-5f; - std::vector axes = {-1}; - - migraphx::module mm; - auto x = mm.add_parameter("x", {f, {2, 3, 4}}); - auto scale = mm.add_parameter("scale", {f, {4}}); - auto bias = mm.add_parameter("bias", {f, {4}}); - auto mean = mm.add_instruction(migraphx::make_op("reduce_mean", {{"axes", axes}}), x); - auto x_sub = add_common_op(mm, migraphx::make_op("sub"), {x, mean}); - auto sqdiff = add_common_op(mm, migraphx::make_op("sqdiff"), {x, mean}); - auto variance = mm.add_instruction(migraphx::make_op("reduce_mean", {{"axes", axes}}), sqdiff); - auto eps_lit = mm.add_literal(migraphx::literal{migraphx::shape{f}, {eps}}); - auto var_eps = add_common_op(mm, migraphx::make_op("add"), {variance, eps_lit}); - auto rsqrt = mm.add_instruction(migraphx::make_op("rsqrt"), var_eps); - auto norm = add_common_op(mm, migraphx::make_op("mul"), {x_sub, rsqrt}); - auto scaled = add_common_op(mm, migraphx::make_op("mul"), {norm, scale}); - add_common_op(mm, migraphx::make_op("add"), {scaled, bias}); - - EXPECT(mm == make_op_module( - "tm::layer_norm", {{"epsilon", eps}, {"axes", axes}}, mm.get_parameters())); -} diff --git a/test/op/builder/torch/linear_test.cpp b/test/op/builder/torch/linear_test.cpp deleted file mode 100644 index a5b6c8e5714..00000000000 --- a/test/op/builder/torch/linear_test.cpp +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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 - -// ND linear flattens to rank 2, delegates to gemm, then reshapes back. -TEST_CASE(torch_kit_linear_op_builder_test) -{ - const auto f = migraphx::shape::float_type; - migraphx::module mm; - auto x = mm.add_parameter("x", {f, {2, 3, 4}}); - auto w = mm.add_parameter("w", {f, {5, 4}}); - auto bias = mm.add_parameter("bias", {f, {5}}); - auto x2d = mm.add_instruction(migraphx::make_op("reshape", {{"dims", {6, 4}}}), x); - auto out = migraphx::op::builder::add("gemm", mm, {x2d, w, bias}, {{"transB", true}}).front(); - mm.add_instruction(migraphx::make_op("reshape", {{"dims", {2, 3, 5}}}), out); - - EXPECT(mm == make_op_module("tm::linear", mm.get_parameters())); -} - -// rank-2 linear is exactly the gemm builder. -TEST_CASE(torch_kit_linear_no_bias_op_builder_test) -{ - const auto f = migraphx::shape::float_type; - migraphx::module mm; - auto x = mm.add_parameter("x", {f, {3, 4}}); - auto w = mm.add_parameter("w", {f, {5, 4}}); - migraphx::op::builder::add("gemm", mm, {x, w}, {{"transB", true}}); - - EXPECT(mm == make_op_module("tm::linear", mm.get_parameters())); -} diff --git a/test/op/builder/torch/lstm_test.cpp b/test/op/builder/torch/lstm_test.cpp deleted file mode 100644 index b247fedb73c..00000000000 --- a/test/op/builder/torch/lstm_test.cpp +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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 - -// The tm::lstm builder expands into an lstm op plus the rnn_last_hs_output and -// rnn_last_cell_output ops. - -TEST_CASE(torch_kit_lstm_forward_op_builder_test) -{ - const std::size_t hidden_size = 2; - - migraphx::module mm; - auto x = mm.add_parameter("x", {migraphx::shape::float_type, {3, 4, 5}}); - auto w = mm.add_parameter("w", {migraphx::shape::float_type, {1, 8, 5}}); - auto r = mm.add_parameter("r", {migraphx::shape::float_type, {1, 8, 2}}); - - // A forward lstm defaults to the {sigmoid, tanh, tanh} activation set. - std::vector actv_funcs{ - migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")}; - - auto hs = mm.add_instruction( - migraphx::make_op( - "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(actv_funcs)}}), - x, - w, - r); - mm.add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); - mm.add_instruction(migraphx::make_op("rnn_last_cell_output"), hs); - - EXPECT(mm == make_op_module("tm::lstm", {{"hidden_size", hidden_size}}, mm.get_parameters())); -} - -TEST_CASE(torch_kit_lstm_bidirectional_op_builder_test) -{ - const std::size_t hidden_size = 2; - - migraphx::module mm; - auto x = mm.add_parameter("x", {migraphx::shape::float_type, {3, 4, 5}}); - auto w = mm.add_parameter("w", {migraphx::shape::float_type, {2, 8, 5}}); - auto r = mm.add_parameter("r", {migraphx::shape::float_type, {2, 8, 2}}); - - // A bidirectional lstm needs the activation set duplicated (6 functions). - std::vector actv_funcs{migraphx::make_op("sigmoid"), - migraphx::make_op("tanh"), - migraphx::make_op("tanh"), - migraphx::make_op("sigmoid"), - migraphx::make_op("tanh"), - migraphx::make_op("tanh")}; - - auto hs = mm.add_instruction( - migraphx::make_op("lstm", - {{"hidden_size", hidden_size}, - {"actv_func", migraphx::to_value(actv_funcs)}, - {"direction", migraphx::op::rnn_direction::bidirectional}}), - x, - w, - r); - mm.add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); - mm.add_instruction(migraphx::make_op("rnn_last_cell_output"), hs); - - migraphx::value options{{"hidden_size", hidden_size}, - {"direction", migraphx::op::rnn_direction::bidirectional}}; - EXPECT(mm == make_op_module("tm::lstm", options, mm.get_parameters())); -} - -TEST_CASE(torch_kit_lstm_custom_actv_funcs_op_builder_test) -{ - const std::size_t hidden_size = 2; - - // Explicitly provided activation functions should be used as-is and not be - // overridden with the defaults. - std::vector actv_funcs{ - migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("sigmoid")}; - migraphx::value options{{"hidden_size", hidden_size}, - {"actv_func", migraphx::to_value(actv_funcs)}}; - - migraphx::module mm; - auto x = mm.add_parameter("x", {migraphx::shape::float_type, {3, 4, 5}}); - auto w = mm.add_parameter("w", {migraphx::shape::float_type, {1, 8, 5}}); - auto r = mm.add_parameter("r", {migraphx::shape::float_type, {1, 8, 2}}); - auto hs = mm.add_instruction(migraphx::make_op("lstm", options), x, w, r); - mm.add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); - mm.add_instruction(migraphx::make_op("rnn_last_cell_output"), hs); - - EXPECT(mm == make_op_module("tm::lstm", options, mm.get_parameters())); -} diff --git a/test/op/builder/torch/nan_to_num_test.cpp b/test/op/builder/torch/nan_to_num_test.cpp deleted file mode 100644 index b635ffa27de..00000000000 --- a/test/op/builder/torch/nan_to_num_test.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 - -// tm::nan_to_num replaces NaN with `nan`, +inf with `posinf`, -inf with `neginf`; -// the inf sign is recovered by comparing the input against 0. where broadcasts its -// operands but does not promote the boolean condition. -TEST_CASE(torch_kit_nan_to_num_op_builder_test) -{ - const auto f = migraphx::shape::float_type; - migraphx::value options{{"nan", 0.0f}, {"posinf", 1e4f}, {"neginf", -1e4f}}; - - migraphx::module mm; - auto x = mm.add_parameter("x", {f, {2, 3}}); - auto nan_lit = mm.add_literal(migraphx::literal{migraphx::shape{f}, {0.0f}}); - auto zero = mm.add_literal(migraphx::literal{migraphx::shape{f}, {0.0f}}); - auto posinf_lit = mm.add_literal(migraphx::literal{migraphx::shape{f}, {1e4f}}); - auto neginf_lit = mm.add_literal(migraphx::literal{migraphx::shape{f}, {-1e4f}}); - - auto is_nan = mm.add_instruction(migraphx::make_op("isnan"), x); - auto result = - add_common_op(mm, migraphx::make_op("where"), {is_nan, nan_lit, x}, {.common_type = false}); - auto is_inf = mm.add_instruction(migraphx::make_op("isinf"), x); - auto less = add_common_op(mm, migraphx::make_op("less"), {x, zero}); - auto greater = add_common_op(mm, migraphx::make_op("greater"), {x, zero}); - auto neg_mask = add_common_op(mm, migraphx::make_op("logical_and"), {less, is_inf}); - auto pos_mask = add_common_op(mm, migraphx::make_op("logical_and"), {greater, is_inf}); - result = add_common_op( - mm, migraphx::make_op("where"), {neg_mask, neginf_lit, result}, {.common_type = false}); - add_common_op( - mm, migraphx::make_op("where"), {pos_mask, posinf_lit, result}, {.common_type = false}); - - EXPECT(mm == make_op_module("tm::nan_to_num", options, mm.get_parameters())); -} diff --git a/test/op/builder/torch/scatter_reduce_test.cpp b/test/op/builder/torch/scatter_reduce_test.cpp deleted file mode 100644 index 171d116ab37..00000000000 --- a/test/op/builder/torch/scatter_reduce_test.cpp +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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 - -// tm::scatter_reduce maps the torch reduction onto the matching scatter op. When -// include_self is false the target positions are first overwritten with the -// reduction identity (via scatter_none) so they drop out of the reduction. -static void check_scatter_reduce(const std::string& reduce, - const std::string& scatter_op, - float identity, - bool include_self) -{ - const auto f = migraphx::shape::float_type; - const auto i = migraphx::shape::int32_type; - - migraphx::module mm; - auto inp = mm.add_parameter("inp", {f, {4, 4}}); - auto idx = mm.add_parameter("idx", {i, {2, 4}}); - auto src = mm.add_parameter("src", {f, {2, 4}}); - auto data = inp; - if(not include_self) - { - auto id = mm.add_literal(migraphx::literal{migraphx::shape{f, {1}}, {identity}}); - id = mm.add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 4}}}), id); - data = mm.add_instruction(migraphx::make_op("scatter_none", {{"axis", 0}}), inp, idx, id); - } - mm.add_instruction(migraphx::make_op(scatter_op, {{"axis", 0}}), data, idx, src); - - migraphx::value options{{"dim", 0}, {"reduce", reduce}, {"include_self", include_self}}; - EXPECT(mm == make_op_module("tm::scatter_reduce", options, mm.get_parameters())); -} - -TEST_CASE(torch_kit_scatter_reduce_sum_include_self) -{ - check_scatter_reduce("sum", "scatter_add", 0.0f, true); -} - -TEST_CASE(torch_kit_scatter_reduce_sum) { check_scatter_reduce("sum", "scatter_add", 0.0f, false); } - -TEST_CASE(torch_kit_scatter_reduce_prod) -{ - check_scatter_reduce("prod", "scatter_mul", 1.0f, false); -} - -TEST_CASE(torch_kit_scatter_reduce_amax) -{ - check_scatter_reduce("amax", "scatter_max", std::numeric_limits::lowest(), false); -} - -TEST_CASE(torch_kit_scatter_reduce_amin) -{ - check_scatter_reduce("amin", "scatter_min", std::numeric_limits::max(), false); -} - -TEST_CASE(torch_kit_scatter_reduce_unsupported_reduce) -{ - EXPECT(test::throws([&] { - make_op_module( - "tm::scatter_reduce", {{"dim", 0}, {"reduce", "bogus"}, {"include_self", true}}, {}); - })); -} diff --git a/test/op/builder/torch/selu_test.cpp b/test/op/builder/torch/selu_test.cpp deleted file mode 100644 index 8b8b1fc2295..00000000000 --- a/test/op/builder/torch/selu_test.cpp +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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 - -// tm::selu == gamma * (max(0, x) + min(0, alpha * (exp(x) - 1))) with the SELU -// constants; literals are created in the builder's order so the modules match. -TEST_CASE(torch_kit_selu_op_builder_test) -{ - const auto f = migraphx::shape::float_type; - migraphx::module mm; - auto x = mm.add_parameter("x", {f, {2, 3}}); - auto zero = mm.add_literal(migraphx::literal{migraphx::shape{f}, {0.0f}}); - auto one = mm.add_literal(migraphx::literal{migraphx::shape{f}, {1.0f}}); - auto alpha = mm.add_literal(migraphx::literal{migraphx::shape{f}, {1.6732632423543772f}}); - auto gamma = mm.add_literal(migraphx::literal{migraphx::shape{f}, {1.0507009873554805f}}); - auto linear = add_common_op(mm, migraphx::make_op("max"), {zero, x}); - auto exp_x = mm.add_instruction(migraphx::make_op("exp"), x); - auto exp_sub = add_common_op(mm, migraphx::make_op("sub"), {exp_x, one}); - auto exp_mul = add_common_op(mm, migraphx::make_op("mul"), {alpha, exp_sub}); - auto exp_part = add_common_op(mm, migraphx::make_op("min"), {zero, exp_mul}); - auto sum = add_common_op(mm, migraphx::make_op("add"), {linear, exp_part}); - add_common_op(mm, migraphx::make_op("mul"), {gamma, sum}); - - EXPECT(mm == make_op_module("tm::selu", mm.get_parameters())); -} diff --git a/test/op/builder/torch/slice_scatter_test.cpp b/test/op/builder/torch/slice_scatter_test.cpp deleted file mode 100644 index 3fd4e683943..00000000000 --- a/test/op/builder/torch/slice_scatter_test.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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 - -// tm::slice_scatter scatters src into the [start:end:step] slice along `dim`; the -// scatter indices carry the resolved position of each src element along that dim. -TEST_CASE(torch_kit_slice_scatter_op_builder_test) -{ - const auto f = migraphx::shape::float_type; - - migraphx::module mm; - auto input = mm.add_parameter("input", {f, {4, 3}}); - auto src = mm.add_parameter("src", {f, {2, 3}}); - - std::vector idx_data = {0, 0, 0, 1, 1, 1}; - auto indices = mm.add_literal( - migraphx::literal{migraphx::shape{migraphx::shape::int64_type, {2, 3}}, idx_data}); - auto std_input = mm.add_instruction(migraphx::make_op("contiguous"), input); - auto std_src = mm.add_instruction(migraphx::make_op("contiguous"), src); - mm.add_instruction( - migraphx::make_op("scatter_none", {{"axis", 0}}), std_input, indices, std_src); - - migraphx::value options{{"dim", 0}, {"start", 0}, {"end", 2}, {"step", 1}}; - EXPECT(mm == make_op_module("tm::slice_scatter", options, mm.get_parameters())); -} diff --git a/test/op/builder/torch/softsign_test.cpp b/test/op/builder/torch/softsign_test.cpp deleted file mode 100644 index f552139d65c..00000000000 --- a/test/op/builder/torch/softsign_test.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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 - -// tm::softsign == x / (1 + |x|). -TEST_CASE(torch_kit_softsign_op_builder_test) -{ - const auto f = migraphx::shape::float_type; - migraphx::module mm; - auto x = mm.add_parameter("x", {f, {2, 3}}); - auto one = mm.add_literal(migraphx::literal{migraphx::shape{f}, {1.0f}}); - auto abs_x = mm.add_instruction(migraphx::make_op("abs"), x); - auto denom = add_common_op(mm, migraphx::make_op("add"), {abs_x, one}); - add_common_op(mm, migraphx::make_op("div"), {x, denom}); - - EXPECT(mm == make_op_module("tm::softsign", mm.get_parameters())); -} diff --git a/test/op/builder/torch/std_test.cpp b/test/op/builder/torch/std_test.cpp deleted file mode 100644 index 3edd2c7a4ad..00000000000 --- a/test/op/builder/torch/std_test.cpp +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 - -// tm::std == sqrt(sum((x - mean)^2) / (N - correction)) reduced over the axes, -// squeezing them out unless keepdim. Here N == 4 and correction == 1, so N - 1 == 3. -TEST_CASE(torch_kit_std_op_builder_test) -{ - const auto f = migraphx::shape::float_type; - migraphx::value options{{"axes", {1}}, {"keepdim", false}, {"correction", 1.0f}}; - - migraphx::module mm; - auto x = mm.add_parameter("x", {f, {2, 4}}); - auto mean = mm.add_instruction(migraphx::make_op("reduce_mean", {{"axes", {1}}}), x); - auto sub = add_common_op(mm, migraphx::make_op("sub"), {x, mean}); - auto sq = add_common_op(mm, migraphx::make_op("mul"), {sub, sub}); - auto sum = mm.add_instruction(migraphx::make_op("reduce_sum", {{"axes", {1}}}), sq); - auto denom = mm.add_literal(migraphx::literal{migraphx::shape{f}, {3.0f}}); - auto var = add_common_op(mm, migraphx::make_op("div"), {sum, denom}); - auto out = mm.add_instruction(migraphx::make_op("sqrt"), var); - mm.add_instruction(migraphx::make_op("squeeze", {{"axes", {1}}}), out); - - EXPECT(mm == make_op_module("tm::std", options, mm.get_parameters())); -} diff --git a/test/op/builder/torch/vector_norm_test.cpp b/test/op/builder/torch/vector_norm_test.cpp deleted file mode 100644 index 5d55029997a..00000000000 --- a/test/op/builder/torch/vector_norm_test.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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 - -// tm::vector_norm reduces abs(x) over axes with the ord-specific formula, then -// squeezes the reduced axes unless keepdim. General p-norm: sum(abs(x)^ord)^(1/ord). -TEST_CASE(torch_kit_vector_norm_p_op_builder_test) -{ - const auto f = migraphx::shape::float_type; - std::vector axes = {1}; - - migraphx::module mm; - auto x = mm.add_parameter("x", {f, {2, 3}}); - auto abs_x = mm.add_instruction(migraphx::make_op("abs"), x); - auto ord_lit = mm.add_literal(migraphx::literal{migraphx::shape{f}, {2.0f}}); - auto pow_x = add_common_op(mm, migraphx::make_op("pow"), {abs_x, ord_lit}); - auto sum_pow = mm.add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), pow_x); - auto recip = mm.add_instruction(migraphx::make_op("recip"), ord_lit); - auto out = add_common_op(mm, migraphx::make_op("pow"), {sum_pow, recip}); - mm.add_instruction(migraphx::make_op("squeeze", {{"axes", axes}}), out); - - EXPECT(mm == make_op_module("tm::vector_norm", - {{"ord", 2.0f}, {"axes", axes}, {"keepdim", false}}, - mm.get_parameters())); -} - -// ord = +inf -> max(abs(x)); keepdim = true leaves the reduced axis in place. -TEST_CASE(torch_kit_vector_norm_inf_op_builder_test) -{ - const auto f = migraphx::shape::float_type; - std::vector axes = {1}; - - migraphx::module mm; - auto x = mm.add_parameter("x", {f, {2, 3}}); - auto abs_x = mm.add_instruction(migraphx::make_op("abs"), x); - mm.add_instruction(migraphx::make_op("reduce_max", {{"axes", axes}}), abs_x); - - EXPECT(mm == - make_op_module( - "tm::vector_norm", - {{"ord", std::numeric_limits::infinity()}, {"axes", axes}, {"keepdim", true}}, - mm.get_parameters())); -} - -// ord = 0 -> count of nonzero elements: sum(abs(x) > 0). -TEST_CASE(torch_kit_vector_norm_zero_op_builder_test) -{ - const auto f = migraphx::shape::float_type; - std::vector axes = {1}; - - migraphx::module mm; - auto x = mm.add_parameter("x", {f, {2, 3}}); - auto abs_x = mm.add_instruction(migraphx::make_op("abs"), x); - auto zero = mm.add_literal(migraphx::literal{migraphx::shape{f}, {0.0f}}); - auto nonzero = add_common_op(mm, migraphx::make_op("greater"), {abs_x, zero}); - auto counts = mm.add_instruction(migraphx::make_op("convert", {{"target_type", f}}), nonzero); - auto out = mm.add_instruction(migraphx::make_op("reduce_sum", {{"axes", axes}}), counts); - mm.add_instruction(migraphx::make_op("squeeze", {{"axes", axes}}), out); - - EXPECT(mm == make_op_module("tm::vector_norm", - {{"ord", 0.0f}, {"axes", axes}, {"keepdim", false}}, - mm.get_parameters())); -} diff --git a/test/op/builder/torch/common_ops_test.cpp b/test/op/builder/torch_kit_test.cpp similarity index 61% rename from test/op/builder/torch/common_ops_test.cpp rename to test/op/builder/torch_kit_test.cpp index 73f8f1d2042..fec515c9ab9 100644 --- a/test/op/builder/torch/common_ops_test.cpp +++ b/test/op/builder/torch_kit_test.cpp @@ -22,17 +22,17 @@ * THE SOFTWARE. */ -#include -#include -#include -#include #include #include #include #include +#include +#include -// The torch kit registers the common (broadcast/convert) ops and a set of plain passthrough -// ops under the "tm::" prefix. Each builder must insert exactly its wrapped op over the args. +// The torch_kit registers builders under the "tm::" prefix. The custom builder +// "tm::lstm" expands into an lstm op plus the rnn_last_hs_output and +// rnn_last_cell_output ops; the remaining builders are thin wrappers around +// native ops, either with common (broadcast/convert) handling or without. namespace { struct param_spec @@ -41,9 +41,11 @@ struct param_spec migraphx::shape shape; }; -// Verifies a plain (non-common) builder inserts exactly the wrapped op over the given args. -// Returns the comparison so the caller can EXPECT() it with the op name as a literal -- a failure -// message then identifies which op did not match. +// Verifies that a plain (non-common) builder inserts exactly the wrapped op over +// the given args, unchanged. Builds the expected module by hand and compares it +// to what the kit's "tm::"-prefixed builder produces. Returns the comparison so +// the caller can EXPECT() it with the op name as a literal -- that way a failure +// message identifies which op did not match. bool check_plain_op(const std::string& op_name, const migraphx::value& options, const std::vector& params) @@ -59,12 +61,92 @@ bool check_plain_op(const std::string& op_name, } } // namespace +TEST_CASE(torch_lstm_forward_op_builder_test) +{ + const std::size_t hidden_size = 2; + + migraphx::module mm; + auto x = mm.add_parameter("x", {migraphx::shape::float_type, {3, 4, 5}}); + auto w = mm.add_parameter("w", {migraphx::shape::float_type, {1, 8, 5}}); + auto r = mm.add_parameter("r", {migraphx::shape::float_type, {1, 8, 2}}); + + // A forward lstm defaults to the {sigmoid, tanh, tanh} activation set. + std::vector actv_funcs{ + migraphx::make_op("sigmoid"), migraphx::make_op("tanh"), migraphx::make_op("tanh")}; + + auto hs = mm.add_instruction( + migraphx::make_op( + "lstm", {{"hidden_size", hidden_size}, {"actv_func", migraphx::to_value(actv_funcs)}}), + x, + w, + r); + mm.add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); + mm.add_instruction(migraphx::make_op("rnn_last_cell_output"), hs); + + EXPECT(mm == make_op_module("tm::lstm", {{"hidden_size", hidden_size}}, mm.get_parameters())); +} + +TEST_CASE(torch_lstm_bidirectional_op_builder_test) +{ + const std::size_t hidden_size = 2; + + migraphx::module mm; + auto x = mm.add_parameter("x", {migraphx::shape::float_type, {3, 4, 5}}); + auto w = mm.add_parameter("w", {migraphx::shape::float_type, {2, 8, 5}}); + auto r = mm.add_parameter("r", {migraphx::shape::float_type, {2, 8, 2}}); + + // A bidirectional lstm needs the activation set duplicated (6 functions). + std::vector actv_funcs{migraphx::make_op("sigmoid"), + migraphx::make_op("tanh"), + migraphx::make_op("tanh"), + migraphx::make_op("sigmoid"), + migraphx::make_op("tanh"), + migraphx::make_op("tanh")}; + + auto hs = mm.add_instruction( + migraphx::make_op("lstm", + {{"hidden_size", hidden_size}, + {"actv_func", migraphx::to_value(actv_funcs)}, + {"direction", migraphx::op::rnn_direction::bidirectional}}), + x, + w, + r); + mm.add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); + mm.add_instruction(migraphx::make_op("rnn_last_cell_output"), hs); + + migraphx::value options{{"hidden_size", hidden_size}, + {"direction", migraphx::op::rnn_direction::bidirectional}}; + EXPECT(mm == make_op_module("tm::lstm", options, mm.get_parameters())); +} + +TEST_CASE(torch_lstm_custom_actv_funcs_op_builder_test) +{ + const std::size_t hidden_size = 2; + + // Explicitly provided activation functions should be used as-is and not be + // overridden with the defaults. + std::vector actv_funcs{ + migraphx::make_op("tanh"), migraphx::make_op("sigmoid"), migraphx::make_op("sigmoid")}; + migraphx::value options{{"hidden_size", hidden_size}, + {"actv_func", migraphx::to_value(actv_funcs)}}; + + migraphx::module mm; + auto x = mm.add_parameter("x", {migraphx::shape::float_type, {3, 4, 5}}); + auto w = mm.add_parameter("w", {migraphx::shape::float_type, {1, 8, 5}}); + auto r = mm.add_parameter("r", {migraphx::shape::float_type, {1, 8, 2}}); + auto hs = mm.add_instruction(migraphx::make_op("lstm", options), x, w, r); + mm.add_instruction(migraphx::make_op("rnn_last_hs_output"), hs); + mm.add_instruction(migraphx::make_op("rnn_last_cell_output"), hs); + + EXPECT(mm == make_op_module("tm::lstm", options, mm.get_parameters())); +} + TEST_CASE(torch_kit_common_unary_op_builder_test) { const std::vector unary_ops{ - "abs", "acos", "asin", "atan", "ceil", "cos", "cosh", "elu", "erf", - "exp", "floor", "isinf", "isnan", "log", "log2", "leaky_relu", "neg", "recip", - "relu", "rsqrt", "sigmoid", "sign", "sin", "sinh", "sqrt", "tan", "tanh"}; + "ceil", "cos", "cosh", "elu", "erf", "exp", "floor", "isinf", + "isnan", "log", "log2", "leaky_relu", "neg", "recip", "relu", "rsqrt", + "sigmoid", "sign", "sin", "sinh", "sqrt", "tan", "tanh"}; std::for_each(unary_ops.begin(), unary_ops.end(), [&](const std::string& op_name) { migraphx::module mm; @@ -78,7 +160,7 @@ TEST_CASE(torch_kit_common_unary_op_builder_test) TEST_CASE(torch_kit_common_binary_op_builder_test) { const std::vector binary_ops{ - "add", "div", "equal", "fmod", "greater", "less", "max", "min", "mul", "pow", "sub"}; + "div", "equal", "fmod", "greater", "less", "max", "min", "mul", "pow", "sub"}; std::for_each(binary_ops.begin(), binary_ops.end(), [&](const std::string& op_name) { migraphx::module mm; @@ -111,15 +193,16 @@ TEST_CASE(torch_kit_common_not_op_builder_test) EXPECT(mm == make_op_module("tm::not", mm.get_parameters())); } -TEST_CASE(torch_kit_common_bitwise_and_op_builder_test) +TEST_CASE(torch_kit_common_dot_op_builder_test) { - // bitwise_and needs integral types; different ranks exercise common broadcasting. + // "dot" is registered as a common op, so its inputs go through common + // broadcasting; use matching square shapes that survive it. migraphx::module mm; - auto a = mm.add_parameter("a", {migraphx::shape::int32_type, {2, 3, 4}}); - auto b = mm.add_parameter("b", {migraphx::shape::int32_type, {4}}); - add_common_op(mm, migraphx::make_op("bitwise_and"), {a, b}); + auto a = mm.add_parameter("a", {migraphx::shape::float_type, {4, 4}}); + auto b = mm.add_parameter("b", {migraphx::shape::float_type, {4, 4}}); + add_common_op(mm, migraphx::make_op("dot"), {a, b}); - EXPECT(mm == make_op_module("tm::bitwise_and", mm.get_parameters())); + EXPECT(mm == make_op_module("tm::dot", mm.get_parameters())); } TEST_CASE(torch_kit_where_op_builder_test) @@ -164,13 +247,14 @@ TEST_CASE(torch_kit_ops_op_builder_test) EXPECT(check_plain_op("broadcast", {{"axis", 1}, {"out_lens", {2, 4, 6}}}, {{"a", {f, {4}}}})); EXPECT(check_plain_op("concat", {{"axis", 0}}, {{"a", {f, {4, 6}}}, {"b", {f, {4, 6}}}})); EXPECT(check_plain_op("contiguous", obj, {{"a", {f, {4, 6}}}})); + EXPECT( + check_plain_op("convolution", obj, {{"x", {f, {1, 3, 8, 8}}}, {"w", {f, {4, 3, 3, 3}}}})); EXPECT(check_plain_op( "convolution_backwards", obj, {{"x", {f, {1, 3, 8, 8}}}, {"w", {f, {3, 4, 3, 3}}}})); EXPECT(check_plain_op("dequantizelinear", obj, {{"x", {i8, {4, 6}}}, {"scale", {f, {4, 6}}}})); EXPECT(check_plain_op("gather", {{"axis", 0}}, {{"data", {f, {4, 6}}}, {"ind", {i64, {2}}}})); EXPECT(check_plain_op("gathernd", obj, {{"data", {f, {4, 6}}}, {"ind", {i64, {2, 1}}}})); EXPECT(check_plain_op("get_tuple_elem", {{"index", 0}}, {{"a", tuple_s}})); - EXPECT(check_plain_op("logsoftmax", {{"axis", 1}}, {{"a", {f, {4, 6}}}})); EXPECT(check_plain_op("multibroadcast", {{"out_lens", {2, 4, 6}}}, {{"a", {f, {4, 6}}}})); EXPECT(check_plain_op("pad", {{"pads", {0, 0, 1, 1}}}, {{"a", {f, {4, 6}}}})); EXPECT(check_plain_op("pooling", diff --git a/test/op_shape_test.cpp b/test/op_shape_test.cpp index 60dba4fc349..387fceddeaf 100644 --- a/test/op_shape_test.cpp +++ b/test/op_shape_test.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -1616,41 +1615,6 @@ TEST_CASE(flatten_dyn_axis4) input); } -TEST_CASE(flatten_sym_axis1) -{ - auto n = var("N", {1, 8}); - migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(4)}, dd{lit(6)}}}; - migraphx::shape output{migraphx::shape::float_type, {dd{n}, dd{lit(24)}}}; - expect_shape(output, migraphx::make_op("flatten", {{"axis", 1}}), input); -} - -TEST_CASE(flatten_sym_multi) -{ - auto n = var("N", {1, 8}); - auto m = var("M", {1, 8}); - auto k = var("K", {1, 8}); - migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{m}, dd{k}}}; - migraphx::shape output{migraphx::shape::float_type, {dd{n}, dd{m * k}}}; - expect_shape(output, migraphx::make_op("flatten", {{"axis", 1}}), input); -} - -TEST_CASE(flatten_sym_axis0) -{ - auto n = var("N", {1, 8}); - auto m = var("M", {1, 8}); - migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{m}}}; - migraphx::shape output{migraphx::shape::float_type, {dd{lit(1)}, dd{n * m}}}; - expect_shape(output, migraphx::make_op("flatten", {{"axis", 0}}), input); -} - -TEST_CASE(flatten_sym_negative_axis) -{ - auto n = var("N", {1, 8}); - migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(4)}, dd{lit(6)}}}; - migraphx::shape output{migraphx::shape::float_type, {dd{lit(4) * n}, dd{lit(6)}}}; - expect_shape(output, migraphx::make_op("flatten", {{"axis", -1}}), input); -} - TEST_CASE(fill_static_int) { migraphx::shape default_value{migraphx::shape::int64_type, {1}, {0}}; @@ -4368,194 +4332,6 @@ TEST_CASE(reshape_dyn_1in_multiple_non_fixed1) expect_shape(output, migraphx::make_op("reshape", {{"dims", new_shape}}), input); } -TEST_CASE(reshape_sym_zero_marker) -{ - auto n = var("N", {1, 8}); - migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(6)}}}; - migraphx::shape output{migraphx::shape::float_type, {dd{n}, dd{lit(2)}, dd{lit(3)}}}; - expect_shape(output, migraphx::make_op("reshape", {{"dims", {0, 2, 3}}}), input); -} - -TEST_CASE(reshape_sym_negative_1_int_missing) -{ - auto n = var("N", {1, 8}); - migraphx::shape input{migraphx::shape::float_type, {dd{lit(2)}, dd{n}, dd{lit(3)}}}; - migraphx::shape output{migraphx::shape::float_type, {dd{lit(2)}, dd{n}, dd{lit(3)}}}; - expect_shape(output, migraphx::make_op("reshape", {{"dims", {2, 0, -1}}}), input); -} - -TEST_CASE(reshape_sym_minus1_first) -{ - auto n = var("N", {1, 8}); - migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(6)}}}; - migraphx::shape output{migraphx::shape::float_type, {dd{lit(3) * n}, dd{lit(2)}}}; - expect_shape(output, migraphx::make_op("reshape", {{"dims", {-1, 2}}}), input); -} - -TEST_CASE(reshape_sym_minus1_distributes_over_sum) -{ - auto n = var("N", {1, 8}); - migraphx::shape input{migraphx::shape::float_type, {dd{n + lit(1)}, dd{lit(6)}}}; - migraphx::shape output{migraphx::shape::float_type, {dd{lit(3) * n + lit(3)}, dd{lit(2)}}}; - expect_shape(output, migraphx::make_op("reshape", {{"dims", {-1, 2}}}), input); -} - -TEST_CASE(reshape_sym_dims_smaller_than_input) -{ - auto n = var("N", {1, 8}); - auto m = var("M", {1, 8}); - migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(2)}, dd{m}}}; - migraphx::shape output{migraphx::shape::float_type, {dd{n}, dd{lit(2) * m}}}; - expect_shape(output, migraphx::make_op("reshape", {{"dims", {0, -1}}}), input); -} - -TEST_CASE(reshape_sym_broadcast_input_standard) -{ - auto n = var("N", {1, 8}); - migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(6)}}, {lit(0), lit(1)}}; - migraphx::shape output{migraphx::shape::float_type, {dd{n}, dd{lit(2)}, dd{lit(3)}}}; - expect_shape(output, migraphx::make_op("reshape", {{"dims", {0, 2, 3}}}), input); - EXPECT(output.standard()); -} - -TEST_CASE(reshape_sym_transposed_literal_unsqueeze) -{ - auto n = var("N", {1, 8}); - migraphx::shape input{migraphx::shape::float_type, {dd{lit(6)}, dd{n}}, {lit(1), lit(6)}}; - std::vector dims = {2, 3, dd{n}}; - migraphx::shape output{ - migraphx::shape::float_type, {dd{lit(2)}, dd{lit(3)}, dd{n}}, {lit(3), lit(1), lit(6)}}; - expect_shape(output, migraphx::make_op("reshape", {{"dims", migraphx::to_value(dims)}}), input); - EXPECT(not output.standard()); -} - -TEST_CASE(reshape_sym_transposed_symbolic_strides) -{ - auto n = var("N", {1, 8}); - migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(6)}}, {lit(1), n}}; - migraphx::shape output{ - migraphx::shape::float_type, {dd{n}, dd{lit(2)}, dd{lit(3)}}, {lit(1), lit(3) * n, n}}; - expect_shape(output, migraphx::make_op("reshape", {{"dims", {0, 2, 3}}}), input); - EXPECT(not output.standard()); -} - -TEST_CASE(reshape_sym_nonstandard_indeterminate_falls_back) -{ - auto n = var("N", {1, 8}); - migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(6)}}, {lit(1), n}}; - std::vector dims = {6, dd{n}}; - migraphx::shape output{migraphx::shape::float_type, {dd{lit(6)}, dd{n}}}; - expect_shape(output, migraphx::make_op("reshape", {{"dims", migraphx::to_value(dims)}}), input); - EXPECT(output.standard()); -} - -TEST_CASE(reshape_sym_nonpacked_unsqueeze) -{ - auto n = var("N", {1, 8}); - migraphx::shape input{ - migraphx::shape::float_type, {dd{n}, dd{lit(4)}, dd{lit(16)}}, {lit(128), lit(32), lit(2)}}; - std::vector dims = {0, 4, 2, 8}; - migraphx::shape output{migraphx::shape::float_type, - {dd{n}, dd{lit(4)}, dd{lit(2)}, dd{lit(8)}}}; - expect_shape(output, migraphx::make_op("reshape", {{"dims", migraphx::to_value(dims)}}), input); - EXPECT(output.standard()); -} - -TEST_CASE(reshape_sym_target_dim_negative_1) -{ - auto n = var("N", {1, 8}); - migraphx::shape input = {migraphx::shape::float_type, {6}}; - std::vector dims = {dd{n}, -1}; - migraphx::shape output{migraphx::shape::float_type, {dd{n}, dd{lit(6) / n}}}; - expect_shape(output, migraphx::make_op("reshape", {{"dims", migraphx::to_value(dims)}}), input); -} - -TEST_CASE(reshape_sym_minus1_non_exact_div) -{ - auto n = var("N", {1, 8}); - auto m = var("M", {1, 8}); - migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{m}, dd{lit(6)}}}; - migraphx::shape output{migraphx::shape::float_type, - {dd{(lit(2) * m * n) / lit(3)}, dd{lit(3)}, dd{lit(3)}}}; - expect_shape(output, migraphx::make_op("reshape", {{"dims", {-1, 3, 3}}}), input); -} - -TEST_CASE(reshape_sym_target_middle_axis) -{ - auto n = var("N", {1, 8}); - migraphx::shape input = {migraphx::shape::float_type, {24}}; - std::vector dims = {2, dd{n}, dd{lit(12) / n}}; - migraphx::shape output{migraphx::shape::float_type, {dd{lit(2)}, dd{n}, dd{lit(12) / n}}}; - expect_shape(output, migraphx::make_op("reshape", {{"dims", migraphx::to_value(dims)}}), input); -} - -TEST_CASE(reshape_sym_target_minus1_cancels_to_literal) -{ - auto n = var("N", {1, 8}); - migraphx::shape input = {migraphx::shape::float_type, {dd{lit(12)}, dd{n}}}; - std::vector dims = {-1, dd{lit(2) * n}}; - migraphx::shape output{migraphx::shape::float_type, {dd{lit(6)}, dd{lit(2) * n}}}; - expect_shape(output, migraphx::make_op("reshape", {{"dims", migraphx::to_value(dims)}}), input); -} - -TEST_CASE(reshape_sym_target_minus1_symbolic_missing) -{ - auto n = var("N", {1, 8}); - auto m = var("M", {1, 8}); - migraphx::shape input = {migraphx::shape::float_type, {dd{n}, dd{m}}}; - std::vector dims = {dd{lit(2) * n}, -1}; - migraphx::shape output{migraphx::shape::float_type, {dd{lit(2) * n}, dd{m / lit(2)}}}; - expect_shape(output, migraphx::make_op("reshape", {{"dims", migraphx::to_value(dims)}}), input); -} - -TEST_CASE(reshape_sym_target_collapse_axes) -{ - auto n = var("N", {1, 8}); - auto m = var("M", {1, 8}); - auto k = var("K", {1, 8}); - migraphx::shape input = {migraphx::shape::float_type, {dd{n}, dd{m}, dd{k}}}; - std::vector dims = {dd{n * m}, dd{k}}; - migraphx::shape output{migraphx::shape::float_type, {dd{n * m}, dd{k}}}; - expect_shape(output, migraphx::make_op("reshape", {{"dims", migraphx::to_value(dims)}}), input); -} - -TEST_CASE(reshape_sym_element_mismatch_throws) -{ - auto n = var("N", {1, 8}); - migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(6)}}}; - throws_shape(migraphx::make_op("reshape", {{"dims", {0, 2, 2}}}), input); -} - -TEST_CASE(reshape_sym_symbolic_dim_mismatch_throws) -{ - auto n = var("N", {1, 8}); - migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(6)}}}; - std::vector dims = {dd{n}, dd{lit(7)}}; - throws_shape(migraphx::make_op("reshape", {{"dims", migraphx::to_value(dims)}}), input); -} - -TEST_CASE(reshape_range_input_sym_dim_throws) -{ - auto n = var("N", {1, 8}); - migraphx::shape input{migraphx::shape::float_type, {{1, 8}, {6, 6}}}; - std::vector dims = {dd{n}, dd{lit(2)}, dd{lit(3)}}; - throws_shape(migraphx::make_op("reshape", {{"dims", migraphx::to_value(dims)}}), input); -} - -TEST_CASE(reshape_range_dim_like_throws) -{ - migraphx::shape input{migraphx::shape::float_type, {6}}; - std::vector dims = {dd{1, 4}, dd{1, 6}}; - throws_shape(migraphx::make_op("reshape", {{"dims", migraphx::to_value(dims)}}), input); -} - -TEST_CASE(reshape_sym_multiple_neg_throws) -{ - auto n = var("N", {1, 8}); - migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(6)}}}; - throws_shape(migraphx::make_op("reshape", {{"dims", {1, -1, -1}}}), input); -} - TEST_CASE(reshape_lazy_shape) { migraphx::shape input{migraphx::shape::float_type, {24, 1, 1, 1}}; @@ -4688,72 +4464,6 @@ TEST_CASE(reshape_lazy_nonpacked_squeeze2) throws_shape(migraphx::make_op("reshape_lazy", {{"dims", {64}}}), input); } -TEST_CASE(reshape_lazy_sym_nonpacked_squeeze) -{ - auto n = var("N", {2, 8}); - migraphx::shape input{migraphx::shape::float_type, {dd{lit(4)}, dd{n}}, {lit(2) * n, lit(2)}}; - std::vector dims = {dd{lit(4) * n}}; - migraphx::shape output{migraphx::shape::float_type, {dd{lit(4) * n}}, {lit(2)}}; - expect_shape( - output, migraphx::make_op("reshape_lazy", {{"dims", migraphx::to_value(dims)}}), input); -} - -TEST_CASE(reshape_lazy_sym_nonpacked_unsqueeze) -{ - auto n = var("N", {2, 8}); - migraphx::shape input{migraphx::shape::float_type, {dd{lit(4)}, dd{n}}, {lit(2) * n, lit(2)}}; - std::vector dims = {2, 2, dd{n}}; - migraphx::shape output{migraphx::shape::float_type, - {dd{lit(2)}, dd{lit(2)}, dd{n}}, - {lit(4) * n, lit(2) * n, lit(2)}}; - expect_shape( - output, migraphx::make_op("reshape_lazy", {{"dims", migraphx::to_value(dims)}}), input); -} - -TEST_CASE(reshape_lazy_sym_transposed_squeeze_throws) -{ - auto n = var("N", {2, 8}); - migraphx::shape input{migraphx::shape::float_type, {dd{lit(4)}, dd{n}}, {lit(1), lit(4)}}; - std::vector dims = {dd{lit(4) * n}}; - throws_shape(migraphx::make_op("reshape_lazy", {{"dims", migraphx::to_value(dims)}}), input); -} - -TEST_CASE(reshape_lazy_sym_broadcast_squeeze) -{ - auto n = var("N", {2, 8}); - migraphx::shape input{migraphx::shape::float_type, - {dd{lit(2)}, dd{n}, dd{lit(16)}, dd{lit(1280)}}, - {lit(0), lit(0), lit(0), lit(1)}}; - std::vector dims = {2, dd{lit(16) * n}, 1280}; - migraphx::shape output{migraphx::shape::float_type, - {dd{lit(2)}, dd{lit(16) * n}, dd{lit(1280)}}, - {lit(0), lit(0), lit(1)}}; - expect_shape( - output, migraphx::make_op("reshape_lazy", {{"dims", migraphx::to_value(dims)}}), input); -} - -TEST_CASE(reshape_lazy_sym_broadcast_unsqueeze) -{ - auto n = var("N", {2, 8}); - migraphx::shape input{migraphx::shape::float_type, - {dd{lit(2)}, dd{lit(16) * n}, dd{lit(1280)}}, - {lit(0), lit(0), lit(1)}}; - std::vector dims = {2, dd{n}, 16, 1280}; - migraphx::shape output{migraphx::shape::float_type, - {dd{lit(2)}, dd{n}, dd{lit(16)}, dd{lit(1280)}}, - {lit(0), lit(0), lit(0), lit(1)}}; - expect_shape( - output, migraphx::make_op("reshape_lazy", {{"dims", migraphx::to_value(dims)}}), input); -} - -TEST_CASE(reshape_lazy_sym_element_mismatch_throws) -{ - auto n = var("N", {2, 8}); - migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(6)}}}; - std::vector dims = {dd{n}, 2, 2}; - throws_shape(migraphx::make_op("reshape_lazy", {{"dims", migraphx::to_value(dims)}}), input); -} - TEST_CASE(reshape_lazy_broadcast_unsqueeze1) { migraphx::shape input{migraphx::shape::float_type, {2, 256, 1280}, {0, 0, 1}}; @@ -5641,61 +5351,6 @@ TEST_CASE(slice_dyn_nonfixed_keeps_other_optimals) input); } -TEST_CASE(eval_expr_from_shape_shape) -{ - auto n = var("n", {1, 16}); - auto h = var("h", {1, 32}); - auto w = var("w", {1, 32}); - migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(3)}, dd{h}, dd{w}}}; - expect_shape(migraphx::shape{migraphx::shape::int64_type, {3}}, - migraphx::make_op("eval_expr_from_shape", - {{"expressions", - migraphx::value::array{migraphx::to_value(n), - migraphx::to_value(h / lit(2)), - migraphx::to_value(w / lit(2))}}}), - input); -} - -TEST_CASE(eval_expr_from_shape_missing_symbol) -{ - auto m = var("m", {1, 16}); - auto n = var("n", {1, 16}); - migraphx::shape input{migraphx::shape::float_type, {dd{n}, dd{lit(3)}}}; - throws_shape( - migraphx::make_op("eval_expr_from_shape", - {{"expressions", migraphx::value::array{migraphx::to_value(m)}}}), - input); -} - -TEST_CASE(eval_expr_from_shape_multi_input) -{ - auto m = var("m", {1, 16}); - auto n = var("n", {1, 16}); - migraphx::shape a{migraphx::shape::float_type, {dd{m}, dd{lit(3)}}}; - migraphx::shape b{migraphx::shape::float_type, {dd{lit(2)}, dd{n}}}; - expect_shape(migraphx::shape{migraphx::shape::int64_type, {2}}, - migraphx::make_op( - "eval_expr_from_shape", - {{"expressions", - migraphx::value::array{migraphx::to_value(m + n), migraphx::to_value(m)}}}), - a, - b); -} - -TEST_CASE(eval_expr_from_shape_missing_symbol_multi_input) -{ - auto m = var("m", {1, 16}); - auto n = var("n", {1, 16}); - auto k = var("k", {1, 16}); - migraphx::shape a{migraphx::shape::float_type, {dd{m}, dd{lit(3)}}}; - migraphx::shape b{migraphx::shape::float_type, {dd{lit(2)}, dd{n}}}; - throws_shape( - migraphx::make_op("eval_expr_from_shape", - {{"expressions", migraphx::value::array{migraphx::to_value(m + k)}}}), - a, - b); -} - TEST_CASE(slice_sym) { auto n = var("n", {1, 8}); @@ -6811,41 +6466,6 @@ TEST_CASE(test_squeeze_wrong_axis) throws_shape(migraphx::make_op("squeeze", {{"axes", {0}}}), s1); } -TEST_CASE(test_squeeze_sym) -{ - auto n = var("N", {1, 8}); - auto m = var("M", {1, 8}); - migraphx::shape s1{migraphx::shape::float_type, {dd{n}, dd{lit(1)}, dd{m}}}; - migraphx::shape s2{migraphx::shape::float_type, {dd{n}, dd{m}}}; - expect_shape(s2, migraphx::make_op("squeeze", {{"axes", {1}}}), s1); -} - -TEST_CASE(test_squeeze_sym_transpose) -{ - auto n = var("N", {1, 8}); - migraphx::shape s1{ - migraphx::shape::float_type, {dd{n}, dd{lit(4)}, dd{lit(1)}}, {lit(4), lit(1), lit(4)}}; - migraphx::shape s2{migraphx::shape::float_type, {dd{n}, dd{lit(4)}}, {lit(4), lit(1)}}; - expect_shape(s2, migraphx::make_op("squeeze", {{"axes", {2}}}), s1); -} - -TEST_CASE(test_squeeze_sym_empty_axes) -{ - auto n = var("N", {1, 8}); - auto m = var("M", {1, 8}); - migraphx::shape s1{migraphx::shape::float_type, {dd{n}, dd{lit(1)}, dd{m}, dd{lit(1)}}}; - migraphx::shape s2{migraphx::shape::float_type, {dd{n}, dd{m}}}; - expect_shape(s2, migraphx::make_op("squeeze"), s1); -} - -TEST_CASE(test_squeeze_sym_symbolic_axis_throws) -{ - auto n = var("N", {1, 8}); - auto m = var("M", {1, 8}); - migraphx::shape s1{migraphx::shape::float_type, {dd{n}, dd{lit(1)}, dd{m}}}; - throws_shape(migraphx::make_op("squeeze", {{"axes", {0}}}), s1); -} - TEST_CASE(test_unique_axis_invalid) { migraphx::shape x_shape{migraphx::shape::float_type, {10, 4, 3}}; @@ -7066,49 +6686,6 @@ TEST_CASE(test_unsqueeze_multiple_axes_step) expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {2, 4, 5}}, {"steps", {2}}}), s1); } -TEST_CASE(test_unsqueeze_sym) -{ - auto n = var("N", {1, 8}); - migraphx::shape s1{migraphx::shape::float_type, {dd{n}, dd{lit(5)}, dd{lit(3)}}}; - migraphx::shape s2{migraphx::shape::float_type, {dd{n}, dd{lit(5)}, dd{lit(1)}, dd{lit(3)}}}; - expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {2}}}), s1); -} - -TEST_CASE(test_unsqueeze_sym_symbolic_stride) -{ - auto n = var("N", {1, 8}); - migraphx::shape s1{migraphx::shape::float_type, {dd{lit(6)}, dd{n}}, {lit(1), lit(6)}}; - migraphx::shape s2{ - migraphx::shape::float_type, {dd{lit(1)}, dd{lit(6)}, dd{n}}, {lit(6), lit(1), lit(6)}}; - expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {0}}}), s1); - EXPECT(not s2.standard()); -} - -TEST_CASE(test_unsqueeze_sym_step) -{ - auto n = var("N", {1, 8}); - migraphx::shape s1{migraphx::shape::float_type, {dd{n}, dd{lit(5)}, dd{lit(12)}}}; - migraphx::shape s2{migraphx::shape::float_type, {dd{n}, dd{lit(5)}, dd{lit(2)}, dd{lit(6)}}}; - expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {2}}, {"steps", {2}}}), s1); -} - -TEST_CASE(test_unsqueeze_sym_step_symbolic_divisor) -{ - auto n = var("N", {1, 8}); - auto m = var("M", {1, 8}); - migraphx::shape s1{migraphx::shape::float_type, {dd{n}, dd{m}}}; - migraphx::shape s2{ - migraphx::shape::float_type, {dd{n}, dd{lit(2)}, dd{m / lit(2)}}, {m, m / lit(2), lit(1)}}; - expect_shape(s2, migraphx::make_op("unsqueeze", {{"axes", {1}}, {"steps", {2}}}), s1); -} - -TEST_CASE(test_unsqueeze_sym_step_non_divisible_throws) -{ - auto n = var("N", {1, 8}); - migraphx::shape s1{migraphx::shape::float_type, {dd{n}, dd{lit(5)}, dd{lit(3)}}}; - throws_shape(migraphx::make_op("unsqueeze", {{"axes", {2}}, {"steps", {2}}}), s1); -} - TEST_CASE(transpose_shape) { migraphx::shape input{migraphx::shape::float_type, {2, 2}}; diff --git a/test/ref/eval_expr_from_shape.cpp b/test/ref/eval_expr_from_shape.cpp deleted file mode 100644 index d4c48660851..00000000000 --- a/test/ref/eval_expr_from_shape.cpp +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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 - -TEST_CASE(eval_expr_from_shape_input) -{ - using dd = migraphx::shape::dynamic_dimension; - auto n = migraphx::sym::var("N", {1, 16}); - auto h = migraphx::sym::var("H", {1, 32}); - auto w = migraphx::sym::var("W", {1, 32}); - - migraphx::program p; - auto* mm = p.get_main_module(); - auto x = mm->add_parameter("x", - migraphx::shape{migraphx::shape::float_type, - {dd{n}, dd{migraphx::sym::lit(3)}, dd{h}, dd{w}}}); - mm->add_instruction(migraphx::make_op("eval_expr_from_shape", - {{"expressions", - migraphx::value::array{ - migraphx::to_value(n), - migraphx::to_value(h / migraphx::sym::lit(2)), - migraphx::to_value(w / migraphx::sym::lit(2))}}}), - x); - p.compile(migraphx::make_target("ref")); - - migraphx::shape input_shape{migraphx::shape::float_type, {7, 3, 10, 12}}; - std::vector data(input_shape.elements()); - auto result = p.eval({{"x", migraphx::argument{input_shape, data.data()}}}).back(); - - std::vector values; - result.visit([&](auto output) { values.assign(output.begin(), output.end()); }); - EXPECT(result.get_shape() == migraphx::shape{migraphx::shape::int64_type, {3}}); - EXPECT(values == std::vector{7, 5, 6}); -} - -TEST_CASE(eval_expr_from_shape_multi_symbol) -{ - using dd = migraphx::shape::dynamic_dimension; - auto m = migraphx::sym::var("M", {1, 16}); - auto n = migraphx::sym::var("N", {1, 16}); - - migraphx::program p; - auto* mm = p.get_main_module(); - auto x = mm->add_parameter("x", migraphx::shape{migraphx::shape::float_type, {dd{m}, dd{n}}}); - mm->add_instruction( - migraphx::make_op("eval_expr_from_shape", - {{"expressions", migraphx::value::array{migraphx::to_value(m + n)}}}), - x); - p.compile(migraphx::make_target("ref")); - - migraphx::shape input_shape{migraphx::shape::float_type, {3, 4}}; - std::vector data(input_shape.elements()); - auto result = p.eval({{"x", migraphx::argument{input_shape, data.data()}}}).back(); - - EXPECT(result.get_shape() == migraphx::shape{migraphx::shape::int64_type, {1}}); - EXPECT(result.at() == 7); -} - -TEST_CASE(eval_expr_from_shape_cross_input) -{ - using dd = migraphx::shape::dynamic_dimension; - auto m = migraphx::sym::var("M", {1, 16}); - auto n = migraphx::sym::var("N", {1, 16}); - - migraphx::program p; - auto* mm = p.get_main_module(); - auto a = mm->add_parameter( - "a", migraphx::shape{migraphx::shape::float_type, {dd{m}, dd{migraphx::sym::lit(3)}}}); - auto b = mm->add_parameter( - "b", migraphx::shape{migraphx::shape::float_type, {dd{migraphx::sym::lit(2)}, dd{n}}}); - mm->add_instruction(migraphx::make_op("eval_expr_from_shape", - {{"expressions", - migraphx::value::array{migraphx::to_value(m + n), - migraphx::to_value(m), - migraphx::to_value(n)}}}), - a, - b); - p.compile(migraphx::make_target("ref")); - - migraphx::shape a_shape{migraphx::shape::float_type, {5, 3}}; - migraphx::shape b_shape{migraphx::shape::float_type, {2, 7}}; - std::vector a_data(a_shape.elements()); - std::vector b_data(b_shape.elements()); - auto result = p.eval({{"a", migraphx::argument{a_shape, a_data.data()}}, - {"b", migraphx::argument{b_shape, b_data.data()}}}) - .back(); - - std::vector values; - result.visit([&](auto output) { values.assign(output.begin(), output.end()); }); - EXPECT(result.get_shape() == migraphx::shape{migraphx::shape::int64_type, {3}}); - EXPECT(values == std::vector{12, 5, 7}); -} diff --git a/test/ref/nonzero.cpp b/test/ref/nonzero.cpp index 20cc0d94890..b6a534e0ea1 100644 --- a/test/ref/nonzero.cpp +++ b/test/ref/nonzero.cpp @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2015-2026 Advanced Micro Devices, Inc. All rights reserved. + * Copyright (c) 2015-2023 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 @@ -48,40 +48,3 @@ TEST_CASE(nonzero_test) 1, 1, 0, 0, 0, 0, 0, 1, 0, 2, 0, 2, 0, 2, 0, 0, 0, 0}; EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } - -TEST_CASE(nonzero_transposed_input) -{ - migraphx::program p; - auto* mm = p.get_main_module(); - migraphx::shape s{migraphx::shape::float_type, {2, 3}}; - std::vector data = {1.0f, 0.0f, 2.0f, 0.0f, 3.0f, 4.0f}; - auto input = mm->add_literal(migraphx::literal(s, data)); - auto transposed = - mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), input); - auto ret = mm->add_instruction(migraphx::make_op("nonzero"), transposed); - mm->add_return({ret}); - p.compile(migraphx::make_target("ref")); - auto result = p.eval({}).back(); - std::vector result_vector; - result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); - // np.nonzero(data.reshape(2, 3).T), padded to nonzero output shape {2, 6}. - std::vector gold = {0, 1, 2, 2, 0, 0, 0, 1, 0, 1, 0, 0}; - EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); -} - -TEST_CASE(nonzero_broadcasted_input) -{ - migraphx::program p; - auto* mm = p.get_main_module(); - auto input = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {1.0f}}); - auto broadcasted = - mm->add_instruction(migraphx::make_op("multibroadcast", {{"out_lens", {2, 3}}}), input); - auto ret = mm->add_instruction(migraphx::make_op("nonzero"), broadcasted); - mm->add_return({ret}); - p.compile(migraphx::make_target("ref")); - auto result = p.eval({}).back(); - std::vector result_vector; - result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); - std::vector gold = {0, 0, 0, 1, 1, 1, 0, 1, 2, 0, 1, 2}; - EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); -} diff --git a/test/ref/slice.cpp b/test/ref/slice.cpp index bd58ce3640d..0550434f870 100644 --- a/test/ref/slice.cpp +++ b/test/ref/slice.cpp @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2015-2026 Advanced Micro Devices, Inc. All rights reserved. + * Copyright (c) 2015-2023 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 @@ -26,8 +26,6 @@ #include #include #include -#include -#include #include #include @@ -443,36 +441,3 @@ TEST_CASE(slice_dyn_test1) EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); EXPECT(result.get_shape() == sresult); } - -TEST_CASE(slice_eval_expr_from_shape_input) -{ - using dd = migraphx::shape::dynamic_dimension; - auto n = migraphx::sym::var("n", {1, 3}); - - migraphx::program p; - auto* mm = p.get_main_module(); - migraphx::shape s{migraphx::shape::int32_type, - {dd{migraphx::sym::lit(2)}, dd{migraphx::sym::lit(2)}, dd{n}}}; - auto x = mm->add_parameter("x", s); - - auto end_vals = mm->add_instruction( - migraphx::make_op( - "eval_expr_from_shape", - {{"expressions", - migraphx::value::array{migraphx::to_value(n - migraphx::sym::lit(1))}}}), - x); - mm->add_instruction(migraphx::make_op("slice", {{"axes", {2}}, {"starts", {0}}}), x, end_vals); - p.compile(migraphx::make_target("ref")); - - std::vector data(2 * 2 * 3); - std::iota(data.begin(), data.end(), 0); - migraphx::shape input_shape{migraphx::shape::int32_type, {2, 2, 3}}; - auto result = p.eval({{"x", migraphx::argument{input_shape, data.data()}}}).back(); - - std::vector gold = {0, 1, 3, 4, 6, 7, 9, 10}; - std::vector results_vector; - result.visit([&](auto output) { results_vector.assign(output.begin(), output.end()); }); - EXPECT(migraphx::verify::verify_rms_range(results_vector, gold)); - EXPECT(result.get_shape() == - migraphx::shape{migraphx::shape::int32_type, {2, 2, 2}, {6, 3, 1}}); -} diff --git a/test/reshape_dims_test.cpp b/test/reshape_dims_test.cpp deleted file mode 100644 index ef5d8a5c550..00000000000 --- a/test/reshape_dims_test.cpp +++ /dev/null @@ -1,395 +0,0 @@ -/* - * 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 - -using dd = migraphx::shape::dynamic_dimension; -using se = migraphx::sym::expr; -using migraphx::sym::lit; -using migraphx::sym::var; - -static const auto ftype = migraphx::shape::float_type; - -// reshape_dims always answers in the symbolic domain, so evaluate the result back to a concrete -// shape for comparison. nullopt means the layout could not be proven. -static migraphx::optional -static_reshape(const migraphx::shape& input, const std::vector& rdims, bool lazy) -{ - auto r = migraphx::reshape_dims(input, rdims, {.lazy = lazy}); - if(not r.has_value()) - return migraphx::nullopt; - return r->to_static(); -} - -static migraphx::optional -sym_reshape(const migraphx::shape& input, - const std::vector& rdims, - bool lazy, - const std::unordered_map& sym_map) -{ - auto r = migraphx::reshape_dims(input, rdims, {.lazy = lazy}); - if(not r.has_value()) - return migraphx::nullopt; - return r->to_static(sym_map); -} - -//////////////////////////////////////////////////////////////////////////////// -// reshape_dims: static inputs -//////////////////////////////////////////////////////////////////////////////// - -TEST_CASE(standard_merge) -{ - migraphx::shape s{ftype, {2, 3, 4}}; - migraphx::shape expected{ftype, {2, 12}}; - EXPECT(static_reshape(s, {2, 12}, true) == expected); - EXPECT(static_reshape(s, {2, 12}, false) == expected); -} - -TEST_CASE(standard_split) -{ - migraphx::shape s{ftype, {2, 12}}; - migraphx::shape expected{ftype, {2, 3, 4}}; - EXPECT(static_reshape(s, {2, 3, 4}, true) == expected); - EXPECT(static_reshape(s, {2, 3, 4}, false) == expected); -} - -TEST_CASE(standard_identity) -{ - migraphx::shape s{ftype, {2, 3, 4}}; - EXPECT(static_reshape(s, {2, 3, 4}, true) == s); -} - -TEST_CASE(standard_flatten) -{ - migraphx::shape s{ftype, {2, 3, 4}}; - migraphx::shape expected{ftype, {24}}; - EXPECT(static_reshape(s, {24}, true) == expected); -} - -// Merging axes that are not adjacent in memory cannot be expressed as a view, so lazy reshape -// declines while a copy-permitting reshape repacks to a standard layout. -TEST_CASE(transposed_unmergeable) -{ - migraphx::shape s{ftype, {2, 3, 4}, {12, 1, 3}}; - migraphx::shape expected{ftype, {2, 12}}; - EXPECT(static_reshape(s, {2, 12}, true) == migraphx::nullopt); - EXPECT(static_reshape(s, {2, 12}, false) == expected); -} - -// The trailing axes of this permutation are adjacent, so the merge holds as a view and the -// permutation carries through to the result. -TEST_CASE(transposed_mergeable) -{ - migraphx::shape s{ftype, {2, 3, 4}, {1, 8, 2}}; - migraphx::shape expected{ftype, {2, 12}, {1, 2}}; - EXPECT(static_reshape(s, {2, 12}, true) == expected); - EXPECT(static_reshape(s, {2, 12}, false) == expected); -} - -// Splitting an axis is the inverse of merging it and recovers the original strides. -TEST_CASE(transposed_split) -{ - migraphx::shape s{ftype, {2, 12}, {1, 2}}; - migraphx::shape expected{ftype, {2, 3, 4}, {1, 8, 2}}; - EXPECT(static_reshape(s, {2, 3, 4}, true) == expected); -} - -// A broadcasted axis keeps its zero stride through a lazy merge of the packed trailing axes. -// Without a view requirement the ambiguous permutation falls back to a standard layout instead. -TEST_CASE(broadcasted) -{ - migraphx::shape s{ftype, {2, 3, 4}, {0, 4, 1}}; - migraphx::shape lazy_expected{ftype, {2, 12}, {0, 1}}; - migraphx::shape copy_expected{ftype, {2, 12}}; - EXPECT(static_reshape(s, {2, 12}, true) == lazy_expected); - EXPECT(static_reshape(s, {2, 12}, false) == copy_expected); -} - -TEST_CASE(broadcasted_scalar) -{ - migraphx::shape s{ftype, {2, 3}, {0, 0}}; - migraphx::shape expected{ftype, {6}, {0}}; - EXPECT(static_reshape(s, {6}, true) == expected); -} - -// A broadcast axis cannot merge into a non-broadcast one, since the result would need two -// different strides for one axis. -TEST_CASE(broadcasted_unmergeable) -{ - migraphx::shape s{ftype, {2, 3}, {0, 1}}; - EXPECT(static_reshape(s, {6}, true) == migraphx::nullopt); -} - -// A sliced shape has gaps between its axes, so merging them loses the gap and needs a copy. -TEST_CASE(nonpacked) -{ - migraphx::shape s{ftype, {2, 2}, {4, 1}}; - migraphx::shape expected{ftype, {4}}; - EXPECT(static_reshape(s, {4}, true) == migraphx::nullopt); - EXPECT(static_reshape(s, {4}, false) == expected); -} - -// Axes of length 1 past the end of the walk inherit the last stride. -TEST_CASE(trailing_ones) -{ - migraphx::shape s{ftype, {2, 3, 4}, {1, 8, 2}}; - migraphx::shape expected{ftype, {2, 12, 1, 1}, {1, 2, 2, 2}}; - EXPECT(static_reshape(s, {2, 12, 1, 1}, true) == expected); -} - -// A trailing axis that is not 1 would change the element count. -TEST_CASE(trailing_non_one) -{ - migraphx::shape s{ftype, {2, 3, 4}, {1, 8, 2}}; - EXPECT(static_reshape(s, {2, 12, 2}, true) == migraphx::nullopt); -} - -// No run of input axes multiplies to 5, so the walk cannot line the two shapes up. -TEST_CASE(mismatched_elements) -{ - migraphx::shape s{ftype, {2, 3, 4}, {12, 1, 3}}; - EXPECT(static_reshape(s, {5, 5}, true) == migraphx::nullopt); - EXPECT(static_reshape(s, {5, 5}, false) == migraphx::nullopt); -} - -// Range-based dynamic dimensions have no stride expressions to reason about, so the layout is -// unprovable rather than an error. -TEST_CASE(range_dynamic) -{ - migraphx::shape s{ftype, {{1, 4}, {3, 3}, {4, 4}}}; - EXPECT(static_reshape(s, {2, 12}, true) == migraphx::nullopt); - EXPECT(static_reshape(s, {2, 12}, false) == migraphx::nullopt); -} - -// A static input and its symbolic lift must resolve through the same path. Static shapes carry no -// dyn_dims()/dyn_strides(), so they have to be lifted internally rather than read as if they were -// already symbolic. -TEST_CASE(static_matches_symbolic_lift) -{ - const std::vector inputs = {{ftype, {2, 3, 4}}, - {ftype, {2, 3, 4}, {12, 1, 3}}, - {ftype, {2, 3, 4}, {1, 8, 2}}, - {ftype, {2, 3, 4}, {0, 4, 1}}, - {ftype, {2, 3, 4}, {24, 8, 2}}}; - const std::vector> targets = { - {2, 12}, {24}, {2, 3, 4}, {6, 4}, {2, 2, 6}}; - for(const auto& s : inputs) - { - for(const auto& target : targets) - { - for(bool lazy : {true, false}) - { - EXPECT(static_reshape(s, target, lazy) == - static_reshape(s.to_symbolic(), target, lazy)); - } - } - } -} - -//////////////////////////////////////////////////////////////////////////////// -// reshape_dims: symbolic inputs -//////////////////////////////////////////////////////////////////////////////// - -TEST_CASE(symbolic_standard) -{ - auto n = var("n", {1, 8}); - std::unordered_map sym_map = {{n, 2}}; - migraphx::shape s{ftype, {dd{n}, dd{lit(3)}, dd{lit(4)}}}; - migraphx::shape expected{ftype, {2, 12}}; - EXPECT(sym_reshape(s, {n, lit(12)}, true, sym_map) == expected); -} - -// A symbolic stride merges the same way a literal one does when the ratio is provable. -TEST_CASE(symbolic_transposed_mergeable) -{ - auto n = var("n", {1, 8}); - std::unordered_map sym_map = {{n, 2}}; - migraphx::shape s{ftype, {dd{n}, dd{lit(3)}, dd{lit(4)}}, {lit(1), n * 4, n}}; - migraphx::shape expected{ftype, {2, 12}, {1, 2}}; - EXPECT(sym_reshape(s, {n, lit(12)}, true, sym_map) == expected); -} - -TEST_CASE(symbolic_broadcasted) -{ - auto n = var("n", {1, 8}); - std::unordered_map sym_map = {{n, 2}}; - migraphx::shape s{ftype, {dd{n}, dd{lit(3)}, dd{lit(4)}}, {lit(0), lit(4), lit(1)}}; - migraphx::shape lazy_expected{ftype, {2, 12}, {0, 1}}; - migraphx::shape copy_expected{ftype, {2, 12}}; - EXPECT(sym_reshape(s, {n, lit(12)}, true, sym_map) == lazy_expected); - EXPECT(sym_reshape(s, {n, lit(12)}, false, sym_map) == copy_expected); -} - -// n ranges over [1, 8], so neither n < 8 nor 8 < n holds for every value and the walk cannot pick -// between squeezing and unsqueezing. -TEST_CASE(symbolic_unprovable_ordering) -{ - auto n = var("n", {1, 8}); - migraphx::shape s{ftype, {dd{n}, dd{lit(4)}}, {lit(1), n}}; - EXPECT(migraphx::reshape_dims(s, {lit(8), lit(4)}, {.lazy = true}) == migraphx::nullopt); -} - -// Merging a literal axis with a symbolic one yields a symbolic output dim. n is bounded below by -// 2 so that 4 < 4n is provable; at n == 1 the ordering would be indeterminate. -TEST_CASE(symbolic_merge_into_symbol) -{ - auto n = var("n", {2, 8}); - std::unordered_map sym_map = {{n, 2}}; - migraphx::shape s{ftype, {dd{lit(3)}, dd{lit(4)}, dd{n}}, {lit(1), n * 3, lit(3)}}; - migraphx::shape expected{ftype, {3, 8}, {1, 3}}; - EXPECT(sym_reshape(s, {lit(3), n * 4}, true, sym_map) == expected); -} - -// Column-major strides make the outer axis the denser one, so merging the two axes is a -// transposed merge that no view can express. A copy repacks to a standard layout. -TEST_CASE(symbolic_transposed_unmergeable) -{ - auto n = var("n", {1, 8}); - std::unordered_map sym_map = {{n, 3}}; - migraphx::shape s{ftype, {dd{n}, dd{lit(4)}}, {lit(1), n}}; - migraphx::shape expected{ftype, {12}}; - EXPECT(sym_reshape(s, {n * 4}, true, sym_map) == migraphx::nullopt); - EXPECT(sym_reshape(s, {n * 4}, false, sym_map) == expected); -} - -// Resolving the symbols first and reshaping the concrete shape must agree with reshaping -// symbolically and resolving afterwards. -TEST_CASE(symbolic_matches_static_eval) -{ - auto n = var("n", {1, 8}); - std::unordered_map sym_map = {{n, 2}}; - const std::vector inputs = { - {ftype, {dd{n}, dd{lit(3)}, dd{lit(4)}}}, - {ftype, {dd{n}, dd{lit(3)}, dd{lit(4)}}, {lit(1), n * 4, n}}, - {ftype, {dd{n}, dd{lit(3)}, dd{lit(4)}}, {lit(0), lit(4), lit(1)}}, - {ftype, {dd{n}, dd{lit(3)}, dd{lit(4)}}, {lit(12), lit(1), lit(3)}}}; - for(const auto& s : inputs) - { - for(bool lazy : {true, false}) - { - auto from_sym = sym_reshape(s, {n, lit(12)}, lazy, sym_map); - auto from_static = static_reshape(s.to_static(sym_map), {2, 12}, lazy); - EXPECT(from_sym == from_static); - } - } -} - -//////////////////////////////////////////////////////////////////////////////// -// resolve_reshape_dims -//////////////////////////////////////////////////////////////////////////////// - -TEST_CASE(resolve_literals) -{ - migraphx::shape s{ftype, {dd{lit(2)}, dd{lit(3)}, dd{lit(4)}}}; - std::vector
expected = {dd{lit(2)}, dd{lit(12)}}; - EXPECT(migraphx::resolve_reshape_dims(s, {2, 12}) == expected); -} - -// A 0 entry copies the input dim at that index. -TEST_CASE(resolve_zero_copies_input_dim) -{ - migraphx::shape s{ftype, {dd{lit(2)}, dd{lit(3)}, dd{lit(4)}}}; - std::vector
expected = {dd{lit(2)}, dd{lit(12)}}; - EXPECT(migraphx::resolve_reshape_dims(s, {0, 12}) == expected); -} - -TEST_CASE(resolve_zero_copies_symbol) -{ - auto n = var("n", {1, 8}); - migraphx::shape s{ftype, {dd{n}, dd{lit(12)}}}; - std::vector
expected = {dd{n}, dd{lit(12)}}; - EXPECT(migraphx::resolve_reshape_dims(s, {0, 12}) == expected); -} - -// A -1 entry is the leftover element count after the explicit dims. -TEST_CASE(resolve_infers_negative_one) -{ - migraphx::shape s{ftype, {dd{lit(2)}, dd{lit(3)}, dd{lit(4)}}}; - std::vector
expected = {dd{lit(2)}, dd{lit(12)}}; - EXPECT(migraphx::resolve_reshape_dims(s, {2, -1}) == expected); - EXPECT(migraphx::resolve_reshape_dims(s, {-1, 12}) == expected); -} - -TEST_CASE(resolve_infers_negative_one_over_symbol) -{ - auto n = var("n", {1, 8}); - migraphx::shape s{ftype, {dd{n}, dd{lit(3)}, dd{lit(4)}}}; - auto result = migraphx::resolve_reshape_dims(s, {-1, 12}); - EXPECT(result.size() == 2); - EXPECT(result[0] == dd{n}); - EXPECT(result[1] == dd{lit(12)}); -} - -// A symbolic dim entry is taken as-is. -TEST_CASE(resolve_symbolic_entry) -{ - auto n = var("n", {1, 8}); - migraphx::shape s{ftype, {dd{n}, dd{lit(3)}, dd{lit(4)}}}; - std::vector
expected = {dd{n}, dd{lit(12)}}; - EXPECT(migraphx::resolve_reshape_dims(s, {migraphx::dim_like{dd{n}}, 12}) == expected); -} - -TEST_CASE(resolve_rank_change) -{ - migraphx::shape s{ftype, {dd{lit(24)}}}; - std::vector
expected = {dd{lit(2)}, dd{lit(3)}, dd{lit(4)}}; - EXPECT(migraphx::resolve_reshape_dims(s, {2, 3, -1}) == expected); -} - -//////////////////////////////////////////////////////////////////////////////// -// validate_reshape_dims -//////////////////////////////////////////////////////////////////////////////// - -TEST_CASE(validate_accepts_literals) -{ - migraphx::validate_reshape_dims("reshape", {1, 2, 3}); - migraphx::validate_reshape_dims("reshape", {0, 2, -1}); - migraphx::validate_reshape_dims("reshape", {}); -} - -TEST_CASE(validate_accepts_symbolic) -{ - auto n = var("n", {1, 8}); - migraphx::validate_reshape_dims("reshape", {migraphx::dim_like{dd{n}}, 12}); -} - -TEST_CASE(validate_rejects_range_dim) -{ - EXPECT(test::throws( - [&] { migraphx::validate_reshape_dims("reshape", {migraphx::dim_like{dd{1, 4}}, 12}); }, - "dim entries must be int64 or symbolic")); -} - -TEST_CASE(validate_rejects_multiple_inferred_dims) -{ - EXPECT(test::throws( - [&] { migraphx::validate_reshape_dims("reshape", {-1, 2, -1}); }, - "can only have one -1 dim")); -} - -int main(int argc, const char* argv[]) { test::run(argc, argv); } diff --git a/test/sym.cpp b/test/sym.cpp index 9e4dc1d2fd0..e435b093ab8 100644 --- a/test/sym.cpp +++ b/test/sym.cpp @@ -831,39 +831,6 @@ TEST_CASE(expr_variable_constraint_equality) EXPECT(migraphx::sym::same_symbol(var("x"), var("x", c))); } -TEST_CASE(find_variables_collects_distinct) -{ - auto x = var("x"); - auto y = var("y"); - auto pair = call("find_variables_collects_distinct", [](auto a, auto b) { return a + b; }); - auto vars = migraphx::sym::find_variables(pair(x, pair(y, x))); - EXPECT(vars == std::vector{x, y}); -} - -TEST_CASE(find_variables_constant_is_empty) -{ - EXPECT(migraphx::sym::find_variables(lit(5)).empty()); - EXPECT(migraphx::sym::find_variables(lit(2) + lit(3)).empty()); - EXPECT(migraphx::sym::find_variables(expr{}).empty()); -} - -TEST_CASE(find_variables_strips_metadata) -{ - auto c = interval{int64_t{1}, int64_t{16}}; - auto pair = call("find_variables_strips_metadata", [](auto a, auto b) { return a + b; }); - auto vars = migraphx::sym::find_variables(pair(var("x", c), var("x"))); - EXPECT(vars == std::vector{var("x")}); -} - -TEST_CASE(find_variables_shared_subexpression) -{ - auto pair = call("find_variables_shared_subexpression", [](auto a, auto b) { return a + b; }); - auto e = pair(var("x"), lit(1)); - for(int i = 0; i < 20; ++i) - e = pair(e, e); - EXPECT(migraphx::sym::find_variables(e) == std::vector{var("x")}); -} - TEST_CASE(expr_equal_compound) { auto x = var("x"); diff --git a/test/test_pytest_bridge.py b/test/test_pytest_bridge.py deleted file mode 100644 index 0c4738528a9..00000000000 --- a/test/test_pytest_bridge.py +++ /dev/null @@ -1,91 +0,0 @@ -##################################################################################### -# 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. -##################################################################################### -"""Pytest bridge for the MIGraphX test suite - -NOTE: the file is named ``test_*`` on purpose so pytest's default discovery can find it -""" -import os -import shutil -import subprocess - -import pytest - -_HERE = os.path.dirname(os.path.abspath(__file__)) - - -def _resolve_test_dir(): - env_dir = os.environ.get("MIGRAPHX_TEST_DIR") - if env_dir: - return env_dir - for candidate in (_HERE, os.getcwd()): - if os.path.exists(os.path.join(candidate, "CTestTestfile.cmake")): - return candidate - return _HERE - - -def _migraphx_lib_dir(): - try: - import migraphx - except ImportError: - return None - return os.path.dirname(os.path.abspath(migraphx.__file__)) - - -def _ctest_env(test_dir): - env = dict(os.environ) - lib_dirs = [d for d in (os.path.join(test_dir, "lib"), _migraphx_lib_dir()) - if d and os.path.isdir(d)] - if lib_dirs: - existing = env.get("LD_LIBRARY_PATH", "") - env["LD_LIBRARY_PATH"] = os.pathsep.join( - lib_dirs + ([existing] if existing else [])) - return env - - -def _ensure_executable(test_dir): - bin_dir = os.path.join(test_dir, "bin") - if not os.path.isdir(bin_dir): - return - for name in os.listdir(bin_dir): - try: - os.chmod(os.path.join(bin_dir, name), 0o755) - except OSError: - pass - - -@pytest.mark.skipif(shutil.which("ctest") is None, - reason="ctest not found; install CMake to run the suite") -def test_migraphx(): - test_dir = _resolve_test_dir() - if not os.path.exists(os.path.join(test_dir, "CTestTestfile.cmake")): - pytest.skip( - f"No CTestTestfile.cmake in {test_dir}; set MIGRAPHX_TEST_DIR to a " - "build or installed-tests directory.") - _ensure_executable(test_dir) - result = subprocess.run( - ["ctest", "--test-dir", test_dir, "-j", str(os.cpu_count() or 1), - "--timeout", "5000", "--output-on-failure"], - env=_ctest_env(test_dir), - ) - assert result.returncode == 0, f"ctest reported failures (exit {result.returncode})" diff --git a/test/verify/test_nonzero.cpp b/test/verify/test_nonzero.cpp index ac894195bb8..dbbbad4a548 100644 --- a/test/verify/test_nonzero.cpp +++ b/test/verify/test_nonzero.cpp @@ -51,24 +51,3 @@ template struct test_nonzero; template struct test_nonzero; template struct test_nonzero; template struct test_nonzero; - -template -struct test_nonzero_transpose : verify_program> -{ - migraphx::program create_program() const - { - migraphx::program p; - auto* mm = p.get_main_module(); - migraphx::shape s{DType, {2, 3}}; - auto x = mm->add_parameter("data", s); - auto transposed = - mm->add_instruction(migraphx::make_op("transpose", {{"permutation", {1, 0}}}), x); - auto r = mm->add_instruction(migraphx::make_op("nonzero"), transposed); - mm->add_return({r}); - - return p; - } -}; - -template struct test_nonzero_transpose; -template struct test_nonzero_transpose; diff --git a/tools/generate.py b/tools/generate.py index 7d9d11105b1..c776cbc0399 100644 --- a/tools/generate.py +++ b/tools/generate.py @@ -1,7 +1,7 @@ ##################################################################################### # The MIT License (MIT) # -# Copyright (c) 2015-2026 Advanced Micro Devices, Inc. All rights reserved. +# Copyright (c) 2015-2023 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 @@ -41,72 +41,40 @@ def clang_format(buffer, **kwargs): **kwargs).stdout.decode('utf-8') -def maybe_format(buffer, do_format=True): - return clang_format(buffer) if do_format else buffer - - -def api_generate(input_path: Path, output_path: Path, do_format=True): - output_path.parent.mkdir(parents=True, exist_ok=True) +def api_generate(input_path: Path, output_path: Path): with open(output_path, 'w') as f: - f.write(maybe_format(api.run(input_path), do_format)) + f.write(clang_format(api.run(input_path))) -def te_generate(input_path: Path, output_path: Path, do_format=True): +def te_generate(input_path: Path, output_path: Path): with open(output_path, 'w') as f: - f.write(maybe_format(te.run(input_path), do_format)) - - -def generate_api(output_dir: Path, do_format=True): - runpy.run_path(str(migraphx_py_path)) - header_path = output_dir / 'include/migraphx/migraphx.h' - source_path = output_dir / 'api.cpp' - api_generate(work_dir / 'api/migraphx.h', header_path, do_format) - print(f'Finished generating header {header_path}') - api_generate(work_dir / 'api/api.cpp', source_path, do_format) - print(f'Finished generating source {source_path}') - - -def generate_all(do_format=True): - files = Path('include').absolute().iterdir() - for f in [f for f in files if f.is_file()]: - te_generate(f, src_dir / f'include/migraphx/{f.name}', do_format) - generate_api(src_dir / 'api', do_format) + f.write(clang_format(te.run(input_path))) def main(): parser = argparse.ArgumentParser() parser.add_argument('-f', '--clang-format', type=Path) - parser.add_argument('--api-only', - action='store_true', - help='Only generate the C API files (migraphx.h and ' - 'api.cpp) under the directory given by ' - '--api-output-dir instead of writing into the source ' - 'tree') - parser.add_argument('--api-output-dir', - type=Path, - help='Base output directory for the generated C API ' - 'files: migraphx.h is written under include/migraphx/ ' - 'and api.cpp at the top level') args = parser.parse_args() - if args.api_only and not args.api_output_dir: - parser.error('--api-only requires --api-output-dir') - global clang_format_path if args.clang_format: clang_format_path = args.clang_format + if not clang_format_path.is_file(): + print(f"{clang_format_path}: invalid path or not installed", + file=sys.stderr) + return + try: - if args.api_only: - # These files are only consumed by the compiler, so skip - # clang-format; only `make generate` formats them for review. - generate_api(args.api_output_dir, do_format=False) - else: - if not clang_format_path.is_file(): - print(f"{clang_format_path}: invalid path or not installed", - file=sys.stderr) - return - generate_all() + files = Path('include').absolute().iterdir() + for f in [f for f in files if f.is_file()]: + te_generate(f, src_dir / f'include/migraphx/{f.name}') + runpy.run_path(str(migraphx_py_path)) + api_generate(work_dir / 'api/migraphx.h', + src_dir / 'api/include/migraphx/migraphx.h') + print('Finished generating header migraphx.h') + api_generate(work_dir / 'api/api.cpp', src_dir / 'api/api.cpp') + print('Finished generating source api.cpp') except subprocess.CalledProcessError as ex: if ex.stdout: print(ex.stdout.decode('utf-8'))