-
Notifications
You must be signed in to change notification settings - Fork 7.7k
Refactor MLA kv cache #21835
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
nvjullin
wants to merge
11
commits into
sgl-project:main
Choose a base branch
from
nvjullin:refactor-mla-kv-cache
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.
+278
−166
Open
Refactor MLA kv cache #21835
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
18ff61a
fixed double bug accidental correctness in flashmla_auto
nvjullin 0cc8fa9
fixed None for cudagraph
nvjullin 42c2541
fixed broken refactor
nvjullin 6b721d0
rewrote MLA kv-cache to be explicit about layout
nvjullin 46dc55e
added requant path for flashmla_kv
nvjullin 3b54270
compute metadata whenever needed
nvjullin 9574c2b
fixed rope in mixed backends
nvjullin 2c43503
clean topk method dispatch
Fridge003 9e53233
clean comments
Fridge003 131a109
fixed stray renames and rewrote comment
nvjullin 295526d
Revert "added requant path for flashmla_kv"
nvjullin 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
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 |
|---|---|---|
|
|
@@ -35,6 +35,7 @@ | |
| seqlens_expand_triton, | ||
| ) | ||
| from sglang.srt.layers.dp_attention import get_attention_tp_size | ||
| from sglang.srt.mem_cache.memory_pool import MLAKVCacheLayout | ||
| from sglang.srt.model_executor.forward_batch_info import ForwardBatch, ForwardMode | ||
| from sglang.srt.utils import is_cuda, is_hip | ||
|
|
||
|
|
@@ -302,15 +303,13 @@ def __init__( | |
| ) | ||
| self.use_nsa = is_deepseek_nsa(model_runner.model_config.hf_config) | ||
| assert self.use_nsa, "NSA backend only supports DeepSeek NSA" | ||
| self.nsa_kv_cache_store_fp8 = ( | ||
| model_runner.token_to_kv_pool.nsa_kv_cache_store_fp8 | ||
| ) | ||
| self.kv_cache_layout = model_runner.token_to_kv_pool.kv_cache_layout | ||
| self.kv_cache_size = model_runner.token_to_kv_pool.kv_cache_size | ||
| self.nsa_index_topk = get_nsa_index_topk(model_runner.model_config.hf_config) | ||
| self.max_context_len = model_runner.model_config.context_len | ||
| self.num_q_heads = ( | ||
| model_runner.model_config.num_attention_heads // get_attention_tp_size() | ||
| ) | ||
| self.kv_cache_dim = model_runner.token_to_kv_pool.kv_cache_dim | ||
| self.qk_nope_head_dim = model_runner.model_config.qk_nope_head_dim | ||
| self.kv_lora_rank = model_runner.model_config.kv_lora_rank | ||
| self.qk_rope_head_dim = model_runner.model_config.qk_rope_head_dim | ||
|
|
@@ -319,11 +318,12 @@ def __init__( | |
| self.req_to_token = model_runner.req_to_token_pool.req_to_token | ||
|
|
||
| self.use_mha: bool = False | ||
| nsa_prefill_backend = model_runner.server_args.nsa_prefill_backend | ||
| self.prefill_is_flashmla_auto = nsa_prefill_backend == "flashmla_auto" | ||
| self.nsa_prefill_impl: _NSA_IMPL_T = ( | ||
| model_runner.server_args.nsa_prefill_backend | ||
| None if self.prefill_is_flashmla_auto else nsa_prefill_backend | ||
| ) | ||
| self.nsa_decode_impl: _NSA_IMPL_T = model_runner.server_args.nsa_decode_backend | ||
| self.enable_auto_select_prefill_impl = self.nsa_prefill_impl == "flashmla_auto" | ||
|
|
||
| self._arange_buf = torch.arange(16384, device=self.device, dtype=torch.int32) | ||
|
|
||
|
|
@@ -383,8 +383,22 @@ def _transform_table_1_to_real(self, page_table: torch.Tensor) -> torch.Tensor: | |
| ) | ||
| return page_table[:, strided_indices] // page_size | ||
|
|
||
| @staticmethod | ||
| def should_use_decode_backend(forward_mode: ForwardMode) -> bool: | ||
| """Whether the given forward mode dispatches to nsa_decode_backend. | ||
|
|
||
| This is answerable from static config alone (does not depend on | ||
| set_nsa_impl), so it is safe to call before init_forward_metadata. | ||
| """ | ||
| return ( | ||
| forward_mode.is_decode_or_idle() | ||
| or forward_mode.is_target_verify() | ||
| or forward_mode.is_draft_extend(include_v2=True) | ||
| ) | ||
|
|
||
| def init_forward_metadata(self, forward_batch: ForwardBatch): | ||
| """Init the metadata for a forward pass.""" | ||
| """Sets the metadata as fields of NativeSparseAttnBackend for a single forward pass. | ||
| Using the forward metadata for more than one forward pass is a bug.""" | ||
| batch_size = forward_batch.batch_size | ||
| device = forward_batch.seq_lens.device | ||
|
|
||
|
|
@@ -403,10 +417,16 @@ def init_forward_metadata(self, forward_batch: ForwardBatch): | |
| ] | ||
|
|
||
| page_table_1_flattened = None | ||
| # Topk indices needs to transform from indices in each request to indices | ||
| # in the flattened array of all requests by | ||
| # topk_indices = topk_indices + topk_indices_offset | ||
| # Offset is repeated once for each token in the request in a flattened array. | ||
| # e.g, for three requests with lengths [2, 3, 4], | ||
| # topk_indices_offset = [0, 0, 2, 2, 2, 5, 5, 5, 5] | ||
| # Only used for prefill with TopkTransformMethod.RAGGED. | ||
| topk_indices_offset = None | ||
|
|
||
| # Centralized dispatch: decide all strategies for this batch | ||
| self.set_nsa_prefill_impl(forward_batch) | ||
| self.set_nsa_impl(forward_batch) | ||
| topk_transform_method = self.get_topk_transform_method( | ||
| forward_batch.forward_mode | ||
| ) | ||
|
|
@@ -643,6 +663,7 @@ def init_forward_metadata(self, forward_batch: ForwardBatch): | |
| seq_len_q=1, | ||
| ) | ||
| if self.nsa_decode_impl == "flashmla_kv" | ||
| or self.nsa_prefill_impl == "flashmla_kv" | ||
| else None | ||
| ), | ||
| paged_mqa_schedule_metadata=paged_mqa_schedule_metadata, | ||
|
|
@@ -787,9 +808,8 @@ def init_forward_metadata_capture_cuda_graph( | |
| forward_mode: ForwardMode, | ||
| spec_info: Optional[SpecInput], | ||
| ): | ||
| self.set_nsa_prefill_impl(forward_batch=None) | ||
| self.set_nsa_impl(forward_batch=None) | ||
|
|
||
| """Initialize forward metadata for capturing CUDA graph.""" | ||
| if forward_mode.is_decode_or_idle(): | ||
| # Normal Decode | ||
| # Get sequence information | ||
|
|
@@ -944,10 +964,9 @@ def init_forward_metadata_replay_cuda_graph( | |
| seq_lens_cpu: Optional[torch.Tensor], | ||
| out_cache_loc: Optional[torch.Tensor] = None, | ||
| ): | ||
| """Initialize forward metadata for replaying CUDA graph.""" | ||
| assert seq_lens_cpu is not None | ||
|
|
||
| self.set_nsa_prefill_impl(forward_batch=None) | ||
| self.set_nsa_impl(forward_batch=None) | ||
|
|
||
| seq_lens = seq_lens[:bs] | ||
| seq_lens_cpu = seq_lens_cpu[:bs] | ||
|
|
@@ -1104,16 +1123,15 @@ def init_forward_metadata_replay_cuda_graph_from_precomputed( | |
| precomputed: PrecomputedMetadata, | ||
| forward_mode: ForwardMode, | ||
| ): | ||
| """Fast path: copy precomputed metadata to this backend's metadata. | ||
|
|
||
| This function only performs copy operations, no computation. | ||
| """Compared to init_forward_metadata_replay_cuda_graph, | ||
| this function copies precomputed metadata instead of computing them. | ||
|
|
||
| Args: | ||
| bs: Batch size | ||
| precomputed: Precomputed metadata to copy from | ||
| forward_mode: Forward mode | ||
| """ | ||
| self.set_nsa_prefill_impl(forward_batch=None) | ||
| self.set_nsa_impl(forward_batch=None) | ||
|
|
||
| metadata = self.decode_cuda_graph_metadata[bs] | ||
|
|
||
|
|
@@ -1275,10 +1293,7 @@ def forward_extend( | |
|
|
||
| nsa_impl = ( | ||
| self.nsa_decode_impl | ||
| if ( | ||
| forward_batch.forward_mode.is_target_verify() | ||
| or forward_batch.forward_mode.is_draft_extend(include_v2=True) | ||
| ) | ||
| if self.should_use_decode_backend(forward_batch.forward_mode) | ||
| else self.nsa_prefill_impl | ||
| ) | ||
|
|
||
|
|
@@ -1399,18 +1414,18 @@ def forward_extend( | |
| if q_rope is not None: | ||
| q_all = concat_mla_absorb_q_general(q_nope, q_rope) | ||
|
|
||
| if topk_transform_method == TopkTransformMethod.RAGGED: | ||
| if any(forward_batch.extend_prefix_lens_cpu): | ||
| page_table_1_flattened = ( | ||
| self.forward_metadata.page_table_1_flattened | ||
| ) | ||
| assert page_table_1_flattened is not None | ||
| kv_cache = dequantize_k_cache_paged( | ||
| kv_cache, page_table_1_flattened | ||
| ) | ||
| else: | ||
| kv_cache = _cat([k, k_rope], dim=-1) | ||
| page_table_1 = topk_indices | ||
| if topk_transform_method != TopkTransformMethod.RAGGED: | ||
| raise ValueError( | ||
| "Internal error: Unexpected topk transform method for NSA backend flashmla_sparse." | ||
| ) | ||
|
|
||
| if any(forward_batch.extend_prefix_lens_cpu): | ||
| page_table_1_flattened = self.forward_metadata.page_table_1_flattened | ||
| assert page_table_1_flattened is not None | ||
| kv_cache = dequantize_k_cache_paged(kv_cache, page_table_1_flattened) | ||
| else: | ||
| kv_cache = _cat([k, k_rope], dim=-1) | ||
| page_table_1 = topk_indices | ||
|
|
||
| return self._forward_flashmla_sparse( | ||
| q_all=q_all, | ||
|
|
@@ -1712,10 +1727,14 @@ def _forward_flashmla_kv( | |
|
|
||
| # TODO the 2nd dim is seq_len_q, need to be >1 when MTP | ||
| q_all = q_all.view(-1, 1, layer.tp_q_head_num, layer.head_dim) | ||
| kv_cache = kv_cache.view(-1, self.real_page_size, 1, self.kv_cache_dim) | ||
| kv_cache = kv_cache.view(-1, self.real_page_size, 1, self.kv_cache_size) | ||
| assert self.real_page_size == 64, "only page size 64 is supported" | ||
|
|
||
| if not self.nsa_kv_cache_store_fp8: | ||
| if self.kv_cache_layout != MLAKVCacheLayout.FP8_NOPE_WITH_BLOCK_SCALE_BF16_ROPE: | ||
| assert ( | ||
| self.kv_cache_layout != MLAKVCacheLayout.FP8_NOPE_FP8_ROPE | ||
| ), "Internal error: NSA backend flashmla_kv does not support FP8_NOPE_FP8_ROPE" | ||
|
|
||
| # inefficiently quantize the whole cache | ||
| kv_cache = quantize_k_cache(kv_cache) | ||
|
|
||
|
|
@@ -1937,6 +1956,9 @@ def _forward_trtllm( | |
|
|
||
| merge_query = q_rope is not None | ||
| if self.kv_cache_dtype == torch.float8_e4m3fn: | ||
| assert ( | ||
| self.kv_cache_layout == MLAKVCacheLayout.FP8_NOPE_FP8_ROPE | ||
| ), "Internal error: trtllm mla backend only supports FP8_NOPE_FP8_ROPE" | ||
| # For FP8 path, we quantize the query and rope parts and merge them into a single tensor | ||
| # Note: rope application in deepseek_v2.py:forward_absorb_prepare is skipped for FP8 decode path of this trtllm_mla backend | ||
| assert q_rope is not None, "For FP8 path q_rope should not be None." | ||
|
|
@@ -1973,7 +1995,9 @@ def _forward_trtllm( | |
| ) | ||
|
|
||
| k_cache = forward_batch.token_to_kv_pool.get_key_buffer(layer.layer_id) | ||
| kv_cache = k_cache.view(-1, self.real_page_size, self.kv_cache_dim).unsqueeze(1) | ||
| kv_cache = k_cache.view(-1, self.real_page_size, self.kv_cache_size).unsqueeze( | ||
| 1 | ||
| ) | ||
|
|
||
| if merge_query: | ||
| q_nope = q.view(-1, layer.tp_q_head_num, layer.v_head_dim) | ||
|
|
@@ -2009,7 +2033,7 @@ def _forward_trtllm( | |
| _, num_heads, head_dim = q_all.shape | ||
|
|
||
| q = q_all.view(batch_size, 1, num_heads, head_dim) | ||
| kv = kv_cache.view(-1, 1, self.real_page_size, self.kv_cache_dim) | ||
| kv = kv_cache.view(-1, 1, self.real_page_size, self.kv_cache_size) | ||
| block_tables = page_table_1.unsqueeze(1) | ||
| seq_lens = metadata.cache_seqlens_int32 if seq_lens is None else seq_lens | ||
|
|
||
|
|
@@ -2056,9 +2080,10 @@ def get_cuda_graph_seq_len_fill_value(self): | |
| """Get the fill value for sequence length in CUDA graph.""" | ||
| return 1 | ||
|
|
||
| def set_nsa_prefill_impl(self, forward_batch: Optional[ForwardBatch] = None): | ||
| def set_nsa_impl(self, forward_batch: Optional[ForwardBatch] = None): | ||
| """ | ||
| Decide all attention prefill dispatch strategies for this batch. | ||
| Decide all attention dispatch strategies for this batch. | ||
| Sets nsa_prefill_impl, nsa_decode_impl and use_mha depending on forward mode. | ||
| """ | ||
| from sglang.srt.utils import get_device_sm, is_blackwell | ||
|
|
||
|
|
@@ -2087,9 +2112,17 @@ def set_nsa_prefill_impl(self, forward_batch: Optional[ForwardBatch] = None): | |
| else: | ||
| self.use_mha = False # Decode/verify always use MLA | ||
|
|
||
| # forward_batch is None only for cudagraph | ||
| if forward_batch is None or forward_batch.forward_mode.is_decode_or_idle(): | ||
| assert self.nsa_decode_impl != "flashmla_auto" | ||
| return | ||
|
|
||
| # Set MLA implementation only if not using MHA | ||
| if not self.use_mha and self.enable_auto_select_prefill_impl: | ||
| if self.nsa_kv_cache_store_fp8: | ||
| if not self.use_mha and self.prefill_is_flashmla_auto: | ||
| if ( | ||
| self.kv_cache_layout | ||
| == MLAKVCacheLayout.FP8_NOPE_WITH_BLOCK_SCALE_BF16_ROPE | ||
| ): | ||
| if ( | ||
| is_blackwell() | ||
| and forward_batch is not None | ||
|
|
@@ -2109,19 +2142,13 @@ def set_nsa_prefill_impl(self, forward_batch: Optional[ForwardBatch] = None): | |
| def get_topk_transform_method( | ||
| self, forward_mode: Optional[ForwardMode] = None | ||
| ) -> TopkTransformMethod: | ||
| """ | ||
| SGLANG_NSA_FUSE_TOPK controls whether to fuse the topk transform into the topk kernel. | ||
| This method is used to select the topk transform method which can be fused or unfused. | ||
| """ | ||
| if ( | ||
| # disable for MTP | ||
| self.nsa_kv_cache_store_fp8 | ||
| if forward_mode is None or forward_mode.is_decode_or_idle(): | ||
| return TopkTransformMethod.PAGED | ||
| elif ( | ||
| self.kv_cache_layout == MLAKVCacheLayout.FP8_NOPE_WITH_BLOCK_SCALE_BF16_ROPE | ||
|
Contributor
Author
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 is a faithful refactor from the original code, but got flagged as a bug by gemini. I don't believe flashmla_sparse accepts PAGED topk though, so this seems like a bug in the original code? |
||
| and self.nsa_prefill_impl == "flashmla_sparse" | ||
| ): | ||
| topk_transform_method = TopkTransformMethod.RAGGED | ||
|
|
||
| if forward_mode is not None and (forward_mode.is_decode_or_idle()): | ||
| topk_transform_method = TopkTransformMethod.PAGED | ||
| else: | ||
| topk_transform_method = TopkTransformMethod.PAGED | ||
| return topk_transform_method | ||
|
|
@@ -2133,11 +2160,12 @@ def get_indexer_metadata( | |
| forward_batch.hisparse_coordinator is not None | ||
| and forward_batch.forward_mode.is_decode_or_idle() | ||
| ) | ||
| topk_transform_method = self.get_topk_transform_method( | ||
| forward_batch.forward_mode | ||
| ) | ||
| return NSAIndexerMetadata( | ||
| attn_metadata=self.forward_metadata, | ||
| topk_transform_method=self.get_topk_transform_method( | ||
| forward_batch.forward_mode | ||
| ), | ||
| topk_transform_method=topk_transform_method, | ||
| paged_mqa_schedule_metadata=self.forward_metadata.paged_mqa_schedule_metadata, | ||
| force_unfused_topk=force_unfused, | ||
| ) | ||
|
|
@@ -2229,7 +2257,7 @@ def init_forward_metadata_replay_cuda_graph( | |
|
|
||
| # Set nsa_prefill_impl for first 3 backends (required by the method) | ||
| for i in range(3): | ||
| self.attn_backends[i].set_nsa_prefill_impl(forward_batch=None) | ||
| self.attn_backends[i].set_nsa_impl(forward_batch=None) | ||
|
|
||
| # Prepare FlashMLA tensors if needed | ||
| flashmla_num_splits_src = None | ||
|
|
||
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.
The refactoring of the
flashmla_sparsepath inforward_extendintroduces a regression for models using theBF16layout.Currently,
get_topk_transform_methodreturnsTopkTransformMethod.PAGEDfor theBF16layout (line 2153). However, the new code inforward_extendexplicitly raises aValueErrorif the method is notRAGGED(line 1417). This means any BF16 model using the NSA backend withflashmla_sparse(which is the default for BF16 inset_nsa_impl, line 2141) will crash at runtime.By restoring the conditional check for
TopkTransformMethod.RAGGED, thePAGEDmethod (used by BF16) can correctly proceed using the physical indices inpage_table_1(computed at line 1388) and the globalkv_cachebuffer, while theRAGGEDspecific logic (including dequantization) remains restricted to the appropriate layouts.