From 1587c5336e07a3446f0c1d53e45ec92bf7bfde02 Mon Sep 17 00:00:00 2001 From: Lakhinder Walia <139581206+lakhinderwalia@users.noreply.github.com> Date: Fri, 13 Oct 2023 19:11:44 -0700 Subject: [PATCH] qlinearconv operator (#2225) --- src/onnx/padding.cpp | 2 +- src/onnx/parse_qlinearconv.cpp | 241 +++++++++++++++++++++++ test/onnx/gen_onnx.py | 110 +++++++++++ test/onnx/onnx_test.cpp | 55 ++++++ test/onnx/qlinearconv_pad_0_test.onnx | Bin 0 -> 284 bytes test/onnx/qlinearconv_pad_1_test.onnx | Bin 0 -> 275 bytes test/onnx/qlinearconv_scale_1D_test.onnx | Bin 0 -> 308 bytes test/onnx/qlinearconv_test.onnx | Bin 0 -> 238 bytes test/onnx/verify_onnx.cpp | 106 ++++++++++ 9 files changed, 513 insertions(+), 1 deletion(-) create mode 100644 src/onnx/parse_qlinearconv.cpp create mode 100644 test/onnx/qlinearconv_pad_0_test.onnx create mode 100644 test/onnx/qlinearconv_pad_1_test.onnx create mode 100644 test/onnx/qlinearconv_scale_1D_test.onnx create mode 100644 test/onnx/qlinearconv_test.onnx diff --git a/src/onnx/padding.cpp b/src/onnx/padding.cpp index c8f67678c13..948735d6973 100644 --- a/src/onnx/padding.cpp +++ b/src/onnx/padding.cpp @@ -47,7 +47,7 @@ void cal_auto_padding_size(onnx_parser::node_info info, return; } - auto auto_pad = info.attributes["auto_pad"].s(); + auto auto_pad = to_upper(info.attributes["auto_pad"].s()); if(auto_pad.find("SAME") != std::string::npos) { bool is_same_upper = (auto_pad.find("SAME_UPPER") != std::string::npos); diff --git a/src/onnx/parse_qlinearconv.cpp b/src/onnx/parse_qlinearconv.cpp new file mode 100644 index 00000000000..9fb205637fd --- /dev/null +++ b/src/onnx/parse_qlinearconv.cpp @@ -0,0 +1,241 @@ +/* + * The MIT License (MIT) + * + * 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 + * 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 + +namespace migraphx { +inline namespace MIGRAPHX_INLINE_NS { +namespace onnx { + +/* + ********************************************************************************* + * Reference: see QLinearConv in * + * https://github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md * + ********************************************************************************* + +com.microsoft.QLinearConv + +Version +This version of the operator has been available since version 1 of the 'com.microsoft' operator set. + +ATTRIBUTES: +auto_pad : string +channels_last : int +dilations : list of ints +group : int +kernel_shape : list of ints +pads : list of ints +strides : list of ints + +INPUTS (8 - 9): +x : T1 +x_scale : tensor(float) +x_zero_point : T1 +w : T2 +w_scale : tensor(float) +w_zero_point : T2 +y_scale : tensor(float) +y_zero_point : T3 +B (optional) : T4 + +OUTPUTS: +y : T3 + +Type Constraints: +T1 : tensor(int8), tensor(uint8) +T2 : tensor(int8), tensor(uint8) +T3 : tensor(int8), tensor(uint8) +T4 : tensor(int32) + +More details also at: +https://xadupre.github.io/draft/onnx/onnx_doc_folder/onnx__QLinearConv.html + +*/ + +struct parse_qlinearconv : op_parser +{ + std::vector operators() const { return {{"QLinearConv"}}; } + + // basic type checking for QLinearConv Operator + void check_inputs(const std::vector& inp_arg) const + { + if(inp_arg.size() < 8) + MIGRAPHX_THROW("QLINEARCONV: missing inputs"); + + const instruction_ref& in_x = inp_arg[0]; + const instruction_ref& in_scale_x = inp_arg[1]; + const instruction_ref& in_w = inp_arg[3]; + const instruction_ref& in_scale_w = inp_arg[4]; + const instruction_ref& in_scale_y = inp_arg[6]; + + auto sh_x = in_x->get_shape(); + auto sh_w = in_w->get_shape(); + auto type_x = sh_x.type(); + auto type_w = sh_w.type(); + + assert(in_x->get_shape().ndim() > 2); + + if(type_x != shape::int8_type and type_x != shape::uint8_type) + MIGRAPHX_THROW("QLINEARCONV: unsupported input type"); + if(type_w != shape::int8_type and type_w != shape::uint8_type) + MIGRAPHX_THROW("QLINEARCONV: unsupported weight type"); + if(in_scale_x->get_shape().type() != shape::float_type) + MIGRAPHX_THROW("QLINEARCONV x scale type should be float"); + if(in_scale_w->get_shape().type() != shape::float_type) + MIGRAPHX_THROW("QLINEARCONV: wt scale type should be float"); + if(in_scale_y->get_shape().type() != shape::float_type) + MIGRAPHX_THROW("QLINEARCONV: y scale type should be float"); + if(inp_arg.size() > 8 and inp_arg[8]->get_shape().type() != shape::int32_type) + MIGRAPHX_THROW("QLINEARCONV y bias should be int32"); + } + + // process all attributes of QLinearConv Operator.. + value process_attributes(const onnx_parser& parser, + const onnx_parser::node_info& info, + const std::vector& args) const + { + value values; + + const auto& in_x = args[0]; + const auto& wt = args[3]; + + size_t kdims = in_x->get_shape().ndim() - 2; + + check_padding_mode(info, "QLINEARCONV"); + + values["stride"] = std::vector(kdims, 1); + values["dilation"] = std::vector(kdims, 1); + values["padding"] = std::vector(kdims, 0); + values["group"] = 1; + + if(contains(info.attributes, "group")) + values["group"] = parser.parse_value(info.attributes.at("group")).template at(); + + if(contains(info.attributes, "strides")) + { + std::vector st; + copy(info.attributes.at("strides").ints(), std::back_inserter(st)); + check_attr_sizes(kdims, st.size(), "QLINEARCONV: inconsistent strides"); + values["stride"] = st; + } + + if(contains(info.attributes, "dilations")) + { + std::vector dil; + copy(info.attributes.at("dilations").ints(), std::back_inserter(dil)); + check_attr_sizes(kdims, dil.size(), "QLINEARCONV: inconsistent dilations"); + values["dilation"] = dil; + } + + if(contains(info.attributes, "pads")) + { + std::vector pads; + copy(info.attributes.at("pads").ints(), std::back_inserter(pads)); + check_attr_sizes(kdims, pads.size() / 2, "QLINEARCONV: inconsistent padding"); + values["padding"] = pads; + } + else if(contains(info.attributes, "auto_pad")) + { + auto in_lens = in_x->get_shape().lens(); + auto wt_lens = wt->get_shape().lens(); + std::vector k_lens(wt_lens.begin() + 2, wt_lens.end()); + std::vector pads = values["padding"].to_vector(); + cal_auto_padding_size( + info, values, k_lens, values["dilation"].to_vector(), in_lens, pads); + values["padding"] = pads; + } + + recalc_conv_attributes(values, kdims); + + return values; + } + + instruction_ref add_bias_to_conv(const instruction_ref bias_arg, + const instruction_ref conv_instr, + const onnx_parser::node_info& info) const + { + auto conv_sh = conv_instr->get_shape(); + auto conv_lens = conv_sh.lens(); + auto conv_type = conv_sh.type(); + + auto broadcast_bias = info.add_instruction( + migraphx::make_op("broadcast", {{"axis", 1}, {"out_lens", conv_lens}}), bias_arg); + auto f_bias = + info.add_instruction(make_op("convert", {{"target_type", conv_type}}), broadcast_bias); + + return info.add_instruction(migraphx::make_op("add"), conv_instr, f_bias); + }; + + instruction_ref parse(const op_desc& /* opd */, + const onnx_parser& parser, + const onnx_parser::node_info& info, + const std::vector& args) const + { + check_inputs(args); + + auto values = process_attributes(parser, info, args); + + // input: quantized x, scale, zero_pt + const instruction_ref& in_x = args[0]; + const instruction_ref& in_scale_x = args[1]; + const instruction_ref& in_zero_pt_x = args[2]; + + // input: quantized weights, scale, zero_pt + const instruction_ref& in_w = args[3]; + const instruction_ref& in_scale_w = args[4]; + const instruction_ref& in_zero_pt_w = args[5]; + + // for the dequantized output y: scale & zero_pt + const instruction_ref& in_scale_y = args[6]; + const instruction_ref& in_zero_pt_y = args[7]; + + auto dquant_x = bcast_qdq_instr("dequantizelinear", in_x, in_scale_x, in_zero_pt_x, info); + + auto dquant_w = bcast_qdq_instr("dequantizelinear", in_w, in_scale_w, in_zero_pt_w, info); + + auto conv_op = migraphx::make_op("convolution", values); + + auto conv_x_w = info.add_instruction(conv_op, dquant_x, dquant_w); + + // Biases, if any.. : is an optional argument. + if(args.size() > 8) + conv_x_w = add_bias_to_conv(args[8], conv_x_w, info); + + auto quant_conv = + bcast_qdq_instr("quantizelinear", conv_x_w, in_scale_y, in_zero_pt_y, info); + return quant_conv; + } +}; + +} // namespace onnx +} // namespace MIGRAPHX_INLINE_NS +} // namespace migraphx diff --git a/test/onnx/gen_onnx.py b/test/onnx/gen_onnx.py index dc57e455ca4..d5ad263131a 100644 --- a/test/onnx/gen_onnx.py +++ b/test/onnx/gen_onnx.py @@ -5151,6 +5151,116 @@ def qlinearadd_bcast_test(): [sc_a, zero_pt_a, sc_b, zero_pt_b, sc_c, zero_pt_c]) +@onnx_test() +def qlinearconv_test(): + # https://xadupre.github.io/draft/onnx/onnx_doc_folder/onnx__QLinearConv.html + x = helper.make_tensor_value_info('X', TensorProto.UINT8, [1, 1, 7, 7]) + sc_x = helper.make_tensor('1', TensorProto.FLOAT, [], [0.00369204697]) + zero_pt_x = helper.make_tensor('2', TensorProto.UINT8, [], [132]) + + wt = helper.make_tensor('3', TensorProto.UINT8, [1, 1, 1, 1], [0]) + sc_wt = helper.make_tensor('4', TensorProto.FLOAT, [], [0.00172794575]) + zero_pt_wt = helper.make_tensor('5', TensorProto.UINT8, [], [255]) + + sc_y = helper.make_tensor('6', TensorProto.FLOAT, [], [0.00162681262]) + zero_pt_y = helper.make_tensor('7', TensorProto.UINT8, [], [123]) + + out = helper.make_tensor_value_info('out', TensorProto.UINT8, [1, 1, 7, 7]) + + node = onnx.helper.make_node( + 'QLinearConv', + inputs=['X', '1', '2', '3', '4', '5', '6', '7'], + outputs=['out'], + ) + return ([node], [x], [out], + [sc_x, zero_pt_x, wt, sc_wt, zero_pt_wt, sc_y, zero_pt_y]) + + +@onnx_test() +def qlinearconv_pad_1_test(): + # https://xadupre.github.io/draft/onnx/onnx_doc_folder/onnx__Conv.html + x = helper.make_tensor_value_info('X', TensorProto.UINT8, [1, 1, 5, 5]) + sc_x = helper.make_tensor('1', TensorProto.FLOAT, [], + [0.09411764705882353]) + zero_pt_x = helper.make_tensor('2', TensorProto.UINT8, [], [0]) + + wt = helper.make_tensor('3', TensorProto.UINT8, [1, 1, 3, 3], + [1, 1, 1, 1, 1, 1, 1, 1, 1]) + sc_wt = helper.make_tensor('4', TensorProto.FLOAT, [], [1.0]) + zero_pt_wt = helper.make_tensor('5', TensorProto.UINT8, [], [0]) + + sc_y = helper.make_tensor('6', TensorProto.FLOAT, [], [0.6352941176470588]) + zero_pt_y = helper.make_tensor('7', TensorProto.UINT8, [], [0]) + + out = helper.make_tensor_value_info('out', TensorProto.UINT8, [1, 1, 5, 5]) + + node = onnx.helper.make_node( + 'QLinearConv', + inputs=['X', '1', '2', '3', '4', '5', '6', '7'], + outputs=['out'], + pads=[1, 1, 1, 1], + ) + return ([node], [x], [out], + [sc_x, zero_pt_x, wt, sc_wt, zero_pt_wt, sc_y, zero_pt_y]) + + +@onnx_test() +def qlinearconv_pad_0_test(): + # https://xadupre.github.io/draft/onnx/onnx_doc_folder/onnx__Conv.html + x = helper.make_tensor_value_info('X', TensorProto.UINT8, [1, 1, 5, 5]) + sc_x = helper.make_tensor('1', TensorProto.FLOAT, [], + [0.09411764705882353]) + zero_pt_x = helper.make_tensor('2', TensorProto.UINT8, [], [0]) + + wt = helper.make_tensor('3', TensorProto.UINT8, [1, 1, 3, 3], + [1, 1, 1, 1, 1, 1, 1, 1, 1]) + sc_wt = helper.make_tensor('4', TensorProto.FLOAT, [], [1.0]) + zero_pt_wt = helper.make_tensor('5', TensorProto.UINT8, [], [0]) + + sc_y = helper.make_tensor('6', TensorProto.FLOAT, [], [0.6352941176470588]) + zero_pt_y = helper.make_tensor('7', TensorProto.INT8, [], [-128]) + + out = helper.make_tensor_value_info('out', TensorProto.INT8, [1, 1, 3, 3]) + + node = onnx.helper.make_node( + 'QLinearConv', + inputs=['X', '1', '2', '3', '4', '5', '6', '7'], + outputs=['out'], + pads=[0, 0, 0, 0], + ) + return ([node], [x], [out], + [sc_x, zero_pt_x, wt, sc_wt, zero_pt_wt, sc_y, zero_pt_y]) + + +@onnx_test() +def qlinearconv_scale_1D_test(): + # https://xadupre.github.io/draft/onnx/onnx_doc_folder/onnx__Conv.html + x = helper.make_tensor_value_info('X', TensorProto.UINT8, [1, 1, 5, 5]) + sc_x = helper.make_tensor('1', TensorProto.FLOAT, [], + [0.09411764705882353]) + zero_pt_x = helper.make_tensor('2', TensorProto.UINT8, [], [0]) + + wt = helper.make_tensor( + '3', TensorProto.UINT8, [2, 1, 3, 3], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2]) + sc_wt = helper.make_tensor('4', TensorProto.FLOAT, [2], [1.0, 0.5]) + zero_pt_wt = helper.make_tensor('5', TensorProto.UINT8, [2], [0, 0]) + + sc_y = helper.make_tensor('6', TensorProto.FLOAT, [], [0.6352941176470588]) + zero_pt_y = helper.make_tensor('7', TensorProto.INT8, [], [-128]) + + out = helper.make_tensor_value_info('out', TensorProto.INT8, [1, 2, 3, 3]) + + node = onnx.helper.make_node( + 'QLinearConv', + inputs=['X', '1', '2', '3', '4', '5', '6', '7'], + outputs=['out'], + pads=[0, 0, 0, 0], + ) + return ([node], [x], [out], + [sc_x, zero_pt_x, wt, sc_wt, zero_pt_wt, sc_y, zero_pt_y]) + + @onnx_test() def quantizelinear_test(): arg0 = helper.make_tensor_value_info('0', TensorProto.FLOAT, [5]) diff --git a/test/onnx/onnx_test.cpp b/test/onnx/onnx_test.cpp index f45af14d100..c86bbe0fbb4 100644 --- a/test/onnx/onnx_test.cpp +++ b/test/onnx/onnx_test.cpp @@ -4904,6 +4904,61 @@ TEST_CASE(qlinearadd_test) EXPECT(p.sort() == prog.sort()); } +TEST_CASE(qlinearconv_test) +{ + migraphx::program p; + auto* mm = p.get_main_module(); + + auto x = mm->add_parameter("X", {migraphx::shape::uint8_type, {1, 1, 7, 7}}); + + auto sc_x = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0.00369204697}}); + auto z_pt_x = mm->add_literal(migraphx::literal{migraphx::shape::uint8_type, {132}}); + + auto w = mm->add_literal( + migraphx::literal{migraphx::shape{migraphx::shape::uint8_type, {1, 1, 1, 1}}, {0}}); + + auto sc_w = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0.00172794575}}); + auto z_pt_w = mm->add_literal(migraphx::literal{migraphx::shape::uint8_type, {255}}); + + auto sc_y = mm->add_literal(migraphx::literal{migraphx::shape::float_type, {0.00162681262}}); + auto z_pt_y = mm->add_literal(migraphx::literal{migraphx::shape::uint8_type, {123}}); + + auto scale_x_bcast = mm->add_instruction( + migraphx::make_op("multibroadcast", {{"out_lens", {1, 1, 7, 7}}}), sc_x); + + auto z_pt_x_bcast = mm->add_instruction( + migraphx::make_op("multibroadcast", {{"out_lens", {1, 1, 7, 7}}}), z_pt_x); + + auto fp_x = + mm->add_instruction(migraphx::make_op("dequantizelinear"), x, scale_x_bcast, z_pt_x_bcast); + + auto scale_w_bcast = mm->add_instruction( + migraphx::make_op("multibroadcast", {{"out_lens", {1, 1, 1, 1}}}), sc_w); + + auto z_pt_w_bcast = mm->add_instruction( + migraphx::make_op("multibroadcast", {{"out_lens", {1, 1, 1, 1}}}), z_pt_w); + + auto fp_w = + mm->add_instruction(migraphx::make_op("dequantizelinear"), w, scale_w_bcast, z_pt_w_bcast); + + auto fp_y = mm->add_instruction(migraphx::make_op("convolution"), fp_x, fp_w); + + auto scale_y_bcast = mm->add_instruction( + migraphx::make_op("multibroadcast", {{"out_lens", {1, 1, 7, 7}}}), sc_y); + + auto z_pt_y_bcast = mm->add_instruction( + migraphx::make_op("multibroadcast", {{"out_lens", {1, 1, 7, 7}}}), z_pt_y); + + auto y = + mm->add_instruction(migraphx::make_op("quantizelinear"), fp_y, scale_y_bcast, z_pt_y_bcast); + + mm->add_return({y}); + + auto prog = migraphx::parse_onnx("qlinearconv_test.onnx"); + + EXPECT(p.sort() == prog.sort()); +} + TEST_CASE(quantizelinear_test) { migraphx::program p; diff --git a/test/onnx/qlinearconv_pad_0_test.onnx b/test/onnx/qlinearconv_pad_0_test.onnx new file mode 100644 index 0000000000000000000000000000000000000000..5f9b34cbe77081babec10ebe294efe934c8edcab GIT binary patch literal 284 zcmdRby#Vx?7#B%V!0b3_VLoE&g zCM`w=Cq^SJ2@XaeWaeN7if}T*fD@xJL<0jugS``@30#9IM0oKcC7`gGmY@K$7FWZ6 xC}4DAG>?)7I$ubPON4_-NPvrpgAs~Zfi!E9EEmWRU}Rby#Vx?7#B%V!0b3_VLoE&g zCM`w=Cq^SJ2@XaeWaeN7if}T*fD@xJL<0jugS``@30#9IM0oKcC7`ewT-ZEH8t7^v gF)k4fCLsYXCJsg@W(Cr$NwQoZr{j`#ViMp70PoQ+TmS$7 literal 0 HcmV?d00001 diff --git a/test/onnx/qlinearconv_scale_1D_test.onnx b/test/onnx/qlinearconv_scale_1D_test.onnx new file mode 100644 index 0000000000000000000000000000000000000000..da50910c66d2826f182d50b518d3ce2138a1b8f9 GIT binary patch literal 308 zcmaJ+%Sr=55S;2su<1QGJBT3S!9DhE^8_J8Mvq>+iI-)Kdk{jR?&>+8;3tUwNc^%m zd3m&&rlE?erkMT7%k*hhm9LZe?7gTb<+LjD>!PXZ=Hi27B)Ajgf{vgo=n49QfnezE z{H@87+nbH~Dw;Kw)^qt-Uz+WF;^3cfO&qe;&vH2m$dw%_Oh8A^nHX%@M#f_g+9RN= zDO2>aF^jP=W*pGdBvPbgVnlzN{QZ?hWuU3EDvQ+)2pHa<#!miNj+nfY_-(iCc-s$W SvM#of?Vn6hvg-}x4u1i~_BuNN literal 0 HcmV?d00001 diff --git a/test/onnx/qlinearconv_test.onnx b/test/onnx/qlinearconv_test.onnx new file mode 100644 index 0000000000000000000000000000000000000000..661e8ff97cb3c5dfd4f047a87de84dfdd6750ea9 GIT binary patch literal 238 zcmdHqZ z#GGGRqQo8O1J>gV)Pre&7PkPS5{v7%LTe{RLoH4LCM~8GMkhuiEddTj2m}f-GB`0B zLsZ(i6)0||#Q{=Y?ZjvvB@J|zkQkQ;2a}Kh7ZV2~6te?q R_9R&@klS!cJ246H0|4j|Di8nw literal 0 HcmV?d00001 diff --git a/test/onnx/verify_onnx.cpp b/test/onnx/verify_onnx.cpp index 43226d97157..69f7a6333d6 100644 --- a/test/onnx/verify_onnx.cpp +++ b/test/onnx/verify_onnx.cpp @@ -1318,6 +1318,112 @@ TEST_CASE(qlinearadd_bcast_test) EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); } +TEST_CASE(qlinearconv_test) +{ + // https://xadupre.github.io/draft/onnx/onnx_doc_folder/onnx__QLinearConv.html + migraphx::program p = migraphx::parse_onnx("qlinearconv_test.onnx"); + p.compile(migraphx::make_target("ref")); + + migraphx::shape sx{migraphx::shape::uint8_type, {1, 1, 7, 7}}; + std::vector x_data = {255, 174, 162, 25, 203, 168, 58, 15, 59, 237, 95, 129, 0, + 64, 56, 242, 153, 221, 168, 12, 166, 232, 178, 186, 195, 237, + 162, 237, 188, 39, 124, 77, 80, 102, 43, 127, 230, 21, 83, + 41, 40, 134, 255, 154, 92, 141, 42, 148, 247}; + + migraphx::parameter_map pp; + pp["X"] = migraphx::argument(sx, x_data.data()); + auto result = p.eval(pp).back(); + + std::vector result_vector; + result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); + + std::vector gold = {0, 81, 93, 230, 52, 87, 197, 240, 196, 18, 160, 126, 255, + 191, 199, 13, 102, 34, 87, 243, 89, 23, 77, 69, 60, 18, + 93, 18, 67, 216, 131, 178, 175, 153, 212, 128, 25, 234, 172, + 214, 215, 121, 0, 101, 163, 114, 213, 107, 8}; + + EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); +} + +TEST_CASE(qlinearconv_pad_0_test) +{ + // https:xadupre.github.io/draft/onnx/onnx_doc_folder/onnx__Conv.html + + migraphx::program p = migraphx::parse_onnx("qlinearconv_pad_0_test.onnx"); + + p.compile(migraphx::make_target("ref")); + + migraphx::shape sx{migraphx::shape::uint8_type, {1, 1, 5, 5}}; + + std::vector x_data = {0, 11, 21, 32, 42, 53, 64, 74, 85, 96, 106, 117, 128, + 138, 149, 159, 170, 181, 191, 202, 212, 223, 234, 244, 255}; + + migraphx::parameter_map pp; + pp["X"] = migraphx::argument(sx, x_data.data()); + auto result = p.eval(pp).back(); + + std::vector result_vector; + result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); + + // # (1, 1, 3, 3) output tensor + std::vector gold = {-43, -29, -15, 28, 42, 56, 99, 113, 127}; + + EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); +} + +TEST_CASE(qlinearconv_pad_1_test) +{ + // https:xadupre.github.io/draft/onnx/onnx_doc_folder/onnx__Conv.html + migraphx::program p = migraphx::parse_onnx("qlinearconv_pad_1_test.onnx"); + + p.compile(migraphx::make_target("ref")); + + migraphx::shape sx{migraphx::shape::uint8_type, {1, 1, 5, 5}}; + + std::vector x_data = {0, 11, 21, 32, 42, 53, 64, 74, 85, 96, 106, 117, 128, + 138, 149, 159, 170, 181, 191, 202, 212, 223, 234, 244, 255}; + + migraphx::parameter_map pp; + pp["X"] = migraphx::argument(sx, x_data.data()); + auto result = p.eval(pp).back(); + + std::vector result_vector; + result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); + + // # (1, 1, 5, 5) output tensor + std::vector gold = {19, 33, 43, 52, 38, 52, 85, 99, 113, 80, 99, 156, 170, + 184, 128, 146, 227, 241, 255, 175, 113, 175, 184, 194, 132}; + + EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); +} + +TEST_CASE(qlinearconv_scale_1D_test) +{ + // https:xadupre.github.io/draft/onnx/onnx_doc_folder/onnx__Conv.html + + migraphx::program p = migraphx::parse_onnx("qlinearconv_scale_1D_test.onnx"); + + p.compile(migraphx::make_target("ref")); + + migraphx::shape sx{migraphx::shape::uint8_type, {1, 1, 5, 5}}; + + std::vector x_data = {0, 11, 21, 32, 42, 53, 64, 74, 85, 96, 106, 117, 128, + 138, 149, 159, 170, 181, 191, 202, 212, 223, 234, 244, 255}; + + migraphx::parameter_map pp; + pp["X"] = migraphx::argument(sx, x_data.data()); + auto result = p.eval(pp).back(); + + std::vector result_vector; + result.visit([&](auto output) { result_vector.assign(output.begin(), output.end()); }); + + // # (1, 2, 3, 3) output tensor + std::vector gold = { + -43, -29, -15, 28, 42, 56, 99, 113, 127, -43, -29, -15, 28, 42, 56, 99, 113, 127}; + + EXPECT(migraphx::verify::verify_rms_range(result_vector, gold)); +} + TEST_CASE(resize_downsample_f_test) { migraphx::program p = migraphx::parse_onnx("resize_downsample_f_test.onnx");