Skip to content

perf(compiler): fold constant-index extracts of assembled shape vectors - #1312

Open
donghanbj wants to merge 7 commits into
mainfrom
perf/infer-shapes-fold-extract
Open

donghanbj wants to merge 7 commits into
mainfrom
perf/infer-shapes-fold-extract

Conversation

@donghanbj

@donghanbj donghanbj commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Summary

A new func.func(hip-fold-constant-extracts) pass, run immediately before --hip-infer-shapes, folds constant-index tensor.extracts of vectors assembled with tensor.insert_slice, so those shape elements no longer reach runtime as small device-to-host copies. Gemma-4 E4B decode is 12-13% faster on gfx1151.

Related issue or design

None.

Why

A shape vector built from a dim and a small constant, e.g. the Gemma-4 E4B KV-sharing layers' ConstantOfShape(Concat(Shape(q)[0:1], [0, 512])), is read back element by element to size the result. Upstream folds stop at the tensor.insert_slice, and hip.constant has no folder, so every element reached runtime as a small device-to-host memref.copy: 36 hipMemcpyAsync per decode step on Gemma-4 E4B (ktrace, 45 -> 9 per step with this PR), about 20 of which block the host for more than 100 us while the stream drains. With the copies gone, the host waits once at the existing end-of-step sync instead of stalling the GPU at every layer.

What

  • New pass hip-fold-constant-extracts (lib/Dialect/Transforms/FoldConstantExtracts.cpp): resolves a constant-index tensor.extract through static-offset, unit-stride insert_slices to the scalar it reads: a tensor.from_elements operand, an inline integer hip.constant element, or another integer constant element. Dynamic indices, dynamic or strided or rank-reducing slices, and other producers keep the extract.
  • Rewrites only the resolved extracts and erases only the producers they leave without uses; everything else, including code that was already dead, is left as is, so functions without a resolvable extract are unchanged.
  • Placed immediately before hip-infer-shapes, so reification sees these scalars as constants, and before hip-externalize-constants, which may replace the inline hip.constant payloads it reads. Where the constant tensor is stored is still decided by hip-externalize-constants.
  • New lit test test/lit/Dialect/hip-fold-constant-extracts.mlir; pass documented in docs/pipeline_pass_menu.md, docs/design/hip-shape-inference.md and docs/design/constant-handling-design.md.

Test plan

  • Rebuilt hip-mlir-opt / hip-compiler and hipgpu.dll on main 3c569ca: MorphizenMLIRLitTests 664 passed (including the new test), 9 unsupported, 0 failed.
  • Gemma-4 E4B AWQ uint4 (split fp16 export) end to end through OGA on gfx1151, 128 generated tokens, 3 iterations per prompt, 2 rounds: generated text identical to main at all three prompt lengths.
  • Folded extracts per compile, counted on the IR entering the pass:
Model Folded
Gemma-4 E4B (AWQ split fp16 and k-quant exports) 54 per decoder compile
Gemma-4 E2B, Gemma-4 12B, Gemma-4 26B-A4B, Gemma-3 4B 0
Qwen3.5-9B, Qwen3.6-27B, Qwen3.8-27B 0
Mistral-7B, Llama-3.1-8B, Qwen2.5-14B 0

For every compile of the models with no folds, the IR after the pass is identical to its input.

Performance

Decode TPS, Gemma-4 E4B AWQ uint4, gfx1151 (Windows), main 3c569ca, 128 generated tokens, mean of 2 rounds x 3 iterations:

Prompt tokens main this PR delta
388 50.72 57.49 +13.3%
2279 48.92 55.41 +13.3%
16375 42.26 47.19 +11.7%

ROCm GenAI CI, same model on node xconucstrhalo17 (gfx1151, Windows), LLM_ROCm_dynamic, 128 generated tokens, 3 repetitions after 1 warmup. The PR build is the current code (fc8e2ec; the later head only merges main again), and both packages are on main cf8ff09:

