Repository navigation
Fix(attention): adapt the FlyDSL paged decode to aiter #5809 (explicit work plans) - #2514
Conversation
…plans aiter #5809 fixes a plan's capacity from workgroup_budget when it is built, and target and draft tune to different budgets (B16: 512 vs 256). Since #2366 both share one capture plan per (batch, kv_heads), refreshed in turn, so one of them could never get its budget; and a budget hit with a different capacity made the op mint a second plan inside the captured graph. - Key capture plans by (batch, kv_heads, budget, device). Equal budgets keep the single shared object of #2366. - Builders tag attn_metadata with a role: target from prepare_decode and the capture builder, draft from prepare_mtp_decode. - During the eager warmup before capture, the op resolves the budget once per (role, batch) from the real tensors; on a capacity mismatch it asks the builder to adopt the tuned plan and writes it back to the metadata, so the capture records it. During capture and once decided, the op uses the plan as given and never looks up a budget or builds another plan. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…fixes - MiniMax-M3 sparse decode: every sparse layer of a step selects the same per-row lengths (0 differences over 9,000 C15 steps x 56 layer pairs), so the shared runtime plan is refreshed by the first sparse layer of the forward only -- one planner launch per step instead of 57, in eager and in the captured graph. Opt-in per call site (refresh_once_per_forward); dense eager and plugin callers still refresh every call. - Route calls with more rows than aiter's planner accepts (4096) to gluon: every FlyDSL call needs a plan now, and a long sparse prefill folded into rows used to raise inside the worker. - Resolve the FlyDSL context bound only on the FlyDSL path, so a deployment with FlyDSL off pays nothing and gluon gains no new way to raise. - GDN capture builder and Qwen4 draft metadata now carry the plan role and builder, so capture never looks up a budget there either. - Skip the budget tests when aiter lacks pa_decode_tuning instead of erroring. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…aning This branch needs aiter with #5809 and is merged after it; nothing probes for it at runtime, so the docs now say ATOM and aiter must be upgraded together. Also correct what ATOM_PA_FLYDSL_PLAN does since #5809: there is no static FlyDSL path, PLAN=0 routes to gluon, dense plans take their budget from the offline tuning cache, and the sparse sites use runtime plans. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…get plans The latest aiter #5809 removed pa_decode_tuning (get_cached_budget, make_shape, storage_key) and ships no tuned budgets, so importing it fails at the first FlyDSL decode. - Build every explicit plan with aiter's default workgroup budget (2*CU). Measured on the agentic context distribution, the default was the fastest budget at every dense and draft batch checked. - Drop the per-role budget plans (role tags, adopt_flydsl_budget, plan_owner): target and EAGLE draft share one capture plan again, as in #2366. - Keep the once-per-step sparse refresh, the 4096-row gluon fallback and the FlyDSL-only context bound. - Tests: an explicit plan needs no tuning module and passes no budget (red if the old import comes back); the sparse refresh tests move into TestExplicitPlan. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…re too Review fixes on top of the default-budget adaptation: - Sparse decode refreshed its plan once per forward, marked on ATOM's ForwardContext. Capture runs the warmup and the capture forward on one context and stream, so the capture skipped the refresh and every graph replay ran on the warmup's dummy lengths. The vLLM plugin never sets that context for MiniMax-M3, so there the mark outlived the step and eager steps stopped refreshing after the first. The mark now lives on the step's decode metadata (plan_step_owner, passed by the native and vLLM sparse decode), keyed by capture state; without an owner every call refreshes. Step-scoped and per-call plans no longer share a cache entry. - Dense runtime plans take aiter's default partition ceiling, like the capture-owned plans. Sparse and the vLLM bridge keep their static split count (plan_partition_cap). - A context bound below the query window routes to gluon instead of raising. - Pass the clamped bound to aiter; drop the dead no-plan scratch branch; fix stale comments and the PLAN env docs. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
aiter #5809 left FlyDSL no planner-less path, so ATOM_PA_FLYDSL=1 ATOM_PA_FLYDSL_PLAN=0 could only route to gluon -- exactly what ATOM_PA_FLYDSL=0 does. Gate the builder and the op on ATOM_PA_FLYDSL alone, remove the env and its docs row (folding the plan behaviour into the ATOM_PA_FLYDSL row), and warn once at startup when the old variable is still set, so a leftover PLAN=0 does not silently change meaning. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
🏷️ CI GuideRuns automatically on every eligible PR before approval:
Heavy model tests:
|
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Drop the stale "static path" note in the GDN capture builder, scope the builder-minting test docstring to the builder, inline the leftover flydsl_enabled alias, and say in the ATOM_PA_FLYDSL row which callers use capture-owned vs runtime plans and which plan ceiling each takes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
I reviewed this at
The issues below are mostly in how runtime plans are keyed and cached. CorrectnessTBO ubatches can share a runtime plan and use each other's lengths. The runtime plan key starts with The sparse "refresh once per step" relies on Runtime plans are never released
The vLLM bridge is hit hardest. Two related points:
Robustness
Docs and recipe
Smaller items
Not verifiedThe new |
- Key runtime plans by TBO ubatch id too: ubatches share one compute stream, so two equal-sized ubatches could refresh one step-scoped plan with each other's lengths. - Refresh the sparse plan once per step only when sparse_local_block >= 1 (tail block forced, so every sparse layer has the same lengths); otherwise refresh on every call. Native and vLLM sites. - Probe once for aiter #5809's FlyDSL API (required keyword-only work_plan and max_context_length); an older aiter now falls back to gluon with a warning instead of raising on the first decode. - Stop passing max_seqlen_k as the dense scheduling bound: it differs between graph and eager and selects the kernel specialization. Both now use the block-table bound. - Drop the dead sliding-window handling from runtime plans (the gate rejects windows), say "sequences" for the 4096 planner limit, fix stale comments, and note in the recipe which configuration its FlyDSL-vs-gluon numbers were measured on. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Fixed in a79c0c5: TBO ubatch id in the plan key, once-per-step refresh only with sparse_local_block >= 1, an aiter API probe that falls back to gluon, one block-table bound for graph and eager, dead window code, and stale docs and comments. Runtime-plan lifetime (measured 33–75 MiB per rank per hour) and builder-owned plans are out of scope and listed under Known Limitations. |
…610131505 Pin rocm/atom-dev:nightly_202610131505 (ATOM 0.1.8.dev44+g661ef7661) by digest and drop ATOM_PA_FLYDSL_PLAN, which ROCm/ATOM#2514 removed (now ignored). 将 MiniMax-M3 MI355X ATOM AgentX 镜像更新至 nightly_202610131505 (ATOM 0.1.8.dev44+g661ef7661),按 digest 固定;移除已被 ROCm/ATOM#2514 删除并忽略的 ATOM_PA_FLYDSL_PLAN。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Motivation
ROCm/aiter#5809 removes FlyDSL's static (no-plan) decode path: every
pa_decodecall now needs an explicit work plan. ATOM still takes the static path wherever there is no capture-owned plan: dense decode at an uncaptured batch size, MiniMax-M3 sparse decode, and the vLLM bridge. As a result,ATOM_PA_FLYDSL=1on an aiter with #5809 fails at the first FlyDSL decode.This PR gives every FlyDSL call an explicit plan with aiter's default workgroup budget (2×CU). Calls the planner cannot take go to gluon instead of raising. It also removes
ATOM_PA_FLYDSL_PLAN: without a planner-less path, setting it to0could only mean gluon, which is whatATOM_PA_FLYDSL=0already does. A leftover setting is ignored with a warning.Merge after aiter #5809; upgrade ATOM and aiter together. With
ATOM_PA_FLYDSL=0(the default) nothing changes. Results: GSM8K 0.963–0.968; agentic E2E is neutral against the pre-#5809 FlyDSL path.Design
ATOM_PA_FLYDSL_PLAN0= static FlyDSLDefault budget. #5809 ships no tuned budgets, and its autotune key does not include context length. On the agentic context distribution, the default 2×CU was the fastest budget at every dense and draft batch size we measured.
Once-per-step sparse refresh. All 57 sparse layers in a step select the same per-row lengths. The local tail block is always selected, so each length depends only on
seq_len; we observed 0 differences over 9,000 steps. One planner launch per step is therefore enough. Two details make this correct:plan_step_owner), not on ATOM's ForwardContext. The vLLM plugin never sets that context, so a marker stored there would outlive the step.Partition ceiling. Native dense runtime plans use aiter's default ceiling, as capture-owned plans do. Sparse keeps its cap of 8, because each row reads a bounded top-k window. The vLLM bridge keeps its static split count until that path is measured.
Test Result
Config: MI355X, MiniMax-M3-MXFP4 TP4 + EAGLE3-GQA,
ATOM_PA_FLYDSL=1. aiter is #5809820e5e3dfmerged with mainb1cdc19eb.Unit tests:
tests/test_paged_attention_dispatch.py, 142 passed. Each behavioral fix has a test that fails when that fix is reverted.Accuracy (GSM8K 5-shot, 1319 questions, real acceptance):
Concurrency 8 keeps decode batches between 5 and 16, so sparse attention runs through FlyDSL under graph replay. At concurrency 32 most steps go to ASM, which is why that column did not expose the bug.
E2E (agentic, 3600 s per point, same node). This PR vs the pre-#5809 FlyDSL path:
All differences are within the run-to-run spread, so the change is performance-neutral. The kernel microbench shows #5809's dense decode 5.6–8.9% faster at batch 16–40, but this does not show up end to end: dense attention is only 3 layers plus the draft.
Known Limitations
max_num_seqs). The vLLM bridge is the exposed case: its mixed prefill+decode steps do not pad the decode count, so withATOM_PA_FLYDSL=1(off by default) it can accumulate one ~8 MiB plan per distinct decode count up tomax_num_seqs; this path has not been run. Calls without a per-step owner (the vLLM bridge, native dense eager) also refresh their plan on every layer.sparse_local_block >= 1(otherwise every call refreshes). With these settings, per-layer lengths matched in every step: 0 differences over 10,200 steps (571K layer comparisons) withATOM_M3_INDEXER_CP=1at C15. In that run, 88% of requests exceeded the 36,864-token CP long-path threshold.tests/test_paged_attention_dispatch.pybecause the non-GPU job has no aiter; this is the same on main. Run the tests locally.Submission Checklist