昇腾 8 卡910b vLLM-Ascend + Mooncake PD 分离:kv_port 明明“通”但却一直异常的端口冲突复盘
一、环境背景
两台昇腾服务器做 PD 分离,单机 8 卡。P 节点跑 kv_producer 做 prefill,D 节点跑 kv_consumer 做 decode,KV 通过 MooncakeConnectorV1 传输。按 8 卡全 TP 部署,kv-transfer-config 里 prefill/decode 都是 dp_size=2、tp_size=4。
最初的理解很简单:kv_port 配一个就行,比如 30000;P 先起、D 后起,能 telnet/nc 通就说明没问题。结果线上反复出现 D 卡在等远端 KV、P 日志报请求超过超时阈值被强制释放、偶尔还伴随连接被重置,排查了防火墙、MTU、RDMA、配置一致性一圈,最后发现根因是端口被别的进程占了。
二、为什么 8 卡不能只盯一个 kv_port
这是本次最关键的认知修正。Mooncake 在昇腾多卡下不是只用一个端口:
官方 PD 分离文档明确,每个 P 或 D 节点会占用从 kv_port 到 kv_port + num_chips 的端口来初始化 socket 监听;8 卡时也就是占用 kv_port、kv_port+1、…、kv_port+7 共 8 个端口。
也就是说,如果你配 kv_port=30000,实际要保障 30000~30007 这一组端口全部空闲、全部给 Mooncake 对应 8 个 NPU worker 使用。只查 30002 通不通,完全不代表 30000~30007 没被别人占。
另外,昇腾 AscendDirectTransport 走 RDMA 时还会在一段保留区间随机分配端口:8 卡每节点保留范围是 20000~27999。如果 kv_port 落进这个区间,或者与其随机分配结果重叠,就会出现间歇性 Address already in use,表现为启动偶尔失败、KV 线程起不来。 官方对 8 卡的建议是 kv_port 至少用 28000 以上,避开 20000~27999。
三、本次故障现象
1. P 先启动,日志里能看到 Mooncake/TransferEngine 相关初始化,ss 看 30002 是 LISTEN。
2. D 后启动,nc 测 P 的 30002 有时通,curl 也能返回点东西,乍看像正常。
3. 发请求后 D 长期不返 token,D 日志出现等待远端 KV/RecomputeScheduler 仍处于 WAITING_FOR_REMOTE_KVS 之类状态;P 日志后续出现请求超过超时阈值被 force freed。
4. 抓包能看到 TCP 能建链,但应用层 KV 传输不正常,严重时出现重置。
5. 把 P、D 全停了重来,有时“自己又好了”,过一阵又坏。
这种“端口通但业务不通”的最典型原因,就是 kv_port 起始端口被监听了,但 8 卡所需的连续端口组里某些端口被其它服务吃了,或者 kv_port 整体与昇腾随机 RDMA 端口区间重叠。
四、定位过程
在 P 节点先列完整监听端口,而不只查单个 30002:
ss -tlnp | grep -E '3000[0-9]|3001[0-9]'
如果 8 卡用 30002 起,正常应看到 30002~30009 都归 vllm/mooncake 进程。若其中某个端口属于 grafana、prometheus、其它 vllm 实例、容器映射、或者遗留 python 进程,就是冲突点。
再用 lsof 精确定进程:
lsof -i :30002
lsof -i :30003
...
lsof -i :30009
或者用 fuser:
fuser -k 30002/tcp 30003/tcp 30004/tcp 30005/tcp 30006/tcp 30007/tcp 30008/tcp 30009/tcp
注意 fuser 只用来查看也行,别上来 -k。若发现 30002 被 grafana 占用,那就是本例根因:你以为 Mooncake 在听,实际是 Grafana 在听;或者 30002 被 Mooncake 占、但 30003~30009 某几个被 Grafana/其它服务占,外层连 30002 通,内部按卡建链时卡死。
Docker 环境还要看容器映射,避免宿主机 30002~30009 被别的容器映射到:
docker ps --format '{{.Names}}\t{{.Ports}}' | grep -E '3000|3001'
如果 Grafana 容器映射了 30002:3000 或 30002:30002 之类,必然冲突。
五、根因结论
8 卡 Mooncake PD 分离的 kv_port 不是单端口,而是一组连续端口。本次 30002 作为起始端口,表面上 nc/curl 都通,但因为其它服务占用了该组中的部分端口(或 30002 本身被非 Mooncake 进程占用),导致部分 NPU worker 的 KV 监听/传输通道起不来。P 侧收不到完整建链或发 KV 失败,D 侧就一直等远端 KV;请求拖到超时后 P 强制释放,表现为“偶尔能好、大部分时间卡死”。
另外一个潜在叠加项:如果 kv_port 配得偏低,进入昇腾 AscendDirectTransport 的 20000~27999 随机区间,也会出现类似间歇性占用,不能只怪外部服务。
六、解决办法
第一,停掉冲突服务。若确认 30002 或 30003~30009 被 Grafana 占用,停 Grafana 或改 Grafana 端口:
systemctl stop grafana-server
或 docker 停对应容器
docker stop <grafana容器名>
改 Grafana 自身监听时不要再用 30002~30009,比如改到 33000 或按监控独立段规划;改完重启 Grafana,再确认 30002~30009 全空。
第二,8 卡重新规划 kv_port。不要再使用可能与昇腾 RDMA 随机端口重叠的低位段,按官方建议 8 卡用 28000 以上。 例如统一用 28002 作为起始,则实际占用 28002~28009;P、D 的 kv_port 保持一致,engine_id 按集群规划唯一。配置示例思路:
P:kv_port 28002,kv_role kv_producer,prefill/decode dp=1/tp=8,use_ascend_direct=true,delayed_free=true
D:kv_port 28002,kv_role kv_consumer,同样 dp/tp,delayed_free=true,kv_load_failure_policy=recompute
第三,起服务前先校验整组端口全空:
for p in (seq 28002 28009); do ss -tlnp | grep -q ": p " && echo "occupied p" || echo "free p"; done
有 occupied 就别起 vllm,先查进程清掉。
第四,清 Mooncake 残留再起。P、D 都做:
rm -f /tmp/mooncake* /tmp/transfer_engine* 2>/dev/null
rm -f /dev/shm/mooncake /dev/shm/ascend 2>/dev/null
P 先起,等日志出现 Application startup complete、且 ss 看到 28002~28009 都归 vllm 后,再起 D。
第五,验证不再只看 nc。D 起完后发一个短请求,同时观察 P 是否出现 KV cache transfer 完成、D 是否从等待远端 KV转为正常生成;并故意取消一次请求,确认 D 能在 VLLM_MOONCAKE_ABORT_REQUEST_TIMEOUT 内恢复,而不是永久卡死。
七、经验沉淀
PD 分离查端口不能只查一个 kv_port。8 卡就要查 kv_port 到 kv_port+7 整组;昇腾还要避开 20000~27999 的 AscendDirectTransport 随机段,官方建议 8 卡 kv_port 用 28000+。 nc/curl 通只能证明 TCP 监听可达,不能证明对应进程是 Mooncake、也不能证明 8 个 worker 端口都正常。最稳的做法是起服务前用脚本扫整组端口、用 lsof/fuser 看占用进程、用 ss 看归属进程名,再配合 DEBUG 日志观察 KV 传输,而不是“能连上就等于没问题”。
本次最大坑就是:30002 通,但 30002~30009 里被 Grafana 或其它服务分占了,导致表面一切正常、实际 KV 一直建不全。把整组端口交还 Mooncake 后,问题彻底消失。
如果你愿意,我可以把上面“起服务前端口预检脚本”写成可直接用的 shell,P/D 通用,自动扫 kv_port~kv_port+7、识别占用进程、并避开 20000-27999。
鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐

所有评论(0)