Support GLM-5.3-Flash hybrid attention CPU offload and PD index mapping (#40310)

Co-authored-by: Xinyuan Tong <115166877+JustinTong0323@users.noreply.github.com>
This commit is contained in:
Yuxuan Zhang
2026-09-21 16:03:03 -07:00
committed by GitHub
co-authored by Xinyuan Tong
parent 0229025127
commit 00986c81be
4 changed files with 105 additions and 11 deletions
@@ -220,7 +220,8 @@ def transform_index_page_table_prefill_fast(
cu_seqlens_q: Optional[torch.Tensor] = None,
) -> torch.Tensor:
assert page_size == 1
assert topk_indices.shape[1] == 2048
assert topk_indices.ndim == 2
assert topk_indices.shape[1] > 0
real_num_tokens = sum(extend_lens_cpu)
result = _allocate_prefill_result(topk_indices, real_num_tokens, output_num_tokens)
if real_num_tokens == 0:
@@ -52,6 +52,12 @@ if TYPE_CHECKING:
from sglang.srt.mem_cache.memory_pool import DSATokenToKVPool
def _should_fuse_kpool_topk(metadata: BaseIndexerMetadata) -> bool:
return envs.SGLANG_DSA_FUSE_TOPK.get() and not getattr(
metadata, "force_unfused_topk", False
)
class IndexerKPool(MultiPlatformOp):
def __init__(
self,
@@ -784,7 +790,7 @@ class IndexerKPool(MultiPlatformOp):
paged_page_table: Optional[torch.Tensor] = None,
paged_page_table_row_index: Optional[torch.Tensor] = None,
) -> Tuple[Optional[torch.Tensor], Optional[torch.Tensor], Optional[torch.Tensor]]:
if not envs.SGLANG_DSA_FUSE_TOPK.get():
if not _should_fuse_kpool_topk(metadata):
return None, None, None
topk_method = metadata.topk_transform_method
@@ -977,7 +983,7 @@ class IndexerKPool(MultiPlatformOp):
page_table_all = None
page_table_row_index_all = None
topk_offsets_all = None
if envs.SGLANG_DSA_FUSE_TOPK.get():
if _should_fuse_kpool_topk(metadata):
if topk_method == TopkTransformMethod.PAGED:
page_table_all = plan.ragged_paged_page_table
page_table_row_index_all = plan.ragged_paged_page_table_row_index
@@ -1209,7 +1215,7 @@ class IndexerKPool(MultiPlatformOp):
page_table_local = None
topk_offsets_local = None
if (
envs.SGLANG_DSA_FUSE_TOPK.get()
_should_fuse_kpool_topk(metadata)
and topk_method == TopkTransformMethod.PAGED
):
page_table_local = (
@@ -1219,7 +1225,7 @@ class IndexerKPool(MultiPlatformOp):
)
page_table_local = page_table_local.unsqueeze(0).expand(q_len, -1)
elif (
envs.SGLANG_DSA_FUSE_TOPK.get()
_should_fuse_kpool_topk(metadata)
and topk_method == TopkTransformMethod.RAGGED
and topk_offsets is not None
):
+55 -6
View File
@@ -85,6 +85,57 @@ def create_offloader(dp_rank: int):
return NoopOffloader()
def _get_offloaded_device_state(module: torch.nn.Module, device: torch.device):
transferred = {}
device_state = {}
for name, value in module.state_dict(keep_vars=True).items():
key = id(value)
if key not in transferred:
transferred[key] = value.detach().to(device, non_blocking=True)
device_state[name] = transferred[key]
return device_state
def _get_resident_parameter_ids(module: torch.nn.Module):
# functional_call only replaces registered parameters and buffers, so cached
# tensors held as ordinary attributes need their backing weights to stay put.
resident = set()
for owner in module.modules():
# MLA post_load_weights derives w_kc/w_vc from kv_b_proj weights on
# their current device. These attributes already exist before loading.
projection = getattr(owner, "kv_b_proj", None)
if (
isinstance(projection, torch.nn.Module)
and hasattr(owner, "w_kc")
and hasattr(owner, "w_vc")
):
resident.update(id(parameter) for parameter in projection.parameters())
# KDA caches a storage-sharing view of qkv_conv1d.weight in conv_weights
# during construction. Offloading the weight would leave that view stale.
projection = getattr(owner, "qkv_conv1d", None)
attention = getattr(owner, "attn", None)
if isinstance(projection, torch.nn.Module) and isinstance(
attention, torch.nn.Module
):
weight = getattr(projection, "weight", None)
cached = getattr(attention, "conv_weights", None)
if (
isinstance(weight, torch.nn.Parameter)
and isinstance(cached, torch.Tensor)
and cached.device == weight.device
):
weight_storage = weight.untyped_storage()
cached_storage = cached.untyped_storage()
if (
weight_storage.nbytes() > 0
and weight_storage.data_ptr() != 0
and weight_storage.nbytes() == cached_storage.nbytes()
and weight_storage.data_ptr() == cached_storage.data_ptr()
):
resident.add(id(weight))
return resident
class OffloaderV1(BaseOffloader):
def __init__(self, cpu_offload_max_bytes: int):
self._cpu_offload_bytes = 0
@@ -114,7 +165,10 @@ class OffloaderV1(BaseOffloader):
# offload parameters to CPU
# use pin_memory if possible, which helps cudagraph capture speed
offloaded_parameters = False
resident_parameter_ids = _get_resident_parameter_ids(module)
for p in module.parameters():
if id(p) in resident_parameter_ids:
continue
if self._cpu_offload_bytes >= self._cpu_offload_max_bytes:
# we use per-parameter offloading
# one module might have some parameters offloaded and some not
@@ -139,12 +193,7 @@ class OffloaderV1(BaseOffloader):
def forward(*args, **kwargs):
module.forward = original_forward
device_state = {
# here we blindly call `to(device)`
# if the parameter is already on the device, it will be a no-op
k: v.to(device, non_blocking=True)
for k, v in module.state_dict().items()
}
device_state = _get_offloaded_device_state(module, device)
output = functional_call(module, device_state, args=args, kwargs=kwargs)
module.forward = forward
return output