[js/web] Support float16 in Conv3DNaive WebGPU shader - #32357
Conversation
The Conv3DNaive shader resolves the WGSL element type into `t` via tensorTypeToWsglStorageType() but never uses it: the generated WGSL hardcodes f32 in 12 places (accessor return types, vec2/3/4 literals, the accumulator initialiser and the final store) while the storage buffers are declared with the tensor's actual dtype. For an fp16 model this mismatch produces all-zero output on current Chromium (older versions raised a WGSL validation error instead). Replace the hardcoded f32 with the already-computed `t`. No new imports, no change to the fp32 path. Adds conv3dncdhw_f16.jsonc with six cases covering the scalar, vec2, vec3 and vec4 remainder branches plus padding and bias. All values are multiples of 0.5 so partial sums are exact in float16 and results are independent of accumulation order; 12/12 pass on webgpu and wasm with zero difference. Measured on Chromium 148 / Apple M5 Pro: single Conv3D fp16 27.9 ms vs fp32 41.7 ms (1.49x); a 30-layer 3D U-Net 96.4 ms vs 146.3 ms (1.52x), rel. error vs fp32 0.069% / 0.87%. Fixes microsoft#22974
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
|
@microsoft-github-policy-service agree |
There was a problem hiding this comment.
Pull request overview
Adds float16 support to the JSEP WebGPU Conv3D shader.
Changes:
- Uses storage element types in generated WGSL.
- Adds six float16 Conv3D cases.
- Registers the new WebGPU test suite.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
conv3d_naive_webgpu.ts |
Updates WGSL types for float16. |
conv3dncdhw_f16.jsonc |
Adds float16 Conv3D coverage. |
suite-test-list.jsonc |
Registers the tests. |
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
@microsoft-github-policy-service agree |
|
Xinzi He (@Novestars) Thanks for the contribution. Since the jsep will be deprecated soon and replaced by wasm + native webgpu, could you check whether the issue still exists in wasm+webgpu instead of wasm + jsep? If yes, the fixing in there will be appreciated. Thanks. |
|
Thanks Jiajia Qin (@qjia7). Checked on 1.30.0-dev.20260904 (Chrome 152, Apple M5 Pro): the native WebGPU EP (onnxruntime-web/webgpu import) is already correct for fp16 Conv3D — the same models give max diff 7.3e-4 vs the wasm fp16 reference, and fp16 is 1.55× faster than fp32 (11.9 vs 18.5 ms at [1,1,48,168,168]). conv3d_naive.cc uses the x_value_t/x_element_t aliases throughout, which is exactly what this PR does for JSEP. So the bug is JSEP-only; the default onnxruntime-web / /all imports still return all zeros for fp16 Conv3D. Per docs/JSEP_Deprecation.md correctness fixes in existing JSEP kernels are still accepted, so I'm happy to keep this as a minimal JSEP-only fix for users still on the default import, or close it if you'd rather steer people to onnxruntime-web/webgpu — your call. |
Description
Adds float16 support to the
Conv3DNaiveWebGPU shader in the JSEP WebGPU EP.conv3d_naive_webgpu.tsalready resolves the WGSL element type at line 324:but the generated shader hardcodes
f32in 12 places while the storage buffers are declared withinputs[0].dataType. For an fp16 model this produces a type mismatch between the accessor functions and the buffers.This PR replaces those 12 hardcoded
f32with${t}. No new imports, no changes outside this file, no C++ changes.Motivation and Context
Fixes #22974.
Conv3D in float16 has never worked on the WebGPU EP. Yulong Wang (@fs-eire) noted in #22974 ("Need to implement f16 support for Conv3D") in Dec 2024.
The failure mode has since changed and is now worse. On older Chromium it raised a WGSL validation error:
On Chromium 148 it silently returns all zeros — no error, no warning, and roughly 9× faster than the fp32 path, so it looks like a successful, fast run.
Minimal repro (single
Convnode, input[1,1,8,32,32]f16, weight[8,1,3,3,3], strides 1, pads 1):Verification (locally built js/web,
npm run pull:wasm+npm run build)Single Conv3D, input
[1,1,48,168,168]f16, weight[8,1,3,3,3]:fp16 is 1.49× faster than fp32 and numerically correct.
A full 3D U-Net (48×168×168, 32 base channels, ~30 conv layers):
1.52× faster. The 0.872% end-to-end relative difference is the accumulated effect of the f16 accumulator across ~30 layers — see the open question below.
A 2D model was run as a regression check and is unaffected (fp16 6.49 ms, fp32 13.5 ms, agreeing as before).
Not size-dependent — it fails identically at
[1,1,48,168,168], so this is distinct from the f16 index-arithmetic issue in #28976.Tested on Chromium 148 / Apple M5 Pro (
metal-3), against onnxruntime-web 1.29.0 and 1.30.0-dev.20260826-b1f76d586a.Tests
Added
js/web/test/data/ops/conv3dncdhw_f16.jsonc(registered in the webgpu section ofsuite-test-list.jsonc, next toconv3dncdhw.jsonc), following the existingpad_f16.jsoncconvention of keeping float16 coverage in its own file.Six cases, with channel counts chosen to exercise every branch of the shader's inner loop:
vec4vec4+vec2remaindervec4+vec3remainderAll input and weight values are multiples of 0.5, so every partial sum is exactly representable in float16 and the expected outputs do not depend on accumulation order. Verified against the locally built runtime: 12/12 pass on both webgpu and wasm with max diff exactly 0.0.
Every case fails on webgpu before the patch (all-zero output) and passes after.
Open question
This substitution also makes the accumulator
f16(var value = ${t}(0)), so a3×3×3×C_indot product now accumulates in half precision. Keeping the accumulator inf32and casting only at the store would be numerically safer for large channel counts, but requires extra casts aroundgetBiasByOutputCoordsand the activation snippet. I kept this PR to the minimal type fix — happy to follow up with f32 accumulation, or to fold it in here if preferred.The measured 1.5× speedup is consistent with this shader being load-bound: the
vec4<f16>loads halve the load traffic relative tovec4<f32>.