mirror of
https://github.com/saymrwulf/onnxruntime.git
synced 2026-07-29 20:14:01 +00:00
[WebNN EP] Align QDQ ops with latest Chromium implementation (#22180)
- Pass inputs to WebNN directly, WebNN will handle the broadcasting - If `zero_point` is not provided, make a WebNN Constant with 0 values and same shape as `scale` input
This commit is contained in:
parent
74adfc2099
commit
57668339e4
3 changed files with 38 additions and 67 deletions
|
|
@ -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<int32_t>(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<int32_t> target_shape{static_cast<int>(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<emscripten::val>("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<emscripten::val>("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<void>("push", scale);
|
||||
concat_zero_point_inputs.call<void>("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<emscripten::val>("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<emscripten::val>(
|
||||
"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<emscripten::val>(webnn_op_type.c_str(), input, scale, zero_point, options);
|
||||
emscripten::val output =
|
||||
model_builder.GetBuilder().call<emscripten::val>(webnn_op_type.c_str(), input, scale, zero_point, options);
|
||||
|
||||
model_builder.AddOperand(output_defs[0]->Name(), std::move(output));
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include "core/providers/common.h"
|
||||
#include "core/providers/shared/utils/utils.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
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<uint32_t>& 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;
|
||||
|
|
|
|||
|
|
@ -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<uint32_t>& shape = {});
|
||||
// Use the buffers to persist WebNN allocated data like transposed weight.
|
||||
// It ensures the validity during inference session.
|
||||
std::vector<std::unique_ptr<uint8_t[]>> mem_persist_buffers_;
|
||||
|
|
|
|||
Loading…
Reference in a new issue