-
Notifications
You must be signed in to change notification settings - Fork 100
[dsv4] kv cache fp8 #1600
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
Open
amd-ruitang3
wants to merge
3
commits into
ROCm:main
Choose a base branch
from
amd-ruitang3:dsv4_kvcache_fp8
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
[dsv4] kv cache fp8 #1600
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -904,6 +904,132 @@ def sparse_attn_v4_paged_decode_reference( | |
| return _sparse_attn_ragged_torch(q, unified_kv, attn_sink, topk_idxs, softmax_scale) | ||
|
|
||
|
|
||
| def _sparse_attn_v4_paged_decode_asm( | ||
| unified_kv: torch.Tensor, | ||
| kv_indices: torch.Tensor, | ||
| kv_indptr: torch.Tensor, | ||
| attn_sink: torch.Tensor, | ||
| softmax_scale: float, | ||
| unified_kv_rope: torch.Tensor, | ||
| q_packed_in: torch.Tensor, | ||
| q_rope_in: torch.Tensor, | ||
| qo_indptr: torch.Tensor, | ||
| kv_last_page_lens: torch.Tensor, | ||
| num_kv_splits: int | None = None, | ||
| ) -> torch.Tensor: | ||
| """Native 2buff fp8 V4 decode via the aiter assembly kernel | ||
| ``mla_decode_fwd_v4_nm`` (ROCm/aiter#3112, mi350/gfx950). | ||
|
|
||
| ``unified_kv`` is the packed 512-byte fp8 NoPE pool ``[P, 512]`` and | ||
| ``unified_kv_rope`` is the bf16 RoPE pool ``[P, 64]`` — consumed with NO | ||
| requant (caller stored the 2buff layout natively). Q is supplied pre-packed | ||
| (``q_packed_in``/``q_rope_in``) by the compute-only 2buff quant; no per-call | ||
| quantize. | ||
|
|
||
| The flat-CSR ``unified_kv`` is made compatible with the kernel's paged | ||
| format by treating each token as a 1-token page (``page_size=1``): | ||
| ``kv_page_indices = kv_indices``, ``kv_indptr`` is the existing CSR indptr, | ||
| ``kv_last_page_lens = ones(N)``, ``qo_indptr = arange(N+1)``, | ||
| ``max_seqlen_q = 1``. No translation layer needed. | ||
|
|
||
| softmax_scale is ignored by the kernel (hardcodes 1/sqrt(512)); passed | ||
| through for API parity. | ||
| """ | ||
| import aiter | ||
| import aiter.mla | ||
|
|
||
| from atom.model_ops.v4_kernels.v4_quant import ( | ||
| V4_DIM_NOPE, | ||
| V4_DIM_QK_PACKED, | ||
| V4_DIM_ROPE, | ||
| ) | ||
|
|
||
| assert q_packed_in.dim() == 3 and q_packed_in.shape[-1] == V4_DIM_QK_PACKED, ( | ||
| f"asm v4 nm: q_packed_in must be [N, H, {V4_DIM_QK_PACKED}] fp8, " | ||
| f"got {tuple(q_packed_in.shape)}" | ||
| ) | ||
| assert q_rope_in.dim() == 3 and q_rope_in.shape[-1] == V4_DIM_ROPE, ( | ||
| f"asm v4 nm: q_rope_in must be [N, H, {V4_DIM_ROPE}] bf16, " | ||
| f"got {tuple(q_rope_in.shape)}" | ||
| ) | ||
| q_packed = q_packed_in | ||
| q_rope = q_rope_in | ||
| device = q_packed.device | ||
| N, H, _ = q_packed.shape | ||
|
|
||
| assert unified_kv.dim() == 2 and unified_kv.shape[-1] == V4_DIM_QK_PACKED, ( | ||
| f"asm v4 nm: native fp8 unified_kv must be [P, {V4_DIM_QK_PACKED}] fp8, " | ||
| f"got {tuple(unified_kv.shape)}" | ||
| ) | ||
| assert unified_kv_rope.dim() == 2 and unified_kv_rope.shape[-1] == V4_DIM_ROPE, ( | ||
| f"asm v4 nm: native fp8 unified_kv_rope must be [P, {V4_DIM_ROPE}] bf16, " | ||
| f"got {tuple(unified_kv_rope.shape)}" | ||
| ) | ||
|
|
||
| nhead_kv = 1 | ||
| page_size = 1 | ||
| # Kernel expects KV as [num_page, page_size=1, num_kv_heads=1, dim]. | ||
| kv_packed = unified_kv.view(-1, page_size, nhead_kv, V4_DIM_QK_PACKED) | ||
| kv_rope = unified_kv_rope.view(-1, page_size, nhead_kv, V4_DIM_ROPE) | ||
|
|
||
| # ---- per-token paged layout (one slot per token, decode=1) ---------- | ||
| # qo_indptr = arange(N+1) and kv_last_page_lens = ones(N) are per-token | ||
| # (max_seqlen_q=1, page_size=1) and depend ONLY on N. They are built by the | ||
| # attn_metadata builder (deepseek_v4_attn._attach_v4_paged_decode_meta / | ||
| # prepare_mtp_decode) via the SAME forward_vars staging path as kv_indptr / | ||
| # kv_indices — re-staged (H2D into a persistent CpuGpuBuffer) every fwd so | ||
| # the captured graph reads a freshly-copied backing store at replay — and | ||
| # threaded in through `sparse_attn_v4_paged_decode`. | ||
| # The per-seq index tensors (qo_indptr [num_seqs+1], kv_indptr [num_seqs+1], | ||
| # kv_last_page_lens [num_seqs]) are staged by the attn-metadata builder at the | ||
| # PADDED decode grid T_pad so one set of buffers serves both a CG-captured | ||
| # (padded) replay and an eager (real-T) forward. The asm kernel derives | ||
| # num_seqs from qo_indptr.numel()-1, so on an EAGER forward whose real token | ||
| # count N is smaller than the staged T_pad — e.g. an uncaptured MTP verify | ||
| # batch of bs*(k+1) tokens that is not a captured cudagraph size — the kernel | ||
| # would emit T_pad output rows while q_packed and the `output` buffer hold | ||
| # only N=q_packed.shape[0] rows. That is an OOB write into `output` on the | ||
| # split>1 path and a shape mismatch on the logits[:,0] path (o=[T_pad,...] vs | ||
| # q=[N,...]). Trim every per-seq tensor to the ACTUAL query count N: the real | ||
| # region is self-consistent (qo_indptr[:N+1] == arange(N+1), kv_indptr[:N+1] | ||
| # is the real ragged cumsum) and the dropped tail is exactly the 0-length | ||
| # CG-padded slots. No-op when captured/padded (N == T_pad). | ||
| qo_indptr = qo_indptr[: N + 1].to(torch.int32) | ||
| kv_last_page_lens = kv_last_page_lens[:N].to(torch.int32) | ||
| kv_indptr_i32 = kv_indptr[: N + 1].to(torch.int32) | ||
| kv_page_indices_i32 = kv_indices.to(torch.int32).contiguous() | ||
| max_seqlen_q = 1 | ||
|
|
||
| output = torch.empty( | ||
| (N, H, V4_DIM_NOPE + V4_DIM_ROPE), dtype=torch.bfloat16, device=device | ||
| ) | ||
| out_16_nosplit = 1 if num_kv_splits == 1 else 0 | ||
|
|
||
| logits, _ = aiter.mla.mla_decode_fwd_v4_nm( | ||
| q_packed, | ||
| q_rope, | ||
| kv_packed, | ||
| kv_rope, | ||
| output, | ||
| qo_indptr, | ||
| kv_indptr_i32, | ||
| kv_page_indices_i32, | ||
| kv_last_page_lens, | ||
| max_seqlen_q, | ||
| sink=attn_sink, | ||
| sm_scale=softmax_scale, | ||
| out_16_nosplit=out_16_nosplit, | ||
| num_kv_splits=num_kv_splits, | ||
| ) | ||
| # Result lands in `output` for the bf16-direct write (out_16_nosplit=1) or | ||
| # the stage2 LSE merge (resolved splits > 1). Only the single-pass fp32 | ||
| # path leaves it in logits[:, 0]. logits.shape[1] = resolved split count. | ||
| resolved_splits = logits.shape[1] | ||
| if out_16_nosplit != 0 or resolved_splits > 1: | ||
| return output.to(torch.bfloat16) | ||
|
Collaborator
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. ?? |
||
| return logits[:, 0].to(torch.bfloat16) | ||
|
|
||
|
|
||
| def sparse_attn_v4_paged_decode( | ||
| q: torch.Tensor, | ||
| unified_kv: torch.Tensor, | ||
|
|
@@ -912,12 +1038,36 @@ def sparse_attn_v4_paged_decode( | |
| attn_sink: torch.Tensor, | ||
| softmax_scale: float, | ||
| kv_scales: torch.Tensor | None = None, | ||
| unified_kv_rope: torch.Tensor | None = None, | ||
| q_packed_in: torch.Tensor | None = None, | ||
| q_rope_in: torch.Tensor | None = None, | ||
| qo_indptr: torch.Tensor | None = None, | ||
| kv_last_page_lens: torch.Tensor | None = None, | ||
| ) -> torch.Tensor: | ||
| """V4 decode sparse attention over a unified KV pool with paged indices. | ||
|
|
||
| When ``kv_scales`` is provided, ``unified_kv`` must be fp8 (e4m3fnuz) and | ||
| will be dequantized in-kernel using 1xGROUP_SIZE (default 64) block scales. | ||
| Native 2buff fp8 (``unified_kv_rope`` provided): routes to the aiter asm | ||
| kernel (op5) with pre-packed fp8 Q (``q_packed_in``/``q_rope_in``); the | ||
| fp8 NoPE pool + bf16 RoPE pool are read with no requant. | ||
|
|
||
| Otherwise (bf16): the existing Triton / reference path. When ``kv_scales`` | ||
| is provided, ``unified_kv`` must be fp8 (e4m3fnuz) and is dequantized | ||
| in-kernel using 1xGROUP_SIZE (default 64) block scales (legacy 1buff, | ||
| unreachable from the model). | ||
| """ | ||
| if unified_kv_rope is not None: | ||
| return _sparse_attn_v4_paged_decode_asm( | ||
| unified_kv, | ||
| kv_indices, | ||
| kv_indptr, | ||
| attn_sink, | ||
| softmax_scale, | ||
| unified_kv_rope, | ||
| q_packed_in, | ||
| q_rope_in, | ||
| qo_indptr=qo_indptr, | ||
| kv_last_page_lens=kv_last_page_lens, | ||
| ) | ||
| if get_gfx() == "gfx1250": | ||
| return pa_decode_sparse( | ||
| q, | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
remove these useless .to()