[WebNN EP] Merge support for segment anything into main branch (#16208)

We implemented a number of new ops and data types to support running
segment anything model on Chromium WebNN DML backend (POC) in a forked
branch https://github.com/honry/onnxruntime/tree/stable-diffusion

In this PR, we migrate the changes in the forked branch to main branch,
includes:
 - 22 new ops
- New tensor data types: bool, int32, uint32, uint64, int64, float16 (As
JavaScript hasn't shipped Float16Array, we use Uint16Array as a
workaound)
 - Handle empty input tensors and duplicated outputs
 - Fixed some nits
This commit is contained in:
Wanming Lin 2023-06-07 09:56:37 -07:00 committed by GitHub
parent 05bea0d3c3
commit a8c2f24ae0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 2130 additions and 187 deletions

View file

@ -262,7 +262,7 @@ else()
endif()
if (onnxruntime_USE_WEBNN)
set_property(TARGET onnxruntime_webassembly APPEND_STRING PROPERTY LINK_FLAGS " --bind")
set_property(TARGET onnxruntime_webassembly APPEND_STRING PROPERTY LINK_FLAGS " --bind -sWASM_BIGINT")
endif()
# Set link flag to enable exceptions support, this will override default disabling exception throwing behavior when disable exceptions.

View file

@ -17,6 +17,7 @@ const NUMERIC_TENSOR_TYPE_TO_TYPEDARRAY_MAP = new Map<string, SupportedTypedArra
['uint8', Uint8Array],
['int8', Int8Array],
['uint16', Uint16Array],
['float16', Uint16Array],
['int16', Int16Array],
['int32', Int32Array],
['bool', Uint8Array],
@ -112,11 +113,18 @@ export class Tensor implements TensorInterface {
throw new TypeError(`Unsupported tensor type: ${arg0}.`);
}
if (Array.isArray(arg1)) {
// use 'as any' here because TypeScript's check on type of 'SupportedTypedArrayConstructors.from()' produces
// incorrect results.
// 'typedArrayConstructor' should be one of the typed array prototype objects.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data = (typedArrayConstructor as any).from(arg1);
if (arg0 === 'float16') {
// Throw error here because when user try to use number array as data,
// e.g. new Tensor('float16', [1, 2, 3, 4], dims)), it will actually call
// Uint16Array.from(arg1) which generates wrong data.
throw new TypeError(`Unsupported tensor type: ${arg0}.`);
} else {
// use 'as any' here because TypeScript's check on type of 'SupportedTypedArrayConstructors.from()' produces
// incorrect results.
// 'typedArrayConstructor' should be one of the typed array prototype objects.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data = (typedArrayConstructor as any).from(arg1);
}
} else if (arg1 instanceof typedArrayConstructor) {
data = arg1;
} else {

View file

@ -35,7 +35,7 @@ export declare namespace Tensor {
int64: BigInt64Array;
string: string[];
bool: Uint8Array;
float16: never; // hold on using Uint16Array before we have a concrete solution for float 16
float16: Uint16Array; // Keep using Uint16Array until we have a concrete solution for float 16.
float64: Float64Array;
uint32: Uint32Array;
uint64: BigUint64Array;
@ -54,7 +54,7 @@ export declare namespace Tensor {
int64: bigint;
string: string;
bool: boolean;
float16: never; // hold on before we have a concret solution for float 16
float16: number; // Keep using Uint16Array until we have a concrete solution for float 16.
float64: number;
uint32: number;
uint64: bigint;

View file

@ -45,6 +45,8 @@ export const tensorDataTypeStringToEnum = (type: string): DataType => {
return DataType.int32;
case 'uint32':
return DataType.uint32;
case 'float16':
return DataType.float16;
case 'float32':
return DataType.float;
case 'float64':
@ -80,6 +82,8 @@ export const tensorDataTypeEnumToString = (typeProto: DataType): Tensor.Type =>
return 'int32';
case DataType.uint32:
return 'uint32';
case DataType.float16:
return 'float16';
case DataType.float:
return 'float32';
case DataType.double:
@ -110,6 +114,8 @@ export const tensorTypeToTypedArrayConstructor = (type: Tensor.Type): Float32Arr
Int8ArrayConstructor|Uint16ArrayConstructor|Int16ArrayConstructor|Int32ArrayConstructor|BigInt64ArrayConstructor|
Uint8ArrayConstructor|Float64ArrayConstructor|Uint32ArrayConstructor|BigUint64ArrayConstructor => {
switch (type) {
case 'float16':
return Uint16Array;
case 'float32':
return Float32Array;
case 'uint8':

View file

@ -27,11 +27,12 @@ bool GetShape(const NodeArg& node_arg, std::vector<int64_t>& shape, const loggin
return true;
}
bool IsNodeSupported(const Node& node, const GraphViewer& graph_viewer, const logging::Logger& logger) {
bool IsNodeSupported(const Node& node, const GraphViewer& graph_viewer,
const WebnnDeviceType device_type, const logging::Logger& logger) {
const auto& op_builders = GetOpBuilders();
if (Contains(op_builders, node.OpType())) {
const auto* op_builder = op_builders.at(node.OpType());
return op_builder->IsOpSupported(graph_viewer.GetAllInitializedTensors(), node, logger);
return op_builder->IsOpSupported(graph_viewer.GetAllInitializedTensors(), node, device_type, logger);
} else {
return false;
}
@ -40,6 +41,10 @@ bool IsNodeSupported(const Node& node, const GraphViewer& graph_viewer, const lo
bool IsInputSupported(const NodeArg& input, const std::string& parent_name, const logging::Logger& logger) {
const auto& input_name = input.Name();
const auto* shape_proto = input.Shape();
// Optional tensors can be indicated by an empty name, just ignore it.
if (input_name.empty()) {
return true;
}
// We do not support input with no shape.
if (!shape_proto) {
LOGS(logger, VERBOSE) << "Input [" << input_name << "] of [" << parent_name
@ -59,6 +64,7 @@ bool IsInputSupported(const NodeArg& input, const std::string& parent_name, cons
std::vector<std::vector<NodeIndex>> GetSupportedNodes(const GraphViewer& graph_viewer,
const emscripten::val& wnn_builder_,
const WebnnDeviceType device_type,
const logging::Logger& logger) {
std::vector<std::vector<size_t>> supported_node_groups;
@ -78,7 +84,7 @@ std::vector<std::vector<NodeIndex>> GetSupportedNodes(const GraphViewer& graph_v
// Firstly check if platform supports the WebNN op.
if (CheckSingleOp(node->OpType(), wnn_builder_)) {
LOGS(logger, VERBOSE) << "Operator type: [" << node->OpType() << "] is supported by browser";
supported = IsNodeSupported(*node, graph_viewer, logger);
supported = IsNodeSupported(*node, graph_viewer, device_type, logger);
}
LOGS(logger, VERBOSE) << "Operator type: [" << node->OpType()
@ -103,5 +109,35 @@ std::vector<std::vector<NodeIndex>> GetSupportedNodes(const GraphViewer& graph_v
return supported_node_groups;
}
bool IsSupportedDataType(const int32_t data_type, const WebnnDeviceType device_type) {
// Current data type implementation status of WebNN is inconsistent along with different backends,
// The XNNPack backend supports only FP32, while the DML backend POC supports more.
if (device_type == WebnnDeviceType::CPU) {
return std::find(supported_cpu_data_types.begin(), supported_cpu_data_types.end(), data_type) !=
supported_cpu_data_types.end();
} else {
return std::find(supported_gpu_data_types.begin(), supported_gpu_data_types.end(), data_type) !=
supported_gpu_data_types.end();
}
}
bool IsValidMultidirectionalBroadcast(std::vector<int64_t>& shape_a,
std::vector<int64_t>& shape_b,
const logging::Logger& logger) {
int64_t size_a = shape_a.size();
int64_t size_b = shape_b.size();
int64_t smaller_size = std::min(size_a, size_b);
for (int64_t i = 0; i < smaller_size; i++) {
// right alignment
int64_t axis_a = size_a - i - 1;
int64_t axis_b = size_b - i - 1;
// Broadcastable tensors must either have each dimension the same size or equal to one.
if (shape_a[axis_a] != shape_b[axis_b] && shape_a[axis_a] != 1 && shape_b[axis_b] != 1) {
return false;
}
}
return true;
}
} // namespace webnn
} // namespace onnxruntime

View file

@ -7,7 +7,9 @@
#include <core/common/status.h>
#include "core/common/inlined_containers.h"
#include <core/graph/basic_types.h>
#include "core/optimizer/initializer.h"
#include "core/providers/common.h"
#include "core/providers/shared/utils/utils.h"
#include <emscripten.h>
#include <emscripten/val.h>
@ -23,38 +25,138 @@ class Logger;
namespace webnn {
enum class WebnnDeviceType {
CPU,
GPU,
};
bool GetShape(const NodeArg& node_arg, std::vector<int64_t>& shape, const logging::Logger& logger);
template <typename T>
std::string GetShapeString(std::vector<T>& shape) {
std::stringstream shape_info;
shape_info << "[";
for (size_t i = 0; i < shape.size(); i++) {
if (i != 0) {
shape_info << ", ";
}
shape_info << shape[i];
}
shape_info << "]";
return shape_info.str();
}
template <typename T>
bool ReadIntArrayFrom1DTensor(const onnx::TensorProto& tensor, std::vector<T>& array, const logging::Logger& logger) {
std::vector<uint8_t> unpacked_tensor;
auto status = onnxruntime::utils::UnpackInitializerData(tensor, unpacked_tensor);
if (!status.IsOK()) {
LOGS(logger, ERROR) << "Error while unpacking shape: " << status.ErrorMessage();
return false;
}
const auto& dims = tensor.dims();
if (dims.size() != 1) {
LOGS(logger, VERBOSE) << "The tensor must be 1D.";
return false;
}
int64_t rank = dims[0];
switch (tensor.data_type()) {
case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64: {
const int64_t* array_data = reinterpret_cast<const int64_t*>(unpacked_tensor.data());
if constexpr (std::is_same<T, int64_t>::value) {
array.assign(array_data, array_data + rank);
} else {
std::transform(array_data, array_data + rank,
std::back_inserter(array),
[](int64_t dim) -> T { return SafeInt<T>(dim); });
};
break;
}
case ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32: {
const int32_t* array_data = reinterpret_cast<const int32_t*>(unpacked_tensor.data());
array.assign(array_data, array_data + rank);
break;
}
default:
return false;
}
return true;
}
bool IsInputSupported(const NodeArg& node_arg, const std::string& parent_name, const logging::Logger& logger);
// Get a list of groups of supported nodes, each group represents a subgraph supported by WebNN EP.
std::vector<std::vector<NodeIndex>> GetSupportedNodes(const GraphViewer& graph_viewer,
const emscripten::val& wnn_builder_,
const WebnnDeviceType device_type,
const logging::Logger& logger);
static const InlinedHashMap<std::string, std::string> op_map = {
{"ArgMax", "argMax"},
{"ArgMin", "argMin"},
{"Add", "add"},
{"Sub", "sub"},
{"Mul", "mul"},
{"Div", "div"},
{"Pow", "pow"},
{"Cos", "cos"},
{"Equal", "equal"},
{"Erf", "erf"},
{"Not", "logicalNot"},
{"Floor", "floor"},
{"Flatten", "flattenTo2d"},
{"Sin", "sin"},
{"Sqrt", "sqrt"},
{"Relu", "relu"},
{"LeakyRelu", "leakyRelu"},
{"Sigmoid", "sigmoid"},
{"Slice", "slice"},
{"Softmax", "softmax"},
{"Cast", "cast"},
{"Clip", "clamp"},
{"Conv", "conv2d"},
{"ConvTranspose", "convTranspose2d"},
{"Concat", "concat"},
{"Expand", "expand"},
{"Gather", "gather"},
{"Gemm", "gemm"},
{"MatMul", "matmul"},
{"GlobalAveragePool", "averagePool2d"},
{"GlobalMaxPool", "maxPool2d"},
{"AveragePool", "averagePool2d"},
{"LayerNormalization", "meanVarianceNormalization"},
{"MaxPool", "maxPool2d"},
{"ReduceMax", "reduceMax"},
{"ReduceMean", "reduceMean"},
{"Reshape", "reshape"},
{"Resize", "resample2d"},
{"Transpose", "transpose"}};
{"Split", "split"},
{"Transpose", "transpose"},
{"Unsqueeze", "unsqueeze"},
};
inline bool CheckSingleOp(const std::string& op_type, const emscripten::val& wnn_builder_) {
return op_map.find(op_type) != op_map.end() && wnn_builder_[op_map.find(op_type)->second].as<bool>();
}
constexpr std::array<ONNX_NAMESPACE::TensorProto_DataType, 1> supported_cpu_data_types = {
ONNX_NAMESPACE::TensorProto_DataType_FLOAT,
};
constexpr std::array<ONNX_NAMESPACE::TensorProto_DataType, 7> supported_gpu_data_types = {
ONNX_NAMESPACE::TensorProto_DataType_BOOL,
ONNX_NAMESPACE::TensorProto_DataType_FLOAT16,
ONNX_NAMESPACE::TensorProto_DataType_FLOAT,
ONNX_NAMESPACE::TensorProto_DataType_INT32,
ONNX_NAMESPACE::TensorProto_DataType_INT64,
ONNX_NAMESPACE::TensorProto_DataType_UINT32,
ONNX_NAMESPACE::TensorProto_DataType_UINT64,
};
bool IsSupportedDataType(const int32_t data_type, const WebnnDeviceType device_type);
bool IsValidMultidirectionalBroadcast(std::vector<int64_t>& shape_a,
std::vector<int64_t>& shape_b,
const logging::Logger& logger);
} // namespace webnn
} // namespace onnxruntime

View file

@ -0,0 +1,95 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Intel Corporation. All rights reserved.
// Licensed under the MIT License.
#include "core/providers/common.h"
#include "core/providers/shared/utils/utils.h"
#include "core/providers/webnn/builders/helper.h"
#include "core/providers/webnn/builders/model_builder.h"
#include "core/providers/webnn/builders/op_builder_factory.h"
#include "base_op_builder.h"
namespace onnxruntime {
namespace webnn {
class ArgMaxMinOpBuilder : public BaseOpBuilder {
// Add operator related.
private:
Status AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node,
const logging::Logger& logger) const override ORT_MUST_USE_RESULT;
// Operator support related.
bool IsOpSupportedImpl(const InitializedTensorSet& initializers, const Node& node,
WebnnDeviceType /* device_type */, const logging::Logger& logger) const override;
};
// Add operator related.
Status ArgMaxMinOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder,
const Node& node,
const logging::Logger& logger) const {
const auto& input_defs = node.InputDefs();
emscripten::val input = model_builder.GetOperand(input_defs[0]->Name());
std::vector<int64_t> input_shape;
ORT_RETURN_IF_NOT(GetShape(*input_defs[0], input_shape, logger), "Cannot get shape");
const auto input_rank = input_shape.size();
NodeAttrHelper helper(node);
int64_t axis = helper.Get("axis", 0);
const auto keep_dims = helper.Get("keepdims", 1);
const auto select_last_index = helper.Get("select_last_index", 0);
axis = HandleNegativeAxis(axis, input_rank);
emscripten::val options = emscripten::val::object();
options.set("axis", static_cast<int32_t>(axis));
options.set("keepDimensions", keep_dims == 1);
options.set("selectLastIndex", select_last_index == 1);
emscripten::val output = emscripten::val::object();
const auto& op_type = node.OpType();
if (op_type == "ArgMax") {
output = model_builder.GetBuilder().call<emscripten::val>("argMax", input, options);
} else if (op_type == "ArgMin") {
output = model_builder.GetBuilder().call<emscripten::val>("argMin", input, options);
} else {
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT, "ArgMaxMinOpBuilder, unknown op: ", op_type);
}
model_builder.AddOperand(node.OutputDefs()[0]->Name(), std::move(output));
return Status::OK();
}
// Operator support related.
bool ArgMaxMinOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& /* initializers */,
const Node& node,
WebnnDeviceType /* device_type */,
const logging::Logger& logger) const {
const auto& input_defs = node.InputDefs();
std::vector<int64_t> input_shape;
if (!GetShape(*input_defs[0], input_shape, logger))
return false;
return true;
}
void CreateArgMaxMinOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations) {
if (op_registrations.op_builder_map.find(op_type) != op_registrations.op_builder_map.cend())
return;
static std::vector<std::string> op_types =
{
"ArgMax",
"ArgMin",
};
op_registrations.builders.push_back(std::make_unique<ArgMaxMinOpBuilder>());
for (const auto& type : op_types) {
op_registrations.op_builder_map.emplace(type, op_registrations.builders.back().get());
}
}
} // namespace webnn
} // namespace onnxruntime

View file

@ -38,7 +38,7 @@ bool HasExternalInitializer(const InitializedTensorSet& initializers, const Node
Status BaseOpBuilder::AddToModelBuilder(ModelBuilder& model_builder, const Node& node,
const logging::Logger& logger) const {
ORT_RETURN_IF_NOT(
IsOpSupported(model_builder.GetInitializerTensors(), node, logger),
IsOpSupported(model_builder.GetInitializerTensors(), node, model_builder.GetWebnnDeviceType(), logger),
"Unsupported operator ",
node.OpType());
ORT_RETURN_IF_ERROR(AddToModelBuilderImpl(model_builder, node, logger));
@ -50,8 +50,8 @@ Status BaseOpBuilder::AddToModelBuilder(ModelBuilder& model_builder, const Node&
// Operator support related.
bool BaseOpBuilder::IsOpSupported(const InitializedTensorSet& initializers, const Node& node,
const logging::Logger& logger) const {
if (!HasSupportedInputs(node, logger))
const WebnnDeviceType device_type, const logging::Logger& logger) const {
if (!HasSupportedInputs(node, device_type, logger))
return false;
// We do not support external initializers for now.
@ -61,10 +61,11 @@ bool BaseOpBuilder::IsOpSupported(const InitializedTensorSet& initializers, cons
if (!HasSupportedOpSet(node, logger))
return false;
return IsOpSupportedImpl(initializers, node, logger);
return IsOpSupportedImpl(initializers, node, device_type, logger);
}
bool BaseOpBuilder::HasSupportedInputs(const Node& node, const logging::Logger& logger) const {
bool BaseOpBuilder::HasSupportedInputs(const Node& node, const WebnnDeviceType device_type,
const logging::Logger& logger) const {
const auto node_name = MakeString("Node [", node.Name(), "] type [", node.OpType(), "]");
for (const auto* input : node.InputDefs()) {
if (!IsInputSupported(*input, node_name, logger)) {
@ -72,10 +73,12 @@ bool BaseOpBuilder::HasSupportedInputs(const Node& node, const logging::Logger&
}
}
return HasSupportedInputsImpl(node, logger);
return HasSupportedInputsImpl(node, device_type, logger);
}
bool BaseOpBuilder::HasSupportedInputsImpl(const Node& node, const logging::Logger& logger) const {
bool BaseOpBuilder::HasSupportedInputsImpl(const Node& node,
const WebnnDeviceType device_type,
const logging::Logger& logger) const {
// We only check the type of input 0 by default, specific op builder can override this.
const auto& input = *node.InputDefs()[0];
@ -83,7 +86,7 @@ bool BaseOpBuilder::HasSupportedInputsImpl(const Node& node, const logging::Logg
if (!GetType(input, input_type, logger))
return false;
if (input_type != ONNX_NAMESPACE::TensorProto_DataType_FLOAT) {
if (!IsSupportedDataType(input_type, device_type)) {
LOGS(logger, VERBOSE) << "[" << node.OpType()
<< "] Input type: [" << input_type
<< "] is not supported for now";

View file

@ -28,22 +28,23 @@ class BaseOpBuilder : public IOpBuilder {
// Operator support related.
public:
bool IsOpSupported(const InitializedTensorSet& initializers, const Node& node,
const logging::Logger& logger) const override;
const WebnnDeviceType device_type, const logging::Logger& logger) const override;
protected:
virtual bool IsOpSupportedImpl(const InitializedTensorSet& /* initializers */, const Node& /* node */,
const logging::Logger& /* logger */) const {
const WebnnDeviceType /* device_type */, const logging::Logger& /* logger */) const {
return true;
}
virtual bool HasSupportedInputsImpl(const Node& node, const logging::Logger& logger) const;
virtual bool HasSupportedInputsImpl(const Node& node, const WebnnDeviceType device_type,
const logging::Logger& logger) const;
virtual int GetMinSupportedOpSet(const Node& /* node */) const { return 1; }
virtual int GetMaxSupportedOpSet(const Node& /* node */) const { return 19; }
private:
bool HasSupportedOpSet(const Node& node, const logging::Logger& logger) const;
bool HasSupportedInputs(const Node& node, const logging::Logger& logger) const;
bool HasSupportedInputs(const Node& node, const WebnnDeviceType device_type, const logging::Logger& logger) const;
};
} // namespace webnn

View file

@ -41,6 +41,8 @@ Status BinaryOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const
output = model_builder.GetBuilder().call<emscripten::val>("mul", input0, input1);
} else if (op_type == "Div") {
output = model_builder.GetBuilder().call<emscripten::val>("div", input0, input1);
} else if (op_type == "Pow") {
output = model_builder.GetBuilder().call<emscripten::val>("pow", input0, input1);
} else {
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
"BinaryOpBuilder::AddToModelBuilderImpl, unknown op: ", op_type);
@ -53,7 +55,7 @@ Status BinaryOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const
// Operator support related.
int BinaryOpBuilder::GetMinSupportedOpSet(const Node& /* node */) const {
// Add/Sub/Mul/Div opset 6- has broadcast attributes we do not support now.
// Add/Sub/Mul/Div/Pow opset 6- has broadcast attributes we do not support now.
return 7;
}
@ -67,6 +69,7 @@ void CreateBinaryOpBuilder(const std::string& op_type, OpBuilderRegistrations& o
"Sub",
"Mul",
"Div",
"Pow",
};
op_registrations.builders.push_back(std::make_unique<BinaryOpBuilder>());

View file

@ -0,0 +1,105 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Intel Corporation. All rights reserved.
// Licensed under the MIT License.
#include "core/providers/common.h"
#include "core/providers/webnn/builders/helper.h"
#include "core/providers/webnn/builders/model_builder.h"
#include "core/providers/webnn/builders/op_builder_factory.h"
#include "core/providers/shared/utils/utils.h"
#include "base_op_builder.h"
namespace onnxruntime {
namespace webnn {
class CastOpBuilder : public BaseOpBuilder {
// Add operator related.
private:
Status AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node,
const logging::Logger& logger) const override ORT_MUST_USE_RESULT;
// Operator support related.
private:
bool IsOpSupportedImpl(const InitializedTensorSet& /* initializers */, const Node& node,
const WebnnDeviceType device_type, const logging::Logger& logger) const override;
int GetMinSupportedOpSet(const Node& node) const override;
};
// Add operator related.
Status CastOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder,
const Node& node,
const logging::Logger& logger) const {
const auto& input_name = node.InputDefs()[0]->Name();
emscripten::val input = model_builder.GetOperand(input_name);
NodeAttrHelper helper(node);
// We already checked the "to" type in IsOpSupportedImpl.
const auto to_type = helper.Get("to", ONNX_NAMESPACE::TensorProto_DataType_FLOAT);
std::string operand_type;
switch (to_type) {
case ONNX_NAMESPACE::TensorProto_DataType_BOOL:
operand_type = "uint8";
break;
case ONNX_NAMESPACE::TensorProto_DataType_FLOAT16:
operand_type = "float16";
break;
case ONNX_NAMESPACE::TensorProto_DataType_FLOAT:
operand_type = "float32";
break;
case ONNX_NAMESPACE::TensorProto_DataType_INT32:
operand_type = "int32";
break;
case ONNX_NAMESPACE::TensorProto_DataType_INT64:
operand_type = "int64";
break;
case ONNX_NAMESPACE::TensorProto_DataType_UINT32:
operand_type = "uint32";
break;
case ONNX_NAMESPACE::TensorProto_DataType_UINT64:
operand_type = "uint64";
break;
default:
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
"The Cast node has unsupported 'to' type, name: ",
node.Name(), " type: ", to_type);
}
emscripten::val output =
model_builder.GetBuilder().call<emscripten::val>("cast", input, emscripten::val(operand_type));
model_builder.AddOperand(node.OutputDefs()[0]->Name(), std::move(output));
return Status::OK();
}
// Operator support related.
int CastOpBuilder::GetMinSupportedOpSet(const Node& /* node */) const {
// Since opset 6, Cast uses attribute "to" as int type.
return 6;
}
bool CastOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& /* initializers */,
const Node& node,
const WebnnDeviceType device_type,
const logging::Logger& logger) const {
NodeAttrHelper helper(node);
// Check cast output type.
const auto to_type = helper.Get("to", ONNX_NAMESPACE::TensorProto_DataType_UNDEFINED);
if (!IsSupportedDataType(to_type, device_type)) {
LOGS(logger, VERBOSE) << "Invalid cast to type " << to_type << ".";
return false;
}
return true;
}
void CreateCastOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations) {
op_registrations.builders.push_back(std::make_unique<CastOpBuilder>());
op_registrations.op_builder_map.emplace(op_type, op_registrations.builders.back().get());
}
} // namespace webnn
} // namespace onnxruntime

View file

@ -24,7 +24,7 @@ class ClipOpBuilder : public BaseOpBuilder {
// Operator support related.
private:
bool IsOpSupportedImpl(const InitializedTensorSet& initializers, const Node& node,
const logging::Logger& logger) const override;
const WebnnDeviceType /* device_type */, const logging::Logger& logger) const override;
};
// Add operator related.
@ -66,7 +66,9 @@ Status ClipOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder,
// Operator support related.
bool ClipOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers, const Node& node,
bool ClipOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers,
const Node& node,
const WebnnDeviceType /* device_type */,
const logging::Logger& logger) const {
float min, max;
return GetClipMinMax(initializers, node, min, max, logger);

View file

@ -27,8 +27,8 @@ class ConvOpBuilder : public BaseOpBuilder {
// Operator support related.
private:
bool IsOpSupportedImpl(const InitializedTensorSet& /* initializers */, const Node& /* node */,
const logging::Logger& /* logger */) const override;
bool IsOpSupportedImpl(const InitializedTensorSet& initializers, const Node& node,
const WebnnDeviceType /* device_type */, const logging::Logger& logger) const override;
};
void ConvOpBuilder::AddInitializersToSkip(ModelBuilder& model_builder, const Node& node) const {
@ -96,7 +96,7 @@ Status AddInitializerInNewLayout(ModelBuilder& model_builder,
bool is_conv) {
const auto& tensor = *model_builder.GetInitializerTensors().at(name);
auto data_type = tensor.data_type();
if (data_type != ONNX_NAMESPACE::TensorProto_DataType_FLOAT) {
if (!IsSupportedDataType(data_type, model_builder.GetWebnnDeviceType())) {
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
"The initializer of graph has unsupported type, name: ",
tensor.name(), " type: ", data_type);
@ -123,7 +123,29 @@ Status AddInitializerInNewLayout(ModelBuilder& model_builder,
SafeInt<size_t> num_elements = SafeInt<size_t>(Product(dest_shape));
size_t element_size = 4;
size_t element_size{0};
switch (data_type) {
case ONNX_NAMESPACE::TensorProto_DataType_FLOAT16:
element_size = sizeof(uint16_t);
break;
case ONNX_NAMESPACE::TensorProto_DataType_FLOAT:
element_size = sizeof(float);
break;
case ONNX_NAMESPACE::TensorProto_DataType_INT32:
element_size = sizeof(int32_t);
break;
case ONNX_NAMESPACE::TensorProto_DataType_INT64:
element_size = sizeof(int64_t);
break;
case ONNX_NAMESPACE::TensorProto_DataType_UINT32:
element_size = sizeof(uint32_t);
break;
case ONNX_NAMESPACE::TensorProto_DataType_UINT64:
element_size = sizeof(uint64_t);
break;
default:
break;
}
std::unique_ptr<uint8_t[]> buffer_holder(new uint8_t[element_size * num_elements]);
uint8_t* buffer = buffer_holder.get();
@ -157,7 +179,7 @@ Status AddInitializerInNewLayout(ModelBuilder& model_builder,
}
}
ORT_RETURN_IF_ERROR(model_builder.AddOperandFromPersistMemoryBuffer(name, buffer, num_elements * element_size,
dest_shape, 4));
dest_shape, data_type));
return Status::OK();
}
@ -175,7 +197,6 @@ Status ConvOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const N
const auto dilations = helper.Get("dilations", std::vector<int32_t>{1, 1});
auto pads = helper.Get("pads", std::vector<int32_t>{0, 0, 0, 0});
const auto& weight = input_defs[1]->Name();
if (op_type == "Conv") {
emscripten::val options = emscripten::val::object();
ORT_RETURN_IF_ERROR(SetConvBaseOptions(model_builder, node, options, strides, dilations, pads, logger));
@ -193,6 +214,7 @@ Status ConvOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const N
}
}
emscripten::val filter = model_builder.GetOperand(input_defs[1]->Name());
output = model_builder.GetBuilder().call<emscripten::val>("conv2d", input, filter, options);
} else {
emscripten::val options = emscripten::val::object();
@ -242,7 +264,6 @@ Status ConvOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const N
options.set("outputPadding", emscripten::val::array(output_padding));
}
emscripten::val filter = model_builder.GetOperand(input_defs[1]->Name());
output = model_builder.GetBuilder().call<emscripten::val>("convTranspose2d", input, filter, options);
}
@ -252,7 +273,9 @@ Status ConvOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const N
// Operator support related.
bool ConvOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers, const Node& node,
bool ConvOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers,
const Node& node,
const WebnnDeviceType /* device_type */,
const logging::Logger& logger) const {
const auto& name = node.Name();
const auto& op_type = node.OpType();

View file

@ -0,0 +1,116 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Intel Corporation. All rights reserved.
// Licensed under the MIT License.
#include "core/common/safeint.h"
#include "core/framework/tensorprotoutils.h"
#include "core/optimizer/initializer.h"
#include "core/providers/common.h"
#include "core/providers/cpu/tensor/reshape_helper.h"
#include "core/providers/shared/utils/utils.h"
#include "core/providers/webnn/builders/helper.h"
#include "core/providers/webnn/builders/model_builder.h"
#include "core/providers/webnn/builders/op_builder_factory.h"
#include "base_op_builder.h"
namespace onnxruntime {
namespace webnn {
class ExpandOpBuilder : public BaseOpBuilder {
// Add operator related.
public:
void AddInitializersToSkip(ModelBuilder& model_builder, const Node& node) const override;
private:
Status AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node,
const logging::Logger& logger) const override ORT_MUST_USE_RESULT;
// Operator support related.
private:
bool IsOpSupportedImpl(const InitializedTensorSet& initializers, const Node& node,
const WebnnDeviceType /* device_type */, const logging::Logger& logger) const override;
};
// Add operator related.
void ExpandOpBuilder::AddInitializersToSkip(ModelBuilder& model_builder, const Node& node) const {
model_builder.AddInitializerToSkip(node.InputDefs()[1]->Name());
}
Status ExpandOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder,
const Node& node,
const logging::Logger& logger) const {
const auto& input_defs = node.InputDefs();
const auto& initializers(model_builder.GetInitializerTensors());
const auto& shape_tensor = *initializers.at(input_defs[1]->Name());
std::vector<int32_t> new_shape;
ORT_RETURN_IF_NOT(ReadIntArrayFrom1DTensor(shape_tensor, new_shape, logger), "Cannot get shape.");
emscripten::val input = model_builder.GetOperand(input_defs[0]->Name());
std::vector<int64_t> input_shape;
ORT_RETURN_IF_NOT(GetShape(*input_defs[0], input_shape, logger), "Cannot get input's shape.");
if (new_shape.size() < input_shape.size()) {
// Enlarge new shape to input.rank, right aligned with leading ones
new_shape.insert(new_shape.begin(), input_shape.size() - new_shape.size(), 1);
}
emscripten::val output =
model_builder.GetBuilder().call<emscripten::val>("expand",
input, emscripten::val::array(new_shape));
model_builder.AddOperand(node.OutputDefs()[0]->Name(), std::move(output));
return Status::OK();
}
// Operator support related.
bool ExpandOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers,
const Node& node,
const WebnnDeviceType /* device_type */,
const logging::Logger& logger) const {
const auto& input_defs = node.InputDefs();
const auto& shape_name = input_defs[1]->Name();
if (!Contains(initializers, shape_name)) {
LOGS(logger, VERBOSE) << "The shape must be a constant initializer.";
return false;
}
std::vector<int64_t> new_shape;
const auto& shape_tensor = *initializers.at(shape_name);
if (shape_tensor.data_type() != ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64) {
LOGS(logger, VERBOSE) << "The type of tensor's element data must be INT64.";
return false;
}
if (!ReadIntArrayFrom1DTensor(shape_tensor, new_shape, logger)) {
LOGS(logger, VERBOSE) << "Cannot get shape.";
return false;
}
std::vector<int64_t> input_shape;
if (!GetShape(*input_defs[0], input_shape, logger)) {
LOGS(logger, VERBOSE) << "Cannot get input's shape.";
return false;
}
if (input_shape.empty()) {
LOGS(logger, VERBOSE) << "Expand does not support empty input's shape.";
return false;
}
if (new_shape.size() > input_shape.size()) {
LOGS(logger, VERBOSE) << "The size of shape must be less than or equal to the rank of input.";
}
if (!IsValidMultidirectionalBroadcast(input_shape, new_shape, logger)) {
LOGS(logger, VERBOSE) << "The input cannot expand to shape " << GetShapeString(new_shape);
return false;
}
return true;
}
void CreateExpandOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations) {
op_registrations.builders.push_back(std::make_unique<ExpandOpBuilder>());
op_registrations.op_builder_map.emplace(op_type, op_registrations.builders.back().get());
}
} // namespace webnn
} // namespace onnxruntime

View file

@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Intel Corporation. All rights reserved.
// Licensed under the MIT License.
#include "core/common/safeint.h"
#include "core/providers/common.h"
#include "core/providers/shared/utils/utils.h"
#include "core/providers/webnn/builders/helper.h"
#include "core/providers/webnn/builders/model_builder.h"
#include "core/providers/webnn/builders/op_builder_factory.h"
#include "base_op_builder.h"
namespace onnxruntime {
namespace webnn {
class FlattenOpBuilder : public BaseOpBuilder {
// Add operator related.
private:
Status AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node,
const logging::Logger& logger) const override ORT_MUST_USE_RESULT;
};
// Add operator related.
Status FlattenOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder,
const Node& node,
const logging::Logger& logger) const {
const auto& input_defs = node.InputDefs();
std::vector<int64_t> input_shape;
ORT_RETURN_IF(input_defs.size() < 1, "Flatten has no input tensor");
if (!GetShape(*input_defs[0], input_shape, logger)) {
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
"FlattenOpBuilder::AddToModelBuilderImpl, cannot get input shape");
}
int64_t rank = input_shape.size();
NodeAttrHelper helper(node);
int64_t axis = helper.Get("axis", 1);
ORT_ENFORCE(axis >= -rank && axis <= rank, "axis ", axis,
" is not in valid range [-", rank, ",", rank, "]");
if (axis < 0) {
axis += rank;
}
emscripten::val inputs = model_builder.GetOperand(input_defs[0]->Name());
emscripten::val output = model_builder.GetBuilder().call<emscripten::val>("flattenTo2d", inputs,
static_cast<int32_t>(axis));
model_builder.AddOperand(node.OutputDefs()[0]->Name(), std::move(output));
return Status::OK();
}
void CreateFlattenOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations) {
op_registrations.builders.push_back(std::make_unique<FlattenOpBuilder>());
op_registrations.op_builder_map.emplace(op_type, op_registrations.builders.back().get());
}
} // namespace webnn
} // namespace onnxruntime

