arganzheng · 2026-08-25
十五篇正文 · 一条请求路径 · 一切围绕动态请求、KV 状态与 GPU 资源
一个持续生成的请求,如何在 SLO、KV 显存与 GPU 算力之间被系统持续协调?
%%{init: {"flowchart": {"wrappingWidth": 110}}}%%
flowchart LR
A["01 为什么难"] --> B["02 衡量"] --> C["03 请求全景"] --> D["04 Scheduler"] --> E["05 KV Cache"] --> F["06 GPU 执行"] --> NX["07–15 扩展与适配<br/>(下图)"]
%%{init: {"flowchart": {"wrappingWidth": 140}}}%%
flowchart LR
G["07 解码扩展"] --> H["08 Multi-GPU"] --> I["09 PD 分离"] --> J["10–15<br/>(下图)"]
后半程回答「一个引擎怎么长成一个系统」——适配模型、变体负载、异构硬件,最后对照同类
%%{init: {"flowchart": {"wrappingWidth": 190}}}%%
flowchart TB
J["10 模型适配"] --> K["11 multi-LoRA / 多模态"] --> L["12 硬件解耦"]
L --> M["13 下一站"] --> N["14 源码中的一次请求"] --> O["15 对照 SGLang"]
Llama-3-70B、8×H100:每 token KV 320 KB、每请求约 734 MB
Prefill 偏计算密集,Decode 偏内存带宽;两者竞争同一组 GPU 资源。
吞吐算系统做了多少,延迟算用户等多久,Goodput 算 SLO 内有效工作
Executor 是引擎内对象:DP 定 EngineCoreProc 数、TP×PP 定 WorkerProc 数
DP rank 各有一个 EngineCoreProc;每个执行 rank 对应一个 WorkerProc。
跨进程传递的是请求元数据、token IDs 与调度结果,不是完整模型状态。
主线程立即回到 schedule;序列化走输出线程,detokenize 与 stop 检查在进程 A。
包图沿实际 import 展开;csrc 经 CMake / setup.py 编译进 Python wheel。
每轮结合 token budget、请求状态与 KV 空间,决定运行哪些请求、每个请求推进多少 token。
block_size=16;2,050 token 占 129 块,Prefix Cache 只复用满块
Decode 权重读取下界约 5.3 ms、实测约 10 ms;decode 占总时 97%
接受长度 2.5、batch 1 时约 2.1×;batch 128 时每步慢 1.9 倍
TP/PP/EP/CP 解决装不下,DP 解决要更多吞吐且在最外层
匹配≠就绪、算完≠可回收;分离换来配置自由与交接成本
模型层管结构,运行时层管调度/Cache/并行,算子层管 Kernel
8 个 rank-16 槽位静态占 1.44 GB/卡,与实际加载数无关
一张图 encoder 输出 9–22 MB,它占的 KV 184–438 MB,约 20 倍
核心依赖抽象能力而非具体芯片;OOT 靠主仓库的稳定契约
四转变:配置→自动计划、缓存→分布式状态、执行→多阶段、目标→Goodput
同一请求五笔账:734 MB KV、5 段 chunk、decode 3,000 ms
| 维度 | vLLM v0.27.1 | SGLang v0.5.18 |
|---|---|---|
| 出发点 | PagedAttention:显存碎片 → 块 | RadixAttention:前缀复用 → 树 |
| 调度 | 一个调度器,token 预算,无阶段 | 每 rank 一份调度器,prefill 优先 |
| KV | block hash、block_table |
RadixCache、req_to_token |
| 进程 | SchedulerOutput 跨进程到 WorkerProc | Scheduler 与 ModelRunner 同进程 |
一句话:vLLM 是“一个调度器 + 块”,SGLang 是“N 个相同调度器 + 树”。