From 6d5ee4d69bd7aac085bd8dca5a391227e628948d Mon Sep 17 00:00:00 2001 From: zesongw Date: Fri, 15 Dec 2023 06:33:44 +0800 Subject: [PATCH] [WebNN EP] Use explicit padding (#18688) WebNN will remove autoPad option, we need to use explicit padding values. Compute padding values of autopad(same-upper, same-lower) for Op Pool, Conv and ConvTranspose. --- .../webnn/builders/impl/builder_utils.cc | 42 ++--- .../webnn/builders/impl/builder_utils.h | 3 +- .../webnn/builders/impl/conv_op_builder.cc | 153 +++++++++--------- .../webnn/builders/impl/pool_op_builder.cc | 34 ++-- 4 files changed, 111 insertions(+), 121 deletions(-) diff --git a/onnxruntime/core/providers/webnn/builders/impl/builder_utils.cc b/onnxruntime/core/providers/webnn/builders/impl/builder_utils.cc index 516ac74643..d147ffbbd1 100644 --- a/onnxruntime/core/providers/webnn/builders/impl/builder_utils.cc +++ b/onnxruntime/core/providers/webnn/builders/impl/builder_utils.cc @@ -19,9 +19,10 @@ common::Status ComputeConvPads(const std::vector input_shape, const std::vector& onnx_strides, const std::vector& onnx_dilations, AutoPadType auto_pad_type, - std::vector& pads_out) { - const int64_t input_size_y = input_shape[2]; - const int64_t input_size_x = input_shape[3]; + std::vector& pads_out, + bool use_nchw) { + const int64_t input_size_y = use_nchw ? input_shape[2] : input_shape[1]; + const int64_t input_size_x = use_nchw ? input_shape[3] : input_shape[2]; const int64_t stride_y = onnx_strides[0]; const int64_t stride_x = onnx_strides[1]; const int64_t dilation_y = onnx_dilations[0]; @@ -53,32 +54,17 @@ common::Status HandleAutoPad(const std::vector input_shape, const std::vector& onnx_strides, const std::vector& onnx_dilations, AutoPadType auto_pad_type, - AutoPadType& auto_pad_type_out) { - auto_pad_type_out = auto_pad_type; - if (auto_pad_type == AutoPadType::NOTSET && onnx_dilations == std::vector{1, 1}) { - { - std::vector same_upper_pads; - ORT_RETURN_IF_ERROR(ComputeConvPads(input_shape, weight_size_y, weight_size_x, - onnx_pads, onnx_strides, onnx_dilations, - AutoPadType::SAME_UPPER, same_upper_pads)); - if (onnx_pads == same_upper_pads) { - auto_pad_type_out = AutoPadType::SAME_UPPER; - return Status::OK(); - } - } - - { - std::vector same_lower_pads; - ORT_RETURN_IF_ERROR(ComputeConvPads(input_shape, weight_size_y, weight_size_x, - onnx_pads, onnx_strides, onnx_dilations, - AutoPadType::SAME_LOWER, same_lower_pads)); - if (onnx_pads == same_lower_pads) { - auto_pad_type_out = AutoPadType::SAME_LOWER; - return Status::OK(); - } - } + std::vector& pads_out, + bool use_nchw) { + if (AutoPadType::SAME_UPPER == auto_pad_type) { + ORT_RETURN_IF_ERROR(ComputeConvPads(input_shape, weight_size_y, weight_size_x, + onnx_pads, onnx_strides, onnx_dilations, + AutoPadType::SAME_UPPER, pads_out, use_nchw)); + } else { + ORT_RETURN_IF_ERROR(ComputeConvPads(input_shape, weight_size_y, weight_size_x, + onnx_pads, onnx_strides, onnx_dilations, + AutoPadType::SAME_LOWER, pads_out, use_nchw)); } - return Status::OK(); } diff --git a/onnxruntime/core/providers/webnn/builders/impl/builder_utils.h b/onnxruntime/core/providers/webnn/builders/impl/builder_utils.h index 76acbca053..cb7c3c6955 100644 --- a/onnxruntime/core/providers/webnn/builders/impl/builder_utils.h +++ b/onnxruntime/core/providers/webnn/builders/impl/builder_utils.h @@ -21,7 +21,8 @@ common::Status HandleAutoPad(const std::vector input_shape, const std::vector& onnx_strides, const std::vector& onnx_dilations, AutoPadType auto_pad_type, - AutoPadType& auto_pad_type_out) ORT_MUST_USE_RESULT; + std::vector& pads_out, + bool use_nchw) ORT_MUST_USE_RESULT; } // namespace webnn } // namespace onnxruntime diff --git a/onnxruntime/core/providers/webnn/builders/impl/conv_op_builder.cc b/onnxruntime/core/providers/webnn/builders/impl/conv_op_builder.cc index e94db2faa8..df0d54e3fd 100644 --- a/onnxruntime/core/providers/webnn/builders/impl/conv_op_builder.cc +++ b/onnxruntime/core/providers/webnn/builders/impl/conv_op_builder.cc @@ -44,7 +44,7 @@ common::Status SetConvBaseOptions(ModelBuilder& model_builder, const Node& node, emscripten::val& options, const std::vector& strides, const std::vector& dilations, - const std::vector& pads, + std::vector& pads, const logging::Logger& logger) { NodeAttrHelper helper(node); const auto group = helper.Get("group", static_cast(1)); @@ -55,29 +55,85 @@ common::Status SetConvBaseOptions(ModelBuilder& model_builder, options.set("dilations", emscripten::val::array(dilations)); options.set("groups", group); // Add Padding. - // Usually using autopadding is more efficient than using explicit padding. - // Try to see if we can map explicit padding to auto padding. std::vector input_shape; ORT_RETURN_IF_NOT(GetShape(*input_defs[0], input_shape, logger), "Cannot get shape"); - AutoPadType auto_pad_type; - ORT_RETURN_IF_ERROR(HandleAutoPad(input_shape, weight_shape[2], weight_shape[3], - helper.Get("pads", std::vector{0, 0, 0, 0}), - helper.Get("strides", std::vector{1, 1}), - helper.Get("dilations", std::vector{1, 1}), - StringToAutoPadType(helper.Get("auto_pad", "NOTSET")), - auto_pad_type)); - if (AutoPadType::SAME_UPPER == auto_pad_type || AutoPadType::SAME_LOWER == auto_pad_type) { - if (AutoPadType::SAME_LOWER == auto_pad_type) { // default is SAME_UPPER - options.set("autoPad", emscripten::val("same-lower")); + AutoPadType auto_pad_type = StringToAutoPadType(helper.Get("auto_pad", "NOTSET")); + if (node.OpType() == "Conv") { + // Calculate explicit padding for autoPad. + if (AutoPadType::SAME_UPPER == auto_pad_type || AutoPadType::SAME_LOWER == auto_pad_type) { + std::vector pads_out; + ORT_RETURN_IF_ERROR(HandleAutoPad(input_shape, weight_shape[2], weight_shape[3], + helper.Get("pads", std::vector{0, 0, 0, 0}), + helper.Get("strides", std::vector{1, 1}), + helper.Get("dilations", std::vector{1, 1}), + auto_pad_type, + pads_out, + model_builder.GetPreferredLayout() == DataLayout::NCHW)); + std::transform(pads_out.begin(), pads_out.end(), pads.begin(), + [](int64_t pad) -> int32_t { return static_cast(pad); }); + } + } else if (node.OpType() == "ConvTranspose") { + // When the 'output_shape' is specificed, the 'output_padding' values + // in options.outputPadding are ignored. + std::vector dim; + std::vector output_padding{0, 0}; + if (helper.HasAttr("output_shape")) { + // Default value of 'output_shape' will be ignore as we already check if + // it's existed. + dim = helper.Get("output_shape", std::vector{-1, -1}); + // Extract the height and width. + std::vector output_shape; + if (dim.size() == 2) { + output_shape = dim; + } else if (dim.size() == 4) { + output_shape = {dim[2], dim[3]}; + } else { + return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT, "Invalid output shape"); + } + // Padding values are auto generated. + if (helper.HasAttr("kernel_shape")) { + std::vector kernel_shape = helper.Get("kernel_shape", std::vector{-1, -1}); + std::vector total_padding(2); + std::vector input_shape; + ORT_RETURN_IF_NOT(GetShape(*input_defs[0], input_shape, logger), "Cannot get shape"); + for (size_t i = 0; i < 2; i++) { + // Get the dimensions of H and W. + // For NHWC layout, the dimensions of H and W correspond to index 1 and 2. + // For NCHW layout, the dimensions of H and W correspond to index 2 and 3. + if (model_builder.GetPreferredLayout() == DataLayout::NHWC) { + total_padding[i] = strides[i] * (narrow(input_shape[i + 1]) - 1) + + output_padding[i] + ((kernel_shape[i] - 1) * dilations[i] + 1) - output_shape[i]; + } else { + ORT_RETURN_IF_NOT(model_builder.GetPreferredLayout() == DataLayout::NCHW, + "WebNN GPU backend preferred layout should be NCHW."); + total_padding[i] = strides[i] * (narrow(input_shape[i + 2]) - 1) + + output_padding[i] + ((kernel_shape[i] - 1) * dilations[i] + 1) - output_shape[i]; + } + } + AutoPadType auto_pad_type = StringToAutoPadType(helper.Get("auto_pad", "NOTSET")); + if (AutoPadType::SAME_UPPER == auto_pad_type || AutoPadType::SAME_LOWER == auto_pad_type) { + pads[0] = total_padding[0] / 2; + pads[1] = total_padding[0] - pads[0]; + pads[2] = total_padding[1] / 2; + pads[3] = total_padding[1] - pads[2]; + if (AutoPadType::SAME_LOWER == auto_pad_type) { + std::swap(pads[0], pads[1]); + std::swap(pads[2], pads[3]); + } + } + } + options.set("outputSizes", emscripten::val::array(output_shape)); } else { - options.set("autoPad", emscripten::val("same-upper")); + output_padding = helper.Get("output_padding", std::vector{0, 0}); + options.set("outputPadding", emscripten::val::array(output_padding)); } } else { - // Permute the ONNX's pads, which is [beginning_height, beginning_width, ending_height, ending_width], - // while WebNN's padding is [beginning_height, ending_height, beginning_width, ending_width]. - const std::vector padding{pads[0], pads[2], pads[1], pads[3]}; - options.set("padding", emscripten::val::array(padding)); + return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT, "conv_op_builder only supports Op Conv and ConvTranspose."); } + // Permute the ONNX's pads, which is [beginning_height, beginning_width, ending_height, ending_width], + // while WebNN's padding is [beginning_height, ending_height, beginning_width, ending_width]. + const std::vector padding{pads[0], pads[2], pads[1], pads[3]}; + options.set("padding", emscripten::val::array(padding)); // Add bias if present. if (input_defs.size() > 2) { @@ -198,17 +254,17 @@ Status ConvOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const N const auto strides = helper.Get("strides", std::vector{1, 1}); const auto dilations = helper.Get("dilations", std::vector{1, 1}); auto pads = helper.Get("pads", std::vector{0, 0, 0, 0}); - const auto& weight = input_defs[1]->Name(); + const auto& weight_name = input_defs[1]->Name(); + emscripten::val options = emscripten::val::object(); + ORT_RETURN_IF_ERROR(SetConvBaseOptions(model_builder, node, options, strides, dilations, pads, logger)); if (op_type == "Conv") { - emscripten::val options = emscripten::val::object(); - ORT_RETURN_IF_ERROR(SetConvBaseOptions(model_builder, node, options, strides, dilations, pads, logger)); int groups = options["groups"].as(); std::vector input_shape; ORT_RETURN_IF_NOT(GetShape(*input_defs[0], input_shape, logger), "Cannot get shape"); if (model_builder.GetPreferredLayout() == DataLayout::NHWC) { bool depthwise = (groups == input_shape[3] && groups != 1); options.set("inputLayout", emscripten::val("nhwc")); - ORT_RETURN_IF_ERROR(AddInitializerInNewLayout(model_builder, weight, !depthwise)); + ORT_RETURN_IF_ERROR(AddInitializerInNewLayout(model_builder, weight_name, !depthwise)); if (!depthwise) { options.set("filterLayout", emscripten::val("ohwi")); } else { @@ -219,61 +275,10 @@ Status ConvOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const N output = model_builder.GetBuilder().call("conv2d", input, filter, options); } else { - emscripten::val options = emscripten::val::object(); - ORT_RETURN_IF_ERROR(SetConvBaseOptions(model_builder, node, options, strides, dilations, pads, logger)); if (model_builder.GetPreferredLayout() == DataLayout::NHWC) { options.set("inputLayout", emscripten::val("nhwc")); options.set("filterLayout", emscripten::val("ohwi")); - ORT_RETURN_IF_ERROR(AddInitializerInNewLayout(model_builder, weight, false)); - } - - // When the 'output_shape' is specificed, the 'output_padding' values - // in options.outputPadding are ignored. - std::vector dim; - std::vector output_padding{0, 0}; - if (helper.HasAttr("output_shape")) { - // Default value of 'output_shape' will be ignore as we already check if - // it's existed. - dim = helper.Get("output_shape", std::vector{-1, -1}); - // Extract the height and width. - std::vector output_shape; - if (dim.size() == 2) { - output_shape = dim; - } else if (dim.size() == 4) { - output_shape = {dim[2], dim[3]}; - } else { - return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT, "Invalid output shape"); - } - // Padding values are auto generated. - if (helper.HasAttr("kernel_shape")) { - std::vector kernel_shape = helper.Get("kernel_shape", std::vector{-1, -1}); - std::vector total_padding(2); - std::vector input_shape; - ORT_RETURN_IF_NOT(GetShape(*input_defs[0], input_shape, logger), "Cannot get shape"); - for (size_t i = 0; i < 2; i++) { - // Get the dimensions of H and W. - // For NHWC layout, the dimensions of H and W correspond to index 1 and 2. - // For NCHW layout, the dimensions of H and W correspond to index 2 and 3. - if (model_builder.GetPreferredLayout() == DataLayout::NHWC) { - total_padding[i] = strides[i] * (narrow(input_shape[i + 1]) - 1) + - output_padding[i] + ((kernel_shape[i] - 1) * dilations[i] + 1) - output_shape[i]; - } else { - ORT_RETURN_IF_NOT(model_builder.GetPreferredLayout() == DataLayout::NCHW, - "WebNN GPU backend preferred layout should be NCHW."); - total_padding[i] = strides[i] * (narrow(input_shape[i + 2]) - 1) + - output_padding[i] + ((kernel_shape[i] - 1) * dilations[i] + 1) - output_shape[i]; - } - } - pads[0] = total_padding[0] - (total_padding[0] / 2); - pads[1] = total_padding[0] / 2; - pads[2] = total_padding[1] - (total_padding[1] / 2); - pads[3] = total_padding[1] / 2; - options.set("padding", emscripten::val::array(pads)); - } - options.set("outputSizes", emscripten::val::array(output_shape)); - } else { - output_padding = helper.Get("output_padding", std::vector{0, 0}); - options.set("outputPadding", emscripten::val::array(output_padding)); + ORT_RETURN_IF_ERROR(AddInitializerInNewLayout(model_builder, weight_name, false)); } emscripten::val filter = model_builder.GetOperand(input_defs[1]->Name()); output = model_builder.GetBuilder().call("convTranspose2d", input, filter, options); diff --git a/onnxruntime/core/providers/webnn/builders/impl/pool_op_builder.cc b/onnxruntime/core/providers/webnn/builders/impl/pool_op_builder.cc index ae7c111c1f..739c3b3f38 100644 --- a/onnxruntime/core/providers/webnn/builders/impl/pool_op_builder.cc +++ b/onnxruntime/core/providers/webnn/builders/impl/pool_op_builder.cc @@ -81,28 +81,26 @@ Status PoolOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const auto onnx_kernel_shape = helper.Get("kernel_shape", std::vector{0, 0}); const auto onnx_strides = helper.Get("strides", std::vector{1, 1}); const auto onnx_pads = helper.Get("pads", std::vector{0, 0, 0, 0}); - + auto pads = helper.Get("pads", std::vector{0, 0, 0, 0}); std::vector input_shape; ORT_RETURN_IF_NOT(GetShape(*input_defs[0], input_shape, logger), "Cannot get shape"); - AutoPadType auto_pad_type; - ORT_RETURN_IF_ERROR(HandleAutoPad(input_shape, onnx_kernel_shape[0], onnx_kernel_shape[1], - onnx_pads, onnx_strides, {1, 1} /* dilations */, - StringToAutoPadType(helper.Get("auto_pad", "NOTSET")), - auto_pad_type)); - + AutoPadType auto_pad_type = StringToAutoPadType(helper.Get("auto_pad", "NOTSET")); if (AutoPadType::SAME_UPPER == auto_pad_type || AutoPadType::SAME_LOWER == auto_pad_type) { - if (AutoPadType::SAME_LOWER == auto_pad_type) { // default is SAME_UPPER - options.set("autoPad", "same-lower"); - } else { - options.set("autoPad", "same-upper"); - } - } else { - const std::vector pads = helper.Get("pads", std::vector{0, 0, 0, 0}); - // Permute the ONNX's pads, which is [beginning_height, beginning_width, ending_height, ending_width], - // while WebNN's padding is [beginning_height, ending_height, beginning_width, ending_width]. - const std::vector padding{pads[0], pads[2], pads[1], pads[3]}; - options.set("padding", emscripten::val::array(padding)); + std::vector pads_out; + ORT_RETURN_IF_ERROR(HandleAutoPad(input_shape, onnx_kernel_shape[0], onnx_kernel_shape[1], + onnx_pads, + helper.Get("strides", std::vector{1, 1}), + helper.Get("dilations", std::vector{1, 1}), + auto_pad_type, + pads_out, + model_builder.GetPreferredLayout() == DataLayout::NCHW)); + std::transform(pads_out.begin(), pads_out.end(), pads.begin(), + [](int64_t pad) -> int32_t { return static_cast(pad); }); } + // Permute the ONNX's pads, which is [beginning_height, beginning_width, ending_height, ending_width], + // while WebNN's padding is [beginning_height, ending_height, beginning_width, ending_width]. + const std::vector padding{pads[0], pads[2], pads[1], pads[3]}; + options.set("padding", emscripten::val::array(padding)); const auto ceil_mode = helper.Get("ceil_mode", 0); options.set("roundingType", ceil_mode == 0 ? emscripten::val("floor")