Skip to content

feat(hipsr): add pool-alloc liveness analysis - #591

Merged
zz002 merged 1 commit into
mainfrom
feat/hipsr-pool-alloc-109-liveness
Jul 30, 2026
Merged

feat(hipsr): add pool-alloc liveness analysis#591
zz002 merged 1 commit into
mainfrom
feat/hipsr-pool-alloc-109-liveness

Conversation

@zz002

@zz002 zz002 commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

-hipsr-pool-alloc now computes each pool domain's alloc live ranges. The pass still rewrites nothing; a new emit-pool-report option reports each range as a remark so lit can assert it.

Related issue or design

Upstream wcy123/onnx-hipdnn-ep#109 (liveness analysis utility), a step of wcy123/onnx-hipdnn-ep#19, which links the HIPSR Pool Allocation Pass design page. Follows #582 (#108, skeleton).

Why

A live range starts at the first write, not at the alloc. After materialize-init-tensors the allocs sit together at the top of the domain, so ranges keyed on the alloc index all overlap and nothing can ever share pool space. Taking the first DPS outs write as the start keeps disjoint ranges disjoint, which is what makes the grouping in the follow-ups worth doing.

Indices are those of the domain block's own ops, so a user nested in a region has to fold to its enclosing block-level op — Block::findAncestorOpInBlock, as PartitionPoolDomains.cpp already does. Looking the nested op up directly returns index 0 and silently shortens the range, which would later let a live buffer be reused. hipsr.pool_domain is IsolatedFromAbove, so failing to find an ancestor means the IR broke that invariant: an assert, not a fallback branch.

emit-pool-report is the option name the whole series uses, not test scaffolding: #110-#118 add group, size and reuse figures to the same report.

What

  • Passes.td: emit-pool-report bool option, default false, worded after the legacy hip-pool-allocs emit-fragmentation-report.
  • HipsrPoolAllocPass.cpp: Lifetime plus computeLiveness(Block &) returning a Value -> Lifetime map, and emitPoolingReport() which walks the block so remark order is block order. An alloc with no DPS outs write has no range and is absent from the map; the warning for that case belongs to #118.
  • test/lit/Dialect/Hipsr/pool_alloc_report.mlir: one RUN line, --verify-diagnostics for the ranges and FileCheck --implicit-check-not for the unchanged module.

Not done: alias-aware last use. The legacy BufferUtils.h tracks view-like aliases through BufferViewFlowAnalysis because hip domains contain them; hipsr domain bodies do not yet. This has to be revisited before #117 wires the rewrite into a bufferized pipeline, otherwise an aliased use shortens the range.

Test plan

  • lit --filter pool_allocpool_alloc_report.mlir and pool_alloc.mlir PASS.
  • Full check-hip-mlir-lit — 356 passed, 2 unsupported, 0 failed.
  • hip-mlir-opt --help lists --emit-pool-report under --hipsr-pool-alloc.
  • Mutation check: changing one expected range fails the run, and injecting a hipsr.get_pool into a domain trips --implicit-check-not, so neither half of the RUN line is vacuous.
  • pre-commit run --all-files clean.

Notes for reviewers

  • The default path is unchanged from the skeleton: the analysis runs, nothing is rewritten, nothing is printed. The lit RUN line asserts the module is byte-for-byte the input while the report is on.
  • Lifetime carries only start/end. The endpoint ops are only needed by #118's debug trace, so they land with it.
  • Follow-ups land in upstream order #110-#118, each on top of the previous one.

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.

Computes each pool domain's alloc live ranges: start is the first op writing
the buffer as a DPS `outs`, end its last use, both in indices of the domain
block's own ops. Users nested in a region fold to their enclosing block-level
op, so a lifetime is never cut short by an index the block does not know.

The pass still rewrites nothing. A new `emit-pool-report` option reports each
range as a remark so LIT can assert it; grouping, sizing, pool emission and
the full report land in the follow-up issues.

Upstream issue: wcy123/onnx-hipdnn-ep#109

Co-authored-by: Cursor <cursoragent@cursor.com>
@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

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.9552 368640 0

Threshold: 0.01 | Run: 3398 - Commit: 120b7f8

@github-actions

Copy link
Copy Markdown

MorphiZen EP Performance Results

Model QPS Session (s) 1st Infer (ms) CPU% Mem (MB)
full_model_seq128 7.53 6.46 364 3 1244
GroupQueryAttention_seq128 4261.68 1.69099 12 6 310
matmul_down_seq128 528.28 2.37 75 3 352

