Prhub

#43742 [Bugfix][Mooncake] Release GPU pin on failed store in MooncakeStoreConnector

原始 PR 作者 Dao007forever 合并时间 2026-06-02 09:29 文件变更 2 提交数 4 评论 0 代码增减 +193 / -161

执行摘要

修复 Mooncake 存储失败时 GPU pin 未释放的 bug

在 MooncakeStoreConnector 中,当 batch_is_exist 抛出异常(或 batch_put 异常)时,stored_requests[req_id] 计数器未被递减,导致 _get_and_clear_finished_sending 永远不会标记请求完成,调度器持续保持 delay_free_blocks,GPU 块永久泄漏。

值得合并。修复了内存泄漏 bug,逻辑正确,测试充足。可以快速合入。

讨论亮点

无显著讨论。Review 仅有一人(ivanium)批准并评论 LGTM,说明变更清晰直接。

实现拆解

  1. 分析问题:原 _handle_request 在多个 early-return 和正常路径末尾手动调用 dec_stored_requestrequest_queue.task_done(),但异常路径(如 batch_is_existraise)会跳过这些调用,导致计数器泄漏。
  2. 重构 _handle_request:将早期返回条件(token_len==0should_skip_request)和主存储逻辑(key 构造、batch_is_existbatch_put)整体移入 try 块,并在 finally 中统一执行 dec_stored_requesttask_done()。这样无论正常返回还是异常传播,计数器都会递减。
  3. 保留异常传播batch_is_exist 的异常在 except 块中记录后 raise,由外层 KVTransferThread.run() 捕获日志,不影响 finally 执行。batch_put 的异常已经被 try-except 记录且不重新抛出,finally 仍能执行。
  4. 新增单元测试:在 test_mooncake_store_worker.py 中添加 test_store_sending_thread_releases_pin_on_batch_is_exist_failuretest_store_sending_thread_releases_pin_on_batch_put_failure,验证异常后 stored_requests 正确归零,且 batch_put 未被调用(对 batch_is_exist 情形)。
文件 模块 状态 重要度
vllm/distributed/kv_transfer/kv_connector/v1/mooncake/store/worker.py KV 连接器 modified 7.74
tests/v1/kv_connector/unit/test_mooncake_store_worker.py 测试 modified 5.64

关键符号

_handle_request test_store_sending_thread_releases_pin_on_batch_is_exist_failure test_store_sending_thread_releases_pin_on_batch_put_failure

关键源码片段

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

核心源码,修复异常路径下 GPU pin 未释放的 bug,是变更的主要逻辑所在。

def _handle_request(self, req_meta: ReqMeta):
    # ... 前置初始化代码 ...
    if req_id not in self.stored_requests:
        self.request_queue.task_done()
        return
​
    # 将 main body 包裹在 try 中,确保 finally 总被执行
    try:
        if token_len == 0:
            # finally 将执行 dec_stored_request 和 task_done
            return
        if self._should_skip_request(req_id):
            return
​
        # 构造存储 key 列表 ...
        # ( 省略具体实现,保持简洁 )
​
        if not keys:
            return
​
        # batch_is_exist 可能抛出异常
        save_exists_start = time.perf_counter()
        try:
            exists_states = self.store.batch_is_exist(keys)
        except Exception:
            self._record_operation(
                "save_exists",
                save_exists_start,
                len(keys),
                status="error",
                num_failed_keys=len(keys),
            )
            raise # 异常传播,但 finally 仍会执行
​
        # ... 后续 batch_put 等操作 ...
        # ( 省略 )
    finally:
        # 无论成功或异常,都递减 stored_requests[req_id] 并标记任务完成
        # 这让调度器可以释放 GPU blocks(通过 delay_free_blocks)
        self.dec_stored_request(req_id)
        self.request_queue.task_done()
tests/v1/kv_connector/unit/test_mooncake_store_worker.py test-coverage

新增两个单元测试,验证异常场景下计数器归零,确保修复有效且无回归。

def test_store_sending_thread_releases_pin_on_batch_is_exist_failure():
    # batch_is_exist 抛出异常时,stored_requests 仍应递减
    store = MagicMock()
    store.batch_is_exist.side_effect = RuntimeError("mooncake down")
    thread = _make_store_sending_thread(store)
​
    thread.add_stored_request("req-a")
    with pytest.raises(RuntimeError):
        thread._handle_request(_make_store_req("req-a", [b"a0", b"a1"]))
​
    assert thread.stored_requests["req-a"] == 0
    store.batch_put_from_multi_buffers.assert_not_called()
​
​
def test_store_sending_thread_releases_pin_on_batch_put_failure():
    # batch_put_from_multi_buffers 异常时,计数仍应递减
    store = MagicMock()
    store.batch_is_exist.return_value = [0, 0]
    store.batch_put_from_multi_buffers.side_effect = RuntimeError("rdma error")
    thread = _make_store_sending_thread(store)
​
    thread.add_stored_request("req-a")
    thread._handle_request(_make_store_req("req-a", [b"a0", b"a1"]))
​
    assert thread.stored_requests["req-a"] == 0

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

回归风险:低。修复仅在异常路径增加 finally 清理,正常路径行为不变(所有原来的 dec_stored_request 调用被删除,但 finally 会执行等价操作,行为一致)。异常处理:batch_is_exist 的异常仍被重新抛出,外层日志记录不受影响。batch_put 异常已被捕捉不抛出,也无变化。测试覆盖:两个新测试覆盖了主要异常场景,但 Mooncake 存储的其他异常路径(如 prepare_value)可能未被覆盖,但最终 finally 块确保计数器递减,所以风险较小。

对用户:消除了 Mooncake 共享 KV 缓存模式下偶发的 GPU 内存泄漏,提升稳定性。对系统:MooncakeStoreConnector 会正确释放 pin,调度器不再阻塞释放,避免 OOM。对团队:很小的改动,易于 review 和部署。

核心路径变更 GPU 内存泄漏风险 依赖外部存储服务

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论