View file

@ -0,0 +1,75 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Intel Corporation. All rights reserved.
// Licensed under the MIT License.
#include "core/providers/common.h"
#include "core/providers/shared/utils/utils.h"
#include "core/providers/webnn/builders/helper.h"
#include "core/providers/webnn/builders/model_builder.h"
#include "core/providers/webnn/builders/op_builder_factory.h"
#include "base_op_builder.h"
namespace onnxruntime {
namespace webnn {
class GatherOpBuilder : public BaseOpBuilder {
// Add operator related.
private:
Status AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node,
const logging::Logger& logger) const override ORT_MUST_USE_RESULT;
// Operator support related.
bool IsOpSupportedImpl(const InitializedTensorSet& /* initializers */, const Node& node,
const WebnnDeviceType /* device_type */, const logging::Logger& logger) const override;
};
// Add operator related.
Status GatherOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder,
const Node& node,
const logging::Logger& logger) const {
const auto& input_defs = node.InputDefs();
std::vector<int64_t> input_shape;
ORT_RETURN_IF_NOT(GetShape(*input_defs[0], input_shape, logger), "Cannot get shape");
const auto rank = input_shape.size();
NodeAttrHelper helper(node);
const uint32_t axis = static_cast<uint32_t>(HandleNegativeAxis(helper.Get("axis", 1), rank));
emscripten::val input = model_builder.GetOperand(input_defs[0]->Name());
emscripten::val indices = model_builder.GetOperand(input_defs[1]->Name());
emscripten::val options = emscripten::val::object();
options.set("axis", axis);
emscripten::val output = model_builder.GetBuilder().call<emscripten::val>("gather", input, indices, options);
model_builder.AddOperand(node.OutputDefs()[0]->Name(), std::move(output));
return Status::OK();
}
// Operator support related.
bool GatherOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& /* initializers */,
const Node& node,
const WebnnDeviceType /* device_type */,
const logging::Logger& logger) const {
const auto& input_defs = node.InputDefs();
std::vector<int64_t> input_shape;
if (!GetShape(*input_defs[0], input_shape, logger))
return false;
const auto rank = input_shape.size();
if (rank < 1) {
LOGS(logger, VERBOSE) << "Gather only supports input shapes >= 1D, but input is "
<< rank << "d shape";
return false;
}
return true;
}
void CreateGatherOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations) {
op_registrations.builders.push_back(std::make_unique<GatherOpBuilder>());
op_registrations.op_builder_map.emplace(op_type, op_registrations.builders.back().get());
}
} // namespace webnn
} // namespace onnxruntime

View file

@ -23,106 +23,117 @@ class GemmOpBuilder : public BaseOpBuilder {
// Operator support related.
private:
bool IsOpSupportedImpl(const InitializedTensorSet& /* initializers */, const Node& /* node */,
const logging::Logger& /* logger */) const override;
bool IsOpSupportedImpl(const InitializedTensorSet& initializers, const Node& node,
const WebnnDeviceType /* device_type */, const logging::Logger& logger) const override;
};
// Add operator related.
Status GemmOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node,
const logging::Logger& /* logger */) const {
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
emscripten::val a = model_builder.GetOperand(node.InputDefs()[a_idx]->Name());
emscripten::val b = model_builder.GetOperand(node.InputDefs()[b_idx]->Name());
emscripten::val options = emscripten::val::object();
NodeAttrHelper helper(node);
const auto transA = helper.Get("transA", 0);
options.set("aTranspose", emscripten::val(transA == 1));
const auto transB = helper.Get("transB", 0);
options.set("bTranspose", emscripten::val(transB == 1));
const auto alpha = helper.Get("alpha", 1.0f);
const auto beta = helper.Get("beta", 1.0f);
options.set("alpha", alpha);
options.set("beta", beta);
emscripten::val output = emscripten::val::object();
if (op_type == "MatMul") {
output = model_builder.GetBuilder().call<emscripten::val>("matmul", a, b);
} else { // Gemm
emscripten::val options = emscripten::val::object();
NodeAttrHelper helper(node);
const auto transA = helper.Get("transA", 0);
options.set("aTranspose", emscripten::val(transA == 1));
const auto transB = helper.Get("transB", 0);
options.set("bTranspose", emscripten::val(transB == 1));
const auto alpha = helper.Get("alpha", 1.0f);
const auto beta = helper.Get("beta", 1.0f);
options.set("alpha", alpha);
options.set("beta", beta);
// Add bias if present.
if (input_defs.size() > 2) {
options.set("c", model_builder.GetOperand(node.InputDefs()[c_idx]->Name()));
// Add bias if present.
if (input_defs.size() > 2) {
options.set("c", model_builder.GetOperand(node.InputDefs()[c_idx]->Name()));
}
output = model_builder.GetBuilder().call<emscripten::val>("gemm", a, b, options);
}
emscripten::val output = model_builder.GetBuilder().call<emscripten::val>("gemm", a, b, options);
model_builder.AddOperand(node.OutputDefs()[0]->Name(), std::move(output));
return Status::OK();
}
// Operator support related.
bool GemmOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers, const Node& node,
bool GemmOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers,
const Node& node,
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<int64_t> 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<int64_t> 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;
}
}
// C of Gemm.
if (input_defs.size() == 3) {
std::vector<int64_t> c_shape;
if (!GetShape(*input_defs[c_idx], c_shape, logger))
return false;
size_t c_dim = c_shape.size();
if (c_dim > 1) {
// TODO: Supports other shape of C.
// Currently WebNN implementation in Chromium only supports 1-D C.
return false;
}
if (c_dim == 0) {
LOGS(logger, VERBOSE) << "C of Gemm is a scalar";
} else {
auto c_size = c_shape[c_dim - 1];
NodeAttrHelper helper(node);
const auto transB = helper.Get("transB", 0);
if (c_size != (transB == 0 ? b_shape[1] : b_shape[0])) {
LOGS(logger, VERBOSE) << "C of Gemm must be a vector of b_shape["
<< (transB == 0 ? "1" : "0") << "]"
<< " b_shape: [" << b_shape[0] << ", " << b_shape[1] << "]"
<< " c_size: " << c_size;
if (op_type == "Gemm") {
std::vector<int64_t> 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<int64_t> 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;
}
}
// C of Gemm.
if (input_defs.size() == 3) {
std::vector<int64_t> c_shape;
if (!GetShape(*input_defs[c_idx], c_shape, logger))
return false;
size_t c_dim = c_shape.size();
if (c_dim > 1) {
// TODO: Supports other shape of C.
// Currently WebNN implementation in Chromium only supports 1-D C.
return false;
}
if (c_dim == 0) {
LOGS(logger, VERBOSE) << "C of Gemm is a scalar";
} else {
auto c_size = c_shape[c_dim - 1];
NodeAttrHelper helper(node);
const auto transB = helper.Get("transB", 0);
if (c_size != (transB == 0 ? b_shape[1] : b_shape[0])) {
LOGS(logger, VERBOSE) << "C of Gemm must be a vector of b_shape["
<< (transB == 0 ? "1" : "0") << "]"
<< " b_shape: [" << b_shape[0] << ", " << b_shape[1] << "]"
<< " c_size: " << c_size;
return false;
}
}
}
}
@ -131,8 +142,19 @@ bool GemmOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers,
}
void CreateGemmOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations) {
if (op_registrations.op_builder_map.find(op_type) != op_registrations.op_builder_map.cend())
return;
static std::vector<std::string> op_types =
{
"Gemm",
"MatMul",
};
op_registrations.builders.push_back(std::make_unique<GemmOpBuilder>());
op_registrations.op_builder_map.emplace(op_type, op_registrations.builders.back().get());
for (const auto& type : op_types) {
op_registrations.op_builder_map.emplace(type, op_registrations.builders.back().get());
}
}
} // namespace webnn
} // namespace onnxruntime

View file

@ -0,0 +1,76 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Intel Corporation. All rights reserved.
// Licensed under the MIT License.
#include <core/providers/common.h>
#include "core/providers/webnn/builders/model_builder.h"
#include "core/providers/webnn/builders/op_builder_factory.h"
#include "core/providers/webnn/builders/helper.h"
#include "base_op_builder.h"
namespace onnxruntime {
namespace webnn {
class LogicalOpBuilder : public BaseOpBuilder {
// Add operator related.
private:
Status AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node,
const logging::Logger& logger) const override ORT_MUST_USE_RESULT;
// Operator support related.
bool IsOpSupportedImpl(const InitializedTensorSet& /* initializers */, const Node& node,
const WebnnDeviceType /* device_type */, const logging::Logger& logger) const override;
};
// Add operator related.
Status LogicalOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node,
const logging::Logger& /* logger */) const {
const auto& op_type = node.OpType();
emscripten::val input0 = model_builder.GetOperand(node.InputDefs()[0]->Name());
emscripten::val input1 = model_builder.GetOperand(node.InputDefs()[1]->Name());
emscripten::val output = emscripten::val::object();
if (op_type == "Equal") {
output = model_builder.GetBuilder().call<emscripten::val>("equal", input0, input1);
} else {
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
"LogicalOpBuilder::AddToModelBuilderImpl, unknown op: ", op_type);
}
model_builder.AddOperand(node.OutputDefs()[0]->Name(), std::move(output));
return Status::OK();
}
void CreateLogicalOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations) {
if (op_registrations.op_builder_map.find(op_type) != op_registrations.op_builder_map.cend())
return;
static std::vector<std::string> op_types =
{
"Equal",
};
op_registrations.builders.push_back(std::make_unique<LogicalOpBuilder>());
for (const auto& type : op_types) {
op_registrations.op_builder_map.emplace(type, op_registrations.builders.back().get());
}
}
bool LogicalOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& /* initializers */,
const Node& node,
const WebnnDeviceType /* device_type */,
const logging::Logger& logger) const {
const auto& name = node.Name();
const auto& op_type = node.OpType();
const auto& input_defs = node.InputDefs();
if (input_defs.size() < 2) {
LOGS(logger, VERBOSE) << op_type << " [" << name << "] requires at least 2 inputs, actual: "
<< input_defs.size();
return false;
}
return true;
}
} // namespace webnn
} // namespace onnxruntime

View file

@ -0,0 +1,153 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Intel Corporation. All rights reserved.
// Licensed under the MIT License.
#include "core/common/safeint.h"
#include "core/optimizer/initializer.h"
#include "core/providers/common.h"
#include "core/providers/shared/utils/utils.h"
#include "core/providers/webnn/builders/helper.h"
#include "core/providers/webnn/builders/model_builder.h"
#include "core/providers/webnn/builders/op_builder_factory.h"
#include "base_op_builder.h"
namespace onnxruntime {
namespace webnn {
class NormalizationOpBuilder : public BaseOpBuilder {
// Add operator related.
private:
Status AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node,
const logging::Logger& logger) const override ORT_MUST_USE_RESULT;
// Operator support related.
private:
bool IsOpSupportedImpl(const InitializedTensorSet& initializers, const Node& node,
const WebnnDeviceType /* device_type */, const logging::Logger& logger) const override;
};
Status NormalizationOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder,
const Node& node,
const logging::Logger& logger) const {
const auto& input_defs = node.InputDefs();
ORT_RETURN_IF_NOT(input_defs.size() >= 2, "LayerNormalization requires at least two inputs.");
emscripten::val input = model_builder.GetOperand(input_defs[0]->Name());
std::vector<int64_t> input_shape;
ORT_RETURN_IF_NOT(GetShape(*input_defs[0], input_shape, logger), "Cannot get input shape");
const auto rank = input_shape.size();
emscripten::val options = emscripten::val::object();
std::vector<int64_t> scale_shape;
ORT_RETURN_IF_NOT(GetShape(*input_defs[1], scale_shape, logger), "Cannot get scale shape");
const auto scale_size = scale_shape.size();
ORT_RETURN_IF_NOT(scale_size >= 1 && scale_size <= rank, "The scale size should be less than or equal to input size.");
if (scale_size < rank) {
// Enlarge new shape to input.rank, right aligned with leading ones
scale_shape.insert(scale_shape.begin(), rank - scale_size, 1);
std::vector<int32_t> new_scale_shape;
std::transform(scale_shape.cbegin(), scale_shape.cend(),
std::back_inserter(new_scale_shape),
[](int64_t dim) -> int32_t { return SafeInt<int32_t>(dim); });
emscripten::val reshape_scale = model_builder.GetOperand(input_defs[1]->Name());
emscripten::val reshape_output_scale =
model_builder.GetBuilder().call<emscripten::val>("reshape", reshape_scale, emscripten::val::array(new_scale_shape));
options.set("scale", reshape_output_scale);
} else {
options.set("scale", model_builder.GetOperand(input_defs[1]->Name()));
}
if (input_defs.size() == 3) {
// Inputs contain optional bias
std::vector<int64_t> bias_shape;
ORT_RETURN_IF_NOT(GetShape(*input_defs[2], bias_shape, logger), "Cannot get bias shape");
const auto bias_size = bias_shape.size();
ORT_RETURN_IF_NOT(bias_size >= 1 && bias_size <= rank, "The bias size should be less than or equal to input size.");
if (bias_size < rank) {
// Enlarge new shape to input.rank, right aligned with leading ones
bias_shape.insert(bias_shape.begin(), rank - bias_size, 1);
std::vector<int32_t> new_bias_shape;
std::transform(bias_shape.cbegin(), bias_shape.cend(),
std::back_inserter(new_bias_shape),
[](int64_t dim) -> int32_t { return SafeInt<int32_t>(dim); });
emscripten::val reshape_bias = model_builder.GetOperand(input_defs[2]->Name());
emscripten::val reshape_output_bias =
model_builder.GetBuilder().call<emscripten::val>("reshape", reshape_bias, emscripten::val::array(new_bias_shape));
options.set("bias", reshape_output_bias);
} else {
options.set("bias", model_builder.GetOperand(input_defs[2]->Name()));
}
}
NodeAttrHelper helper(node);
options.set("epsilon", helper.Get("epsilon", 1e-05f));
int64_t axis = helper.Get("axis", -1);
axis = HandleNegativeAxis(axis, rank);
std::vector<int32_t> axes{static_cast<int32_t>(axis)};
options.set("axes", emscripten::val::array(axes));
emscripten::val output = model_builder.GetBuilder().call<emscripten::val>("meanVarianceNormalization", input, options);
model_builder.AddOperand(node.OutputDefs()[0]->Name(), std::move(output));
return Status::OK();
}
// Operator support related.
bool NormalizationOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers,
const Node& node,
const WebnnDeviceType /* device_type */,
const logging::Logger& logger) const {
const auto& input_defs = node.InputDefs();
if (input_defs.size() < 2) {
LOGS(logger, VERBOSE) << "LayerNormalization requires at least two inputs.";
return false;
}
std::vector<int64_t> input_shape;
if (!GetShape(*input_defs[0], input_shape, logger)) {
LOGS(logger, VERBOSE) << "Cannot get input shape.";
return false;
}
const auto rank = input_shape.size();
NodeAttrHelper helper(node);
int64_t axis = helper.Get("axis", -1);
axis = HandleNegativeAxis(axis, rank);
const auto& scale_name = input_defs[1]->Name();
if (!Contains(initializers, scale_name)) {
LOGS(logger, VERBOSE) << "The scale must be a constant initializer.";
return false;
}
if (input_defs.size() == 3) {
// Inputs contain optional bias
const auto& bias_name = input_defs[2]->Name();
if (!Contains(initializers, bias_name)) {
LOGS(logger, VERBOSE) << "The bias must be a constant initializer.";
return false;
}
}
const auto& output_defs = node.OutputDefs();
if (output_defs.size() != 1) {
LOGS(logger, VERBOSE) << "MeanVarianceNormalization output count must be one.";
return false;
}
return true;
}
void CreateNormalizationOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations) {
op_registrations.builders.push_back(std::make_unique<NormalizationOpBuilder>());
op_registrations.op_builder_map.emplace(op_type, op_registrations.builders.back().get());
}
} // namespace webnn
} // namespace onnxruntime

View file

@ -23,8 +23,8 @@ class PoolOpBuilder : public BaseOpBuilder {
// Operator support related.
private:
bool IsOpSupportedImpl(const InitializedTensorSet& initializers, const Node& node,
const logging::Logger& logger) const override;
bool IsOpSupportedImpl(const InitializedTensorSet& /* initializers */, const Node& node,
const WebnnDeviceType /* device_type */, const logging::Logger& logger) const override;
};
// Add operator related.
@ -108,7 +108,9 @@ Status PoolOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder,
}
// Operator support related.
bool PoolOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& /* initializers */, const Node& node,
bool PoolOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& /* initializers */,
const Node& node,
const WebnnDeviceType /* device_type */,
const logging::Logger& logger) const {
const auto& op_type = node.OpType();
const auto& input_defs = node.InputDefs();

View file

@ -0,0 +1,139 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Intel Corporation. All rights reserved.
// Licensed under the MIT License.
#include "core/providers/common.h"
#include "core/providers/shared/utils/utils.h"
#include "core/providers/webnn/builders/helper.h"
#include "core/providers/webnn/builders/model_builder.h"
#include "core/providers/webnn/builders/op_builder_factory.h"
#include "core/optimizer/initializer.h"
#include "base_op_builder.h"
#include "builder_utils.h"
namespace onnxruntime {
namespace webnn {
class ReductionOpBuilder : public BaseOpBuilder {
// Add operator related.
public:
void AddInitializersToSkip(ModelBuilder& model_builder, const Node& node) const override;
// Add operator related.
private:
Status AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node,
const logging::Logger& logger) const override ORT_MUST_USE_RESULT;
// Operator support related.
private:
bool IsOpSupportedImpl(const InitializedTensorSet& /* initializers */, const Node& node,
const WebnnDeviceType /* device_type */, const logging::Logger& logger) const override;
};
// Add operator related.
void ReductionOpBuilder::AddInitializersToSkip(ModelBuilder& model_builder, const Node& node) const {
const auto& input_defs = node.InputDefs();
if (input_defs.size() > 1) {
model_builder.AddInitializerToSkip(input_defs[1]->Name()); // axes
}
}
// Add operator related.
Status ReductionOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder,
const Node& node,
const logging::Logger& logger) const {
const auto& input_defs = node.InputDefs();
emscripten::val input = model_builder.GetOperand(input_defs[0]->Name());
std::vector<int64_t> input_shape;
ORT_RETURN_IF_NOT(GetShape(*input_defs[0], input_shape, logger), "Cannot get shape");
const auto input_rank = input_shape.size();
NodeAttrHelper helper(node);
const auto keep_dims = helper.Get("keepdims", 1);
emscripten::val options = emscripten::val::object();
options.set("keepDimensions", keep_dims == 1);
std::vector<int32_t> axes_data;
emscripten::val output = emscripten::val::object();
const auto opset = node.SinceVersion();
if (opset >= 18) {
// Since opset 18, axes is an optional input.
const auto noop_with_empty_axes = helper.Get("noop_with_empty_axes", 0);
if (input_defs.size() > 1) {
// Optional input axes is provided, use axes initializer data.
const auto& initializers(model_builder.GetInitializerTensors());
const auto& axes_tensor = *initializers.at(input_defs[1]->Name());
Initializer axes_initializer(axes_tensor);
const auto axes_data_span = axes_initializer.DataAsSpan<int64_t>();
std::transform(
axes_data_span.begin(), axes_data_span.end(), std::back_inserter(axes_data),
[input_rank](int64_t axis) -> int32_t { return HandleNegativeAxis(axis, input_rank); });
} else {
if (noop_with_empty_axes) {
// When axes is empty and this attribute is set to true, input tensor will not be reduced.
output = input;
model_builder.AddOperand(node.OutputDefs()[0]->Name(), std::move(output));
return Status::OK();
}
}
} else {
if (helper.HasAttr("axes")) {
auto axes = helper.Get("axes", std::vector<int64_t>{});
std::transform(
axes.begin(), axes.end(), std::back_inserter(axes_data),
[input_rank](int64_t axis) -> int32_t { return HandleNegativeAxis(axis, input_rank); });
}
}
if (axes_data.size() > 0) {
options.set("axes", emscripten::val::array(axes_data));
}
const auto& op_type = node.OpType();
if (op_type == "ReduceMax") {
output = model_builder.GetBuilder().call<emscripten::val>("reduceMax", input, options);
} else if (op_type == "ReduceMean") {
output = model_builder.GetBuilder().call<emscripten::val>("reduceMean", input, options);
} else {
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT, "ReductionOpBuilder, unknown op: ", op_type);
}
model_builder.AddOperand(node.OutputDefs()[0]->Name(), std::move(output));
return Status::OK();
}
// Operator support related.
bool ReductionOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& /* initializers */,
const Node& node,
const WebnnDeviceType /* device_type */,
const logging::Logger& logger) const {
const auto& input_defs = node.InputDefs();
std::vector<int64_t> input_shape;
if (!GetShape(*input_defs[0], input_shape, logger))
return false;
return true;
}
void CreateReductionOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations) {
if (op_registrations.op_builder_map.find(op_type) != op_registrations.op_builder_map.cend())
return;
static std::vector<std::string> op_types =
{
"ReduceMax",
"ReduceMean",
};
op_registrations.builders.push_back(std::make_unique<ReductionOpBuilder>());
for (const auto& type : op_types) {
op_registrations.op_builder_map.emplace(type, op_registrations.builders.back().get());
}
}
} // namespace webnn
} // namespace onnxruntime

