Mooncake 与 vLLM V0 解耦推理集成实战:基于 MooncakeConnector 的跨节点 KV Cache 传输部署指南
Mooncake 与 vLLM V0 解耦推理集成实战:基于 MooncakeConnector 的跨节点 KV Cache 传输部署指南
本篇技术指南以 Mooncake 仓库中的 vLLM V0 集成文档(vllm-integration-v0.2.md)为骨架,系统讲解如何在 vLLM V0(≤ v0.6.4.post1)上通过 MooncakeConnector 实现 Prefill-Decode(PD)分离的跨节点 KV Cache 直传,涵盖 mooncake.json 配置、RDMA/TCP 传输选型、--kv-transfer-config 参数语义、代理服务器实现与端到端验证。读完本文,你将能够独立搭建一个 1 个 Prefill 实例 + 1 个 Decode 实例的解耦推理环境,并通过 OpenAI 兼容接口验证 KV Cache 传输是否真正生效,同时了解如何向 vLLM V1 迁移。
归档说明:本文所依据的 v0.2 集成文档目前已在仓库中被标记为
orphan并归档,其内容已合并进统一的 Disaggregated Prefill-Decode 指南(见其中的 V0 Legacy 章节)。本文面向仍运行 vLLM V0 存量环境的读者;新部署请优先使用 vLLM V1 后端。
一、集成背景:为什么需要跨节点 KV Cache 传输
在大模型解耦推理(Disaggregated Prefill-Decode)架构中,Prefill 实例负责处理长 Prompt 并生成 KV Cache,Decode 实例负责逐 token 生成。两者通常分布在不同的机器上,因此 Prefill 节点生成的 KV Cache 必须高效地传输到 Decode 节点,才能避免 Decode 侧重复计算,从而提升整机吞吐与首字延迟表现。
v0.2 版本的 Mooncake 集成基于 vLLM 的 PR 10502 与 PR 10884(vLLM 版本为 v0.6.4.post1/main),通过 MooncakeConnector 把 Mooncake Transfer Engine 接入 vLLM 的 KV 传输框架。其核心思路是:
- Prefill 实例(
kv_producer)生成的 KV Cache 通过 Mooncake Transfer Engine 直接跨节点传输(优先 RDMA,回退 TCP),不经过磁盘、不经过共享存储; - Decode 实例(
kv_consumer)从远端接收 KV Cache 直接复用; - 实例间的拓扑信息与连接元数据由 etcd / redis / http 等元数据服务统一管理。
官方统一指南中对其效果给出的表述是:使用 RDMA 技术可实现跨节点 KV Cache 直传,在 8x RoCE 网络上达到 142.25 GB/s 峰值带宽(约 71.1% 链路利用率),详见 Disaggregated Prefill-Decode 指南 的 Overview 部分。
需要强调的是,v0.2 集成文档本身声明:这仍是实验性版本,会随 vLLM 社区的反馈随时调整。文档中同时记录了两个重要的演进节点:
- 2025-04-10 更新:官方团队开始推进 vLLM V1 集成;
- 2025-09-05 更新:已发布基于 Mooncake Store 与 LMCache 的 vLLM V1 集成,详见 vllmv1-lmcache-integration.md。
二、安装与版本约束
2.1 安装 mooncake-transfer-engine
pip3 install mooncake-transfer-engine
注意事项:
- 如果安装后遇到
lib*.so缺失等问题,请先执行pip3 uninstall mooncake-transfer-engine卸载,再根据 构建指南 手动编译二进制。 - 版本约束(重要):对于 vLLM 版本 ≤ v0.8.4,要求
mooncake-transfer-engine ≤ 0.3.3.post2。在最新版本中,旧接口mooncake_vllm_adaptor已被废弃。
2.2 从源码安装 vLLM(V0 时代的标准做法)
v0.2 集成文档给出的 vLLM 安装方式是克隆官方仓库后从源码构建(注意:需要包含 C++ 与 CUDA 代码):
# 1. 从 vLLM 官方仓库克隆(请按 vLLM 官方指引获取仓库地址)
git clone <vllm-official-repo-url>
# 2. 进入仓库并卸载旧版本后重新以可编辑模式构建
cd vllm
pip3 uninstall vllm -y
pip3 install -e .
排错提示:
- 若构建失败,可先升级 cmake:
pip3 install cmake --upgrade; - 若遇到无法自行解决的问题,参考 vLLM 官方编译指南(对应 v0.6.4.post1 的 installation 页面)。
三、核心配置文件:mooncake.json 全字段详解
MooncakeConnector(V0 时代)不使用 kv_ip/kv_port 做分布式连接配置,而是通过一个统一的 mooncake.json 配置文件建立分布式连接。该文件需要在 Prefill 与 Decode 两侧都准备好,且两侧使用完全相同的文件即可——Decode 侧无需修改 prefill_url 和 decode_url。
3.1 RDMA 场景配置
{
"prefill_url": "192.168.0.137:13003",
"decode_url": "192.168.0.139:13003",
"metadata_server": "192.168.0.139:2379",
"metadata_backend": "etcd",
"protocol": "rdma",
"device_name": "erdma_0"
}
3.2 TCP 场景配置
{
"prefill_url": "192.168.0.137:13003",
"decode_url": "192.168.0.139:13003",
"metadata_server": "192.168.0.139:2379",
"metadata_backend": "etcd",
"protocol": "tcp",
"device_name": ""
}
TCP 场景下 device_name 置为空字符串即可;文档同时预告:在下一版本中,当配置缺少 protocol 字段时会支持自动探测。
3.3 字段语义与取值说明
| 字段 | 含义与取值 | 关键注意事项 |
|---|---|---|
prefill_url |
Prefill 节点的 IP 与端口 | 该端口用于与元数据服务器通信 |
decode_url |
Decode 节点的 IP 与端口 | 该端口用于与元数据服务器通信;同节点部署时端口必须与 prefill_url 相差至少 50(如 "192.168.0.137:13103");若两侧误填相同 URL,系统会自动给 decode_url 端口 +100 |
metadata_server |
Mooncake Transfer Engine 的元数据服务器地址 | 支持多种后端前缀写法,详见下表 |
metadata_backend |
元数据后端类型:"etcd" / "redis" / "http" |
该字段缺失且 metadata_server 未携带后端前缀时,默认使用 etcd;该参数将在下一版本废弃,官方建议直接为 metadata_server 提供带目标后端前缀的完整 URL |
protocol |
数据传输协议:"rdma" / "tcp" |
决定 KV Cache 走 RDMA 直传还是 TCP 回退 |
device_name |
数据传输所用网卡设备名 | protocol 为 "rdma" 时必填;多网卡用逗号分隔且不能有空格,如 "erdma_0,erdma_1" |
metadata_server 支持的写法示例(来自原文档):
- etcd 后端:
"192.168.0.137:2379"、"etcd://192.168.0.137:2379",或 etcd 集群多节点"etcd://192.168.0.137:2379,192.168.0.138:2379" - redis 后端:
"redis://192.168.0.137:6379" - http 后端:
"http://192.168.0.137:8080/metadata"
四、端到端运行示例:1 Prefill + 1 Decode
以下命令中的 IP 与端口请按实际环境替换。假设已在仓库根目录准备好 mooncake.json。
4.1 启动 etcd 元数据服务器
# 1. 启动 etcd 服务器
etcd --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://localhost:2379
# 运行上述命令前可能需要先终止其他 etcd 进程(端口占用)
4.2 启动 Prefill 实例(producer 角色)
MOONCAKE_CONFIG_PATH=./mooncake.json VLLM_USE_MODELSCOPE=True python3 -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen2.5-7B-Instruct-GPTQ-Int4 \
--port 8100 \
--max-model-len 10000 \
--gpu-memory-utilization 0.8 \
--kv-transfer-config '{"kv_connector":"MooncakeConnector","kv_role":"kv_producer","kv_rank":0,"kv_parallel_size":2,"kv_buffer_size":2e9}'
4.3 启动 Decode 实例(consumer 角色)
MOONCAKE_CONFIG_PATH=./mooncake.json VLLM_USE_MODELSCOPE=True python3 -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen2.5-7B-Instruct-GPTQ-Int4 \
--port 8200 \
--max-model-len 10000 \
--gpu-memory-utilization 0.8 \
--kv-transfer-config '{"kv_connector":"MooncakeConnector","kv_role":"kv_consumer","kv_rank":1,"kv_parallel_size":2,"kv_buffer_size":2e9}'
4.4 通用启动参数说明
| 参数 | 说明 |
|---|---|
MOONCAKE_CONFIG_PATH |
mooncake.json 配置文件路径 |
VLLM_USE_MODELSCOPE |
可选;若能访问 HuggingFace 可移除 |
--model |
指定使用的模型 |
--port |
vLLM 服务监听端口 |
--max-model-len |
模型最大上下文长度 |
--tensor-parallel-size(-tp) |
现已支持多 GPU 张量并行,如追加 -tp 2;同节点跑 Prefill 与 Decode 时,两侧必须使用不同的 CUDA_VISIBLE_DEVICES(如 Prefill 用 0,1、Decode 用 2,3) |
4.5 --kv-transfer-config 参数详解(V0 语义)
kv_connector:必须设为MooncakeConnector;kv_role:节点角色,取值为kv_producer(Prefill 侧)或kv_consumer(Decode 侧);kv_rank:实例的 rank,目前kv_producer固定为 0,kv_consumer固定为 1;kv_parallel_size:目前固定为 2;kv_buffer_size:KV Cache 查找缓冲区大小(示例为2e9,即 2 GB)。如果 Prompt 的平均input_len较大,请增大该值;若仍出现 OOM,请调低--gpu-memory-utilization的比例;kv_ip/kv_port:这两个参数是为PyNcclConnector的分布式部署指定 master 节点 IP 与端口而设计的,当前MooncakeConnector不使用它们——MooncakeConnector改用配置文件(mooncake.json)建立分布式连接,因此现阶段无需为它设置这两个参数。
五、代理服务器:实现 Prefill → Decode 的请求编排
解耦推理需要一个"路由/代理"角色:把用户请求先发给 Prefill(只做 Prefill 阶段),再把完成 Prefill 后的请求转发给 Decode(继续生成 token)。v0.2 文档给出了一个基于 Quart + aiohttp 的最小可运行实现,在其中一个节点(以 Prefill 节点为例)执行:
python3 proxy_server.py
完整实现如下(务必修改代码中的 IP 地址):
import os
import aiohttp
from quart import Quart, make_response, request
AIOHTTP_TIMEOUT = aiohttp.ClientTimeout(total=6 * 60 * 60)
app = Quart(__name__)
async def forward_request(url, data):
async with aiohttp.ClientSession(timeout=AIOHTTP_TIMEOUT) as session:
headers = {
"Authorization": f"Bearer {os.environ.get('OPENAI_API_KEY')}"
}
async with session.post(url=url, json=data,
headers=headers) as response:
if response.status == 200:
if True:
async for chunk_bytes in response.content.iter_chunked(
1024):
yield chunk_bytes
else:
content = await response.read()
yield content
@app.route('/v1/completions', methods=['POST'])
async def handle_request():
try:
original_request_data = await request.get_json()
prefill_request = original_request_data.copy()
# change max_tokens = 1 to let it only do prefill
prefill_request['max_tokens'] = 1
# finish prefill
async for _ in forward_request('http://localhost:8100/v1/completions',
prefill_request):
continue
# return decode
generator = forward_request('http://192.168.0.139:8200/v1/completions', # Be sure to change the IP address for your machine
original_request_data)
response = await make_response(generator)
response.timeout = None
return response
except Exception as e:
import sys
import traceback
exc_info = sys.exc_info()
print("Error occurred in disagg prefill proxy server")
print(e)
print("".join(traceback.format_exception(*exc_info)))
if __name__ == '__main__':
app.run(host="0.0.0.0",port=8000)
这个代理的实现要点:
- 将原始请求复制一份并把
max_tokens改为 1,先发往 Prefill 实例(localhost:8100),消费完 Prefill 的输出后丢弃其生成结果——目的是让 Prefill 只完成预填充、产出 KV Cache; - 随后把原始请求(保留用户真实的
max_tokens)转发给 Decode 实例(192.168.0.139:8200,需按实际机器修改),并以流式响应返回给客户端; AIOHTTP_TIMEOUT设置为 6 小时,response.timeout = None,避免长生成任务被超时中断;- 该代理仅作演示用途,生产环境建议采用更健壮的代理/网关方案(仓库的 统一指南 中也有同样的提示)。
六、用 OpenAI 兼容接口验证整条链路
代理启动后,通过端口 8000 发起 OpenAI 兼容的 v1/completions 请求:
curl -s http://localhost:8000/v1/completions -H "Content-Type: application/json" -d '{
"model": "Qwen/Qwen2.5-7B-Instruct-GPTQ-Int4",
"prompt": "San Francisco is a",
"max_tokens": 1000
}'
- 如果不在代理服务器本机测试,请把
localhost换成代理服务器的 IP; - 请求返回后,可结合 vLLM 日志确认 Prefill 与 Decode 两侧均收到请求、且 KV Cache 走的是传输链路(若链路异常,Decode 侧会回退为自行计算 KV,表现为结果正确但 Prefill 的 KV 未被复用)。
七、仓库源码佐证与进阶实践
7.1 端到端测试脚本印证配置形态
仓库的 e2e 测试脚本 test_vllm_1p1d_erdma.sh 提供了"1 个 Prefill + 1 个 Decode 跑通全链路"的自动化参考,其中几个细节可与本文对照:
- 远端节点运行
kv_producer(端口 8010),本地节点运行kv_consumer(端口 8020),与 v0.2 文档的角色/端口约定一致; - 使用
--tensor-parallel-size 2 --max-model-len 32768 --gpu-memory-utilization 0.85,并通过CUDA_VISIBLE_DEVICES隔离同机 GPU; - 现代 vLLM 版本下的配置已简化为
{"kv_connector":"MooncakeConnector","kv_role":"kv_producer"}(不再需要kv_rank/kv_parallel_size/kv_buffer_size),并支持通过kv_connector_extra_config传入num_workers(如 ROCm 冒烟场景设 1); - 该脚本还展示了
MC_MAX_CONCURRENT_REG_MR、MC_TE_FILTERS等 Mooncake Transfer Engine 侧环境变量在 ROCm/CUDA 环境中的真实用法。
7.2 V1 Connector 的实现与环境变量
当前仓库中的 V1 Connector 实现位于 mooncake_connector_v1.py,从源码可以确认以下行为:
- 通过
kv_connector_module_path: "mooncake.mooncake_connector_v1"显式指定 Connector 模块; - 支持
kv_role的kv_producer/kv_consumer/kv_both三种取值(kv_both为实验性的对称功能); - 提供若干
VLLM_MOONCAKE_*环境变量:VLLM_MOONCAKE_SENDER_WORKERS(发送侧线程池大小,代码默认 10)、VLLM_MOONCAKE_PROTOCOL(默认"rdma")、VLLM_MOONCAKE_SIDE_CHANNEL_PORT(默认 6557)、VLLM_MOONCAKE_ABORT_REQUEST_TIMEOUT(请求中止时自动释放 KV Cache 的超时,代码默认 120 秒,可通过环境变量覆盖)等。
7.3 代理的演进
从 test_vllm_1p1d_erdma.sh 的代理选择逻辑可以看出:vLLM ≥ 0.16.0 时使用 vLLM 官方示例中的 mooncake_connector_proxy.py(通过 --prefill / --decode / --host / --port 启动),更早版本则使用仓库内的 toy_proxy_server.py(通过 --prefiller-host / --prefiller-port / --decoder-host / --decoder-port 启动)。仓库的统一指南还特别指出:MooncakeConnector 需要一个能生成 transfer_id 并把 Prefill 端 bootstrap 地址转发给 Decode 端的路由器,若代理未实现该契约,输出结果虽然正确,但 KV Cache 会在 Decode 侧被静默重算,传输链路并未真正生效。
八、向 vLLM V1 迁移的路径
v0.2 文档明确标注为实验性且已归档,官方建议新部署直接使用 vLLM V1 后端,迁移要点如下:
- 配置大幅简化:V1 场景下不再需要
mooncake.json与kv_rank/kv_parallel_size/kv_buffer_size,只需在--kv-transfer-config中声明kv_connector与kv_role,见 Disaggregated Prefill-Decode 指南; - 环境变量取代配置文件:
VLLM_MOONCAKE_BOOTSTRAP_PORT(默认 8998,仅 Prefill 侧需要,多 worker 时端口按base_port + dp_rank * tp_size + tp_rank递增)、VLLM_MOONCAKE_ABORT_REQUEST_TIMEOUT(默认值以 mooncake_connector_v1.py 源码为准)等; - 代理方案替换:从自研 Quart 代理切换为 vLLM 官方示例代理(
mooncake_connector_proxy.py)或更健壮的网关方案; - 性能数据:V1 集成的基准测试结果见仓库的 vLLM PD Disaggregation Performance 文档;
- Mooncake Store / LMCache 路线:若需要 KV Cache 的持久化共享(如容灾、多副本),可参考 vllmv1-lmcache-integration.md。
九、常见排错速查
- 连接异常:确认各节点网络互通、防火墙放行
metadata_server、prefill_url/decode_url所用端口,并确认 RDMA 设备已正确配置且出现在device_name中; - 缺少动态库:
lib*.so缺失时卸载 pip 包并从源码重新编译,见 构建指南; - 同机部署端口冲突:确保
decode_url与prefill_url端口差 ≥ 50,或依赖系统自动 +100 的兜底逻辑; - OOM:调大
kv_buffer_size(长 Prompt 场景)或调低--gpu-memory-utilization; - 调试日志:V1 场景可用
VLLM_LOGGING_LEVEL=DEBUG开启详细诊断输出。