Prhub

#35950 refactor(disagg): drop dead placeholder overrides in Common KV sender/receiver

原始 PR 作者 ShangmingCai 合并时间 2026-08-22 15:45 文件变更 1 提交数 1 评论 2 代码增减 +0 / -9

执行摘要

删除 Common KV 连接层 3 处死桩,恢复 ABC 强制

作者在 PR body 中指出三处桩方法均为不可达死代码:CommonKVSender.poll 注解为 -> KVPoll 却返回 Nonefailure_exception 抛出 "Fake KVReceiver Exception",该字符串是从 fake/conn.pyFakeKVReceiver 复制粘贴而来,甚至被复制进了 Sender 侧,名词都是错的——真实后端一旦触达会从生产路径报告 "Fake" 失败。作者通过 AST 遍历确认所有子类(Nixl / Mooncake / Mori)都已各自覆盖这两个方法,且无 super().poll() / super().failure_exception() 委托,两个 Common 类也没有被直接实例化。删除比修正更优,因为能恢复 BaseKVSender 的 ABC 约束:CommonKVSender 原先实现了全部 abstractmethod 因而完全具体,删除后 pollfailure_exception 重新成为强制契约。

值得快速精读,尤其是 PR body 的验证方法论:作者用 AST 遍历证明死代码不可达、用 abstractmethod 集合对比证明 ABC 语义变化、用镜像类实测构造行为,是"删除代码"类 PR 的高质量范本。核心设计决策是"删除而非修正"——通过恢复 ABC 强制,把未来后端的实现遗漏从运行时无声失败提前到构造期显式报错,这类"用类型/抽象约束消灭一类 bug"的思路可直接借鉴。

讨论亮点

该 PR 没有 review 评论线程,但 PR body 中作者主动暴露了两个值得注意的决策点:

  • out-of-tree 兼容性(design):删除后 CommonKVSender 变为抽象类,外部后端若未实现 poll / failure_exception 会在实例化时直接失败而非首次使用时失败。作者明确判定这是"修复而不是 break"——"such a backend was already broken, it just failed later and far less intelligibly",并注明这是唯一需要 reviewer 关注的行为后果。

  • 验证方法论(testing):作者没有写单元测试,而是用 AST 遍历生成覆盖表、程序化比较各类的 abstractmethod 集合、并用镜像类实测 before/after 构造行为。PR body 中强调覆盖表"derived by AST walk ... not by eye",体现出对"删除不可达代码"这类改动采用结构验证而非样例测试的思路。

合入前作者在 issue 评论中发起 /rerun-group disaggregation,CI 在 4-gpu-gb3002-gpu-h1008-gpu-h20 上共重跑多组 disaggregation 测试全部通过,补上了本地无法覆盖的多 GPU 验证。

实现拆解

  1. 定位死桩:在 common/conn.py CommonKVSender 中定位 poll(返回 None 且与类型注解矛盾)与 failure_exception(抛出 "Fake KVReceiver Exception"),并在 CommonKVReceiver 中定位同样错误的 failure_exception。三处均位于 _check_bootstrap_timeout / _check_waiting_timeoutclear 之间,是历史复制粘贴留下的占位符。

  2. 全量证明不可达:作者对整个 disaggregation 模块做 AST 遍历生成覆盖表——NixlKVSender / MooncakeKVSender / MoriKVSender 均覆盖 pollfailure_exceptionAscendKVSender / AscendKVReceiver 继承 Mooncake 类获得真实实现;并程序化确认无任何 super().poll() / super().failure_exception() 委托、无直接实例化。这一步把"看起来是死代码"升级为"证明是死代码"。

  3. 删除并恢复 ABC 约束:直接删除 9 行。关键差异在于 CommonKVSender 原先实现完 BaseKVSender 的全部 abstractmethod 因而是具体类,删除后 pollfailure_exception 重新成为抽象方法,未来后端漏实现会在构造时报 TypeErrorCommonKVReceiver 原本就未实现 poll,本就是抽象类,因此对该类只是纯粹的死代码清理,实例化行为无变化。

  4. 验证与配套:无测试文件变更(作者认为删除不可达代码无可测行为);作者通过镜像抽象面做构造行为前后对照验证,并用固定版本的 ruff / black / isort 通过静态检查。多 GPU 的 PD 故障注入路径未在本地执行,依赖 CI 的 disaggregation 组验证(合入前通过 /rerun-group disaggregation 重跑并全绿)。

  5. 兼容性说明:唯一潜在行为差异是 out-of-tree 后端若继承 CommonKVSender 而未实现这两个方法,将从"首次使用时失败"提前到"实例化时失败"。作者明确论证这是修复而非破坏,并主动提请 reviewer 注意。

