mirror of
https://github.com/saymrwulf/onnxruntime.git
synced 2026-07-30 20:18:08 +00:00
[WebNN] Support negative steps for slice (#22871)
Slice with negative steps can be emulated by reverse+slice.
This commit is contained in:
parent
558ae8621c
commit
afbb53937c
3 changed files with 51 additions and 59 deletions
|
|
@ -85,7 +85,7 @@ operators and the supported opset domain/versions in **WebNN EP** by ONNX Runtim
|
|||
| ReduceSumSquare | ai.onnx(7-10, 11-12, 13-17, 18+) | reduceSumSquare | ✓ | ✓ | Input 'axes' if present should be a constant |
|
||||
| Relu | ai.onnx(7-12, 13, 14+) | relu | ✓ | ✓ | |
|
||||
| Reshape | ai.onnx(7-12, 13, 14-18, 19-20, 21+) | reshape | ✓ | ✓ | Input 'shape' should be a constant, 0 dimension value in 'shape' is not supported |
|
||||
| Resize | ai.onnx(11-12, 13-17, 18, 19+) | resample2d | ✓ | ✓ | Only supports 4-D input, antialias == 0, coordinate_transformation_mode == 'half_pixel', exclude_outside == 0, keep_aspect_ratio_policy == 'stretch', 'linear' and 'nearest' modes, input 'scales' and 'sizes' if present must be a constant |
|
||||
| Resize | ai.onnx(11-12, 13-17, 18, 19+) | resample2d | ✓ | ✓ | Only supports 4-D input, antialias == 0, exclude_outside == 0, keep_aspect_ratio_policy == 'stretch', 'linear' and 'nearest' modes, input 'scales' and 'sizes' if present must be a constant |
|
||||
| ScatterElements | ai.onnx(11-12, 13-15, 16-17, 18+) | scatterElements | ✗ | ✓ | Only supports 'reduction' == 'none' |
|
||||
| ScatterND | ai.onnx(11-12, 13-15, 16-17, 18+) | scatterND | ✗ | ✓ | Only supports 'reduction' == 'none' |
|
||||
| Shape | ai.onnx(7-12, 13-14, 15-18, 19-20, 21+) | slice | ✓ | ✓ | |
|
||||
|
|
@ -95,7 +95,7 @@ operators and the supported opset domain/versions in **WebNN EP** by ONNX Runtim
|
|||
| Softplus | ai.onnx(7+) | softplus | ✓ | ✓ | |
|
||||
| Softsign | ai.onnx(7+) | softsign | ✓ | ✓ | |
|
||||
| Sin | ai.onnx(7+) | sin | ✓ | ✓ | |
|
||||
| Slice | ai.onnx(7-9, 10, 11-12, 13+) | slice | ✓ | ✓ | Input 'starts', 'ends', 'axes', and 'steps' if present must be a constant, only supports 'steps' value >= 1 |
|
||||
| Slice | ai.onnx(7-9, 10, 11-12, 13+) | slice, reverse | ✓ | ✓ | Input 'starts', 'ends', 'axes', and 'steps' if present must be a constant |
|
||||
| Softmax | ai.onnx(7-10, 11-12, 13+) | softmax | ✓ | ✓ | |
|
||||
| Split | ai.onnx(7-10, 11-12, 13-17, 18+) | split | ✓ | ✓ | Input 'split' if present should be a constant |
|
||||
| Sqrt | ai.onnx(7-12, 13+) | sqrt | ✓ | ✓ | |
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ inline std::string GetTensorName(const ConstPointerContainer<std::vector<NodeArg
|
|||
return (input_defs.size() > index) ? std::string(input_defs[index]->Name()) : "";
|
||||
}
|
||||
|
||||
inline std::vector<uint32_t> GetVecUint32FromVecInt64(const std::vector<int64_t>& int64_vec) {
|
||||
inline std::vector<uint32_t> GetVecUint32FromVecInt64(gsl::span<const int64_t> int64_vec) {
|
||||
std::vector<uint32_t> uint32_vec;
|
||||
uint32_vec.reserve(int64_vec.size());
|
||||
std::transform(int64_vec.begin(), int64_vec.end(),
|
||||
|
|
|
|||
|
|
@ -40,8 +40,7 @@ void SliceOpBuilder::AddInitializersToSkip(ModelBuilder& model_builder, const No
|
|||
}
|
||||
}
|
||||
|
||||
Status SliceOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder,
|
||||
const Node& node,
|
||||
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;
|
||||
|
|
@ -49,10 +48,7 @@ Status SliceOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder,
|
|||
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);
|
||||
std::vector<int32_t> steps(rank);
|
||||
emscripten::val input = model_builder.GetOperand(input_defs[0]->Name());
|
||||
|
||||
// Copy the data from the starts/ends/axes/steps initializers.
|
||||
std::vector<int64_t> input_starts;
|
||||
|
|
@ -76,8 +72,7 @@ Status SliceOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder,
|
|||
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 ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "Data type for starts and ends inputs is not supported in this build.");
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
|
|
@ -89,31 +84,55 @@ Status SliceOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder,
|
|||
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); });
|
||||
std::transform(compute_metadata.steps_.cbegin(), compute_metadata.steps_.cend(), steps.begin(),
|
||||
[](int64_t i) { return SafeInt<uint32_t>(i); });
|
||||
// Check if reverse op is needed.
|
||||
std::vector<uint32_t> reverse_axes;
|
||||
emscripten::val reverse_output = input;
|
||||
for (size_t i = 0; i < rank; ++i) {
|
||||
if (compute_metadata.steps_[i] < 0) {
|
||||
reverse_axes.push_back(SafeInt<uint32_t>(i));
|
||||
compute_metadata.steps_[i] = -compute_metadata.steps_[i];
|
||||
compute_metadata.starts_[i] = input_shape[i] - 1 - compute_metadata.starts_[i];
|
||||
compute_metadata.ends_[i] = input_shape[i] - 1 - compute_metadata.ends_[i];
|
||||
}
|
||||
}
|
||||
if (!reverse_axes.empty()) {
|
||||
emscripten::val reverse_options = emscripten::val::object();
|
||||
reverse_options.set("axes", emscripten::val::array(reverse_axes));
|
||||
reverse_options.set("label", node.Name() + "_reverse");
|
||||
reverse_output = model_builder.GetBuilder().call<emscripten::val>("reverse", input, reverse_options);
|
||||
}
|
||||
|
||||
emscripten::val options = emscripten::val::object();
|
||||
options.set("strides", emscripten::val::array(steps));
|
||||
options.set("label", node.Name());
|
||||
emscripten::val output = model_builder.GetBuilder().call<emscripten::val>("slice", inputs,
|
||||
emscripten::val::array(starts),
|
||||
emscripten::val::array(sizes),
|
||||
options);
|
||||
// Check if slice op is needed.
|
||||
bool is_slice_required = false;
|
||||
for (size_t i = 0; i < rank; ++i) {
|
||||
if (compute_metadata.steps_[i] != 1 || compute_metadata.starts_[i] != 0 ||
|
||||
compute_metadata.ends_[i] != input_shape[i]) {
|
||||
is_slice_required = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
emscripten::val output = reverse_output;
|
||||
if (is_slice_required) {
|
||||
std::vector<uint32_t> starts = GetVecUint32FromVecInt64(compute_metadata.starts_);
|
||||
std::vector<uint32_t> steps = GetVecUint32FromVecInt64(compute_metadata.steps_);
|
||||
std::vector<uint32_t> sizes(rank);
|
||||
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 options = emscripten::val::object();
|
||||
options.set("strides", emscripten::val::array(steps));
|
||||
options.set("label", node.Name());
|
||||
output = model_builder.GetBuilder().call<emscripten::val>("slice", reverse_output, emscripten::val::array(starts),
|
||||
emscripten::val::array(sizes), options);
|
||||
}
|
||||
|
||||
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 {
|
||||
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();
|
||||
|
|
@ -133,39 +152,12 @@ bool SliceOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers,
|
|||
// Optional tensors (axes, steps) can be indicated by an empty name, just ignore it.
|
||||
const std::string input_name = GetTensorName(input_defs, i);
|
||||
if (!input_name.empty() && !Contains(initializers, input_name)) {
|
||||
LOGS(logger, VERBOSE) << "Input [" << input_name << "] of " << op_type
|
||||
<< " [" << name << "] must be known as initializer";
|
||||
LOGS(logger, VERBOSE) << "Input [" << input_name << "] of " << op_type << " [" << name
|
||||
<< "] must be known as initializer";
|
||||
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 less than 1.
|
||||
if (data_type == ONNX_NAMESPACE::TensorProto_DataType_INT64) {
|
||||
if (std::any_of(reinterpret_cast<int64_t*>(unpacked_tensor.data()),
|
||||
reinterpret_cast<int64_t*>(unpacked_tensor.data() + unpacked_tensor.size()),
|
||||
[](int64_t i) { return i < 1; })) {
|
||||
LOGS(logger, VERBOSE) << "WebNN slice doesn't support steps less than 1";
|
||||
return false;
|
||||
}
|
||||
} else if (data_type == ONNX_NAMESPACE::TensorProto_DataType_INT32) {
|
||||
if (std::any_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; })) {
|
||||
LOGS(logger, VERBOSE) << "WebNN slice doesn't support steps less than 1";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue