mirror of
https://github.com/saymrwulf/onnxruntime.git
synced 2026-07-23 19:32:23 +00:00
[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:
parent
05bea0d3c3
commit
a8c2f24ae0
36 changed files with 2130 additions and 187 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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':
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>());
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue