Repository navigation
Conversation
🏷️ CI GuideRuns automatically on every eligible PR before approval:
Heavy model tests:
|
Let the Kimi-K3 attention builder take a prefill padded to a recorded token-graph bucket (supports_token_graph), so its dense layers replay as region graphs between the KDA and MLA eager regions. - kimi_mla_gdn_attn.py: supports_token_graph; a padded prefill keeps the real rows' metadata, marks them (prefill_real_rows) and gives the pad rows slot -1; image prefills run eagerly (step_needs_eager); a capture builder on the request-less dummy prefill. - base_attention.py, kimi_k3/model.py: on a marked step the MLA region cuts its inputs to the real rows, rebuilding the group-major FP8 q_scale, and both regions hand their output back at bucket height. - atom/utils/token_graph_rows.py (new, no AITER): the cut and the hand-back. - region_graph.py, envs.py, docs: ATOM_TOKEN_GRAPH_EAGER_PAD (default off), which runs a padded step eagerly instead of replaying it. - tests/test_kimi_k3_prefill_token_graphs.py: CPU tests. Off with ATOM_TOKEN_GRAPH_SIZES= (empty). Derived from our earlier piecewise prefill-graph patch, rebuilt on ROCm#2688's region graphs. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: David Zhu <david.zhu@amd.com>
6b4f454 to
8dab925
Compare
valar-cop
left a comment
There was a problem hiding this comment.
some changes needed..
mono-decode agreement at concurrency 4, gsm8k on/off with the command, and a GPU run of the padded sparse-indexer step?
ci green.
| q, k, v, positions, qkv = ( | ||
| real_rows(t, real, rows) for t in (q, k, v, positions, qkv) | ||
| ) | ||
| q_scale = real_rows(q_scale, real, rows, groups_major=True) |
There was a problem hiding this comment.
groups_major hardcoded? fp4x2 producer returns token-major scales, this would permute them..
| phase = _phase() if per_phase else None | ||
| if graph.has(key, phase): | ||
| if mode == "replay": | ||
| if envs.ATOM_TOKEN_GRAPH_EAGER_PAD: |
There was a problem hiding this comment.
this sends every replay eager, not just padded steps? gate on the padded marker instead of graph.has()
| # A padded prefill: the metadata covers the real rows only, so attention | ||
| # runs on those, and its FP8 group scales must not see the pad rows. | ||
| q, k, v, positions, qkv = ( | ||
| real_rows(t, real, rows) for t in (q, k, v, positions, qkv) |
There was a problem hiding this comment.
mrope positions are (3, N), token axis last, so this returns them uncut while q is cut.. cut last axis or raise?
| return out | ||
| if out.shape[0] > rows: | ||
| raise ValueError(f"a region returned {out.shape[0]} rows for a {rows}-row step") | ||
| padded = out.new_zeros((rows, *out.shape[1:])) |
There was a problem hiding this comment.
zeros bucket-high tensor per layer per step, ~2GB of fill on the TTFT path.. keep one zeroed buffer and overwrite real rows?
| attn_metadata.gdn_metadata = self._build_gdn_capture_metadata(bs) | ||
| return attn_metadata, context | ||
|
|
||
| def build_for_token_graph_capture(self, tokens: int): |
There was a problem hiding this comment.
duplicates dummy_execution prefill.. share one constructor, and missing 0 < tokens bound?
| monkeypatch, eager_pad | ||
| ): | ||
| monkeypatch.setenv("ATOM_TOKEN_GRAPH_EAGER_PAD", "1" if eager_pad else "0") | ||
| ctx = SimpleNamespace(tr_graph="replay") |
There was a problem hiding this comment.
this pins the over-broad switch, correct padded-only fix would fail it.. assert eager only for a marked step?
Motivation
Main records prefill token graphs (
atom/utils/region_graph.py, #2688) for an attention backend withsupports_token_graph. Until now only DeepSeek-V4.1's had it. Kimi-K3's model was already a@token_graphunit, and its KDA mixer already an@eager_region(), but its builder could not take a padded prefill, so every Kimi-K3 prefill ran eagerly. Short prefills are host-bound: #2688's docs put the saving at 30–40 ms of TTFT per prefill below 3072 tokens on DeepSeek-V4.1.This PR lets Kimi-K3 take them. A prefill of at most 3072 tokens pads to the smallest recorded bucket (
ATOM_TOKEN_GRAPH_SIZES, default256,512,1024,2048,3072). The dense layers between the KDA and MLA regions then replay as graphs, and the regions run the step's real rows.It rewrites an earlier internal patch of ours ("0016": piecewise prefill graphs on
CUDAGraphWrapper, on ATOM6e9b3e1c5) onto #2688's region graphs. #2688 removed that wrapper.Technical Details
Changes
kimi_mla_gdn_attn.py(the Kimi-K3 builder):supports_token_graph = True.prepare_prefill: the base builder already builds prefill metadata for the scheduled rows only (cu_seqlens_q, slots, positions, context lengths). On a step padded to a bucket, Kimi-K3 records the real row count (AttentionMetaData.prefill_real_rows) and sets the pad rows' slots to -1, so no recorded write lands anywhere for them.step_needs_eager: a prefill carrying images runs eagerly, as on DeepSeek-V4.1.build_for_token_graph_capture(tokens): the request-less prefillModelRunner.dummy_execution(prefill=True)runs, on placeholder page 0 with no state slot. KDA gets no metadata, so it writes no state. MLA writes to the placeholder page only.base_attention.unified_attention_with_output_base): cutsq,k,v,positionsandqkvto the real rows, rebuilds the group-major FP8q_scale(transpose_scale=True) at the real height, and hands the output back at bucket height.kimi_k3.model.kda_attention_with_output):_forward_implalready runs the real rows; the output is handed back at bucket height.atom/utils/token_graph_rows.py(new, no AITER):padded_prefill_rows(),real_rows()andto_rows().atom/utils/forward_context.pygets theAttentionMetaData.prefill_real_rowsfield (default None).ATOM_TOKEN_GRAPH_EAGER_PAD(default 0;region_graph.py,envs.py, docs row): a padded step runs eagerly instead of replaying, for A/B checks. It is generic and about 3 lines inregion_graph.py, so it could be split into its own PR if preferred.docs/environment_variables.md:ATOM_TOKEN_GRAPH_SIZESnow names Kimi-K3, and the new row.Checked
_pad_to_token_graphtakes the group's largest row count, which is the rank's own, so the bucket is the one the rank would choose. Above the largest bucket the step runs eagerly. Unit-tested.last_token_indices, taken from the real rows'cu_seqlens_q, andaux_forslices to the same row count, so pad rows are not read.model_output(model_runner.py:3203)has_mla_indexer): the sharedatom/model_ops/indexeris already region-graph aware. Its band logits are an eager region readingindex_rowslive, and its K write skips absent slots (nope.py:67), which is why pad slots are -1.eager_region(stream=...)._padded_inputs),TokenGraphcopies every tensor argument into its own buffers, and the text embedding runs inside the recorded unit.Relation to the internal patch ("0016")
q_scalerebuild; handing the result back at bucket height; the pad-without-graphs switch.prefill_graphs.pyplus a forcedCUDAGraphWrapperper piece →region_graph;supports_token_graph;ATOM_PREFILL_GRAPH_BUCKETS→ATOM_TOKEN_GRAPH_SIZES;_padded_inputsandTokenGraph's input buffers.ca_comm.enable_register_for_capturing: main records insidegraph_capture();ATOM_DUAL_STREAM_PIECEWISE;Default
TR_HYBRIDmode, like DeepSeek-V4.1's, which matches the maintainers' preference for default-on.ATOM_TOKEN_GRAPH_SIZES=(empty), an existing knob that records nothing; no new gate.ATOM_TOKEN_GRAPH_EAGER_PAD(Test Plan).Not in this PR
docs/environment_variables.md). That is a framework change, so it is proposed separately.Test Plan
tests/test_kimi_k3_prefill_token_graphs.py, plus the related suites.q_scalerebuild (ported from the internal patch's CPU test);black --checkandruff checkon the touched files.build(), with the dual-stream MoE on. Record capture time and pool size.ATOM_TOKEN_GRAPH_EAGER_PAD=1against replay, on the same prompts (completions and logprobs), at concurrency 8 and with mono decode at concurrency 4..github/scripts/atom_test.sh accuracy), graphs on and off.Test Result
test_region_graph.pyand the two builder modules that import AITER.atom.utils. ATOM's Linux CPU CI needs none of that.A prefill step at 512 / 1,024 / 1,536 / 2,048 tokens cost about 82 / 117 / 130 / 148 ms, against about 180 ms eager. At concurrency 128 the gain was close to neutral.
Submission Checklist
AI assistance: AI tools (Claude, in Cursor) helped write the change, its tests and this description. I reviewed every changed line and ran the tests above myself. The commit has a
Co-authored-by:trailer.