Skip to content

WebGPU: enable INT64 for Equal/Sub/Where/ReduceSum under enable_int64 flag to support Gemma4#29392

Merged
feich-ms merged 15 commits into
mainfrom
user/feich/webgpu_int64_equal_sub_where_reducesum
Jul 7, 2026
Merged

WebGPU: enable INT64 for Equal/Sub/Where/ReduceSum under enable_int64 flag to support Gemma4#29392
feich-ms merged 15 commits into
mainfrom
user/feich/webgpu_int64_equal_sub_where_reducesum

Conversation

@feich-ms

@feich-ms feich-ms commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Summary

These four ops were forcing CPU fallback when inputs were INT64, preventing WebGPU graph capture. Converted from static macro registration to the factory function pattern (same as Cast/Unsqueeze/Expand/Range) so INT64 type constraints are included when enable_int64=true.

enable_int64 is always true when enable_graph_capture=trueGetKernelRegistry calls RegisterKernels(true, true) unconditionally in that path — so this fix has no effect on the default (non-graph-capture) execution path.

Changed ops

Op Versions
Equal 7–10, 11–12, 13–18, 19+
Sub 7–12, 13, 14+
Where 9–15, 16+
ReduceSum 1–10, 11–12, 13+

Approach

Each op follows the established factory function pattern:

  • CreateXVersionedKernelInfo<Start, End>(bool enable_int64) / CreateXKernelInfo<Since>(bool enable_int64)
  • Removed from static build_kernel_create_info_function_table[]
  • Registered in RegisterKernels() alongside Cast/Unsqueeze/Expand/Range

ReduceSum version 13+ retains InputMemoryType(OrtMemTypeCPUInput, 1) (axes input must be on CPU) — unchanged from the original registration.

Where factory functions delegate to GetOpTypeConstraints(enable_int64, /*enable_bool=*/true) rather than a local duplicate type list. All three files (where.cc, binary_elementwise_ops.cc, reduction_ops.cc) use stack-allocated KernelDefBuilder() consistent with other WebGPU factory functions, and drop the unused webgpu_execution_provider.h include.

Runtime fixes for INT64 inference

BinaryElementwise (Sub, Equal) and Where required additional shader-level fixes to correctly handle INT64 tensors at inference time:

  • Vectorization disabled for INT64: INT64 is stored as vec2<u32> (8 bytes/element) and has no vec4 representation. Vectorization is now disabled when either input is INT64, and component=1 is used for correct buffer binding.
  • Correct dispatch size: vec_size is computed as element count (not rounded-up vec4 count) for INT64 outputs.
  • Scalar input path: The scalar broadcast path no longer applies an extra .x dereference on top of GetByOffset, which already extracts the i32 element for INT64.
  • Where non-broadcast path: Non-broadcast INT64 Where now uses the element-per-thread shader path instead of the vec4 fast path that would truncate values to i32.
  • Cache key: is_int64 is included in the Where cache hint, preventing float and INT64 shaders from incorrectly sharing a cached entry.
  • Shape indices for INT64: Shape indices are registered for non-broadcast INT64 Where so BroadcastedIndicesToOffset has the required helpers available.
  • Equal INT64 with bool output: The element-wise path now explicitly reads 4 consecutive INT64 elements per thread and packs them into a vec4<bool> before writing to the bool output buffer. Previously, only element 0 was read and broadcast across all 4 positions of the comparison, giving wrong results.

Testing

  • All 1174 nodes of a Gemma4 WebGPU decoder model assigned to WebGpuExecutionProvider (previously ~20 nodes fell back to CPU due to INT64 Equal/Sub/Where/ReduceSum)
  • End-to-end graph capture inference: ~87 tok/s on Gemma4-E2B-it INT4
  • USE_WEBGPU-guarded unit tests added for all four ops with kEnableInt64=1: Sub_int64_webgpu, Equal_int64_webgpu, Where_int64_webgpu, ReduceSum_int64_webgpu

Related

@feich-ms feich-ms changed the title webgpu: enable INT64 for Equal/Sub/Where/ReduceSum under enable_int64 flag WebGPU: enable INT64 for Equal/Sub/Where/ReduceSum under enable_int64 flag Jun 29, 2026
@feich-ms feich-ms added the ep:WebGPU ort-web webgpu provider label Jun 29, 2026
@feich-ms feich-ms requested a review from Copilot June 29, 2026 15:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the WebGPU EP kernel registration for Equal, Sub, Where, and ReduceSum so that INT64 type constraints are included when enable_int64=true, avoiding unintended CPU fallback (notably in graph-capture scenarios).

