目录
1 背景
2 pp8 tp2显卡是怎么分的
3 prof分析
4 vllm pp时的overlap
4.1 vllm的pp prefill怎么设置overlap
4.2 那为什么我之前的没有overlap
5 prefill的overlap--增加--no-async-scheduling参数
6 export VLLM_PP_LAYER_PARTITION
6.1 export VLLM_PP_LAYER_PARTITION="10,11,12,12,12,11,13,12"
6.2 export VLLM_PP_LAYER_PARTITION="7,11,11,11,11,11,15,16"
6.3 export VLLM_PP_LAYER_PARTITION="14,11,12,12,12,12,11,9"
6. 4 问题分析
7 sglang和vllm的PP并行机制分析
7.1 SGLang 使用 Micro-Batch 做 Overlap
7.1.1 一个长请求如何形成 Pipeline
7.1.2 多个短请求也可以形成 Batch
7.1.3 Async P2P进一步减少 Bubble
7.1.4 为什么 Chunk Size 很重要?
7.2 vLLM 使用虚拟引擎实现 Overlap
7.2.1 Virtual Engine 是什么?
7.2.2 为什么叫 Virtual Engine?
7.2.3 一个长 Prefill 怎么办?
7.2.4 Virtual Engine 并不等于“一个 VE 永久绑定一个 Chunk”
7.3 vLLM 和 SGLang 的 PP 机制总结
7.3.1 共性:都是为了减少 Pipeline Bubble
7.3.2 主要区别
7.3.3 最核心的理解
7.3.4 一个需要特别注意的术语问题
1 背景
kimi k3测试下了pp8 tp2的性能,性能比较差,然后简单分析下prof。
2 pp8 tp2显卡是怎么分的
这种并行策略下,显卡是这么分的,每两个显卡是一组,
| 全局 rank | PP stage | TP rank |
|---|---|---|
0, 1 | PP0(第 1 段) | TP0, TP1 |
2, 3 | PP1 | TP0, TP1 |
4, 5 | PP2 | TP0, TP1 |
6, 7 | PP3 | TP0, TP1 |
8, 9 | PP4 | TP0, TP1 |
10, 11 | PP5 | TP0, TP1 |
12, 13 | PP6 | TP0, TP1 |
14, 15 | PP7(第 8 段) | TP0, TP1 |
这里也基本上能看到,每两张卡是一样的。
3 prof分析
分析prof的时候,可以把偶数的prof放一起看,比如0 2 4 6 8 10 12 14,但是当我把这8个放一起有些乱,所以就只看四个,8 10 12 14这四个。
这里可以看到是没有overlap起来,这里的pp是等上一个卡算完了,然后下一个卡才开始算,这就是性能差的原因,
然后这里红色的nccl就是pp8之间的数据传输,然后蓝色的是在等待,
然后tp之间的那个nccl通信在这里
为什么会这样,因为decoder是需要依赖上一个decoder的,那么就是要等待上一个decoder结束了才能开始下一个,所以这里只能等待。
性能差的主要原因是 PP 气泡:前面 stage 算完后长时间等待(profile 里可见长 NCCL Broadcast)。同一序列的 decode 确实依赖上一 token,必须等整条 pipeline 结束才能算下一 token。若要把 GPU 填满,需要把工作拆成多个 microbatch,让不同 batch 同时处在不同 PP stage。SGLang 有这套 PP microbatch / async P2P;当前 vLLM 的 PP 路径基本没有对等能力,因此 decode 场景下 PP 利用率往往明显差于「少 PP、多 TP」。
这里说的是vllm的pp在decoder的时候是没有micrpbatch,是没法overlap的,但是pp时候prefill是可以overlap的,只是他不叫microbatch,
4 vllm pp时的overlap
vLLM 里 PP 的 prefill 可以 overlap,机制是 batch queue(同时挂pp_size个 in-flight batch),代码里一般叫 concurrent batches,不叫 microbatch。
和 SGLang 的差别主要是名字和完整度:
- SGLang:明确叫 PP microbatch,有独立 event loop、可调
--pp-max-micro-batch-size/--pp-async-batch-depth。 - vLLM:开 PP 后自动
max_concurrent_batches = pp_size,用队列让不同 batch/chunk 停在不同 stage;prefill 还能跳过 sampled-token broadcast,所以 overlap 能起来。
所以:prefill 的 overlap 有;只是 vLLM 不把这套叫 microbatch,也没有 SGLang 那么完整的一套专用实现。Decode 仍然很难靠这个填满。
4.1 vllm的pp prefill怎么设置overlap
这个是不用设置开关的,当使用pp的时候,prefill阶段自动的就会把 batch queue 设成pp_size,prefill 就能 overlap。
4.2 那为什么我之前的没有overlap
我之前的命令,服务端是类似
PYTHONUNBUFFERED=1 vllm serve /module2/Kimi-K3 \ --trust-remote-code \ --moe-backend auto \ -cc.pass_config.fuse_allreduce_rms=False \ -pp 8 \ -tp 2 \ --load-format fastsafetensors \ --gpu-memory-utilization 0.95 \ --mm-encoder-tp-mode data \ --limit-mm-per-prompt '{"image": 0}' \ --max-model-len 32768 \ --max-num-seqs 128 \ --max-num-batched-tokens 16384 \ --enable-auto-tool-choice \ --tool-call-parser kimi_k3 \ --reasoning-parser kimi_k3 \ --nnodes 2 \ --node-rank 0 \ --master-addr 13.13.4.20 \ --profiler-config '{"profiler": "torch", "torch_profiler_dir": "/volume/vllm_prof/", "torch_profiler_with_stack": true}'客户端是类似
vllm bench serve --dataset-name random --model /module2/Kimi-K3 \ --trust-remote-code \ --num-prompts 16 \ --max-concurrency 16 \ --random-input-len 256 \ --random-output-len 32 \ --random-range-ratio 0.0 \ --endpoint /v1/completions \ --tokenizer /module2/Kimi-K3 \ --temperature 0.0 \ --ignore-eos \ --host 10.16.1.20 \ --profile那么相当于我的是
input=256, 并发=16, max-num-batched-tokens=16384, max-num-seqs=128一次 step 的预算远大于实际工作量:
- 16 条 × 256 token = 4096 ≪ 16384
- 16 条 ≪ 128
调度器会把 16 条打成一个大 batch,一起走 PP0 → PP1 → … → PP7。这不是 16 个小 batch 停在不同 stage,而是 同一拍里 16 条一起过整条管,所以还是楼梯 + 前面 rank 干等。
短的一条不行。 256 token 一次算完,PP0 干完就没下一块了。
足够长、被切成多 chunk 的一条可以。 这才是 prefill overlap 的典型打法:
- PP0 算完 chunk1,KV 已写好,立刻开同一条的 chunk2
- 后面 stage 还在算 chunk1
这不依赖「多条用户请求」,依赖的是 同一条被切成多块。
比如
--max-num-batched-tokens 320 bench: --random-input-len 32000 --random-output-len 1假如一次只能处理16384,那么不管你是多个16384 的batch,还是一个batch但是长度远远大于16384,都可以,核心原理就是这次客户端发送过来的请求,一次处理不了就会切成queue了,如果一次能处理就不会切,那么就看起来是在串行等待了
5 prefill的overlap--增加--no-async-scheduling参数
前面分析说当把这个max-num-batched-tokens设置的小点就能overlap了,但是经过实际测试验证,这样是没有用的,吞吐和性能并没有上升,真正想提高pp prefill性能,需要加--no-async-scheduling参数
- 如果不加这个参数,那么默认就是异步调度的,那么这时候pp8结束之后,会得到sample token,然后用broadcast将这个token告诉前面的7个pp,那么这时候相当于前面的7个pp都在等待第8个pp,
- 如果加了这个参数,就 不是异步调度了,这时候相当于这个token产生之后不做广播,而是由框架去调度,框架在下一步的调度中将这个token给加上,
- 这个参数主要能提高prefill的性能,对于decoder,也有作用但是作用有限,因为decoder就是要等上一个token产生了才能进行下一个token的推理,只不过当并发任务很多的时候,这个参数可以让比如在等待的时候,其他的pp可以先去做其他batch的推理,而不用一直在这里等他这个batch的下一个token产生。
我加上这个参数重新测试性能,提升特别明显,但是跟sglang还有点差距
这里感觉8个pp分到的层不一样,现在默认的是
[11, 11, 12, 12, 12, 12, 12, 11]
但是可能虽然层看着差不多,但是每个层的复杂度不一样,所以可以试试用参数设置下分层
export VLLM_PP_LAYER_PARTITION="10,11,12,12,12,11,13,12"
6 export VLLM_PP_LAYER_PARTITION
6.1 export VLLM_PP_LAYER_PARTITION="10,11,12,12,12,11,13,12"
这样export VLLM_PP_LAYER_PARTITION="10,11,12,12,12,11,13,12"做了之后,还是不行。
6.2 export VLLM_PP_LAYER_PARTITION="7,11,11,11,11,11,15,16"
邪门,更差了,
6.3 export VLLM_PP_LAYER_PARTITION="14,11,12,12,12,12,11,9"
6. 4 问题分析
把前面几个prof对比看,其实发现一个问题,
就是这个prof里面,其实是倒过来的,就是pp0其实是在最下面的,这也能解释为什么减小pp0层数怎么更差了,
7 vllm和sglang的PP机制
PP 把不同层放到不同 stage 上。一个 batch 要从 PP0 依次传到 PP7。如果同时只有一个执行单元,前面的 stage 算完就得空等,这就是 pipeline bubble。
两边都会尽量让多个执行单元同时在流水线里。差别在于,同一级能不能边算、边和下一级通信。
7.1 SGLang
SGLang 把 chunk(micro-batch)当成流水线里的执行单元。不同 stage 同时处理不同 chunk。中间一级(比如 PP2)在算当前块时,通信已经叠在旁边:
PP2 正在算 C1 (当前这块,数据早就收齐了)
│
├── 同时把 C0 发给 PP3 (上一块,异步 send,不用等发完才开始算 C1)
│
└── 同时从 PP1 收 C2 (下一块,先把 recv 挂上,算 C1 的时候数据在路上)
算的是当前块,发的是上一块,收的是下一块。当前这块必须先收完才能算。
7.2 vLLM(V1,现在的实现)
V1 不用 Virtual Engine。调度器用batch_queue(PP=8 时大约 8 个槽)让多个 batch 同时在流水线里:PP0 算新的 batch 时,PP7 可以还在算更早的 batch。这一层填的是「不同 stage 同时有活」。
同一级内部没有 SGLang 那种重叠。非第一级每一步是:
① wait:PP2等「上一拍发给 PP3」的 send 真正结束
② 再 irecv:收「马上要算的这一块」从 PP1 来的数据
③ 再算这一块
④ 算完再 isend 给 PP3
算 C2 之前,先等 C1 发给 PP3 结束,再收 C2,再算 C2,算完才把 C2 发出去。算 C2 的时候,不会同时发 C1,也不会收 C3。
代码里 overlapping micro-batch 仍是 TODO(#18019)。isend只是先发出去,下一拍开头立刻wait,所以通信没有和下一批计算并行。
7.3 不同点
| SGLang | vLLM V1 | |
|---|---|---|
多个执行单元同时在 PP 里 | 有,chunk / micro-batch | 有, |
同一级边算边通信 | 有。算 C1 时发 C0、收 C2 | 没有。先等上次 send,再收当前块,算完再发 |
改层划分对吞吐 | 各级纯计算时间更齐,容易反映到吞吐 | 只能削一点不均。通信空等还在,改切层往往涨不了 |
一句话:两边都能让 PP0~PP7 同时干活。SGLang 还能让「上一拍的发送、当前的计算、下一块的接收」叠在一起。vLLM 现在不能,通信和下一个 batch 的计算是串开的。