muti-connector 的原理
MultiConnector 是 vLLM 的 KV 连接器代理,可以让多个子连接器(如 offload、transfer)同时运行。
核心流程:
请求进来 → 每个 sub-connector 调用 get_num_new_matched_tokens
→ 取最大值作为 num_external_tokens
→ 中标者:返回最大值的 connector
→ 未中标者:num_external_tokens = 0
关键问题:
- 冷请求(所有 connector 都返回 0)→ chosen_connector = -1 → 所有 connector 都未中标
- 修改前:所有 connector 拿到 empty_blocks → offload connector 无法记录存储
- 修改后:所有 connector 拿到真实 blocks,但 num_external_tokens = 0
update_state_after_alloc 的原理
OffloadingConnectorScheduler.update_state_after_alloc
简而言之,就是如果 num_external_tokens > 0,找出哪些 block 需要加载,然后创建 job。
SimpleCPUOffloadScheduler.update_state_after_alloc
blocks 一分配就记录到 _reqs_to_store,请求执行完后,从 _reqs_to_store 取出 blocks 创建存储任务。
差异分析
其实 scheduler-output 就包含了所有 connector 需要的信息,但是可以提前保存 store 任务,也可以最后保存 store 任务。
| 方式 | 时机 | 代表 connector |
|---|---|---|
| 提前记录 | update_state_after_alloc 时(blocks 刚分配完) |
SimpleCPUOffloadScheduler |
| 最后记录 | _build_store_jobs 时(构建 connector meta 前) |
OffloadingConnectorScheduler |
Bug 根源
对于一个冷请求,num_external_tokens == 0,但存在 store 任务。在目前的判断中直接被拒了。
具体机制:
| 步骤 | 修改前 | 修改后 |
|---|---|---|
| MultiConnector 调用 | update_state_after_alloc(request, empty_blocks, 0) |
update_state_after_alloc(request, blocks, 0) |
| SimpleCPUOffloadScheduler 记录 | _reqs_to_store[req_id] = StoreRequestState(block_ids=[]) |
_reqs_to_store[req_id] = StoreRequestState(block_ids=[101, 102, 103]) |
| 后续存储 | 看到空 blocks → 什么都不存 ❌ | 看到真实 blocks → 正常存储 ✅ |
恶性循环:冷请求 → 没存 → 下次还是冷请求 → 永远存不了
num_external_tokens == 0 对不同 connector 的影响
| Connector | num_external_tokens == 0 时的行为 | 是否受影响 |
|---|---|---|
| OffloadingConnectorScheduler | 直接 return,不创建加载任务 | ❌ 不受影响(存储路径不依赖这个方法) |
| SimpleCPUOffloadScheduler | 创建存储记录,但 blocks 是空的 | ✅ 受影响(存储记录是空的)→ 已修复 |
| moriio_connector | 可能错误加载(没有检查 num_external_tokens) | ✅ 受影响 → 已修复 |
新 Contract 的定义
PR #46865 在 base.py docstring 中明确了新 contract:
"Decide whether to load based on num_external_tokens, not on
whether blocks is empty: blocks may be non-empty even when
num_external_tokens == 0 (e.g. a non-chosen sub-connector of
MultiConnector still receives the request's real blocks)."
含义:
- 连接器应该根据
num_external_tokens判断是否加载 - 不应该根据
blocks是否为空来判断 - 因为 MultiConnector 修改后,non-chosen connector 也会拿到真实 blocks
moriio_connector 为什么需要修复
moriio connector 用于 P/D 分离(Prefill/Decode 分离)。
两个独立参数:
num_external_tokens:D 节点本地还需要从外部加载多少 tokens(D 节点自己计算)remote_block_ids:P 节点有哪些 blocks 的数据可以传给 D 节点(P 节点传递)
什么场景 num_external_tokens == 0 但 remote_block_ids 存在?
- D 节点本地已经有大部分数据(prefix cache hit)
- 但 P 节点仍然有 blocks 需要通知释放
修改前:
local_block_ids = blocks.get_block_ids()[0] # ← 不管 num_external_tokens
修改后:
if num_external_tokens > 0:
local_block_ids = blocks.get_block_ids()[0]
else:
local_block_ids = [] # ← 不加载,但仍然通知 P 释放
PR #46865 的完整修复范围
| 文件 | 改动类型 | 改了什么 | 为什么改 |
|---|---|---|---|
| base.py | 文档 | 更新 docstring | 明确新 contract |
| multi_connector.py | 核心修复 | empty_blocks → blocks | 让 non-chosen connector 拿到真实 blocks |
| moriio_connector.py | 适配修复 | 加 num_external_tokens 判断 | 防止错误加载 |