Changes:

  • Converted Equal/Sub/Where/ReduceSum from static macro/table-based registration to factory-function registration, enabling conditional INT64 type constraints.
  • Wired the new factory registrations into RegisterKernels(enable_graph_capture, enable_int64) alongside existing conditional-int64 kernels (e.g., Cast/Unsqueeze/Expand/Range).
  • Preserved existing special-casing (e.g., ReduceSum v13+ axes input remains OrtMemTypeCPUInput).

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
onnxruntime/core/providers/webgpu/webgpu_execution_provider.cc Removes static table entries for the 4 ops and registers them via RegisterKernels() with enable_int64 propagation.
onnxruntime/core/providers/webgpu/tensor/where.h Declares Where kernel factory functions to support conditional INT64 registration.
onnxruntime/core/providers/webgpu/tensor/where.cc Implements Where kernel factory functions and conditional type constraints.
onnxruntime/core/providers/webgpu/reduction/reduction_ops.h Declares ReduceSum kernel factory functions to support conditional INT64 registration.
onnxruntime/core/providers/webgpu/reduction/reduction_ops.cc Implements ReduceSum kernel factory functions with conditional INT64 type constraints and preserves CPU axes input for v13+.
onnxruntime/core/providers/webgpu/math/binary_elementwise_ops.h Declares Equal/Sub kernel factory functions to support conditional INT64 registration.
onnxruntime/core/providers/webgpu/math/binary_elementwise_ops.cc Implements Equal/Sub kernel factory functions with conditional INT64 type constraints.

Comment thread onnxruntime/core/providers/webgpu/tensor/where.cc Outdated
Comment thread onnxruntime/core/providers/webgpu/tensor/where.cc Outdated
Comment thread onnxruntime/core/providers/webgpu/tensor/where.cc Outdated
Comment thread onnxruntime/core/providers/webgpu/tensor/where.cc Outdated
Comment thread onnxruntime/core/providers/webgpu/math/binary_elementwise_ops.cc Outdated
Comment thread onnxruntime/core/providers/webgpu/reduction/reduction_ops.cc Outdated
Comment thread onnxruntime/core/providers/webgpu/reduction/reduction_ops.cc Outdated
Comment thread onnxruntime/core/providers/webgpu/reduction/reduction_ops.cc Outdated
@feich-ms feich-ms force-pushed the user/feich/webgpu_int64_equal_sub_where_reducesum branch from cc3a5a2 to a6a73a4 Compare June 30, 2026 09:24
@feich-ms feich-ms requested a review from Copilot June 30, 2026 09:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.

Comment thread onnxruntime/core/providers/webgpu/math/binary_elementwise_ops.cc
Comment thread onnxruntime/core/providers/webgpu/math/binary_elementwise_ops.cc
Comment thread onnxruntime/core/providers/webgpu/tensor/where.cc
Comment thread onnxruntime/core/providers/webgpu/reduction/reduction_ops.cc
Comment thread onnxruntime/core/providers/webgpu/webgpu_execution_provider.cc

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Comment thread onnxruntime/core/providers/webgpu/math/binary_elementwise_ops.cc Outdated
Comment thread onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Comment thread onnxruntime/core/providers/webgpu/math/binary_elementwise_ops.cc Outdated
@feich-ms feich-ms marked this pull request as ready for review July 1, 2026 04:12
@feich-ms feich-ms changed the title WebGPU: enable INT64 for Equal/Sub/Where/ReduceSum under enable_int64 flag WebGPU: enable INT64 for Equal/Sub/Where/ReduceSum under enable_int64 flag to support Gemma4 Jul 2, 2026
@hariharans29

Copy link
Copy Markdown
Member

Review: PR #29392 — WebGPU: enable INT64 for Equal/Sub/Where/ReduceSum under enable_int64 flag to support Gemma4

LGTM overall. The enable_int64 gating is scoped correctly (only reachable via graph capture, so no impact on the default execution path), the factory-function registration mirrors the Cast/Unsqueeze/Expand/Range pattern already in the EP, and the shader-level INT64 fixes are all necessary — the Equal-INT64→bool packing fix in particular corrects a real silent-wrong-answer that existed before this PR. A few non-blocking items below.

