动手 2:吞吐基准
谁该读这一篇? 准备拿数据说话(vs vLLM、vs 上代版本)的工程师 / 架构师。 前置阅读:
01-setup.md。 耗时: 1-2 小时(含跑数据) 学完能: 1. 用bench_serving.py跑一组可重复的吞吐数字; 2. 解读输出里的 4 个核心 metric; 3. 区分"在线吞吐"和"离线吞吐"基准的差异; 4. 横向对比同配置 vLLM; 5. 避免常见的"benchmark 骗自己"陷阱。
1. 基准脚本概览
SGLang 自带几个基准入口:
| 脚本 | 模式 | 用途 |
|---|---|---|
bench_serving.py |
在线 | 模拟客户端发请求,最贴近生产 |
bench_offline_throughput.py |
离线 | 单进程灌满,不模拟网络 |
bench_one_batch.py |
单 batch | 微基准,看一个 batch 的耗时 |
bench_one_batch_server.py |
单 batch via HTTP | 同上但走 server |
生产对比看 bench_serving.py。其它适合研究 / 调参。
2. 起 server
python -m sglang.launch_server \
--model-path Qwen/Qwen2.5-7B-Instruct \
--port 30000 \
--tp 1 \
--enable-metrics
跑基准前让 server 闲置 30 秒(避免冷启动数据污染)。
3. 跑在线基准
最常用:
python -m sglang.bench_serving \
--backend sglang \
--port 30000 \
--model Qwen/Qwen2.5-7B-Instruct \
--tokenizer Qwen/Qwen2.5-7B-Instruct \
--num-prompts 1000 \
--request-rate 8 \
--dataset-name sharegpt \
--dataset-path /path/to/ShareGPT_V3_unfiltered_cleaned_split.json
参数说明:
| 参数 | 含义 |
|---|---|
--backend |
sglang / vllm / trt-llm / openai |
--num-prompts |
总请求数 |
--request-rate |
每秒发多少(poisson) |
--dataset-name |
sharegpt / random / hellaswag / mmlu |
--dataset-path |
数据集路径 |
--max-concurrency |
最大并发上限 |
ShareGPT 数据集需要自己下载:
wget https://huggingface.co/datasets/anon8231489123/ShareGPT_Vicuna_unfiltered/resolve/main/ShareGPT_V3_unfiltered_cleaned_split.json
4. 输出解读
============ Serving Benchmark Result ============
Backend: sglang
Traffic request rate: 8.0
Successful requests: 1000
Benchmark duration (s): 127.45
Total input tokens: 183245
Total generated tokens: 93412
Request throughput (req/s): 7.85
Input token throughput (tok/s): 1438.21
Output token throughput (tok/s): 733.04
Total token throughput (tok/s): 2171.25
---------------Time to First Token----------------
Mean TTFT (ms): 156.21
Median TTFT (ms): 132.45
P99 TTFT (ms): 582.13
-----Time per Output Token (excl. first token)----
Mean TPOT (ms): 22.34
Median TPOT (ms): 20.12
P99 TPOT (ms): 45.32
---------------Inter-token Latency----------------
Mean ITL (ms): 22.34
Median ITL (ms): 20.12
P99 ITL (ms): 45.32
==================================================
核心 4 个 metric:
- TTFT:首 token 时延(用户感知"卡了多久")。
- TPOT:每后续 token 间隔(用户感知"打字速度")。
- Throughput:req/s 和 token/s(容量指标)。
- P99:高分位(SLO 谈判用,p99 > 1s 是黄牌)。
5. 对比基线
跑两套同样的脚本,对比:
# A: SGLang
python -m sglang.bench_serving --backend sglang ... > sglang.log
# 切到 vLLM
# (重起 vllm server 同模型同 GPU)
python -m sglang.bench_serving --backend vllm ... > vllm.log
# 比对
diff <(grep -E "Throughput|TTFT|TPOT" sglang.log) <(grep -E "Throughput|TTFT|TPOT" vllm.log)
重要:变量必须控制好:
- 同 GPU、同模型、同量化
- 同 prompt 数据集(不要一个 sharegpt 一个 random)
- 同 request rate
- server warmup 时间一致
- 关掉所有不相关进程
6. Sweep 参数
业务上线前要扫一遍 request_rate:
for rate in 1 2 4 8 16 32 64; do
python -m sglang.bench_serving \
--backend sglang \
--num-prompts $((rate * 100)) \
--request-rate $rate \
... \
> result_$rate.log
done
画图:x = rate, y = TTFT p99 / TPOT mean / Throughput。 拐点(TTFT 突增的 rate)就是"单副本容量"。
7. 常见骗自己的陷阱
| 陷阱 | 矫正 |
|---|---|
| 用 random dataset 测 prefix cache | random 不可能有共享前缀;用 sharegpt |
| 第一次请求计入数据 | 加 warmup 阶段 |
| Request rate < 实际并发 | 真实业务并发更高;按 max_concurrency 算 |
| 只看 mean,忽略 p99 | SLO 看 p99,mean 会掩盖长尾 |
| 输出长度太短 | TPOT 没体现;让 output ≥ 100 token |
| 跑一次就发表 | 跑 3-5 次取中位数 |
| 量化版本不一致 | fp8 vs bf16 不可比 |
| 不同 batch_size 不一致 | 控制 max_running_requests |
| 不同 attention backend | 显式 --attention-backend |
8. 离线基准(吞吐上限)
python -m sglang.bench_offline_throughput \
--model-path Qwen/Qwen2.5-7B-Instruct \
--dataset-path ShareGPT.json \
--num-prompts 1000 \
--random-input-len 1024 \
--random-output-len 256
不走 HTTP,直接 Python 调 engine API,吞吐上限值(peak)。 和在线基准对比:差值约 5-15%,超过说明 HTTP / TM 是瓶颈。
9. 单 batch 微基准
研究单层耗时、kernel 选择时用:
python -m sglang.bench_one_batch \
--model-path ... \
--batch-size 32 \
--input-len 1024 \
--output-len 1
输出:单步 forward 时延(ms),可以拆 attention / MLP / sampler。
10. 真实业务基准(推荐)
最有说服力:用业务真实流量回放。
# 1) 业务侧采样 1 万条真实请求 → traces.jsonl
# 2) 写个客户端按真实时间间隔重放
python replay.py --traces traces.jsonl --target http://localhost:30000
# 3) 看 server 端 metrics 累计
curl http://localhost:30000/metrics > metrics_after.txt
这种基准最接近"上线后会发生什么"。
11. 结果汇报模板
## SGLang vs vLLM benchmark — 2026-05-29
### 硬件 / 配置
- GPU: 4× H100 80GB
- Model: Llama-3.1-70B-Instruct (FP8)
- TP=4, --max-running-requests=128
- Dataset: ShareGPT V3
- Request rate: 16 QPS
### 关键数字(中位数 of 3 次跑)
| | TTFT mean | TTFT p99 | TPOT mean | Throughput (tok/s) |
|--------|-----------|----------|-----------|--------------------|
| SGLang | 245 ms | 681 ms | 31 ms | 4521 |
| vLLM | 312 ms | 832 ms | 33 ms | 4123 |
| Δ | -21% | -18% | -6% | +10% |
### 解读
- SGLang TTFT 下降 21%, 主要因为 cache_hit_rate 56% vs vLLM 12%(sharegpt 有共享前缀)。
- TPOT 接近持平(attention kernel 都是 FlashInfer)。
- Throughput 提升 10%。
### 风险
- 业务真实 prefix 复用度未知;可能比 sharegpt 高或低。
- vLLM v1 prefix caching 也在改进,差距可能缩小。
12. 小结
- 在线基准用
bench_serving.py+ sharegpt 数据集;生产对比看它。 - 核心 4 个 metric:TTFT / TPOT / Throughput / p99。
- 控制变量(同 GPU / 同模型 / 同量化 / 同 warmup)。
- Sweep request rate 找拐点。
- 真实业务流量回放最可信。
- 汇报时给 中位数 + 多次跑数据 + 解读,不要单次数字。
13. 自检
- 跑 random dataset 和 sharegpt dataset,cache_hit_rate 会一样吗?
答案
**完全不一样**。 Random dataset:每 prompt 完全独立随机生成,几乎没有共享前缀,cache_hit_rate ~0%。 ShareGPT:真实用户对话,多个对话共享 system prompt("You are ChatGPT...")或频繁重复 user 引用上文,cache_hit_rate 30-60%。 后果:用 random 测的吞吐数字不能代表 cache-aware 引擎(SGLang)的真实表现,会**严重低估** SGLang 优势;测 vLLM 也不公平(PagedAttention cache 失效场景)。 选 dataset:业务对照测,sharegpt 是 baseline,自己业务真实流量回放最可信。- P99 TTFT 比 mean 大 4× 是什么信号?
答案
严重长尾。原因可能: (1) **prompt 长度分布长尾**:大多数 prompt 短(mean TTFT 200ms),少数 4k+ 长 prompt 触发 chunked prefill 多轮分摊(p99 TTFT 800ms)。 (2) **cache miss 分布不均**:部分请求命中长 prefix(mean 低),部分完全 miss(p99 高)。 (3) **抢占 / 排队**:高负载时排队请求 TTFT 远高于即时调度的。 (4) **CUDA Graph fallback 突发**:碰到 LoRA / spec 边缘 case fallback 多 → 个别请求 TPOT 拖累。 p99/mean = 2-3× 是正常长尾,4× 偏高,5× 是黄牌;SLO 看 p99 而不是 mean。- 在线基准比离线慢 5-15% 的差额去哪了?
答案
差额组成: (1) **HTTP server**:FastAPI + Granian 解析 + 路由 5-10 μs/请求。 (2) **TokenizerManager**:tokenize + ReqState 维护 + ZMQ 发送 0.5-2 ms/请求。 (3) **DetokenizerManager**:增量 detokenize + ZMQ 回流 0.5-2 ms/请求。 (4) **SSE 流式回写**:每 token 走一次 HTTP chunk + 网络栈。 (5) **客户端开销**:openai SDK / curl 本身耗时。 离线基准(`bench_offline_throughput`)直接 Python 调 Engine API,跳过这些。 差额超 20% 说明 HTTP / TM 是真正瓶颈,要排查(开 multi worker / 用 Granian / 检查网络)。- 怎么判断我跑的是单副本容量上限?
答案
做 **sweep**:固定 num_prompts,扫 request_rate 1, 2, 4, 8, 16, 32, 64, 128... 画 (rate vs TTFT_p99) 曲线,找拐点: - 拐点前:TTFT 随 rate 线性微涨(GPU 还有余量)。 - 拐点:TTFT 突跳几倍(开始排队)。 - 拐点后:TTFT 指数涨(队列堆积)。 拐点的 rate 就是单副本上限 QPS。 也看 `waiting_queue_size` 指标:稳态 < 10 是没饱和,持续 > 50 是过载,突然涨是拐点。 单副本上限 = 拐点 × 0.8 留余量。- 我说 SGLang 比 vLLM 快 30%,怎么证明?
答案
严格证明要: (1) **同硬件**:同型号 GPU、同 CPU、同网络。 (2) **同模型**:同 checkpoint、同量化、同 attention backend(可能的话)。 (3) **同 dataset**:sharegpt 或业务真实回放。 (4) **同 request_rate**:扫 sweep,对比同 rate 下的指标。 (5) **同 warmup**:两边 server 起来后跑 30s 灌满 cache。 (6) **重复 3-5 次取中位数**:单次跑有 5-10% noise。 (7) **报告 p50 + p99**:不只 mean。 (8) **指标对齐**:用同 prometheus 指标定义。 报告里给出(rate, TTFT_p99, TPOT_mean, Throughput)四元组每组三次跑的中位数,列出 cache_hit_rate,给业务背景,让人能复现。 说"快 30%"前先验证 cache 命中率是否真有差距,是不是 dataset 选偏了。14. 下一步
03-debug-scheduler.md— 调试 scheduler。04-custom-frontend.md— 写自定义 DSL。08-production-deployment/04-slo.md— SLO 与容量。- 源码:
bench_serving.py。