From de3158e78d09992e4b5085c15da44108d9c6fa83 Mon Sep 17 00:00:00 2001 From: zesongw Date: Sat, 2 Mar 2024 08:55:50 +0800 Subject: [PATCH] [WebNN EP] Add contraints for MatMul (#19713) ### Description Add constraints to MatMul: - The input must be at least 2D. - CPU backend: The input rank must be the same. - CPU backend: The input shape except for the last two axis must be the same. ### Motivation and Context Prevent regression for some models. --- .../webnn/builders/impl/gemm_op_builder.cc | 75 +++++++++++-------- 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/onnxruntime/core/providers/webnn/builders/impl/gemm_op_builder.cc b/onnxruntime/core/providers/webnn/builders/impl/gemm_op_builder.cc index d5f84f853f..455e0e5f16 100644 --- a/onnxruntime/core/providers/webnn/builders/impl/gemm_op_builder.cc +++ b/onnxruntime/core/providers/webnn/builders/impl/gemm_op_builder.cc @@ -91,44 +91,33 @@ Status GemmOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const N bool GemmOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers, const Node& node, - const WebnnDeviceType /* device_type */, + const WebnnDeviceType device_type, const logging::Logger& logger) const { (void)initializers; const auto& op_type = node.OpType(); const auto& input_defs(node.InputDefs()); const size_t a_idx = 0, b_idx = 1, c_idx = 2; // A*B+C + std::vector a_shape; + if (!GetShape(*input_defs[a_idx], a_shape, logger)) + return false; + if (Product(a_shape) == 0) { + LOGS(logger, VERBOSE) << "A must be non-empty"; + return false; + } + + std::vector b_shape; + if (!GetShape(*input_defs[b_idx], b_shape, logger)) + return false; + if (Product(b_shape) == 0) { + LOGS(logger, VERBOSE) << "B must be non-empty"; + return false; + } + if (op_type == "Gemm") { - std::vector a_shape; - { - if (!GetShape(*input_defs[a_idx], a_shape, logger)) - return false; - - if (a_shape.size() != 2) { - LOGS(logger, VERBOSE) << "A must be 2D"; - return false; - } - - if (Product(a_shape) == 0) { - LOGS(logger, VERBOSE) << "A must be non-empty"; - return false; - } - } - - std::vector b_shape; - { - if (!GetShape(*input_defs[b_idx], b_shape, logger)) - return false; - - if (b_shape.size() != 2) { - LOGS(logger, VERBOSE) << "B must be 2D"; - return false; - } - - if (Product(b_shape) == 0) { - LOGS(logger, VERBOSE) << "B must be non-empty"; - return false; - } + if (a_shape.size() != 2 || b_shape.size() != 2) { + LOGS(logger, VERBOSE) << "A and B must be 2D for Gemm"; + return false; } // C of Gemm. @@ -162,6 +151,30 @@ bool GemmOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers, } } + if (op_type == "MatMul") { + if (a_shape.size() < 2 || b_shape.size() < 2) { + LOGS(logger, VERBOSE) << "Inputs of MatMul must be at least 2D"; + return false; + } + + // WebNN CPU backend has two more constraints. + // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/ml/webnn/ml_graph_xnnpack.cc;l=1177 + // TODO: Remove this workaround when Chromium enables broadcast for MatMul on WebNN CPU backend. + if (device_type == WebnnDeviceType::CPU) { + if (a_shape.size() != b_shape.size()) { + LOGS(logger, VERBOSE) << "The rank of two inputs for WebNN CPU backend MatMul must be the same."; + return false; + } + + for (size_t i = 0; i < a_shape.size() - 2; i++) { + if (a_shape[i] != b_shape[i]) { + LOGS(logger, VERBOSE) << "WebNN CPU backend can't support broadcasting for MatMul."; + return false; + } + } + } + } + return true; }