[WebGPU] Handle empty-rank MatMul and DequantizeLinear correctly (#33171)
### Change and motivation
Models with optional quantized LoRA adapters can represent an inactive
adapter with zero-rank tensors. These valid inputs produce empty
dequantization results and MatMuls with a zero inner dimension. WebGPU
must handle them without binding null input buffers or issuing
zero-sized dispatches. The adapter tensors are overridable
initializer-backed inputs, so their zero-rank defaults cannot be treated
as permanently constant when adapters can be activated or removed at
runtime.
This change returns correctly shaped empty outputs without GPU work and
initializes nonempty zero-K MatMul outputs with zero, bias and any
applicable activation. Empty weight transposes used by Conv are handled
before dispatch. DequantizeLinear validates scale and zero-point shapes
before returning for empty input, with safe handling of zero dimensions
during block-size inference.
The MatMul shape helper checks leading batch dimensions directly when
selecting its flattening shortcut, preserving batch broadcasting when K
is zero. Empty-input handling precedes the current MatMul execution
planner, while explicitly forced algorithms still undergo zero-K
prerequisite validation. General batch broadcasting preserves zero
dimensions using the same rule as the common broadcast helper; offset
generation reuses the validated output batch dimensions. Regressions
cover zero batches on either operand, eager and deferred offset
generation, and rejection of incompatible zero/non-unit batches. The CPU
FP32 and FP64 zero-K kernels initialize the complete broadcast output
tensor, not just one M-by-N slice. CPU-only regressions poison
preallocated outputs and verify every element across repeated runs: 120
elements for broadcast MatMul and six elements for FusedMatMul with
transposed A=[2,0,3] and vector B=[0], in both FP32 and FP64. Regression
tests cover FP32/FP16, vectors, broadcasting, empty outputs, Conv
bias/activation and malformed quantization inputs. Capture tests retain
stable GPU-bound tensors, assert capture, and reset output buffers
between runs to verify that replay computes the expected result. CPU
fallback is disabled in GPU regressions.
### Validation
- Native ORT 1.30 builds and targeted source checks passed on Windows,
including contrib-enabled and contrib-disabled configurations;
repository-pinned lint passed. The shared MatMul helper also passes
32-bit x86 compile checks with and without NDEBUG and with implicit
64-to-32 narrowing treated as an error; a static assertion verifies
32-bit ptrdiff_t.
- Actual compact adapters passed base -> rewrite -> webpage summary ->
base on native CPU FP32 and an RTX 4070 browser FP16 run with full
WebGPU validation and capture enabled. Repeated responses matched within
each backend, and all eight replayed CPU responses matched the GPU
responses exactly.
- CPU prefill/decode logits matched the reference exactly for base, both
active adapters and restored base states.
- Native CPU FP32 broadcast and zero-batch regressions passed twice per
shape, including the sentinel-filled 120-element output and zero batches
with a nonzero inner dimension. Affected CPU/WebGPU sources and tests
compile with and without NDEBUG.
- [macOS ARM64 CPU Debug
CI](https://github.com/microsoft/onnxruntime/actions/runs/37890352409/job/113689652274)
executed and passed the FP64 broadcast/zero-batch regressions and the
shared-helper eager/deferred-offset and incompatible-batch tests on
correctness commit 938581b989.
- [macOS ARM64 WebGPU Release
CI](https://github.com/microsoft/onnxruntime/actions/runs/37890352409/job/113689652248)
executed and passed the FP32/FP16 zero-K matrix/batch, vector/scalar and
empty-output tests on that correctness commit. The shared capture-test
helper now resolves its graph using the normal OpTester resolve options
before serialization. Both consumers compile with and without NDEBUG,
and pinned lint passes; the corrected capture setup has also passed
targeted Windows Debug execution as described below.
- The expanded native Windows Debug suite, with the full CPU registry
and bundled Dawn/D3D12, executed all 20 focused regressions in 13.131
seconds, including the FP32/FP64 transposed zero-K right-vector cases
added in 3b55d1471b. Coverage also includes shared-helper offsets,
WebGPU FP32/FP16 zero-K/empty outputs, graph capture/replay, Conv
bias/activation/empty outputs, and empty DequantizeLinear validation.
All 20 passed; none were skipped or missing. Capture assertions, stable
bindings, replay sentinels and disabled CPU fallback remained enabled.
- After merging current main and resolving the MatMul dispatcher/test
conflicts, the rebuilt native Windows Debug runner passed all 20 focused
regressions without skips, plus 10 additional upstream MatMul
execution/algorithm tests. Six upstream subgroup, subgroup-matrix and
split-K tests were skipped because the local adapter lacks their
required features/configuration; these remain covered by CI. Forced
naive zero-K execution, forced packed zero-K rejection, and
dynamic-shape algorithm reselection passed locally.
Fresh cross-platform CI, including the corrected WebGPU capture
regressions, must pass before merge.
---------
Co-authored-by: Mustapha Jaber <mustjab+microsoft@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>