Prhub

#40449 [Bugfix] release KV blocks for skipped P-ranks to prevent invalid KV errors and timeouts when P_tp > D_tp and MLA

原始 PR 作者 yangrz7 合并时间 2026-04-29 02:38 文件变更 2 提交数 4 评论 4 代码增减 +120 / -1

执行摘要

修复 MLA 场景下 skip P-rank KV 块释放的请求 ID 错误

在 MLA 架构中,KV Cache 在 P 侧所有 TP rank 上完全复制,D 只需从单个 P rank 读取即可获得完整 KV Cache。_read_blocks_for_req 在读取远程秩 [0] 后正确退出,但向跳过的 P rank 发送释放通知时使用了错误的请求 ID(本地 req_id 而非 prefill 侧的 request_id),导致 KV block 不能被识别而只能等待超时释放。详见 PR body:'In MLA architecture, KV Cache is fully replicated across all TP ranks on the P side — D only needs to read from one P rank to obtain a complete KV Cache. _read_blocks_for_req correctly breaks after reading remote_ranks[0], but then sends a "free notification" to the skipped P ranks using the wrong request ID'。

值得精读。该 PR 用一行代码展示了分布式系统中请求 ID 传递不一致的典型 bug,并附有高质量的单元测试,是理解 MLA PD-disaggregated 流程和 Nixl 连接器的好材料。

讨论亮点
  • reviewer Dao007forever 要求包含单元测试(评论),作者 yangrz7 将测试纳入 PR,并针对 CI 中的 Transformers GenerationConfig 失败合并 main 修复。
  • 自动审核 bot gemini-code-assist 确认变更正确:确保远程秩使用正确的请求标识。
  • maintainer simon-mo 直接批准,无额外意见。

实现拆解

  1. 定位问题:在 vllm/distributed/kv_transfer/kv_connector/v1/nixl/worker.py_read_blocks_for_req 方法中,当 self.use_mla and tp_ratio < 0 时,构造通知 ID 使用了本地请求 ID(req_id)而非远程预填充侧的请求 ID(meta.remote.request_id),导致接收方无法匹配。
  2. 一行修复:将 notif_id = f"{req_id}:{self.world_size}".encode() 修改为 notif_id = f"{meta.remote.request_id}:{self.world_size}".encode(),确保通知使用预填充侧统一请求 ID。
  3. 新增单元测试:在 tests/v1/kv_connector/unit/test_nixl_connector.py 中增加 test_mla_broadcast_notif_uses_remote_request_id 测试方法,构建 D TP=1、P TP=4 的 MLA 场景,通过 Mock 验证 send_notif 被调用时携带了正确的 remote.request_id
  4. 配套调整:测试文件额外注册远程引擎和代理状态,模拟了 transfer_topodst_xfer_side_handles 等依赖组件,确保测试可独立运行。
文件 模块 状态 重要度
vllm/distributed/kv_transfer/kv_connector/v1/nixl/worker.py KV 传输 modified 5.58
tests/v1/kv_connector/unit/test_nixl_connector.py 单元测试 modified 6.3

关键符号

_read_blocks_for_req test_mla_broadcast_notif_uses_remote_request_id

关键源码片段

vllm/distributed/kv_transfer/kv_connector/v1/nixl/worker.py core-logic

核心修复文件:在 _read_blocks_for_req 的 MLA + tp_ratio < 0 分支中,将通知 ID 从本地 req_id 改为 meta.remote.request_id,一行变更解决了 KV 块无法释放的问题。

# 文件 : vllm/distributed/kv_transfer/kv_connector/v1/nixl/worker.py
# 方法 _read_blocks_for_req 中的关键片段(第 1971-1978 行)if self.use_mla and tp_ratio < 0:
    # 需要通知其他 remote rank 当前请求已读取完毕,
    # 以便它们可以更新状态或释放资源。
    # 注意:这里必须使用预填充侧的 request_id (
    # meta.remote.request_id ),因为 remote rank
    # 以该 ID 索引其 _reqs_to_send;若用本地 decode
    # 的 req_id,对方无法匹配,导致块延迟释放。
    notif_id = f"{meta.remote.request_id}:{self.world_size}".encode()
    remote_agents = self._remote_agents[meta.remote.engine_id]
    for rank_to_notify, agent in remote_agents.items():
        if rank_to_notify != remote_rank:
            self.nixl_wrapper.send_notif(agent, notif_msg=notif_id)
tests/v1/kv_connector/unit/test_nixl_connector.py test-coverage

新增单元测试 test_mla_broadcast_notif_uses_remote_request_id,模拟 MLA + D_tp < P_tp 场景,验证通知 ID 正确性,防止回归。

# 文件 : tests/v1/kv_connector/unit/test_nixl_connector.py
# 新增测试方法(第 2487 行起)@patch(
    "vllm.distributed.kv_transfer.kv_connector.v1.nixl.worker.NixlWrapper",
    FakeNixlWrapper,
)
def test_mla_broadcast_notif_uses_remote_request_id(
    self, default_vllm_config, dist_init
):
    """验证 MLA + remote TP > local TP 时,广播通知必须使用
    预填充侧的 request_id,否则会被 _get_new_notifs 拒绝。"""
    decode_tp_size = 1
    prefill_tp_size = 4
    vllm_config = create_vllm_config()
    vllm_config.parallel_config.tensor_parallel_size = decode_tp_size
​
    connector = NixlConnector(
        vllm_config, KVConnectorRole.WORKER, make_kv_cache_config(block_size=16)
    )
    connector.connector_worker = FakeNixlConnectorWorker(
        vllm_config, connector.engine_id, hand_shake_latency=0
    )
    worker = connector.connector_worker
    worker.use_mla = True # 强制走 MLA 路径
​
    # 手动注册远程引擎并填充传输拓扑依赖
    remote_engine_id = "remote_engine"
    worker.transfer_topo.register_remote_engine(...)
    worker._remote_agents[remote_engine_id] = {
        rank: f"agent_p{rank}" for rank in range(prefill_tp_size)
    }
    worker.dst_xfer_side_handles = {
        remote_engine_id: {rank: 100 + rank for rank in range(prefill_tp_size)}
    }
​
    decode_req_id = "decode-req-AAAA"
    prefill_req_id = "prefill-req-BBBB"
​
    metadata = NixlConnectorMetadata()
    metadata.add_new_req_to_recv(
        request_id=decode_req_id,
        local_block_ids=([0, 1, 2],),
        kv_transfer_params={
            "remote_block_ids": ([10, 11, 12],),
            "remote_engine_id": remote_engine_id,
            "remote_request_id": prefill_req_id,
            ...
        },
    )
    meta = metadata.reqs_to_recv[decode_req_id]
​
    # 后续通过 patch worker._read_blocks_for_req 或直接调用并捕获
    # send_notif 参数,验证 notif_msg 包含 prefill_req_id 而非 decode_req_id
    # (具体断言实现见 PR 补充代码)

评论区精华

要求包含单元测试 测试

reviewer Dao007forever 提供了参考测试链接,要求将此 bug 的单元测试纳入 PR。

结论:作者 yangrz7 已将测试包含进来,并合并 main 修复 CI 失败。 · 已解决

风险与影响

风险极低:仅有 1 行核心逻辑变更,且完全隔离在 MLA + tp_ratio < 0 的特定分支内,不影响其他路径。新增的单元测试直接覆盖该路径,验证通知 ID 的正确性。若修改有误,仅影响 MLA PD-disaggregated 下通知机制(可能导致类似原 bug 的行为),不会引发数据损坏或崩溃。

  • 用户影响:修复了 MLA PD-disaggregated 部署(如 GLM5)在 P_tp > D_tp 时大量产生的 'Potentially invalid KV blocks' 错误和 'Releasing expired KV blocks' 超时警告,KV 块释放及时,避免内存泄漏和请求失败。
  • 系统影响:消除因通知 ID 不匹配导致的 block 延迟释放,节省显存和时间开销。
  • 团队影响:无迁移成本,一行修复即可回传至已有分支。
核心路径变更 MLA 特定逻辑 新增测试覆盖

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论