摘要:本文记录一次昆仑芯 P800 八卡推理集群 XCCL 集合通信故障的完整诊断过程:从 TP=8 服务启动超时,到卡对二分测试定位四张问题卡,再到 TP=4 降级部署后吞吐意外反超 TP=8。结论部分诚实区分「带病基线」与「并行度收益」两种解释,并强调诊断方法本身的工程价值。
一、引言:一次不讲数学的故障
集群配置为 8× 昆仑芯 P800(8×96G),推理框架为 vllm-kunlun,模型为 Qwen3.8-27B,服务默认以 tensor-parallel-size 8 部署。在一次容器重启、多次强制终止进程并反复执行 xpu-smi -r 复位之后,服务启动卡在初始化阶段,最终报出 ProcessGroupXCCL checkTimeout:首个 ALLREDUCE(Numel=1,OpType=ALLREDUCE)超时,Rank 0/4/6 报错。
推理服务初始化阶段的 AllReduce 通常只传输 1 个元素,属于最轻量的集合通信。这一步超时,基本可以判定通信栈本身出了问题。真正麻烦的是:八张卡里哪些卡有问题、链路是否还能用、服务要多久才能恢复。
二、诊断过程
2.1 排除法:链路、显存、代码、共享内存
第一轮排查按常规路径展开,检查项与结果如下:
| 检查项 | 方法 | 结果 |
|---|---|---|
| XpuLink 链路 | xpu-smi xpulink -s | 8 卡全部 active |
| 单卡状态 | xpu-smi -q | 显存/利用率正常 |
| 业务代码 | 回滚上一稳定版本 | 问题依旧复现 |
| 共享内存 | df -h /dev/shm | 容量与权限正常 |
常规排查没有发现异常,故障却稳定复现。此时可以排除业务代码、显存硬件与共享内存三类原因,重点转向 XCCL 通信队列本身。
2.2 卡对测试:把故障压缩到具体卡组
为了定位问题卡,编写最小 2 卡 AllReduce 测试脚本,逐对测试 4 组卡对:0+1、2+3、4+5、6+7。脚本只做一次小 Tensor 的 AllReduce,用于验证卡对之间的 XCCL 集合通信是否可用。
import torch
import torch.distributed as dist
def main():
dist.init_process_group(backend="xccl")
x = torch.ones(1).to("xpu")
dist.all_reduce(x)
if dist.get_rank() == 0:
print("allreduce ok:", x.item())
if __name__ == "__main__":
main()一个关键细节:昆仑芯 torch 处于 CUDA 兼容模式,设备过滤必须使用 CUDA_VISIBLE_DEVICES,而不是 XPU_VISIBLE_DEVICES。后者在容器内会使 device_count 变为 0,导致测试脚本误报为「没有可用设备」。
CUDA_VISIBLE_DEVICES=0,1 python -m torch.distributed.run \
--nproc_per_node=2 allreduce_test.py四组卡对的测试结果如下:
| 卡对 | AllReduce 结果 |
|---|---|
| 0+1 | 失败 |
| 2+3 | 失败 |
| 4+5 | 通过 |
| 6+7 | 通过 |
结果指向 0/1/2/3 四张卡的 XCCL 通信队列损坏。四张卡同时「掉线」,也解释了 TP=8 服务启动时 Rank 0/4/6 先后报超时——环上任何一段不通,AllReduce 都无法完成。
三、降级部署:TP=8 退守 TP=4
定位问题卡组后,优先恢复生产。将服务改为仅使用健康卡 4/5/6/7,以 TP=4 启动:
CUDA_VISIBLE_DEVICES=4,5,6,7 python -m vllm.entrypoints.openai.api_server \
--model <model-path> \
--tensor-parallel-size 4服务恢复 READY,健康检查通过。此时的目标是保生产,性能数据是附带收益。
四、数据与解读:TP=4 意外反超 TP=8
降级部署完成后,例行压测出现一个「违背数学」的结果:
| 配置 | 256 并发吞吐 | 单流吞吐 |
|---|---|---|
| TP=8(故障前) | 1616 tok/s | - |
| TP=4(健康 4 卡) | 1735.9 tok/s | 55-57 tok/s |
TP=4 的 256 并发吞吐为 1735.9 tok/s,较此前 TP=8 的 1616 tok/s 高出约 7%。四张卡跑赢八张卡,属于推理服务界一次标准「背刺」。这个结果需要谨慎解读,下面呈现两种假设。
假设一:此前的 TP=8 是「带病成绩」。 卡 0/1/2/3 可能早已处于半坏状态,并未完全离线,但在 TP=8 的 AllReduce 环上持续拖慢整体通信。1616 tok/s 不是健康八卡的真实水平,TP=4 的 1735.9 tok/s 才是当前条件下的可用上限。
假设二:GDN recurrent kernel 的并行度差异。 该模型在 TP=4 时每卡承担 12 个 V 头,乘以 256 batch 后形成 3072 个独立递推单元;TP=8 时每卡只有 1536 个。更多并行单元让 kernel 调度更满。叠加 4 卡 AllReduce 环比 8 卡环更小、通信开销更低,TP=4 存在真实的性能优势。
历史旁证:旧算子栈(独立于官方、使用汇编编写的算子库和运行库)在 TP=8 全健康时,256 并发吞吐为 1542 tok/s,仍低于本次 TP=4 的 1736 tok/s。这说明 TP=4 的优势不完全来自坏卡拖累,并行度与通信环大小的贡献同样存在。
五、结论
4 > 8 这个不等式暂时成立,但它需要干净的对照组才能转正。最终结论要等 4 张坏卡恢复后,在同版本代码、同模型、同并发下做干净对比。
本文的工程价值不在性能结论,而在诊断方法:用卡对二分测试把通信故障压缩到具体卡组,再用降级部署保住生产。TP=4 反超 TP=8 是这次故障处置中一个值得记录的注脚——它提醒我们,并行度不是越大越好,通信健康与 kernel 并行度才是吞吐的真实底色。