Refactor staging registration metadata fields (#33910)

Co-authored-by: Shangming Cai <csmthu@gmail.com>
This commit is contained in:
YAMY
2026-08-10 17:53:32 +08:00
committed by GitHub
co-authored by Shangming Cai
parent 0b6189d0e8
commit c971d7ac9c
5 changed files with 74 additions and 52 deletions
@@ -10,7 +10,6 @@ from __future__ import annotations
import dataclasses import dataclasses
import logging import logging
import struct
import threading import threading
import time import time
from typing import TYPE_CHECKING, List, Optional, Tuple from typing import TYPE_CHECKING, List, Optional, Tuple
@@ -529,31 +528,6 @@ class StagingTransferInfo:
self.ends[idx] = end self.ends[idx] = end
@dataclasses.dataclass
class StagingRegisterInfo:
"""Staging buffer registration info attached to a KVArgsRegisterInfo."""
base_ptr: int = 0
total_size: int = 0
@classmethod
def from_zmq_fields(
cls, msg: list, msg_start_offset: int
) -> Optional[StagingRegisterInfo]:
i = msg_start_offset
base_ptr = (
struct.unpack("Q", msg[i])[0] if len(msg) > i and len(msg[i]) == 8 else 0
)
total_size = (
int(msg[i + 1].decode("ascii"))
if len(msg) > i + 1 and len(msg[i + 1]) > 0
else 0
)
if base_ptr == 0 and total_size == 0:
return None
return cls(base_ptr=base_ptr, total_size=total_size)
class PrefillStagingStrategy: class PrefillStagingStrategy:
"""Prefill-side staging transfer: readiness check + gather-RDMA execution. """Prefill-side staging transfer: readiness check + gather-RDMA execution.
@@ -27,7 +27,6 @@ from sglang.srt.disaggregation.common.staging_handler import (
STAGING_WATERMARK_WAIT_S, STAGING_WATERMARK_WAIT_S,
DecodeStagingContext, DecodeStagingContext,
PrefillStagingContext, PrefillStagingContext,
StagingRegisterInfo,
StagingTransferInfo, StagingTransferInfo,
) )
from sglang.srt.disaggregation.common.utils import ( from sglang.srt.disaggregation.common.utils import (
@@ -144,8 +143,8 @@ class KVArgsRegisterInfo:
dst_dcp_rank: int = 0 dst_dcp_rank: int = 0
requires_dcp_relayout: bool = False requires_dcp_relayout: bool = False
dcp_token_item_lens: Optional[List[int]] = None dcp_token_item_lens: Optional[List[int]] = None
# Note: always put the staging field at the final (since the staging field is optional and contains multiple inputs) staging_base_ptr: int = 0
staging: Optional[StagingRegisterInfo] = None staging_total_size: int = 0
@classmethod @classmethod
def from_zmq(cls, msg: List[bytes]): def from_zmq(cls, msg: List[bytes]):
@@ -176,15 +175,20 @@ class KVArgsRegisterInfo:
if len(msg) > 13 and msg[13] != b"" if len(msg) > 13 and msg[13] != b""
else [] else []
), ),
# msg[14:16] belong to the staging field below; DCP trails it. staging_base_ptr=(
struct.unpack("Q", msg[14])[0]
if len(msg) > 14 and len(msg[14]) == 8
else 0
),
staging_total_size=(
int(msg[15].decode("ascii")) if len(msg) > 15 and msg[15] != b"" else 0
),
dst_dcp_size=( dst_dcp_size=(
int(msg[16].decode("ascii")) if len(msg) > 16 and msg[16] != b"" else 1 int(msg[16].decode("ascii")) if len(msg) > 16 and msg[16] != b"" else 1
), ),
dst_dcp_rank=( dst_dcp_rank=(
int(msg[17].decode("ascii")) if len(msg) > 17 and msg[17] != b"" else 0 int(msg[17].decode("ascii")) if len(msg) > 17 and msg[17] != b"" else 0
), ),
# Note: always put the staging field at the final
staging=StagingRegisterInfo.from_zmq_fields(msg, 14),
) )
@@ -467,8 +471,8 @@ class MooncakeKVManager(CommonKVManager):
ret = staging_strategy.transfer( ret = staging_strategy.transfer(
req.mooncake_session_id, req.mooncake_session_id,
kv_chunk.prefill_kv_indices, kv_chunk.prefill_kv_indices,
target_info.staging.base_ptr + c_offset, target_info.staging_base_ptr + c_offset,
target_info.staging.total_size - c_offset, target_info.staging_total_size - c_offset,
target_info, target_info,
) )
if ret == -1: if ret == -1:
@@ -1693,7 +1697,10 @@ class MooncakeKVManager(CommonKVManager):
elif ( elif (
self.enable_staging self.enable_staging
and staging_strategy is not None and staging_strategy is not None
and target_rank_registration_info.staging is not None and (
target_rank_registration_info.staging_base_ptr != 0
or target_rank_registration_info.staging_total_size != 0
)
): ):
ret, deferred = self._do_staging_transfer( ret, deferred = self._do_staging_transfer(
staging_strategy, staging_strategy,
+17 -11
View File
@@ -25,10 +25,7 @@ from sglang.srt.disaggregation.common.conn import (
CommonKVSender, CommonKVSender,
KVTransferError, KVTransferError,
) )
from sglang.srt.disaggregation.common.staging_handler import ( from sglang.srt.disaggregation.common.staging_handler import STAGING_WATERMARK_WAIT_S
STAGING_WATERMARK_WAIT_S,
StagingRegisterInfo,
)
from sglang.srt.disaggregation.common.utils import ( from sglang.srt.disaggregation.common.utils import (
FastQueue, FastQueue,
TransferKVChunk, TransferKVChunk,
@@ -233,9 +230,8 @@ class KVArgsRegisterInfo:
dst_state_layer_ids: List[List[int]] = dataclasses.field(default_factory=list) dst_state_layer_ids: List[List[int]] = dataclasses.field(default_factory=list)
dst_homogeneous_mem_kind: Optional[str] = None dst_homogeneous_mem_kind: Optional[str] = None
kv_xfer_segments: Optional[List[_KVXferPreparedSegment]] = None kv_xfer_segments: Optional[List[_KVXferPreparedSegment]] = None
# Keep last: optional, parsed from a variable-length tail of the ZMQ staging_base_ptr: int = 0
# frame in from_zmq() below, so positional construction stays stable. staging_total_size: int = 0
staging: Optional[StagingRegisterInfo] = None
@classmethod @classmethod
def from_zmq(cls, msg: List[bytes]): def from_zmq(cls, msg: List[bytes]):
@@ -304,7 +300,14 @@ class KVArgsRegisterInfo:
dst_state_item_lens=dst_state_item_lens, dst_state_item_lens=dst_state_item_lens,
dst_state_dim_per_tensor=dst_state_dim_per_tensor, dst_state_dim_per_tensor=dst_state_dim_per_tensor,
dst_state_layer_ids=dst_state_layer_ids, dst_state_layer_ids=dst_state_layer_ids,
staging=StagingRegisterInfo.from_zmq_fields(msg, 14), staging_base_ptr=(
struct.unpack("Q", msg[14])[0]
if len(msg) > 14 and len(msg[14]) == 8
else 0
),
staging_total_size=(
int(msg[15].decode("ascii")) if len(msg) > 15 and msg[15] != b"" else 0
),
) )
@@ -1203,7 +1206,10 @@ class NixlKVManager(CommonKVManager):
and not self.is_mla_backend and not self.is_mla_backend
and not self.is_hybrid_mla_backend and not self.is_hybrid_mla_backend
and decode_tp_size != self.attn_tp_size and decode_tp_size != self.attn_tp_size
and dst_info.staging is not None and (
dst_info.staging_base_ptr != 0
or dst_info.staging_total_size != 0
)
) )
kv_xfer_handle = None kv_xfer_handle = None
@@ -1972,8 +1978,8 @@ class NixlKVManager(CommonKVManager):
handle = self.send_kvcache_staged( handle = self.send_kvcache_staged(
req.agent_name, req.agent_name,
src_prefill_kv_indices, src_prefill_kv_indices,
dst_info.staging.base_ptr + c_offset, dst_info.staging_base_ptr + c_offset,
dst_info.staging.total_size - c_offset, dst_info.staging_total_size - c_offset,
dst_info.gpu_id, dst_info.gpu_id,
dst_info.decode_tp_rank, dst_info.decode_tp_rank,
dst_info.decode_tp_size, dst_info.decode_tp_size,
@@ -1,3 +1,4 @@
import struct
import unittest import unittest
from types import SimpleNamespace from types import SimpleNamespace
from unittest.mock import patch from unittest.mock import patch
@@ -13,6 +14,9 @@ from sglang.srt.disaggregation.common.utils import (
unpack_int_lists, unpack_int_lists,
unpack_list_of_buffers, unpack_list_of_buffers,
) )
from sglang.srt.disaggregation.mooncake.conn import (
KVArgsRegisterInfo as MooncakeKVArgsRegisterInfo,
)
from sglang.srt.disaggregation.utils import ( from sglang.srt.disaggregation.utils import (
MetadataBuffers, MetadataBuffers,
get_dsv4_c128_state_indices, get_dsv4_c128_state_indices,
@@ -31,6 +35,35 @@ register_cpu_ci(est_time=2, suite="base-a-test-cpu")
class TestDisaggregationWire(unittest.TestCase): class TestDisaggregationWire(unittest.TestCase):
def test_mooncake_registration_staging_fields(self):
msg = [
b"room",
b"127.0.0.1",
b"1234",
b"session",
struct.pack("Q", 0x1000),
struct.pack("Q", 0x2000),
b"",
b"0",
b"1",
b"128",
b"",
b"",
b"",
b"",
struct.pack("Q", 0x3000),
b"4096",
b"4",
b"2",
]
info = MooncakeKVArgsRegisterInfo.from_zmq(msg)
self.assertEqual(info.staging_base_ptr, 0x3000)
self.assertEqual(info.staging_total_size, 4096)
self.assertEqual(info.dst_dcp_size, 4)
self.assertEqual(info.dst_dcp_rank, 2)
def test_int_lists_roundtrip(self): def test_int_lists_roundtrip(self):
cases = [ cases = [
("Q", [[1, 2, 3], [4]]), ("Q", [[1, 2, 3], [4]]),
@@ -244,9 +244,8 @@ class TestNixlKVArgsRegisterInfo(CustomTestCase):
self.assertEqual(info.dst_dcp_rank, 3) self.assertEqual(info.dst_dcp_rank, 3)
self.assertEqual(info.dst_state_layer_ids, [[4], [4, 5]]) self.assertEqual(info.dst_state_layer_ids, [[4], [4, 5]])
self.assertEqual(info.dst_kv_layer_ids, [2, 7]) self.assertEqual(info.dst_kv_layer_ids, [2, 7])
self.assertIsNotNone(info.staging) self.assertEqual(info.staging_base_ptr, staging_ptr)
self.assertEqual(info.staging.base_ptr, staging_ptr) self.assertEqual(info.staging_total_size, 1048576)
self.assertEqual(info.staging.total_size, 1048576)
def test_from_zmq_allows_missing_state_and_staging_fields(self): def test_from_zmq_allows_missing_state_and_staging_fields(self):
msg = [ msg = [
@@ -272,7 +271,8 @@ class TestNixlKVArgsRegisterInfo(CustomTestCase):
self.assertEqual(info.dst_kv_item_lens, [256]) self.assertEqual(info.dst_kv_item_lens, [256])
self.assertEqual(info.dst_dcp_size, 1) self.assertEqual(info.dst_dcp_size, 1)
self.assertEqual(info.dst_dcp_rank, 0) self.assertEqual(info.dst_dcp_rank, 0)
self.assertIsNone(info.staging) self.assertEqual(info.staging_base_ptr, 0)
self.assertEqual(info.staging_total_size, 0)
class TestNixlTransferStatus(CustomTestCase): class TestNixlTransferStatus(CustomTestCase):
@@ -452,7 +452,8 @@ class TestNixlTransferWorker(CustomTestCase):
dst_kv_ptrs=[0], dst_kv_ptrs=[0],
dst_aux_ptrs=[0], dst_aux_ptrs=[0],
gpu_id=0, gpu_id=0,
staging=None, staging_base_ptr=0,
staging_total_size=0,
kv_xfer_segments=None, kv_xfer_segments=None,
dst_homogeneous_mem_kind="VRAM", dst_homogeneous_mem_kind="VRAM",
# Non-DCP peer. Without this the worker raises AttributeError # Non-DCP peer. Without this the worker raises AttributeError
@@ -936,7 +937,8 @@ class TestNixlStaging(CustomTestCase):
decode_tp_rank=0, decode_tp_rank=0,
dst_kv_item_len=128, dst_kv_item_len=128,
dst_kv_item_lens=[], dst_kv_item_lens=[],
staging=SimpleNamespace(base_ptr=0x8000, total_size=4096), staging_base_ptr=0x8000,
staging_total_size=4096,
) )
calls = [] calls = []
mgr.send_kvcache_staged = ( mgr.send_kvcache_staged = (