Skip to content

[ASM] [HIP] [MLA v4 nm] Split plan helper and persistent decode kernel with in-kernel split and merge - #6126

Merged
yhl-amd merged 15 commits into
ROCm:mainfrom
yhl-amd:feat/mla-v4-nm-persistent-hca
Oct 4, 2026
Merged

yhl-amd merged 15 commits into
ROCm:mainfrom
yhl-amd:feat/mla-v4-nm-persistent-hca

Conversation

@yhl-amd

@yhl-amd yhl-amd commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Two parts for the gfx950 V4-NM fp8 decode (mla_decode_fwd_v4_nm), used by DeepSeek-V4 decode in ATOM. This PR supersedes #5890: its commits are included here.

1. Host-only split count for callers, from a cost model (formerly #5890)

  • get_mla_v4_nm_num_kv_splits(num_seqs, num_heads, kv_len) -> int. Split count for mla_decode_fwd_v4_nm, computed on the host with no device work. Callers pass it with the uniform split_indptr [0, s, 2s, ...]; any prefix of a longer one is valid, so a caller can keep one constant buffer per split count and slice it (ATOM does), and CUDA graphs read constant memory.
    • The count minimizes a per-arch latency model (workgroup waves x per-wave KV time, plus the partial-write/stage-2 cost of splitting). Coefficients live in a per-arch table; gfx950 is fitted. Other archs keep the wrapper's occupancy-only pick.
    • It depends only on num_seqs, num_heads and the caller's kv_len, never on kv_page_indices, whose length is meaningless for capacity-sized graph buffers.
  • Wrapper unchanged when no split count is passed. The auto pick inside mla_decode_fwd_v4_nm is the same as main.
  • Stage-2 fix. With a caller-supplied split_indptr that gives every seq one split on a grid > 1, the merge took the in-place BF16 copy (keyed on split_indptr[-1] == num_seqs) even though the kernel wrote FP32 partials, returning NaN. The v4 nm merge now always reduces the FP32 partials.

Why callers should not rely on the old auto pick: it maximizes CU occupancy only, so for gqa=128 single-token decode it picks 13-16 splits (several workgroup waves) for batches whose KV already fits one wave, adding partial writes and stage-2 work without adding parallelism.

2. Persistent decode kernel with in-kernel split and merge

A persistent variant of the kernel (ps=1 row in mla_v4_asm.csv). One launch per call: a fixed grid of 2*P workgroups walks per-partition work descriptors, the split plan is computed inside the kernel from the real kv_indptr every call, and split rows are merged by the last-arriving workgroup. Rows that are not split write the final bf16 output directly. Because nothing about the split depends on host-side shapes, it is CUDA-graph safe with varying KV lengths.

  • hsa/gfx950/mla_v4/mla_a8w8_qh64_qseqlen1_gqaratio64_nm_ps.co + CSV row (ps=1, lse=1).
  • csrc/py_itfs_cu/asm_mla_v4.cu: mla_decode_v4_ps_asm entry (documented packed kernarg struct, shape / workspace checks).
  • aiter/mla.py: get_mla_v4_nm_ps_workspace(device, num_partitions=128) and mla_decode_fwd_v4_nm_ps(q_packed, q_rope, kv_packed, kv_rope, kv_indptr, kv_page_indices, sink, workspace, out=None, return_lse=False, lse=None).
  • Contract: 128 heads, N <= 32768, row-dense KV pools, rows with K=0 left unwritten (same as the non-persistent kernel). Calls sharing a workspace must be ordered on the GPU; allocate the workspace outside graph capture.

Merged with current main; the op_tests/test_mla_v4_nm.py conflict with #5905 is resolved (the varlen check uses _gated_allclose, which now also returns the mismatch fraction).

Consumer: ROCm/ATOM#2462 (split plan for the decode ASM path, HCA layers on the persistent kernel).

Test plan

  • op_tests/test_mla_v4_nm.py on gfx950 (MI355X), after merging main, fresh JIT build: 47 passed
    • split count: test_v4_nm_split_planner_picks, test_v4_nm_num_kv_splits_uses_cost_model, test_v4_nm_split_plan_accuracy, test_v4_nm_split_plan_cudagraph_replay (captured once, replayed with different KV lengths), test_v4_nm_one_split_per_seq_on_wide_grid
    • persistent kernel: accuracy vs reference incl. K=0 rows and long K, lse, offset kv_indptr, workspace reuse across batch sizes, CUDA-graph replay with changing KV lengths, agreement with mla_decode_fwd_v4_nm, guard-page out-of-bounds checks, argument rejection
  • ruff (0.16.0) / black on changed files
  • End-to-end DeepSeek-V4-Pro serving through ATOM (companion PR) with real speculative acceptance: graph capture OK, accuracy checks pass
  • GSM8K 5-shot (1319) on DeepSeek-V4-Pro-DSpark through ATOM, TP8 + DP attention + EP8, real acceptance, together with the MegaMoE changes: 0.9606 strict / 0.9613 flexible (stock image 0.9530)

🤖 Generated with Claude Code

yhl-amd and others added 9 commits September 27, 2026 08:18
The gfx950 v4 nm auto split pick (ignore_total_kv=1) maximizes CU
occupancy only, so it prefers whole multiples of cu_num: for gqa=128
single-token decode it picks 13-16 splits (several workgroup waves) for
batches whose KV already fits one wave, paying extra partial writes and a
wider stage-2 merge for no added parallelism.

- On gfx950, pick num_kv_splits by minimizing a fitted latency model:
  workgroup waves x per-wave KV time, plus the partial-write/stage-2 cost
  of splitting. Other archs keep the existing pick.
- Add kv_len_hint: callers that pass capacity-sized kv_page_indices (e.g.
  CUDA-graph decode) cannot be planned from total_kv / num_seqs, so they
  pass the per-seq KV length to plan for. The pick depends only on shapes
  and this int, so it stays fixed across graph capture and replay.
- Fix the stage-2 merge for a caller-supplied split_indptr with one split
  per seq on a grid > 1: it took the in-place BF16 copy (keyed on
  split_indptr[-1] == num_seqs) although the kernel wrote FP32 partials,
  returning NaN.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: yihonglie <hyi@amd.com>
Planning inside mla_decode_fwd_v4_nm needs a per-seq KV length, and the
only one the wrapper can see, kv_page_indices.shape[0] / num_seqs, is
meaningless for CUDA-graph callers whose index buffers are capacity-sized.

- Add get_mla_v4_nm_split_plan(num_seqs, num_heads, kv_len, split_indptr=):
  returns the split count (cost model on gfx950, occupancy pick elsewhere)
  and fills a caller-owned persistent split_indptr in place. Callers build
  it in their metadata step and pass both to the wrapper.
- Drop kv_len_hint; the wrapper's own auto pick is unchanged from main.
- Tests: plan fill, accuracy through the wrapper, and CUDA-graph replay with
  different KV lengths under a captured plan. _run_varlen_point now returns
  its checkAllclose error so the new tests gate on it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: yihonglie <hyi@amd.com>
- get_mla_v4_nm_split_plan returns MlaV4NmSplitPlan(num_kv_splits,
  split_indptr). The field names are the wrapper's kwargs, so callers pass
  a plan on as mla_decode_fwd_v4_nm(..., **plan._asdict()).
- Latency-model coefficients live in a per-arch table; archs without an
  entry keep the occupancy-only pick. Only gfx950 is fitted.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: yihonglie <hyi@amd.com>
…erge

A persistent variant of the v4 nm fp8 decode kernel (gqa=128, one query
token per row). Each call is one launch of grid (2P, 1, 1): the kernel
plans the KV split over P partitions from kv_indptr, runs the attention and
merges the split partials itself, so callers need no host split plan and
no stage-2 merge, and the launch is CUDA-graph safe with varying KV lengths.

- hsa/gfx950/mla_v4: mla_a8w8_qh64_qseqlen1_gqaratio64_nm_ps.co, symbol
  _ZN5aiter39mla_a8w8_qh64_qseqlen1_gqaratio64_nm_psE, registered as the
  ps=1 row of mla_v4_asm.csv.
- csrc: mla_decode_v4_ps_asm in module_mla_v4_asm, with its own documented
  kernarg struct (same 21 x 16 B layout, repurposed slots marked), shape /
  dtype / contiguity / workspace checks and its own kernel cache.
- aiter.mla: MlaV4NmPsWorkspace, get_mla_v4_nm_ps_workspace(device,
  num_partitions=128) and mla_decode_fwd_v4_nm_ps(...). One workspace
  serves calls that are ordered on the GPU; allocate it outside capture.
- Tests: accuracy and LSE against the fp8-dequant reference (uniform,
  ragged, K=0 rows, N up to 3000, K up to 8320), kv_indptr with offset and
  extra entries, workspace reuse across batch sizes, partition counts,
  CUDA-graph replay with changing KV lengths, agreement with
  mla_decode_fwd_v4_nm, argument checks.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: yihonglie <hyi@amd.com>
mla_a8w8_qh64_qseqlen1_gqaratio64_nm_ps.co is now reproducible from sources
in the repo: nm_ps_src/gen.py patches the disassembly of the ROCm#5195 decode
kernel (asm/rt.s) into the persistent kernel, splices in the in-kernel split
planner and last-arriver merge (splice/*.hip, compiled with hipcc -S),
renames the kernel symbol, assembles for gfx950 and can check the sha256
against the shipped .co. README.md documents provenance, rebuild steps and
the kernarg layout.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: yihonglie <hyi@amd.com>
The persistent code object never reads kernarg slot 0xb8, yet the span
workspace [P, 2] was a field of MlaV4NmPsWorkspace, a required argument of
mla_decode_v4_ps_asm and size-checked in C++. Remove it from the workspace,
the ctypes signature and the C++ entry point, and pass nullptr at 0xb8.
Document the three regions of the cnt counter buffer next to its size.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: yihonglie <hyi@amd.com>
…rnel

Run mla_decode_fwd_v4_nm_ps in a subprocess at large N (up to the 32768 row
limit) with every input and output placed at the very end of its own
2 MiB-rounded allocation under the non-caching allocator, so an access one
element past q, qrope, the KV pools, kv_indptr (exactly N+1 entries),
kv_page_indices, sink, out or lse faults the GPU. Covers the lse and no-lse
paths and checks the merge counters return to zero.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: yihonglie <hyi@amd.com>
…rator

Rename the spliced planner / combine kernels and their labels
(t8pre -> ps_plan, t8fin -> ps_merge, T8_* -> PS_*) and regenerate the code
object. .text, .rodata (kernel descriptor) and .note (metadata) are
byte-identical to the previous build; only local symbol names in .symtab
change. Update the expected sha256.

README fixes: the row limit is N <= 32768 (out / q byte offsets wrap above
it; the cnt / arange sizes come from a 65536-row layout), cnt is int32 with
its three regions spelled out, and slot 0xb8 is passed as 0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: yihonglie <hyi@amd.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: yihonglie <hyi@amd.com>
@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

🏷️ CI Guide

Runs automatically on every PR:

  • ✅ Pre-checks (submodule verification, code formatting)
  • ✅ Aiter op tests (gfx942 + gfx950)
  • ✅ Triton tests on MI35X (only when aiter/ops/triton/** or related paths are changed)

Extended tests (opt-in via labels):

Label Tests
ci:gfx1250-ffm-triton Run the five-shard gfx1250 FFM Triton test suite
ci:triton-300x Run an additional Triton test job on MI300X in PRs (added automatically when gfx942 configs change); main branch always runs both MI35X and MI300X
ci:triton-355 Run the full Triton test suite on MI35X, not only the tests the change affects
multigpu Aiter multi-GPU tests on the 8-GPU runner
ci:sglang SGLang integration tests: DeepSeek-R1-MXFP4 accuracy, Qwen 3.5 accuracy
ci:atom ATOM benchmark: DeepSeek-R1-0528, GPT-OSS-120B
ci:atom_full ATOM accuracy suite for PR and main models from ATOM models_accuracy.json
ci:vllm vLLM benchmark: GPT-OSS-120B, DeepSeek-R1-0528, Kimi-K2.5
ci:all All standard extended tests (excludes ci:atom_full)

Only add ci:atom_full for FlyDSL or Triton upgrades.
Add labels via the sidebar or gh pr edit 6126 --add-label <label>

One backend per PR:
A PR changes one kernel backend: [Triton/Gluon] (Triton and Gluon count as one), [HIP], [ASM], [CK], [OPUS] or [FlyDSL]. If the title ends up with two backend tags, split the PR -- as stacked pull requests when one part cannot merge without the other.

PR title tags & labels:
Component tags ([Triton/Gluon], [HIP], [CK], [ASM], ...) are added to the PR title and as PR labels automatically from the changed files and re-synced on every push — change-type tags like [fix]/[Perf], op tags like [MLA], and human labels (ci:*) are left untouched. Add the no-auto-title label to stop the title rewrites; labels stay in sync either way.

yhl-amd and others added 3 commits October 3, 2026 10:46
Drop hsa/gfx950/mla_v4/nm_ps_src (assembly, patch scripts and HIP splice
sources). Nothing at build or run time reads it; the kernel ships as
mla_a8w8_qh64_qseqlen1_gqaratio64_nm_ps.co, unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: yihonglie <hyi@amd.com>
Resolve op_tests/test_mla_v4_nm.py: _run_varlen_point takes main's
_gated_allclose (ROCm#5905), which now also returns the mismatch fraction so the
split-plan tests can keep asserting on it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: yihonglie <hyi@amd.com>
dict literals for the workspace kwargs and the test inputs; one with-statement
for the capture-time rejection check. No behaviour change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: yihonglie <hyi@amd.com>
@yhl-amd yhl-amd changed the title [MLA v4 nm] Persistent decode kernel with in-kernel split and merge [MLA v4 nm] Split plan helper and persistent decode kernel with in-kernel split and merge Oct 4, 2026
yhl-amd added a commit to yhl-amd/ATOM that referenced this pull request Oct 4, 2026
…ew fixes

- The metadata builder no longer builds the HCA split plan for forwards whose
  HCA decode the persistent kernel takes (it plans on the GPU from the real
  kv_indptr). The decision uses only init-time state (workspace allocated
  before any capture) and the row gate shared with dispatch
  (hca_persist.rows_ok), so capture and every replay agree; a call the kernel
  then declines gets aiter's own split pick.
- _prepare_hca_persist delegates to hca_persist.prepare_if_usable and returns
  whether HCA can take the kernel.
- DeepseekV4Attention passes its own kv_fp8 flag to prepare_if_usable.
- Docs: the workspace is allocated at model load, not by the builder; the
  split-plan helper reference points at ROCm/aiter#6126.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: yihonglie <hyi@amd.com>
yhl-amd and others added 2 commits October 4, 2026 00:03
get_mla_v4_nm_num_kv_splits(num_seqs, num_heads, kv_len) returns the split
count the plan helper picks, with no device work. The uniform split_indptr
for it is [0, s, 2s, ...], and any prefix of a longer one is valid, so a
caller can keep one constant buffer per split count and slice it instead of
writing a split_indptr before every call. get_mla_v4_nm_split_plan now builds
on it; its behaviour is unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: yihonglie <hyi@amd.com>
Nothing calls the plan helper or MlaV4NmSplitPlan any more: callers take the
count from get_mla_v4_nm_num_kv_splits and pair it with the uniform
split_indptr [0, s, 2s, ...]. The tests build that pair with a local helper,
and the mla_decode_fwd_v4_nm docstring points at the count helper.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: yihonglie <hyi@amd.com>
@yhl-amd
yhl-amd marked this pull request as ready for review October 4, 2026 05:28
@yhl-amd
yhl-amd requested a review from a team October 4, 2026 05:28
@github-actions github-actions Bot changed the title [MLA v4 nm] Split plan helper and persistent decode kernel with in-kernel split and merge [ASM] [HIP] [MLA v4 nm] Split plan helper and persistent decode kernel with in-kernel split and merge Oct 4, 2026
@yhl-amd

yhl-amd commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor Author

Local accuracy validation with the companion ROCm/ATOM#2462 (GSM8K, ATOM CI accuracy configs)

Setup. 8x MI355X. Image: rocm/atom-dev:latest @ sha256:f6940ab6…, the image the nightly CI used for ATOM main 5b228e925.

Config CI threshold CI nightly @5b228e925 Local baseline Local PR
DeepSeek-V4-Pro (TP8) 0.94 0.9515 / 0.9522 0.9484 / 0.9492 0.9477 / 0.9477
DeepSeek-V4-Pro MTP (MTP3) 0.94, accept 0.622 0.9530 / 0.9530 0.9583 / 0.9591, accept 65.92% 0.9507 / 0.9515, accept 65.97%
DeepSeek-V4-Pro DSpark (DPA) 0.93 0.9500 / 0.9500 0.9484 / 0.9500, accept 43.64% 0.9477 / 0.9484, accept 43.91%
DeepSeek-V4-Pro DEP RCCL (DPA + EP) 0.94 0.9560 / 0.9568 0.9522 / 0.9538 0.9575 / 0.9575

Each entry is flexible-extract / strict-match. Acceptance is accepted / total draft tokens summed over every [MTP Stats Interval] line of every rank. For DPA that sum is needed because each rank logs its own stats.

  • Every PR result is above the CI threshold.
  • PR − baseline ranges from −0.0076 to +0.0053 with no consistent direction. That is within the ±0.006 standard error of a single 1319-sample run. Acceptance is unchanged for MTP3 and slightly higher for DSpark.
  • DPA configs (DSpark, DEP RCCL): these run at 128 local heads. The HCA persistent kernel is enabled there; all 8 ranks log V4 HCA persistent decode: device N ready.
  • TP8 and MTP3: these run at 16 local heads. The persistent kernel stays off by design (it needs 128 heads), so these configs cover the split-plan path of the decode ASM kernel.

🤖 Generated with Claude Code

@yhl-amd
yhl-amd merged commit 7bfe279 into ROCm:main Oct 4, 2026
62 checks passed
yhl-amd added a commit to ROCm/ATOM that referenced this pull request Oct 4, 2026
…nt V4-NM kernel (#2462)

* perf(dsv4): adapt tuned AITER ASM FP8 decode

* style: use functools cache for device arch

* perf(dsv4): steer AITER decode split pick with kv_len_hint

AITER's V4 NM wrapper can now pick num_kv_splits from a latency model when
told the per-token KV length to plan for (`kv_len_hint`). ATOM's decode
kv_indices buffers are capacity-sized, so AITER cannot derive that length
itself; the builder publishes the worst case per layer type and the decode
call forwards it:

- CSA: window + index_topk
- HCA: window + max_model_len // 128

Both depend only on config, so the pick stays fixed across CUDA graph
capture and replay. With an AITER build that predates `kv_len_hint`
(detected from the wrapper signature), CSA keeps the batch split table and
HCA keeps AITER's own auto pick.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: yihonglie <hyi@amd.com>

* perf(dsv4): pass AITER decode split plans through attention metadata

Replace kv_len_hint with an explicit split plan. The V4 metadata builder
calls AITER's get_mla_v4_nm_split_plan once per decode step for each sparse
layer type and stores (num_kv_splits, split_indptr) on the attention
metadata; the fp8 ASM decode forwards both to mla_decode_fwd_v4_nm.

- Plans are sized for the padded row count and the longest kv_len of the
  layer type (SWA: window, CSA: window + index_topk, HCA: window +
  max_model_len // 128), so capture and replay agree.
- split_indptr lives in persistent forward_vars buffers (per ubatch prefix)
  that the builder rewrites in place before each forward; graphs of
  different sizes share them.
- MTP draft steps clear the SWA plan (it is sized for the verify rows) and
  fall back to AITER's own pick.
- AITER builds without get_mla_v4_nm_split_plan keep the CSA batch table.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: yihonglie <hyi@amd.com>

* refactor(dsv4): one split-plan path, owned by whoever builds the decode CSR

- v4_decode_split_plan (paged_decode) is the single place that knows about
  aiter's get_mla_v4_nm_split_plan; it returns None on older aiter, which
  leaves the split pick to aiter.
- Each owner of a decode CSR builds its plan next to it, in its own
  persistent split_indptr buffer: the V4 metadata builder (SWA/CSA/HCA for
  verify, SWA for the MTP draft) and DSpark's DSparkIndexBuffers (built with
  the CSR at stage 0, read by every stage).
- sparse_attn_v4_paged_decode takes one split_plan argument; the model picks
  it in the same ratio branch as kv_indices / kv_indptr.
- Drop the legacy CSA batch table and the query_group / kv_kind parameters
  it needed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: yihonglie <hyi@amd.com>

* [DSV4] Run HCA fp8 decode through aiter's persistent V4-NM kernel

HCA (compress_ratio 128) decode calls now use
aiter.mla.mla_decode_fwd_v4_nm_ps: one launch that plans the KV split on
the GPU from kv_indptr and merges the partials in-kernel, so no host-side
split plan and no stage-2 merge.

- atom/model_ops/v4_kernels/hca_persist.py: gates, per-device workspace
  (get_mla_v4_nm_ps_workspace) and the call. Taken only for fp8 KV, 128
  local heads, gfx950, row-dense KV pools and
  ATOM_V4_HCA_PERSIST_MIN_ROWS <= rows <= 32768; everything else stays on
  the decode ASM + split plan. An aiter without the kernel stays on the
  ASM path and logs once.
- The V4 metadata builder allocates the workspace at init, before warmup,
  KV sizing and graph capture. All HCA decode calls run on the forward's
  compute stream, so one workspace per device is enough.
- sparse_attn_v4_paged_decode takes the caller's compress_ratio (passed
  from DeepseekV4Attention's decode call site).
- ATOM_V4_HCA_PERSIST (default 1) and ATOM_V4_HCA_PERSIST_MIN_ROWS
  (default 15), documented in docs/environment_variables.md.
- Tests: mocked dispatch rules, and a gfx950 test against the ASM path
  and the torch reference, including padded rows, an offset kv_indptr and
  CUDA-graph replay with changing KV lengths.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: yihonglie <hyi@amd.com>

* [DSV4] Allocate the persistent HCA workspace at model load on every path

Only the native V4 metadata builder called hca_persist.prepare(). The vLLM
and SGLang plugins reach the same persistent HCA decode branch without that
builder, so their workspace was allocated lazily on the first qualifying
call: after KV sizing, and as a hard failure if that call was being
captured into a CUDA graph.

- Every HCA DeepseekV4Attention layer now calls
  hca_persist.prepare_if_usable() at construction (switch on, fp8 KV,
  128 local heads, gfx950), so model load allocates the workspace in native
  ATOM and in both plugins, before KV sizing and graph capture. The native
  builder call stays (idempotent) and shares the eligibility check.
- The dispatch gate asks hca_persist.workspace_ready(): a call that finds
  no workspace allocates it when eager, and stays on the ASM path under
  capture instead of raising.
- Tests: CPU dispatch tests for the capture fallback and the layer-init
  hook; GPU test capturing a graph with no workspace (ASM result checked
  against the reference).

Signed-off-by: yihonglie <hyi@amd.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [DSV4] Satisfy ruff in the persistent HCA changes

Fold hca_persist into the existing v4_kernels import, drop the E402 noqa
directives ruff no longer enables, use dict literals in the GPU test, and
underscore an unused unpacked tensor. No behaviour change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: yihonglie <hyi@amd.com>

* [DSV4] Fix the non-GPU unit tests for the split-plan and HCA imports

test_deepseek_v4_transfer_regions loads the real deepseek_v4_attn against a
fixed set of import stubs, which did not cover what the decode changes added:
aiter.dist.parallel_state.get_tensor_model_parallel_world_size,
v4_kernels.v4_decode_split_plan and v4_kernels.hca_persist. Under the CPU
runner the module then failed to import (41 errors); with test_pp collected
first, its narrower parallel_state stub was picked up instead. Supply the
three in the test's own stub set (hca_persist reports itself unusable, as it
is without aiter), and use a dict literal in the prefill dispatch test.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: yihonglie <hyi@amd.com>

* [DSV4] Skip the HCA split plan where the persistent kernel runs; review fixes

- The metadata builder no longer builds the HCA split plan for forwards whose
  HCA decode the persistent kernel takes (it plans on the GPU from the real
  kv_indptr). The decision uses only init-time state (workspace allocated
  before any capture) and the row gate shared with dispatch
  (hca_persist.rows_ok), so capture and every replay agree; a call the kernel
  then declines gets aiter's own split pick.
- _prepare_hca_persist delegates to hca_persist.prepare_if_usable and returns
  whether HCA can take the kernel.
- DeepseekV4Attention passes its own kv_fp8 flag to prepare_if_usable.
- Docs: the workspace is allocated at model load, not by the builder; the
  split-plan helper reference points at ROCm/aiter#6126.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: yihonglie <hyi@amd.com>

* [DSV4] Build decode split plans on the host from one constant table

Every fp8 decode split plan is now (s, split_table[s - 1]): aiter's host-only
split count (get_mla_v4_nm_num_kv_splits) and a row of a [16, T + 1]
uniform split_indptr table built once at init (v4_uniform_split_table).
No forward writes a split_indptr any more, and CUDA graphs of every size
read constant memory, so a graph can no longer see another size's plan.

- The builder's per-layer-type, per-ubatch and MTP-draft split_indptr
  buffers are replaced by the one shared table; DSparkIndexBuffers keeps its
  own table sized for its draft rows.
- With planning free, the HCA plan is built again for every forward (the
  persistent kernel ignores it), so the builder no longer tracks whether HCA
  takes that kernel.
- A split count beyond the table, or rows beyond it, leaves the pick to aiter.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: yihonglie <hyi@amd.com>

* [DSV4] Describe split plans as plain tuples in the docstrings

aiter no longer has MlaV4NmSplitPlan; ATOM's plans were already
(num_kv_splits, split_indptr) tuples from v4_decode_split_plan.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: yihonglie <hyi@amd.com>

---------

Signed-off-by: yihonglie <hyi@amd.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants