From fc375a6f58ba9b4cd673782ca840e9dd222c9bc1 Mon Sep 17 00:00:00 2001 From: Wanming Lin Date: Thu, 31 Oct 2024 08:52:10 +0800 Subject: [PATCH] [WebNN] Support And, Or and Xor ops (#22598) Co-authored-by: Dwayne Robinson --- js/web/docs/webnn-operators.md | 5 +- js/web/test/suite-test-list.jsonc | 50 +++++++++---------- .../core/providers/webnn/builders/helper.h | 3 ++ .../webnn/builders/impl/logical_op_builder.cc | 38 +++++++------- .../webnn/builders/op_builder_factory.cc | 3 ++ 5 files changed, 53 insertions(+), 46 deletions(-) diff --git a/js/web/docs/webnn-operators.md b/js/web/docs/webnn-operators.md index 004188bd66..2293de8d55 100644 --- a/js/web/docs/webnn-operators.md +++ b/js/web/docs/webnn-operators.md @@ -13,6 +13,7 @@ operators and the supported opset domain/versions in **WebNN EP** by ONNX Runtim |:------:|:------:|:------:|:-:|:-:|:------| | Abs | ai.onnx(7-12, 13+) | abs | ✓ | ✓ | | | Add | ai.onnx(7-12, 13, 14+) | add | ✓ | ✓ | | +| And | ai.onnx(7+) | logicalAnd | ✗ | ✓ | | | ArgMax | ai.onnx(7-10, 11, 12, 13+) | argMax | ✓ | ✓ | | | ArgMin | ai.onnx(7-10, 11, 12, 13+) | argMin | ✓ | ✓ | | | AveragePool | ai.onnx(7-9, 10, 11, 12-18, 19+) | averagePool2d | ✓ | ✓ | Only supports 4-D input, 2-D 'kernel_shape', 'count_include_pad' value is 0 | @@ -62,7 +63,8 @@ operators and the supported opset domain/versions in **WebNN EP** by ONNX Runtim | Min | ai.onnx(7, 8-11, 12, 13+) | min | ✓ | ✓ | | | Mul | ai.onnx(7-12, 13, 14+) | mul | ✓ | ✓ | | | Neg | ai.onnx(7-12, 13+) | neg | ✓ | ✓ | | -| Not | ai.onnx(7+) | logicalnot | ✓ | ✓ | | +| Not | ai.onnx(7+) | logicalNot | ✓ | ✓ | | +| Or | ai.onnx(7+) | logicalOr | ✗ | ✓ | | | Pad | ai.onnx(7-10, 11-12, 13-17, 18, 19-20, 21+) | pad | ✓ | ✓ | modes == 'wrap' is not supported | | Pow | ai.onnx(7-11, 12, 13-14, 15+) | pow | ✓ | ✓ | | | PRelu | ai.onnx(7-8, 9-15, 16+) | prelu | ✓ | ✓ | WebNN CPU backend restricts the last dimension of input and slope to be same (Chromium issue: https://issues.chromium.org/issues/335517470) | @@ -101,3 +103,4 @@ operators and the supported opset domain/versions in **WebNN EP** by ONNX Runtim | Trilu | ai.onnx(14+) | triangular | ✓ | ✓ | Input 'k' (option 'diagonal' for WebNN) if present should be a constant | | Unsqueeze | ai.onnx(7-10, 11-12, 13-20, 21+) | reshape | ✓ | ✓ | | | Where | ai.onnx(7-8, 9-15, 16+) | where | ✓ | ✓ | | +| Xor | ai.onnx(7+) | logicalXor | ✗ | ✓ | | diff --git a/js/web/test/suite-test-list.jsonc b/js/web/test/suite-test-list.jsonc index d4e7198257..fc1dbd1f20 100644 --- a/js/web/test/suite-test-list.jsonc +++ b/js/web/test/suite-test-list.jsonc @@ -1532,14 +1532,14 @@ "test_add_bcast", // "test_add_uint8", "test_add", - // "test_and_bcast3v1d", - // "test_and_bcast3v2d", - // "test_and_bcast4v2d", - // "test_and_bcast4v3d", - // "test_and_bcast4v4d", - // "test_and2d", - // "test_and3d", - // "test_and4d", + "test_and_bcast3v1d", + "test_and_bcast3v2d", + "test_and_bcast4v2d", + "test_and_bcast4v3d", + "test_and_bcast4v4d", + "test_and2d", + "test_and3d", + "test_and4d", "test_argmax_default_axis_example_select_last_index", "test_argmax_default_axis_example", "test_argmax_default_axis_random_select_last_index", @@ -2089,14 +2089,14 @@ // // "test_optional_get_element", // // "test_optional_has_element_empty", // // "test_optional_has_element", - // "test_or_bcast3v1d", - // "test_or_bcast3v2d", - // "test_or_bcast4v2d", - // "test_or_bcast4v3d", - // "test_or_bcast4v4d", - // "test_or2d", - // "test_or3d", - // "test_or4d", + "test_or_bcast3v1d", + "test_or_bcast3v2d", + "test_or_bcast4v2d", + "test_or_bcast4v3d", + "test_or_bcast4v4d", + "test_or2d", + "test_or3d", + "test_or4d", "test_pow_bcast_array", "test_pow_bcast_scalar", "test_pow_example", @@ -2550,16 +2550,16 @@ "test_unsqueeze", // "test_wrap_pad" // "test_upsample_nearest", - "test_where_example" + "test_where_example", // "test_where_long_example", - // "test_xor_bcast3v1d", - // "test_xor_bcast3v2d", - // "test_xor_bcast4v2d", - // "test_xor_bcast4v3d", - // "test_xor_bcast4v4d", - // "test_xor2d", - // "test_xor3d", - // "test_xor4d" + "test_xor_bcast3v1d", + "test_xor_bcast3v2d", + "test_xor_bcast4v2d", + "test_xor_bcast4v3d", + "test_xor_bcast4v4d", + "test_xor2d", + "test_xor3d", + "test_xor4d" ], "ops": [] } diff --git a/onnxruntime/core/providers/webnn/builders/helper.h b/onnxruntime/core/providers/webnn/builders/helper.h index adeb34a4c7..932d5eca90 100644 --- a/onnxruntime/core/providers/webnn/builders/helper.h +++ b/onnxruntime/core/providers/webnn/builders/helper.h @@ -191,6 +191,7 @@ std::vector> GetSupportedNodes(const GraphViewer& graph_v static const InlinedHashMap op_map = { {"Abs", "abs"}, {"Add", "add"}, + {"And", "logicalAnd"}, {"ArgMax", "argMax"}, {"ArgMin", "argMin"}, {"AveragePool", "averagePool2d"}, @@ -244,6 +245,7 @@ static const InlinedHashMap op_map = { {"Mul", "mul"}, {"Neg", "neg"}, {"Not", "logicalNot"}, + {"Or", "logicalOr"}, {"Pad", "pad"}, {"Pow", "pow"}, {"PRelu", "prelu"}, @@ -282,6 +284,7 @@ static const InlinedHashMap op_map = { {"Trilu", "triangular"}, {"Unsqueeze", "reshape"}, {"Where", "where"}, + {"Xor", "logicalXor"}, }; inline bool CheckSingleOp(const std::string& op_type, const emscripten::val& wnn_builder, diff --git a/onnxruntime/core/providers/webnn/builders/impl/logical_op_builder.cc b/onnxruntime/core/providers/webnn/builders/impl/logical_op_builder.cc index ea7f70b459..d56fdbc08c 100644 --- a/onnxruntime/core/providers/webnn/builders/impl/logical_op_builder.cc +++ b/onnxruntime/core/providers/webnn/builders/impl/logical_op_builder.cc @@ -33,28 +33,20 @@ Status LogicalOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, cons const auto& op_type = node.OpType(); emscripten::val input0 = model_builder.GetOperand(input_defs[0]->Name()); emscripten::val input1 = emscripten::val::undefined(); - if (input_defs.size() > 1) { - input1 = model_builder.GetOperand(input_defs[1]->Name()); - } emscripten::val output = emscripten::val::object(); emscripten::val options = emscripten::val::object(); options.set("label", node.Name()); - if (op_type == "Equal") { - output = model_builder.GetBuilder().call("equal", input0, input1, options); - } else if (op_type == "Greater") { - output = model_builder.GetBuilder().call("greater", input0, input1, options); - } else if (op_type == "GreaterOrEqual") { - output = model_builder.GetBuilder().call("greaterOrEqual", input0, input1, options); - } else if (op_type == "Less") { - output = model_builder.GetBuilder().call("lesser", input0, input1, options); - } else if (op_type == "LessOrEqual") { - output = model_builder.GetBuilder().call("lesserOrEqual", input0, input1, options); - } else if (op_type == "Not") { - output = model_builder.GetBuilder().call("logicalNot", input0, options); + + std::string webnn_op_type; + ORT_RETURN_IF_NOT(GetWebNNOpType(op_type, webnn_op_type), "Cannot get WebNN op type"); + + if (input_defs.size() == 1) { + // Not + output = model_builder.GetBuilder().call(webnn_op_type.c_str(), input0, options); } else { - return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT, - "LogicalOpBuilder::AddToModelBuilderImpl, unknown op: ", op_type); + input1 = model_builder.GetOperand(input_defs[1]->Name()); + output = model_builder.GetBuilder().call(webnn_op_type.c_str(), input0, input1, options); } model_builder.AddOperand(node.OutputDefs()[0]->Name(), std::move(output)); @@ -68,11 +60,14 @@ bool LogicalOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& /* initiali const auto& name = node.Name(); const auto& op_type = node.OpType(); const auto& input_defs = node.InputDefs(); - if (input_defs.size() < 2 && op_type != "Not") { - LOGS(logger, VERBOSE) << op_type << " [" << name << "] requires at least 2 inputs, actual: " - << input_defs.size(); + + size_t expected_input_count = (op_type == "Not") ? 1 : 2; + if (input_defs.size() != expected_input_count) { + LOGS(logger, VERBOSE) << op_type << " [" << name << "] expected input count: " + << expected_input_count << ", actual: " << input_defs.size(); return false; } + return true; } @@ -105,12 +100,15 @@ void CreateLogicalOpBuilder(const std::string& op_type, OpBuilderRegistrations& static std::vector op_types = { + "And", "Equal", "Greater", "GreaterOrEqual", "Less", "LessOrEqual", "Not", + "Or", + "Xor", }; op_registrations.builders.push_back(std::make_unique()); diff --git a/onnxruntime/core/providers/webnn/builders/op_builder_factory.cc b/onnxruntime/core/providers/webnn/builders/op_builder_factory.cc index 343502e71a..7860150707 100644 --- a/onnxruntime/core/providers/webnn/builders/op_builder_factory.cc +++ b/onnxruntime/core/providers/webnn/builders/op_builder_factory.cc @@ -121,12 +121,15 @@ static OpBuilderRegistrations CreateOpBuilderRegistrations() { } { // Logical + CreateLogicalOpBuilder("And", op_registrations); CreateLogicalOpBuilder("Equal", op_registrations); CreateLogicalOpBuilder("Greater", op_registrations); CreateLogicalOpBuilder("GreaterOrEqual", op_registrations); CreateLogicalOpBuilder("Less", op_registrations); CreateLogicalOpBuilder("LessOrEqual", op_registrations); CreateLogicalOpBuilder("Not", op_registrations); + CreateLogicalOpBuilder("Or", op_registrations); + CreateLogicalOpBuilder("Xor", op_registrations); } { // LSTM