diff --git a/onnxruntime/core/providers/webnn/builders/impl/expand_op_builder.cc b/onnxruntime/core/providers/webnn/builders/impl/expand_op_builder.cc index 665075018715f..b6d718b5c79ae 100644 --- a/onnxruntime/core/providers/webnn/builders/impl/expand_op_builder.cc +++ b/onnxruntime/core/providers/webnn/builders/impl/expand_op_builder.cc @@ -39,7 +39,7 @@ class ExpandOpBuilder : public BaseOpBuilder { void ExpandOpBuilder::AddInitializersToSkip(ModelBuilder& model_builder, const Node& node) const { const auto& input_defs = node.InputDefs(); const auto& shape_name = input_defs[1]->Name(); - // Only skip the shape input when it is a constant initializer AND the input has static shape. + // Skip the shape input when it is a constant initializer AND the input has static shape. // When the input has dynamic shape, we need the shape operand for dynamicExpand even if it's constant. if (model_builder.GetGraphViewer().GetConstantInitializer(shape_name) && !HasDynamicShape(*input_defs[0])) { diff --git a/onnxruntime/core/providers/webnn/builders/impl/qdq_op_builder.cc b/onnxruntime/core/providers/webnn/builders/impl/qdq_op_builder.cc index e07814521dafa..5d7e2c5620faf 100644 --- a/onnxruntime/core/providers/webnn/builders/impl/qdq_op_builder.cc +++ b/onnxruntime/core/providers/webnn/builders/impl/qdq_op_builder.cc @@ -85,10 +85,11 @@ Status QDQOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder, } } - // For per-axis quantization/dequantization and axis is not equal to input_rank - 1, - // we need to reshape the scale and zero_point tensors to make them broadcastable with the input tensor. + // For per-axis quantization/dequantization, the scale is 1-D. + // WebNN requires the scale and zero_point tensors to have the same rank as the input tensor. + // We need to reshape them to make them broadcastable with the input tensor. if (scale_shape.size() == 1 && input_rank > 1 && - block_size == 0 && axis != static_cast(input_rank - 1)) { + block_size == 0) { // Insert ones before and after the axis dimension for broadcasting of scale tensor. // Use emscripten::val::array() to support dynamic axis dim via input["shape"][axis]. emscripten::val target_shape = emscripten::val::array(); diff --git a/onnxruntime/core/providers/webnn/builders/model_builder.cc b/onnxruntime/core/providers/webnn/builders/model_builder.cc index 3667d42cb9a39..c5850cc29b84e 100644 --- a/onnxruntime/core/providers/webnn/builders/model_builder.cc +++ b/onnxruntime/core/providers/webnn/builders/model_builder.cc @@ -401,6 +401,7 @@ Status ModelBuilder::AddOperations() { const auto& node_indices = graph_viewer_.GetNodesInTopologicalOrder(); for (size_t i = 0; i < node_indices.size(); i++) { const auto* node(graph_viewer_.GetNode(node_indices[i])); + if (const auto* op_builder = GetOpBuilder(*node)) { ORT_RETURN_IF_ERROR(op_builder->AddToModelBuilder(*this, *node, logger_)); } else { diff --git a/onnxruntime/core/providers/webnn/webnn_execution_provider.cc b/onnxruntime/core/providers/webnn/webnn_execution_provider.cc index fa6b9ad0f5200..4a8363199e365 100644 --- a/onnxruntime/core/providers/webnn/webnn_execution_provider.cc +++ b/onnxruntime/core/providers/webnn/webnn_execution_provider.cc @@ -87,13 +87,15 @@ WebNNExecutionProvider::GetCapability(const onnxruntime::GraphViewer& graph_view const auto supported_nodes = webnn::GetSupportedNodes(graph_viewer, wnn_builder, wnn_device_type_, wnn_limits_, logger); + std::unordered_set supported_nodes_with_folded = supported_nodes; + const auto gen_metadef_name = [&]() { HashValue model_hash; int metadef_id = metadef_id_generator_.GenerateId(graph_viewer, model_hash); return MakeString(WEBNN, "_", model_hash, "_", metadef_id); }; - auto result = utils::CreateSupportedPartitions(graph_viewer, supported_nodes, {}, + auto result = utils::CreateSupportedPartitions(graph_viewer, supported_nodes_with_folded, {}, gen_metadef_name, WEBNN, kWebNNExecutionProvider, &node_unit_map, /*drop_constant_initializers*/ true); @@ -286,7 +288,9 @@ common::Status WebNNExecutionProvider::Compile(const std::vector int64_t { + auto it = dim_param_to_input_dim.find(operand); + if (it != dim_param_to_input_dim.end()) { + const size_t src_idx = it->second.first; + const size_t src_dim = it->second.second; + if (src_idx < runtime_input_shapes.size() && + src_dim < runtime_input_shapes[src_idx].size()) { + return runtime_input_shapes[src_idx][src_dim]; + } + } + auto fixed_it = fixed_dim_param_values.find(operand); + if (fixed_it != fixed_dim_param_values.end()) { + return fixed_it->second; + } + return -1; // unresolved + }; + + int64_t left_val = resolve_operand(left); + int64_t right_val = resolve_operand(right); + if (left_val >= 0 && right_val >= 0) { + output_shape[dim_idx] = left_val + right_val; + } + } + } } } @@ -458,12 +496,10 @@ common::Status WebNNExecutionProvider::Compile(const std::vector inferred) { + inferred = candidate; + } + } + } + + if (inferred > 0) { + LOGS_DEFAULT(WARNING) << "[WebNN] Unresolved output dim for [" << output_name + << "] at index " << dim_idx << " (dim_param: [" << unresolved_dim_param + << "]). Inferred from runtime inputs: " << inferred; + output_shape[dim_idx] = inferred; + } else { + LOGS_DEFAULT(ERROR) << "[WebNN] Failed to resolve dynamic output dimension for output [" + << output_name << "] at dim index [" << dim_idx + << "], dim_param: [" << unresolved_dim_param + << "]. No input dims available for inference."; + return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, + "[WebNN] Failed to resolve dynamic output dimension for output: ", output_name, + " at dim index: ", dim_idx, + ". dim_param: ", unresolved_dim_param); + } } }