vLLM 推理性能深度分析:阶梯并发实验、Roofline 模型与调度瓶颈验证
环境:AutoDL | 2×RTX 4080 | Qwen/Qwen2.5-7B-Instruct 涵盖 TP=1 与 TP=2 两组阶梯并发实验,以及调度瓶颈深度分析
一、实验环境与基础配置
1.1 服务启动(TP=2)
vllm serve /root/autodl-tmp/models/Qwen/Qwen2.5-7B-Instruct --tensor-parallel-size 2
显存状态(冷启动后)
GPU 0: 30356MiB / 32760MiB — VLLM::Worker_TP0
GPU 1: 30356MiB / 32760MiB — VLLM::Worker_TP1
功耗:GPU0 37W / GPU1 39W(冷启动空载,P2 状态)
1.2 统一压测参数
所有压测使用相同的参数口径:
./vllm-bench --backend vllm --base-url http://127.0.0.1:8000 \
--model /root/autodl-tmp/models/Qwen/Qwen2.5-7B-Instruct \
--dataset-name random \
--random-input-len 1024 --random-output-len 128 \
--num-prompts 1000 --max-concurrency 200
| 参数 | 值 |
|---|---|
| 数据集 | random |
| Input length | 1024 |
| Output length | 128 |
| Prompt 数 | 1000(基线)/ 300~500(阶梯实验) |
| 最大并发 | 1~200 阶梯递增 |
1.3 GPU 裸算力基线(并发=1)
将并发度调为 1,消除 prefill 与 decode 的互相抢占,得到 GPU 纯算力基线。
TP=1 基线(单卡)
./vllm-bench --backend vllm --base-url http://127.0.0.1:8000 \
--model /root/autodl-tmp/models/Qwen/Qwen2.5-7B-Instruct \
--dataset-name random \
--random-input-len 1024 --random-output-len 128 \
--num-prompts 1000 --max-concurrency 1
| 指标 | 值 | 说明 |
|---|---|---|
| TPOT (ITL) | 25ms | GPU 裸算力:单请求独占 GPU 的 decode 耗时 |
| TTFT | 250ms | GPU 裸算力:prefill 1024 tokens 的耗时 |

TP=2 基线(双卡)
| 指标 | 值 | 说明 |
|---|---|---|
| TPOT (ITL) | 12.07ms | 双卡裸算力,约为 TP=1 的 48% |
| TTFT | 62.17ms | 双卡裸算力,约为 TP=1 的 25% |
| E2EL | 1595ms | 符合公式 62 + 127×12.07 ≈ 1595ms ✅ |
TPOT 增量对比
| 场景 | Mean TPOT | TPOT 增量 | 占比 |
|---|---|---|---|
| TP=1 并发=1(裸算力) | 25ms | — | — |
| TP=2 并发=1(裸算力) | 12ms | — | — |
| TP=2 并发=200 热 | 222ms | 210ms | 95% |
| TP=2 并发=200 冷 | 255ms | 243ms | 95% |
TPOT 增量 = 实际 TPOT − conc=1 裸算力 TPOT。注意:高并发下 decode step 同时处理多个 token(GEMM),FLOPs 远高于 conc=1 的 GEMV 操作。因此 TPOT 增量中同时包含了合法计算增长与调度/同步开销,不应全部归为"空闲时间"。
二、TP=2 冷启动 vs 热启动
首次发现:冷启动后第一次压测的吞吐明显低于紧接着的第二次,差异约 13%。
2.1 整体概览
| 指标 | 第一次(冷启动) | 第二次(热) | 变化 |
|---|---|---|---|
| Benchmark 耗时 | 184.06s | 162.91s | -11.5% |
| Total Token 吞吐 | 6247 tok/s | 7058 tok/s | +13.0% |
| Request 吞吐 | 5.43 req/s | 6.14 req/s | +13.1% |
| Output Token 吞吐 | 695 tok/s | 786 tok/s | +13.0% |
2.2 延迟指标
| 指标 | 第一次 | 第二次 | 变化 |
|---|---|---|---|
| Mean TTFT | 4105ms | 4076ms | ≈ 无差异 |
| P99 TTFT | 28579ms | 22938ms | -19.7% |
| Mean TPOT | 255ms | 222ms | -12.9% |
| Median TPOT | 282ms | 222ms | -21.3% |
| Mean E2EL | 36520ms | 32294ms | -11.6% |
| P99 E2EL | 62661ms | 47541ms | -24.1% |
| Median E2EL | 37314ms | 33904ms | -9.1% |
2.3 Steady-State 对比
| 指标 | 第一次 | 第二次 | 变化 |
|---|---|---|---|
| Mean TTFT | 1888ms | 2169ms | +14.9%↑ |
| Mean TPOT | 254ms | 228ms | -10.2% |
| Request 吞吐 | 4.48 req/s | 5.08 req/s | +13.4% |
| Output Token 吞吐 | 653 tok/s | 739 tok/s | +13.2% |
2.4 实验结论
现象总结
| 观察 | 说明 |
|---|---|
| ✅ E2E 延迟第二次降低 ~12% | 热请求比冷请求整体更快完成 |
| ✅ TPOT 降低 ~13% | 每次 decode step 效率更高 |
| ✅ Token 吞吐提升 ~13% | 单位时间处理更多 token |
✅ num_requests_waiting 降低 |
排队请求减少,调度更高效 |
| ❌ TTFT 无明显差异 | 冷启动不影响首 token 生成速度 |
| ❌ GPU 利用率一致 | 瓶颈不在 GPU 计算,在 CPU 调度侧 |
核心推论
- Warmup 很重要:第一次压测触发了 scheduler 和 attention 后端的初始化,第二次直接受益
- 当前瓶颈在 CPU/调度侧:GPU-Util 两次均为 99%,说明 GPU 持续有 kernel 在执行,TPOT 差异来自 scheduler 排队优化(注:GPU-Util 仅表示非空闲,不直接反映 SM 饱和度)
- 单请求的 TTFT/TPOT 没变,但整体吞吐提升 → 请求之间重叠度更高,空闲时间更少
E2EL 验算公式
E2EL ≈ TTFT + (output_len - 1) × TPOT
第一次: 4105 + 127 × 255 = 36490 ≈ 36520 ✅
第二次: 4076 + 127 × 222 = 32270 ≈ 32294 ✅
2.5 原始日志
================= Serving Benchmark Result =================
Successful requests: 1000
Failed requests: 0
Maximum request concurrency: 200
Benchmark duration (s): 184.06
Total input tokens: 1021847
Total generated tokens: 128000
Request throughput (req/s): 5.43
Output token throughput (tok/s): 695.43
Peak output token throughput (tok/s): 3683.00
Peak concurrent requests: 210.00
Total token throughput (tok/s): 6247.13
--------------------Time to First Token---------------------
Mean TTFT (ms): 4105.63
Median TTFT (ms): 1316.94
P99 TTFT (ms): 28579.09
P90 TTFT (ms): 13852.11
----------Time per Output Token (excl. 1st token)-----------
Mean TPOT (ms): 255.23
Median TPOT (ms): 281.67
P99 TPOT (ms): 288.21
P90 TPOT (ms): 285.97
--------------------Inter-token Latency---------------------
Mean ITL (ms): 255.23
Median ITL (ms): 325.15
P99 ITL (ms): 335.78
P90 ITL (ms): 329.41
---------------------End-to-end Latency---------------------
Mean E2EL (ms): 36520.30
Median E2EL (ms): 37314.13
P99 E2EL (ms): 62661.13
P90 E2EL (ms): 47221.33
============================================================
============ Steady-State Metrics =============
Concurrency threshold: >= 0.95 * 200 = 190
Window: 0.1s -> 180.7s (180.6s)
Observed peak concurrency: 200
Requests started in window: 811 / 1000 (81.1%)
Requests completed in window: 810
-----------------------------------------------
Request throughput (req/s): 4.48
Output token throughput (tok/s): 653.02
Input token throughput (tok/s): 4588.30
Total token throughput (tok/s): 5241.31
-----------------------------------------------
Mean TTFT (ms): 1888.12
Median TTFT (ms): 1315.00
P99 TTFT (ms): 28543.14
P90 TTFT (ms): 2914.28
-----------------------------------------------
Mean TPOT (ms): 254.27
Median TPOT (ms): 282.80
P90 TPOT (ms): 286.04
P99 TPOT (ms): 288.31
================= Serving Benchmark Result =================
Successful requests: 1000
Failed requests: 0
Maximum request concurrency: 200
Benchmark duration (s): 162.91
Total input tokens: 1021847
Total generated tokens: 128000
Request throughput (req/s): 6.14
Output token throughput (tok/s): 785.70
Peak output token throughput (tok/s): 3800.00
Peak concurrent requests: 217.00
Total token throughput (tok/s): 7058.06
--------------------Time to First Token---------------------
Mean TTFT (ms): 4076.72
Median TTFT (ms): 1941.42
P99 TTFT (ms): 22938.96
P90 TTFT (ms): 13225.27
----------Time per Output Token (excl. 1st token)-----------
Mean TPOT (ms): 222.19
Median TPOT (ms): 222.21
P99 TPOT (ms): 282.03
P90 TPOT (ms): 277.71
--------------------Inter-token Latency---------------------
Mean ITL (ms): 222.19
Median ITL (ms): 318.70
P99 ITL (ms): 333.32
P90 ITL (ms): 328.38
---------------------End-to-end Latency---------------------
Mean E2EL (ms): 32294.56
Median E2EL (ms): 33904.32
P99 E2EL (ms): 47541.62
P90 E2EL (ms): 39384.57
============================================================
============ Steady-State Metrics =============
Concurrency threshold: >= 0.95 * 200 = 190
Window: 0.1s -> 159.6s (159.5s)
Observed peak concurrency: 200
Requests started in window: 811 / 1000 (81.1%)
Requests completed in window: 810
-----------------------------------------------
Request throughput (req/s): 5.08
Output token throughput (tok/s): 739.41
Input token throughput (tok/s): 5195.51
Total token throughput (tok/s): 5934.91
-----------------------------------------------
Mean TTFT (ms): 2168.68
Median TTFT (ms): 1639.29
P99 TTFT (ms): 22938.77
P90 TTFT (ms): 3541.12
-----------------------------------------------
Mean TPOT (ms): 228.32
Median TPOT (ms): 254.46
P90 TPOT (ms): 278.01
P99 TPOT (ms): 282.53
三、Warmup 验证
目标:找到最小成本的预热方式,消除冷启动后首次压测的性能劣化(~13% 吞吐差距)
3.1 方案一:短请求 curl(❌ 无效)
for i in {1..10}; do
curl -s http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"/root/autodl-tmp/models/Qwen/Qwen2.5-7B-Instruct","messages":[{"role":"user","content":"hello"}],"max_tokens":10,"temperature":0}' \
> /dev/null && echo "✅ $i/10"
done
结果:预热后第一次压测的吞吐仍明显低于第二次。

原因分析:短请求(max_tokens=10)不会触发和正式压测相同规模的 KV cache 分配和 scheduler 调度路径,预热不充分。
3.2 方案二:长文本 curl(❌ 仍不足)
for i in $(seq 1 10); do
echo "=== Warmup $i/10 ==="
curl -s http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "/root/autodl-tmp/models/Qwen/Qwen2.5-7B-Instruct",
"messages": [{"role": "user", "content": "请详细解释机器学习中的注意力机制是如何工作的,包括自注意力和多头注意力的原理。"}],
"max_tokens": 128,
"temperature": 0
}' > /dev/null
echo " ✅ done"
done
结果:预热后两次压测仍有明显差距,第二次吞吐显著高于第一次。

原因分析:虽然输出长度匹配了(128 tokens),但 curl 是串行请求,无法模拟压测时的高并发调度压力。
3.3 方案三:低并发 vllm-bench(✅ 有效)
./vllm-bench --backend vllm --base-url http://127.0.0.1:8000 \
--model /root/autodl-tmp/models/Qwen/Qwen2.5-7B-Instruct \
--dataset-name random \
--random-input-len 1024 --random-output-len 128 \
--num-prompts 50 --max-concurrency 10
结果:预热效果明显,第一次正式压测的吞吐接近第二次。

3.4 方案四:高并发 vllm-bench(✅✅ 更优)
./vllm-bench --backend vllm --base-url http://127.0.0.1:8000 \
--model /root/autodl-tmp/models/Qwen/Qwen2.5-7B-Instruct \
--dataset-name random \
--random-input-len 1024 --random-output-len 128 \
--num-prompts 50 --max-concurrency 50
结果:预热后第一次正式压测的吞吐几乎等同于第二次,冷热差距基本消除。

3.5 Warmup 小结
| 方案 | 请求方式 | 并发 | 输出长度 | 效果 |
|---|---|---|---|---|
| 一 | curl 串行 | 1 | 10 | ❌ 无效 |
| 二 | curl 串行 | 1 | 128 | ❌ 仍不足 |
| 三 | vllm-bench | 10 | 128 | ✅ 有效 |
| 四 | vllm-bench | 50 | 128 | ✅✅ 最优 |
关键结论:
- 并发是 warmup 的关键变量,串行请求即使输出长度匹配也无法预热 scheduler
- 输出长度要匹配线上口径,太短不会触发足够的 decode 路径
- 推荐 warmup 策略:用与线上相同的 input/output 长度 + 中等并发(正式并发的 25%
50%),跑 50100 个 prompt 即可
四、单卡计算受限实验(TP=1 阶梯并发)
背景:冷热对比发现 TP=2 场景下瓶颈在 CPU 调度侧,但缺少数据证明 tensor core 是否真正饱和(GeForce 卡不支持 DCGM PROF 指标,无法区分 compute-bound 与 memory-bound)。
目标:改用单卡(TP=1),通过逐步提高并发度,找到 GPU 从 memory-bound → compute-bound 的拐点。
4.1 实验设计
思路:decode 阶段在低并发下是 GEMV(矩阵×向量),天然 memory-bound。提高并发后 continuous batching 将多个 decode 请求合并成 GEMM,batch size 越大,tensor core 越饱和。
方法:单卡启动服务,用 vllm-bench 阶梯式提高并发,记录每轮的关键指标。
# 服务端(TP=1)
vllm serve /root/autodl-tmp/models/Qwen/Qwen2.5-7B-Instruct --tensor-parallel-size 1
# 阶梯压测脚本
for conc in 8 16 32 64 128 200; do
if [ $conc -le 32 ]; then N=300; else N=500; fi
echo "======================================"
echo " 并发度 = $conc (num-prompts=$N)"
echo "======================================"
nvidia-smi --query-gpu=power.draw,temperature.gpu,\
utilization.gpu,utilization.memory --format=csv -l 1 \
> /tmp/gpu_monitor_conc${conc}.csv &
MONITOR_PID=$!
./vllm-bench --backend vllm --base-url http://127.0.0.1:8000 \
--model /root/autodl-tmp/models/Qwen/Qwen2.5-7B-Instruct \
--dataset-name random \
--random-input-len 1024 --random-output-len 128 \
--num-prompts $N --max-concurrency $conc
kill $MONITOR_PID 2>/dev/null
done
4.2 采集指标
| 类别 | 指标 | 数据来源 | 说明 |
|---|---|---|---|
| 性能 | TPOT (mean/median) | vllm-bench 输出 | 每个 token 的生成时间 |
| 性能 | Token 吞吐 (tok/s) | vllm-bench 输出 | 系统总吞吐 |
| 性能 | Request 吞吐 (req/s) | vllm-bench 输出 | 每秒完成请求数 |
| 功耗 | GPU 功耗 (W) | nvidia-smi power.draw |
接近 TDP=320W 说明算力受限 |
| 温度 | GPU 温度 (°C) | nvidia-smi temperature.gpu |
避免 throttling(≥85°C 需关注) |
| SM | GPU-Util (%) | nvidia-smi utilization.gpu |
接近 100% 是必要条件,但非充分 |
| Mem | Mem-Util (%) | nvidia-smi utilization.memory |
高 → memory-bound;下降 → compute-bound |
4.3 实验结果
TP=1, Qwen2.5-7B-Instruct, input=1024, output=128(2026-07-02 实测)
整体指标
| 指标 | conc=8 | conc=16 | conc=32 | conc=64 | conc=128 | conc=200 |
|---|---|---|---|---|---|---|
| Benchmark 耗时 | 142.82s | 100.13s | 74.77s | 104.20s | 96.97s | 106.44s |
| Request 吞吐 | 2.10 req/s | 3.00 req/s | 4.01 req/s | 4.80 req/s | 5.16 req/s | 4.70 req/s |
| Output Token 吞吐 | 268.87 tok/s | 383.50 tok/s | 513.60 tok/s | 614.19 tok/s | 660.02 tok/s | 601.26 tok/s |
| Total Token 吞吐 | 2415.54 tok/s | 3445.47 tok/s | 4614.23 tok/s | 5518.27 tok/s | 5930.01 tok/s | 5402.06 tok/s |
| Mean TTFT | 417.42ms | 696.92ms | 1113.52ms | 1742.84ms | 3849.61ms | 8274.87ms |
| P99 TTFT | 1006.08ms | 1895.69ms | 2959.38ms | 7838.34ms | 16154.24ms | 30199.08ms |
| Mean TPOT | 26.38ms | 36.19ms | 52.33ms | 90.22ms | 162.54ms | 262.62ms |
| Median TPOT | 26.48ms | 37.05ms | 53.64ms | 94.46ms | 180.43ms | 285.73ms |
| Mean E2EL | 3767.33ms | 5293.65ms | 7759.02ms | 13200.18ms | 24491.84ms | 41627.83ms |
Steady-State 指标
| 指标 | conc=8 | conc=16 | conc=32 | conc=64 | conc=128 | conc=200 |
|---|---|---|---|---|---|---|
| Request 吞吐 | 2.09 req/s | 2.93 req/s | 3.79 req/s | 4.40 req/s | 4.11 req/s | 3.06 req/s |
| Output Token 吞吐 | 271.18 tok/s | 380.92 tok/s | 518.98 tok/s | 580.73 tok/s | 571.74 tok/s | 513.08 tok/s |
| Mean TTFT | 423.89ms | 668.58ms | 1025.30ms | 1414.30ms | 2597.92ms | 4966.95ms |
| Mean TPOT | 26.46ms | 36.41ms | 53.54ms | 90.99ms | 165.13ms | 251.96ms |
GPU 监控(nvidia-smi 实测)
| 指标 | conc=8 | conc=16 | conc=32 | conc=64 | conc=128 | conc=200 |
|---|---|---|---|---|---|---|
| GPU 功耗 avg | 261W | 284W | 293W | 298W | 306W | 304W |
| GPU 功耗 max | 308W | 316W | 317W | 315W | 316W | 315W |
| GPU-Util avg | 100% | 99% | 99% | 98% | 99% | 98% |
| Mem-Util avg | 85% | 69% | 57% | 46% | 51% | 55% |
| 温度 avg | 66°C | 72°C | 75°C | 77°C | 78°C | 78°C |

4.4 结果分析
TPOT 随并发增长趋势
| 并发度 | Mean TPOT | 相比 conc=1 的倍数 | TPOT 增量 | GPU 功耗 |
|---|---|---|---|---|
| 1(裸算力) | 25ms | 1.0× | — | 250W |
| 8 | 26ms | 1.1× | 1ms | 261W |
| 16 | 36ms | 1.4× | 11ms | 284W |
| 32 | 52ms | 2.1× | 27ms | 293W |
| 64 | 90ms | 3.6× | 65ms | 298W |
| 128 | 163ms | 6.5× | 138ms | 306W |
| 200 | 263ms | 10.5× | 238ms | 304W |
关键观察:
- conc=8 时 TPOT 几乎无变化(25→26ms),TPOT 增量几乎为零
- conc=16 开始 TPOT 明显抬头(26→36ms,+38%),batch 开始积累
- conc=32 时 TPOT 翻倍(26→52ms),continuous batching 形成有效 GEMM
- conc=64 时 TPOT 加速增长(52→90ms,+73%),TPOT 增量 65ms(含合法计算增长 + 调度开销)
- conc=128 时 TPOT 继续恶化(90→163ms,+81%),TPOT 增量 138ms
- conc=200 时已过饱和(163→263ms,+61%),吞吐反而下降
吞吐拐点分析
| 并发度 | Output Token 吞吐 | Total Token 吞吐 | 边际收益 |
|---|---|---|---|
| 8 | 269 tok/s | 2416 tok/s | — |
| 16 | 384 tok/s | 3445 tok/s | +43% |
| 32 | 514 tok/s | 4614 tok/s | +34% |
| 64 | 614 tok/s | 5518 tok/s | +19% |
| 128 | 660 tok/s | 5930 tok/s | +7% |
| 200 | 601 tok/s | 5402 tok/s | -9% ⚠️ |
吞吐在 conc=128 达到峰值,conc=200 反而下降 9%,说明系统已过饱和。排队延迟 > 新增并发带来的收益。
状态判定
| 并发区间 | 状态 | 说明 |
|---|---|---|
| ≤8 | 🔵 memory-bound | GEMV 模式,TPOT 几乎等于裸算力,Mem-Util 100% |
| 16~32 | 🟡 过渡区 | batch 逐渐形成 GEMM,TPOT 开始增长,吞吐快速提升 |
| 64 | 🟢 最佳性价比点 | Mem-Util 降至 46%(最低),功耗 298W(93% TDP);OI=55 仍低于 Ridge=135,纯 Roofline 属带宽区,但硬件已接近饱和 |
| 128 | 🟢 吞吐峰值 | 功耗峰值 306W,吞吐极限 660 tok/s;OI=109 仍低于 Ridge |
| 200 | ⚫ 过饱和 | 吞吐倒退 9%,TTFT 飙到 8.3s |
4.5 结论:最佳性价比拐点 = conc=64
GPU 功耗与 Mem-Util 证据链
| 并发度 | 功耗 | TDP 占比 | Mem-Util | 状态判定 |
|---|---|---|---|---|
| 1 | 250W | 78% | 100% | 🔵 memory-bound — GEMV,显存带宽瓶颈 |
| 8 | 261W | 82% | 85% | 🔵 memory-bound |
| 16 | 284W | 89% | 69% | 🟡 过渡区 |
| 32 | 293W | 92% | 57% | 🟡 过渡区 |
| 64 | 298W | 93% | 46% ✅ | 🟢 硬件近饱和(OI=55 < Ridge=135,Roofline 仍属带宽区) |
| 128 | 306W | 96% ✅ | 51% | 🟢 硬件近饱和(OI=109 < Ridge=135) |
| 200 | 304W | 95% | 55% | ⚫ 过饱和 |
关键判据:
- ✅ Mem-Util 从 100% 降至 46% — 显存带宽不再是瓶颈
- ✅ GPU 功耗 ≥ 300W(94% TDP) — conc≥64 时功耗持续 298W+
- ✅ GPU-Util 始终 ~99% — GPU 持续有 kernel 在执行
- ✅ TPOT 在 conc≥32 后近似线性增长 — 并发翻倍,TPOT 也翻倍
结论:单卡 RTX 4080 在 conc=64 时达到最佳性价比拐点。此时 GPU 功耗 298W(93% TDP)、Mem-Util 降至 46%,硬件接近饱和。但算术强度 OI=55 仍低于 Ridge Point(135),纯 Roofline 意义上仍处于带宽区——硬件虽接近饱和,但 scheduler 开销已占主导,继续提高并发的边际收益急剧递减。
4.6 Roofline Model 分析
理论天花板
| 参数 | RTX 4080 |
|---|---|
| FP16 Tensor Core 峰值 | 97 TFLOPS |
| 显存带宽 | 716.8 GB/s |
| Ridge Point (OI) | 97 / 0.717 ≈ 135 FLOPs/Byte |
| 模型权重 (FP16) | 7B × 2 bytes = 14 GB |
| 单 token decode FLOPs | 2 × 7B = 14G FLOPs |
OI 定量计算(以 Qwen2.5-7B 为例):
- 权重读取:~14 GB(每步固定,跨 batch 共享)
- KV cache 读取:2 × B × seq_len × d_model × (n_kv_heads / n_heads) × 2 bytes,随并发 B 增长
- FLOPs:2BN(Attention)+ 6B·d_model·ffn_dim(SwiGLU FFN),随 B 线性增长
- OI = FLOPs / (weight_bytes + kv_bytes),由于 KV cache 读取随 B 增加,OI 略低于并发度(低约 15~19%)
各并发度在 Roofline 上的位置
| 并发 | OI | Ridge=135 | 实测 TFLOPS | 天花板达成率 | 瓶颈判定 |
|---|---|---|---|---|---|
| 1 | 1 | <135 (带宽区) | 0.56 | 78% ⬅️带宽 | 🔵 接近带宽天花板(GEMV, memory-bound) |
| 8 | 7 | <135 (带宽区) | 4.25 | 74% ⬅️带宽 | 🔵 靠近带宽天花板 |
| 16 | 14 | <135 (带宽区) | 6.19 | 54% ⬅️带宽 | 🟡 开始偏离带宽天花板 |
| 32 | 28 | <135 (带宽区) | 8.56 | 37% ⬅️带宽 | 🟡 偏离加剧 |
| 64 | 55 | <135 (带宽区) | 9.93 | 22% ⬅️带宽 | 🟡 带宽区但 scheduler-bound |
| 128 | 109 | <135 (带宽区) | 11.02 | 12% ⬅️带宽 | 🟡 带宽区但 scheduler-bound |
| 200 | 168 | >135 (计算区) | 10.66 | 11% ⬅️算力 | ⚫ 过饱和 |
注:「天花板达成率」的计算方式取决于 OI 所处的区域:
- OI < Ridge(带宽区):
实测 TFLOPS / (OI × 显存带宽),表示离带宽天花板的距离。例如 conc=64 达成率 22%(带宽天花板 39.4 TFLOPS)。- OI > Ridge(计算区):
实测 TFLOPS / 算力峰值,表示离算力天花板的距离。例如 conc=200 达成率 11%(算力天花板 97 TFLOPS)。它与 nvidia-smi 的 Mem-Util 是两回事(后者是显存控制器繁忙程度),例如 conc=64 达成率 22%,但 Mem-Util 为 46%。

关键发现
- conc=1 贴着带宽天花板跑(78%) → 符合预期,GEMV 天然 memory-bound,OI=1 远低于 Ridge Point(135)
- conc≥16 后逐渐偏离带宽天花板 → continuous batching 开始形成 GEMM,OI 增长但达成率下降
- conc≥64 后 OI 仍低于 Ridge Point(55 < 135) → 理论上仍处于 Roofline 的带宽区,但并非 memory-bound,因为天花板达成率仅 22%,远未触及带宽天花板。scheduler 开销是主要原因
- OI=55 时带宽天花板:0.717 × 55 = 39.4 TFLOPS
- 实测:9.93 TFLOPS,仅达到带宽天花板的 25%
- conc=200 首次越过 Ridge Point(OI=168 > 135) → 进入计算区,天花板达成率切换为算力天花板(97 TFLOPS),实测仅达到 11%,说明 scheduler 问题同样锁死了算力利用率,且吞吐下滑
带宽天花板 (conc=1): ████████████████████████░░░ 78% (OI=1, 纯 GEMV)
非 GPU 时间 (conc=64): ██████░░░░░░░░░░░░░░░░ 25% (OI=55, scheduler-bound)
非 GPU 时间 (conc=200): ███░░░░░░░░░░░░░░░░░░░ 11% (OI=168, 过饱和)
TP=1 最优并发度推荐
| 目标 | 推荐并发 | 吞吐 | TPOT | 说明 |
|---|---|---|---|---|
| 最低延迟 | ≤8 | 269 tok/s | 26ms | TPOT 增量≈0 |
| 最佳性价比 | 64 | 614 tok/s | 90ms | 吞吐/延迟均衡点 |
| 最高吞吐 | 128 | 660 tok/s | 163ms | 吞吐极限 |
| ❌ 避免 | 200 | 601 tok/s | 263ms | 过饱和 |
4.7 Nsight 实时监控命令
# 方案 B:先启动服务,然后 attach profile
vllm serve /root/autodl-tmp/models/Qwen/Qwen2.5-7B-Instruct --tensor-parallel-size 1 &
# 找到 PID,attach profiling 30s
nsys profile -t cuda,nvtx,osrt -o decode_profile --duration 30 -p $(pgrep -f "vllm serve")
五、双卡计算受限实验(TP=2 阶梯并发)
用 TP=2 复现 TP=1 的阶梯并发实验,对比 TP Scaling Efficiency 与调度开销差异。
5.1 实验设计
# 服务启动
vllm serve /root/autodl-tmp/models/Qwen/Qwen2.5-7B-Instruct --tensor-parallel-size 2
# 阶梯压测
for conc in 1 64 128 200; do
if [ $conc -le 32 ]; then N=300; else N=500; fi
nvidia-smi --query-gpu=power.draw,temperature.gpu,\
utilization.gpu,utilization.memory --format=csv -l 1 \
> /tmp/gpu_monitor_tp2_conc${conc}.csv &
MONITOR_PID=$!
./vllm-bench --backend vllm --base-url http://127.0.0.1:8000 \
--model /root/autodl-tmp/models/Qwen/Qwen2.5-7B-Instruct \
--dataset-name random \
--random-input-len 1024 --random-output-len 128 \
--num-prompts $N --max-concurrency $conc
kill $MONITOR_PID 2>/dev/null
done
5.2 整体性能
注:conc=64/128/200 为同一轮连续压测(conc 递增,服务持续运行),conc=1 为单独一轮。conc=200 本次实测数据与第二章 conc=200 存在差异(本次 500 样本、20.66s,第二章 1000 样本、162.91s),推测因第二章数据为独立测试而本次为阶梯序列末轮,scheduler 已充分预热。
| 指标 | conc=1 | conc=64 | conc=128 | conc=200 |
|---|---|---|---|---|
| Total Token 吞吐 (tok/s) | 720.74 | 11315.95 | 26628.96 | 27826.71 |
| Output Token 吞吐 (tok/s) | 80.22 | 1259.48 | 2963.84 | 3097.15 |
| Benchmark 耗时 (s) | 478.66 | 50.81 | 21.59 | 20.66 |
| Mean TPOT (ms) | 12.07 | 44.28 | 35.66 | 52.70 |
| Mean TTFT (ms) | 62.17 | 782.36 | 524.91 | 776.69 |
| Mean E2EL (ms) | 1595.33 | 6406.35 | 5053.58 | 7469.61 |
Steady-State 指标
| 指标 | conc=64 | conc=128 | conc=200 |
|---|---|---|---|
| Request 吞吐 (req/s) | 9.22 | 23.80 | 18.75 |
| Output Token 吞吐 (tok/s) | 1219.27 | 3131.35 | 3102.84 |
| Mean TTFT (ms) | 830.52 | 454.58 | 605.99 |
| Mean TPOT (ms) | 47.08 | 35.53 | 50.22 |
性能分析
TPOT 异常下降(conc=128 反而比 conc=64 低):
| 并发度 | Mean TPOT | 相对 conc=1 倍数 | TPOT 增量 | 说明 |
|---|---|---|---|---|
| 1(裸算力) | 12.07ms | 1.0× | — | 双卡裸算力基线 |
| 64 | 44.28ms | 3.7× | 32ms | batch 形成但调度效率不高 |
| 128 | 35.66ms | 3.0× | 24ms | 🔽 反而下降 — batch 效率更优 |
| 200 | 52.70ms | 4.4× | 41ms | 开始过饱和 |
conc=128 的 TPOT 比 conc=64 更低,说明 TP=2 场景下 scheduler 需要更高的并发才能进入高效 batch 模式。这与 TP=1 的行为完全不同(TP=1 中 TPOT 随并发单调递增)。
吞吐增长趋势:
| 并发度 | Output Token 吞吐 | 边际收益 | 累计收益 |
|---|---|---|---|
| 1 | 80 tok/s | — | 1× |
| 64 | 1259 tok/s | — | 15.7× |
| 128 | 2964 tok/s | +135% | 37.0× |
| 200 | 3097 tok/s | +4.5% | 38.6× |
吞吐在 conc=128 达到接近峰值(2964 tok/s),conc=200 仅增长 4.5%,已近饱和。
5.3 GPU 监控数据(nvidia-smi)
nvidia-smi 采样间隔 1s,过滤空闲期(GPU-Util=0%),双卡单独统计。
| 指标 | conc=1 | conc=64 | conc=128 | conc=200 |
|---|---|---|---|---|
| GPU0 功耗 avg (max) | 232W (238) | 215W (237) | 211W (227) | 217W (231) |
| GPU1 功耗 avg (max) | 219W (223) | 200W (222) | 199W (215) | 205W (219) |
| 双卡合计功耗 avg | 451W | 415W | 410W | 421W |
| GPU-Util avg | 98% | 100% | 100% | 99% |
| Mem-Util avg | 97% | 46% | 71% | 67% |
| 温度 avg | 55°C | 57°C | 54°C | 55°C |
与 TP=1 的 Mem-Util 对比
| 并发度 | TP=1 Mem-Util | TP=2 Mem-Util | 差异 |
|---|---|---|---|
| conc=1 | 100% | 97% | ≈ 一致,都 memory-bound |
| conc=64 | 46% ↓ | 46% ↓ | 拐点一致 |
| conc=128 | 51% | 71% ↑ | TP=2 Mem-Util 反而回升 → 不同行为 |
| conc=200 | 55% | 67% | TP=2 始终高于 TP=1 |
关键观察:TP=2 的 Mem-Util 在 conc=64 降至 46%(与 TP=1 一致),但 conc=128 反而回升至 71%,与 TP=1 持续下降的趋势相反。这解释了 conc=128 TPOT 比 conc=64 更低的异常现象。
功耗分析
| 并发度 | TP=1 单卡功耗 | TP=2 双卡合计 | 单卡平均 |
|---|---|---|---|
| conc=1 | 250W | 451W | 226W |
| conc=64 | 298W | 415W | 208W |
| conc=128 | 306W | 410W | 205W |
| conc=200 | 304W | 421W | 211W |
TP=2 单卡功耗远低于 TP=1(205
226W vs 250306W),每卡只处理 3.5B 参数,算力远未饱和。两张卡合计功耗 410~451W,而 TDP 合计 640W,利用率仅 64%~70%。
5.4 TP=1 vs TP=2 对比
| 对比维度 | TP=1 | TP=2 | 差异分析 |
|---|---|---|---|
| 裸算力 TPOT (conc=1) | 25ms | 12.07ms | 🔽 -52% |
| 裸算力 TTFT (conc=1) | 250ms | 62.17ms | 🔽 -75% |
| 峰值吞吐 | 660 tok/s (conc=128) | 3097 tok/s (conc=200) | 🚀 +369% |
| 峰值 TPOT | 163ms (conc=128) | 35.66ms (conc=128) | 🔽 -78% |
| TPOT 行为模式 | 随并发单调递增 | conc=128 反降 | 不同调度特征 |
| 最优性价比并发 | conc=64 (614 tok/s) | conc=128 (2964 tok/s) | TP=2 最佳点在更高并发 |
| 过饱和标志 | conc=200 吞吐下降 9% | conc=200 吞吐仅 +4.5% | TP=2 过饱和更温和 |
| TPOT 增量占比 (conc=200) | 90.5% | 77% | 🔽 TP=2 TPOT 增量占比更低(含合法计算增长 + 调度开销) |
5.5 图表分析