What's good

  1. The factory-function refactor is the right shape. Matches Cast/Unsqueeze/Expand/Range exactly, including the explicit template instantiations at the bottom of each .cc and the forward declarations in the headers. The KERNEL_CREATE_INFO entries for these four ops are removed from the static table with explanatory comments, and the new registrations land in RegisterKernels(). No double-registration risk.
  2. ReduceSum v13+ retains InputMemoryType(OrtMemTypeCPUInput, 1) — the axes input still stays on CPU. Easy to miss in a refactor, handled correctly.
  3. The Equal INT64→bool fix is a real correctness fix, not just a new code path. The old scalar path (input_a_value_t(GetByOffset("0").x) applied component-wise to the vec4 comparison) was silently comparing only element 0 and broadcasting the result across all 4 output positions. The new code reads all 4 elements per thread and packs them properly, with element_count-guarded reads for lanes 1–3.
  4. Vectorization gating is comprehensive: vectorize = !is_int64 && (...) on the outer flag, and the "consider enabling vectorize via divisible-by-4 check" branch is also !is_int64 gated, so INT64 tensors always take the element-per-thread path.
  5. Cache-hint fix in Where (.CacheHint(is_broadcast, is_int64)) prevents INT64 and float shaders from being confused for one another in the shader cache. That would have been a nasty runtime bug otherwise.
  6. Test coverage exists for all 4 ops via USE_WEBGPU-guarded unit tests that flip on kEnableInt64=1 — small tensors, known outputs, exercising the actual WebGPU path.
  7. CI is green (86 / 86) on the latest commit after multiple iterations.

Non-blocking items

  1. Missing #include <memory> — the Optional Lint C++ leg is flagging cpplint warnings in three files:

    • onnxruntime/core/providers/webgpu/math/binary_elementwise_ops.cc:461
    • onnxruntime/core/providers/webgpu/reduction/reduction_ops.cc:86
    • onnxruntime/core/providers/webgpu/tensor/where.cc:202

    All three: Add #include <memory> for make_unique<> [build/include_what_you_use]. Transitively included today so builds pass, but reviewdog is right — add explicit #include <memory> at the top of each .cc.

  2. The i32-range limitation for INT64 deserves more than a code comment. The PR comments make this clear:

    "int64 arithmetic in the WebGPU shader operates on the low 32 bits only (i32 element type). Values outside the int32 range [-2^31, 2^31-1] will produce incorrect results."

    That's an out-of-spec silent-wrong-answer for values ≥ 2³¹ on Sub/Equal/Where/ReduceSum, only mitigated by the fact that enable_int64 currently only activates under graph capture and Gemma4-style workloads use INT64 tensors only for token positions / attention masks that fit in i32. Suggest documenting it near the kEnableInt64 config key so users toggling this flag directly (not via graph capture) know, or logging a one-time warning at kernel-selection time when enable_int64=true.

  3. Style consistency nit in WhereProgram::GenerateShaderCode. The int64 branch uses raw storage-buffer accessors (c_data[...], a_data[...], output_data[global_idx] = ...) while the surrounding non-int64 code uses output.SetByOffset("global_idx", ...) / X.GetByOffset(...). Both work; the raw-access style is legitimate for the packed-bool byte extraction (c_data[offset_c / 4u] & (0xffu << (u32(offset_c % 4u) * 8u))), but a_data[offset_a] / output_data[global_idx] = ... could stay in the standard accessor form for uniformity.

  4. Sanity-check the Equal INT64→bool dispatch bound-checking. The Equal INT64→bool path uses is_output_int64=false → vec_size = (size+3)/4 and reads inputs one element at a time with base = global_idx * 4u. Lanes 1–3 are guarded against base + K < n; lane 0 (base itself) is not. If the workgroup rounding dispatches threads with global_idx >= vec_size, base could exceed n. Under robust buffer access an OOB read returns 0 and 0 == 0 → true would be written via SetByOffset("global_idx", ...), which should silently drop OOB stores — so this is probably fine, but it's the same class of bug that Copilot-swe-agent already caught in commit 563d5f5. Worth confirming that either SetByOffset bounds-checks global_idx or that the shader has an explicit early-return if (global_idx >= uniforms.vec_size) { return; } at the top of this branch. If not, a leading if (base >= uniforms.element_count) { return; } is cheap insurance.

  5. Prefer a WebGPU-EP-owner sign-off before merge. qjia7 has been requested but hasn't reviewed here yet. This PR touches more of the EP than WebGPU: Add indirect dispatch for flash attention graph capture to support Gemma4 #29236 did (kernel registration, shader packing math, cache-hint keys), so I'd wait for qjia7 to look before merging even after we approve.

