Prhub

#30351 [Bug fix] Account for KV replication fan-out in transfer-byte metrics

原始 PR 作者 hunhokim 合并时间 2026-07-15 01:08 文件变更 5 提交数 8 评论 11 代码增减 +138 / -0

执行摘要

修复 PD 分离中 KV 传输指标未考虑复制扇出的问题

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.

此 PR 修复逻辑清晰,测试覆盖完整,值得阅读。设计上“在引导屏障点一次性地解析拓扑不变量并缓存”的模式可推广到其他类似场景。建议从事分离部署或分布式推理监控的开发者重点关注。

讨论亮点

主要讨论点:gemini-code-assist[bot] 在 Review 中指出 _kv_replica_factor 初始放在条件分支中可能导致 AttributeError,因为解码端的 CommonKVManager 可能未定义该属性。作者 hunhokim 采纳建议,将初始化移到 __init__ 的公共部分(在所有 disaggregation_mode 分支之前),确保属性始终存在。该问题已解决,无遗留疑虑。

实现拆解

  1. 属性添加与因子初始化:在 CommonKVManager.__init__ 中添加 _kv_replica_factor 属性,对于 MLA 后端设为 None(待引导时解析),对于非 MLA 后端固定为 1。
  2. 因子解析方法:新增 resolve_kv_replica_factor(transfer_infos) 方法,从任意一个 TransferInforequired_dst_info_num 字段获取复制因子并缓存到 _kv_replica_factor。仅在 is_mla_backend 为 True 时执行,非 MLA 直接跳过。
  3. 安全获取方法:新增 get_kv_replica_factor() 方法,若因子未解析(None)则返回 1 并输出一次性警告,避免指标崩溃。
  4. 指标乘法:在 CommonKVSender.get_transfer_metric() 中计算总字节数时,乘以 kv_mgr.get_kv_replica_factor() 得到真实的传输字节数。
  5. 后端集成:在 Mooncake、Mori、Nixl 三种传输后端的引导栅栏点(即收集到所有解码段目的地信息时)调用 resolve_kv_replica_factor,确保因子在指标计算前被正确解析。
  6. 单元测试:新增 test_kv_transfer_replica_metric.py 文件,包含三个用例:MLA 场景下因子正确缩放 KV 和状态字节、非 MLA 场景因子始终为 1、未解析因子场景下指标不崩溃。
文件 模块 状态 重要度
python/sglang/srt/disaggregation/common/conn.py 连接管理 modified 7.19
test/registered/unit/disaggregation/test_kv_transfer_replica_metric.py 测试 added 7.18
python/sglang/srt/disaggregation/mooncake/conn.py Mooncake 后端 modified 4.18
python/sglang/srt/disaggregation/mori/conn.py Mori 后端 modified 4.18
python/sglang/srt/disaggregation/nixl/conn.py Nixl 后端 modified 4.18

关键符号

get_kv_replica_factor resolve_kv_replica_factor get_transfer_metric

关键源码片段

python/sglang/srt/disaggregation/common/conn.py core-logic

核心实现文件:添加了复制因子管理、解析方法和指标乘法逻辑,是变更的枢纽。

# 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 test-coverage

新增单元测试文件,覆盖复制因子解析与指标计数的三种场景,确保行为正确且不会崩溃。

"""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 CustomTestCaseregister_cpu_ci(est_time=5, suite="base-a-test-cpu")KV_ITEM_LENS_SUM = 100
STATE_ITEM_LENS_SUM = 7def _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 mgrdef _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 senderclass 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()

评论区精华

_kv_replica_factor 属性初始化位置可能导致 AttributeError 正确性

gemini-code-assist[bot] 指出 _kv_replica_factor 仅初始化于 PREFILL 分支,解码端访问时可能引发 AttributeError。

结论:作者将初始化移至 CommonKVManager.__init__ 的公共部分,确保所有模式下均有定义。 · 已解决

风险与影响

  1. 指标路径变更风险:若 resolve_kv_replica_factor 未被调用(例如引导流程异常),_kv_replica_factor 保持 Noneget_transfer_metric 会返回 1 并发出警告,指标可能偏低但不会崩溃,也不影响数据面。
  2. 非 MLA 模型不受影响:非 MLA 后端因子固定为 1,不受解析逻辑影响,无回归风险。
  3. 性能风险:因子解析仅在引导时执行一次,指标计算时只是整数乘法,性能开销可忽略。

影响范围:仅限 PD 分离部署场景下的 MLA 模型(如 DeepSeek-V2-Lite),且拓扑为 Prefill-CP + Decode-TP 时。
影响程度:修复了 KV 传输监控指标的严重失真(从 1/4 恢复到准确值),使运维和调试人员能够正确判断网络状况。对模型推理精度、延迟、吞吐量无任何影响。
用户收益:升级后 Prometheus 等监控上 kv_transfer_total_mbkv_transfer_speed_gb_s 将反映真实的传输字节数,避免误诊网络问题。

指标路径变更 未解析因子回退为 1 但发出警告 仅影响分离部署 MLA 场景

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论