5.6 关键发现
- TP=2 吞吐远超 TP=1:峰值 3097 tok/s vs 660 tok/s(+369%),但这是两张卡合计
- TPOT 异常下降(conc=128 < conc=64):与 TP=1 单调递增完全不同,TP=2 需要更高并发才能进入高效 batch 模式
- conc=128 是 TP=2 的最佳性价比点:吞吐 2964 tok/s(峰值的 96%),TPOT 仅 35.66ms(最低点)
- TPOT 增量占比反而降低:conc=200 的 TPOT 增量占比约 77%,低于 TP=1 的 90.5%(注意:该增量包含 batch 增大带来的合法计算增长,不全是调度开销)
5.7 原始日志
================= Serving Benchmark Result =================
Successful requests: 300
Failed requests: 0
Maximum request concurrency: 1
Benchmark duration (s): 478.66
Total input tokens: 306592
Total generated tokens: 38400
Request throughput (req/s): 0.63
Output token throughput (tok/s): 80.22
Peak output token throughput (tok/s): 83.00
Peak concurrent requests: 2.00
Total token throughput (tok/s): 720.74
--------------------Time to First Token---------------------
Mean TTFT (ms): 62.17
Median TTFT (ms): 44.10
P99 TTFT (ms): 160.71
P90 TTFT (ms): 155.20
----------Time per Output Token (excl. 1st token)-----------
Mean TPOT (ms): 12.07
Median TPOT (ms): 12.07
P99 TPOT (ms): 12.11
P90 TPOT (ms): 12.09
--------------------Inter-token Latency---------------------
Mean ITL (ms): 12.07
Median ITL (ms): 12.08
P99 ITL (ms): 12.70
P90 ITL (ms): 12.36
---------------------End-to-end Latency---------------------
Mean E2EL (ms): 1595.33
Median E2EL (ms): 1576.75
P99 E2EL (ms): 1696.40
P90 E2EL (ms): 1689.94
============================================================
============ Steady-State Metrics =============
Concurrency threshold: >= 0.95 * 1 = 1
Window: 0.0s -> 478.7s (478.7s)
Observed peak concurrency: 1
Requests started in window: 300 / 300 (100.0%)
Requests completed in window: 299
-----------------------------------------------
Request throughput (req/s): 0.62
Output token throughput (tok/s): 80.22
Input token throughput (tok/s): 640.52
Total token throughput (tok/s): 720.74
-----------------------------------------------
Mean TTFT (ms): 62.17
Median TTFT (ms): 44.10
P99 TTFT (ms): 160.71
P90 TTFT (ms): 155.20
-----------------------------------------------
Mean TPOT (ms): 12.07
Median TPOT (ms): 12.07
P90 TPOT (ms): 12.09
P99 TPOT (ms): 12.11
================= Serving Benchmark Result =================
Successful requests: 500
Failed requests: 0
Maximum request concurrency: 64
Benchmark duration (s): 50.81
Total input tokens: 511016
Total generated tokens: 64000
Request throughput (req/s): 9.84
Output token throughput (tok/s): 1259.48
Peak output token throughput (tok/s): 2752.00
Peak concurrent requests: 127.00
Total token throughput (tok/s): 11315.95
--------------------Time to First Token---------------------
Mean TTFT (ms): 782.36
Median TTFT (ms): 392.59
P99 TTFT (ms): 3219.09
P90 TTFT (ms): 1879.11
----------Time per Output Token (excl. 1st token)-----------
Mean TPOT (ms): 44.28
Median TPOT (ms): 24.39
P99 TPOT (ms): 83.53
P90 TPOT (ms): 77.89
--------------------Inter-token Latency---------------------
Mean ITL (ms): 44.28
Median ITL (ms): 23.69
P99 ITL (ms): 270.11
P90 ITL (ms): 55.51
---------------------End-to-end Latency---------------------
Mean E2EL (ms): 6406.35
Median E2EL (ms): 3588.75
P99 E2EL (ms): 12414.67
P90 E2EL (ms): 11486.00
============================================================
============ Steady-State Metrics =============
Concurrency threshold: >= 0.95 * 64 = 61
Window: 0.0s -> 47.6s (47.6s)
Observed peak concurrency: 64
Requests started in window: 440 / 500 (88.0%)
Requests completed in window: 439
-----------------------------------------------
Request throughput (req/s): 9.22
Output token throughput (tok/s): 1219.27
Input token throughput (tok/s): 9449.77
Total token throughput (tok/s): 10669.04
-----------------------------------------------
Mean TTFT (ms): 830.52
Median TTFT (ms): 392.43
P99 TTFT (ms): 3219.63
P90 TTFT (ms): 2102.67
-----------------------------------------------
Mean TPOT (ms): 47.08
Median TPOT (ms): 43.22
P90 TPOT (ms): 77.93
P99 TPOT (ms): 83.60
================= Serving Benchmark Result =================
Successful requests: 500
Failed requests: 0
Maximum request concurrency: 128
Benchmark duration (s): 21.59
Total input tokens: 511016
Total generated tokens: 64000
Request throughput (req/s): 23.15
Output token throughput (tok/s): 2963.84
Peak output token throughput (tok/s): 3712.00
Peak concurrent requests: 247.00
Total token throughput (tok/s): 26628.96
--------------------Time to First Token---------------------
Mean TTFT (ms): 524.91
Median TTFT (ms): 468.75
P99 TTFT (ms): 1131.63
P90 TTFT (ms): 903.48
----------Time per Output Token (excl. 1st token)-----------
Mean TPOT (ms): 35.66
Median TPOT (ms): 36.20
P99 TPOT (ms): 37.21
P90 TPOT (ms): 36.93
--------------------Inter-token Latency---------------------
Mean ITL (ms): 35.66
Median ITL (ms): 34.41
P99 ITL (ms): 98.15
P90 ITL (ms): 39.61
---------------------End-to-end Latency---------------------
Mean E2EL (ms): 5053.58
Median E2EL (ms): 5058.74
P99 E2EL (ms): 5706.48
P90 E2EL (ms): 5519.99
============================================================
============ Steady-State Metrics =============
Concurrency threshold: >= 0.95 * 128 = 122
Window: 0.0s -> 15.9s (15.9s)
Observed peak concurrency: 128
Requests started in window: 379 / 500 (75.8%)
Requests completed in window: 378
-----------------------------------------------
Request throughput (req/s): 23.80
Output token throughput (tok/s): 3131.35
Input token throughput (tok/s): 24395.32
Total token throughput (tok/s): 27526.67
-----------------------------------------------
Mean TTFT (ms): 454.58
Median TTFT (ms): 449.21
P99 TTFT (ms): 1129.99
P90 TTFT (ms): 547.26
-----------------------------------------------
Mean TPOT (ms): 35.53
Median TPOT (ms): 36.47
P90 TPOT (ms): 36.98
P99 TPOT (ms): 37.31
================= Serving Benchmark Result =================
Successful requests: 500
Failed requests: 0
Maximum request concurrency: 200
Benchmark duration (s): 20.66
Total input tokens: 511016
Total generated tokens: 64000
Request throughput (req/s): 24.20
Output token throughput (tok/s): 3097.15
Peak output token throughput (tok/s): 3800.00
Peak concurrent requests: 299.00
Total token throughput (tok/s): 27826.71
--------------------Time to First Token---------------------
Mean TTFT (ms): 776.69
Median TTFT (ms): 625.87
P99 TTFT (ms): 1731.52
P90 TTFT (ms): 1495.49
----------Time per Output Token (excl. 1st token)-----------
Mean TPOT (ms): 52.70
Median TPOT (ms): 57.56
P99 TPOT (ms): 59.60
P90 TPOT (ms): 58.86
--------------------Inter-token Latency---------------------
Mean ITL (ms): 52.70
Median ITL (ms): 52.13
P99 ITL (ms): 154.89
P90 ITL (ms): 62.60
---------------------End-to-end Latency---------------------
Mean E2EL (ms): 7469.61
Median E2EL (ms): 8025.32
P99 E2EL (ms): 8968.65
P90 E2EL (ms): 8877.64
============================================================
============ Steady-State Metrics =============
Concurrency threshold: >= 0.95 * 200 = 190
Window: 0.0s -> 16.6s (16.5s)
Observed peak concurrency: 200
Requests started in window: 311 / 500 (62.2%)
Requests completed in window: 310
-----------------------------------------------
Request throughput (req/s): 18.75
Output token throughput (tok/s): 3102.84
Input token throughput (tok/s): 19228.31
Total token throughput (tok/s): 22331.15
-----------------------------------------------
Mean TTFT (ms): 605.99
Median TTFT (ms): 571.66
P99 TTFT (ms): 1731.22
P90 TTFT (ms): 769.80
-----------------------------------------------
Mean TPOT (ms): 50.22
Median TPOT (ms): 57.64
P90 TPOT (ms): 58.86
P99 TPOT (ms): 59.60
六、调度瓶颈深度分析
6.1 Nsight Systems Profiling
使用 Nsight Systems 对 TP=1、conc=64 场景进行 GPU timeline 采集,精确定位 decode step 中的时间分布。
采集方法
# 方案 B(attach 模式)
vllm serve /root/autodl-tmp/models/Qwen/Qwen2.5-7B-Instruct --tensor-parallel-size 1 &
nsys profile -t cuda,nvtx,osrt -o decode_profile --duration 30 -p $(pgrep -f "vllm serve")
# 另一终端发起压测
./vllm-bench --backend vllm --base-url http://127.0.0.1:8000 \
--model /root/autodl-tmp/models/Qwen/Qwen2.5-7B-Instruct \
--dataset-name random \
--random-input-len 1024 --random-output-len 128 \
--num-prompts 200 --max-concurrency 64
Timeline 整体视图(200ms 窗口)

截图对应约 200ms 时间窗口,完整覆盖约两个 decode step 周期。
| 观察项 | 说明 |
|---|---|
🟩 cudaEventSynchronize(绿色条) |
CPU 线程调用 cuEventSynchronize 等待 GPU 完成 |
🟫 epoll_wait(米色方块) |
EngineCore 线程进行 I/O 事件等待 |
🟫 sem_wait(棕色条) |
线程间信号量同步等待 |
| GPU HW 行(蓝色区域) | GPU 执行 kernel 的时间段 |
CUDA Stream 与 Kernel 分布

Stream 分工
| Stream | 总占比 | 组成 | 角色 |
|---|---|---|---|
| Default stream 7 | 93.6% | 24.6% Graphs + 68.9% Kernels + 6.4% Memory | 主力执行流 |
| Stream 22 | 6.0% | 99.8% Kernels + 0.2% Memory | 辅助流 |
GPU 时间分布(所有 Stream 合计)
| 类别 | 占比 | 含义 |
|---|---|---|
| Kernels | 70.9% | 单个 CUDA kernel 执行时间 |
| Graphs | 23.0% | CUDA Graph 执行(封装多个 kernel 减少 CPU launch 开销) |
| Memory | 6.1% | 数据搬运(Host-Device / Device-Device) |
识别的主要 Kernel
| Kernel 名称 | 类型 | 用途 |
|---|---|---|
cutlass::Kernel2<...s16816gemm_relu_bf16_256x128_32x3_tn_align8> |
CUTLASS GEMM + ReLU | MLP gate/up projection |
ampere_bf16_s16816gemm_bf16_256x128_lda8_f2f_stages_32x3_tn |
CUTLASS GEMM | MLP down projection |
flash::flash_fwd_splitkv_kernel<128,64,128,4,...> |
Flash Attention | Self-attention |
triton_red... |
Triton Reduction | Attention 后处理 |
triton_p... |
Triton Pointwise | RoPE / LayerNorm |
注:CUTLASS kernel 的模板参数
bf16_256x128_32x3表示 tile size 256×128,stages=3,这是针对 RTX 4080 (sm_86) 优化过的配置。
生成时间分布图

截图显示约 200ms 窗口内 GPU 活动与 CPU 同步事件的对应关系。
分析结论
解码后的执行模式
约 90~100ms(一个完整 decode step 周期)
┌──────────────────────────────────────────┐
│ GPU kernel 序列(连续执行) │
│ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │
│ │ K │ │ K │ │ K │ │ K │ │ K │ ... │
│ └───┘ └───┘ └───┘ └───┘ └───┘ │
│ │
│ ▼ cudaEventSynchronize(CPU 等 GPU) │ ← 🟩 绿色条
│ │
│ 调度间隔(~10-20ms) │
│ ├─ epoll_wait(I/O 等待) │
│ └─ sem_wait(线程同步) │
│ │
│ 下一轮 decode kernel 序列开始 │
└──────────────────────────────────────────┘
核心发现
发现一:CPU-GPU 同步开销
cudaEventSynchronize 的存在表明 vLLM 的异步调度中存在残留的 CPU-GPU 同步点。vLLM V1 默认启用了异步调度(async_scheduling=True),允许 CPU 准备第 N+1 步时 GPU 执行第 N 步。但实际 Nsight 数据中仍可见 cudaEventSynchronize 阻塞,说明异步重叠不彻底:
- GPU 执行当前 batch 的 kernel
- CPU 在
cudaEventSynchronize处等待 GPU 完成部分操作(如准备下一批输入数据) - 此后 CPU 执行调度逻辑(
epoll_wait+sem_wait) - GPU 开始下一批 kernel
vLLM 官方也承认 V1 的异步支持是 “retrofitted behavior and hacks”,MRV2(新一代 Model Runner)才真正以异步流水线为原生设计。
发现二:混合执行方式(CUDA Graph + 传统 Launch)
GPU 执行时间中:
- 23.0% 来自 CUDA Graph(预编译的 kernel 序列)
- 70.9% 来自传统单个 kernel launch
vLLM 已在使用 CUDA Graph 优化 attention 等固定路径,但大部分 GEMM 和 element-wise 操作仍以传统方式 launch。
发现三:算子组成
| 算子 | 来源 | 对应模型组件 |
|---|---|---|
cutlass::Kernel2<...gemm_relu...> |
CUTLASS | MLP gate/up projection |
ampere_bf16_s16816gemm... |
CUTLASS | MLP down projection |
flash::flash_fwd_splitkv_kernel |
FlashAttention v2 | Self-attention |
triton_red... / triton_p... |
Triton | Softmax / RoPE / LayerNorm |
可能的开销来源
| 现象 | 可能原因 |
|---|---|
cudaEventSynchronize |
异步调度中的残留同步点,CPU 等待 GPU 完成部分操作 |
epoll_wait |
EngineCore 线程等待 I/O 事件(网络 / IPC) |
sem_wait |
vLLM 内部线程间同步(scheduler worker 通信) |
与实验结果交叉验证
| 指标 | 阶梯实验(conc=64) | Nsight 观察 |
|---|---|---|
| Mean TPOT | 90.22ms | |
| 同步/调度事件 | — | cudaEventSynchronize + epoll_wait + sem_wait 占据 GPU 空闲间隙 ✅ |
| GPU-Util | 98% | GPU kernel 条连续无间隙 ✅ |
6.2 Grafana 监控截图
TP=2 conc=200 压测过程中的实时监控数据:

七、总结与下一步
7.1 TP=1 vs TP=2 瓶颈性质对比
| 场景 | 瓶颈性质 | 核心证据 |
|---|---|---|
| TP=1 | conc≥64 后 硬件近饱和 | 功耗 298 |
| TP=2 | 始终 scheduler-bound | 单卡功耗仅 205 |
TP=1 经过足够并发可以压满单卡算力。TP=2 则永远无法压满 GPU——每卡只处理 3.5B 参数,scheduler 开销增长始终快于 batch 合并带来的算力收益。
7.2 根本原因
从 Nsight Systems profiling 确认,vLLM V1 的异步调度中存在残留的 CPU-GPU 同步点(cudaEventSynchronize)。虽然 vLLM 默认启用异步调度让 CPU 和 GPU 部分重叠,但实际执行中仍有明显的同步等待:
提交 kernel → cudaEventSynchronize(CPU 等 GPU 完成部分操作)→ CPU 调度 → 提交下一批
↑
GPU 空闲,等 CPU
这导致 CPU 和 GPU 无法完全流水线化,是当前系统性能瓶颈的根本原因。官方也在新一代 Model Runner(MRV2)中将异步流水线作为原生设计来彻底解决此问题。
7.3 优化方向评估
| 方案 | 可行性 | 说明 |
|---|---|---|
--num-scheduler-steps |
❌ vLLM V1 不兼容 | 官方说明 |
--max-num-seqs 512 |
⏳ 可试 | 放宽默认 256 上限,可能小幅改善 |
| vLLM MRV2(下一代 Model Runner) | ⏳ 开发中 | 原生异步流水线设计,无 CPU 同步点 |
| 换用 SGLang | ⭐ 最高收益 | 异步调度架构,从根本上减少 cudaEventSynchronize 等待 |
| 换用 TensorRT-LLM | ⭐ 高收益但高投入 | 异步流水线执行,CPU 与 GPU 可重叠 |
7.4 最终结论
当前系统的性能天花板来自 GPU 利用率不足,根因是 vLLM V1 异步调度中的残留同步开销,而非 GPU 硬件瓶颈。
TP=2 虽然能将吞吐推到 TP=1 的 4.7×,但这只是双卡硬件翻倍的物理红利。每张 GPU 的实际利用率仅 64%~71%,算力远未饱和。在不改变推理框架的前提下,通过调参最多只能获得 5%~10% 的边际提升。要突破这个天花板,需要更彻底的异步流水线架构(SGLang / TensorRT-LLM 或 vLLM 新一代 MRV2)从执行模型层面解决 CPU-GPU 串行化问题。
7.5 待办事项清单
- Warmup 验证:并发是关键,推荐
vllm-bench --num-prompts 50 --max-concurrency 50(见第三章) - DCGM 监控完善:GeForce 卡不支持 PROF 指标
- 低并发对比:并发=1 下 TPOT=25ms(见第一章)
- Nsight Systems profiling:发现
cudaEventSynchronize残留同步开销,CPU 与 GPU 无法完全流水线化(见第六章) - 单卡计算受限实验:TP=1 阶梯并发,找到最佳性价比拐点 conc=64,Roofline 验证 GPU 硬件不是瓶颈(见第四章)
- 双卡计算受限实验:TP=2 阶梯并发,发现 TPOT 异常下降和 Mem-Util V 型走势(见第五章)