Reuse shared compressed KV dequantization in DeepSeek V4.1 CP prefill
(cherry picked from commit 44eb378e94610e4ccc6b990bf09c105f8c8f9ee9)
This commit is contained in:
@@ -1296,6 +1296,11 @@ class DeepseekV4AttnBackend(
|
||||
] = None
|
||||
self.online_c128_mtp = OnlineC128MTPController(self)
|
||||
self.sparse_prefill_workspace = SparsePrefillWorkspace(self.device)
|
||||
# CP V4.1 consumers share compressed KV across layers. Separate ratio
|
||||
# workspaces keep those prefixes intact while each layer refreshes SWA.
|
||||
self.shared_compressed_prefill_workspaces = {
|
||||
ratio: SparsePrefillWorkspace(self.device) for ratio in (1, 2)
|
||||
}
|
||||
spec_alg = model_runner.spec_algorithm
|
||||
self.needs_cpu_seq_lens = not spec_alg.is_dspark() and (
|
||||
not _is_cuda or self.online_c128_mtp.enabled()
|
||||
@@ -4034,20 +4039,38 @@ class DeepseekV4AttnBackend(
|
||||
compress_ratio, core_attn_metadata, extra_page_size
|
||||
)
|
||||
n_compressed = flat_token_ids.shape[0]
|
||||
workspace = self.sparse_prefill_workspace.get(
|
||||
n_compressed + cache.swa_token_ids.shape[0]
|
||||
reuse_compressed = compress_ratio in (1, 2) and is_cp_active(forward_batch)
|
||||
workspace_pool = (
|
||||
self.shared_compressed_prefill_workspaces[compress_ratio]
|
||||
if reuse_compressed
|
||||
else self.sparse_prefill_workspace
|
||||
)
|
||||
workspace = workspace_pool.get(n_compressed + cache.swa_token_ids.shape[0])
|
||||
compressed_slice = workspace[:n_compressed]
|
||||
swa_slice = workspace[n_compressed:]
|
||||
|
||||
if compressed_slice is not None:
|
||||
dequantize_k_cache_paged(
|
||||
extra_k_cache,
|
||||
flat_token_ids,
|
||||
page_size=extra_page_size,
|
||||
out=compressed_slice,
|
||||
layout=token_to_kv_pool.get_extra_key_layout(layer_id),
|
||||
)
|
||||
source_key = None
|
||||
if reuse_compressed:
|
||||
source_layer = token_to_kv_pool.source_layer_of(layer_id)
|
||||
source_key = (source_layer, workspace.data_ptr())
|
||||
gather = cache.compressed[compress_ratio]
|
||||
# A source layer may have just updated its cache in place. Consumer
|
||||
# layers only reuse the compressed prefix; their top-k and SWA stay live.
|
||||
if (
|
||||
source_key is None
|
||||
or layer_id == source_key[0]
|
||||
or gather.dequantized_source != source_key
|
||||
):
|
||||
dequantize_k_cache_paged(
|
||||
extra_k_cache,
|
||||
flat_token_ids,
|
||||
page_size=extra_page_size,
|
||||
out=compressed_slice,
|
||||
layout=token_to_kv_pool.get_extra_key_layout(layer_id),
|
||||
)
|
||||
if source_key is not None:
|
||||
gather.dequantized_source = source_key
|
||||
dequantize_k_cache_paged(
|
||||
token_to_kv_pool.get_swa_key_buffer_radix(layer_id),
|
||||
cache.swa_token_ids,
|
||||
|
||||
@@ -78,10 +78,11 @@ def use_dsv4_q8kv8_sparse_prefill(dsv4_prefill_backend: str = "auto") -> bool:
|
||||
class SparsePrefillWorkspace:
|
||||
"""Backend-owned scratch storage for sparse prefill KV dequantization.
|
||||
|
||||
The workspace contents are fully overwritten before every attention call,
|
||||
so token buckets and compression ratios can safely share one buffer. Sparse
|
||||
prefill executes eagerly and serially on the supported paths, which makes it
|
||||
safe to replace the scratch allocation when a larger extent is needed.
|
||||
Callers normally overwrite the entire workspace. Shared compressed-KV
|
||||
callers keep separate workspaces per ratio and track prefix validity in the
|
||||
per-forward gather cache, including the allocation address. Sparse prefill
|
||||
executes eagerly and serially on the supported paths, so the allocation can
|
||||
be replaced when a larger extent is needed.
|
||||
"""
|
||||
|
||||
def __init__(self, device: torch.device):
|
||||
@@ -275,14 +276,18 @@ class CompressedGather:
|
||||
# chunk-invariant per request; subsequent layers only overwrite that prefix.
|
||||
combined_indices: Optional[torch.Tensor] = None
|
||||
combined_lens: Optional[torch.Tensor] = None
|
||||
# Valid only for this forward's gather layout. Each ratio has its own
|
||||
# workspace; its compressed prefix survives consumer layers' SWA writes.
|
||||
dequantized_source: Optional[tuple[int, int]] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class SparsePrefillChunkCache:
|
||||
"""Cache prefill-chunk metadata shared across layers.
|
||||
|
||||
Fields depend on request/token mappings and compressed page tables, not
|
||||
per-layer k_cache; per-layer top-k combinations are recomputed into reused
|
||||
Gather layouts depend on request/token mappings and compressed page tables.
|
||||
Shared-source dequantization keys live only for this forward; per-layer
|
||||
top-k combinations are recomputed into reused
|
||||
buffers.
|
||||
"""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user