diff --git a/onnxruntime/core/providers/webnn/builders/impl/qdq_op_builder.cc b/onnxruntime/core/providers/webnn/builders/impl/qdq_op_builder.cc index 13dee667f6..8b58c40058 100644 --- a/onnxruntime/core/providers/webnn/builders/impl/qdq_op_builder.cc +++ b/onnxruntime/core/providers/webnn/builders/impl/qdq_op_builder.cc @@ -42,70 +42,28 @@ Status QDQOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, int32_t zero_point_type = 0; ORT_RETURN_IF_NOT(GetType(*input_defs[0], input_type, logger), "Cannot get input data type"); ORT_RETURN_IF_NOT(GetType(*output_defs[0], output_type, logger), "Cannot get output data type"); + emscripten::val input = model_builder.GetOperand(input_defs[0]->Name()); emscripten::val scale = model_builder.GetOperand(input_defs[1]->Name()); - emscripten::val zero_point = emscripten::val::null(); + if (input_defs.size() == 3 && input_defs[2]->Exists()) { zero_point = model_builder.GetOperand(node.InputDefs()[2]->Name()); } else { // DequantizeLinear: x_zero_point's data type equals to input data type // QuantizeLinear: x_zero_point's data type equals to output data type + // WebNN requires the zero_point to have the same shape as the scale zero_point_type = op_type == "DequantizeLinear" ? input_type : output_type; - zero_point = model_builder.GetZeroConstant(zero_point_type); - } - - emscripten::val output; - NodeAttrHelper helper(node); - int32_t axis = helper.Get("axis", 1); - int32_t block_size = helper.Get("block_size", 0); - // axis is valid for input shape greater than 1D. - if (input_shape.size() > 1) { - axis = static_cast(HandleNegativeAxis(axis, input_shape.size())); - } - // Insert ones before and after the axis dimension for broadcasting of 1D scale tensor. - if (1 == scale_shape.size() && 1 < input_shape.size()) { - std::vector target_shape{static_cast(input_shape[axis])}; - target_shape.insert(target_shape.begin(), axis, 1); - target_shape.insert(target_shape.end(), input_shape.size() - axis - 1, 1); - emscripten::val reshape_scale_options = emscripten::val::object(); - reshape_scale_options.set("label", node.Name() + "_reshape_scale"); - scale = model_builder.GetBuilder().call("reshape", - scale, - emscripten::val::array(target_shape), - reshape_scale_options); - emscripten::val reshape_zero_point_options = emscripten::val::object(); - reshape_zero_point_options.set("label", node.Name() + "_reshape_zero_point"); - zero_point = model_builder.GetBuilder().call("reshape", - zero_point, - emscripten::val::array(target_shape), - reshape_zero_point_options); - } - - // If block_size is specified, we need to expand the scale and zero_point tensors. - if (block_size > 1) { - emscripten::val concat_scale_inputs = emscripten::val::array(); - emscripten::val concat_zero_point_inputs = emscripten::val::array(); - for (int i = 0; i < block_size; i++) { - concat_scale_inputs.call("push", scale); - concat_zero_point_inputs.call("push", zero_point); - } - - emscripten::val concat_scale_options = emscripten::val::object(); - concat_scale_options.set("label", node.Name() + "_concat_scale"); - scale = model_builder.GetBuilder().call("concat", concat_scale_inputs, axis, concat_scale_options); - - emscripten::val concat_zero_point_options = emscripten::val::object(); - concat_zero_point_options.set("label", node.Name() + "_concat_zero_point"); - zero_point = model_builder.GetBuilder().call( - "concat", concat_zero_point_inputs, axis, concat_zero_point_options); + const auto zero_point_shape = GetVecUint32FromVecInt64(scale_shape); + zero_point = model_builder.GetZeroConstant(zero_point_type, zero_point_shape); } emscripten::val options = emscripten::val::object(); options.set("label", node.Name()); std::string webnn_op_type; ORT_RETURN_IF_NOT(GetWebNNOpType(op_type, webnn_op_type), "Cannot get WebNN op type"); - output = model_builder.GetBuilder().call(webnn_op_type.c_str(), input, scale, zero_point, options); + emscripten::val output = + model_builder.GetBuilder().call(webnn_op_type.c_str(), input, scale, zero_point, options); model_builder.AddOperand(output_defs[0]->Name(), std::move(output)); diff --git a/onnxruntime/core/providers/webnn/builders/model_builder.cc b/onnxruntime/core/providers/webnn/builders/model_builder.cc index e950fa965e..8bcd3621b5 100644 --- a/onnxruntime/core/providers/webnn/builders/model_builder.cc +++ b/onnxruntime/core/providers/webnn/builders/model_builder.cc @@ -14,6 +14,7 @@ #include "core/providers/common.h" #include "core/providers/shared/utils/utils.h" +#include #include namespace onnxruntime { @@ -384,51 +385,62 @@ void ModelBuilder::AddOperand(const std::string& name, const emscripten::val& op wnn_operands_.insert(std::make_pair(name, operand)); } -// Get the zero scalar constant. -// Workaround for builer.constant(value, type) method since it has not been implemented now. -// https://webmachinelearning.github.io/webnn/#api-mlgraphbuilder-constant-value-type -// BTW, the spec is discussing if the builer.constant(value, type) should be dropped at -// https://github.com/webmachinelearning/webnn/issues/475. Fix me according to the spec decision. -const emscripten::val& ModelBuilder::GetZeroConstant(const int32_t& data_type) { +// Get the zero constant with shape. +const emscripten::val& ModelBuilder::GetZeroConstant(const int32_t& data_type, + const std::vector& shape) { std::string name = "webnn_zero_constant_" + std::to_string(data_type); + emscripten::val dims = emscripten::val::array(); + if (!shape.empty()) { + dims = emscripten::val::array(shape); + std::ostringstream name_stream; + name_stream << name; + for (const auto& dim : shape) { + name_stream << "_" << dim; + } + name = name_stream.str(); + } // If the operand does not exist, create it. if (wnn_operands_.find(name) == wnn_operands_.end()) { emscripten::val desc = emscripten::val::object(); - emscripten::val dims = emscripten::val::array(); desc.set("dimensions", dims); desc.set("shape", dims); emscripten::val zero_buffer = emscripten::val::undefined(); if (!SetWebnnDataType(desc, data_type)) { ORT_THROW("Unsupported data type: " + std::to_string(data_type)); } - + auto num_elements = Product(shape); switch (data_type) { - case ONNX_NAMESPACE::TensorProto_DataType_BOOL: case ONNX_NAMESPACE::TensorProto_DataType_INT4: case ONNX_NAMESPACE::TensorProto_DataType_UINT4: + // For WebNN int4 and uint4 tensors are stored in Uint8Array, + // so we need to adjust the number of elements. + num_elements = (num_elements + 1) / 2; + zero_buffer = emscripten::val::global("Uint8Array").new_(num_elements); + break; + case ONNX_NAMESPACE::TensorProto_DataType_BOOL: case ONNX_NAMESPACE::TensorProto_DataType_UINT8: - zero_buffer = emscripten::val::global("Uint8Array").new_(1); + zero_buffer = emscripten::val::global("Uint8Array").new_(num_elements); break; case ONNX_NAMESPACE::TensorProto_DataType_INT8: - zero_buffer = emscripten::val::global("Int8Array").new_(1); + zero_buffer = emscripten::val::global("Int8Array").new_(num_elements); break; case ONNX_NAMESPACE::TensorProto_DataType_FLOAT16: - zero_buffer = emscripten::val::global("Uint16Array").new_(1); + zero_buffer = emscripten::val::global("Uint16Array").new_(num_elements); break; case ONNX_NAMESPACE::TensorProto_DataType_FLOAT: - zero_buffer = emscripten::val::global("Float32Array").new_(1); + zero_buffer = emscripten::val::global("Float32Array").new_(num_elements); break; case ONNX_NAMESPACE::TensorProto_DataType_INT32: - zero_buffer = emscripten::val::global("Int32Array").new_(1); + zero_buffer = emscripten::val::global("Int32Array").new_(num_elements); break; case ONNX_NAMESPACE::TensorProto_DataType_INT64: - zero_buffer = emscripten::val::global("BigInt64Array").new_(1); + zero_buffer = emscripten::val::global("BigInt64Array").new_(num_elements); break; case ONNX_NAMESPACE::TensorProto_DataType_UINT32: - zero_buffer = emscripten::val::global("Uint32Array").new_(1); + zero_buffer = emscripten::val::global("Uint32Array").new_(num_elements); break; case ONNX_NAMESPACE::TensorProto_DataType_UINT64: - zero_buffer = emscripten::val::global("BigUint64Array").new_(1); + zero_buffer = emscripten::val::global("BigUint64Array").new_(num_elements); break; default: break; diff --git a/onnxruntime/core/providers/webnn/builders/model_builder.h b/onnxruntime/core/providers/webnn/builders/model_builder.h index 13937933a0..aeb128adc9 100644 --- a/onnxruntime/core/providers/webnn/builders/model_builder.h +++ b/onnxruntime/core/providers/webnn/builders/model_builder.h @@ -38,7 +38,8 @@ class ModelBuilder { const emscripten::val& GetOpSupportLimits() const { return wnn_limits_; } void AddOperand(const std::string& name, const emscripten::val& operand); - const emscripten::val& GetZeroConstant(const int32_t& data_type); + const emscripten::val& GetZeroConstant( + const int32_t& data_type, const std::vector& shape = {}); // Use the buffers to persist WebNN allocated data like transposed weight. // It ensures the validity during inference session. std::vector> mem_persist_buffers_;