目录
1 介绍
2 空泡现象和原因分析
2.1 空泡表现
2.2 空泡可能原因1
2.3 空泡根本原因
2.3.1 这个gloo:allreduce的由来,为什么存在
2.3.2 修改方法
2.4 Gloo
2.4.1 什么是Gloo
2.4.2 vLLM 为什么用它
3 修改后验证
4 总结
1 介绍
测试某个模型性能时候,发现低延迟有个大空泡,记录下解决过程。
2 空泡现象和原因分析
2.1 空泡表现
首先搭建环境复现空泡问题,复现后现象如下
2.2 空泡可能原因1
按照以往的经验,我以为这次可能是因为这有个H2D的memcpy, 然后算子下发晚了,所以导致了空泡,
我还分析了下
_get_num_sampled_and_rejected_kernel → _post_update_kernel → Memcpy DtoD → 【空泡】 → Memcpy HtoD(很长) → … 同时另一条 stream 上还有 Memcpy DtoH然后这里就是把304 305行那里直接改掉,因为这里都是pinned memory,GPU是可以直接访问的,不需要拷贝,改成
self.temperature = temperature self.seeds = seeds然后我就想去测试下,但是我花时间又看了下这个prof文件,有了意外发现
2.3 空泡根本原因
我发现最根源的原因应该这个gloo,Worker 热路径线程卡在 CPU 侧 gloo allreduce 上等其它 DP,这段时间走不下去,后续 CUDA kernel 迟迟 enqueue不上,GPU 就空等,表现为空泡。
空泡长短主要来自 各 rank 到达时间差(straggler),不是 gloo 传那几个 int 有多慢。
2.3.1 这个gloo:allreduce的由来,为什么存在
这个gloo:allreduce就来自这个地方,vllm的每个dp用这个通信干两件事,
DP>1 时,上游担心各 rank 本步不一致,例如:
| Rank | 真实 tokens | 本地倾向 |
|---|---|---|
DP0 | 13 | pad→16,FULL CG |
DP1 | 16 | 正好 16,FULL CG |
DP2 | 少量/空 | 更小 bucket 或 NONE |
后面还有 EP/DeepEP 等跨 rank 行为时,CG mode 不同、有效 batch 规模不同,存在对不齐甚至死等的风险。
于是 vLLM 在sync_cudagraph_and_dp_padding(vllm/v1/worker/gpu/dp_utils.py)里做协商:
每个 rank 往 CPU 小张量写入:
num_tokens、cg_mode、uniform_token_count;在 DP 的
cpu_group上all_reduce;约定:
token 取全局 max,再统一
dispatch;cg_mode 取 min(有人 eager → 全员 eager);
uniform 不一致则清空。
2.3.2 修改方法
修改方法就是把这个同步删掉,现在: 各 DP 不再看别人,只对本机真实num_tokens做本地dispatch。
这里需要展开说明一下「本地选 bucket」的含义。所谓 bucket,是指 vLLM 在 CUDAGraph 模式下预先捕捉好的若干固定 batch 尺寸档位,例如 4、8、16 等。每个档位对应一组已经编译好的 CUDA Graph,运行时只能从这些档位里选一个来执行,不能随意使用任意 batch 大小。
在原来的全局同步逻辑下,每个 DP rank 都要先通过 gloo allreduce 拿到全局最大的 num_tokens,上游希望各 DP 的 CG mode 和本步有效 batch 规模一致,避免后续跨 rank 路径(含 EP 相关逻辑)因步调不一致出问题。;但代价是每个 rank 都要等最慢的那个到达,空泡就这样被放大了。
删掉同步之后,每个 DP 只根据本机真实的 num_tokens 就近选一个 bucket。比如本机真实 tokens 是 3,就选 4 这个档位;真实 tokens 是 13,就选 16 这个档位。这样每个 rank 的 batch 规模可能不一样,DeepEP-LL 路径不依赖这层跨 DP 的 CG padding 协商,因此可以安全跳过;普通 DP / 其它 all2all 后端则不宜贸然删除。,空泡也就随之消失。
需要特别注意的是,这个改动只适用于 DeepEP-LL 场景。如果是在普通 DP 场景下贸然删掉同步,各 rank 的 CG mode 和 batch 规模不一致,很可能导致跨 rank 通信对不齐甚至死等,反而引入更严重的问题。
2.4 Gloo
2.4.1 什么是Gloo
集合通信库(allreduce / broadcast / barrier 等),不是和 TCP 同级的“网络协议名”;
主要服务CPU / 主机侧小消息;
传输常见走主机网卡(以太网 TCP 等;部分环境也可走 IB);
与NCCL/RCCL在 PyTorch 里是平级 backend:
GPU 大 tensor → NCCL/RCCL;
CPU 元数据 / 控制面 → Gloo。
2.4.2 vLLM 为什么用它
并行组通常拆两套:
device_group:GPU 数据面;cpu_group:CPU 控制面,backend 为 gloo。
sync_cudagraph_and_dp_padding里:
group = get_dp_group().cpu_group tensor = torch.zeros(3, dp_size, dtype=torch.int32, device="cpu") dist.all_reduce(tensor, group=group)所以 Profiler 里事件名是gloo:all_reduce。
重要纠正:
空泡的根因不是“Gloo 传得慢”(就几十字节)。 根因是每步强制全 DP 同步,把各 rank 到达时间的抖动,放大成全体 GPU idle。
3 修改后验证
4 总结
现象:DP+EP decode 出现 GPU 空泡,与
gloo:all_reduce对齐,多 rank 右边界齐 → straggler 等待。CUDAGraph:固定 shape,本地要把 batch pad 到已捕捉 size(我们最大捕捉 16)。
上游 sync:担心各 DP 的 CG mode / pad 规模不一致,每步用 DP
cpu_group(gloo)allreduce 几个 int,统一成 global max tokens + min mode。空泡成因:同步点数据量极小,但每步 barrier 把到达抖动放大成全员 GPU idle。
Gloo:CPU 侧集合通信库,管控制面;NCCL/RCCL/DeepEP 管数据面。
修复:DeepEP-LL 下跳过 CG padding 的跨 DP sync,只保留本地 dispatch;用 HCU 插件 monkey-patch,不改基线。
验证:gloo 热事件消失、轨变密;精度用 standard MTP 回归。