hariharans29
hariharans29 previously approved these changes Jul 2, 2026
@feich-ms feich-ms requested a review from hariharans29 July 3, 2026 01:32
feich-ms and others added 7 commits July 3, 2026 10:51
… flag

These four ops were forcing CPU fallback when inputs were INT64, preventing
WebGPU graph capture. Converted from static macro registration to the factory
function pattern (same as Cast/Unsqueeze/Expand/Range) so INT64 type constraints
are included when enable_int64=true (which is always set when
enable_graph_capture=true via GetKernelRegistry).

Co-Authored-By: Claude <noreply@anthropic.com>
…4 changes

- Use GetOpTypeConstraints(enable_int64, true) in Where factory functions instead of the local WhereOpTypeConstraints helper
- Replace KernelDefBuilder::Create() with KernelDefBuilder() in Where and ReduceSum factory functions for consistency with other WebGPU kernels
- Remove unused webgpu_execution_provider.h includes from where.cc, binary_elementwise_ops.cc, and reduction_ops.cc

Co-Authored-By: Claude <noreply@anthropic.com>
…/Sub/Where/ReduceSum

Add USE_WEBGPU-guarded unit tests with kEnableInt64=1 for all four ops:
- Sub_int64_webgpu, Equal_int64_webgpu (element_wise_ops_test.cc)
- Where_int64_webgpu (where_op_test.cc)
- ReduceSum_int64_webgpu (reduction_ops_test.cc, opset 13 with axes input)

Add NOTE comments on Sub, Equal, and ReduceSum factory functions documenting
that int64 arithmetic uses i32 element type (low 32 bits only), matching the
same limitation already documented in Range.

Co-Authored-By: Claude <noreply@anthropic.com>
BinaryElementwise: disable vec4 vectorization for INT64 inputs (stored as
vec2<u32>), use component=1 for correct buffer binding, and compute vec_size
as element count rather than rounded-up vec4 count for INT64 outputs.
Fix scalar input path to not apply .x dereference on top of GetByOffset
which already extracts the i32 element for INT64.

Where: fix non-broadcast INT64 path to use the correct element-per-thread
shader instead of the vec4 fast path that truncates to i32. Add is_int64 to
the cache hint to prevent float and INT64 shaders sharing a cache entry.
Register shape indices for non-broadcast INT64 so BroadcastedIndicesToOffset
has the required helpers available.

Co-Authored-By: Claude <noreply@anthropic.com>
Equal with INT64 inputs and bool output was dispatching vec_size=(N+3)/4
threads but only reading element 0 of each input and broadcasting it as
a vec4 comparison.  Fix: in the element-wise path, when is_int64_ and
output is Boolx4, explicitly read 4 consecutive elements per thread and
pack them into vec4<bool> before SetByOffset.

ReduceSum_int64_webgpu test declared output shape {} (scalar) but
ReduceSum opset-13 defaults to keepdims=1, so the output is {1}.

Co-Authored-By: Claude <noreply@anthropic.com>
…nds checks and update test to size=5

- Add `element_count` uniform to BinaryElementwiseProgram
- Guard INT64+bool output lanes 1-3 with `if (base+Xu < n)` to avoid OOB
  reads when tensor size is not a multiple of 4
- Update Equal_int64_webgpu test from size=4 to size=5 to exercise tail case
@feich-ms feich-ms force-pushed the user/feich/webgpu_int64_equal_sub_where_reducesum branch from 95d6187 to 4b96d3b Compare July 3, 2026 02:51
Comment thread onnxruntime/core/providers/webgpu/tensor/where.cc
Comment thread onnxruntime/core/providers/webgpu/reduction/reduction_ops.cc
Comment thread onnxruntime/core/providers/webgpu/math/binary_elementwise_ops.cc Outdated
feich-ms and others added 2 commits July 3, 2026 21:21
…lementwise

- Fix Equal INT64 broadcast bug: remove erroneous `is_int64 ||` from the
  element-wise condition in ComputeInternal so non-scalar INT64 broadcast
  correctly routes to the broadcast path (vec4<i32>==vec4<i32> works fine)
