Skip to content

feat(hipsr): lifetime-based pool allocation (single group) - #572

Draft
zz002 wants to merge 13 commits into
mainfrom
feat/hipsr-pool-alloc-skeleton
Draft

feat(hipsr): lifetime-based pool allocation (single group)#572
zz002 wants to merge 13 commits into
mainfrom
feat/hipsr-pool-alloc-skeleton

Conversation

@zz002

@zz002 zz002 commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Superseded — kept as a reference. This work is being re-submitted as a series of small PRs following the upstream breakdown wcy123/onnx-hipdnn-ep#108-#118 (pass skeleton, liveness utility, single-group emit, multi-group offsets, opt-in report). This PR moves to draft and serves as the complete-implementation reference; it will be closed once the split PRs are merged.

Summary

Implement the single-group core of the new hipsr-pool-alloc pass: within each hipsr.pool_domain, memref.alloc buffers whose lifetimes do not overlap collapse into one hipsr.get_pool byte buffer, each alloc rewritten to a memref.view at offset 0. Also lands the no-op pass registration (skeleton) and lit coverage.

Related issue or design

Design: pool-allocs-design-cn HIPSR Pool Allocation Pass + PR9 breakdown §9a (single-group). Series: PR 9 (remove SSA-identity bucketing, lifetime-only grouping).

Why

The old hip PoolAllocs pre-buckets by SSA identity before grouping. This replaces that with pure lifetime grouping so buffers that are only sequentially live — different SSA values, different sizes — can share the same memory. Lifetime start uses the first write (DPS outs), not the alloc index, so grouping is robust to allocs being hoisted adjacent.

What

  • Register -hipsr-pool-alloc (Passes.td def + pass; auto-registered via GEN_PASS_REGISTRATION).
  • Liveness: start = first write (DPS outs), end = last use, by op index.
  • Greedy grouping by lifetime non-overlap; >1 group emits an error and signalPassFailure (single-group bail; multi-group is the follow-up).
  • Single-group emit: group size = elemBytes × Π(static dims) × Π(dynamic-size operands), arith.maxui across allocs, alignUp(256); hipsr.get_pool(ctx, size); each alloc becomes a memref.view at offset 0. ctx comes from the pool_domain block arg (it is IsolatedFromAbove).
  • Size IR is built from the alloc's own dynamic-size operands (not shape_of on the buffer), so it stays valid after the alloc is replaced/erased; views are emitted after the pool to preserve dominance.

Test plan

  • llvm-lit test/lit/Dialect/Hipsr/pool_alloc_single_group.mlir green — first-write disjoint → 1 group, alignUp(256), single alloc, dynamic dim (muli by the size operand), static/dynamic + different element type in one group, no-alloc no-op unchanged, and >1 group → emitError (-verify-diagnostics).
  • pre-commit run --all-files clean.

Notes for reviewers

  • Scope is single group only; multi-group (offset accumulation + pool size sum) is the next PR.
  • kPoolAlignment (256 B) is compiler-owned for GPU coalesced access. Runtime hipdnn_ep_get_pool_base does not validate offsets — the hipMalloc base is already 256-aligned, so offset-256 keeps every view hipMalloc-grade aligned. Unlike the old hip pass it is a constant here rather than a pass option (can be lifted to an option later if needed).
  • Real pipeline hookup awaits partition/materialize; validated here with hand-written memref-mode pool_domain fixtures.

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.

@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 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: 3352 - Commit: 8118cb3

@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 6.39 368 3 1243
GroupQueryAttention_seq128 4388.89 1.67584 11 6 310
matmul_down_seq128 526.99 2.32 76 3 351

EPContext Export Performance

Model QPS Session (s) 1st Infer (ms) CPU% Mem (MB)
full_model_seq128 7.58 44.52 357 3 15589

EPContext Import Performance