文件 模块 状态 重要度
python/sglang/srt/disaggregation/common/conn.py 解聚连接 modified 6.04

关键符号

poll failure_exception

关键源码片段

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

唯一变更文件:删除 `CommonKVSender.poll`、`CommonKVSender.failure_exception`、`CommonKVReceiver.failure_exception` 三个不可达桩方法。价值不在于删除本身,而在于删除后 `CommonKVSender` 从具体类恢复为抽象类,`BaseKVSender` 的 abstractmethod 契约重新生效,未来后端漏实现会在构造时报 `TypeError` 而非运行时无声失败。

# CommonKVSender 删除两个死桩后的类结构(head 版本)。
# BaseKVSender 将 poll 与 failure_exception 声明为 abstractmethod,
# 删除桩后此类重新成为抽象类:未来新增后端若漏实现其中之一,
# 将在构造时立即抛出 TypeError,而不是运行时返回 None 或
# 抛出属于 fake 后端的误导性异常。class CommonKVSender(BaseKVSender):
    # ... 构造、ZMQ 连接与发送逻辑保持不变 ...
​
    def _check_bootstrap_timeout(self) -> Optional[KVPoll]:
        # 存活路径上的真实超时检查,与删除的 poll 桩形成对照:
        # 真正的轮询语义由 Nixl / Mooncake / Mori 等子类各自实现。
        if self.init_time is None:
            return None
        elapsed = time.time() - self.init_time
        if elapsed < self.kv_mgr.bootstrap_timeout:
            return None
        logger.warning_once(
            "Some requests timed out when bootstrapping, "
            "which means prefill instances fail to receive the KV indices "
            "from the decode instance of this request. "
            "If a greater mean TTFT is acceptable, you can "
            "'export SGLANG_DISAGGREGATION_BOOTSTRAP_TIMEOUT=600' "
            "(10 minutes) to relax the timeout condition. "
        )
        self.kv_mgr.record_failure(
            self.bootstrap_room,
            f"Request {self.bootstrap_room} timed out after "
            f"{elapsed:.1f}s in KVPoll.Bootstrapping",
        )
        self.kv_mgr.update_status(self.bootstrap_room, KVPoll.Failed)
        return KVPoll.Failed
​
    # 原位置的 poll / failure_exception 桩已删除:
    # - poll 曾注解 -> KVPoll 却返回 None,属于无声失败;
    # - failure_exception 曾抛出从 fake/conn.py 复制来的
    # "Fake KVReceiver Exception",名词与 Sender 侧语义相悖。
    # 现在二者由 BaseKVSender 的 abstractmethod 强制约束,
    # 任何新子类必须在构造前补齐实现。
​
    def clear(self) -> None:
        # 清理请求状态与辅助表,防止房间结束时引用泄漏。
        self.kv_mgr.request_status.pop(self.bootstrap_room, None)
        if hasattr(self.kv_mgr, "req_to_decode_prefix_len"):
            self.kv_mgr.req_to_decode_prefix_len.pop(self.bootstrap_room, None)
        if hasattr(self.kv_mgr, "transfer_infos"):
            self.kv_mgr.transfer_infos.pop(self.bootstrap_room, None)
        if hasattr(self.kv_mgr, "_deferred_ack_targets"):
            # 若房间在未入队任何 chunk 时结束(如请求被中止),
            # 需要丢弃持有的 ack target,否则会在 prefill 侧泄漏。
            self.kv_mgr._deferred_ack_targets.pop(self.bootstrap_room, None)
