Skip to content

feat(ops): argMax(dim) DSL op with StableHLO lowering + CPU kernel#800

Merged
michalharakal merged 3 commits into
developfrom
functiongemma-selfcompile
Jul 8, 2026
Merged

feat(ops): argMax(dim) DSL op with StableHLO lowering + CPU kernel#800
michalharakal merged 3 commits into
developfrom
functiongemma-selfcompile

Conversation

@michalharakal

Copy link
Copy Markdown
Contributor

Adds argMax(tensor, dim) as a real, HW-agnostic tensor op — the index of the max
along a dim (ties → lowest index), returning Int32 indices. Non-differentiable.

Design (mirrors scaledDotProductAttention)

A single first-class op lowered at the StableHLO stage rather than needing a bespoke
primitive: ArgMaxOperationsConverter composes iota + reduce-max + broadcast_in_dim

  • compare EQ + select + reduce-min — the same stablehlo primitives the causal-mask
    code already emits, so no variadic reducer region is required. The compose happens at the
    StableHLO-primitive level, so no new DSL primitives are added.

Changes

  • TensorOps.argMax (no @Diff, non-nullable dim) → KSP tracing wrapper routes dim to op params.
  • VoidTensorOps shape floor (reduced shape, Int32 output).
  • DefaultCpuOps eager kernel (scalar reduction-to-index).
  • ArgMaxOperationsConverter + registration in StableHloConverterFactory (basic/extended/fast).
  • Delegation added to RecordingTensorOpsDecorator and the dag TestTensorOps.

Why

Lets an LLM export fold its logits → token-ids tail into the DSL trace (retiring an
external Python MLIR rewrite in FunctionGemma). argMax is now reusable for any model.

Tests

  • Eager numeric: last-dim, ties→lowest, middle-dim.
  • StableHLO lowering: the [1,24,262153]→[1,24] FunctionGemma tail emits iota/reduce-max/reduce-min/i32.
  • Full compile-hlo suite green.

🤖 Generated with Claude Code

…rnel

argMax is a real, HW-agnostic DSL op — the index of the max along a dim
(ties -> lowest index), returning Int32 indices. Non-differentiable.

Like scaledDotProductAttention it stays a single op and is lowered at the
StableHLO stage rather than having a dedicated hardware primitive:
ArgMaxOperationsConverter composes iota + reduce-max + broadcast + compare
+ select + reduce-min (the same stablehlo primitives the causal-mask code
already emits), so no variadic reducer region is needed. Registered in
StableHloConverterFactory (basic/extended/fast). Eager path is a scalar
reduction-to-index kernel in DefaultCpuOps. Shape floor in VoidTensorOps;
delegation added to RecordingTensorOpsDecorator and the dag TestTensorOps.

Enables the FunctionGemma compiled export to fold its logits->token-ids
tail into the DSL trace (retiring an external Python MLIR rewrite).

Tested: eager numeric (last-dim, ties, middle-dim) + StableHLO lowering
(the [1,24,262153]->[1,24] gemma tail); full compile-hlo suite green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

📖 Documentation Preview

The documentation has been built successfully for this PR.

Generated Files:

  • Operator documentation: docs/modules/operators/_generated_/
  • JSON schema output: operators.json

Artifacts:

  • Download the documentation-preview-800 artifact to view the complete documentation locally.

This comment will be updated automatically when the PR is updated.

`./gradlew apiDump` after adding the public `argMax` op. Updates the jvm ABI
dumps of every module that re-exposes it via `TensorOps` (interface method +
DSL/graph re-exports): lang-core, lang-dag, backend-cpu, compile-hlo,
compile-dag, compile-opt.

Also captures pre-existing untracked public API in compile-opt/compile-hlo
(FusedOpAllowList / OpGranularityPolicy / a converter ctor) that had drifted
out of the golden dumps on develop — apiDump is generated from the actual
code, so this only reconciles them.

Verified: apiCheck green; js/wasmJs/wasmWasi compile clean; yarn.lock unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

📖 Documentation Preview

The documentation has been built successfully for this PR.

Generated Files:

  • Operator documentation: docs/modules/operators/_generated_/
  • JSON schema output: operators.json

Artifacts:

  • Download the documentation-preview-800 artifact to view the complete documentation locally.

This comment will be updated automatically when the PR is updated.

…/wasm)

The eager DefaultCpuOps.argMax returned an Int32 payload inside a <FP32,Float>
tensor. JVM tolerated the boxing, but Kotlin/Native (linuxX64) and Wasm threw
`ClassCastException: Int cannot be cast to Float` when reading .data — a real
consumer bug, not just a test issue (surfaced by `allTests` in CI).

Fix: the eager kernel now materializes the indices as index-valued floats in the
input dtype, so the result is a consistent Tensor<T,V> readable on every target.
The traced/compiled form is unchanged — still a real i32 tensor (VoidTensorOps +
ArgMaxOperationsConverter), so the board vmfb path is unaffected.

Verified: DefaultCpuOpsArgMaxTest green on linuxX64 + jvm.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

📖 Documentation Preview

The documentation has been built successfully for this PR.

Generated Files:

  • Operator documentation: docs/modules/operators/_generated_/
  • JSON schema output: operators.json

Artifacts:

  • Download the documentation-preview-800 artifact to view the complete documentation locally.

This comment will be updated automatically when the PR is updated.

@michalharakal michalharakal merged commit 6ba0e77 into develop Jul 8, 2026
11 checks passed
@michalharakal michalharakal deleted the functiongemma-selfcompile branch July 8, 2026 17:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant