执行摘要
- 一句话:修复PD分离中KV传输指标未考虑复制扇出的问题
- 推荐动作:此 PR 修复逻辑清晰,测试覆盖完整,值得阅读。设计上“在引导屏障点一次性地解析拓扑不变量并缓存”的模式可推广到其他类似场景。建议从事分离部署或分布式推理监控的开发者重点关注。
功能与动机
When using PD disaggregation for MLA models, KV cache transfer metrics can be incorrectly reported, whenever one prefill sender replicates its KV cache to multiple decode destinations (e.g. Prefill-CP + Decode-TP). As a result, the kv_transfer_speed_gb_s metric appears very low (in this case, 1/4 of the real value, if TP8, would be 1/8), making diagnostics unreliable.
实现拆解
- 属性添加与因子初始化:在
CommonKVManager.__init__ 中添加 _kv_replica_factor 属性,对于 MLA 后端设为 None(待引导时解析),对于非 MLA 后端固定为 1。
- 因子解析方法:新增
resolve_kv_replica_factor(transfer_infos) 方法,从任意一个 TransferInfo 的 required_dst_info_num 字段获取复制因子并缓存到 _kv_replica_factor。仅在 is_mla_backend 为 True 时执行,非 MLA 直接跳过。
- 安全获取方法:新增
get_kv_replica_factor() 方法,若因子未解析(None)则返回 1 并输出一次性警告,避免指标崩溃。
- 指标乘法:在
CommonKVSender.get_transfer_metric() 中计算总字节数时,乘以 kv_mgr.get_kv_replica_factor() 得到真实的传输字节数。
- 后端集成:在 Mooncake、Mori、Nixl 三种传输后端的引导栅栏点(即收集到所有解码段目的地信息时)调用
resolve_kv_replica_factor,确保因子在指标计算前被正确解析。
- 单元测试:新增
test_kv_transfer_replica_metric.py 文件,包含三个用例:MLA 场景下因子正确缩放 KV 和状态字节、非 MLA 场景因子始终为 1、未解析因子场景下指标不崩溃。
关键文件:
python/sglang/srt/disaggregation/common/conn.py(模块 连接管理;类别 source;类型 core-logic;符号 get_kv_replica_factor, resolve_kv_replica_factor, get_transfer_metric): 核心实现文件:添加了复制因子管理、解析方法和指标乘法逻辑,是变更的枢纽。
test/registered/unit/disaggregation/test_kv_transfer_replica_metric.py(模块 测试;类别 test;类型 test-coverage;符号 _room, _make_kv_mgr, _make_sender, TestKVTransferReplicaMetric): 新增单元测试文件,覆盖复制因子解析与指标计数的三种场景,确保行为正确且不会崩溃。
python/sglang/srt/disaggregation/mooncake/conn.py(模块 Mooncake后端;类别 source;类型 core-logic): Mooncake 后端的集成点:在引导线程收集完所有目的地信息后调用 resolve_kv_replica_factor。
python/sglang/srt/disaggregation/mori/conn.py(模块 Mori后端;类别 source;类型 core-logic): Mori 后端的集成点:在 _handle_transfer_message 中所有目的地信息收集完毕后调用 resolve_kv_replica_factor。
python/sglang/srt/disaggregation/nixl/conn.py(模块 Nixl后端;类别 source;类型 core-logic): Nixl 后端的集成点:在 bootstrap_thread 中收集完所有目的地信息后调用 resolve_kv_replica_factor。
关键符号:get_kv_replica_factor, resolve_kv_replica_factor, get_transfer_metric
关键源码片段
python/sglang/srt/disaggregation/common/conn.py
核心实现文件:添加了复制因子管理、解析方法和指标乘法逻辑,是变更的枢纽。
# python/sglang/srt/disaggregation/common/conn.py 核心修改
class CommonKVManager(BaseKVManager):
def __init__(self, ...):
# ...
# 初始化复制因子:MLA 为 None(待引导时解析),非 MLA 固定为 1
self._kv_replica_factor: Optional[int] = None if is_mla_backend else 1
# ...
def get_kv_replica_factor(self) -> int:
# 安全获取因子,未解析时返回 1 并输出一次性警告
if self._kv_replica_factor is None:
logger.warning_once(
"get_kv_replica_factor called before resolve_kv_replica_factor; "
"assuming 1, but the metrics may be inaccurate."
)
return 1
return self._kv_replica_factor
def resolve_kv_replica_factor(self, transfer_infos: Dict) -> None:
# 仅 MLA 后端有复制因子;非 MLA 无需操作
if not self.is_mla_backend:
return
info = next(iter(transfer_infos.values()), None)
if info is None or info.required_dst_info_num is None:
logger.warning_once(
"resolve_kv_replica_factor: no decode destinations available; "
"KV transfer metrics may be inaccurate."
)
return
# 从任意一个 TransferInfo 中读取所需目的地数量作为复制因子
self._kv_replica_factor = info.required_dst_info_num
# 在 CommonKVSender.get_transfer_metric() 中,计算 total_bytes 后:
total_bytes *= self.kv_mgr.get_kv_replica_factor() # 乘以复制因子
self._transfer_metric.transfer_total_bytes = total_bytes
test/registered/unit/disaggregation/test_kv_transfer_replica_metric.py
新增单元测试文件,覆盖复制因子解析与指标计数的三种场景,确保行为正确且不会崩溃。
"""Unit tests for KV-transfer replication accounting in PD disaggregation."""
import unittest
from types import SimpleNamespace
import numpy as np
from sglang.srt.disaggregation.base.conn import KVTransferMetric
from sglang.srt.disaggregation.common.conn import CommonKVManager, CommonKVSender
from sglang.test.ci.ci_register import register_cpu_ci
from sglang.test.test_utils import CustomTestCase
register_cpu_ci(est_time=5, suite="base-a-test-cpu")
KV_ITEM_LENS_SUM = 100
STATE_ITEM_LENS_SUM = 7
def _room(fan_out):
# 构造一个具有 fan_out 个目的地信息的字典,每个目的地都报告 required_dst_info_num = fan_out
return {
f"sess{i}": SimpleNamespace(required_dst_info_num=fan_out)
for i in range(fan_out)
}
def _make_kv_mgr(is_mla_backend):
# 绕过 __init__ 直接构造 CommonKVManager,只设置测试用字段
mgr = CommonKVManager.__new__(CommonKVManager)
mgr.is_mla_backend = is_mla_backend
mgr.kv_item_lens_sum = KV_ITEM_LENS_SUM
mgr.state_item_lens_sum = STATE_ITEM_LENS_SUM
mgr._kv_replica_factor = None if is_mla_backend else 1
return mgr
def _make_sender(kv_mgr):
# 绕过 __init__ 构造 CommonKVSender,只设置测试用字段
sender = CommonKVSender.__new__(CommonKVSender)
sender._transfer_metric = KVTransferMetric()
sender._transfer_num_kv_indices = 0
sender._transfer_num_state_indices = 0
sender.kv_mgr = kv_mgr
return sender
class TestKVTransferReplicaMetric(CustomTestCase):
def test_mla_scales_kv_and_state_bytes_by_fan_out(self):
# MLA:复制因子应为 4,总字节 = (KV 字节 + state 字节 ) * 4
mgr = _make_kv_mgr(is_mla_backend=True)
sender = _make_sender(mgr)
mgr.resolve_kv_replica_factor(_room(4))
self.assertEqual(mgr._kv_replica_factor, 4)
sender._record_transfer_indices(
np.arange(8, dtype=np.int32), [np.arange(5, dtype=np.int32)]
)
expected = (8 * KV_ITEM_LENS_SUM + 5 * STATE_ITEM_LENS_SUM) * 4
self.assertEqual(sender.get_transfer_metric().transfer_total_bytes, expected)
def test_non_mla_factor_is_one_regardless_of_destinations(self):
# 非 MLA:有 8 个目的地但因子固定 1
mgr = _make_kv_mgr(is_mla_backend=False)
sender = _make_sender(mgr)
mgr.resolve_kv_replica_factor(_room(8))
self.assertEqual(mgr._kv_replica_factor, 1)
sender._record_transfer_indices(np.arange(6, dtype=np.int32), None)
self.assertEqual(
sender.get_transfer_metric().transfer_total_bytes, 6 * KV_ITEM_LENS_SUM
)
def test_unresolved_factor_does_not_crash_metric(self):
# 空 room 或 required_dst_info_num 为 None 时,因子保持 None,get_transfer_metric 不会崩溃
for room in ({}, {"sess0": SimpleNamespace(required_dst_info_num=None)}):
mgr = _make_kv_mgr(is_mla_backend=True)
sender = _make_sender(mgr)
mgr.resolve_kv_replica_factor(room)
self.assertIsNone(mgr._kv_replica_factor)
sender._record_transfer_indices(np.arange(4, dtype=np.int32), None)
self.assertIsInstance(
sender.get_transfer_metric().transfer_total_bytes, int
)
if __name__ == "__main__":
unittest.main()
评论区精华
主要讨论点:gemini-code-assist[bot] 在 Review 中指出 _kv_replica_factor 初始放在条件分支中可能导致 AttributeError,因为解码端的 CommonKVManager 可能未定义该属性。作者 hunhokim 采纳建议,将初始化移到 __init__ 的公共部分(在所有 disaggregation_mode 分支之前),确保属性始终存在。该问题已解决,无遗留疑虑。
- _kv_replica_factor 属性初始化位置可能导致 AttributeError (correctness): 作者将初始化移至 CommonKVManager.init 的公共部分,确保所有模式下均有定义。
风险与影响
关联脉络
参与讨论