muti-connector 的原理

MultiConnector 是 vLLM 的 KV 连接器代理,可以让多个子连接器(如 offload、transfer)同时运行。

核心流程

请求进来 → 每个 sub-connector 调用 get_num_new_matched_tokens
         → 取最大值作为 num_external_tokens
         → 中标者:返回最大值的 connector
         → 未中标者: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)."

含义

moriio_connector 为什么需要修复

moriio connector 用于 P/D 分离(Prefill/Decode 分离)。

两个独立参数

什么场景 num_external_tokens == 0remote_block_ids 存在?

修改前

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 判断 防止错误加载