一、环境背景

 

两台昇腾 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" 不是单端口,是一组端口,只查一个等于自欺欺人。 这两个坑单独踩一个就够折腾的,同时踩直接让人怀疑人生。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