Prompt tokens main (#2850) this PR (#2849) delta
19 49.10 56.45 +15.0%
128 49.70 56.14 +12.9%
2048 47.51 52.97 +11.5%
16384 35.82 39.64 +10.7%
geomean 45.14 50.79 +12.5%

TTFT is within 1% of main at every prompt length, and PPL (PPL_VLM_seqallgpu) is identical: 246.481288 for both.

The earlier /run-test models=all result above (Jenkins #2814) was measured on the first revision of this PR, before the fold moved into its own pass.

Checklist

  • The change is focused, or links a design/series explaining its scope.
  • Relevant tests were added or updated and the results are documented.
  • User-facing or design documentation was updated when needed.
  • Substantial AI assistance is disclosed, and I reviewed and understand the result.

AI assistance (Cursor, Claude Opus 5.5) was used for profiling analysis, the implementation, the lit test and the benchmark runs. I reviewed the changes and validated them with the lit suite and the end-to-end runs above.

…ectors

A shape vector built from a dim and a small constant, e.g. the Gemma-4
KV-sharing layers' ConstantOfShape(Concat(Shape(q)[0:1], [0, 512])), is read
back element by element to size the result. Upstream folds stop at the
tensor.insert_slice and hip.constant has no folder, so every element reached
runtime as a small device-to-host memref.copy plus a stream sync: 36 per
decode step on Gemma-4 E4B.

Phase 0 of --hip-infer-shapes now resolves a constant-index tensor.extract
through the insert_slice chain to the scalar it reads (a
tensor.from_elements element or an inline integer hip.constant element).
Only that scalar is materialised; where the constant tensor is stored is
still decided by hip-externalize-constants.

Gemma-4 E4B AWQ uint4 decode on gfx1151 (default settings), over main:
51.63 -> 58.50 TPS (+13.3%, 388-token prompt), 50.08 -> 56.20 (+12.2%, 2k),
43.43 -> 47.67 (+9.8%, 16k); +9.4% to +11.6% with
HIPDNN_EP_MATMUL_DP4A=0. Generated text unchanged.

Co-Authored-By: Cursor Claude Opus 5.5 <cursoragent@cursor.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 commented Oct 25, 2026 •

Copy link
Copy Markdown

L2 Accuracy Results (EP vs CPU)

Model Combined L2 Total Elems Skipped NaN/Inf
conv_test_hybrid 1.12256E-07 64 0
GroupQueryAttention_seq256 59.2561 2621440 0
MatMulNBits_o_seq128 259.905 368640 0
QMoE_seq128 24.2583 368640 0

Threshold: 0.01 | Run: 8249 - Commit: aa3db06

@github-actions

github-actions Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Binary sizes

Baseline: run 41321195111 - 9900502 (dll-sizes)

File This (MB) Main (MB) Delta (MB)
hipgpu.dll 59.45 59.44 +0.01
custom_kernels_gfx11-7-generic.dll 5.71 5.71 0.00
custom_kernels_gfx11-generic.dll 5.96 5.96 0.00

MorphiZen EP Performance Results

Model QPS Session (s) 1st Infer (ms) CPU% Mem (MB)
full_model_seq128 8.97 7.20 265 3 1158
GroupQueryAttention_seq128 10859.10 2.56361 0 5 167
matmul_down_seq128 740.20 3.24 116 3 268

EPContext Export Performance

Model QPS Session (s) 1st Infer (ms) CPU% Mem (MB)
full_model_seq128 9.00 48.42 247 3 15505

EPContext Import Performance

Model QPS Session (s) 1st Infer (ms) CPU% Mem (MB)
full_model_seq128 9.01 9.87 252 3 15546

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 91.3 83.6 1.25 12.34
Llama-3.1-8B-awq-g128-int4-asym-fp16-onnx-dml 1 5 128 128 115.4 49.8 1.15 5.40

OGA Wheel Smoke (Python benchmark_e2e.py)

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

VLM Benchmark Results

Model TTFT (ms) TPS Output
gemma-4-E4B-it-fp16-int4-kquant-prune-logits 3900.7 8.0 This is a high-quality, outdoor photograph featuring a beautiful, fluffy white dog, likely a West Highland White Terrier or a si
Qwen3.8-27B-fp16-ve-fp16-int4-k_quant-gs128-text-dml-onnx 4833.2 14.7 The user wants a detailed description of the image. Let me carefully observe all elements before writing. Subject: A white do

TTFT / TPS vs main

Baseline: run 41321195111 - 9900502 | Gate: TTFT +20% / TPS -20%

Suite Model TTFT (ms) Main Delta TPS Main Delta
oga gpt-oss-20b-webgpu-int4-rtn-block-32 91.3 93.6 -2.4% 83.6 83.5 +0.1%
oga Llama-3.1-8B-awq-g128-int4-asym-fp16-onnx-dml 115.4 116.6 -1.0% 49.8 49.9 -0.2%
vlm gemma-4-E4B-it-fp16-int4-kquant-prune-logits 3900.7 3976.3 -1.9% 8.0 7.5 +6.8%
vlm Qwen3.8-27B-fp16-ve-fp16-int4-k_quant-gs128-text-dml-onnx 4833.2 4905.4 -1.5% 14.7 14.7 0.0%

Run: 8249 - Commit: aa3db06

@donghanbj

Copy link
Copy Markdown
Contributor Author

/run-test models=all node=xsjnucstrhalo09

@amd-yanjunz

amd-yanjunz commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

ROCm GenAI CI finished with result: UNSTABLE.

/// reads: an element of a tensor.from_elements, or of an inline integer
/// hip.constant. Only that scalar is materialised; where the constant tensor
/// itself is stored is still decided by hip-externalize-constants.
struct FoldConstantIndexShapeExtract

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

How is this related to shape inference? Can this be a separate pass?

};

Value src = op.getTensor();
for (int depth = 0; depth < 16; ++depth) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It's not clear why this threshold 16 was chosen?

donghanbj and others added 3 commits October 28, 2026 08:03
… pass

The fold does not depend on any hip-infer-shapes state, so it now runs as
func.func(hip-fold-constant-extracts) immediately before hip-infer-shapes,
which is back to its main version.

- Walk the insert_slice chain without a depth cap; each step moves to a
  dominating operand, so the walk terminates.
- Rewrite only the resolved extracts and erase only the producers they
  leave without uses, instead of a greedy rewrite of the whole module, so
  functions without a resolvable extract are unchanged.
- Resolve any integer constant at the end of the chain, not only an inline
  hip.constant, and take the element attribute as is.
- Cover multi-level walks, multi-dimensional indices, index elements and
  the unresolved slice forms in the lit test; document the pass.

Co-Authored-By: Cursor Claude Opus 5.5 <cursoragent@cursor.com>
Made-with: Cursor
@donghanbj donghanbj changed the title perf(infer-shapes): fold constant-index extracts of assembled shape vectors perf(compiler): fold constant-index extracts of assembled shape vectors Oct 30, 2026
@donghanbj

Copy link
Copy Markdown
Contributor Author

/run-test models=all node=strix-halo-genai

@amd-yanjunz

amd-yanjunz commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

ROCm GenAI CI finished with result: UNSTABLE.

@donghanbj

Copy link
Copy Markdown
Contributor Author

/run-test models=all node=xsjnucstrhalo09

@amd-yanjunz

amd-yanjunz commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

ROCm GenAI CI finished with result: SUCCESS.

Co-Authored-By: Cursor Claude Opus 5.5 <cursoragent@cursor.com>
Made-with: Cursor
@donghanbj

donghanbj commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor Author

@fhanuman Thanks for the review. Both points are addressed in 6abcefe:

  • The fold doesn't depend on any hip-infer-shapes state, so it's now its own pass, func.func(hip-fold-constant-extracts), run right before hip-infer-shapes, which is back to its main version.
  • The 16 depth cap no longer exists; that code was removed in the refactor.

Could you take another look?

@donghanbj
donghanbj requested a review from fhanuman October 31, 2026 09:17

This branch has not been deployed

No deployments
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.

3 participants