diff --git a/onnxruntime/core/providers/webnn/builders/helper.h b/onnxruntime/core/providers/webnn/builders/helper.h index 92aa9abc9fdf7..d94729e60d029 100644 --- a/onnxruntime/core/providers/webnn/builders/helper.h +++ b/onnxruntime/core/providers/webnn/builders/helper.h @@ -54,6 +54,10 @@ std::string GetShapeString(std::vector& shape) { return shape_info.str(); } +inline std::string GetTensorName(const ConstPointerContainer>& input_defs, const size_t index) { + return (input_defs.size() > index) ? std::string(input_defs[index]->Name()) : ""; +} + inline std::vector GetVecUint32FromVecInt64(const std::vector& int64_vec) { std::vector uint32_vec; uint32_vec.reserve(int64_vec.size()); diff --git a/onnxruntime/core/providers/webnn/builders/impl/pad_op_builder.cc b/onnxruntime/core/providers/webnn/builders/impl/pad_op_builder.cc index a2a1e2f2e599d..52b5518857773 100644 --- a/onnxruntime/core/providers/webnn/builders/impl/pad_op_builder.cc +++ b/onnxruntime/core/providers/webnn/builders/impl/pad_op_builder.cc @@ -178,8 +178,10 @@ bool PadOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers, return false; } for (size_t i = 1; i < input_defs.size(); i++) { - if (!Contains(initializers, input_defs[i]->Name())) { - LOGS(logger, VERBOSE) << "Input [" << input_defs[i]->Name() << "] must be known as initializer"; + // Optional tensors (constant_value, axes) 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 << "] must be known as initializer"; return false; } } diff --git a/onnxruntime/core/providers/webnn/builders/impl/reduction_op_builder.cc b/onnxruntime/core/providers/webnn/builders/impl/reduction_op_builder.cc index 1a702649b7f05..f446a7b81d1c0 100644 --- a/onnxruntime/core/providers/webnn/builders/impl/reduction_op_builder.cc +++ b/onnxruntime/core/providers/webnn/builders/impl/reduction_op_builder.cc @@ -134,8 +134,9 @@ bool ReductionOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializ return false; const auto& op_type = node.OpType(); + const std::string axes_name = GetTensorName(input_defs, 1); // If the optional input 'axes' is provided, it must be an initializer. - if (input_defs.size() > 1 && !Contains(initializers, input_defs[1]->Name())) { + if (!axes_name.empty() && !Contains(initializers, axes_name)) { LOGS(logger, VERBOSE) << "Input axes of " << op_type << " must be a constant"; return false; } diff --git a/onnxruntime/core/providers/webnn/builders/impl/resize_op_builder.cc b/onnxruntime/core/providers/webnn/builders/impl/resize_op_builder.cc index 186d1e7c1035a..9018f8c96f300 100644 --- a/onnxruntime/core/providers/webnn/builders/impl/resize_op_builder.cc +++ b/onnxruntime/core/providers/webnn/builders/impl/resize_op_builder.cc @@ -120,8 +120,9 @@ Status ResizeOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, std::vector scales_hw; std::vector sizes_hw; std::vector axes; + std::string scales_name = GetTensorName(input_defs, 2); const bool is_nhwc = model_builder.GetPreferredLayout() == DataLayout::NHWC; - if (input_defs.size() == 3) { // Use scales. + if (!scales_name.empty()) { // Use scales. ORT_RETURN_IF_NOT(GetResizeScales(initializers, node, scales, logger), "Error getting resize scales"); if (is_nhwc) { scales_hw = {scales[1], scales[2]}; @@ -129,7 +130,7 @@ Status ResizeOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, scales_hw = {scales[2], scales[3]}; } options.set("scales", emscripten::val::array(scales_hw)); - } else { // We already checked number of inputs in IsOpSupportedImpl. + } else { // Use sizes, we already checked inputs in IsOpSupportedImpl. std::vector output_sizes; ORT_RETURN_IF_NOT(GetResizeOutputSizes(initializers, node, output_sizes, logger), "Error getting resize output_sizes"); @@ -203,26 +204,31 @@ bool ResizeOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers } { // scales and sizes (if present) must be initializers. - if (input_defs.size() < 3) { - LOGS(logger, VERBOSE) << "Input scales or sizes of Resize must be known"; - return false; - } + const std::string scales_name = GetTensorName(input_defs, 2); + const std::string sizes_name = GetTensorName(input_defs, 3); - // scales - if (input_defs.size() == 3 && !Contains(initializers, input_defs[2]->Name())) { + // scales (scales may be empty tensor) + bool has_scales = !scales_name.empty(); + if ((has_scales && !Contains(initializers, scales_name)) || (!has_scales && node.SinceVersion() == 11)) { LOGS(logger, VERBOSE) << "Input scales of Resize must be known"; return false; } - // sizes - if (input_defs.size() > 3 && !Contains(initializers, input_defs[3]->Name())) { + // sizes (sizes may be empty tensor) + bool has_sizes = !sizes_name.empty(); + if (has_sizes && !Contains(initializers, sizes_name)) { LOGS(logger, VERBOSE) << "Input sizes of Resize must be known"; return false; } + if (has_scales && has_sizes) { + LOGS(logger, VERBOSE) << "Only one of 'scales' and 'sizes' can be specified"; + return false; + } + const bool is_nhwc = node.Domain() == kMSInternalNHWCDomain; // We want to check if the scales or sizes are not trying to resize on N/C channels here. - if (input_defs.size() == 3) { // We are using scales. + if (has_scales) { // We are using scales. std::vector scales; if (!GetResizeScales(initializers, node, scales, logger)) return false; @@ -251,7 +257,9 @@ bool ResizeOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers LOGS(logger, VERBOSE) << "Resize: scale_w: " << scale_w << " is not a whole number"; return false; } - } else { + } + + if (has_sizes) { // We are using sizes. std::vector output_sizes; if (!GetResizeOutputSizes(initializers, node, output_sizes, logger)) diff --git a/onnxruntime/core/providers/webnn/builders/impl/slice_op_builder.cc b/onnxruntime/core/providers/webnn/builders/impl/slice_op_builder.cc index e48cf35012652..4e0628581abf2 100644 --- a/onnxruntime/core/providers/webnn/builders/impl/slice_op_builder.cc +++ b/onnxruntime/core/providers/webnn/builders/impl/slice_op_builder.cc @@ -123,8 +123,10 @@ bool SliceOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers, // Inputs: starts, ends, axes, and steps must be constant initializers if present. for (size_t i = 1; i < input_defs.size(); i++) { - if (!Contains(initializers, input_defs[i]->Name())) { - LOGS(logger, VERBOSE) << "Input [" << input_defs[i]->Name() << "] of " << op_type + // 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"; return false; } diff --git a/onnxruntime/core/providers/webnn/builders/impl/split_op_builder.cc b/onnxruntime/core/providers/webnn/builders/impl/split_op_builder.cc index d568d4e625077..e9a600a5933af 100644 --- a/onnxruntime/core/providers/webnn/builders/impl/split_op_builder.cc +++ b/onnxruntime/core/providers/webnn/builders/impl/split_op_builder.cc @@ -136,9 +136,9 @@ bool SplitOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers, int32_t axis = helper.Get("axis", 0); axis = SafeInt(HandleNegativeAxis(axis, rank)); - if (input_defs.size() == 2) { - // Inputs contains optional 'split' input - const auto& split_name = input_defs[1]->Name(); + const std::string split_name = GetTensorName(input_defs, 1); + // Inputs contain optional 'split' input. + if (!split_name.empty()) { if (!Contains(initializers, split_name)) { LOGS(logger, VERBOSE) << "The split must be a constant initializer."; return false; @@ -166,7 +166,7 @@ bool SplitOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& initializers, 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) { + } else { if (helper.HasAttr("num_outputs")) { // Split has 'num_outputs' attribute when opset is 18. const int32_t num_outputs = helper.Get("num_outputs", 1); diff --git a/onnxruntime/core/providers/webnn/builders/impl/squeeze_unsqueeze_op_builder.cc b/onnxruntime/core/providers/webnn/builders/impl/squeeze_unsqueeze_op_builder.cc index 2a1672c001b0e..9f6ccb98f79dd 100644 --- a/onnxruntime/core/providers/webnn/builders/impl/squeeze_unsqueeze_op_builder.cc +++ b/onnxruntime/core/providers/webnn/builders/impl/squeeze_unsqueeze_op_builder.cc @@ -138,8 +138,8 @@ bool SqueezeUnsqueezeOpBuilder::IsOpSupportedImpl(const InitializedTensorSet& in // Squeeze/Unsqueeze opset 13 uses input 1 as axes, it needs to be an initializer. if (node.SinceVersion() >= 13) { - if (input_defs.size() > 1) { - const auto& axes_name = input_defs[1]->Name(); + const std::string axes_name = GetTensorName(input_defs, 1); + if (!axes_name.empty()) { if (!Contains(initializers, axes_name)) { LOGS(logger, ERROR) << "Input axes of " << op_type << " is not present and constant"; return false;