Skip to content

Commit 8ffc33c

Browse files
authored
Fix WebGPU Conv auto_pad=SAME_UPPER padding calculation (#27249)
The WebGPU Conv and ConvTranspose operators were producing incorrect results when using auto_pad=SAME_UPPER with strides > 1. Root cause: The head padding values were being unnecessarily recalculated after InferPadsAndOutputShape() had already computed the correct values. The recalculation formula could produce incorrect results. Fix: Simply use pads[0] and pads[1] directly, which already contain the correct head padding values computed upstream. This matches the behavior of the TypeScript implementation. Fixes #26734
1 parent b440277 commit 8ffc33c

File tree

3 files changed

+51
-8
lines changed

3 files changed

+51
-8
lines changed

onnxruntime/core/providers/webgpu/nn/conv.cc

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,11 @@ Status Conv<is_channels_last, is_fused>::ComputeInternal(ComputeContext& context
119119
const auto output_height = output_shape_vector[is_channels_last ? 1 : 2];
120120
const auto output_width = output_shape_vector[is_channels_last ? 2 : 3];
121121

122-
uint32_t auto_pad_adjust = conv_attrs_.auto_pad == AutoPadType::SAME_LOWER ? 1 : 0;
123-
auto pad0 = conv_attrs_.auto_pad == AutoPadType::NOTSET ? pads[0] : (pads[0] + pads[2] + auto_pad_adjust) / 2;
124-
auto pad1 = conv_attrs_.auto_pad == AutoPadType::NOTSET ? pads[1] : (pads[1] + pads[3] + auto_pad_adjust) / 2;
125-
std::vector<uint32_t> updated_pads{pad0, pad1};
122+
// pads[0] and pads[1] already contain the correct head (beginning) padding values
123+
// computed by InferPadsAndOutputShape() which handles auto_pad correctly.
124+
// For SAME_UPPER: head gets less padding (pad_needed / 2)
125+
// For SAME_LOWER: head gets more padding ((pad_needed + 1) / 2)
126+
std::vector<uint32_t> updated_pads{pads[0], pads[1]};
126127

127128
if (CanApplyIm2ColMatMulProgram(context,
128129
is_channels_last,

onnxruntime/core/providers/webgpu/nn/conv_transpose.cc

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,12 +92,11 @@ Status ConvTranspose<is_channels_last>::ComputeInternal(ComputeContext& context)
9292
inputs.push_back(bias);
9393
input_output_shapes.push_back(bias->Shape());
9494
}
95-
uint32_t auto_pad_adjust = conv_transpose_attrs_.auto_pad == AutoPadType::SAME_LOWER ? 1 : 0;
96-
auto pad0 = conv_transpose_attrs_.auto_pad == AutoPadType::NOTSET ? pads[0] : (pads[0] + pads[2] + auto_pad_adjust) / 2;
97-
auto pad1 = conv_transpose_attrs_.auto_pad == AutoPadType::NOTSET ? pads[1] : (pads[1] + pads[3] + auto_pad_adjust) / 2;
95+
// pads[0] and pads[1] already contain the correct head (beginning) padding values
96+
// computed by ComputePadsAndOutputShape() which handles auto_pad correctly.
9897
Tensor* output = context.Output(0, computed_output_shape);
9998
input_output_shapes.push_back(output_shape);
100-
auto program = CreateConvTranspose2DProgram(inputs, {pad0, pad1}, strides, dilations, output, is_channels_last, input_output_shapes, static_cast<uint32_t>(conv_transpose_attrs_.group));
99+
auto program = CreateConvTranspose2DProgram(inputs, {pads[0], pads[1]}, strides, dilations, output, is_channels_last, input_output_shapes, static_cast<uint32_t>(conv_transpose_attrs_.group));
101100
return context.RunProgram(program);
102101
}
103102

onnxruntime/test/providers/cpu/nn/conv_op_test.cc

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,49 @@ TEST(ConvTest, Conv2D_AutoPad1) {
502502
TestConvOp(attrs, {X, W}, {X_shape, W_shape}, expected_vals, Y_shape, true);
503503
}
504504

505+
// Regression test for issue #26734: SAME_UPPER with stride > 1
506+
// Tests asymmetric padding calculation that was incorrect in WebGPU EP
507+
TEST(ConvTest, Conv2D_AutoPad_SAME_UPPER_Stride2) {
508+
ConvOpAndTestAttributes attrs = {
509+
"SAME_UPPER", // auto_pad
510+
vector<int64_t>{1, 1}, // dilations
511+
1, // group
512+
vector<int64_t>{3, 3}, // kernel_shape
513+
{}, // pads
514+
vector<int64_t>{2, 2}, // strides > 1 triggers asymmetric padding
515+
{} // excluded EPs
516+
};
517+
518+
// 1x1x4x4 input
519+
vector<float> X = {1.0f, 2.0f, 3.0f, 4.0f,
520+
5.0f, 6.0f, 7.0f, 8.0f,
521+
9.0f, 10.0f, 11.0f, 12.0f,
522+
13.0f, 14.0f, 15.0f, 16.0f};
523+
vector<int64_t> X_shape = {1, 1, 4, 4};
524+
525+
// 3x3 kernel of all 1s for easy verification
526+
vector<float> W = {1.0f, 1.0f, 1.0f,
527+
1.0f, 1.0f, 1.0f,
528+
1.0f, 1.0f, 1.0f};
529+
vector<int64_t> W_shape = {1, 1, 3, 3};
530+
531+
// Output: 2x2 (ceil(4/2) = 2)
532+
// SAME_UPPER with total_pad=1: pad_head=0, pad_tail=1
533+
vector<int64_t> Y_shape = {1, 1, 2, 2};
534+
535+
// Expected values:
536+
// (0,0): 1+2+3+5+6+7+9+10+11 = 54
537+
// (0,1): 3+4+0+7+8+0+11+12+0 = 45
538+
// (1,0): 9+10+11+13+14+15+0+0+0 = 72
539+
// (1,1): 11+12+0+15+16+0+0+0+0 = 54
540+
auto expected_vals = {54.0f, 45.0f, 72.0f, 54.0f};
541+
542+
TestConvOp(attrs, {X, W}, {X_shape, W_shape}, expected_vals, Y_shape);
543+
544+
// NNAPI/CoreML EP requires weight to be an initializer
545+
TestConvOp(attrs, {X, W}, {X_shape, W_shape}, expected_vals, Y_shape, true);
546+
}
547+
505548
TEST(ConvTest, Conv2D_AutoPad2) {
506549
ConvOpAndTestAttributes attrs = {
507550
"SAME_LOWER", // auto_pad

0 commit comments

Comments
 (0)