View file

@ -29,7 +29,7 @@ class ReshapeOpBuilder : public BaseOpBuilder {
// Operator support related.
private:
bool IsOpSupportedImpl(const InitializedTensorSet& initializers, const Node& node,
const logging::Logger& logger) const override;
const WebnnDeviceType /* device_type */, const logging::Logger& logger) const override;
// Reshape opset 4- uses attributes for new shape which we do not support for now.
int GetMinSupportedOpSet(const Node& /* node */) const override { return 5; }
@ -69,7 +69,9 @@ Status ReshapeOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder,
// Operator support related.
bool ReshapeOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers, const Node& node,
bool ReshapeOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers,
const Node& node,
const WebnnDeviceType /* device_type */,
const logging::Logger& logger) const {
const auto& input_defs = node.InputDefs();
const auto& perm_name = input_defs[1]->Name();

View file

@ -30,7 +30,7 @@ class ResizeOpBuilder : public BaseOpBuilder {
// Operator support related.
private:
bool IsOpSupportedImpl(const InitializedTensorSet& initializers, const Node& node,
const logging::Logger& logger) const override;
const WebnnDeviceType /* device_type */, const logging::Logger& logger) const override;
// Resize opset 10- is very different than Resize opset 11+, with many key attributes missing.
// We only support Resize opset 11+ here.
@ -159,7 +159,9 @@ Status ResizeOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder,
// Operator support related.
bool ResizeOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers, const Node& node,
bool ResizeOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers,
const Node& node,
const WebnnDeviceType /* device_type */,
const logging::Logger& logger) const {
const auto& input_defs = node.InputDefs();
@ -255,26 +257,6 @@ bool ResizeOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers
<< ". input_size_c, " << input_shape[c_idx] << ", output_size_c, " << output_sizes[c_idx];
return false;
}
// For now we only support upscale, so the output_size_h and output_size_w should be an integer >= 1.
// TODO support ResizeBilinear
auto output_size_h = output_sizes[2];
auto output_size_w = output_sizes[3];
auto input_size_h = input_shape[2];
auto input_size_w = input_shape[3];
// Onnx spec requires output sizes to be a positive integer, so we are not checking that here.
if (output_size_h % input_size_h != 0) {
LOGS(logger, VERBOSE) << "Resize: output_size_h: " << output_size_h
<< " is not a multiple of input_size_h: " << input_size_h;
return false;
}
if (output_size_w % input_size_w != 0) {
LOGS(logger, VERBOSE) << "Resize: output_size_w: " << output_size_w
<< " is not a multiple of input_size_w: " << input_size_w;
return false;
}
}
}

View file

@ -0,0 +1,164 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Intel Corporation. All rights reserved.
// Licensed under the MIT License.
#include "core/common/safeint.h"
#include "core/framework/tensorprotoutils.h"
#include "core/optimizer/initializer.h"
#include "core/providers/common.h"
#include "core/providers/cpu/tensor/slice_helper.h"
#include "core/providers/shared/utils/utils.h"
#include "core/providers/webnn/builders/helper.h"
#include "core/providers/webnn/builders/model_builder.h"
#include "core/providers/webnn/builders/op_builder_factory.h"
#include "base_op_builder.h"
namespace onnxruntime {
namespace webnn {
class SliceOpBuilder : public BaseOpBuilder {
// Add operator related.
public:
void AddInitializersToSkip(ModelBuilder& model_builder, const Node& node) const override;
private:
Status AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node,
const logging::Logger& logger) const override ORT_MUST_USE_RESULT;
bool IsOpSupportedImpl(const InitializedTensorSet& initializers, const Node& node,
const WebnnDeviceType /* device_type */, const logging::Logger& logger) const override;
// TODO: Support Slice opset < 10, which uses attributes for starts and ends.
int GetMinSupportedOpSet(const Node& /* node */) const override { return 10; }
};
// Add operator related.
void SliceOpBuilder::AddInitializersToSkip(ModelBuilder& model_builder, const Node& node) const {
// Skip all initializer except the first inputs(data).
for (size_t i = 1; i < node.InputDefs().size(); i++) {
model_builder.AddInitializerToSkip(node.InputDefs()[i]->Name());
}
}
Status SliceOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder,
const Node& node,
const logging::Logger& logger) const {
const auto& input_defs = node.InputDefs();
std::vector<int64_t> input_shape;
ORT_RETURN_IF_NOT(GetShape(*input_defs[0], input_shape, logger), "Cannot get input shape");
auto rank = input_shape.size();
NodeAttrHelper helper(node);
emscripten::val inputs = model_builder.GetOperand(input_defs[0]->Name());
std::vector<int32_t> starts(rank);
std::vector<int32_t> sizes(rank);
// Copy the data from the starts/ends/axes/steps initializers.
std::vector<int64_t> input_starts;
std::vector<int64_t> input_ends;
std::vector<int64_t> input_axes;
std::vector<int64_t> input_steps;
SliceOp::PrepareForComputeMetadata compute_metadata(input_shape);
const auto CopyInputData = [&input_defs, &model_builder, &logger](size_t input_idx, std::vector<int64_t>& data,
bool is_required = false) {
data.clear();
std::string input_name;
// This is an optional input, return empty vector.
if (!is_required) {
if (input_defs.size() <= input_idx)
return Status::OK();
input_name = input_defs[input_idx]->Name();
if (input_name.empty())
return Status::OK();
}
input_name = input_defs[input_idx]->Name();
const auto& initializers(model_builder.GetInitializerTensors());
const auto& tensor = *initializers.at(input_name);
if (!ReadIntArrayFrom1DTensor(tensor, data, logger)) {
return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL,
"Data type for starts and ends inputs is not supported in this build.");
}
return Status::OK();
};
ORT_RETURN_IF_ERROR(CopyInputData(1, input_starts, true));
ORT_RETURN_IF_ERROR(CopyInputData(2, input_ends, true));
ORT_RETURN_IF_ERROR(CopyInputData(3, input_axes));
ORT_RETURN_IF_ERROR(CopyInputData(4, input_steps));
ORT_RETURN_IF_ERROR(
SliceOp::PrepareForComputeHelper(input_starts, input_ends, input_axes, input_steps, compute_metadata));
std::transform(compute_metadata.starts_.cbegin(), compute_metadata.starts_.cend(),
starts.begin(),
[](int64_t i) { return SafeInt<uint32_t>(i); });
std::transform(compute_metadata.ends_.cbegin(), compute_metadata.ends_.cend(), compute_metadata.starts_.cbegin(),
sizes.begin(),
[](int64_t i, int64_t j) { return SafeInt<uint32_t>(i - j); });
emscripten::val output = model_builder.GetBuilder().call<emscripten::val>("slice", inputs,
emscripten::val::array(starts),
emscripten::val::array(sizes));
model_builder.AddOperand(node.OutputDefs()[0]->Name(), std::move(output));
return Status::OK();
}
bool SliceOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers,
const Node& node,
const WebnnDeviceType /* device_type */,
const logging::Logger& logger) const {
const auto& name = node.Name();
const auto& op_type = node.OpType();
const auto& input_defs = node.InputDefs();
std::vector<int64_t> input_shape;
if (!GetShape(*input_defs[0], input_shape, logger)) {
return false;
}
if (input_defs.size() == 5) { // Check steps.
const auto& steps_tensor = *initializers.at(input_defs[4]->Name());
std::vector<uint8_t> unpacked_tensor;
auto status = onnxruntime::utils::UnpackInitializerData(steps_tensor, unpacked_tensor);
if (!status.IsOK()) {
LOGS(logger, ERROR) << "Error while unpacking steps_tensor: " << status.ErrorMessage();
return false;
}
const auto data_type = steps_tensor.data_type();
// WebNN doesn't support steps other than 1.
if (data_type == ONNX_NAMESPACE::TensorProto_DataType_INT64) {
if (!std::all_of(reinterpret_cast<int64_t*>(unpacked_tensor.data()),
reinterpret_cast<int64_t*>(unpacked_tensor.data() + unpacked_tensor.size()),
[](int64_t i) { return i == 1; })) {
return false;
}
} else if (data_type == ONNX_NAMESPACE::TensorProto_DataType_INT32) {
if (!std::all_of(reinterpret_cast<int32_t*>(unpacked_tensor.data()),
reinterpret_cast<int32_t*>(unpacked_tensor.data()) +
unpacked_tensor.size() / sizeof(int32_t),
[](int32_t i) { return i == 1; })) {
return false;
}
}
}
if (input_defs.size() < 3) {
LOGS(logger, VERBOSE) << op_type << " [" << name << "] requires at least 3 inputs (data starts and ends) but got "
<< input_defs.size();
return false;
}
const auto& starts_name = input_defs[1]->Name();
const auto& ends_name = input_defs[2]->Name();
if (!Contains(initializers, starts_name) || !Contains(initializers, ends_name)) {
LOGS(logger, VERBOSE) << op_type << " [" << name << "] need starts and ends as initializer.";
return false;
}
return true;
}
void CreateSliceOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations) {
op_registrations.builders.push_back(std::make_unique<SliceOpBuilder>());
op_registrations.op_builder_map.emplace(op_type, op_registrations.builders.back().get());
}
} // namespace webnn
} // namespace onnxruntime

View file

@ -0,0 +1,94 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Intel Corporation. All rights reserved.
// Licensed under the MIT License.
#include "core/providers/common.h"
#include "core/providers/shared/utils/utils.h"
#include "core/providers/webnn/builders/helper.h"
#include "core/providers/webnn/builders/model_builder.h"
#include "core/providers/webnn/builders/op_builder_factory.h"
#include "base_op_builder.h"
namespace onnxruntime {
namespace webnn {
class SoftmaxOpBuilder : public BaseOpBuilder {
// Add operator related.
private:
Status AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node,
const logging::Logger& logger) const override ORT_MUST_USE_RESULT;
// Operator support related.
private:
bool IsOpSupportedImpl(const InitializedTensorSet& /* initializers */, const Node& node,
const WebnnDeviceType /* device_type */, const logging::Logger& logger) const override;
};
Status SoftmaxOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder,
const Node& node,
const logging::Logger& logger) const {
const auto& input_defs = node.InputDefs();
emscripten::val input = model_builder.GetOperand(node.InputDefs()[0]->Name());
emscripten::val output = emscripten::val::object();
std::vector<int64_t> input_shape;
ORT_RETURN_IF_NOT(GetShape(*input_defs[0], input_shape, logger), "Cannot get shape");
const auto input_size = input_shape.size();
// WebNN Softmax only support 2d input shape, reshape input to 2d.
if (input_size != 2) {
int32_t new_shape_0 = input_shape.data()[0];
for (size_t i = 1; i < input_size - 1; i++) {
new_shape_0 *= input_shape.data()[i];
}
emscripten::val new_shape = emscripten::val::array();
new_shape.call<void>("push", new_shape_0);
new_shape.call<void>("push", static_cast<int32_t>(input_shape.back()));
input = model_builder.GetBuilder().call<emscripten::val>("reshape", input, new_shape);
}
output = model_builder.GetBuilder().call<emscripten::val>("softmax", input);
// Reshape output to the same shape of input.
if (input_size != 2) {
emscripten::val new_shape = emscripten::val::array();
for (size_t i = 0; i < input_size; i++) {
new_shape.call<void>("push", static_cast<int32_t>(input_shape[i]));
}
output = model_builder.GetBuilder().call<emscripten::val>("reshape", output, new_shape);
}
model_builder.AddOperand(node.OutputDefs()[0]->Name(), std::move(output));
return Status::OK();
}
// Operator support related.
bool SoftmaxOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& /* initializers */,
const Node& node,
const WebnnDeviceType /* device_type */,
const logging::Logger& logger) const {
const auto& input_defs = node.InputDefs();
std::vector<int64_t> input_shape;
if (!GetShape(*input_defs[0], input_shape, logger))
return false;
const auto input_size = input_shape.size();
if (input_size < 2) {
LOGS(logger, VERBOSE) << "SoftMax only support input size >= 2d shape, input is "
<< input_size << "d shape";
return false;
}
NodeAttrHelper helper(node);
const int32_t axis = helper.Get("axis", 1);
// WebNN softmax only support input axis 1
if (axis != 1 && axis != -1) {
LOGS(logger, VERBOSE) << "SoftMax only support axis 1 or -1, input axis: " << axis;
return false;
}
return true;
}
void CreateSoftmaxOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations) {
op_registrations.builders.push_back(std::make_unique<SoftmaxOpBuilder>());
op_registrations.op_builder_map.emplace(op_type, op_registrations.builders.back().get());
}
} // namespace webnn
} // namespace onnxruntime

View file

