vLLM KV Cache 池化:Mooncake + MultiConnector 生产级实践
Mooncake Master + MultiConnector(MooncakeConnectorV1 + AscendStoreConnector)双层 KV Cache 池化架构的完整实践指南:从原理到 GLM-5.2 生产配置,涵盖前缀缓存、SSD 卸载、Decode KV 回写和排障。
一、什么是 KV Cache 池化
1.1 无池化的 PD 分离
请求 → Proxy → Prefill (max_tokens=1) → KV Cache 直传 → Decode (流式)
↓ ↓
计算完就丢弃 用完就丢弃
每次请求,Prefill 生成的 KV Cache 用一次就扔。下一个相同 System Prompt 的请求,Prefill 要重新计算。
1.2 有池化的 PD 分离
请求 → Proxy → Prefill → KV Cache 写入 Pool → Decode 从 Pool 读取
↓ ↕ ↓
Mooncake Master (KV Pool) 命中则跳过 Prefill
↕
AscendStoreConnector (前缀缓存池 + SSD 卸载)
核心变化:KV Cache 不再是一次性的,而是持久化在共享 Pool 中。后续请求如果命中前缀缓存,Prefill 直接跳过。
1.3 池化的三种层次
| 层次 | 组件 | 功能 | 收益 |
|---|---|---|---|
| L1: KV 传输 | MooncakeConnectorV1 | P→D 间传输 KV Cache | PD 分离的基础 |
| L2: 前缀缓存 | AscendStoreConnector | 相同前缀只算一次 | Prefill 延迟降低 30-90% |
| L3: 全池化 | + SSD 卸载 + Decode 回写 | KV 持久化 + 跨请求复用 | Prefill 接近 0(命中时) |
二、架构全景
2.1 组件关系
┌─────────────────────────────────────────────────────────┐
│ Mooncake Master │
│ - KV block 索引 │
│ - 租约管理 (default_kv_lease_ttl) │
│ - 驱逐策略 (eviction_high_watermark_ratio) │
│ - SSD 卸载调度 (enable_offload) │
└──────┬──────────────────────────────────┬───────────────┘
│ 注册 + 心跳 │ 注册 + 心跳
┌──────▼──────────┐ ┌──────▼──────────┐
│ Prefill 节点 │ RDMA 直传 │ Decode 节点 │
│ │◄──────────────►│ │
│ MultiConnector │ KV blocks │ MultiConnector │
│ ├─ Mooncake V1 │ │ ├─ Mooncake V1 │
│ └─ AscendStore │ │ └─ AscendStore │
│ (kv_producer) │ │ (kv_consumer) │
└──────────────────┘ └──────────────────┘
2.2 MultiConnector 双层设计
MultiConnector
├── MooncakeConnectorV1 ← L1: 跨节点 KV 传输(RDMA)
│ 职责:P→D 发送 KV blocks
│ 端口:kv_port (30000/30100)
│
└── AscendStoreConnector ← L2: 持久化前缀缓存
职责:前缀缓存管理 / SSD 卸载 / Decode 回写
后端:mooncake (复用 Master)
参数:lookup_rpc_port / load_async / consumer_is_to_put
| Connector | kv_role | 做什么 |
|---|---|---|
| MooncakeConnectorV1 | kv_producer | Prefill 节点:将 KV blocks 编码后通过 RDMA 发给 Decode |
| MooncakeConnectorV1 | kv_consumer | Decode 节点:接收 KV blocks 并加载到显存 |
| AscendStoreConnector | kv_producer | Prefill 节点:将前缀缓存写入 Pool |
| AscendStoreConnector | kv_consumer | Decode 节点:从 Pool 读取前缀缓存(load_async=true 异步) |
| AscendStoreConnector | kv_both | 混合模式:同时读和写(PD 共置场景) |
为什么需要两个 Connector? MooncakeConnectorV1 只管传输(快,无状态),AscendStoreConnector 管存储(持久化,有索引)。分开设计让传输路径不阻塞、存储路径可独立扩展(加 SSD)。
三、Mooncake Master
3.1 部署
Mooncake Master 是 KV Pool 的中心调度器,只需要在 Prefill 的 p0 节点上启动一个实例:
mooncake_master \
--port 50088 \
--eviction_high_watermark_ratio 0.9 \
--eviction_ratio 0.1 \
--default_kv_lease_ttl 11000
| 参数 | 含义 | 建议值 |
|---|---|---|
--port | Master RPC 端口 | 50088 |
--eviction_high_watermark_ratio | 触发驱逐的水位(显存占比) | 0.9(90% 显存开始驱逐) |
--eviction_ratio | 每次驱逐的比例 | 0.1(每次驱逐 10% 的 KV blocks) |
--default_kv_lease_ttl | KV block 租约时间(ms) | 11000(11 秒,覆盖最长请求) |
3.2 Docker Compose 集成(GLM-5.2 真实配置)
# 仅在 p0 节点启用(profiles: mooncake)
mooncake:
image: quay.io/ascend/vllm-ascend:v0.23.0rc1
container_name: mooncake-master
restart: unless-stopped
network_mode: host
profiles:
- mooncake
volumes:
- /data/models/GLM-5.2/config:/workspace/config:ro
command:
- mooncake_master
- --port
- "50088"
- --eviction_high_watermark_ratio
- "0.9"
- --eviction_ratio
- "0.1"
- --default_kv_lease_ttl
- "11000"
# 启动(只启动 p0 的 mooncake)
COMPOSE_PROFILES=mooncake docker compose up -d mooncake
3.3 SSD 卸载配置
如果节点有 NVMe SSD(如 /mnt/nvme),可以将溢出的 KV blocks 卸载到 SSD:
mooncake_master \
--port 50088 \
--eviction_high_watermark_ratio 0.9 \
--eviction_ratio 0.1 \
--default_kv_lease_ttl 11000 \
--enable_offload=true \
--client_ttl=120
同时需要在 mooncake.json 中声明 SSD 路径:
{
"local_hostname": "<NODE_IP>",
"metadata_server": "<MASTER_IP>:50088",
"protocol": "rdma",
"device_name": "ibp8s0f0",
"enable_ssd_offload": true,
"ssd_offload_path": "/mnt/nvme/mooncake_ssd"
}
注意:
MOONCAKE_OFFLOAD_TOTAL_SIZE_LIMIT_BYTES默认 2TB。如果 NVMe 只有 1TB,需要手动调小,否则会超出磁盘容量。
四、mooncake.json 模板
所有 Prefill 和 Decode 节点都需要这个文件,指向同一个 Master:
{
"local_hostname": "<本节点 IP>",
"metadata_server": "<p0_IP>:50088",
"protocol": "rdma",
"device_name": "ibp8s0f0"
}
Ansible 生成模板
由于每个节点 IP 不同,用 Ansible 模板生成:
# Jinja2 模板
cat > config/mooncake.json.j2 << 'EOF'
{
"local_hostname": "{{ ansible_host }}",
"metadata_server": "{{ hostvars[groups['prefills'][0]]['ansible_host'] }}:50088",
"protocol": "rdma",
"device_name": "ibp8s0f0"
}
EOF
# Ansible task
- name: 生成 mooncake.json
template:
src: config/mooncake.json.j2
dest: "/data/models/{{ model_name }}/config/mooncake.json"
五、MultiConnector 完整配置
5.1 全局环境变量
所有节点必须设置:
export MOONCAKE_CONFIG_PATH="/workspace/config/mooncake.json" # 指向 mooncake.json
export PYTHONHASHSEED=0 # MultiConnector 要求统一哈希种子
export HCCL_RDMA_TIMEOUT=17 # RDMA 超时
export ASCEND_CONNECT_TIMEOUT=10000 # 连接超时 (ms)
export ASCEND_TRANSFER_TIMEOUT=10000 # 传输超时 (ms)
| 变量 | 值 | 说明 |
|---|---|---|
PYTHONHASHSEED | 必须 0 | 所有节点一致,否则 KV 哈希查找失败 |
HCCL_RDMA_TIMEOUT | 17 | 4.096μs × 2^17 ≈ 537ms |
ASCEND_CONNECT_TIMEOUT | 10000 | ≥ 500ms × Decode 卡数 |
ASCEND_TRANSFER_TIMEOUT | 10000 | 单向传输,需 > RDMA_TIMEOUT × 7 |
5.2 Prefill 节点 — kv_producer
vllm serve /workspace/GLM-5.2-w8a8 \
--kv-transfer-config '{
"kv_connector": "MultiConnector",
"kv_role": "kv_producer",
"kv_load_failure_policy": "recompute",
"kv_connector_extra_config": {
"connectors": [
{
"kv_connector": "MooncakeConnectorV1",
"kv_role": "kv_producer",
"kv_port": "30000",
"kv_connector_extra_config": {
"prefill": {"dp_size": 4, "tp_size": 8},
"decode": {"dp_size": 8, "tp_size": 4}
}
},
{
"kv_connector": "AscendStoreConnector",
"kv_role": "kv_producer",
"kv_connector_extra_config": {
"lookup_rpc_port": "0",
"backend": "mooncake"
}
}
]
}
}'
5.3 Decode 节点 — kv_consumer
vllm serve /workspace/GLM-5.2-w8a8 \
--kv-transfer-config '{
"kv_connector": "MultiConnector",
"kv_role": "kv_consumer",
"kv_load_failure_policy": "recompute",
"kv_connector_extra_config": {
"connectors": [
{
"kv_connector": "MooncakeConnectorV1",
"kv_role": "kv_consumer",
"kv_port": "30100",
"kv_connector_extra_config": {
"prefill": {"dp_size": 4, "tp_size": 8},
"decode": {"dp_size": 8, "tp_size": 4}
}
},
{
"kv_connector": "AscendStoreConnector",
"kv_role": "kv_consumer",
"kv_connector_extra_config": {
"lookup_rpc_port": "0",
"load_async": true,
"backend": "mooncake"
}
}
]
}
}'
5.4 Prefill vs Decode 的关键差异
| 参数 | Prefill | Decode | 原因 |
|---|---|---|---|
kv_role | kv_producer | kv_consumer | P 生产 KV,D 消费 KV |
kv_port | 30000 | 30100 | 不同端口避免冲突(同组 P/D 共享同一 engine_id 但不同端口) |
engine_id | 同 group 内相同 | 同 group 内相同 | 配对的 P 和 D 用同一 ID 通信 |
load_async | 不适用 | true | D 端异步加载 KV 不阻塞推理 |
consumer_is_to_put | 不适用 | true(可选) | D 端也回写 KV(MLA 模型) |
dp_size/tp_size | 自己的拓扑 | 对端的拓扑 | MooncakeConnectorV1 需要知道对端的 DP/TP 布局来做数据路由 |
dp_size/tp_size 的坑:Prefill 节点的 MooncakeConnectorV1 中
"decode": {"dp_size": 8, "tp_size": 4}描述的是 Decode 侧的拓扑,而不是 Prefill 自己。Decode 节点同理,"prefill": {"dp_size": 4, "tp_size": 8}描述的是 Prefill 侧的拓扑。
六、AscendStoreConnector 进阶
6.1 前缀缓存原理
请求1: "你是一个运维专家,精通 K8s 和 Docker..." → Prefill → KV 写入 Pool
请求2: "你是一个运维专家,精通 K8s 和 Docker..." → 命中 Pool → 跳过 Prefill
↓
AscendStoreConnector 返回缓存 KV
AscendStoreConnector 通过哈希匹配前缀,直接返回缓存的 KV blocks。命中时 TTFT 降低 50-90%。
6.2 Decode KV 回写(consumer_is_to_put)
{
"kv_connector": "AscendStoreConnector",
"kv_role": "kv_consumer",
"kv_connector_extra_config": {
"lookup_rpc_port": "0",
"backend": "mooncake",
"consumer_is_to_put": true
}
}
Decode 节点在生成过程中产生的 KV blocks 也可以回写到 Pool,供后续 Prefill 复用。
适用条件:仅 MLA(Multi-head Latent Attention)模型支持。GLM-5.2 使用 MLA,可以使用。
6.3 PD 混合模式(kv_both)
单节点同时跑 Prefill + Decode(无 PD 分离)时:
vllm serve /model \
--kv-transfer-config '{
"kv_connector": "AscendStoreConnector",
"kv_role": "kv_both",
"kv_load_failure_policy": "recompute",
"kv_connector_extra_config": {
"lookup_rpc_port": "1",
"backend": "mooncake"
}
}'
不需要 MooncakeConnectorV1(因为没有跨节点传输),只需要 AscendStoreConnector 做前缀缓存。
七、关键参数速查
| 参数 | 位置 | 含义 | 默认 | 建议 |
|---|---|---|---|---|
kv_load_failure_policy | MultiConnector 顶级 | KV 加载失败策略 | fail | recompute(自动重算) |
kv_port | MooncakeConnectorV1 | KV 传输端口 | — | P:30000, D:30100 |
lookup_rpc_port | AscendStoreConnector | 池化查找端口 | — | 0(自动分配) |
load_async | AscendStoreConnector | 异步加载 | false | D 端设 true |
consumer_is_to_put | AscendStoreConnector | D 端 KV 回写 | false | MLA 模型设 true |
backend | AscendStoreConnector | 存储后端 | mooncake | 保持默认 |
engine_id | PD 分离对 | 配对标识 | — | 同组 P/D 相同 |
八、监控与排障
8.1 Mooncake Master 状态
# 检查 Master 是否正常
curl -s http://<MASTER_IP>:50088/metrics | grep -E "kv_cache|connected"
# 预期输出:
# mooncake_kv_cache_blocks_total 12345
# mooncake_connected_clients 8
8.2 vLLM 日志验证
# 确认 MultiConnector 初始化成功
grep "connector" /workspace/logs/prefill_rank0.log | head -5
# 预期看到:
# [MultiConnector] Initialized with 2 connectors
# [MooncakeConnectorV1] role=kv_producer, port=30000
# [AscendStoreConnector] role=kv_producer, backend=mooncake
# 检查前缀缓存命中率
grep "hit_rate\|prefix_cache" /workspace/logs/prefill_rank0.log
8.3 常见问题
| 现象 | 根因 | 排查 |
|---|---|---|
| Decode 连不上 Prefill | 网络不通或端口未开放 | nc -zv $P0_IP 30000 |
| KV 加载失败,日志全是 recompute | kv_load_failure_policy 配到了子 connector | 必须配在 MultiConnector 顶级 |
| 节点间 KV 哈希不匹配 | PYTHONHASHSEED 不一致 | 所有节点 echo $PYTHONHASHSEED 确认都是 0 |
| AscendStore 连不上 Master | MOONCAKE_CONFIG_PATH 未设置或路径错误 | cat $MOONCAKE_CONFIG_PATH 检查内容 |
| 前缀缓存命中率始终为 0 | 前缀不够相似或 Pool 太满 | 检查 eviction_high_watermark_ratio,必要时降低 |
| 同节点多 rank 端口冲突 | lookup_rpc_port 相同 | 每个 rank 用不同端口或设为 0 自动分配 |
| SSD 卸载报空间不足 | 默认 2TB 配额超磁盘实际容量 | export MOONCAKE_OFFLOAD_TOTAL_SIZE_LIMIT_BYTES=1073741824000 (~1TB) |
九、部署检查清单
□ Mooncake Master 在 p0 节点启动(端口 50088)
□ mooncake.json 生成并分发到所有 P + D 节点
□ 所有节点 PYTHONHASHSEED=0
□ MultiConnector 配置在 Prefill 使用 kv_producer
□ MultiConnector 配置在 Decode 使用 kv_consumer
□ kv_port 在 P 和 D 上不同(30000 vs 30100)
□ dp_size/tp_size 填写的是对端拓扑(不是自己)
□ kv_load_failure_policy 配在 MultiConnector 顶级(不是子 connector)
□ SSD 卸载只在有 NVMe 的节点上启用
□ consumer_is_to_put 仅 MLA 模型使用