昇腾 8 卡 vLLM-Ascend + Mooncake PD 分离:两个“表面正常实则致命“的配置陷阱
一、环境背景
两台昇腾 910B3 服务器,每台 8 卡,单卡 64G 显存,部署 vLLM-Ascend 的 PD 分离(Prefill-Decode Disaggregation),使用 MooncakeConnectorV1 进行 KV Cache 传输。P 节点负责 prefill,D 节点负责 decode,配置为 dp_size=1、tp_size=8。
整个部署过程中踩了两个大坑,单独看每个都像是"配置没问题",合在一起排查时互相干扰,导致排障方向多次跑偏。
二、坑一:kv_ip 配了不生效,必须用 VLLM_HOST_IP
现象
在
"kv-transfer-config" 的 JSON 配置里,明确指定了
"kv_ip"(或
"local_ip"、
"remote_ip" 等字段),指向节点间通信的正确 IP 地址。配置写好了,服务也起来了,但 D 节点始终连不上 P 节点,或者连接行为诡异——有时连到 127.0.0.1,有时连到错误的网卡 IP。
反复检查配置文件,确认 IP 没写错,但问题依旧。
根因
vLLM 在初始化分布式通信和网络绑定时,优先读取环境变量
"VLLM_HOST_IP" 来确定本机 IP,而不是从
"kv-transfer-config" 里的 IP 字段读取。如果你没设这个环境变量,vLLM 会自行探测本机 IP,探测结果可能是 127.0.0.1、docker 网桥 IP、或者 NPU 的 RoCE IP——总之不一定是你想让它用的那个。
"kv-transfer-config" 里的 IP 字段在某些版本/场景下只是"声明"或"注册"用途,实际建链时底层走的还是
"VLLM_HOST_IP" 决定的地址。这就导致你以为配对了,实际 worker 绑定的监听地址根本不是你期望的那个。
解决
在 P 节点和 D 节点的启动脚本里,必须显式设置:
export VLLM_HOST_IP=<节点间通信的主网 IP>
比如:
# P 节点
export VLLM_HOST_IP=10.0.30.10
# D 节点
export VLLM_HOST_IP=10.0.30.11
设完之后再启动 vLLM,worker 才会正确绑定到指定 IP 上监听。这个环境变量不设,配什么
"kv_ip" 都是白搭。
教训
凡是涉及多节点通信的 vLLM 部署,启动前第一件事就是
"export VLLM_HOST_IP"。 把它写进启动脚本,别依赖自动探测。自动探测在有多网卡、有容器、有 RoCE 的复杂环境下,结果几乎一定不符合预期。
三、坑二:8 卡 kv_port 是端口组,单个端口通 ≠ 全部通
现象
"kv_port" 配了 30002,P 节点先启动,D 节点后启动。用
"nc" 或
"curl" 测试 P 的 30002 端口,能连通,有响应。但发请求后 D 节点卡死在
"WAITING_FOR_REMOTE_KVS" 状态,P 节点日志报请求超过超时阈值被强制释放(
"Force freed expired request"),抓包能看到 TCP 建链后被 RST 重置。
防火墙关了,MTU 对了,配置一致性检查过了,网络层看起来毫无问题,但 KV 传输就是不通。
根因
Mooncake 在 8 卡全 TP 模式下,不是只用一个
"kv_port",而是占用从
"kv_port" 到
"kv_port + 7" 的连续 8 个端口,每个 NPU worker 对应一个。也就是说
"kv_port=30002" 时,实际需要 30002~30009 全部空闲且全部归 Mooncake 使用。
表面上 30002 是通的(因为
"ss -tlnp" 显示 LISTEN),但 30002~30009 中的某些端口被其他服务(比如 Grafana)占用了。结果就是部分 worker 的 KV 传输通道起不来,P 节点收不到完整的建链请求,或者发 KV 时找不到对应的目标端口,最终表现为连接被重置、请求超时、D 节点永久卡死。
更隐蔽的是,Grafana 这类服务占用端口后,你
"curl 30002" 还能收到响应——因为 Grafana 本身也在监听,会返回 HTTP 响应。这让你误以为 Mooncake 在工作,实际上请求根本没到 Mooncake。
另外,昇腾 AscendDirectTransport 走 RDMA 时会在 20000~27999 区间随机分配端口。如果
"kv_port" 落在这个区间,或者与其随机分配结果重叠,还会出现间歇性
"Address already in use",表现为有时能启动有时不能。
解决
第一步:停掉冲突服务。 发现 Grafana 占了相关端口,直接停掉或改端口:
systemctl stop grafana-server
# 或 docker stop <grafana容器>
第二步:重新规划 kv_port。 8 卡环境建议用 28000 以上的端口,避开昇腾 RDMA 随机区间 20000~27999。比如统一用 28002,实际占用 28002~28009。
第三步:起服务前预检整组端口:
KV_PORT=28002
for p in $(seq $KV_PORT $((KV_PORT+7))); do
if ss -tlnp | grep -q ":$p "; then
echo "PORT $p OCCUPIED"
lsof -i :$p
else
echo "PORT $p free"
fi
done
有任何一个显示 OCCUPIED,就别起 vLLM,先清掉占用进程。
第四步:清理残留后按顺序启动:
rm -f /tmp/mooncake* /tmp/transfer_engine* 2>/dev/null
rm -f /dev/shm/*mooncake* /dev/shm/*ascend* 2>/dev/null
P 节点先起,确认
"ss" 看到 28002~28009 全部归 vllm 进程后,再起 D 节点。
四、两个坑叠加时的排障灾难
这次最坑的地方在于,这两个问题同时存在于环境中,互相掩盖了真相:
- 因为
"VLLM_HOST_IP" 没设对,D 节点尝试连接的 IP 可能就不是 P 节点的正确地址,导致连接失败。
- 即使 IP 碰巧通了(比如自动探测到了正确的网卡),端口组又被 Grafana 占了几个,KV 传输还是建不全。
- 表面现象就是"网络通、端口通、配置看起来对",然后你开始怀疑 RDMA、怀疑防火墙、怀疑 Mooncake 的 Bug,在错误方向上浪费大量时间。
正确的排障顺序应该是:
1. 先确认
"VLLM_HOST_IP" 设了没有(90% 的多节点通信问题根源)。
2. 再扫整组端口是否全空(8 卡必查
"kv_port"~
"kv_port+7")。
3. 以上两步确认无误后,再去看 RDMA、MTU、防火墙、日志。
五、最终部署检查清单
以后每次部署 PD 分离,启动前按这个清单过一遍:
- [ ]
"export VLLM_HOST_IP=<正确的节点间通信 IP>",P 和 D 各设各的
- [ ]
"kv_port" 用 28000+,避开 20000~27999
- [ ] 整组端口(
"kv_port"~
"kv_port+7")全部空闲,无冲突
- [ ] 无 Grafana / Prometheus / 其他监控组件占用该端口段
- [ ] 清理
"/tmp/mooncake*" 和
"/dev/shm" 下的残留文件
- [ ] P 先起,确认端口全 LISTEN 后再起 D
- [ ] 发一个短请求验证 KV 传输正常,再上压测
六、一句话总结
vLLM 多节点部署,
"VLLM_HOST_IP" 不设就是埋雷;8 卡 Mooncake,
"kv_port" 不是单端口,是一组端口,只查一个等于自欺欺人。 这两个坑单独踩一个就够折腾的,同时踩直接让人怀疑人生。
更多推荐



所有评论(0)