- Extract `can_use_element_wise_mode` to replace duplicated scalar/shape
  condition, shared by both the INT64+bool fast path and the general path
- Add ORT_ENFORCE in ReduceSum to guard against future INT64 vectorization
- Add comment in where.cc explaining why INT64 is excluded from the fast path
- Add Equal tests: size%4==0, lhs_scalar, rhs_scalar, broadcast, broadcast_size%4==0
- Add ReduceSum test: size%4==0
- Add Where broadcast test

Co-Authored-By: Claude <noreply@anthropic.com>
The fast path uses global_idx as a vec4 index (vec_size=output_size/4),
so c_input.GetByOffset reads a packed-bool u32 as vec4<bool> directly.
For INT64, vec_size=output_size (element index), so the same GetByOffset
call would read the wrong condition word — hence the is_int64_ branch
uses explicit bit extraction via offset_c/4u and byte masking.

Co-Authored-By: Claude <noreply@anthropic.com>
Comment thread onnxruntime/core/providers/webgpu/math/binary_elementwise_ops.cc
Comment thread onnxruntime/core/providers/webgpu/tensor/where.cc Outdated
Comment thread onnxruntime/core/providers/webgpu/reduction/reduction_ops.cc Outdated
feich-ms and others added 3 commits July 6, 2026 11:01
- where.cc: use c_input.GetByOffset helper instead of raw c_data access
- reduction_ops.cc: replace tautological ORT_ENFORCE with meaningful
  framework capability check (ToProgramVariableDataType(INT64,4)==InvalidType)
- binary_elementwise_ops.cc: clarify that INT64 broadcast uses this path
  correctly with component=1

Co-Authored-By: Claude <noreply@anthropic.com>
…ec4 structure

Instead of a dedicated code path for INT64+bool output (Equal), integrate INT64
handling into the existing element-wise and broadcast structure:
- Read 4 component-1 elements into vec4 (splat for scalar inputs)
- Reuse the generic expression_ for all types
- Split output write: bool output uses packed SetByOffset, INT64 output writes
  element-by-element with bounds guards
- Remove ORT_ENFORCE from ReduceSum (reviewer feedback: unnecessary guard)
- Add Sub_webgpu_int64_broadcast test case

Co-Authored-By: Claude <noreply@anthropic.com>
Comment thread onnxruntime/core/providers/webgpu/math/binary_elementwise_ops.cc Outdated
Comment thread onnxruntime/core/providers/webgpu/math/binary_elementwise_ops.cc Outdated
Comment thread onnxruntime/core/providers/webgpu/math/binary_elementwise_ops.cc Outdated
Comment thread onnxruntime/core/providers/webgpu/math/binary_elementwise_ops.cc Outdated
feich-ms and others added 2 commits July 6, 2026 16:38
- Rename is_int64_ to is_int64_input_ and pass is_int64_output to the
  program instead of computing it locally from output var_type
- Rename is_int64/is_output_int64 to is_int64_input/is_int64_output in
  ComputeInternal for consistency
- Add per-lane bounds guards for INT64 input reads in element-wise path
  (matching cast.cc pattern) to avoid OOB reads when size % 4 != 0

Co-Authored-By: Claude <noreply@anthropic.com>
Comment thread onnxruntime/core/providers/webgpu/math/binary_elementwise_ops.cc Outdated
Comment thread onnxruntime/core/providers/webgpu/math/binary_elementwise_ops.cc Outdated
…se in output

Move `base` and `element_count` declarations to a shared location before
the element-wise/broadcast branch so both paths can reference them in the
INT64 output block. Use `base` instead of `global_idx * 4u` for clarity.

Co-Authored-By: Claude <noreply@anthropic.com>
@feich-ms feich-ms force-pushed the user/feich/webgpu_int64_equal_sub_where_reducesum branch from b9741c3 to 0371cb5 Compare July 7, 2026 01:04
@feich-ms feich-ms enabled auto-merge (squash) July 7, 2026 03:24
@feich-ms feich-ms merged commit ee91e35 into main Jul 7, 2026
87 checks passed
@feich-ms feich-ms deleted the user/feich/webgpu_int64_equal_sub_where_reducesum branch July 7, 2026 04:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ep:WebGPU ort-web webgpu provider

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants