Skip to content

fix(hip): unify result shapes and accept dynamic batched MatMul - #581

Draft
fhanuman wants to merge 10 commits into
mainfrom
fix/shared-result-shape-construction
Draft

fix(hip): unify result shapes and accept dynamic batched MatMul#581
fhanuman wants to merge 10 commits into
mainfrom
fix/shared-result-shape-construction

Conversation

@fhanuman

@fhanuman fhanuman commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Issue

ONNX-to-HIP conversion and reifyResultShapes independently computed result extents. They could therefore disagree on the DPS destination shape for broadcast, Gemm, MatMul, reductions, Transpose, Gather/GatherND/GatherElements, and MatMulNBits. Reductions with keepdims = 0 could map dynamic output dimensions to the wrong input axes, while dynamic MatMul batch extents could conceal a partial per-axis broadcast that one constant stride cannot represent.

Fix

Centralize each affected shape rule in HipShapeUtils and use it for both destination construction and reification; MatMul and Gemm also share the rule with their static verifiers. Reifiers now return FailureOr, validate before emitting IR, and preserve rank-zero success. MatMul carries independent operand batch counts and strides to the runtime, which rejects dynamically concealed partial broadcasts through the existing recoverable error path instead of dispatching hipBLASLt with an invalid layout.

fhanuman and others added 2 commits July 28, 2026 08:57
Share broadcast, Gemm, and MatMul extent logic so converters allocate the same shapes exposed downstream, while preserving cached MatMul ABI compatibility and rejecting unsupported partial batch broadcasts.

Co-Authored-By: GPT-5.6 Sol <noreply@openai.com>
Made-with: Cursor
Emit explicit errors for invalid strided-batch shapes and document why repeated broadcast operands reuse their first mixed shape.

Co-Authored-By: GPT-5.6 Sol <noreply@openai.com>
Made-with: Cursor
@github-actions

Copy link
Copy Markdown

Thanks for opening a PR!

This project follows LLVM's incremental-development and AI-tool-use
guidance. See CONTRIBUTING.md
for the project workflow.

Before requesting review, please check that:

  1. The change is focused. Substantial work links the relevant issue
    or design discussion.
  2. The PR documents relevant test results and updates affected
    documentation.
  3. If AI tools provided substantial assistance, the description
    explains what was assisted and how it was validated, and commit
    trailers identify the tool. The contributor has reviewed and
    understands the result.

Reviewers are assigned through
CODEOWNERS where ownership
is configured.

@github-actions github-actions Bot added the large-pr Soft size threshold (>500 LOC or >10 files). Reviewer signal, not a block. label Jul 28, 2026
@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown

L2 Accuracy Results (EP vs CPU)

Model Combined L2 Total Elems Skipped NaN/Inf
conv_test_hybrid 4.8668E-07 64 0
GroupQueryAttention_seq256 25.2366 2621440 0
MatMulNBits_o_seq128 259.906 368640 0
QMoE_seq128 34.957 368640 0

Threshold: 0.01 | Run: 3522 - Commit: 237e3a3

@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown

MorphiZen EP Performance Results

Model QPS Session (s) 1st Infer (ms) CPU% Mem (MB)
full_model_seq128 7.52 10.02 366 3 1244
GroupQueryAttention_seq128 4322.41 1.68562 11 6 310
matmul_down_seq128 522.23 2.46 73 3 351

EPContext Export Performance

Model QPS Session (s) 1st Infer (ms) CPU% Mem (MB)
full_model_seq128 7.55 43.73 356 3 15591

EPContext Import Performance

Model QPS Session (s) 1st Infer (ms) CPU% Mem (MB)
full_model_seq128 7.54 9.38 359 3 15761

OGA Benchmark Results

Model Warmup Reps Prompt Len Gen Tokens TTFT (ms) TPS Peak Mem (GB) GPU Mem (GB)
gpt-oss-20b-webgpu-int4-rtn-block-32 1 5 128 128 170.6 79.5 1.33 13.54
Llama-3.1-8B-awq-g128-int4-asym-fp16-onnx-dml 1 5 128 128 267.2 40.1 1.22 6.43

OGA Wheel Smoke (Python benchmark_e2e.py)

Model TTFT (ms) TPS
Llama-3.1-8B-awq-g128-int4-asym-fp16-onnx-dml 191 39.1

Run: 3522 - Commit: 237e3a3

fhanuman and others added 5 commits July 29, 2026 16:23
Remove Python numeric additions from the demonstration branch so the PR stays scoped to compiler changes and targeted IR coverage.

Co-Authored-By: GPT-5.6 Sol <noreply@openai.com>
Made-with: Cursor
Consolidate MatMul on one runtime ABI, align shape helper failure handling, and tighten compiler comments and LIT coverage.

Co-Authored-By: GPT-5.6 Sol <noreply@openai.com>
Made-with: Cursor
…apes

Restore support for `[?, H, M, K] @ [?, H, K, N]`, which the strided-batch check
rejected for any dynamic batch extent spanning more than one batch axis even
though nothing broadcasts and one stride per operand is exact. Representability
is now decided per axis, so only a provable partial broadcast is rejected.

Extend the shared result-shape rule to reductions, which still computed their
destination independently of `reifyResultShapes`: a positional dim copy is wrong
under `keepdims = 0` once a reduced axis precedes a kept one, and gating the
converter on the axes operand *count* while reification gated on it being
*constant* made the two disagree for opset-13+ constant axes. One
`computeReductionDimMap` now backs the static shape, the mixed shape, and result
type inference, replacing ReduceProd's private copy of the rule.

Split each shape category into a pure `infer*` function of static shapes and a
`reify*` function that validates through it before touching the builder, so a
failure cannot leave stray dim ops behind; `reifyGemmResultShape` previously
emitted `tensor.dim` for A and B before validating the optional C. The split
also gives `GemmOp` the static shape verification it lacked. Route the remaining
`hip.max` / `hip.min` destinations through the broadcast helper, share the
variadic pairwise chain between Max and Min, and derive each hipBLASLt batch
stride by comparing the operand's matrix count against the output's.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Made-with: Cursor
Co-authored-by: Cursor <cursoragent@cursor.com>
Replace the sentinel error channel in `verifyHipOpShape`, where an empty outer
vector meant "the callee already diagnosed", with a `FailureOr` returning helper.
Both callers are single-destination ops, so the speculative one-shape-per-init
generality goes away too and a verifier now pairs directly with its `infer*`
rule instead of restating an adaptor lambda.

Express "not known at compile time" as `std::optional` rather than a parallel
`bool`: `resolveReductionAxes` returns an optional view over caller-owned
storage, and `inferReduceResultType` / `createReductionEmptyTensor` take it
directly, so no caller can pass axes that contradict a flag.

Narrow the public surface to shape rules. The reduction dimension map and the
static broadcast fold have no callers outside `HipShapeUtils.cpp` and become
file-static; the map now uses `llvm::SmallBitVector` over the closed axis domain
and `std::optional` in place of a `-1` sentinel, which was ambiguous next to
ONNX negative-axis indices. Move the non-template destination helpers out of the
header that all 87 conversion units include, and assert the right-alignment
precondition whose unsigned subtraction would otherwise wrap silently.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Made-with: Cursor
Co-authored-by: Cursor <cursoragent@cursor.com>
`reifyBroadcastShape` and `reifyReductionWithKeepdims` have no callers outside
`HipShapeUtils.cpp`, so they join the dimension map and the static broadcast fold
as file-static. Every remaining declaration in the header now has a real
external consumer, and the header exposes only shape rules.

Check every dimension in `createEmptyTensorFromReifiedShape` before
materializing any index value: bailing out partway through left stray constants
behind, which is what the surrounding no-stray-ops contract forbids. Guard
`extractConstantIntVector` against the null Value that optional ONNX operands
arrive as, since `getDefiningOp` would dereference it.

Pass the operand's row and column extents to the batch-stride helper instead of
a `function_ref` that materializes the product, which drops the lazily-invoked
callback and the two lambdas at its call sites while keeping the matrix size off
the paths that do not need it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Made-with: Cursor
Co-authored-by: Cursor <cursoragent@cursor.com>
@fhanuman fhanuman changed the title fix(hip): unify result-shape construction and reification fix(hip): unify result shapes and accept dynamic batched MatMul Jul 31, 2026
fhanuman and others added 3 commits July 31, 2026 10:35
`reduce_l2` landed on main after this branch diverged, carrying its own copy of
the reduction dimension map — the same `outToIn` / `SmallSet` / `axesKnown`
structure this branch removed from ReduceProd — and calling
`inferReduceResultType` through the boolean-flag signature that no longer
exists. The merge therefore did not compile, which is why CI failed on all
three build jobs while each side built on its own.

Route it through `resolveReductionAxes` and `createReductionEmptyTensor` like
the other five reduction converters, so its destination and
`reifyResultShapes` share one rule and `keepdims = 0` maps output dimensions
back to the input dimensions they actually come from. No private copy of the
dimension map remains in the tree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Made-with: Cursor
Co-authored-by: Cursor <cursoragent@cursor.com>
Make rank-zero shape success unambiguous by returning FailureOr from the
transpose, gather, GatherND, and shape-preserving reifiers, and validate all
preconditions before emitting dimension IR. Route the corresponding converters
through those shared rules, including MatMulNBits' N-derived output width.

Close the dynamic partial-broadcast hole without aborting the host process:
pass both operand batch counts to the MatMul runtime wrapper, reject counts that
are neither one nor the output batch count, and report the failure through the
existing device error flag so the generated interface returns a recoverable
non-zero status to ORT. Also make verifier diagnostics reachable, normalize
NoneType reduction axes, and align the documentation and LIT ABI coverage.

Co-Authored-By: GPT-5.6 Sol <noreply@openai.com>
Made-with: Cursor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

large-pr Soft size threshold (>500 LOC or >10 files). Reviewer signal, not a block.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant