L20 单卡跑 Qwen2.5-7B 的一次推理压测验收

6 阅读9分钟

目录

背景

这次不是故障排查,而是一次 L20 单卡推理交付验收。目标很简单:确认这台 NVIDIA L20 48GB 单卡服务器能不能稳定跑 Qwen2.5-7B-Instruct,性能是否在正常区间,业务侧该怎么设置并发和上下文长度。

测试环境:

GPU: NVIDIA L20 48GB
模型: Qwen2.5-7B-Instruct
推理框架: vLLM 0.6.6
计算精度: BF16
操作系统: Ubuntu 22.04.4 LTS
GPU Driver: 550.163.01
CUDA: 12.4
cuDNN: 9.1.0
NCCL: 2.21.5
Python: 3.10.12
PyTorch: 2.5.1+cu124
模型权重显存占用: 14.25 GB

vLLM 服务启动命令:

python3 -m vllm.entrypoints.openai.api_server \
  --model /data/Qwen2___5-7B-Instruct \
  --dtype bfloat16 \
  --max-model-len 16384 \
  --gpu-memory-utilization 0.85 \
  --port 8000 \
  --served-model-name Qwen2.5-7B-Instruct

L20 的硬件标称值:

显存: 48 GB GDDR6 ECC
显存带宽: 864 GB/s
FP16 Tensor Core 算力: 119.5 TFLOPS
TDP / 最大功耗: 275W / 316W

这类验收不能只跑一个 curl 看能不能返回。能返回只能说明服务活着,不代表容量够、不代表长文本能用,也不代表硬件没有降频。

现象

压测下来,结论是:硬件健康,推理服务正常,单卡可交付。但有一个很明确的边界:L20 单卡跑 Qwen2.5-7B,短输入在线场景能用,长文本和高并发要控住。

核心结果:

测试项关键指标结果判断
离线吞吐输出吞吐2173 tokens/s正常
在线并发TTFT,10 req/s393 ms良好
并发容量极限吞吐6.7 req/s符合单卡预期
长文本8192 输入 TTFT55 s不能当实时交互用
GPU 算力FP16 实测116.2 TFLOPS,97%优秀
显存带宽实测657 GB/s,78%正常
温度功耗满载峰值温度78℃,无降频健康

真正需要给业务说明的是两点:

1. 短输入场景,建议把单卡并发控制在 10 req/s 以内;
2. 2K 以上长 prompt 会明显拉高首字延迟,4K/8K 不适合在 5 req/s 下做实时交互。

排查过程

1. 先跑离线吞吐,确认 GPU 峰值生成能力

离线吞吐测的是 GPU 满负载时的生成能力,适合判断“这张卡火力够不够”。

命令:

python3 /tmp/benchmark_throughput.py \
  --model /data/Qwen2___5-7B-Instruct \
  --dtype bfloat16 \
  --num-prompts 200 \
  --input-len 512 \
  --output-len 512

结果:

请求吞吐: 4.25 req/s
输出 token 吞吐: 2173 tokens/s
总 token 吞吐: 4347 tokens/s
成功率: 200/200

这个结果在 7B 模型 + L20 + BF16 的正常区间。离线批处理、批量标注、日志分析这类任务可以参考这个数。

2. 再跑在线并发,看用户体感

在线压测看的是服务被持续请求时的延迟和吞吐。重点不是总 tokens/s,而是 TTFT,也就是用户发问后多久看到第一个 token。

命令:

python3 /tmp/benchmark_serving.py \
  --backend openai-chat \
  --model Qwen2.5-7B-Instruct \
  --tokenizer /data/Qwen2___5-7B-Instruct \
  --host localhost \
  --port 8000 \
  --endpoint /v1/chat/completions \
  --num-prompts 200 \
  --request-rate 10 \
  --dataset-name random \
  --random-input-len 512 \
  --random-output-len 512

结果:

请求吞吐: 6.04 req/s
输出 token 吞吐: 659 tokens/s
平均 TTFT: 393 ms
P99 TTFT: 1016 ms
平均 TPOT: 124 ms

10 req/s 下平均首字 0.4 秒,P99 约 1 秒,在线交互体验可以接受。

3. 做并发阶梯,找性能拐点

单点压测容易误导。只测 10 req/s,不知道继续加压后什么时候排队。并发阶梯就是为了找拐点。

命令:

for rate in 1 5 10 20 50; do
  echo "=== request-rate: $rate ==="
  python3 /tmp/benchmark_serving.py \
    --backend openai-chat \
    --model Qwen2.5-7B-Instruct \
    --tokenizer /data/Qwen2___5-7B-Instruct \
    --host localhost \
    --port 8000 \
    --endpoint /v1/chat/completions \
    --num-prompts 200 \
    --request-rate $rate \
    --dataset-name random \
    --random-input-len 512 \
    --random-output-len 512 \
    2>&1 | grep -E "Request throughput|Output token throughput|Mean TTFT|P99 TTFT"
done

结果:

请求速率实际吞吐 req/s输出 tokens/s平均 TTFTP99 TTFT
11.07111118 ms196 ms
54.23465165 ms325 ms
106.04641410 ms1016 ms
206.707352923 ms5357 ms
506.717375698 ms10452 ms

这里很清楚:极限吞吐在 6.7 req/s 左右,超过 20 req/s 后吞吐不再增长,只是排队变长。rate=10 时首字还是 0.4 秒,rate=20 直接跳到 2.9 秒。

容量结论可以直接写:

L20 单卡跑 Qwen2.5-7B,安全工作区间建议 ≤ 10 req/s。
超过这个值后,用户明显感到首字延迟变长。

4. 跑长文本,看 prefill 压力

自动驾驶、检索增强、长上下文问答这类场景,prompt 往往不短。只测 512 token 不够,需要看输入变长后首字延迟怎么变。

命令:

for input_len in 512 2048 4096 8192; do
  echo "=== input_len: $input_len ==="
  python3 /tmp/benchmark_serving.py \
    --backend openai-chat \
    --model Qwen2.5-7B-Instruct \
    --tokenizer /data/Qwen2___5-7B-Instruct \
    --host localhost \
    --port 8000 \
    --endpoint /v1/chat/completions \
    --num-prompts 100 \
    --request-rate 5 \
    --dataset-name random \
    --random-input-len $input_len \
    --random-output-len 512 \
    2>&1 | grep -E "Request throughput|Output token throughput|Mean TTFT|P99 TTFT"
done

结果:

输入长度吞吐 req/s输出 tokens/s平均 TTFTP99 TTFT
5123.443430.15 s0.29 s
20482.293304.2 s7.5 s
40961.3016520.3 s42.3 s
81920.6610455.1 s111.3 s

长文本瓶颈很明显。输入越长,prefill 计算越重,首字延迟非线性上升。5 req/s 下,2048 token 已经到 4 秒,4096/8192 基本不能当实时服务。

这里给业务的口径要直接:

512 token: 在线体验优秀
2048 token: 勉强可用,需看业务是否接受 4s 首字
4096 token 以上: 不适合 5 req/s 实时交互,必须降并发或横向扩展

5. 看硬件算力和显存带宽

推理服务数据正常,还要确认硬件本身没有问题。L20 标称 FP16 Tensor Core 算力 119.5 TFLOPS,显存带宽 864 GB/s。

FP16 matmul 测试:

python3 -c "
import torch, time
N = 8192
a = torch.randn(N, N, dtype=torch.float16, device='cuda')
b = torch.randn(N, N, dtype=torch.float16, device='cuda')
for _ in range(10): c = torch.matmul(a, b)
torch.cuda.synchronize()
start = time.time()
for _ in range(50): c = torch.matmul(a, b)
torch.cuda.synchronize()
elapsed = time.time() - start
tflops = 2 * N**3 * 50 / elapsed / 1e12
print(f'实测 FP16: {tflops:.1f} TFLOPS / 理论峰值: 119.5 TFLOPS')
"

结果:

FP16 算力: 116.2 TFLOPS / 119.5 TFLOPS,达成率 97%

显存带宽测试:

python3 -c "
import torch, time
n = 256*1024*1024
a = torch.randn(n, dtype=torch.float32, device='cuda')
for _ in range(5): b = a * 2.0
torch.cuda.synchronize()
start = time.time()
for _ in range(50): b = a * 2.0
torch.cuda.synchronize()
elapsed = time.time() - start
bw = n * 4 * 2 * 50 / elapsed / 1e9
print(f'实测带宽: {bw:.0f} GB/s / 理论值: 864 GB/s')
"

结果:

显存带宽: 657 GB/s / 864 GB/s,达成率 78%

L20 是 GDDR6 显存,实测带宽达到标称值 75%~80% 算正常。这个结果没有问题。

6. 观察温度、功耗和频率

满载下硬件能不能稳住,也要看。压测时另开窗口采样:

while true; do
  echo "$(date '+%H:%M:%S') $(nvidia-smi --query-gpu=utilization.gpu,memory.used,temperature.gpu,power.draw --format=csv,noheader)"
  sleep 2
done | tee /tmp/gpu_health.log

本次满载约 4 分钟,结果:

GPU 利用率: 78%~100%
核心温度: 69~78℃
功耗: 293~328 W
核心频率: 全程 2520 MHz

核心频率没有掉,温度压在 78℃,没有热降频。功耗瞬时冲到 328W 属于短时波动,整体散热和供电正常。

根因判断

这次验收没有发现硬件异常,也没有发现 vLLM 服务不可用的问题。

真正的容量边界在两个地方:

1. 单卡在线吞吐上限约 6.7 req/s;
2. 长文本 prefill 会显著拉高 TTFT。

所以验收结论不是“这台卡能无限撑在线服务”,而是:

L20 单卡跑 Qwen2.5-7B-Instruct,短输入在线服务可交付;
建议业务并发控制在 10 req/s 以内;
长文本场景需要降并发、chunked prefill 或多机分摊。

解决方案

交付建议分三块。

第一,在线并发要限流。单台 L20 跑 Qwen2.5-7B,业务侧建议控制在 10 req/s 以内。超过这个值,吞吐不会明显增加,只会排队,用户首字延迟会变差。

第二,长文本要单独处理。输入超过 2048 token 后,5 req/s 下 TTFT 已经到 4 秒;4096/8192 token 不适合实时交互。可选方案:

1. 降低并发;
2. 启用 --enable-chunked-prefill;
3. 多卡/多机横向扩展;
4. 业务侧限制 prompt 长度。

第三,max-model-len 不要盲目拉太大。设置过小会拒绝长请求,设置过大会浪费 KV Cache 显存。应该按真实业务最长输入 + 输出 token 来定。

这次启动参数里 --max-model-len 16384 可以覆盖 8K 输入测试,但业务如果长期只跑短 prompt,可以根据实际情况收紧,减少无效 KV Cache 预留。

经验总结

推理验收不能只看“服务能不能返回”。最少要看三类数据:

1. 峰值吞吐:GPU 火力够不够;
2. 在线延迟:用户首字等待多久;
3. 拐点:并发继续加上去后,什么时候开始排队。

这次最有价值的不是 2173 tokens/s,而是并发阶梯和长文本测试。它们直接决定能不能给业务方一个明确口径:

短输入:≤10 req/s 可以交付;
长输入:2K 以上开始明显变慢,4K/8K 不能按实时交互承诺。

硬件指标也要一起看。FP16 算力 97%、显存带宽 78%、满载无降频,说明这台 L20 本身没有缩水。后续如果业务觉得慢,优先看模型、并发、输入长度和 vLLM 参数,而不是先怀疑硬件。

还有一个边界要说清楚:L20 是 PCIe + GDDR6 卡,适合推理,不适合拿来做大规模训练主力。多卡训练还要单独验 NCCL、PCIe、网络带宽。本次报告是单卡推理验收,不覆盖多卡通信能力。

适用场景

这篇复盘适合下面这些场景:

  • NVIDIA L20 单卡交付验收;
  • Qwen2.5-7B、Llama 7B/8B、同级别 7B 模型推理压测;
  • vLLM OpenAI API Server 在线服务压测;
  • 需要给业务方确认单卡可承载并发;
  • 需要判断短 prompt 和长 prompt 的性能边界;
  • 需要验证 GPU 算力、显存带宽、温度功耗是否正常;
  • 需要写一份“能不能交付、能撑多少并发、长文本有没有风险”的验收结论。