EPContext Export Performance

Model QPS Session (s) 1st Infer (ms) CPU% Mem (MB)
full_model_seq128 7.53 45.55 359 3 15591

EPContext Import Performance

Model QPS Session (s) 1st Infer (ms) CPU% Mem (MB)
full_model_seq128 7.54 9.96 358 3 15760

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 175.8 78.3 1.33 13.54
Llama-3.1-8B-awq-g128-int4-asym-fp16-onnx-dml 1 5 128 128 257.9 39.9 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 193 39.1

Run: 3398 - Commit: 120b7f8

@wcy123 wcy123 left a comment

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.

LGTM

@zz002
zz002 merged commit 6e1a74b into main Jul 30, 2026
6 checks passed
@zz002
zz002 deleted the feat/hipsr-pool-alloc-109-liveness branch July 30, 2026 10:00
zz002 added a commit that referenced this pull request Jul 31, 2026
## Summary

`-hipsr-pool-alloc` now groups each pool domain's allocs by lifetime: an
alloc joins the first group whose members are all disjoint from it,
otherwise it opens a new one. The pass still rewrites nothing; the
`emit-pool-report` remark gained the group each alloc landed in.

## Related issue or design

Upstream
[`wcy123/onnx-hipdnn-ep#110`](wcy123/onnx-hipdnn-ep#110)
(greedy grouping utility), a step of
[`wcy123/onnx-hipdnn-ep#19`](wcy123/onnx-hipdnn-ep#19),
which links the HIPSR Pool Allocation Pass design page. Follows
[#591](#591) (`#109`, liveness).

## Why

Group numbering ends up in the report, and `#117` will turn it into pool
offsets, so it has to be reproducible. Two things make it so: allocs are
collected by walking the block rather than iterating the `Value` ->
`Lifetime` map, whose order is pointer-hash dependent; and the sort by
lifetime start is stable, because allocs feeding several `outs` of one
DPS op share a start and an unstable sort would order them arbitrarily.

Touching endpoints count as overlapping. The op that first writes the
later buffer is still reading the earlier one, so `[1,3]` and `[3,5]`
cannot share space.

## What

- `greedyGrouping(Block &, const DenseMap<Value, Lifetime> &)` returning
groups as vectors of allocs, next to `computeLiveness` in the same
anonymous namespace.
- `emitPoolingReport` takes the groups and reports `lifetime [a,b] group
N`. Per-domain totals (alloc and group counts, reuse ratio) belong to
`#118`.
- `pool_alloc_report.mlir`: existing ranges gained their group, plus the
two boundary topologies.

## Test plan

- [x] Full `check-hip-mlir-lit` — 361 passed, 3 unsupported, 0 failed.
- [x] Mutation check: flipping one expected `group N` fails the run, so
the new assertions are not vacuous.
- [x] `pre-commit run --all-files` clean.

Grouping is covered by chromatic number of the interval graph:
`coalesce_static` (four disjoint allocs of differing sizes, one group),
`interleaved_allocs` and `hoisted_allocs` (one alloc over two disjoint
ones, two groups), `split_three_groups` (pairwise overlap, three
groups).

## Notes for reviewers

- `coalesce_static` and `split_three_groups` are lifted verbatim from
the reference implementation in
[#572](#572), minus its `CHECK`
lines, which assert the pooled IR that does not exist yet. Keeping the
fixtures identical means `#117` adds assertions instead of rewriting
tests. They live in `pool_alloc_report.mlir` for now because a remark is
the only thing to assert before the rewrite is wired; `#117` moves them
to `pool_alloc.mlir`.
- Tests are lit rather than gtest: the inputs are a `Block` and its
`Value`s, which gtest would have to build by hand, and the RUN line's
`--implicit-check-not=hipsr.get_pool --implicit-check-not=memref.view`
doubles as proof the pass is still a no-op. Happy to add a unit test if
reviewers prefer one.
- `greedyGrouping` runs unconditionally and its result is only read
under `emit-pool-report`, matching how `computeLiveness` landed in
`#109`. `#117` consumes it for real.

## Checklist

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

AI assistance: Cursor implemented `greedyGrouping`, ported the two
fixtures, and ran the lit and pre-commit validation above; I reviewed
the result and hand-checked every expected lifetime and group against
the fixture IR.
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.

2 participants