把一个 Hugging Face 上的因果语言模型跑出第一个
token,并不难:model.generate()
几行就够。把同一份权重变成可并发、可观测、可按
TTFT/TPOT 调
SLO、能在固定显存里塞进更多活跃会话的服务,难的是另一件事——你需要一层推理框架(inference
framework / serving engine),把调度、KV
管理、并行、采样、API 和内核路径钉成一条可运维的流水线。
本篇是「推理框架」的独立深潜:回答为啥需要、在栈里干什么、2026 常用有哪些、底层怎么举例子、怎么用可复制命令上手。机制细节不重复铺开——Prefill/Decode/KV 心智看 11,分页与连续批处理看 12,产品横评看 13,服务化与网关看 21 / 22,DeepSeek 生产切片看 28。本文补的是:框架解剖学 + 可跑通 Demo。
命令来自各项目公开文档(vLLM、SGLang、LMDeploy、llama.cpp/Ollama);本文未在 GPU 上实跑下列 serve 命令。硬件假设与常见失败模式会写明,吞吐数字一律不当独立复现。
下图是整篇的总览:框架真正拥有的是中间五层。
〇、30 秒版
- 裸 generate 不是服务:缺请求级调度、分页 KV、连续批处理、OpenAI 兼容面与多租户配额时,并发一上来 TTFT/TPOT 和显存会同时崩。
- 框架拥有的层:Scheduler、KV Manager、Parallel Runtime、Sampling/Structured Output、Model Runner(再往下才是 FlashAttention / FlashInfer 一类 kernel)。
- 2026 常用地图:通用默认多看 vLLM;Agent/前缀缓存多看 SGLang;NVIDIA 极致延迟看 TensorRT-LLM;吞吐特化看 LMDeploy;昇腾看 MindIE;DeepSeek 特化看 DeepInfer;边缘/本地看 llama.cpp / Ollama。TGI 已维护模式/仓库归档,仅存量场景,新项目不推荐。
- 底层四件套:Prefill vs Decode 的 roofline 差异、PagedAttention block table、Continuous Batching 时间线、Attention backend 分层——框架是把它们组装成产品,不是发明其中某一篇论文。
- 上手顺序:先 Demo A(vLLM OpenAI server)→ Demo B(SGLang 或 LMDeploy 冒烟)→ 需要本地/CPU 再 Demo C(llama.cpp/Ollama);上生产再叠加 21、22、23。
一、为啥需要推理框架
1.1 从「能跑」到「能卖」
实验室脚本关心的是:这份权重能不能在一张卡上出答案。生产线关心的是另一组指标:
| 指标 | 含义 | 没有框架时常见症状 |
|---|---|---|
| TTFT | 首 token 延迟 | 长 prompt 堵死短请求;队列无优先级 |
| TPOT / ITL | 间 token 延迟 | decode batch 抖动;尾延迟爆炸 |
| 并发会话数 | 同时活跃的 KV 占用 | 缺跨请求 block 管理与共享;碎片、浪费与 OOM 并存 |
| 吞吐 (tok/s) | 单位时间产出 | 静态 batch 里短请求空等;GPU 利用率虚高或虚低 |
| 多租户 | 配额、隔离、多 LoRA | 进程级互踩;无法按 API Key 限流 |
transformers 的 generate()
设计目标是正确性与研究灵活性,不是高并发
serving。它通常:一次处理一个(或静态拼好的)batch;默认 KV
用 DynamicCache
随生成动态增长、连续保存(按固定上限预分配的是
StaticCache 等路径,不是默认);缺跨请求的
block 管理、前缀共享与动态插入;也没有稳定的 OpenAI 兼容
HTTP、grammar-constrained decoding、prefix cache
命中统计。
1.2 瓶颈地图
把问题摊开,会看到四类彼此耦合的瓶颈——框架存在的理由,就是同时对付它们:
- 算力侧:Prefill 偏 compute-bound,需要好的 attention/GEMM 路径与合理的 chunk 切分。
- 带宽侧:小 batch、长 KV 等常见 Decode 场景通常偏 HBM-bound(大 batch 时也可能转向 compute-bound);batch 太小则浪费,太大则 TTFT/尾延迟恶化。
- 显存侧:KV 是并发的硬上限;不做分页与共享,碎片会先于算力把你卡死(见 [12])。
- 系统侧:请求动态到达、取消、超时、多
LoRA 热加载——这是调度与 API 层的事,不是某次
forward()能单独解决的。
一句话:推理框架 = 在固定 GPU 预算下,用调度 + 内存管理 + 内核路径,同时抬高并发、吞吐,并守住延迟 SLO。
二、框架在栈里的位置
回到总览图(fig1)。自上而下:
- Serving
API:
/v1/chat/completions、流式 SSE、鉴权、限流。有的引擎自带(vLLM、SGLang;TGI 存量栈亦如此),有的挂在 Triton / 网关之后(TRT-LLM 常见组合)。 - Scheduler:waiting/running 队列、continuous batching、chunked prefill、抢占与优先级。
- KV Manager:block 分配/回收、prefix/radix cache、必要时 swap。
- Parallel Runtime:张量并行(TP)、流水并行(PP)、数据并行(DP)、专家并行(EP)及对应集合通信。
- Sampling + Structured Output:temperature/top-p、stop、JSON/grammar、tool call 约束。
- Model Runner + Kernels:把 metadata 喂给 FlashAttention / FlashInfer / 厂商 plugin;加载 AWQ/GPTQ/FP8 权重。
再往下是硬件与驱动(CUDA / ROCm / CANN / Metal)。框架不拥有 GPU,但拥有「如何把请求变成对 GPU 友好的步」的策略。 服务化集群、PD 分离编排、跨机 KV 存储——往往在框架之外再叠一层(21、28 的边界讨论)。
三、框架实际做什么
下面按「一次请求穿过引擎」来看职责,而不是按仓库目录罗列文件名。
3.1 请求调度与 Continuous Batching
静态 batch 的致命问题:短请求必须陪长请求等到整批 EOS。Orca 提出的迭代级调度让每个 decode step 都可以接纳新请求、放出已完成请求——这就是 continuous batching。框架的 Scheduler 每步决定:谁 prefill、谁 decode、谁被抢占、本步 token budget 多少。
Chunked prefill(把超长 prompt 切成块与 decode 交织;思想可溯到 SARATHI 一类工作)是同一家族的补丁:避免一次巨大 prefill 饿死所有 decode。机制展开见 12;这里只需记住——没有这层调度,再快的 kernel 也救不了尾延迟。
3.2 Paged KV 与前缀缓存
裸 generate 路径里 KV 多是请求内连续增长;一旦进入多请求 serving,若仍按「整段预留 / 无共享」管理,浪费与碎片会同时发生。PagedAttention 把 KV 切成固定大小 block,用 block table 做逻辑→物理映射,思想直接类比操作系统的虚拟内存。
在此之上,vLLM 一类用 block-hash 做 automatic prefix caching;SGLang 用 radix tree 更吃「多分叉、共享系统提示」的 Agent 流量。框架要保证:分配、共享、写时复制、回收在调度步之间保持一致,否则 attention 会读到错块。
3.3 并行策略
| 策略 | 切什么 | 典型触发 |
|---|---|---|
| TP | 层内矩阵 | 单卡装不下,或要降单步延迟 |
| PP | 层间流水 | 极深模型;注意 bubble |
| DP | 模型副本,按请求分片 | 抬吞吐;要请求级负载均衡 |
| EP | MoE 专家 | DeepSeek 类宽专家;常配 EPLB |
框架负责:进程拓扑、权重分片、collective 插入点、以及「某个并行组合下 attention metadata 怎么拼」。选不对并行,不是慢一点,而是直接挂(NCCL 超时、显存不均、专家热点)。
3.4 量化加载、多 LoRA、结构化输出、投机接口
- 量化:启动时按 AWQ/GPTQ/FP8/FP4 等格式加载;KV 也可量化(长上下文更敏感,见 [14])。
- 多 LoRA:同一底座上热插拔适配器,调度器要按 adapter 分组或做统一 batch。
- 结构化输出:grammar / JSON schema / tool call——采样层与 logits processor 的事,越来越多业务把它当硬需求。
- 投机解码接口:draft 模型或 MTP 头产出候选,target 验证;框架要提供变长 draft、KV 回滚与接受率统计的槽位(15、28 的 DSpark)。
3.5 OpenAI 兼容 API 与观测钩子
生产集成成本往往高于内核微优化。稳定的
/v1/chat/completions、流式、usage
字段、错误码,能让网关与评测脚本少写一半胶水。观测上,框架至少应暴露:queue
wait、TTFT、TPOT、KV 占用、prefix cache hit
rate、running/waiting 队列长度、swap/recompute、batch
size、preempt 次数——再交给 23 那套
GenAI 可观测栈。
四、常用引擎地图(2026)
4.1 能力矩阵(定性)
下表是工程直觉图,不是分数榜。同一引擎在不同模型/卡型上的相对位置会变;细节横评仍以 [13] 为准。
简要注记:
- vLLM:通用 CUDA serving 的默认候选;生态大、模型适配快、OpenAI 兼容成熟。
- SGLang:RadixAttention 与前端 DSL 吃 Agent / 多轮前缀;结构化输出与缓存命中是卖点。
- TensorRT-LLM:开源(Apache-2.0)编译栈
+ NVIDIA 专用 kernel
路径;峰值延迟强,换模型/换架构的工程税也高;也可走原生
trtllm-serve,不必总绑 Triton。 - TGI:历史 HF 路径;官方仓库已于 2026-03-21 归档并进入 maintenance mode,新部署应优先 vLLM / SGLang;仅存量迁移与旧托管链路仍可能碰到(与 13 口径一致;状态核对日期 2026-10-29)。
- LMDeploy:TurboMind 等路径常见于国内对照;小并发延迟与大并发吞吐的相对强弱随版本/模型而变,勿简单标成「只偏吞吐」。
- MindIE:昇腾主路径上的官方/准官方选择(闭源商业组件为主);和 CUDA 引擎不是同一套运维手册。
- DeepInfer:DeepSeek 生产引擎开源切片;当前公开 release 更偏 offline / 特化路径,完整在线 serving 能力与通用多模型覆盖有限,当教材与 DeepSeek 栈入口,不当 vLLM 通用替换(28)。
- llama.cpp / Ollama:CPU/边缘/笔记本;量化 GGUF、启动简单;不是数据中心万卡方案。
- 补充:ONNX Runtime / OpenVINO 在「非自回归 LLM 主航道」或端侧/CPU 推理里仍常见,本文不展开。
4.2 选型多路径
分流口诀:
- 单卡开发 / 演示 → vLLM 或 Ollama。
- 高吞吐多租户 API → vLLM 或 SGLang,再叠 21、22。
- NVIDIA 极致延迟且团队能养编译栈 → TensorRT-LLM。
- 昇腾国产卡 → MindIE(不要假装 vLLM 容器换个 base image 就完事)。
- DeepSeek V4.1 + 匹配硬件 → 认真读 28,再决定 DeepInfer 还是社区适配。
五、底层举例(讲透四件)
下面四件尽量落到可核对的状态,而不是只复述 11 / 12 的结论。机制长文仍回那两篇;这里补「框架怎么把它们钉成一步调度」。
5.1 Prefill vs Decode 的 Roofline
把算术强度粗记成「本步算了多少 FLOP / 搬了多少字节」。Prefill 一次吃进很多 token,强度高,容易撞算力墙(图右侧);Decode 每步只追加极少 query、却要扫(或分页扫)整份 KV,强度低,容易撞带宽墙(图左侧)。纵轴是有效算力(FLOP/s)示意,不是直接把请求吞吐画成坐标。
因此:
- 同一
max_num_seqs对 Prefill / Decode 意义不同; - 小 batch、长 KV 的 Decode 通常偏 HBM-bound;batch 变大后也可能转向 compute-bound——别把「几乎总是」写死;
- 该不该 PD 分离、该不该 chunked prefill,是框架级策略,不是调一个 learning rate。
5.2 PagedAttention / block table(含一步 append)
见 fig5。kernel 侧通过
block_tables[req][logical_block]
找到物理页。append 一个新 token 时:当前
block 未满 → 写下一 slot;写满后 → 才从 free pool 分配新
block,并在 block table 末尾追加映射。框架 bug
的高发区往往在 refcount、prefix
共享(写时复制)、抢占换出后再换入的一致性。
最小 worked
example(block_size = 16,数字仅示意):
| 时刻 | waiting | running | 说明 |
|---|---|---|---|
| t0 | A(prompt=20), B(prompt=10) | ∅ | 两请求入队 |
| t1 | B | A | 调度选 A 做 prefill:A 需 ⌈20/16⌉=2 个 block,例如物理页
{7,3};block_tables[A]=[7,3],第 2
块只用了 4/16 slot |
| t2 | ∅ | A, B | B 入 running:B 需 1 block,例如 {9};若
A/B 共享系统前缀,可提高 refcount
复用同一物理页,而不是各拷一份 |
| t3 | ∅ | A, B | decode 一步:A 在 block 3 的下一 slot 写新
KV(未满则不新开块);若本步 token budget
不足,B 可能继续等下一 step |
| t4 | ∅ | B | A 结束:refcount–,无共享则归还
{7,3};running 容量归还;waiting
仍是「未调度请求」队列,不是 slot 池 |
读代码时,把「逻辑块号 → 物理页 → slot 偏移」三条线同时盯住;错一条,attention 就会读到别人的 KV。
5.3
Continuous Batching:一步 SchedulerOutput
见 fig4。实现上,Scheduler 维护 waiting /
running;每步大致:
SchedulerOutput = {
scheduled: # 本步要跑的 req(prefill 或 decode)
blocks_to_alloc / blocks_to_free,
swapped_in / swapped_out, # 若启用 CPU swap
preempted, # 被挤出 running 的 req
}
Worker: prepare_inputs(SchedulerOutput) → forward → sample
伪代码(概念,非某一版本 API):
def schedule(waiting, running, budget_tokens, free_blocks):
batch = []
# 1) 先尽量留下一步的 decode,避免饿死已上线会话
for r in running:
if budget_tokens <= 0: break
batch.append(r); budget_tokens -= 1
# 2) 再用剩余 budget / 块 接纳 waiting 的 prefill(可 chunk)
while waiting and budget_tokens > 0 and free_blocks > 0:
r = waiting[0]
need = blocks_needed_for_next_chunk(r)
if need > free_blocks: break
alloc(r, need); waiting.pop(0); running.append(r)
batch.append(r); budget_tokens -= chunk_tokens(r)
return SchedulerOutput(batch, ...)
vLLM V1 的主调度器在公开树里仍是 Python(例如
vllm/v1/core/sched/scheduler.py 的
Scheduler),EngineCore
重构后把调度循环与执行边界划得更清;C++/CUDA
主要落在 attention / sampler 等 kernel
与扩展路径,不要笼统写成「调度下沉到 C++」。SGLang
则把 radix 命中与缓存感知调度绑得更紧——产品差异见 13,机制共性见
12。
客户端 abort / 断连时,框架必须:从 running/waiting 摘掉请求 → 停掉后续 sample → 按 refcount 释放 KV。漏这一步,显存会在「用户已经走了」之后继续涨。
5.4 Attention backend 分层
读开源代码时,建议固定视角:
- 谁选择 backend(按 dtype、是否 paged、prefill/decode、硬件能力);
- 谁构造 metadata(positions、block tables、cu_seqlens、alibi/rope);
- 哪个 kernel 真正跑(FA2/FA3、FlashInfer、Triton fused、厂商 plugin)。
| 概念 | 常见落点(vLLM V1 风格,版本会变) | 可观察信号 |
|---|---|---|
| 调度决策 | v1/core/sched/scheduler.py |
running/waiting 长度、preempt |
| Block 表 | KV cache manager / block pool | KV 占用、allocation 失败 |
| Backend 选择 | attention backend registry | 日志里的 FA / FlashInfer 名称 |
| 实际算子 | FlashAttention / FlashInfer / plugin | 单步耗时、GPU SM 利用率 |
FlashAttention 族解决的是 IO-aware tiling;FlashDecoding 针对 decode 的拆分;FlashInfer 把 paged 布局与采样周边接得更齐。框架价值是在正确的步选对正确的 backend,并在切换时不破坏 KV 语义。
5.5 开放问题(框架层仍在吵什么)
- PD 分离 vs chunked prefill:何时拆池比同池交织更划算?看 prompt/decode 比、网络与 KV 传递成本,没有万能阈值。
- block-hash vs radix:多租户下前缀共享的语义与隔离;命中率高不等于账单与噪声公平。
- 编译栈 vs 动态图:TRT-LLM 一类峰值 vs 换模型/换架构的维护税。
- 框架 API vs 外置网关:限流、多租户、统一鉴权放引擎内还是 22——边界清了才好排障。
进阶拼图(PD 分离槽位、投机解码槽位)见下图;落地分别指向 21 与 15/28。
六、部署 Demo(按公开文档整理;标明假设)
共同假设:Linux;Demo A/B 需要匹配驱动的
NVIDIA GPU;能拉 Hugging Face
或已有本地权重。端口:A/B 用
8000;Demo C 的 llama.cpp 示例用
8080,Ollama 默认常为
11434——不要混用。本文不下载大模型、不声称已在本机跑通;下列命令按公开文档整理(核对意识:2026-10-29),版本会变。
6.1 Demo A:vLLM OpenAI server
文档入口:https://docs.vllm.ai(安装与 OpenAI 兼容 server 页)。
python3 -m venv .venv && source .venv/bin/activate
pip install -U pip
pip install vllm # 版本按文档选择;CUDA 轮子需匹配驱动
export MODEL=Qwen/Qwen2.5-0.5B-Instruct
# served-model-name 固定后,下面 curl 不必跟着重写 HF id
vllm serve "$MODEL" --host 0.0.0.0 --port 8000 --served-model-name demo就绪检查(先于 chat):
curl -s http://127.0.0.1:8000/v1/models | headDocker 形态(镜像 tag 随发布变化;常需
--ipc=host,先查文档):
docker run --gpus all --ipc=host -p 8000:8000 \
vllm/vllm-openai:latest \
--model "$MODEL" --served-model-name demo冒烟(model 与
--served-model-name
一致;示意输出,未实跑):
curl http://127.0.0.1:8000/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"model": "demo",
"messages": [{"role":"user","content":"用一句话解释 KV cache"}],
"max_tokens": 128
}'常见失败:
| 现象 | 可能原因 | 处理方向 |
|---|---|---|
| CUDA OOM | 模型过大 / KV 预留过高 | 换小模型;降 max_model_len;量化 |
| 拉模型超时 | HF 网络 | 镜像站或预下载到本地路径 |
nvidia-smi 见不到进程 |
没进 GPU 容器 / 驱动不匹配 | 检查 --gpus、驱动与 CUDA |
| 接口 404 / 模型名不符 | 路径或 served-model-name 不一致 |
先打 /v1/models,再对齐 curl 的
model |
6.2 Demo B:SGLang / LMDeploy 冒烟(串行对照)
本节是冒烟启动,不是完整 benchmark。真正的 TTFT / 缓存命中 / 多轮前缀对照,直接跟 12 / 13 的统一流程,避免在未实跑时另造一套口径。
SGLang(仓库 https://github.com/sgl-project/sglang;当前
quickstart 也可能推荐 uv pip +
sglang serve,以官方页为准):
python3 -m venv .venv-sgl && source .venv-sgl/bin/activate
pip install "sglang[all]" # 或文档推荐的 uv 安装方式
python -m sglang.launch_server \
--model-path "$MODEL" \
--host 0.0.0.0 --port 8000
# 同样先 curl /v1/models,再打 /v1/chat/completionsLMDeploy(默认 API 端口常为 23333,这里显式指定 8000):
python3 -m venv .venv-lmd && source .venv-lmd/bin/activate
pip install lmdeploy
lmdeploy serve api_server "$MODEL" --server-port 8000选 SGLang 还是 LMDeploy:偏 Agent/前缀 → SGLang;偏 TurboMind 路径对照 → LMDeploy。不要和 vLLM 同时占同一张卡——串行更干净。
6.3 Demo C:llama.cpp / Ollama(本地 / 边缘)
无 GPU 或只想本机玩:
# Ollama(安装见 https://ollama.com ;默认端口常 11434)
ollama pull qwen2.5:0.5b
ollama run qwen2.5:0.5b "用一句话解释 continuous batching"llama.cpp(需 GGUF;从源码构建时二进制常见为
./build/bin/llama-server,也可使用发行页预编译包):
# 示例:已 cmake 构建后
./build/bin/llama-server -m ./qwen2.5-0.5b-q4_k_m.gguf --port 8080边界:Demo C 验证的是「本地可用性」,不是数据中心调度能力;不要把 Ollama 的体感延迟直接外推到 vLLM 集群。
6.4 环境与安全备忘
- 生产勿把
--host 0.0.0.0裸露在公网;前面要有鉴权网关(22)。 HF_TOKEN、模型许可证、数据驻留按你的合规要求处理(24)。- 观察指标至少抓:
nvidia-smi、引擎自带 metrics、一次完整的 TTFT/TPOT 手测。
七、落地选型与多路径
务实顺序:
- 路径 A(学习):11 → 12 → 本篇 → 13。先建立「调度 / KV / kernel」分层,再看产品差异。
- 路径 B(上手):Demo A 跑通 → Demo B 对照一项差异(缓存或吞吐)→ 需要本地再 Demo C。
- 路径 C(生产):引擎之上补 21 服务化、22 网关、23 观测;没有 SLO 与限流的 curl 通 ≠ 可上线。
- 路径 D(特化):硬件/模型强约束时再进 TRT-LLM、MindIE 或 DeepInfer;默认不要双栈并行太久——回归与镜像成本是实打实的。
「多路径」不是犹豫症,而是承认:开发机、在线集群、边缘盒子、国产卡、模型厂特化栈从来不是同一个优化问题。
八、与系列交叉引用 + 延伸阅读
| 你想解决的问题 | 去哪篇 |
|---|---|
| Prefill/Decode/KV 心智 | 11 推理引擎基础 |
| PagedAttention / Continuous Batching 机制 | 12 |
| 引擎产品横评与决策树 | 13 |
| 量化与 KV 量化 | 14 |
| 投机解码 / MTP | 15 |
| 服务化 / PD 分离 | 21 |
| 网关 | 22 |
| 可观测 | 23 |
| 成本与安全合规 | 24 |
| DeepSeek 生产引擎切片 | 28 DeepInfer |
| 国产芯片主路径 | 26 |
参考(公开文档与仓库)
- vLLM:https://github.com/vllm-project/vllm · 文档 https://docs.vllm.ai
- SGLang:https://github.com/sgl-project/sglang
- TensorRT-LLM:https://github.com/NVIDIA/TensorRT-LLM
- Text Generation Inference:https://github.com/huggingface/text-generation-inference(2026-03-21 起归档 / maintenance mode;新项目不推荐)
- LMDeploy:https://github.com/InternLM/lmdeploy
- MindIE(华为昇腾推理):以华为官方文档与版本说明为准
- DeepInfer:https://github.com/deepseek-ai/DeepInfer(深潜见 [28])
- llama.cpp:https://github.com/ggerganov/llama.cpp · Ollama:https://ollama.com
- Orca(连续批处理思想):Yu et al., OSDI 2022
- vLLM / PagedAttention:Kwon et al., SOSP 2023
- SGLang / RadixAttention:Zheng et al., NeurIPS 2024
本篇定位回顾:在 [11]/[12] 的机制与 [13] 的产品横评之间,补上「框架为什么存在、内部如何分层、如何用 Demo 摸到那一层」;深度放在解剖学与可运行路径,而不是再做一张吞吐排行榜。
源码下载
本文相关源码已整理,共 1 个文件。
文章导航
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
【大模型基础设施工程】11:推理引擎基础
从 Prefill/Decode 两阶段、KV Cache、Continuous Batching 到 PD 分离,系统讲清楚大模型推理的工程基础。
【大模型基础设施工程】12:PagedAttention 与 Continuous Batching
PagedAttention 与 Continuous Batching 如何抬高推理吞吐:KV Cache 分页、批调度与工程取舍。
【大模型基础设施工程】13:vLLM / SGLang / TensorRT-LLM / TGI 对比
主流推理引擎的架构、性能、生态深度对比,给出工程选型与落地决策依据。
【大模型基础设施工程】21:推理服务化
从单机引擎到生产级集群:Triton、Ray Serve、KServe、vLLM OpenAI Server、PD 分离、LoRA 多租户、KEDA 自动扩缩、Serverless GPU 的全景工程实战。