Model QPS Session (s) 1st Infer (ms) CPU% Mem (MB)
full_model_seq128 7.56 9.49 356 3 15759

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 183.6 79.4 1.33 13.54
Llama-3.1-8B-awq-g128-int4-asym-fp16-onnx-dml 1 5 128 128 198.3 40.5 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 197 39.8

Run: 3352 - Commit: 8118cb3

zz002 added 9 commits July 28, 2026 02:49
Fill in the hipsr-pool-alloc pass: pool the memref.alloc buffers inside a pool_domain whose lifetimes do not overlap into one hipsr.get_pool, replacing each alloc with a memref.view at offset 0. Lifetime start = first write (DPS outs), end = last use. Multi-group inputs error out for now.
Split processDomain into per-step helpers (collectAllocLifetimes, greedyGroup, findContext, findLastAlloc, emitGroupSize, emitPool, replaceAllocsWithViews) mirroring the design-doc phases; inline single-use size/align emitters and the overlaps predicate. Make a poolable domain without an !hipsr.context operand a hard pass failure instead of a silent no-op. Remove code comments per request.
Drop the single-group bail in processDomain and emit N groups into one device pool: poolSize = sum of aligned group sizes (chained arith.addi), each group i placed at a cumulative offset (off0 = 0, off_i = off_{i-1} + size_{i-1}), members of one group sharing that offset. The single-group path stays byte-for-byte identical (poolSize has no addi, offset is a bare constant 0), so the existing positive cases do not regress.

Add pool_alloc_multi_group.mlir (two-group offset chain, intra-group reuse plus cross-group separation, three-group topology, no size-bucketing) and drop the now-obsolete multi-group expected-error case from pool_alloc_single_group.mlir.
…euse

Add multi_dynamic (dynamic alloc size flowing through the poolSize/offset addi chains alongside a static group) and multi_bigroup_mixed (a three-member mixed-dtype group folded by two maxui plus a fourth overlapping group) to pool_alloc_multi_group.mlir, and single_many_members (four disjoint allocs coalescing into one group sharing a single offset) to pool_alloc_single_group.mlir.
@zz002
zz002 force-pushed the feat/hipsr-pool-alloc-skeleton branch from af7bc54 to 69dbc05 Compare July 28, 2026 07:55
zz002 added 4 commits July 28, 2026 02:57
Per-op headers were consolidated into HipsrOps.h; switch the pool-alloc pass to the aggregate header like the rest of the dialect so it builds after rebasing onto main.
emitGroupSize becomes emitGroupSizes returning every group's aligned size, and emitPool accumulates the total pool size itself. findContext keeps the last matching context arg and view replacement uses llvm::zip. No IR/behavior change.
…ed file

Merge the single/multi-group files into pool_alloc.mlir ordered by lifetime-interval chromatic number. Rename cases by behavior (coalesce_* for disjoint reuse vs split_* for overlapping groups) and shape (static/dynamic/mixed); split_with_coalesced_group now carries a dynamic member so one multi-group domain crosses dynamic size and mixed dtype.
Gate the per-domain effectiveness remark behind a new emit-pool-report option, following the hip-pool-allocs convention that reports are opt-in while only real anomalies report unconditionally: an alloc with no DPS write is now warned about instead of silently skipped. The remark carries allocation/group/reuse counts, plus pooled vs naive bytes and intra-group slack when every extent is constant; the naive baseline aligns each alloc separately so the ratio compares like with like and never reports a negative saving. Also add DEBUG_TYPE, four STATISTIC counters, and LLVM_DEBUG traces for lifetime intervals and grouping conflicts.

Refresh the pass description, which still claimed multiple non-overlap groups error out.

New pool_alloc_report.mlir asserts the remark wording and that the option leaves the IR unchanged. pool_alloc.mlir keeps its -verify-diagnostics guard that the default path emits no diagnostics, and gains a dead-alloc case asserting the unpoolable alloc is left alone and excluded from the pool size.
@zz002
zz002 marked this pull request as draft July 29, 2026 06:36
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.

1 participant