From a920f3955ba6772865f41c32db4ba213605cb631 Mon Sep 17 00:00:00 2001 From: Shiyi Zou Date: Mon, 18 Nov 2024 13:41:18 +0800 Subject: [PATCH 1/5] support reverse --- js/web/docs/webnn-operators.md | 4 +- .../webnn/builders/impl/slice_op_builder.cc | 97 +++++++++++-------- 2 files changed, 61 insertions(+), 40 deletions(-) diff --git a/js/web/docs/webnn-operators.md b/js/web/docs/webnn-operators.md index 2ff127e9a0bf2..f31eab6c3df91 100644 --- a/js/web/docs/webnn-operators.md +++ b/js/web/docs/webnn-operators.md @@ -85,7 +85,7 @@ operators and the supported opset domain/versions in **WebNN EP** by ONNX Runtim | ReduceSumSquare | ai.onnx(7-10, 11-12, 13-17, 18+) | reduceSumSquare | ✓ | ✓ | Input 'axes' if present should be a constant | | Relu | ai.onnx(7-12, 13, 14+) | relu | ✓ | ✓ | | | Reshape | ai.onnx(7-12, 13, 14-18, 19-20, 21+) | reshape | ✓ | ✓ | Input 'shape' should be a constant, 0 dimension value in 'shape' is not supported | -| Resize | ai.onnx(11-12, 13-17, 18, 19+) | resample2d | ✓ | ✓ | Only supports 4-D input, antialias == 0, coordinate_transformation_mode == 'half_pixel', exclude_outside == 0, keep_aspect_ratio_policy == 'stretch', 'linear' and 'nearest' modes, input 'scales' and 'sizes' if present must be a constant | +| Resize | ai.onnx(11-12, 13-17, 18, 19+) | resample2d | ✓ | ✓ | Only supports 4-D input, antialias == 0, exclude_outside == 0, keep_aspect_ratio_policy == 'stretch', 'linear' and 'nearest' modes, input 'scales' and 'sizes' if present must be a constant | | ScatterElements | ai.onnx(11-12, 13-15, 16-17, 18+) | scatterElements | ✗ | ✓ | Only supports 'reduction' == 'none' | | ScatterND | ai.onnx(11-12, 13-15, 16-17, 18+) | scatterND | ✗ | ✓ | Only supports 'reduction' == 'none' | | Shape | ai.onnx(7-12, 13-14, 15-18, 19-20, 21+) | slice | ✓ | ✓ | | @@ -95,7 +95,7 @@ operators and the supported opset domain/versions in **WebNN EP** by ONNX Runtim | Softplus | ai.onnx(7+) | softplus | ✓ | ✓ | | | Softsign | ai.onnx(7+) | softsign | ✓ | ✓ | | | Sin | ai.onnx(7+) | sin | ✓ | ✓ | | -| Slice | ai.onnx(7-9, 10, 11-12, 13+) | slice | ✓ | ✓ | Input 'starts', 'ends', 'axes', and 'steps' if present must be a constant, only supports 'steps' value >= 1 | +| Slice | ai.onnx(7-9, 10, 11-12, 13+) | slice, reverse | ✓ | ✓ | Input 'starts', 'ends', 'axes', and 'steps' if present must be a constant, only supports 'steps' value >= 1 or 'steps' value == -1| | Softmax | ai.onnx(7-10, 11-12, 13+) | softmax | ✓ | ✓ | | | Split | ai.onnx(7-10, 11-12, 13-17, 18+) | split | ✓ | ✓ | Input 'split' if present should be a constant | | Sqrt | ai.onnx(7-12, 13+) | sqrt | ✓ | ✓ | | diff --git a/onnxruntime/core/providers/webnn/builders/impl/slice_op_builder.cc b/onnxruntime/core/providers/webnn/builders/impl/slice_op_builder.cc index 78ec0acdfbd9f..2cab8c67e70dc 100644 --- a/onnxruntime/core/providers/webnn/builders/impl/slice_op_builder.cc +++ b/onnxruntime/core/providers/webnn/builders/impl/slice_op_builder.cc @@ -40,8 +40,7 @@ void SliceOpBuilder::AddInitializersToSkip(ModelBuilder& model_builder, const No } } -Status SliceOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, - const Node& node, +Status SliceOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node, const logging::Logger& logger) const { const auto& input_defs = node.InputDefs(); std::vector input_shape; @@ -50,9 +49,6 @@ Status SliceOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, NodeAttrHelper helper(node); emscripten::val inputs = model_builder.GetOperand(input_defs[0]->Name()); - std::vector starts(rank); - std::vector sizes(rank); - std::vector steps(rank); // Copy the data from the starts/ends/axes/steps initializers. std::vector input_starts; @@ -66,18 +62,15 @@ Status SliceOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, std::string input_name; // This is an optional input, return empty vector. if (!is_required) { - if (input_defs.size() <= input_idx) - return Status::OK(); + if (input_defs.size() <= input_idx) return Status::OK(); input_name = input_defs[input_idx]->Name(); - if (input_name.empty()) - return Status::OK(); + if (input_name.empty()) return Status::OK(); } input_name = input_defs[input_idx]->Name(); const auto& initializers(model_builder.GetInitializerTensors()); const auto& tensor = *initializers.at(input_name); if (!ReadIntArrayFrom1DTensor(tensor, data, logger)) { - return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, - "Data type for starts and ends inputs is not supported in this build."); + return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "Data type for starts and ends inputs is not supported in this build."); } return Status::OK(); @@ -89,31 +82,59 @@ Status SliceOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, ORT_RETURN_IF_ERROR( SliceOp::PrepareForComputeHelper(input_starts, input_ends, input_axes, input_steps, compute_metadata)); - std::transform(compute_metadata.starts_.cbegin(), compute_metadata.starts_.cend(), - starts.begin(), - [](int64_t i) { return SafeInt(i); }); - std::transform(compute_metadata.ends_.cbegin(), compute_metadata.ends_.cend(), compute_metadata.starts_.cbegin(), - sizes.begin(), - [](int64_t i, int64_t j) { return SafeInt(i - j); }); - std::transform(compute_metadata.steps_.cbegin(), compute_metadata.steps_.cend(), steps.begin(), - [](int64_t i) { return SafeInt(i); }); - - emscripten::val options = emscripten::val::object(); - options.set("strides", emscripten::val::array(steps)); - options.set("label", node.Name()); - emscripten::val output = model_builder.GetBuilder().call("slice", inputs, - emscripten::val::array(starts), - emscripten::val::array(sizes), - options); + // Check if reverse op is needed. + std::vector reverse_axes; + emscripten::val reverse_output = inputs; + for (size_t i = 0; i < rank; ++i) { + if (compute_metadata.steps_[i] == -1) { + reverse_axes.push_back(SafeInt(i)); + compute_metadata.steps_[i] = 1; + compute_metadata.starts_[i] = input_shape[i] - 1 - compute_metadata.starts_[i]; + compute_metadata.ends_[i] = input_shape[i] - 1 - compute_metadata.ends_[i]; + } + } + if (!reverse_axes.empty()) { + emscripten::val reverse_options = emscripten::val::object(); + reverse_options.set("axes", emscripten::val::array(reverse_axes)); + reverse_options.set("label", node.Name() + "_reverse"); + reverse_output = model_builder.GetBuilder().call("reverse", inputs, reverse_options); + } + + // Check if slice op is needed. + bool is_slice_required = false; + for (size_t i = 0; i < rank; ++i) { + if (compute_metadata.steps_[i] != 1 || compute_metadata.starts_[i] != 0 || + compute_metadata.ends_[i] != input_shape[i]) { + is_slice_required = true; + break; + } + } + + emscripten::val output = reverse_output; + if (is_slice_required) { + std::vector starts(rank); + std::vector sizes(rank); + std::vector steps(rank); + std::transform(compute_metadata.starts_.cbegin(), compute_metadata.starts_.cend(), starts.begin(), + [](int64_t i) { return SafeInt(i); }); + std::transform(compute_metadata.ends_.cbegin(), compute_metadata.ends_.cend(), compute_metadata.starts_.cbegin(), + sizes.begin(), [](int64_t i, int64_t j) { return SafeInt(i - j); }); + std::transform(compute_metadata.steps_.cbegin(), compute_metadata.steps_.cend(), steps.begin(), + [](int64_t i) { return SafeInt(i); }); + + emscripten::val options = emscripten::val::object(); + options.set("strides", emscripten::val::array(steps)); + options.set("label", node.Name()); + output = model_builder.GetBuilder().call("slice", inputs, emscripten::val::array(starts), + emscripten::val::array(sizes), options); + } model_builder.AddOperand(node.OutputDefs()[0]->Name(), std::move(output)); return Status::OK(); } -bool SliceOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers, - const Node& node, - const WebnnDeviceType /* device_type */, - const logging::Logger& logger) const { +bool SliceOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers, const Node& node, + const WebnnDeviceType /* device_type */, const logging::Logger& logger) const { const auto& name = node.Name(); const auto& op_type = node.OpType(); const auto& input_defs = node.InputDefs(); @@ -133,8 +154,8 @@ bool SliceOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers, // Optional tensors (axes, steps) can be indicated by an empty name, just ignore it. const std::string input_name = GetTensorName(input_defs, i); if (!input_name.empty() && !Contains(initializers, input_name)) { - LOGS(logger, VERBOSE) << "Input [" << input_name << "] of " << op_type - << " [" << name << "] must be known as initializer"; + LOGS(logger, VERBOSE) << "Input [" << input_name << "] of " << op_type << " [" << name + << "] must be known as initializer"; return false; } } @@ -148,19 +169,19 @@ bool SliceOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers, return false; } const auto data_type = steps_tensor.data_type(); - // WebNN doesn't support steps less than 1. + // WebNN slice only supports steps >= 1 or steps == -1. if (data_type == ONNX_NAMESPACE::TensorProto_DataType_INT64) { if (std::any_of(reinterpret_cast(unpacked_tensor.data()), reinterpret_cast(unpacked_tensor.data() + unpacked_tensor.size()), - [](int64_t i) { return i < 1; })) { - LOGS(logger, VERBOSE) << "WebNN slice doesn't support steps less than 1"; + [](int64_t i) { return i < -1 || i == 0; })) { + LOGS(logger, VERBOSE) << "WebNN slice only supports steps >= 1 or steps == -1"; return false; } } else if (data_type == ONNX_NAMESPACE::TensorProto_DataType_INT32) { if (std::any_of(reinterpret_cast(unpacked_tensor.data()), reinterpret_cast(unpacked_tensor.data()) + unpacked_tensor.size() / sizeof(int32_t), - [](int32_t i) { return i < 1; })) { - LOGS(logger, VERBOSE) << "WebNN slice doesn't support steps less than 1"; + [](int32_t i) { return i < -1 || i == 0; })) { + LOGS(logger, VERBOSE) << "WebNN slice only supports steps >= 1 or steps == -1"; return false; } } From 9d45152600cbac2f6f8a3f5eded0278a7a2a9ca2 Mon Sep 17 00:00:00 2001 From: Shiyi Zou Date: Tue, 19 Nov 2024 09:27:59 +0800 Subject: [PATCH 2/5] support negative steps --- js/web/docs/webnn-operators.md | 2 +- .../webnn/builders/impl/slice_op_builder.cc | 31 ++----------------- 2 files changed, 3 insertions(+), 30 deletions(-) diff --git a/js/web/docs/webnn-operators.md b/js/web/docs/webnn-operators.md index f31eab6c3df91..0c3f6b7263ad6 100644 --- a/js/web/docs/webnn-operators.md +++ b/js/web/docs/webnn-operators.md @@ -95,7 +95,7 @@ operators and the supported opset domain/versions in **WebNN EP** by ONNX Runtim | Softplus | ai.onnx(7+) | softplus | ✓ | ✓ | | | Softsign | ai.onnx(7+) | softsign | ✓ | ✓ | | | Sin | ai.onnx(7+) | sin | ✓ | ✓ | | -| Slice | ai.onnx(7-9, 10, 11-12, 13+) | slice, reverse | ✓ | ✓ | Input 'starts', 'ends', 'axes', and 'steps' if present must be a constant, only supports 'steps' value >= 1 or 'steps' value == -1| +| Slice | ai.onnx(7-9, 10, 11-12, 13+) | slice, reverse | ✓ | ✓ | Input 'starts', 'ends', 'axes', and 'steps' if present must be a constant | | Softmax | ai.onnx(7-10, 11-12, 13+) | softmax | ✓ | ✓ | | | Split | ai.onnx(7-10, 11-12, 13-17, 18+) | split | ✓ | ✓ | Input 'split' if present should be a constant | | Sqrt | ai.onnx(7-12, 13+) | sqrt | ✓ | ✓ | | diff --git a/onnxruntime/core/providers/webnn/builders/impl/slice_op_builder.cc b/onnxruntime/core/providers/webnn/builders/impl/slice_op_builder.cc index 2cab8c67e70dc..577e417d26158 100644 --- a/onnxruntime/core/providers/webnn/builders/impl/slice_op_builder.cc +++ b/onnxruntime/core/providers/webnn/builders/impl/slice_op_builder.cc @@ -86,9 +86,9 @@ Status SliceOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const std::vector reverse_axes; emscripten::val reverse_output = inputs; for (size_t i = 0; i < rank; ++i) { - if (compute_metadata.steps_[i] == -1) { + if (compute_metadata.steps_[i] < 0) { reverse_axes.push_back(SafeInt(i)); - compute_metadata.steps_[i] = 1; + compute_metadata.steps_[i] = -compute_metadata.steps_[i]; compute_metadata.starts_[i] = input_shape[i] - 1 - compute_metadata.starts_[i]; compute_metadata.ends_[i] = input_shape[i] - 1 - compute_metadata.ends_[i]; } @@ -160,33 +160,6 @@ bool SliceOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers, } } - if (input_defs.size() == 5) { // Check steps. - const auto& steps_tensor = *initializers.at(input_defs[4]->Name()); - std::vector unpacked_tensor; - auto status = onnxruntime::utils::UnpackInitializerData(steps_tensor, unpacked_tensor); - if (!status.IsOK()) { - LOGS(logger, ERROR) << "Error while unpacking steps_tensor: " << status.ErrorMessage(); - return false; - } - const auto data_type = steps_tensor.data_type(); - // WebNN slice only supports steps >= 1 or steps == -1. - if (data_type == ONNX_NAMESPACE::TensorProto_DataType_INT64) { - if (std::any_of(reinterpret_cast(unpacked_tensor.data()), - reinterpret_cast(unpacked_tensor.data() + unpacked_tensor.size()), - [](int64_t i) { return i < -1 || i == 0; })) { - LOGS(logger, VERBOSE) << "WebNN slice only supports steps >= 1 or steps == -1"; - return false; - } - } else if (data_type == ONNX_NAMESPACE::TensorProto_DataType_INT32) { - if (std::any_of(reinterpret_cast(unpacked_tensor.data()), - reinterpret_cast(unpacked_tensor.data()) + unpacked_tensor.size() / sizeof(int32_t), - [](int32_t i) { return i < -1 || i == 0; })) { - LOGS(logger, VERBOSE) << "WebNN slice only supports steps >= 1 or steps == -1"; - return false; - } - } - } - return true; } From 50b3152e8e2930ed2e356b9ad4f26b82b74f0b58 Mon Sep 17 00:00:00 2001 From: Shiyi Zou Date: Wed, 20 Nov 2024 09:46:44 +0800 Subject: [PATCH 3/5] inputs->input, also fix slice inputs->reverse_output --- .../providers/webnn/builders/impl/slice_op_builder.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/onnxruntime/core/providers/webnn/builders/impl/slice_op_builder.cc b/onnxruntime/core/providers/webnn/builders/impl/slice_op_builder.cc index 577e417d26158..0cb9de0a17381 100644 --- a/onnxruntime/core/providers/webnn/builders/impl/slice_op_builder.cc +++ b/onnxruntime/core/providers/webnn/builders/impl/slice_op_builder.cc @@ -48,7 +48,7 @@ Status SliceOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const auto rank = input_shape.size(); NodeAttrHelper helper(node); - emscripten::val inputs = model_builder.GetOperand(input_defs[0]->Name()); + emscripten::val input = model_builder.GetOperand(input_defs[0]->Name()); // Copy the data from the starts/ends/axes/steps initializers. std::vector input_starts; @@ -84,7 +84,7 @@ Status SliceOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const // Check if reverse op is needed. std::vector reverse_axes; - emscripten::val reverse_output = inputs; + emscripten::val reverse_output = input; for (size_t i = 0; i < rank; ++i) { if (compute_metadata.steps_[i] < 0) { reverse_axes.push_back(SafeInt(i)); @@ -97,7 +97,7 @@ Status SliceOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const emscripten::val reverse_options = emscripten::val::object(); reverse_options.set("axes", emscripten::val::array(reverse_axes)); reverse_options.set("label", node.Name() + "_reverse"); - reverse_output = model_builder.GetBuilder().call("reverse", inputs, reverse_options); + reverse_output = model_builder.GetBuilder().call("reverse", input, reverse_options); } // Check if slice op is needed. @@ -125,7 +125,7 @@ Status SliceOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const emscripten::val options = emscripten::val::object(); options.set("strides", emscripten::val::array(steps)); options.set("label", node.Name()); - output = model_builder.GetBuilder().call("slice", inputs, emscripten::val::array(starts), + output = model_builder.GetBuilder().call("slice", reverse_output, emscripten::val::array(starts), emscripten::val::array(sizes), options); } From 2abf96eebc9d3ab3be4624510a55ed0a9c87108c Mon Sep 17 00:00:00 2001 From: Shiyi Zou Date: Thu, 21 Nov 2024 10:28:18 +0800 Subject: [PATCH 4/5] address comments --- onnxruntime/core/providers/webnn/builders/helper.h | 2 +- .../webnn/builders/impl/slice_op_builder.cc | 14 ++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/onnxruntime/core/providers/webnn/builders/helper.h b/onnxruntime/core/providers/webnn/builders/helper.h index aa84fb0bd43af..23489f142df3e 100644 --- a/onnxruntime/core/providers/webnn/builders/helper.h +++ b/onnxruntime/core/providers/webnn/builders/helper.h @@ -82,7 +82,7 @@ inline std::string GetTensorName(const ConstPointerContainer index) ? std::string(input_defs[index]->Name()) : ""; } -inline std::vector GetVecUint32FromVecInt64(const std::vector& int64_vec) { +inline std::vector GetVecUint32FromVecInt64(gsl::span int64_vec) { std::vector uint32_vec; uint32_vec.reserve(int64_vec.size()); std::transform(int64_vec.begin(), int64_vec.end(), diff --git a/onnxruntime/core/providers/webnn/builders/impl/slice_op_builder.cc b/onnxruntime/core/providers/webnn/builders/impl/slice_op_builder.cc index 0cb9de0a17381..49d59707adcae 100644 --- a/onnxruntime/core/providers/webnn/builders/impl/slice_op_builder.cc +++ b/onnxruntime/core/providers/webnn/builders/impl/slice_op_builder.cc @@ -62,9 +62,11 @@ Status SliceOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const std::string input_name; // This is an optional input, return empty vector. if (!is_required) { - if (input_defs.size() <= input_idx) return Status::OK(); + if (input_defs.size() <= input_idx) + return Status::OK(); input_name = input_defs[input_idx]->Name(); - if (input_name.empty()) return Status::OK(); + if (input_name.empty()) + return Status::OK(); } input_name = input_defs[input_idx]->Name(); const auto& initializers(model_builder.GetInitializerTensors()); @@ -112,15 +114,11 @@ Status SliceOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const emscripten::val output = reverse_output; if (is_slice_required) { - std::vector starts(rank); + std::vector starts = GetVecUint32FromVecInt64(compute_metadata.starts_); + std::vector steps = GetVecUint32FromVecInt64(compute_metadata.steps_);; std::vector sizes(rank); - std::vector steps(rank); - std::transform(compute_metadata.starts_.cbegin(), compute_metadata.starts_.cend(), starts.begin(), - [](int64_t i) { return SafeInt(i); }); std::transform(compute_metadata.ends_.cbegin(), compute_metadata.ends_.cend(), compute_metadata.starts_.cbegin(), sizes.begin(), [](int64_t i, int64_t j) { return SafeInt(i - j); }); - std::transform(compute_metadata.steps_.cbegin(), compute_metadata.steps_.cend(), steps.begin(), - [](int64_t i) { return SafeInt(i); }); emscripten::val options = emscripten::val::object(); options.set("strides", emscripten::val::array(steps)); From b38c4a941b2b89345930e35867dfef24eb7c5d51 Mon Sep 17 00:00:00 2001 From: Shiyi Zou Date: Fri, 22 Nov 2024 08:51:11 +0800 Subject: [PATCH 5/5] remove ; --- .../core/providers/webnn/builders/impl/slice_op_builder.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onnxruntime/core/providers/webnn/builders/impl/slice_op_builder.cc b/onnxruntime/core/providers/webnn/builders/impl/slice_op_builder.cc index 49d59707adcae..d51297f19f1c2 100644 --- a/onnxruntime/core/providers/webnn/builders/impl/slice_op_builder.cc +++ b/onnxruntime/core/providers/webnn/builders/impl/slice_op_builder.cc @@ -115,7 +115,7 @@ Status SliceOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const emscripten::val output = reverse_output; if (is_slice_required) { std::vector starts = GetVecUint32FromVecInt64(compute_metadata.starts_); - std::vector steps = GetVecUint32FromVecInt64(compute_metadata.steps_);; + std::vector steps = GetVecUint32FromVecInt64(compute_metadata.steps_); std::vector sizes(rank); std::transform(compute_metadata.ends_.cbegin(), compute_metadata.ends_.cend(), compute_metadata.starts_.cbegin(), sizes.begin(), [](int64_t i, int64_t j) { return SafeInt(i - j); });