@ -0,0 +1,182 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Intel Corporation. All rights reserved.
// Licensed under the MIT License.
#include "core/common/safeint.h"
#include "core/optimizer/initializer.h"
#include "core/providers/common.h"
#include "core/providers/shared/utils/utils.h"
#include "core/providers/webnn/builders/helper.h"
#include "core/providers/webnn/builders/model_builder.h"
#include "core/providers/webnn/builders/op_builder_factory.h"
#include "base_op_builder.h"
namespace onnxruntime {
namespace webnn {
class SplitOpBuilder : public BaseOpBuilder {
// Add operator related.
private:
Status AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node,
const logging::Logger& logger) const override ORT_MUST_USE_RESULT;
// Operator support related.
private:
bool IsOpSupportedImpl(const InitializedTensorSet& initializers, const Node& node,
const WebnnDeviceType /* device_type */, const logging::Logger& logger) const override;
int GetMinSupportedOpSet(const Node& node) const override;
};
Status SplitOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder,
const Node& node,
const logging::Logger& logger) const {
const auto& input_defs = node.InputDefs();
emscripten::val input = model_builder.GetOperand(input_defs[0]->Name());
emscripten::val output_array;
std::vector<int64_t> input_shape;
ORT_RETURN_IF_NOT(GetShape(*input_defs[0], input_shape, logger), "Cannot get shape");
const auto rank = input_shape.size();
emscripten::val options = emscripten::val::object();
NodeAttrHelper helper(node);
int64_t axis = helper.Get("axis", 0);
axis = HandleNegativeAxis(axis, rank);
options.set("axis", static_cast<int32_t>(axis));
if (input_defs.size() == 2) {
// Inputs contains optional 'split' input
std::vector<int32_t> splits;
const auto& initializers(model_builder.GetInitializerTensors());
const auto& split_tensor = *initializers.at(input_defs[1]->Name());
ORT_RETURN_IF_NOT(ReadIntArrayFrom1DTensor(split_tensor, splits, logger), "Cannot get split.");
output_array = model_builder.GetBuilder().call<emscripten::val>("split",
input,
emscripten::val::array(splits),
options);
ORT_RETURN_IF_NOT(output_array["length"].as<int32_t>() == static_cast<int32_t>(splits.size()),
"The size of outputs must be equal to the size of 'split' input.");
} else {
if (helper.HasAttr("num_outputs")) {
const int64_t num_outputs = helper.Get("num_outputs", 1);
ORT_RETURN_IF_NOT(num_outputs > 0, "The 'num_outputs' must be a positive integer.");
if (input_shape[axis] % num_outputs == 0) {
// The 'num_outputs' evenly divide the dim value at 'axis' specified.
output_array = model_builder.GetBuilder().call<emscripten::val>("split",
input,
static_cast<int32_t>(num_outputs),
options);
} else {
std::vector<int64_t> mapping_split;
mapping_split.insert(mapping_split.begin(), num_outputs - 1, input_shape[axis] / num_outputs);
mapping_split.insert(mapping_split.end(), input_shape[axis] % num_outputs);
std::vector<int32_t> converted_splits;
std::transform(mapping_split.cbegin(), mapping_split.cend(),
std::back_inserter(converted_splits),
[](int64_t dim) -> int32_t { return SafeInt<int32_t>(dim); });
output_array = model_builder.GetBuilder().call<emscripten::val>("split",
input,
emscripten::val::array(converted_splits),
options);
}
ORT_RETURN_IF_NOT(output_array["length"].as<int32_t>() == static_cast<int32_t>(num_outputs),
"The size of outputs must be equal to 'num_outputs'.");
} else {
// w/o 'split' input for opset 13
// Refer to https://github.com/microsoft/onnxruntime/blob/a7ad859e3ab60bddfcf2fefa96bfcb550f0fc04c/onnxruntime/core/providers/dml/OperatorAuthorHelper/OperatorHelper.cpp#L984-L989
// split input stream equally across output streams.
const auto& output_defs = node.OutputDefs();
auto output_count = output_defs.size();
output_array = model_builder.GetBuilder().call<emscripten::val>("split",
input, static_cast<int32_t>(output_count),
options);
ORT_RETURN_IF_NOT(output_array["length"].as<int32_t>() == static_cast<int32_t>(output_count),
"The size of outputs must be equal to the count of output nodes.");
}
}
for (int64_t i = 0, count = output_array["length"].as<int32_t>(); i < count; i++) {
model_builder.AddOperand(node.OutputDefs()[i]->Name(), std::move(output_array[i]));
}
return Status::OK();
}
// Operator support related.
int SplitOpBuilder::GetMinSupportedOpSet(const Node& /* node */) const {
// Since opset 13, Split has optional 'split' input.
return 13;
}
bool SplitOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers,
const Node& node,
const WebnnDeviceType /* device_type */,
const logging::Logger& logger) const {
const auto& input_defs = node.InputDefs();
std::vector<int64_t> input_shape;
if (!GetShape(*input_defs[0], input_shape, logger)) {
LOGS(logger, VERBOSE) << "Cannot get input's shape.";
return false;
}
const auto rank = input_shape.size();
NodeAttrHelper helper(node);
int64_t axis = helper.Get("axis", 0);
axis = HandleNegativeAxis(axis, rank);
if (input_defs.size() == 2) {
// Inputs contains optional 'split' input
const auto& split_name = input_defs[1]->Name();
if (!Contains(initializers, split_name)) {
LOGS(logger, VERBOSE) << "The split must be a constant initializer.";
return false;
}
// Values should be >= 0. Sum of the values must be equal to the dim value at 'axis' specified.
std::vector<int64_t> split;
const auto& split_tensor = *initializers.at(input_defs[1]->Name());
if (split_tensor.data_type() != ONNXTensorElementDataType::ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64) {
LOGS(logger, VERBOSE) << "The type of tensor's element data must be INT64.";
return false;
}
if (!ReadIntArrayFrom1DTensor(split_tensor, split, logger)) {
LOGS(logger, VERBOSE) << "Cannot get split.";
return false;
}
int64_t sum = 0;
for (int64_t i = 0; i < split.size(); i++) {
if (split[i] < 0) {
LOGS(logger, VERBOSE) << "Value of split should be greater than or equal to 0.";
return false;
}
sum += split[i];
}
if (sum != input_shape[axis]) {
LOGS(logger, VERBOSE) << "Sum of the split's values must be equal to the dim value at 'axis' specified.";
return false;
}
} else if (input_defs.size() == 1) {
if (helper.HasAttr("num_outputs")) {
// Split has 'num_outputs' attribute when opset is 18.
const int32_t num_outputs = helper.Get("num_outputs", 1);
if (num_outputs < 1) {
LOGS(logger, VERBOSE) << "The 'num_outputs' must be a positive integer.";
return false;
}
} else {
const auto opset = node.SinceVersion();
if (opset >= 18) {
LOGS(logger, VERBOSE) << "The 'num_outputs' should be specified when 'split' isn't specified.";
return false;
}
}
}
return true;
}
void CreateSplitOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations) {
op_registrations.builders.push_back(std::make_unique<SplitOpBuilder>());
op_registrations.op_builder_map.emplace(op_type, op_registrations.builders.back().get());
}
} // namespace webnn
} // namespace onnxruntime

View file

@ -0,0 +1,73 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Intel Corporation. All rights reserved.
// Licensed under the MIT License.
#include <core/providers/common.h>
#include "core/providers/webnn/builders/model_builder.h"
#include "core/providers/webnn/builders/op_builder_factory.h"
#include "core/providers/webnn/builders/helper.h"
#include "base_op_builder.h"
namespace onnxruntime {
namespace webnn {
class UnaryOpBuilder : public BaseOpBuilder {
// Add operator related.
private:
Status AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node,
const logging::Logger& logger) const override ORT_MUST_USE_RESULT;
};
// Add operator related.
Status UnaryOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node,
const logging::Logger& /* logger */) const {
const auto& op_type(node.OpType());
emscripten::val input = model_builder.GetOperand(node.InputDefs()[0]->Name());
emscripten::val output = emscripten::val::object();
if (op_type == "Cos") {
output = model_builder.GetBuilder().call<emscripten::val>("cos", input);
} else if (op_type == "Erf") {
output = model_builder.GetBuilder().call<emscripten::val>("erf", input);
} else if (op_type == "Floor") {
output = model_builder.GetBuilder().call<emscripten::val>("floor", input);
} else if (op_type == "Not") {
output = model_builder.GetBuilder().call<emscripten::val>("logicalNot", input);
} else if (op_type == "Sin") {
output = model_builder.GetBuilder().call<emscripten::val>("sin", input);
} else if (op_type == "Sqrt") {
output = model_builder.GetBuilder().call<emscripten::val>("sqrt", input);
} else {
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
"UnaryOpBuilder::AddToModelBuilderImpl, unknown op: ", op_type);
}
model_builder.AddOperand(node.OutputDefs()[0]->Name(), std::move(output));
return Status::OK();
}
void CreateUnaryOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations) {
if (op_registrations.op_builder_map.find(op_type) != op_registrations.op_builder_map.cend())
return;
static std::vector<std::string> op_types =
{
"Cos",
"Erf",
"Floor",
"Not",
"Sin",
"Sqrt",
};
op_registrations.builders.push_back(std::make_unique<UnaryOpBuilder>());
for (const auto& type : op_types) {
op_registrations.op_builder_map.emplace(type, op_registrations.builders.back().get());
}
}
} // namespace webnn
} // namespace onnxruntime

View file

@ -0,0 +1,126 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Intel Corporation. All rights reserved.
// Licensed under the MIT License.
#include "core/providers/common.h"
#include "core/providers/shared/utils/utils.h"
#include "core/providers/webnn/builders/helper.h"
#include "core/providers/webnn/builders/model_builder.h"
#include "core/providers/webnn/builders/op_builder_factory.h"
#include "core/optimizer/initializer.h"
#include "base_op_builder.h"
#include "builder_utils.h"
namespace onnxruntime {
namespace webnn {
class UnsqueezeOpBuilder : public BaseOpBuilder {
// Add operator related.
public:
void AddInitializersToSkip(ModelBuilder& model_builder, const Node& node) const override;
private:
Status AddToModelBuilderImpl(ModelBuilder& model_builder, const Node& node,
const logging::Logger& logger) const override ORT_MUST_USE_RESULT;
// Operator support related.
private:
bool IsOpSupportedImpl(const InitializedTensorSet& initializers, const Node& node,
const WebnnDeviceType /* device_type */, const logging::Logger& logger) const override;
};
// Add operator related.
void UnsqueezeOpBuilder::AddInitializersToSkip(ModelBuilder& model_builder, const Node& node) const {
// Unsqueeze opset 13 uses input 1 as axes, add it to initializer skip list.
const auto& input_defs = node.InputDefs();
if (node.SinceVersion() > 12 && input_defs.size() > 1) {
model_builder.AddInitializerToSkip(input_defs[1]->Name()); // "axes"
model_builder.AddInputToSkip(input_defs[1]->Name());
}
}
// Add operator related.
Status UnsqueezeOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder,
const Node& node,
const logging::Logger& logger) const {
const auto& input_defs = node.InputDefs();
emscripten::val input = model_builder.GetOperand(input_defs[0]->Name());
std::vector<int64_t> input_shape;
ORT_RETURN_IF_NOT(GetShape(*input_defs[0], input_shape, logger), "Cannot get input shape");
const auto input_rank = input_shape.size();
NodeAttrHelper helper(node);
emscripten::val options = emscripten::val::object();
std::vector<int32_t> axes_data;
if (node.SinceVersion() >= 13) {
// Input axes is provided, use axes initializer data.
const auto& initializers = model_builder.GetInitializerTensors();
const auto& axes_tensor = *initializers.at(input_defs[1]->Name());
Initializer axes_initializer(axes_tensor);
const auto axes_data_span = axes_initializer.DataAsSpan<int64_t>();
const auto output_rank = input_rank + axes_data_span.size();
std::transform(
axes_data_span.begin(), axes_data_span.end(), std::back_inserter(axes_data),
[output_rank](int64_t axis) -> int32_t { return HandleNegativeAxis(axis, output_rank); });
} else {
if (helper.HasAttr("axes")) {
auto axes = helper.Get("axes", std::vector<int64_t>{});
const auto output_rank = input_rank + axes.size();
std::transform(
axes.begin(), axes.end(), std::back_inserter(axes_data),
[output_rank](int64_t axis) -> int32_t { return HandleNegativeAxis(axis, output_rank); });
}
}
if (axes_data.size() > 0) {
options.set("axes", emscripten::val::array(axes_data));
}
emscripten::val output = model_builder.GetBuilder().call<emscripten::val>("unsqueeze", input, options);
model_builder.AddOperand(node.OutputDefs()[0]->Name(), std::move(output));
return Status::OK();
}
// Operator support related.
bool UnsqueezeOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers,
const Node& node,
const WebnnDeviceType /* device_type */,
const logging::Logger& logger) const {
const auto& input_defs = node.InputDefs();
std::vector<int64_t> input_shape;
if (!GetShape(*input_defs[0], input_shape, logger))
return false;
// Unsqueeze opset 13 uses input 1 as axes, it needs to be an initializer.
if (node.SinceVersion() >= 13) {
if (input_defs.size() < 2) {
LOGS(logger, ERROR) << "Input axes of Unsqueeze must be provided";
return false;
}
const auto& axes_name = input_defs[1]->Name();
if (!Contains(initializers, axes_name)) {
LOGS(logger, ERROR) << "Input axes of Unsqueeze must be known";
return false;
}
} else {
if (input_defs.size() < 1) {
LOGS(logger, ERROR) << "Unsqueeze has no input tensor";
return false;
}
}
return true;
}
void CreateUnsqueezeOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations) {
op_registrations.builders.push_back(std::make_unique<UnsqueezeOpBuilder>());
op_registrations.op_builder_map.emplace(op_type, op_registrations.builders.back().get());
}
} // namespace webnn
} // namespace onnxruntime

View file

