WebGPU: Add HardSwish support - #29828
Conversation
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
This PR adds native HardSwish operator support to the WebGPU execution path so models using HardSwish can remain fully on the WebGPU EP (for f32/f16), avoiding CPU fallback and GPU↔CPU transfers.
Changes:
- Add a WebGPU WGSL implementation and kernel registration for
HardSwish(opset 14+). - Add corresponding ORT Web (JS/WebGPU) operator wiring for
HardSwishand update the supported-operators doc. - Add a WebGPU provider unit test covering
HardSwishfor float32 and float16.
Show a summary per file
| File | Description |
|---|---|
| onnxruntime/test/providers/webgpu/hardswish_test.cc | Adds WebGPU EP unit tests for HardSwish (f32/f16). |
| onnxruntime/core/providers/webgpu/webgpu_execution_provider.cc | Registers HardSwish kernel in the WebGPU EP kernel table. |
| onnxruntime/core/providers/webgpu/math/unary_elementwise_ops.h | Adds WGSL helper implementation string for HardSwish. |
| onnxruntime/core/providers/webgpu/math/unary_elementwise_ops.cc | Implements and registers the HardSwish unary elementwise kernel. |
| onnxruntime/core/providers/js/operators/unary.cc | Registers HardSwish for the JS EP unary operator set. |
| onnxruntime/core/providers/js/js_execution_provider.cc | Adds HardSwish kernel to the JS EP kernel registry table. |
| js/web/lib/wasm/jsep/webgpu/ops/unary-op.ts | Adds the WebGPU shader expression implementation for HardSwish. |
| js/web/lib/wasm/jsep/webgpu/op-resolve-rules.ts | Routes ONNX HardSwish to the new WebGPU unary implementation. |
| js/web/docs/webgpu-operators.md | Documents HardSwish as supported by WebGPU EP in ORT Web. |
Review details
- Files reviewed: 9/9 changed files
- Comments generated: 1
- Review effort level: Low
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
@adrastogi, I've fixed the copilot review comments. Could you please help trigger the build/test pipelines? |
|
@hariharans29, PTAL, thanks! |
Review: PR #29828 — WebGPU: Add HardSwish support (head
|
| x | x/6 + 0.5 | clamp(0,1) | y = x·clamp |
|---|---|---|---|
| -8 | -0.833… | 0 | 0 (saturated below) |
| -6 | -0.5 | 0 | 0 |
| -3 | 0 | 0 | 0 (left saturation edge) |
| -1 | 0.333… | 0.333… | -0.333… |
| 0 | 0.5 | 0.5 | 0 |
| 0.5 | 0.583… | 0.583… | 0.292… |
| 1 | 0.667… | 0.667… | 0.667… |
| 3 | 1.0 | 1.0 | 3 (right saturation edge) |
| 6 | 1.5 | 1 | 6 |
| 8 | 1.833… | 1 | 8 |
Both saturation edges (x = ±3) and both far-saturation regions (x = -8 and x = 8) are exercised, and 0.5 sits inside the strictly-linear region as a spot check. Good boundary set.
Kernel registration completeness
- Native WebGPU EP:
WEBGPU_ELEMENTWISE_KERNEL(HardSwish, 14, WebGpuSupportedFloatTypes())+KERNEL_CREATE_INFO(14, HardSwish)in the provider'sbuild_kernel_create_info_function_table. Standard pattern, mirrors HardSigmoid one line above. - JS EP: matching
JSEP_ELEMENTWISE_KERNEL(HardSwish, 14, HardSwish)+ create-info entry in the JS provider's table. - Opset: HardSwish was introduced at opset 14, so no
_VERSIONED_lower-bound kernel is needed. Correct.
Test
- Skips via
GTEST_SKIPifDefaultWebGpuExecutionProvider()returns null — standard hygiene. - Fixed input
{-6, -3, -1, 0, 1, 3, 6, 8, -8, 0.5}withkDims{2, 5}(asstd::vector<int64_t>after the Copilot fixup — better than raw C array, easier to pass toAddInput/AddOutput). - Expected values computed via
std::transformwith the exact ONNX formula (x * std::max(0.0f, std::min(1.0f, x / 6.0f + 0.5f))).reserve()'d before the transform. Clean. - Float32 branch: exact comparison.
- Float16 branch:
SetOutputAbsErr(0.01)+SetOutputRelErr(0.01). Sensible for fp16 given output range up to 8 (fp16 ULP near 8 is ~0.008 — the tolerance is one-ULP-ish, generous enough for the shader-sidef16(1/6)rounding plus themin/maxboundary noise). - Uses
ConfigEp(std::move(webgpu_ep)).RunWithConfig()which is the current ORT test-utility convention.
Two tests (Float32, Float16) via RunHardSwishTest<T>(). Enough to cover both dtype specializations.
Copilot review disposition
Copilot AI reviewed 9/9 files, 1 comment on hardswish_test.cc, resolved by e40a746 — the fixup changed the dims declaration to std::vector<int64_t> (matches the test-file idiom elsewhere in the tree). Small nit, addressed correctly.
CI
84 / 86 checks OK. Two unaccounted for — could be pending Azure Pipelines legs that require /azp run or workflows that no code in this PR would break. Worth eyeballing but nothing in the change is a plausible cause of a real regression on the missing legs.
Minor observations (not blockers)
- The JSEP path writes
vec4<${dataType}>(${dataType}(1.0 / 6.0)) * ${a}— firstf16(1.0/6.0)scalar cast (compile-time), then vec4 broadcast, then vec4 multiply. Slightly more verbose than the native EP's scalaralpha * vbut semantically identical in WGSL. Both compile down to the same broadcast-multiply. Fine as-is; if you want maximum symmetry with the native helper, factor out the scalar and rely on WGSL broadcast, but not worth churning for. webgpu-operators.mdcorrectly shows opset14+(matches the schema range). Good.- No
com.ms.internal.nhwcvariant — correct, HardSwish is a per-element unary and layout-agnostic, so the NHWC transformer doesn't need a separate kernel entry. - The test skips gracefully when the WebGPU EP isn't in the build — standard pattern, but worth noting that in a WebGPU-off build the tests always skip, so the coverage guard depends on someone actually running with
--use_webgpu.
Bottom line
Approve. The math is exactly the ONNX HardSwish spec, both the native WebGPU EP and JSEP paths implement it consistently, kernel registration is complete for opset 14+, and the test exercises both saturation edges plus the linear region for f32 and f16. The one Copilot nit was addressed. Merge when a maintainer signs off and the two missing CI checks come back green.
Description
HardSwishcurrently falls off the WebGPU partition because the EP has no native kernel and no decomposition pass rewrites it into WebGPU-supported ops. This change adds direct WebGPU support soHardSwishstays on device for f32/f16 models.Motivation and Context
HardSwishis common in mobile and vision models, such as MobileNet V3 and MediaPipe's Selfie Segmenter; without native WebGPU support it falls back to CPU, introducing avoidable GPU↔CPU transfers.HardSwishexecution on WebGPU with the same fixed semantics as the ONNX operator.Fix #29756