离线模式与服务模式的批构成差异与结论边界
在 **** 的 ****(8 卡,单卡 64GB HBM,卡间互联 392 GB/s)上用 SGLang 起服务。离线跑一次 bench_offline_throughput,把全部请求一次性交给引擎,得到的吞吐高、TTFT 低;换成 bench_serving 走同一个模型、同一组 --max-running-requests,吞吐低于离线值,ITL 的 P95 抬升。反向的情形同样存在:线上服务的稳态吞吐达标,改回离线一次性提交,却因峰值显存不足而启动失败。两次测量的硬件相同,差异来自批怎么构成、请求怎么到达。离线模式把请求当成一个静态批次处理,服务模式按到达逐个拼入动态批次,两者的调度与显存管理不同,同一模型命中的算子路径也可能不同,一处的数字不能搬到另一处。
两种模式的批构成与调度
离线模式的请求一次性提交,批次构成在一次前向内固定,没有到达过程与排队。SGLang 的 benchmark 文档把三种工具区分得清楚:bench_offline_throughput 直接在进程内起 Engine,不走 HTTP 服务端,测的是最大可达吞吐;bench_serving 是异步 HTTP 客户端,按受控速率与可配置并发向运行中的服务发请求,测的是在线指标;bench_one_batch 直接调 ModelRunner、跳过 scheduler,用固定静态批,因为没有动态 batching,真实服务能处理的 batch size 在这里可能直接 OOM。
服务模式走连续批处理(continuous batching),每一步动态拼入与退出请求。传统静态 batching 以批内最大序列长度对齐、浪费算力,连续批处理把粒度做细,代价是调度复杂度与批内序列长度的方差。
| 维度 | 离线模式 | 服务模式 |
|---|---|---|
| 请求到达 | 一次性提交,无到达过程 | 按速率到达,有排队 |
| 批构成 | 静态批,批内固定 | continuous batching,每步动态拼入/退出 |
| 调度 | 直接跑 Engine,批构成不受调度约束 |
scheduler 决定批构成,--max-running-requests 限并发 |
| 显存管理 | 整批预占,峰值高 | KV 池按 block 动态分配,可抢占与重算 |
| 主要指标 | 吞吐、输入吞吐 | TTFT、TPOT / ITL、goodput |
| 长序列处理 | 常整段 prefill | chunked prefill 切块混批 |
参数与取值
| 参数 | 模式 | 作用 |
|---|---|---|
--max-running-requests |
服务 | 并发序列上限,直接决定批规模与 KV 占用 |
--max-prefill-tokens |
两者 | 单批 token 上限,压平 prefill 峰值 |
--chunked-prefill-size |
服务 | 长 prefill 切块、与 decode 混批(-1 关闭) |
--max-concurrency |
压测客户端 | 受控并发,服务模式取数用 |
--num-prompts |
压测 | 请求量,服务模式应显著大于并发数(无固定倍数要求) |
gpu_memory_utilization |
两者 | 显存预算比例,两种模式都要记 |
离线模式还有一个隐含参数:提交总量本身就是批规模,没有 --max-running-requests 的节流,峰值由这一批请求的总量决定。
显存与算子路径的连带差异
服务模式的 KV cache 按 block 分配,前缀缓存、抢占(preemption)与重算都在这一层发生;离线整批提交时,KV 占用按批内最长序列与全部请求量预占,峰值出现在 prefill。这决定同一批请求在两种模式下的显存曲线不同:离线看峰值,服务看池子的周转。chunked prefill 相关开关(--chunked-prefill-size、--max-prefill-tokens)在服务模式里用于把长 prefill 切块、与 decode 混批,离线模式常不启用或形同虚设,因此这一开关的收益只在服务模式里有对口负载。
同一模型在两种模式下命中的算子路径可能不同。动态 shape 与动态 batch 会掉出图捕获或后端 guard 的覆盖窗口,退回通用实现;这类回退只在服务模式的动态批下出现,离线静态批命中的是另一条路径。两种情况都要记 resolved_backend(本笔记自设的记录字段,非框架日志字段;真实凭证是启动日志里那一行后端选择):服务模式按「每步实际运行的序列数」记,离线模式按「批固定时的形状」记,两边的命中结果不能互换。
对照要固定哪些量
两种模式的指标不可比,除非对照的工况固定。要固定的量有三组:
- 请求集与长度分布:用
--random-input-len与--random-output-len给定,或指定数据集(--dataset-name sharegpt),确保两种模式跑的是同一批请求。 - 并发与批上限:服务模式的
--max-running-requests与压测客户端的--max-concurrency分别记录;离线模式的「并发」是批内序列数,与服务模式的含义不同。 - 采样与预算:
temperature/top_p/max_tokens与gpu_memory_utilization保持一致,否则输出长度分布与显存预算两边不同。
三组不齐时,两种模式的差值里混进了负载差异,无法归因到模式本身。
触发条件与现象
- 离线吞吐高、线上达不到:批构成从静态变成动态,显存从预占变成按需,排队与调度进入路径。
- 线上吞吐高、离线失败:离线整批预占的峰值显存超出预算。
- 同一优化一模式成立、另一模式不成立:chunked prefill、prefix caching 这类开关只在服务模式有对口负载。
- 指标口径错位:离线报吞吐,线上报 TTFT / ITL,把离线吞吐当成线上吞吐。
- 尾延迟抬升:服务模式的 ITL 受队列与混批影响,均值正常,P95 却高于阈值。
- 日志缺行:启动日志里的 KV 池块数与后端选择结果只在服务模式出现,离线没有服务端,这两行无从读到。
- 收益窗口错位:prefix caching 在离线一次性提交时命中率看似高,转到服务模式后批构成长短交错,命中率随之下滑。
定位动作
- 固定负载:同一请求集、同一
--random-input-len/--random-output-len分布、同一--max-running-requests,两种模式各跑一遍。 - 两种模式各取一套分段指标:prefill 段取
TTFT,decode 段取TPOT/ITL,不看端到端单值。 - 对照批构成:记录服务模式下实际运行的序列数与队列长度,确认
--max-running-requests是否被 KV 池压低。 - 对照显存:离线记峰值,服务记池子可用块数与周转。
- 核对命中路径:两种模式各记
resolved_backend,确认是否走了同一条实现。 - 不外推:离线结果只作离线结论,线上结论用线上工况复测。
# 服务模式:受控速率 + 可配置并发(字段名示意)
python -m sglang.bench_serving \
--backend sglang \
--max-concurrency 16 \
--num-prompts 80 \
--random-input-len 256 --random-output-len 32
# 离线模式:一次性提交,不走 HTTP 服务端(字段名示意)
python -m sglang.benchmark.offline_throughput \
--model-path <model-path> \
--num-prompts 80
服务模式的 --num-prompts 应显著大于并发数(无固定倍数要求),以便测到稳态;离线模式没有到达过程,提交量不构成这个约束,它测的是一次批的全部负载。
记录字段
mode: serving # serving / offline
batching_mode: continuous # continuous / static
arrival_pattern: # 固定速率 / 一次性提交
concurrency: --max-running-requests=<值>, --max-concurrency=<值>
batch_tokens: --max-prefill-tokens=<值>
chunked_prefill: <是否启用>, --chunked-prefill-size=<值>
workload:
input_len: <分布>
output_len: <分布>
num_prompts: <服务模式应显著大于并发数>
perf:
ttft: # prefill 段
tpot: # decode 段
itl_p95:
throughput:
memory:
offline_peak: # 离线峰值
serving_kv_pool: # 服务模式池子可用块数
path:
resolved_backend: # 两种模式各记一条
小结
离线模式与服务模式在批构成、调度与显存管理上不同,同一模型命中的算子路径也可能不同,一种模式下的指标不能直接搬到另一种模式。判定时两种模式各取一套分段指标再对照:离线记峰值与总吞吐,服务记 TTFT / TPOT / ITL 与队列、池子周转。对照前把请求集、并发与预算三组量固定住,差值才归因得到模式本身。chunked prefill、prefix caching 这类开关只在有对口负载的模式里成立,写结论时必须带模式与批构成。