@ -29,13 +29,42 @@ Status Model::Predict(const InlinedHashMap<std::string, OnnxTensorData>& inputs,
for (const auto& input : inputs) {
const std::string& name = input.first;
const struct OnnxTensorData tensor = input.second;
if (tensor.tensor_info.data_type != ONNX_NAMESPACE::TensorProto_DataType_FLOAT) {
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
"The input of graph has unsupported type, name: ",
name, " type: ", tensor.tensor_info.data_type);
}
auto num_elements = SafeInt<size_t>(Product(tensor.tensor_info.shape));
emscripten::val view{emscripten::typed_memory_view(num_elements, static_cast<const float*>(tensor.buffer))};
emscripten::val view = emscripten::val::undefined();
switch (tensor.tensor_info.data_type) {
case ONNX_NAMESPACE::TensorProto_DataType_BOOL:
view = emscripten::val{emscripten::typed_memory_view(num_elements,
static_cast<const uint8_t*>(tensor.buffer))};
break;
case ONNX_NAMESPACE::TensorProto_DataType_FLOAT16:
view = emscripten::val{emscripten::typed_memory_view(num_elements,
static_cast<const uint16_t*>(tensor.buffer))};
break;
case ONNX_NAMESPACE::TensorProto_DataType_FLOAT:
view = emscripten::val{emscripten::typed_memory_view(num_elements,
static_cast<const float*>(tensor.buffer))};
break;
case ONNX_NAMESPACE::TensorProto_DataType_INT32:
view = emscripten::val{emscripten::typed_memory_view(num_elements,
static_cast<const int32_t*>(tensor.buffer))};
break;
case ONNX_NAMESPACE::TensorProto_DataType_INT64:
view = emscripten::val{emscripten::typed_memory_view(num_elements,
static_cast<const int64_t*>(tensor.buffer))};
break;
case ONNX_NAMESPACE::TensorProto_DataType_UINT32:
view = emscripten::val{emscripten::typed_memory_view(num_elements,
static_cast<const uint32_t*>(tensor.buffer))};
break;
case ONNX_NAMESPACE::TensorProto_DataType_UINT64:
view = emscripten::val{emscripten::typed_memory_view(num_elements,
static_cast<const uint64_t*>(tensor.buffer))};
break;
default:
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
"The input of graph has unsupported type, name: ",
name, " type: ", tensor.tensor_info.data_type);
}
#ifdef ENABLE_WEBASSEMBLY_THREADS
// Copy the inputs from Wasm SharedArrayBuffer to the pre-allocated ArrayBuffers.
wnn_inputs_[name].call<void>("set", view);
@ -55,13 +84,43 @@ Status Model::Predict(const InlinedHashMap<std::string, OnnxTensorData>& inputs,
for (const auto& output : outputs) {
const std::string& name = output.first;
const struct OnnxTensorData tensor = output.second;
if (tensor.tensor_info.data_type != ONNX_NAMESPACE::TensorProto_DataType_FLOAT) {
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
"The input of graph has unsupported type, name: ",
name, " type: ", tensor.tensor_info.data_type);
}
auto num_elements = SafeInt<size_t>(Product(tensor.tensor_info.shape));
emscripten::val view{emscripten::typed_memory_view(num_elements, static_cast<const float*>(tensor.buffer))};
emscripten::val view = emscripten::val::undefined();
switch (tensor.tensor_info.data_type) {
case ONNX_NAMESPACE::TensorProto_DataType_BOOL:
view = emscripten::val{emscripten::typed_memory_view(num_elements,
static_cast<const uint8_t*>(tensor.buffer))};
break;
case ONNX_NAMESPACE::TensorProto_DataType_FLOAT16:
view = emscripten::val{emscripten::typed_memory_view(num_elements,
static_cast<const uint16_t*>(tensor.buffer))};
break;
case ONNX_NAMESPACE::TensorProto_DataType_FLOAT:
view = emscripten::val{emscripten::typed_memory_view(num_elements,
static_cast<const float*>(tensor.buffer))};
break;
case ONNX_NAMESPACE::TensorProto_DataType_INT32:
view = emscripten::val{emscripten::typed_memory_view(num_elements,
static_cast<const int32_t*>(tensor.buffer))};
break;
case ONNX_NAMESPACE::TensorProto_DataType_INT64:
view = emscripten::val{emscripten::typed_memory_view(num_elements,
static_cast<const int64_t*>(tensor.buffer))};
break;
case ONNX_NAMESPACE::TensorProto_DataType_UINT32:
view = emscripten::val{emscripten::typed_memory_view(num_elements,
static_cast<const uint32_t*>(tensor.buffer))};
break;
case ONNX_NAMESPACE::TensorProto_DataType_UINT64:
view = emscripten::val{emscripten::typed_memory_view(num_elements,
static_cast<const uint64_t*>(tensor.buffer))};
break;
default:
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
"The output of graph has unsupported type, name: ",
name, " type: ", tensor.tensor_info.data_type);
}
#ifdef ENABLE_WEBASSEMBLY_THREADS
output_views.insert({name, view});
#else
@ -69,7 +128,6 @@ Status Model::Predict(const InlinedHashMap<std::string, OnnxTensorData>& inputs,
#endif
}
wnn_context_.call<emscripten::val>("computeSync", wnn_graph_, wnn_inputs_, wnn_outputs_);
#ifdef ENABLE_WEBASSEMBLY_THREADS
// Copy the outputs from pre-allocated ArrayBuffers back to the Wasm SharedArrayBuffer.
for (const auto& output : outputs) {
@ -102,16 +160,64 @@ void Model::AllocateInputOutputBuffers() {
for (const auto& input : inputs_) {
const auto& input_info = input_output_info_.at(input);
const auto input_shape = input_info.shape;
const auto num_elements = SafeInt<size_t>(Product(input_shape));
wnn_inputs_.set(input,
emscripten::val::global("Float32Array").new_(static_cast<const int>(num_elements)));
const int32_t num_elements = SafeInt<int32_t>(Product(input_shape));
const auto data_type = input_info.data_type;
switch (data_type) {
case ONNX_NAMESPACE::TensorProto_DataType_BOOL:
wnn_inputs_.set(input, emscripten::val::global("Uint8Array").new_(num_elements));
break;
case ONNX_NAMESPACE::TensorProto_DataType_FLOAT16:
wnn_inputs_.set(input, emscripten::val::global("Uint16Array").new_(num_elements));
break;
case ONNX_NAMESPACE::TensorProto_DataType_FLOAT:
wnn_inputs_.set(input, emscripten::val::global("Float32Array").new_(num_elements));
break;
case ONNX_NAMESPACE::TensorProto_DataType_INT32:
wnn_inputs_.set(input, emscripten::val::global("Int32Array").new_(num_elements));
break;
case ONNX_NAMESPACE::TensorProto_DataType_INT64:
wnn_inputs_.set(input, emscripten::val::global("BigInt64Array").new_(num_elements));
break;
case ONNX_NAMESPACE::TensorProto_DataType_UINT32:
wnn_inputs_.set(input, emscripten::val::global("Uint32Array").new_(num_elements));
break;
case ONNX_NAMESPACE::TensorProto_DataType_UINT64:
wnn_inputs_.set(input, emscripten::val::global("BigUint64Array").new_(num_elements));
break;
default:
break;
}
}
for (const auto& output : outputs_) {
const auto& output_info = input_output_info_.at(output);
const auto output_shape = output_info.shape;
const auto num_elements = SafeInt<size_t>(Product(output_shape));
wnn_outputs_.set(output,
emscripten::val::global("Float32Array").new_(static_cast<const int>(num_elements)));
const int32_t num_elements = SafeInt<int32_t>(Product(output_shape));
const auto data_type = output_info.data_type;
switch (data_type) {
case ONNX_NAMESPACE::TensorProto_DataType_BOOL:
wnn_outputs_.set(output, emscripten::val::global("Uint8Array").new_(num_elements));
break;
case ONNX_NAMESPACE::TensorProto_DataType_FLOAT16:
wnn_outputs_.set(output, emscripten::val::global("Uint16Array").new_(num_elements));
break;
case ONNX_NAMESPACE::TensorProto_DataType_FLOAT:
wnn_outputs_.set(output, emscripten::val::global("Float32Array").new_(num_elements));
break;
case ONNX_NAMESPACE::TensorProto_DataType_INT32:
wnn_outputs_.set(output, emscripten::val::global("Int32Array").new_(num_elements));
break;
case ONNX_NAMESPACE::TensorProto_DataType_INT64:
wnn_outputs_.set(output, emscripten::val::global("BigInt64Array").new_(num_elements));
break;
case ONNX_NAMESPACE::TensorProto_DataType_UINT32:
wnn_outputs_.set(output, emscripten::val::global("Uint32Array").new_(num_elements));
break;
case ONNX_NAMESPACE::TensorProto_DataType_UINT64:
wnn_outputs_.set(output, emscripten::val::global("BigUint64Array").new_(num_elements));
break;
default:
break;
}
}
}

View file

@ -19,12 +19,13 @@ namespace webnn {
ModelBuilder::ModelBuilder(const GraphViewer& graph_viewer, const logging::Logger& logger,
const emscripten::val& context, const emscripten::val& builder,
const DataLayout preferred_layout)
const DataLayout preferred_layout, const WebnnDeviceType wnn_device_type)
: graph_viewer_(graph_viewer),
logger_(logger),
wnn_context_(context),
wnn_builder_(builder),
preferred_layout_(preferred_layout) {}
preferred_layout_(preferred_layout),
wnn_device_type_(wnn_device_type) {}
Status ModelBuilder::Initialize() {
PreprocessInitializers();
@ -91,32 +92,66 @@ Status ModelBuilder::RegisterInitializers() {
for (const auto& pair : GetInitializerTensors()) {
const auto& tensor = *pair.second;
const auto& name = tensor.name();
if (Contains(skipped_initializers_, name))
// Optional tensors can be indicated by an empty name, just ignore it.
if (name.empty() || Contains(skipped_initializers_, name))
continue;
const auto& shape = tensor.dims();
std::vector<int32_t> dims;
if (shape.empty()) {
// This is a scalar initializer, WebNN requires a shape, make this a {1} tensor.
dims = {1};
} else {
std::transform(shape.cbegin(), shape.cend(),
std::back_inserter(dims),
[](int64_t dim) -> int32_t { return SafeInt<int32_t>(dim); });
}
// When the shape is empty, it is scalar initializer that dims = {};
std::transform(shape.cbegin(), shape.cend(),
std::back_inserter(dims),
[](int64_t dim) -> int32_t { return SafeInt<int32_t>(dim); });
emscripten::val desc = emscripten::val::object();
desc.set("dimensions", emscripten::val::array(dims));
auto data_type = tensor.data_type();
emscripten::val operand = emscripten::val::object();
if (data_type == ONNX_NAMESPACE::TensorProto_DataType_FLOAT) {
if (IsSupportedDataType(data_type, wnn_device_type_)) {
unpacked_tensors_.push_back({});
std::vector<uint8_t>& unpacked_tensor = unpacked_tensors_.back();
ORT_RETURN_IF_ERROR(onnxruntime::utils::UnpackInitializerData(tensor, unpacked_tensor));
auto num_elements = SafeInt<size_t>(Product(tensor.dims()));
desc.set("type", emscripten::val("float32"));
emscripten::val view{emscripten::typed_memory_view(num_elements,
reinterpret_cast<float*>(unpacked_tensor.data()))};
emscripten::val view = emscripten::val::undefined();
switch (data_type) {
case ONNX_NAMESPACE::TensorProto_DataType_BOOL:
desc.set("type", emscripten::val("uint8"));
view = emscripten::val{emscripten::typed_memory_view(num_elements,
reinterpret_cast<uint8_t*>(unpacked_tensor.data()))};
break;
case ONNX_NAMESPACE::TensorProto_DataType_FLOAT16:
desc.set("type", emscripten::val("float16"));
view = emscripten::val{emscripten::typed_memory_view(num_elements,
reinterpret_cast<uint16_t*>(unpacked_tensor.data()))};
break;
case ONNX_NAMESPACE::TensorProto_DataType_FLOAT:
desc.set("type", emscripten::val("float32"));
view = emscripten::val{emscripten::typed_memory_view(num_elements,
reinterpret_cast<float*>(unpacked_tensor.data()))};
break;
case ONNX_NAMESPACE::TensorProto_DataType_INT32:
desc.set("type", emscripten::val("int32"));
view = emscripten::val{emscripten::typed_memory_view(num_elements,
reinterpret_cast<int32_t*>(unpacked_tensor.data()))};
break;
case ONNX_NAMESPACE::TensorProto_DataType_INT64:
desc.set("type", emscripten::val("int64"));
view = emscripten::val{emscripten::typed_memory_view(num_elements,
reinterpret_cast<int64_t*>(unpacked_tensor.data()))};
break;
case ONNX_NAMESPACE::TensorProto_DataType_UINT32:
desc.set("type", emscripten::val("uint32"));
view = emscripten::val{emscripten::typed_memory_view(num_elements,
reinterpret_cast<uint32_t*>(unpacked_tensor.data()))};
break;
case ONNX_NAMESPACE::TensorProto_DataType_UINT64:
desc.set("type", emscripten::val("uint64"));
view = emscripten::val{emscripten::typed_memory_view(num_elements,
reinterpret_cast<uint64_t*>(unpacked_tensor.data()))};
break;
default:
break;
}
#ifdef ENABLE_WEBASSEMBLY_THREADS
// Workaround for WebAssembly multi-threads enabled since WebNN API only accepts non-shared ArrayBufferView.
// https://www.w3.org/TR/webnn/#typedefdef-mlnamedarraybufferviews
@ -188,18 +223,36 @@ Status ModelBuilder::RegisterModelInputOutput(const NodeArg& node_arg, bool is_i
const auto* type_proto = node_arg.TypeAsProto();
if (!type_proto || !type_proto->tensor_type().has_elem_type()) {
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
"The ", input_output_type, " of graph doesn't have elem_type: ", name);
"The ", input_output_type, " of graph doesn't have elem_type: ", name);
}
data_type = type_proto->tensor_type().elem_type();
switch (data_type) {
case ONNX_NAMESPACE::TensorProto_DataType_BOOL:
desc.set("type", emscripten::val("uint8"));
break;
case ONNX_NAMESPACE::TensorProto_DataType_FLOAT16:
desc.set("type", emscripten::val("float16"));
break;
case ONNX_NAMESPACE::TensorProto_DataType_FLOAT:
desc.set("type", emscripten::val("float32"));
break;
case ONNX_NAMESPACE::TensorProto_DataType_INT32:
desc.set("type", emscripten::val("int32"));
break;
case ONNX_NAMESPACE::TensorProto_DataType_INT64:
desc.set("type", emscripten::val("int64"));
break;
case ONNX_NAMESPACE::TensorProto_DataType_UINT32:
desc.set("type", emscripten::val("uint32"));
break;
case ONNX_NAMESPACE::TensorProto_DataType_UINT64:
desc.set("type", emscripten::val("uint64"));
break;
default: {
// TODO: support other type.
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT,
"The ", input_output_type, " of graph doesn't have valid type, name: ", name,
"The ", input_output_type, " of graph doesn't have valid type, name: ", name,
" type: ", type_proto->tensor_type().elem_type());
}
}
@ -246,14 +299,53 @@ Status ModelBuilder::AddOperations() {
Status ModelBuilder::AddOperandFromPersistMemoryBuffer(
const std::string& name, const void* buffer, const size_t size,
const std::vector<uint32_t> shape, const size_t element_size) {
const std::vector<uint32_t> shape, const int32_t data_type) {
auto persist_buffer = std::make_unique<uint8_t[]>(size);
uint8_t* dest = persist_buffer.get();
memcpy(dest, buffer, size);
emscripten::val view{emscripten::typed_memory_view(size / element_size, reinterpret_cast<const float*>(dest))};
emscripten::val view = emscripten::val::undefined();
emscripten::val desc = emscripten::val::object();
switch (data_type) {
case ONNX_NAMESPACE::TensorProto_DataType_BOOL:
view = emscripten::val{emscripten::typed_memory_view(size / sizeof(uint8_t),
reinterpret_cast<const uint8_t*>(dest))};
desc.set("type", emscripten::val("uint8"));
break;
case ONNX_NAMESPACE::TensorProto_DataType_FLOAT16:
view = emscripten::val{emscripten::typed_memory_view(size / sizeof(uint16_t),
reinterpret_cast<const uint16_t*>(dest))};
desc.set("type", emscripten::val("float16"));
break;
case ONNX_NAMESPACE::TensorProto_DataType_FLOAT:
view = emscripten::val{emscripten::typed_memory_view(size / sizeof(float),
reinterpret_cast<const float*>(dest))};
desc.set("type", emscripten::val("float32"));
break;
case ONNX_NAMESPACE::TensorProto_DataType_INT32:
view = emscripten::val{emscripten::typed_memory_view(size / sizeof(int32_t),
reinterpret_cast<const int32_t*>(dest))};
desc.set("type", emscripten::val("int32"));
break;
case ONNX_NAMESPACE::TensorProto_DataType_INT64:
view = emscripten::val{emscripten::typed_memory_view(size / sizeof(int64_t),
reinterpret_cast<const int64_t*>(dest))};
desc.set("type", emscripten::val("int64"));
break;
case ONNX_NAMESPACE::TensorProto_DataType_UINT32:
view = emscripten::val{emscripten::typed_memory_view(size / sizeof(uint32_t),
reinterpret_cast<const uint32_t*>(dest))};
desc.set("type", emscripten::val("uint32"));
break;
case ONNX_NAMESPACE::TensorProto_DataType_UINT64:
view = emscripten::val{emscripten::typed_memory_view(size / sizeof(uint64_t),
reinterpret_cast<const uint64_t*>(dest))};
desc.set("type", emscripten::val("uint64"));
break;
default:
break;
}
desc.set("dimensions", emscripten::val::array(shape));
desc.set("type", emscripten::val("float32"));
emscripten::val operand = emscripten::val::object();
#ifdef ENABLE_WEBASSEMBLY_THREADS
// Workaround for WebAssembly multi-threads enabled since WebNN API only accepts non-shared ArrayBufferView.

View file

@ -9,6 +9,7 @@
#include "model.h"
#include "core/framework/execution_provider.h"
#include "core/providers/webnn/builders/helper.h"
#include <emscripten.h>
#include <emscripten/val.h>
@ -20,8 +21,9 @@ class IOpBuilder;
class ModelBuilder {
public:
ModelBuilder(const GraphViewer& graph_viewer, const logging::Logger& logger, const emscripten::val& context,
const emscripten::val& builder, const DataLayout preferred_layout);
ModelBuilder(const GraphViewer& graph_viewer, const logging::Logger& logger,
const emscripten::val& context, const emscripten::val& builder,
const DataLayout preferred_layout, const WebnnDeviceType wnn_device_type);
~ModelBuilder() = default;
Status Compile(std::unique_ptr<Model>& model) ORT_MUST_USE_RESULT;
@ -40,7 +42,7 @@ class ModelBuilder {
// Add a constant operand (allocate persist buffer and move the ownership to mem_persist_buffers_).
Status AddOperandFromPersistMemoryBuffer(
const std::string& name, const void* buffer,
const size_t size, const std::vector<uint32_t> shape, const size_t element_size = 4);
const size_t size, const std::vector<uint32_t> shape, const int32_t data_type);
// Find if an output has a fuseable activation (e.g., Relu).
emscripten::val FindActivation(const Node& node, const NodeArg& output,
const InlinedHashSet<std::string> supported_nodes = {});
@ -50,6 +52,8 @@ class ModelBuilder {
DataLayout GetPreferredLayout() const { return preferred_layout_; }
WebnnDeviceType GetWebnnDeviceType() const { return wnn_device_type_; }
// The initializer will be processed separately, skip it as an initializer.
void AddInitializerToSkip(const std::string& tensor_name);
@ -66,6 +70,7 @@ class ModelBuilder {
emscripten::val wnn_context_ = emscripten::val::object();
emscripten::val wnn_builder_ = emscripten::val::object();
DataLayout preferred_layout_;
WebnnDeviceType wnn_device_type_;
std::vector<std::vector<uint8_t>> unpacked_tensors_;
InlinedHashMap<std::string, emscripten::val> wnn_operands_;
std::vector<std::string> input_names_;

View file

@ -2,6 +2,8 @@
// Copyright (c) Intel Corporation. All rights reserved.
// Licensed under the MIT License.
#include "core/providers/webnn/builders/helper.h"
#pragma once
namespace onnxruntime {
@ -27,7 +29,7 @@ class IOpBuilder {
public:
// Check if an operator is supported.
virtual bool IsOpSupported(const InitializedTensorSet& initializers, const Node& node,
const logging::Logger& logger) const = 0;
const WebnnDeviceType device_type, const logging::Logger& logger) const = 0;
};
} // namespace webnn

View file

@ -15,11 +15,21 @@ namespace webnn {
static OpBuilderRegistrations CreateOpBuilderRegistrations() {
OpBuilderRegistrations op_registrations;
{ // Unary
CreateUnaryOpBuilder("Cos", op_registrations);
CreateUnaryOpBuilder("Erf", op_registrations);
CreateUnaryOpBuilder("Floor", op_registrations);
CreateUnaryOpBuilder("Not", op_registrations);
CreateUnaryOpBuilder("Sin", op_registrations);
CreateUnaryOpBuilder("Sqrt", op_registrations);
}
{ // Binary
CreateBinaryOpBuilder("Add", op_registrations);
CreateBinaryOpBuilder("Sub", op_registrations);
CreateBinaryOpBuilder("Mul", op_registrations);
CreateBinaryOpBuilder("Div", op_registrations);
CreateBinaryOpBuilder("Pow", op_registrations);
}
{ // Activations
@ -28,6 +38,15 @@ static OpBuilderRegistrations CreateOpBuilderRegistrations() {
CreateActivationOpBuilder("Sigmoid", op_registrations);
}
{ // ArgMax/ArgMin
CreateArgMaxMinOpBuilder("ArgMax", op_registrations);
CreateArgMaxMinOpBuilder("ArgMin", op_registrations);
}
{ // Cast
CreateCastOpBuilder("Cast", op_registrations);
}
{ // Clip
CreateClipOpBuilder("Clip", op_registrations);
}
@ -41,8 +60,29 @@ static OpBuilderRegistrations CreateOpBuilderRegistrations() {
CreateConcatOpBuilder("Concat", op_registrations);
}
{ // Gemm
{ // Expand
CreateExpandOpBuilder("Expand", op_registrations);
}
{ // Gather
CreateGatherOpBuilder("Gather", op_registrations);
}
{ // Flatten
CreateFlattenOpBuilder("Flatten", op_registrations);
}
{ // Gemm/MatMul
CreateGemmOpBuilder("Gemm", op_registrations);
CreateGemmOpBuilder("MatMul", op_registrations);
}
{ // Logical
CreateLogicalOpBuilder("Equal", op_registrations);
}
{ // LayerNormalization
CreateNormalizationOpBuilder("LayerNormalization", op_registrations);
}
{ // Pool
@ -52,6 +92,11 @@ static OpBuilderRegistrations CreateOpBuilderRegistrations() {
CreatePoolOpBuilder("MaxPool", op_registrations);
}
{ // Reduction
CreateReductionOpBuilder("ReduceMax", op_registrations);
CreateReductionOpBuilder("ReduceMean", op_registrations);
}
{ // Reshape
CreateReshapeOpBuilder("Reshape", op_registrations);
}
@ -60,10 +105,26 @@ static OpBuilderRegistrations CreateOpBuilderRegistrations() {
CreateResizeOpBuilder("Resize", op_registrations);
}
{ // Slice
CreateSliceOpBuilder("Slice", op_registrations);
}
{ // Softmax
CreateSoftmaxOpBuilder("Softmax", op_registrations);
}
{ // Split
CreateSplitOpBuilder("Split", op_registrations);
}
{ // Transpose
CreateTransposeOpBuilder("Transpose", op_registrations);
}
{ // Unsqueeze
CreateUnsqueezeOpBuilder("Unsqueeze", op_registrations);
}
return op_registrations;
}

View file

@ -20,15 +20,28 @@ struct OpBuilderRegistrations {
const InlinedHashMap<std::string, const IOpBuilder*>& GetOpBuilders();
void CreateActivationOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
void CreateArgMaxMinOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
void CreateBinaryOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
void CreateCastOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
void CreateClipOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
void CreateConvOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
void CreateConcatOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
void CreateExpandOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
void CreateFlattenOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
void CreateGatherOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
void CreateGemmOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
void CreateLogicalOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
void CreateNormalizationOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
void CreatePoolOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
void CreateReductionOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
void CreateReshapeOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
void CreateResizeOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
void CreateSliceOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
void CreateSoftmaxOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
void CreateSplitOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
void CreateTransposeOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
void CreateUnaryOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
void CreateUnsqueezeOpBuilder(const std::string& op_type, OpBuilderRegistrations& op_registrations);
} // namespace webnn
} // namespace onnxruntime

View file

@ -52,8 +52,10 @@ WebNNExecutionProvider::WebNNExecutionProvider(
// WebNN EP uses NHWC layout for CPU XNNPACK backend and NCHW for GPU DML backend.
if (webnn_device_flags.compare("cpu") == 0) {
preferred_layout_ = DataLayout::NHWC;
wnn_device_type_ = webnn::WebnnDeviceType::CPU;
} else {
preferred_layout_ = DataLayout::NCHW;
wnn_device_type_ = webnn::WebnnDeviceType::GPU;
}
if (webnn_power_flags.compare("default") != 0) {
context_options.set("powerPreference", emscripten::val(webnn_power_flags));
@ -100,7 +102,7 @@ WebNNExecutionProvider::GetCapability(const onnxruntime::GraphViewer& graph_view
const auto& logger = *GetLogger();
const auto node_groups = webnn::GetSupportedNodes(graph_viewer, wnn_builder_, logger);
const auto node_groups = webnn::GetSupportedNodes(graph_viewer, wnn_builder_, wnn_device_type_, logger);
if (node_groups.empty()) {
return result;
@ -130,13 +132,18 @@ WebNNExecutionProvider::GetCapability(const onnxruntime::GraphViewer& graph_view
InlinedHashSet<const NodeArg*> subgraph_inputs;
InlinedHashSet<const NodeArg*> subgraph_outputs;
std::vector<const NodeArg*> ordered_subgraph_inputs;
std::vector<const NodeArg*> ordered_subgraph_outputs;
// Output should be unique. It may be produced as graph output and subgraph output.
InlinedHashSet<const NodeArg*> ordered_subgraph_outputs;
for (const auto& index : group) {
sub_graph->nodes.push_back(index);
const auto* node = graph_viewer.GetNode(index);
for (const auto* input : node->InputDefs()) {
if (!input->Exists()) {
// skip the placeholder inputs.
continue;
}
// if the node input was not produced by this subgraph, add it to the subgraph inputs.
if (node_outputs.count(input) == 0) {
if (subgraph_inputs.count(input) == 0) {
@ -151,7 +158,7 @@ WebNNExecutionProvider::GetCapability(const onnxruntime::GraphViewer& graph_view
node_outputs.insert(output_def);
// if output is overall graph output we need to produce it.
if (graph_outputs.count(output_def) != 0) {
ordered_subgraph_outputs.push_back(output_def);
ordered_subgraph_outputs.insert(output_def);
}
}
@ -161,7 +168,7 @@ WebNNExecutionProvider::GetCapability(const onnxruntime::GraphViewer& graph_view
const auto* output_def = output_defs[it->GetSrcArgIndex()];
if (subgraph_outputs.count(output_def) == 0) {
subgraph_outputs.insert(output_def);
ordered_subgraph_outputs.push_back(output_def);
ordered_subgraph_outputs.insert(output_def);
}
}
}
@ -213,7 +220,8 @@ common::Status WebNNExecutionProvider::Compile(const std::vector<FusedNodeAndGra
Node& fused_node = fused_node_and_graph.fused_node;
const onnxruntime::GraphViewer& graph_viewer(fused_node_and_graph.filtered_graph);
webnn::ModelBuilder builder(graph_viewer, *GetLogger(), wnn_context_, wnn_builder_, preferred_layout_);
webnn::ModelBuilder builder(graph_viewer, *GetLogger(), wnn_context_,
wnn_builder_, preferred_layout_, wnn_device_type_);
std::unique_ptr<webnn::Model> model;
ORT_RETURN_IF_ERROR(builder.Compile(model));
// Build map from input name to its index in input definitions.
@ -311,7 +319,13 @@ common::Status WebNNExecutionProvider::Compile(const std::vector<FusedNodeAndGra
void* output_buffer;
switch (output_type) {
case ONNX_NAMESPACE::TensorProto_DataType_BOOL:
case ONNX_NAMESPACE::TensorProto_DataType_FLOAT16:
case ONNX_NAMESPACE::TensorProto_DataType_FLOAT:
case ONNX_NAMESPACE::TensorProto_DataType_INT32:
case ONNX_NAMESPACE::TensorProto_DataType_INT64:
case ONNX_NAMESPACE::TensorProto_DataType_UINT32:
case ONNX_NAMESPACE::TensorProto_DataType_UINT64:
output_buffer = output_tensor.GetTensorMutableRawData();
break;
default:

View file

@ -6,6 +6,7 @@
#include "core/common/inlined_containers.h"
#include "core/framework/execution_provider.h"
#include "core/providers/webnn/builders/helper.h"
#include <emscripten.h>
#include <emscripten/val.h>
@ -44,6 +45,7 @@ class WebNNExecutionProvider : public IExecutionProvider {
emscripten::val wnn_builder_ = emscripten::val::object();
DataLayout preferred_layout_;
webnn::WebnnDeviceType wnn_device_type_;
InlinedHashMap<std::string, std::unique_ptr<onnxruntime::webnn::Model>> models_;
};
} // namespace onnxruntime