Mooncake 与 vLLM V0 解耦推理集成实战:基于 MooncakeConnector 的跨节点 KV Cache 传输部署指南

原创2026-10-03 17:22:55471 阅读
文章标签:人工智能大模型模型推理服务后端

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 后端,迁移要点如下:

  1. 配置大幅简化:V1 场景下不再需要 mooncake.json 与 kv_rank/kv_parallel_size/kv_buffer_size,只需在 --kv-transfer-config 中声明 kv_connector 与 kv_role,见 Disaggregated Prefill-Decode 指南;
  2. 环境变量取代配置文件:VLLM_MOONCAKE_BOOTSTRAP_PORT(默认 8998,仅 Prefill 侧需要,多 worker 时端口按 base_port + dp_rank * tp_size + tp_rank 递增)、VLLM_MOONCAKE_ABORT_REQUEST_TIMEOUT(默认值以 mooncake_connector_v1.py 源码为准)等;
  3. 代理方案替换:从自研 Quart 代理切换为 vLLM 官方示例代理(mooncake_connector_proxy.py)或更健壮的网关方案;
  4. 性能数据:V1 集成的基准测试结果见仓库的 vLLM PD Disaggregation Performance 文档;
  5. 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 开启详细诊断输出。
登录后查看全文
Mooncake