​
    def abort(self):
        # 兜底终止路径:记录失败并迁移到 KVPoll.Failed 终态。
        self.kv_mgr.record_failure(self.bootstrap_room, "Aborted by AbortReq.")
        self.kv_mgr.update_status(self.bootstrap_room, KVPoll.Failed)
        self.conclude_state = KVPoll.Failed

评论区精华

out-of-tree 后端兼容性:实例化时机提前 设计

PR body 中作者主动标注唯一行为后果:外部后端若继承 CommonKVSender 且未实现 poll / failure_exception,将从首次使用时失败提前到实例化时失败。作者论证这是修复而非破坏。无 reviewer 评论对此提出异议。

结论:作者判定为合理修复:"such a backend was already broken, it just failed later and far less intelligibly"。无 in-tree 行为变化,PR 按原样合入。 · 已解决

验证方法:AST 遍历代替单元测试 测试

作者未新增测试文件,理由是被删代码不可达、无可测行为;验证依赖 AST 覆盖表、abstractmethod 集合程序化对比、镜像类构造行为实测,以及合入前的 /rerun-group disaggregation CI 重跑(多组 PD 测试全绿)。

结论:验证路径被接受:CI 的 disaggregation 组(2-gpu-h100、4-gpu-gb300、8-gpu-h20)全部通过,覆盖了本地无法执行的多 GPU 故障路径。 · 已解决

风险与影响

风险整体很低,具体边界如下:

  • 外部兼容性(唯一真实风险):任何 out-of-tree 后端直接继承 CommonKVSender 且未自行实现 poll / failure_exception,在升级后将于构造阶段抛出 TypeError。这是提前而非新的失败,但对外部集成方仍是可见行为变化,需在变更说明中明确。

  • 回归风险:删除的桩经 AST 与 super() 委托扫描证明不可达,in-tree 三个后端(Nixl / Mooncake / Mori)及 Ascend 继承链均已实现两个方法,运行时行为不变;KVPoll 在文件中仍有 38 处引用,删除不会孤悬导入。

  • 验证盲区:PD 故障路径需要多 GPU 硬件,本地未执行真实分布式测试;作者以 AST 等价验证 + CI disaggregation 组覆盖(已全绿)弥补,但严格说故障注入路径的端到端行为仍主要依赖 CI 背书。

    • 无性能、安全与数据面影响——被删代码从未执行。

对用户与线上系统:无任何运行时影响,纯静态删除,fake/conn.py 成为 "Fake ... Exception" 字符串唯一出处。对开发者:CommonKVSender 重新成为抽象类是实质的契约强化——未来新增传输后端(如新的 RDMA 实现)将在 CI 或本地启动的极早期获得清晰的 TypeError 提示,而不是带着 poll 返回 None 的隐患上线。对团队维护:这是 disaggregation 清理系列的最后一块拼图之一,与该系列共同把 srt/disaggregation 模块的"死代码 + 复制粘贴"存量系统性清除。影响面仅限 common/conn.py 一个文件、9 行删除,属于低风险高确定性的维护性改造。

外部后端实例化行为变化 纯删除无运行时行为变化 多 GPU 路径依赖 CI 验证

关联 Issue

#35838 refactor(disagg): remove unreferenced dead code
#35843 refactor(disagg): remove dead build_and_send_encode_request
#35844 refactor(disagg): remove dead get_embedding_port
#35847 refactor(disagg): collapse duplicated branches in get_kv_class
#35886 refactor(disagg): extract _all_reduce_polls helper
#35890 fix(disagg): PD transfer-failure injection was silently inert
#35948 refactor(disagg): hoist duplicated _handle_staging_req into a mixin

完整报告

参与讨论