「4就是大于8,数学真不是体育老师教的」XCCL通信故障及国产 XPU TP=4 意外反超 TP=8 的通信诊断

摘要:本文记录一次昆仑芯 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 -s8 卡全部 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/s55-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 并行度才是吞吐的真实底色。

标签:XCCL, 通信, TP=4, XPU