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 455e0e5f16..ed32013216 100644 --- a/onnxruntime/core/providers/webnn/builders/impl/gemm_op_builder.cc +++ b/onnxruntime/core/providers/webnn/builders/impl/gemm_op_builder.cc @@ -42,6 +42,26 @@ Status GemmOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const N if (!GetShape(*input_defs[a_idx], a_shape, logger)) { return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT, "Can not get shape of A."); } + std::vector b_shape; + if (!GetShape(*input_defs[b_idx], b_shape, logger)) { + return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT, "Can not get shape of B."); + } + // If the first argument is 1-D, it is promoted to a matrix by prepending a 1 to its dimensions. + bool extended_a_shape = false; + if (a_shape.size() == 1) { + extended_a_shape = true; + a_shape.insert(a_shape.begin(), 1); + a = model_builder.GetBuilder().call("reshape", a, + emscripten::val::array(GetVecUint32FromVecInt64(a_shape))); + } + // If the second argument is 1-D, it is promoted to a matrix by appending a 1 to its dimensions. + bool extended_b_shape = false; + if (b_shape.size() == 1) { + extended_b_shape = true; + b_shape.push_back(1); + b = model_builder.GetBuilder().call("reshape", b, + emscripten::val::array(GetVecUint32FromVecInt64(b_shape))); + } // The inputs of MatMul must be at least 3D for WebNN CPU backend. Use GEMM for 2D case. // TODO: Remove this workaround when it is fixed in Chromium. if (model_builder.GetWebnnDeviceType() == WebnnDeviceType::CPU && a_shape.size() == 2) { @@ -49,6 +69,27 @@ Status GemmOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const N } else { output = model_builder.GetBuilder().call("matmul", a, b); } + // If the inputs are both 1D, reduce the output to a scalar. + if (extended_a_shape && extended_b_shape) { + output = model_builder.GetBuilder().call("reshape", output, emscripten::val::array()); + } + // After matrix multiplication the prepended 1 is removed. + else if (extended_a_shape) { + std::vector new_shape; + for (size_t i = 0; i < b_shape.size() - 2; i++) { + new_shape.push_back(narrow(b_shape[i])); + } + new_shape.push_back(narrow(b_shape.back())); + output = model_builder.GetBuilder().call("reshape", output, emscripten::val::array(new_shape)); + } + // After matrix multiplication the appended 1 is removed. + else if (extended_b_shape) { + std::vector new_shape; + for (size_t i = 0; i < a_shape.size() - 1; i++) { + new_shape.push_back(narrow(a_shape[i])); + } + output = model_builder.GetBuilder().call("reshape", output, emscripten::val::array(new_shape)); + } } else if (op_type == "MatMulInteger") { emscripten::val a_zero_point = emscripten::val::null(); emscripten::val b_zero_point = emscripten::val::null(); @@ -152,10 +193,10 @@ 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; - } + // If the first argument is 1-D, it is promoted to a matrix by prepending a 1 to its dimensions. + // If the second argument is 1-D, it is promoted to a matrix by appending a 1 to its dimensions. + if (a_shape.size() == 1) a_shape.insert(a_shape.begin(), 1); + if (b_shape.size() == 1) b_shape.push_back(1); // 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