[Feature] Unified memory: support decode context parallelism for the trtllm_mla family (#37693)
This commit is contained in:
@@ -82,13 +82,23 @@ def create_mla_kv_page_table_for_dcp(
|
||||
req_pool_indices_ptr,
|
||||
local_seq_lens_ptr,
|
||||
block_kv_indices_ptr,
|
||||
v2p_ptr, # in: [num_pages + 1] int64 -- virtual->physical page table
|
||||
req_to_token_stride: tl.constexpr,
|
||||
block_table_stride: tl.constexpr,
|
||||
mult, # runtime: kernel_page_multiplier of the target sub-pool
|
||||
PHYSICAL_PAGE_SIZE: tl.constexpr,
|
||||
DCP_SIZE: tl.constexpr,
|
||||
DCP_RANK: tl.constexpr,
|
||||
PAGES_PER_BLOCK: tl.constexpr,
|
||||
HAS_V2P: tl.constexpr,
|
||||
):
|
||||
"""This rank's cyclic slice of each request, as a page table.
|
||||
|
||||
``HAS_V2P`` picks the id space the emitted page number is in: the
|
||||
DCP-collapsed page IS physical on a static pool, and still VIRTUAL under
|
||||
the unified memory pool, where it takes one more gather through ``v2p_ptr``
|
||||
and a ``mult`` scale to reach the per-layer views.
|
||||
"""
|
||||
req = tl.program_id(0)
|
||||
page_block = tl.program_id(1)
|
||||
page_offsets = page_block * PAGES_PER_BLOCK + tl.arange(0, PAGES_PER_BLOCK)
|
||||
@@ -102,10 +112,16 @@ def create_mla_kv_page_table_for_dcp(
|
||||
mask=mask,
|
||||
other=0,
|
||||
)
|
||||
physical_pages = virtual_locs // DCP_SIZE // PHYSICAL_PAGE_SIZE
|
||||
pages = virtual_locs // DCP_SIZE // PHYSICAL_PAGE_SIZE
|
||||
if HAS_V2P:
|
||||
# A `-1` in req_to_token and a freed (`-1`) v2p row both clamp to entry
|
||||
# 0, the reserved padding page.
|
||||
pages = tl.where(virtual_locs < 0, 0, pages)
|
||||
physical = tl.load(v2p_ptr + pages, mask=mask, other=0)
|
||||
pages = tl.maximum(physical * mult, 0)
|
||||
tl.store(
|
||||
block_kv_indices_ptr + req * block_table_stride + page_offsets,
|
||||
physical_pages,
|
||||
pages.to(tl.int32),
|
||||
mask=mask,
|
||||
)
|
||||
|
||||
|
||||
@@ -305,12 +305,10 @@ def _validate_unified_memory_dcp(server_args: Any) -> None:
|
||||
"transfer, where translate_kv_indices_for_transfer would abort a "
|
||||
"server that had already booted."
|
||||
)
|
||||
# trtllm_mla (and its cutedsl_mla / tokenspeed_mla subclasses) build the
|
||||
# MLA block table straight from req_to_token with
|
||||
# create_flashmla_kv_indices_triton, whose v2p gather assumes UNWIDENED
|
||||
# page ids; the DCP variant (create_mla_kv_page_table_for_dcp) has no v2p
|
||||
# gather at all. Wire one of them through the other to add those here.
|
||||
dcp_allowed = {"flashinfer"}
|
||||
# The trtllm_mla family builds its DCP block table through the pool's v2p
|
||||
# gather (create_mla_kv_page_table_for_dcp), so it speaks the same
|
||||
# two-stage contract as flashinfer.
|
||||
dcp_allowed = {"flashinfer", "trtllm_mla", "cutedsl_mla", "tokenspeed_mla"}
|
||||
backends = set(attention_backends_of(resolved_view(server_args)))
|
||||
backends.discard(None)
|
||||
assert backends <= dcp_allowed, (
|
||||
|
||||
@@ -174,22 +174,17 @@ class CuteDslMLABackend(TRTLLMMLABackend):
|
||||
if self.data_type == torch.float8_e4m3fn:
|
||||
assert q_rope is not None and k_rope is not None
|
||||
if cos_sin_cache is None:
|
||||
if (
|
||||
save_kv_cache
|
||||
and self._fused_set_kv_concat_q_fp8
|
||||
and not self.kv_index_translator.is_translating
|
||||
):
|
||||
# Static pool: out_cache_loc is already the physical loc.
|
||||
# Fused: bf16->fp8 quantize + KV scatter + q concat in one
|
||||
# launch; None when not covered.
|
||||
query = self._set_kv_and_concat_q_fp8_fused(
|
||||
layer=layer,
|
||||
loc=forward_batch.out_cache_loc,
|
||||
q=q,
|
||||
q_rope=q_rope,
|
||||
k=k,
|
||||
k_rope=k_rope,
|
||||
)
|
||||
if save_kv_cache and self._fused_set_kv_concat_q_fp8:
|
||||
loc = self._resolve_fused_write_loc(forward_batch)
|
||||
if loc is not None:
|
||||
query = self._set_kv_and_concat_q_fp8_fused(
|
||||
layer=layer,
|
||||
loc=loc,
|
||||
q=q,
|
||||
q_rope=q_rope,
|
||||
k=k,
|
||||
k_rope=k_rope,
|
||||
)
|
||||
if query is None:
|
||||
q, k, k_rope = mla_quantize_without_rope_for_fp8(
|
||||
q, q_rope, k.squeeze(1), k_rope.squeeze(1)
|
||||
@@ -211,7 +206,7 @@ class CuteDslMLABackend(TRTLLMMLABackend):
|
||||
if query is None and save_kv_cache:
|
||||
assert k is not None and k_rope is not None
|
||||
self.token_to_kv_pool.set_mla_kv_buffer(
|
||||
layer, forward_batch.out_cache_loc, k, k_rope
|
||||
layer, self._kv_write_loc(forward_batch), k, k_rope
|
||||
)
|
||||
|
||||
if query is not None:
|
||||
|
||||
@@ -389,7 +389,7 @@ class TokenspeedMLABackend(TRTLLMMLABackend):
|
||||
|
||||
if save_kv_cache:
|
||||
self.token_to_kv_pool.set_mla_kv_buffer(
|
||||
layer, forward_batch.out_cache_loc, k, k_rope
|
||||
layer, self._kv_write_loc(forward_batch), k, k_rope
|
||||
)
|
||||
|
||||
query = q.view(-1, layer.tp_q_head_num, layer.head_dim)
|
||||
|
||||
@@ -377,6 +377,8 @@ class TRTLLMMLABackend(FlashInferMLAAttnBackend):
|
||||
) -> None:
|
||||
parallel = get_parallel()
|
||||
pages_per_block = get_num_page_per_block_flashmla(self.page_size)
|
||||
# None on a static pool, whose collapsed page is already physical.
|
||||
v2p = self.kv_index_translator.full_v2p_table
|
||||
create_mla_kv_page_table_for_dcp[
|
||||
(
|
||||
block_kv_indices.shape[0],
|
||||
@@ -389,12 +391,15 @@ class TRTLLMMLABackend(FlashInferMLAAttnBackend):
|
||||
req_pool_indices,
|
||||
local_seq_lens,
|
||||
block_kv_indices,
|
||||
v2p,
|
||||
self.req_to_token.stride(0),
|
||||
block_kv_indices.stride(0),
|
||||
self.kv_index_translator.full_page_multiplier,
|
||||
PHYSICAL_PAGE_SIZE=self.page_size,
|
||||
DCP_SIZE=parallel.dcp_size,
|
||||
DCP_RANK=parallel.dcp_rank,
|
||||
PAGES_PER_BLOCK=pages_per_block,
|
||||
HAS_V2P=v2p is not None,
|
||||
)
|
||||
|
||||
def _create_block_kv_indices(
|
||||
@@ -777,6 +782,16 @@ class TRTLLMMLABackend(FlashInferMLAAttnBackend):
|
||||
else:
|
||||
self._decode_kernel_loc = None
|
||||
|
||||
def _kv_write_loc(self, forward_batch: ForwardBatch) -> torch.Tensor:
|
||||
"""The loc an unfused KV scatter must write at: the capture-stable
|
||||
buffer under a captured unified-pool decode, since the translate
|
||||
rebinds `out_cache_loc` to a fresh tensor the graph never recorded;
|
||||
the batch's own loc everywhere else.
|
||||
"""
|
||||
if self._decode_kernel_loc is not None:
|
||||
return self._decode_kernel_loc
|
||||
return forward_batch.out_cache_loc
|
||||
|
||||
def _resolve_fused_write_loc(
|
||||
self, forward_batch: ForwardBatch
|
||||
) -> Optional[torch.Tensor]:
|
||||
@@ -1198,6 +1213,12 @@ class TRTLLMMLABackend(FlashInferMLAAttnBackend):
|
||||
):
|
||||
return None
|
||||
parallel = get_parallel()
|
||||
# `loc` is WIDENED: the kernel resolves the owner rule itself, and that
|
||||
# is also its only skip. A DCP-resolved loc never reaches here -- see
|
||||
# the `_fused_set_kv_concat_q_fp8` gate.
|
||||
assert not (parallel.dcp_enabled and self.kv_index_translator.is_translating), (
|
||||
"fused fp8 KV write reached with a DCP-resolved loc"
|
||||
)
|
||||
return set_mla_kv_concat_q_fp8(
|
||||
kv_buffer=kv_2d,
|
||||
loc=loc,
|
||||
@@ -1205,8 +1226,6 @@ class TRTLLMMLABackend(FlashInferMLAAttnBackend):
|
||||
cache_k_rope=k_rope_2d,
|
||||
q_nope=q_nope,
|
||||
q_rope=q_rope_3d,
|
||||
# DCP cyclic KV sharding: virtual loc -> owner mask + loc//world
|
||||
# (identity when attn_dcp_size == 1).
|
||||
dcp_world_size=parallel.attn_dcp_size,
|
||||
dcp_rank=parallel.attn_dcp_rank,
|
||||
)
|
||||
|
||||
@@ -392,6 +392,22 @@ class KVIndexTranslator:
|
||||
self._index_table_memo = (weakref.ref(forward_batch), view)
|
||||
return view
|
||||
|
||||
@property
|
||||
def full_v2p_table(self) -> Optional[torch.Tensor]:
|
||||
"""The full-attention virtual->physical PAGE table, or None when this
|
||||
pool needs no translation.
|
||||
|
||||
For the DCP page-table builders, whose gather is over a rank's cyclic
|
||||
slice rather than a row prefix, so `build_index_table` cannot serve
|
||||
them.
|
||||
"""
|
||||
return self._full_v2p_table
|
||||
|
||||
@property
|
||||
def full_page_multiplier(self) -> int:
|
||||
"""Scales a physical page into the id space the per-layer views use."""
|
||||
return self._full_page_multiplier
|
||||
|
||||
def bind_and_verify_backends(self, backends) -> None:
|
||||
"""Boot: make every reachable backend carry THIS translator.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user