Repository navigation
[Kimi-K3] Prefill token graphs: the KDA/MLA backend takes a padded prefill #2960
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,7 @@ | |
| from atom.utils import envs, mark_spliting_op | ||
| from atom.utils.region_graph import eager_region | ||
| from atom.utils.selector import Family, get_attn_backend | ||
| from atom.utils.token_graph_rows import padded_prefill_rows, real_rows, to_rows | ||
|
|
||
| from .attention_mla import MLAModules, _mla_output_width | ||
|
|
||
|
|
@@ -1060,23 +1061,32 @@ def unified_attention_with_output_base( | |
| ) -> torch.Tensor: | ||
| atom_config = get_current_atom_config() | ||
| self = atom_config.compilation_config.static_forward_context[layer_name] | ||
| rows, real = q.shape[0], padded_prefill_rows() | ||
| if real is not None and real < rows: | ||
| # 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. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. mrope positions are (3, N), token axis last, so this returns them uncut while q is cut.. cut last axis or raise? |
||
| ) | ||
| q_scale = real_rows(q_scale, real, rows, groups_major=True) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. groups_major hardcoded? fp4x2 producer returns token-major scales, this would permute them.. |
||
| if use_mla: | ||
| return self.impl.forward( | ||
| out = self.impl.forward( | ||
| query=q, | ||
| k_nope=k, | ||
| k_rope=v, | ||
| positions=positions, | ||
| q_scale=q_scale, | ||
| ) | ||
| else: | ||
| return self.impl.forward( | ||
| out = self.impl.forward( | ||
| query=q, | ||
| key=k, | ||
| value=v, | ||
| position=positions, | ||
| q_scale=q_scale, | ||
| qkv=qkv, | ||
| ) | ||
| return out if real is None else to_rows(out, rows) | ||
|
|
||
|
|
||
| def linear_attention_with_output_base_fake( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -42,7 +42,7 @@ | |
| import torch | ||
|
|
||
| from atom.distributed.nccl_watchdog import drain_nccl_works | ||
| from atom.utils import forward_context, weak_ref_tensor, weak_ref_tensors | ||
| from atom.utils import envs, forward_context, weak_ref_tensor, weak_ref_tensors | ||
| from atom.utils.graph_holders import register_graph_holder | ||
|
|
||
| __all__ = [ | ||
|
|
@@ -580,6 +580,9 @@ def _run_token_graph(owner, forward: Callable, args, kwargs, per_phase=False): | |
| 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. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this sends every replay eager, not just padded steps? gate on the padded marker instead of graph.has() |
||
| # The padded step run eagerly: a debug A/B against the replay. | ||
| return forward(*args, **kwargs) | ||
| return graph.replay(args, kwargs, phase=phase) | ||
| if mode == "record": | ||
| graph.mark_captured(key, phase) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| # SPDX-License-Identifier: MIT | ||
| # Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved. | ||
|
|
||
| """The real rows of a prefill padded to a token-graph bucket. | ||
|
|
||
| A backend whose eager regions plan their launches from the step's real rows | ||
| (Kimi-K3's KDA and MLA) builds its prefill metadata for those rows only and | ||
| marks the step with how many there are (`AttentionMetaData.prefill_real_rows`). | ||
| A region then cuts its bucket-high inputs back to the real rows, and hands its | ||
| output back at bucket height, the shape the next recorded segment reads | ||
| (`atom/utils/region_graph.py`). Outside such a step each helper is the | ||
| identity, so an unpadded prefill and every decode run as before. | ||
| """ | ||
|
|
||
| import torch | ||
|
|
||
| from atom.utils import forward_context | ||
|
|
||
|
|
||
| def padded_prefill_rows() -> int | None: | ||
| """The real rows of the running step if it is a marked padded prefill, else None.""" | ||
| metadata = getattr(forward_context.get_forward_context(), "attn_metadata", None) | ||
| return getattr(metadata, "prefill_real_rows", None) | ||
|
|
||
|
|
||
| def real_rows( | ||
| t: torch.Tensor | None, real: int, rows: int, groups_major: bool = False | ||
| ) -> torch.Tensor | None: | ||
| """`t` cut to its first `real` rows when it is a `rows`-high token-major tensor. | ||
|
|
||
| A 2-D per-group scale written group-major (`transpose_scale`: row r of group | ||
| g at g * rows + r in a (rows, groups) buffer) is rebuilt group-major at the | ||
| real height, which is what an unpadded producer lays out. | ||
| """ | ||
| if t is None or t.dim() == 0 or t.shape[0] != rows: | ||
| return t | ||
| if groups_major and t.dim() == 2 and t.shape[1] > 1: | ||
| groups = t.shape[1] | ||
| return t.reshape(groups, rows)[:, :real].contiguous().reshape(real, groups) | ||
| return t[:real] | ||
|
|
||
|
|
||
| def to_rows(out: torch.Tensor, rows: int) -> torch.Tensor: | ||
| """`out` at `rows` rows: its own rows, then zeros. Identity at `rows` already.""" | ||
| if out.shape[0] == rows: | ||
| 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. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. zeros bucket-high tensor per layer per step, ~2GB of fill on the TTFT path.. keep one zeroed buffer and overwrite real rows? |
||
| padded[: out.shape[0]] = out | ||
| return padded | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
duplicates dummy_execution prefill.. share one constructor, and missing 0 < tokens bound?