惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Microsoft Azure Blog
Microsoft Azure Blog
博客园_首页
Forbes - Security
Forbes - Security
WordPress大学
WordPress大学
P
Proofpoint News Feed
T
Threat Research - Cisco Blogs
L
LINUX DO - 热门话题
L
Lohrmann on Cybersecurity
Spread Privacy
Spread Privacy
D
Darknet – Hacking Tools, Hacker News & Cyber Security
大猫的无限游戏
大猫的无限游戏
博客园 - 三生石上(FineUI控件)
P
Privacy International News Feed
A
About on SuperTechFans
T
Tailwind CSS Blog
I
InfoQ
S
Securelist
云风的 BLOG
云风的 BLOG
罗磊的独立博客
Recent Announcements
Recent Announcements
T
The Exploit Database - CXSecurity.com
B
Blog RSS Feed
V
Visual Studio Blog
Know Your Adversary
Know Your Adversary
The GitHub Blog
The GitHub Blog
Jina AI
Jina AI
腾讯CDC
Cyberwarzone
Cyberwarzone
有赞技术团队
有赞技术团队
AWS News Blog
AWS News Blog
博客园 - 【当耐特】
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
F
Full Disclosure
S
Secure Thoughts
博客园 - 司徒正美
J
Java Code Geeks
Y
Y Combinator Blog
Google Online Security Blog
Google Online Security Blog
GbyAI
GbyAI
N
News and Events Feed by Topic
Help Net Security
Help Net Security
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Project Zero
Project Zero
T
Tenable Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
T
Tor Project blog
MyScale Blog
MyScale Blog
Scott Helme
Scott Helme
小众软件
小众软件
K
Kaspersky official blog

土法炼钢兴趣小组的算法知识备份

国密算法与国密 TLS 系列索引 【系统架构设计】架构质量属性:不只是"高可用高性能" 【系统架构设计百科】告警策略:如何避免"狼来了" 【系统架构设计】CQRS:读写分离的架构哲学 【系统架构设计】空间架构:极端扩展场景的解法 【系统架构设计】微服务架构深度审视:优势、代价与适用边界 【系统架构设计】扩展性原理:水平、垂直与对角扩展 【系统架构设计】无状态设计:扩展的第一步也是最难的一步 【系统架构设计】缓存架构:从本地到分布式的多级缓存体系 【系统架构设计】管道与过滤器:Unix 哲学的架构表达 【系统架构设计】复杂性管理:架构的核心战场 【系统架构设计】消息队列架构:异步解耦的设计与陷阱 【系统架构设计】CDN 架构:全球加速的设计原理 【系统架构设计】连接池设计:被忽视的性能杀手 【系统架构设计】弹性设计模式:熔断器、舱壁与超时 【系统架构设计】高可用设计模式:冗余、故障转移与仲裁 【系统架构设计】容量规划:从拍脑袋到数据驱动 【系统架构设计】数据库扩展:分库分表的工程实践与替代方案 【系统架构设计】SLO 工程:可靠性的量化管理 【系统架构设计】性能建模:用数学思维分析系统瓶颈 【系统架构设计】混沌工程:主动验证系统的韧性 【系统架构设计】零拷贝与内存映射:数据搬运的极致优化 【系统架构设计】线程模型:从 thread-per-request 到协程 【系统架构设计】容灾架构:多活与灾备设计 【系统架构设计】数据库性能模式:索引、查询与连接管理 【系统架构设计】数据建模:从关系范式到文档模型的真实权衡 【系统架构设计】吞吐量优化:批处理、流水线与并发模型 【系统架构设计】流处理架构:从批处理到实时的范式迁移 【系统架构设计】搜索引擎架构:倒排索引之上的系统设计 【系统架构设计】时序数据架构:监控与 IoT 的存储设计 【系统架构设计】数据迁移与版本化:在线不停机的数据演进 【系统架构设计】数据湖与数据仓库:分析架构的演进路线 【系统架构设计】API 网关设计:入口层的职责边界 【系统架构设计】应用层数据一致性模式:在正确性与性能之间走钢丝 【系统架构设计】多模数据库选型:Polyglot Persistence 的工程实践 【系统架构设计】服务发现与注册:动态拓扑的基础设施 【系统架构设计】配置管理架构:从配置文件到配置中心 【系统架构设计】全链路压测:大规模系统的性能验证 【系统架构设计】幂等性设计:分布式环境下的安全重试 【系统架构设计】契约测试与 Schema 演进:服务间的信任协议 【系统架构设计】长连接与推送架构:WebSocket、SSE 与 MQTT 【系统架构设计】延迟分析:从 P50 到 P999 的全链路追踪 【系统架构设计百科】DDD 战术模式:聚合、实体与值对象 【系统架构设计百科】防腐层与开放主机服务:系统集成的 DDD 方案 【系统架构设计百科】领域事件与事件风暴:从业务到架构的桥梁 【系统架构设计百科】CQRS + Event Sourcing 完整实战:从领域建模到部署 【系统架构设计百科】DDD 与微服务:用领域模型划分服务边界 【系统架构设计】DDD 战略设计:限界上下文与上下文映射 【系统架构设计百科】认证架构:从 Session 到 JWT 到 OIDC 【系统架构设计】API 设计哲学:REST vs GraphQL vs gRPC 的真实权衡 排序算法专题:从 TimSort 到并行排序 【密码学百科】国密算法体系:SM2/SM3/SM4/SM9 全景解读 【密码学百科】承诺方案:Pedersen 承诺、向量承诺与多项式承诺 【密码学百科】不经意传输与隐私信息检索:OT、OT 扩展与 PIR 【密码学百科】门限密码学:门限签名、门限解密与分布式密钥生成 完美哈希:从理论到 gperf 实践 【密码学百科】安全多方计算:从 Yao 的混淆电路到实用 MPC 【密码学百科】同态加密:从 Paillier 到全同态加密(FHE) 【密码学百科】零知识证明系统:zk-SNARKs、zk-STARKs 与 Bulletproofs 【密码学百科】概率论与密码分析:生日攻击、差分分析与线性分析 【密码学百科】计算复杂性与归约:密码安全性证明的基石 【密码学百科】秘密共享:Shamir 方案、VSS 与安全多方计算入口 【密码学百科】椭圆曲线代数:Weierstrass 方程、点群运算与曲线选择 【密码学百科】离散对数与配对密码学:从 DLP 到 BLS 签名 【密码学百科】格密码数学基础:SVP、LWE 与格基约化 【密码学百科】抽象代数:群、环、域的密码学视角 【密码学百科】有限域算术:GF(2^n) 运算与在 AES/ECC 中的应用 【密码学百科】数论进阶:二次剩余、椭圆曲线上的 Weil 配对 【密码学百科】密码学简史:从凯撒密码到量子时代 【密码学百科】威胁模型与安全目标:CIA 三要素之外 【密码学百科】Kerckhoffs 原则与现代密码设计哲学 【密码学百科】随机性:密码学的基石 【密码学百科】信息论入门:熵、完美保密与 Shannon 定理 【密码学百科】分组密码原理:Feistel 网络与 SPN 结构 【密码学百科】AES 逐步拆解:SubBytes 到 MixColumns 的数学 【密码学百科】分组密码工作模式全览:ECB/CBC/CTR/OFB/CFB 【密码学百科】流密码:RC4 的兴衰与 ChaCha20 的崛起 【密码学百科】密码学哈希函数:MD5→SHA-2→SHA-3 的进化之路 【密码学百科】MAC 与 HMAC:消息认证的正确姿势 【密码学百科】认证加密(AEAD):GCM、ChaCha20-Poly1305 与 OCB 【密码学百科】密钥派生函数:HKDF、PBKDF2、Argon2 与密码存储 【密码学百科】公钥密码的数论基础:模运算、群、原根 【密码学百科】RSA 从原理到攻击:教科书 RSA 为什么不安全 【密码学百科】Diffie-Hellman 密钥交换与离散对数问题 【密码学百科】椭圆曲线密码学(ECC):从几何直觉到点群运算 【密码学百科】数字签名:ECDSA、EdDSA 与 Schnorr 签名 【密码学百科】现代密钥交换:X25519、ECDHE 与前向保密 【密码学百科】混合加密与 KEM/DEM 范式:ECIES 与 HPKE 【密码学百科】填充方案:PKCS#1 v1.5、OAEP 与 PSS 【密码学百科】TLS 协议全解析:从握手到 0-RTT 【密码学百科】PKI 与数字证书:信任链的构建与崩塌 【密码学百科】密码认证协议:从 SRP 到 OPAQUE 【密码学百科】零知识证明入门:如何证明你知道而不泄露 【密码学百科】安全信道构造:Noise 协议框架与 Signal 协议 【密码学百科】密钥管理工程:HSM、KMS 与密钥生命周期 【密码学百科】侧信道攻击:从时序攻击到功耗分析 【密码学百科】密码学实现陷阱:三层漏洞分类、审计工具链与系统性预防 密码敏捷性:如何设计可升级的密码系统 【密码学百科】OpenSSL/BoringSSL 架构剖析:ENGINE、Provider 与 FIPS 模块 排序基准测试:用数据说话
【大模型基础设施工程】11:推理引擎基础
2026-04-22 · via 土法炼钢兴趣小组的算法知识备份

训练把模型”炼”出来,推理把模型”用”出来。前者是离线任务、按天计量、看吞吐;后者是在线服务、按毫秒计量、看尾延迟。训练工程师可以容忍一次 checkpoint 恢复花半小时,推理工程师被 P99 延迟超了 50ms 就要被叫到会议室。

本篇是系列的推理篇开篇,目的是把”大模型推理”这件事从第一性原理讲清楚:为什么要有 KV Cache、为什么要区分 Prefill 和 Decode、为什么 vLLM 的 Continuous Batching 能把吞吐翻几倍、为什么 DeepSeek / Kimi 都要做 Prefill / Decode 分离。之后的第 12–16 篇会分别展开 PagedAttention、主流引擎对比、量化、推测解码、长上下文。

一、推理 vs 训练的工程差异

1.1 两种 workload 的本质区别

训练是一个离线、同步、吞吐导向的批处理任务。一次 step 处理一个固定 shape 的 batch,forward + backward + optimizer step 三段式,所有样本同生同死。你不关心单样本何时返回,只关心每秒能吃多少 token、每轮 epoch 多长、loss 收敛多快。

推理是在线、异步、延迟导向的服务。请求一个一个来,长度不等、到达时间不等、产出 token 数不等。你不能让后到的请求等前面的请求跑完,不能让短请求陪长请求一起排队。你需要在亚秒级给出首 token,在几十毫秒级给出每个后续 token,还要把 GPU 利用率拉起来。

维度 训练 推理
时效 离线,可以跑几周 在线,毫秒级 SLO
输入 固定 shape 的 batch 变长、动态到达
方向 Forward + Backward 只 Forward
显存 参数 + 梯度 + optimizer state + 激活 参数 + KV Cache
瓶颈 算力 / 通信 访存 / KV 空间
核心指标 Tokens/sec、MFU TTFT、TPOT、并发数
计量 卡时 QPS、1K tokens 价格
失败处理 Checkpoint 回滚 请求重试 / 降级

1.2 为什么推理有自己的一套系统

一台训推混用的方案看上去很美,实际几乎没有人这样做。原因是两边的优化方向几乎相反:

  • 训练要把 batch 撑大,推理要让每个请求尽快出结果。
  • 训练要保 gradient 精度,推理可以做 INT4 / FP8 激进量化。
  • 训练要 checkpoint 持久化,推理要 KV Cache 这种临时内存结构。
  • 训练的并行是 TP+PP+DP+SP+EP 五维,推理更多是 TP + 少量 PP,且 PP 对延迟不友好。

所以主流路线是:训练用 Megatron / DeepSpeed / TorchTitan 等;推理用 vLLM / SGLang / TensorRT-LLM / TGI 等专用引擎。模型从训练态导出(通常是 HF safetensors),推理态再做量化、图优化、权重切分。

1.3 推理的 SLO 模型

一个在线推理服务的 SLO 典型长这样:

  • TTFT P95 < 500ms(聊天)/ < 2s(长文档问答)
  • TPOT P95 < 50ms(对应 20 tok/s 起的流式体验)
  • 可用性 99.9%
  • 超时 30s

这套 SLO 直接决定了引擎怎么调参:batch 不能太大(大 batch 会拉长 TTFT 和 TPOT 尾延迟),也不能太小(小 batch 浪费算力)。所有”继续 batching vs 立刻 decode”的取舍都围绕这条线。

1.4 模型从训练态到推理态

模型权重的生命周期不止是”加载一下那么简单”:

  1. 训练结束 → HuggingFace safetensors / Megatron 分布式 checkpoint。
  2. 格式转换:把分布式 checkpoint 合并为单文件,重命名 key,切分到推理侧的 TP 拓扑。
  3. 量化:GPTQ / AWQ / FP8 校准,生成量化权重 + scale。
  4. 图编译(TRT-LLM 专属):用 trtllm-build 生成 engine,针对 batch size 桶和 seq 长度做 kernel 自动调优。
  5. 部署打包:权重 + tokenizer + 配置上传对象存储(OSS/S3),节点拉取。

这条链路一旦稳定下来,就是每天跑几十次的”模型发布流水线”,和 DevOps 世界的 CI/CD 平行存在。

二、Transformer 推理流程回顾

2.1 自回归生成

把大模型当黑盒,推理接口就是:

输入:  prompt = [t1, t2, ..., tn]
输出:  [tn+1, tn+2, ..., tn+m]   (直到 EOS 或 max_new_tokens)

每一步生成新 token 的伪代码是:

tokens = prompt
while len(tokens) - len(prompt) < max_new_tokens:
    logits = model(tokens)          # shape: [seq, vocab]
    next_id = sample(logits[-1])    # 只取最后一个位置
    tokens.append(next_id)
    if next_id == EOS: break

这份代码能跑,但极慢。问题在 model(tokens) 这一行:每生成一个 token 就把前缀重新 forward 了一遍,计算量随长度平方增长。

2.2 KV Cache 的由来

Transformer 的每一层 attention 计算是:

Q = x @ Wq        # [seq, d]
K = x @ Wk        # [seq, d]
V = x @ Wv        # [seq, d]
out = softmax(Q @ K^T / sqrt(d)) @ V

自回归推理时,生成第 t 个 token 只需要新的 Q_t、但需要与所有历史位置的 K、V 做 attention。历史位置的 K、V 在上一步已经算过了,下一步不会变(因为是 causal mask)。

这就是 KV Cache 的核心:把每一层每个位置的 K、V 缓存下来,下一步只算新位置的 K、V,拼到缓存上,再做 attention。

# 带 KV cache 的 forward
def attn_step(x_new, kv_cache_k, kv_cache_v):
    q = x_new @ Wq
    k_new = x_new @ Wk
    v_new = x_new @ Wv
    k = concat(kv_cache_k, k_new, dim=seq)
    v = concat(kv_cache_v, v_new, dim=seq)
    out = softmax(q @ k.T / sqrt(d)) @ v
    return out, k, v     # 新的 k/v 回写 cache

复杂度从每步 O(n²) 降到每步 O(n),总体从 O(n³) 降到 O(n²)。代价是显存:要存下所有层所有位置的 K 和 V。

三、Prefill 和 Decode 两阶段

3.1 阶段划分

KV Cache 的引入,天然把推理分成两个阶段:

  • Prefill 阶段:把整个 prompt 一次性 forward,计算并填充所有位置的 KV Cache。这里有 seq 个位置同时算,是一个并行的 [bs, seq, d] 的矩阵乘。
  • Decode 阶段:逐 token 生成。每一步输入是 [bs, 1, d],只新增一个位置的 KV,再做 attention。

一个典型 4K prompt 生成 512 tokens 的请求,它的时间线是这样的:

Prefill (4096 tokens 一次性):  ~200ms   → 给出 TTFT
Decode (512 步,每步 ~30ms):   ~15s     → 每步给出一个 token

3.2 Arithmetic intensity 的巨大差异

这两个阶段的计算访存比完全不同,决定了它们的瓶颈也不同。

先复习 roofline:一个 kernel 的上限性能由 arithmetic intensity I(计算 FLOPs / 访存 bytes)决定。I 高就是 compute-bound,吃算力(Tensor Core);I 低就是 memory-bound,吃带宽(HBM)。

A100 SXM4 的 peak:FP16 算力 312 TFLOPS,HBM 带宽 2.0 TB/s,拐点 I ≈ 156 FLOPs/byte。

对于一个 Linear 层 y = x @ W,x 形状 [B, d_in],W 形状 [d_in, d_out]:

  • FLOPs ≈ 2 · B · d_in · d_out
  • 读 W: d_in · d_out · 2 bytes(FP16)
  • I ≈ B(近似,忽略 x 和 y 的访存)

看到没,算术强度几乎就是 batch size。

Prefill:输入是整个 prompt,有效 batch 是 bs · seq。一个 4K prompt + bs=4 就是 16K 的”宽度”,I 远远超过拐点,compute-bound,吃满 Tensor Core。

Decode:每一步输入只有 1 个 token,有效 batch 就是 bs。bs=4、bs=16 的 I 都远低于 156,memory-bound,瓶颈在读权重和读 KV。

这条结论非常重要,后面几乎所有推理优化都围绕它展开:

  • Decode 受限于 HBM 带宽 → 加大 batch(continuous batching)、压 KV(GQA/MLA、量化)、合并 kernel(FlashDecoding)
  • Prefill 受限于算力 → 常规的 GEMM 优化、FlashAttention、chunked prefill 避免阻塞 decode
  • Prefill 和 Decode 特性不同 → 物理上分离部署(PD-disaggregation)

3.3 Prefill / Decode 时序图

Prefill / Decode 时序图

这张图说明了一切:单请求在 Decode 阶段把 GPU”空转”得很厉害。把多个请求的 Decode 合并成一个大 batch,就是 continuous batching 的动机。

四、KV Cache 深入

4.1 维度与大小估算

标准 Multi-Head Attention(MHA)的 KV Cache 形状:

kv_cache: [num_layers, 2, batch_size, num_heads, seq_len, head_dim]
                       ^
                       K / V

单请求单层单 token 的 KV 字节数(FP16):

per_layer_per_token = 2 (K+V) * num_heads * head_dim * 2 bytes

对几个代表性模型算一下每 token 的 KV 开销:

模型 层数 heads × head_dim(KV) 单 token 单层 总 KV/token
LLaMA-2 7B (MHA) 32 32 × 128 2 × 32 × 128 × 2 = 16 KB 512 KB
LLaMA-2 70B (GQA, 8 kv_heads) 80 8 × 128 4 KB 320 KB
LLaMA-3 70B (GQA, 8) 80 8 × 128 4 KB 320 KB
DeepSeek-V2 (MLA) 60 latent 512 ~1 KB 级别 ~70 KB

拿 LLaMA-2 70B 算 4K 上下文:

KV per seq = 320 KB/tok × 4096 tok ≈ 1.3 GB(单序列!)

考虑一些实现把 K/V 分开对齐、加 padding,实际逼近 2 GB/序列。这个数字解释了为什么长上下文 + 多并发推理的显存瓶颈不在参数,而在 KV Cache:H100 80GB 减去权重后剩 ~40GB,只能同时服务 ~20 个 4K 上下文,或者 ~5 个 16K 上下文。这就是 KV 是推理显存头号杀手 的由来。

4.2 GQA / MQA:砍 KV head 数

MQA(Multi-Query Attention,2019):所有 Q head 共享同一组 K、V。KV 大小除以 num_heads,极致压缩,但精度损失大,训练难。

GQA(Grouped-Query Attention,2023):折中方案。Q 分 num_heads 组,KV 分 num_kv_heads 组(通常 8),每组 KV 被多个 Q 共享。LLaMA-2 70B / LLaMA-3 / Mistral / Qwen2 / DeepSeek-V1 全部用 GQA。

标准 MHA:   Q[H], K[H], V[H]
MQA:        Q[H], K[1], V[1]
GQA(G=8):  Q[H], K[8], V[8]  —— H=64 时 K/V 内存是 MHA 的 1/8

GQA 带来 4-8× 的 KV 节省,对长上下文 / 高并发场景是决定性的。代价是微小的 PPL 上升,且要在训练阶段就选好组数(不能事后转换)。

4.3 MLA:DeepSeek 的 KV 压缩方案

DeepSeek-V2 / V3 引入了 Multi-head Latent Attention(MLA)。核心思想是把 K、V 投影到一个低维 latent 空间缓存,算 attention 前再”解压”回来。

简化表述:

# 传统 MHA:缓存 K, V ∈ R^{H·d_h}   一共 2 · H · d_h
# MLA:    缓存 c_kv ∈ R^{d_c}      一共 d_c(通常 d_c << 2·H·d_h)

c_kv = x @ W_kv_down      # 压到 latent
# 推理时:
K = c_kv @ W_k_up
V = c_kv @ W_v_up

实际实现里用了”吸收”技巧:W_k_upW_q 可以预先合并,使 decode 时不需要真的把 K 解压到高维,而是把 query 投到 latent 空间,直接和 c_kv 做 attention。这样计算量也没增加多少。

DeepSeek-V3 的 MLA 把 KV Cache 从 GQA 的 ~4KB/层/token 进一步压到约 1-1.5KB/层/token,是长上下文成本下降的关键之一。DeepSeek V3 能在低成本上跑 128K 上下文,MLA 功不可没。

4.4 KV Cache 结构图

KV Cache 结构图

4.5 KV Cache 的量化与驱逐

除了结构上减小 KV,还有两类正交手段:

  • KV 量化:把 FP16 的 KV 压到 INT8 / INT4 / FP8。KV-Int8 几乎无损,KV-Int4 要校准。vLLM、TRT-LLM 都原生支持。
  • KV 驱逐 / 稀疏:长上下文下,注意到最近 n 个 token 和开头 n 个 token 权重最大,中间可以抛弃或者压缩。StreamingLLM、H2O、SnapKV 是代表方案。这条路线在 128K+ 场景下被逐步应用。

4.6 KV Cache 的显存预算推导

在一台 H100 80GB 上部署 LLaMA-3-70B(FP16),实操预算大致如下:

权重:        70B × 2 bytes   = 140 GB   → 4 卡 TP=4,单卡占 35 GB
框架保留:    激活 / 临时 buffer   ≈ 5 GB
可用 KV:     80 - 35 - 5       ≈ 40 GB / 卡   →  4 卡合计 ~160 GB KV
单 token KV: GQA(8 kv_heads)   = 320 KB / tok
可服务的"活跃 token 总量":
  160 GB / 320 KB ≈ 500K tokens

500K 活跃 token 听起来不少,换算到并发就缩水得很快:

  • 4K 上下文:最多 ~125 个并发请求
  • 16K 上下文:~30 个
  • 128K 上下文:~3-4 个(而且这是”稳态”,不含 prefill 期)

再乘上 PagedAttention 的碎片率(5-10%)和系统保留,真实数字还要再打折。这个预算直接决定了集群规模和商业化价格模型。

4.7 KV 的数据生命周期

一个请求内 KV 的生命周期:

[admit] → [prefill 产生 KV] → [decode N 步,每步 append 1 行] → [done / timeout → 回收]

完成后 KV 的处理策略分三种:

  • 立即释放:最简单,memory 碎片问题由 PagedAttention 解决。
  • 保留做 prefix cache:热 system prompt 留住,冷的 LRU 踢出。
  • 下沉到 CPU/NVMe:Mooncake 风格,给下一次会话做二级命中。

多轮对话里,活跃会话的 KV 往往整会话保留(避免每轮从头 prefill),这是”会话粘性”必须做的工程决策。

五、Batching 的三代演化

单请求推理下,GPU 永远吃不饱。要拉吞吐必须 batch。但 LLM 的变长 + 分阶段让 batching 远比视觉模型复杂。

5.1 第一代:Static Batching

把同时到达的 N 个请求 pad 到最长,一起 forward。伪代码:

batch = collect_requests_for(timeout=50ms, max=8)
outputs = model.generate(pad(batch))

问题很明显:

  • 请求必须等到 batch 凑齐或超时,TTFT 被拖长。
  • 最短请求必须陪最长请求跑完 max_new_tokens,GPU 和用户都亏。
  • 只要 batch 里有一个长请求,整体的”结束时间”就被拉长。

Triton、TorchServe 在 LLM 前时代都是这种模式。

5.2 第二代:Dynamic Batching

Triton Inference Server 提供的模式:请求到达队列,调度器在 max_queue_delay 内窥视,尽量凑够 preferred_batch_size,再一起发到模型。

对 CV 模型和 BERT 有效,对 LLM 依然不够:LLM 每一步输入/输出都在变,不是”一次 forward 出完整结果”,batch 的粒度不应该是”请求”,而应该是”每一步”。

5.3 第三代:Continuous Batching / In-flight Batching

这是 vLLM(Orca 论文思想 + PagedAttention 实现)和 TensorRT-LLM 的核心贡献。

关键观察:Decode 阶段每一步都是独立的 [bs, 1, d] forward,不同请求的 decode step 完全可以拼到一个 batch。请求的”寿命”由它们产出多少 token 决定,但每一步 batch 的组成可以动态变化

新的调度循环(简化版):

running = []                          # 正在 decode 的请求
waiting = []                          # 排队的 prefill 请求

while True:
    # 1. 有新请求且算力允许:做 prefill,加入 running
    if waiting and can_admit():
        req = waiting.pop(0)
        prefill(req)                  # 算出它的首 token + KV cache
        running.append(req)

    # 2. 把 running 里所有请求的"下一步 decode"拼成大 batch
    batch = [r.last_token for r in running]
    logits = model.decode_step(batch, kv_caches=[r.kv for r in running])

    # 3. 每个请求采样各自的 next token
    for r, lg in zip(running, logits):
        r.append(sample(lg))
        if r.done(): running.remove(r); yield r.output

好处:

  • 请求到达即进入 batch,不用等对齐。TTFT 大幅下降。
  • 早完成的请求立刻释放 slot,晚到的请求立刻顶上,GPU 永不空转。
  • Decode step 的有效 batch 可以做到几十甚至上百,Decode 从严重 memory-bound 的 bs=1 拉到 bs=64 的高 arithmetic intensity。

坏处 / 挑战:

  • 每步 batch 形状都在变,需要 kernel 支持变长 QKV 和变长 KV Cache。
  • KV Cache 不能按”最大长度”预分配,否则浪费极大 → 这就是 PagedAttention 的由来(下一篇详讲)。
  • Prefill 和 Decode 混在一个 step 里会互相拖累(prefill 慢、decode 快)→ 需要 chunked prefill 或 PD 分离。

现代引擎(vLLM、SGLang、TRT-LLM、TGI、Mindie 等)默认都开 continuous batching。

5.4 Chunked Prefill

一个棘手问题:某个请求的 4K prompt prefill 要 200ms,这 200ms 里所有 decode 请求都在干等,TPOT 尾延迟会爆炸。

解法:把长 prefill 切成小块,每次只做比如 512 个 token 的 prefill,和一批 decode 拼成一个”混合 batch”一起 forward。vLLM 从 0.4 开始的 --enable-chunked-prefill、SGLang 的默认调度、DeepSeek-V3 的分离式 serve 方案都使用了这个思想。

5.5 Continuous Batching 时序对比

Continuous Batching 时序对比

六、推理关键指标

6.1 延迟指标

  • TTFT(Time To First Token):请求到达到输出第一个 token 的时间。由队列等待 + prefill 时间决定。聊天类目标 < 500ms,长文档目标 < 2s。
  • TPOT(Time Per Output Token)/ ITL(Inter-Token Latency):产出相邻两个 token 的间隔。决定”流式打字”的体感速度。20 tok/s 是下限,40+ tok/s 令人舒适。
  • E2E Latency:总响应时间,= TTFT + output_len × TPOT。
  • Queue Time:请求排队到被调度的时间。过载时这一项会爆炸。

6.2 吞吐指标

  • Total Tokens/sec:输入+输出 token 总吞吐。
  • Output Tokens/sec:只计输出,更贴近计费和用户价值。
  • Requests/sec(QPS):请求吞吐,和 token 数强相关,单独看意义不大。
  • Concurrency:同时处理的请求数,continuous batching 下这是核心调优旋钮。

6.3 SLO 与 P50/P95/P99

在线服务从不看平均值。P95/P99 才是真实用户体验。

P50 TTFT: 150ms      —— 一般用户
P95 TTFT: 400ms      —— 刚好满足
P99 TTFT: 1200ms     —— 1% 用户等了 1.2 秒

推理引擎的尾延迟常常来自:

  • 某个超长 prompt 的 prefill 阻塞一切(→ chunked prefill)
  • KV Cache 满了新请求被 preempt(→ 容量规划)
  • 某个慢请求被反复让位
  • 跨节点 TP 的通信尖峰

生产上必须 per-model 维度监控 TTFT/TPOT/Queue 三条线的 P95/P99,超限直接告警或触发扩容。

6.4 输入 vs 输出 token 的瓶颈

不同业务的输入输出配比,决定了到底卡在 Prefill 还是 Decode。

场景 输入 token 输出 token 瓶颈
短对话 200 200 均衡偏 Decode
角色扮演 / 情感陪伴 500 1000 Decode
Code 补全 2K 50 Prefill
文档摘要 / 长文翻译 10K 500 Prefill 主
RAG 问答 5K 200 Prefill 主
Agent 多轮工具调用 累计 20K 300 × N 轮 Prefill 持续主导
推理类(o1 / R1)长思考 500 10K+ Decode

不同配比下最佳配置天差地别:RAG 场景要把更多算力分给 Prefill(更大 TP、更多 Prefill-only 节点);陪伴类聊天要压 Decode 显存(MLA、KV 量化、超长 continuous batching)。

6.5 压测与 benchmark 工具

生产上看的几套工具链:

  • vllm benchmark_serving.py:vLLM 自带,支持 ShareGPT 数据集模拟真实请求分布。
  • llmperf(Anyscale):开源,按 TTFT / ITL / 吞吐做压力测试。
  • genai-perf(NVIDIA):配套 TRT-LLM 和 Triton 的官方压测。
  • guidellm(Neural Magic):按 SLO 找最大 QPS。

一个标准 benchmark 报告至少包含:

模型 · 硬件 · 引擎版本 · 量化 · 并行配置 · 请求到达分布 · 输入/输出长度分布
-----------------------------------------------------------------------
QPS    TTFT P50/P95/P99    TPOT P50/P95/P99    Output tok/s    GPU util    KV util

没有到达分布(poisson 或真实 trace)和长度分布的 benchmark 基本可以忽略 —— 用固定长度 + 同步压力的数字严重偏离真实场景。

七、Decode kernel 层优化

Decode 是 memory-bound 的关键阶段,这个阶段每一毫秒都要精打细算。

7.1 FlashAttention 系列在 decode 中的变体

标准 FlashAttention(v1/v2/v3)优化的是 prefill 那种大 seq × seq 的 attention,通过 tiling 把 softmax 融到寄存器里,避免物化 QK^T

Decode 场景有两个特点:

  • Q 只有 1 个位置(或 few token,推测解码时几个)
  • KV 长度可能很长(128K)

针对性优化是 FlashDecoding(2023)和 FlashDecoding++(2024):把 KV 维度切成多个 chunk,每个 SM 处理一段,分别算出局部 softmax,最后用 log-sum-exp 合并。让长 context 下的 decode 也能打满 SM。

7.2 Paged Attention kernel

PagedAttention(vLLM)不只是调度机制,还是一个 kernel:它在 CUDA 里实现了按”页表”访问非连续 KV 块的 attention。代价是访存比连续布局略低一些,收益是 KV 内存碎片几乎消失、跨请求共享 KV 变得平凡。下一篇有详解。

7.3 CUDA Graph:消灭 launch 开销

Decode 每步 forward 只要几十毫秒,但一个 70B 模型一次 forward 里有几百上千个 kernel,每个 kernel launch 要 5-20μs,累计就是毫秒级开销,占 TPOT 的相当比例。

CUDA Graph 把一段 kernel 序列录制成图,后续只用一次提交就能执行整张图,launch 开销降到几乎为 0。对固定 shape 的 decode step 尤其合适:batch 大小和 seq 长度虽然每步不同,但只要做成”按 batch 分桶”的几张 graph 就够用。

vLLM、TRT-LLM 都默认启用 CUDA Graph。注意事项:

  • Graph 只能录制静态 shape,变长 batch 要分多个 graph 桶
  • Paged attention 里”页表”是 device 内存,可以被 graph 正常捕获
  • Prefill 一般不用 graph(shape 变化太大,编译不划算)

7.4 权重加载与 kernel fusion

Decode 受限于读权重,那就少读一次。

  • QKV fused:Q、K、V 三个投影合成一个 GEMM。
  • Gate + Up fused(SwiGLU):MLP 的 gate 和 up 投影合成一个。
  • Residual + RMSNorm 融合到下一个 GEMM 里:少读一次激活。

TRT-LLM 的 plugin 系统、vLLM 的自定义 kernel、SGLang 对 FlashInfer 的依赖都在做这件事。

7.5 Speculative 执行与多流

高级优化里还有两条线:

  • 多 CUDA Stream:prefill 和 decode 放不同 stream,通信和计算 overlap。
  • Persistent kernel:几个 kernel 合并成一个”长待机”kernel 持续跑,避免 launch 和同步开销。FlashInfer 的 persistent kernel、CUTLASS 3.x 的 grouped GEMM 都走这条路。
  • Prefetch 权重:下一层的权重在当前层计算时就开始 DMA 到 SM 附近的 shared memory 或 L2(硬件支持有限,但 B200 / H200 有改善)。

7.6 一次 Decode step 的性能拆解(70B / H100 · 参考数值)

单 token 单请求(bs=1)total ≈ 30 ms,其中:
  读权重 70B × 2B / (3TB/s HBM)       ≈ 23 ms   <—— 主导
  读 KV   320KB × 4K tok × 1 / HBM    ≈  0.4 ms
  计算 FLOPs 2 × 70B × 1  / 989 TFLOPS ≈  0.14 ms
  kernel launch × 1000                ≈  5-10 ms  <—— CUDA Graph 能砍掉
  同步 / all-reduce(TP=4)             ≈  1-2 ms

看清楚了吗:绝大部分时间在读权重,其次是 kernel launch。所以:

  • 降权重读取 → 量化(Int8/Int4/FP8)直接把”读权重”时间减半、减四。
  • 降 launch 开销 → CUDA Graph
  • 增大 batch → 权重读一次,服务 N 个请求,摊薄到 23/N ms,这才是 continuous batching 能把吞吐做到几千 tok/s 的根因。

八、Prefill / Decode 分离

8.1 为什么要分离

前面讲过 Prefill 和 Decode 的特性截然不同:

维度 Prefill Decode
计算特性 compute-bound memory-bound
偏好硬件 算力强(H100、B200) 带宽高(HBM3e、大 NVLink 域)
最佳 TP 中等(2-4) 小(1-2)或极大(靠 NVLink 域)
对延迟敏感 TTFT TPOT
对 batch 敏感 拼不太动 越大越好

把两者混在一个 pod 里,必然互相妥协。分离后,每个 pod 可以按自己的负载独立选 TP、选型号、选副本数。

8.2 典型架构

Client
   │
   ▼
Router ──► Prefill Pool(少量大 GPU · 高 TP · 侧重算力)
              │
              │ 通过 RDMA / NVLink 把 KV Cache 传输过去
              ▼
           Decode Pool(多节点 · 小 TP · 侧重带宽 · 长期持有 KV)
              │
              ▼
           Streaming 输出给客户端

关键难点在”KV Cache 传输”:prefill 完一个 4K prompt,KV 可能是几 GB 级别,要通过 RDMA(IB / RoCE)快速打给 Decode 节点,传输时间必须远小于 decode 一整轮的时间。这是一个纯粹的网络+内存工程问题。

8.3 代表性系统

  • DistServe(2024):UCSD / 北大的开源系统,论文里首次系统给出 PD 分离的调度模型,证明在同样 SLO 下吞吐可 2-4×。
  • Splitwise(Microsoft):Azure 的生产方案,定义了 prompt phase / token phase。
  • Mooncake(Moonshot AI / Kimi):国内代表。开源 mooncake transfer engine,用 KVCache Store 做集中式 KV 管理,RDMA 传输 KV,内存池做二级缓存,在 Kimi 的超长上下文服务上大规模落地。
  • DeepSeek-V3 Serving:DeepSeek 官方披露的 serve 方案,H800 集群上做 PD 分离,prefill 节点 EP 更大(MoE 专家并行),decode 节点 MLA 压 KV,两类节点异构部署,成本做到业界惊人水平。
  • vLLM / SGLang:近期版本也在路由层支持 PD 分离(--disaggregated)。

分离方案目前仍在快速演进。对多数团队,先用合一部署 + continuous batching + chunked prefill 能满足大多数负载;当单副本 P95 打不住,或 MoE / 超长上下文场景下,再考虑上分离方案。

8.4 PD 分离的工程挑战

  • KV 传输的时间预算:decode 每步 30ms,KV 传输必须在 1 个 decode step 内完成。4K 上下文 GQA 模型的 KV 大约 1-2 GB,200 Gbps RDMA 单向传输约 40-80ms,单向就已经打满预算。解法:传输与 decode 的首步并行、层粒度流式传输(第一层传好就开始算)、或干脆要求更高带宽(400G / NVLink 域内)。
  • Router 的调度策略:要同时考虑 prefill 池队列长度、decode 池 KV 占用、跨池传输延迟。热点 prompt 还要尽量调度到同一 prefill 节点以命中 prefix cache。
  • 故障域:prefill 节点挂掉,其上的请求可以重做;decode 节点挂掉,所有在途请求的 KV 都丢失,用户体验直接中断。decode 节点的可用性要求更高,KV 多副本是未来方向。
  • 配比动态性:白天 RAG 多(prefill 重),夜里 Agent 多(decode 重),两池子大小不同。需要弹性伸缩策略和跨池动态转换能力。

8.5 何时”不”分离

PD 分离不是银弹。以下场景保持合一部署更划算:

  • 单副本规模不大(< 8 卡),运维复杂度不值得。
  • 输入输出较均衡,chunked prefill 已经够用。
  • 跨节点带宽不够(< 100Gbps RDMA),KV 传输会成为瓶颈。
  • 模型很小(7B 以下),单步 decode 本身就很快,收益边际递减。

九、KV Cache 复用

9.1 Prefix Caching

真实业务里同一个 system prompt 被成千上万次复用:“你是一个专业助手…(2000 字设定)…请回答用户问题”。没有 prefix caching 的引擎,每次都要从头 prefill 这 2000 token,纯浪费。

Prefix caching 的思想:把最近处理过的 prefix 的 KV Cache 保留在显存(或下沉到 CPU / NVMe),新请求来时查前缀命中,命中的段直接复用 KV,只 prefill 新增部分。

命中一段长度 L 的前缀,直接省掉 L 个 token 的 prefill 时间 —— RAG / 长 system prompt / 多轮对话场景下这是数量级的加速。

9.2 Radix Tree:SGLang 的贡献

SGLang 把 prefix 组织成 Radix Tree(前缀树):每个节点是一段 token,边是一段 KV。新请求来时自底向上匹配,命中多深就复用多深。

相比”哈希存整个 prefix”的平面方案,Radix Tree 天然支持多分叉复用:比如一个 system prompt 下衍生出 100 个不同用户的对话,共享前缀只存一份,每个对话的私有部分挂在前缀节点下。这对 Agent 场景(同一套工具定义 prompt + 千差万别的后续)特别友好。

vLLM 在 0.5+ 也支持了 prefix caching(非 radix,基于 block hash),TRT-LLM、TGI 都有各自实现。这已经是现代推理引擎的标配。

9.3 分布式 KV 存储

单节点显存装不下所有活跃 prefix,于是出现多级:GPU HBM → CPU DRAM → NVMe → 分布式 KV 服务。Mooncake 的 KVCache Store、阿里 PAI-EAS 的 KV 分层、字节的 ByteCache 都是这条路线。命中远端 KV 也比重新 prefill 快(因为长 prompt 的 prefill 本身就是几百 ms)。

9.4 共享前缀的 attention 正确性

有个坑:prefix cache 命中后,不同请求共享同一段 KV,attention 计算在”共享段”上要正确处理 causal mask 和位置编码。RoPE 这类相对位置编码天然兼容(同样的 token 在同样的位置),ALiBi 也类似。但如果位置编码依赖 batch 内全局坐标,就会出错。主流引擎(RoPE 为主)已经默认兼容。

9.5 命中率观测

生产上 prefix cache 是”看起来有用,实际看命中率”。必须暴露指标:

prefix_cache_hit_tokens_total / prefix_cache_miss_tokens_total
prefix_cache_hit_rate(tokens 维度,不是 requests 维度)
prefix_cache_size_bytes / prefix_cache_evictions_total

RAG 类业务命中率往往不高(每次 context 都不同);Agent / 系统 prompt 驱动的业务命中率能到 70%+,TTFT 直接折半。

十、采样策略

生成 token 的时候,最后一层 logits 要转成一个具体的 id。策略不只影响质量,也影响工程 —— 某些采样(比如 beam search)会把 batch 规模膨胀。

10.1 基础采样

  • Greedy:永远取 argmax,确定性最强,容易复读。
  • Temperature:logits / T 后 softmax。T<1 更锐利,T>1 更平坦。
  • Top-k:只在概率最高的 k 个里归一化采样。
  • Top-p(nucleus):累计概率超过 p 的最小集合里采样。
  • Min-p:按最大概率的比例设阈值,动态过滤。
  • Repetition penalty / Presence penalty / Frequency penalty:惩罚已出现的 token,抑制复读。

一组常用默认:temperature=0.7, top_p=0.9, presence_penalty=0.1

10.2 结构化生成

不少业务要求输出是严格 JSON / SQL / XML。主流方案是 约束解码:在每一步采样前,按文法规则屏蔽掉非法 token 的 logits,只在合法集合里采。代表实现:Outlines、xgrammar、lm-format-enforcer,以及 SGLang、TRT-LLM、vLLM 的内置约束解码。

10.3 推测解码(下一篇详讲)

简单提一下:用一个小”草稿模型”一次吐 k 个 candidate token,大模型并行验证(一个 forward 验证所有 k 个,因为 causal 下可以同时算)。接受的那些直接采纳,不接受的从分歧点继续。在大模型上能带来 2-3× 的 decode 加速。第 15 篇专题讨论。

10.4 采样在 batch 下的工程细节

当上百个请求拼到同一个 decode step,每个请求的采样参数可能都不同(temperature、top_p、seed 都是请求级)。主流引擎会做”向量化采样”:

logits: [B, V]  →  per-request divide temperature
                →  per-request top-k/top-p 掩码(gather + scatter)
                →  per-request categorical sample(使用不同 seed 生成的 uniform noise)

这些操作 vLLM 放在一个叫 Sampler 的融合 kernel 里完成。对高并发 decode 来说,采样 kernel 本身也能占 1-3ms,不容忽视。

十一、多模态推理

视觉语言模型(VLM)、音频语言模型(ALM)、端到端多模态模型(Qwen2-VL / GPT-4o / Gemini / GLM-4V / MiniCPM-V / InternVL)的推理链路大致是:

图像 ──► ViT / Vision Encoder ──► 一串 image tokens ──┐
文本 ──► Tokenizer ───────────────► 一串 text tokens ──┴──► LLM ──► 文本输出

工程上的独特挑战:

  • Vision Encoder 是额外的一次 forward,可能用不同的 kernel 栈(CNN / ViT),延迟要算在 TTFT 里。
  • 图像 token 很多:一张 448×448 图像常产生 256-1024 个 image tokens,视频更夸张(每帧几百 + N 帧)。Prompt 实际有效长度暴涨。
  • 变长 prefill:同一 batch 里,一个请求有 3 张图、一个请求纯文本,prefill 形状天差地别 → 必须 continuous batching。
  • 缓存策略:image token 的 KV 也能 prefix cache,同一张图反复问答的场景可以直接复用。
  • 输出多模态(图 / 语音)需要引入 diffusion / vocoder 模块,推理链路更长,不在本篇范围。

11.1 视觉 encoder 与 LLM 的分离

大厂的工程实践里,Vision Encoder 通常和 LLM 分开部署:

  • Vision Encoder 相对小(几百 MB 到 几 GB),但输入分辨率可变、batch 也不好凑。
  • LLM 部分沿用现有推理引擎。
  • 两者之间通过 gRPC 或 shared memory 把 image embedding 传过去,接到 LLM 的 prefill 之前。

分开的好处是 Vision 和 LLM 可以独立弹性:长文本场景 Vision 少用,短图多问答场景 LLM decode 少用。

11.2 视频推理的特殊性

视频输入一秒就是几十帧,token 数会爆炸。工程上:

  • 采样:不是全帧喂,而是关键帧采样或时序压缩(Qwen2-VL 的 M-RoPE、Gemini 的 chunk 处理)。
  • 长上下文: 一段 3 分钟视频 token 化后可达 10 万级,对 KV 压力极大,MLA / KV 量化几乎是必需。
  • 流式视频:在线场景一边 encode 一边 prefill,类似音视频 codec 的 pipeline 思想。

主流引擎对多模态支持:vLLM、SGLang 均已原生支持主流 VLM;TRT-LLM 需要针对性插件;国内推理平台(火山方舟、阿里 PAI-EAS、腾讯 TI、百度千帆)都把 VLM 作为一等公民。

十二、动手:最简 Decode 循环

把所有概念落到一段可跑的 PyTorch 代码里。这里用 HuggingFace 的一个小模型演示手写 decode + KV cache。

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

MODEL = "Qwen/Qwen2.5-0.5B"   # 任意小 decoder-only 模型都行
tok = AutoTokenizer.from_pretrained(MODEL)
model = AutoModelForCausalLM.from_pretrained(
    MODEL, torch_dtype=torch.float16
).cuda().eval()

@torch.inference_mode()
def generate(prompt: str, max_new=64, temperature=0.7, top_p=0.9):
    # === Prefill ===
    input_ids = tok(prompt, return_tensors="pt").input_ids.cuda()
    out = model(input_ids, use_cache=True)
    logits = out.logits[:, -1, :]         # 只要最后一位
    past_kv = out.past_key_values         # [(K0,V0), (K1,V1), ...] per layer

    generated = [input_ids]

    # === Decode loop ===
    for _ in range(max_new):
        # 采样
        if temperature == 0:
            next_id = logits.argmax(-1, keepdim=True)
        else:
            probs = torch.softmax(logits / temperature, dim=-1)
            # top-p
            sorted_p, sorted_i = torch.sort(probs, descending=True)
            cum = sorted_p.cumsum(-1)
            mask = cum - sorted_p > top_p
            sorted_p[mask] = 0
            sorted_p /= sorted_p.sum(-1, keepdim=True)
            pick = torch.multinomial(sorted_p, 1)
            next_id = sorted_i.gather(-1, pick)

        generated.append(next_id)
        if next_id.item() == tok.eos_token_id:
            break

        # 只把新 token 送进模型,KV cache 继续增长
        out = model(next_id, past_key_values=past_kv, use_cache=True)
        logits = out.logits[:, -1, :]
        past_kv = out.past_key_values

    return tok.decode(torch.cat(generated, dim=1)[0], skip_special_tokens=True)

print(generate("解释一下什么是 KV Cache:", max_new=128))

这段代码做了两件关键的事:

  1. 第一次 model(input_ids)Prefill,建立所有层的 KV Cache,返回 past_key_values
  2. 之后每一步只喂 1 个新 token,配合上一次的 past_key_valuesDecode,HF 内部会把新 K/V append 上去。

past_key_values=past_kv 这一行改成 None,强制每步重算整个前缀,你会立刻感受到几十倍的速度差 —— 这就是 KV Cache 的价值。

想看 KV Cache 的真实大小,加一行:

k0, v0 = past_kv[0]
print(k0.shape, k0.dtype)     # e.g. [1, num_kv_heads, seq, head_dim] fp16
total = sum(k.numel()*k.element_size() + v.numel()*v.element_size()
            for k, v in past_kv)
print(f"KV bytes: {total/1024/1024:.2f} MB")

对 Qwen2.5-0.5B + 4096 上下文,这个值大约几十 MB;换成 LLaMA-2 70B,同样上下文就是上面算过的 ~1.3 GB。

十三、引擎选型思路预告

13.1 一个推理引擎的内部结构

站在这里回头看,一个现代推理引擎的骨架大致是:

         ┌──────────────────────────────────────────┐
  HTTP   │  API Server(OpenAI 兼容 / gRPC)          │
  ───►  │  · 鉴权、限流、日志                          │
         │  · 把请求放入 RequestQueue                 │
         └────────────────┬─────────────────────────┘
                          ▼
         ┌──────────────────────────────────────────┐
         │  Scheduler(continuous batching 核心)     │
         │  · admit / preempt / schedule             │
         │  · prefix cache 查表                       │
         │  · 混合 batch(prefill chunks + decodes)  │
         └────────────────┬─────────────────────────┘
                          ▼
         ┌──────────────────────────────────────────┐
         │  Model Executor(可多 worker / TP / PP)   │
         │  · Paged KV Manager(block table)         │
         │  · CUDA Graph 池                          │
         │  · Flash / FlashDecoding / 量化 kernels    │
         │  · Sampler(fused)                        │
         └──────────────────────────────────────────┘

每一层后面都会有专文。但凡一个 production-grade 引擎,少不了这四层。各家差别在于:调度策略谁更聪明、KV 管理谁更省、kernel 谁更快、API 谁更兼容。

13.2 最终决策

下一篇会详聊 PagedAttention 与 Continuous Batching 的实现细节,第 13 篇做 vLLM / SGLang / TRT-LLM / TGI / LMDeploy 的横向对比。这里先给一张决策概览:

场景 推荐起点
通用 HF 模型 / 快速上线 vLLM(生态最广、默认 continuous batching + paged attention + prefix cache)
极致 TTFT / 复杂 prompt 编排 / Agent SGLang(RadixAttention、Python DSL)
NVIDIA 生产级 / 极致单卡吞吐 / 量化 TensorRT-LLM(闭源友好、FP8 / INT4 最强)
HuggingFace 官方 serve TGI
国产卡 / 华为昇腾 MindIE;寒武纪 MagicMind;海光 DTK;摩尔线程 / 沐曦自研
国产云托管 火山方舟、阿里 PAI-EAS、腾讯 TI-ONE、百度千帆

十四、小结与常见误区

14.1 核心要点回顾

  1. LLM 推理分 Prefill + Decode 两阶段,前者 compute-bound,后者 memory-bound。
  2. KV Cache 是推理第一优化对象;显存主要被它吃。GQA / MLA / KV 量化 / 驱逐是四条正交路线。
  3. Continuous Batching 是吞吐提升的关键;Chunked Prefill 解决 prefill 阻塞 decode。
  4. Prefill/Decode 分离 是大规模 serve 的终极形态,DeepSeek、Kimi、Azure 都在用。
  5. Prefix Caching / Radix Tree 让多用户共享 system prompt 成本骤降。
  6. FlashDecoding + CUDA Graph + kernel fusion 把 decode 每毫秒榨干。
  7. 核心指标 TTFT / TPOT / Throughput,必须按 P50/P95/P99 监控。

14.2 常见误区

  • “推理就是 forward 一遍” —— 忽略了自回归 N 次 forward。
  • “batch 越大吞吐越高” —— 超出显存就 OOM,P99 也会起飞。
  • “TTFT 短就是好” —— 如果 prefill 过度抢占 decode,TPOT 会糟糕。
  • “KV 就是小东西” —— 对 70B + 长上下文,KV 比权重还大。
  • “量化一定损失小” —— KV-Int4、Weight-Int4 有坑,要校准。
  • “多模态 = 图像编码器拼 LLM” —— 工程上 prefill 形状极度动态,对调度是噩梦。

14.3 给推理工程师的 checklist

上线一个模型之前,逐项确认:

十五、延伸阅读与下一步

推理是 LLM 基础设施里发展最快、开源最活跃的一块。本篇只是从工程第一性原理搭了个骨架,后续几篇会逐层填肉:

  • 第 12 篇:PagedAttention 的数据结构、block 大小、CoW、分配回收算法;Continuous Batching 的调度策略(FIFO / priority / fairness)、抢占机制。
  • 第 13 篇:vLLM / SGLang / TRT-LLM / TGI / LMDeploy / MindIE 横向对比:支持模型、量化、性能、API、运维成熟度、踩坑实录。
  • 第 14 篇:量化工程:SmoothQuant / GPTQ / AWQ / FP8 / INT4 各自的精度损失、硬件适配、工具链。
  • 第 15 篇:推测解码(Speculative Decoding)、Medusa、EAGLE、MTP(Multi-Token Prediction),以及 DeepSeek V3 MTP 的工程落地。
  • 第 16 篇:长上下文工程:RoPE 外推、YaRN、Position Interpolation、StreamingLLM、KV Retrieval、分段注意力,以及 128K/1M 上下文的真实成本模型。

推荐在读完本篇后亲自操作一遍:

  1. 在单张 24GB 卡上用 vLLM 起一个 Qwen2.5-7B,用 benchmark_serving.py 跑 ShareGPT,观察 GPU KV cache usage 和 TTFT/TPOT 随并发变化的曲线。
  2. 关掉 --enable-prefix-caching,重新跑同一组负载,对比 TTFT 差异。
  3. 切到 --enforce-eager(关掉 CUDA Graph),对比 TPOT 尾延迟。
  4. 切换 --max-num-seqs 从 16 到 256,观察吞吐与 P99 的 trade-off。

手上有 4 张 H100 的读者,可以复现 DistServe 的 PD 分离实验,直观感受分离带来的 P95 TTFT 改善。没有硬件的读者,可以基于本篇第十二节的代码,把 KV cache enabled/disabled 切换,观察单序列速度差 —— 这是所有推理优化的”起点”。

还想深挖的方向有:

  • 推理经济学:一次 1K tokens 调用的真实成本拆解(GPU 秒 × 量化比 × 并发 × 电费),对照各家云 API 定价。第 24 篇会展开。
  • 异构硬件:H100 vs H200 vs B200 vs MI300X vs 国产卡(昇腾 910B、寒武纪思元、海光 DCU)的推理实测差异。
  • 在线学习/增量更新:推理引擎热更新权重的工程实践(LoRA 动态挂载、Prefix Tuning 切换)。

推理工程的魅力在于:模型一旦定型,剩下的每 1% 提升都来自工程。把上面的拼图一块一块拼齐,你就能把一个”能跑”的模型变成”能赚钱”的服务。


上一篇:【大模型基础设施工程】10:Checkpoint 与故障容忍 下一篇:【大模型基础设施工程】12:PagedAttention 与 Continuous Batching

参考资料

  1. Kwon et al. Efficient Memory Management for Large Language Model Serving with PagedAttention (vLLM), SOSP 2023.
  2. Yu et al. Orca: A Distributed Serving System for Transformer-Based Generative Models, OSDI 2022.
  3. Dao. FlashAttention-2 / FlashAttention-3.
  4. Hong et al. FlashDecoding++, 2024.
  5. Zhong et al. DistServe: Disaggregating Prefill and Decoding for Goodput-optimized LLM Serving, OSDI 2024.
  6. Patel et al. Splitwise: Efficient Generative LLM Inference Using Phase Splitting, Microsoft, ISCA 2024.
  7. Moonshot AI. Mooncake: A KVCache-centric Disaggregated Architecture for LLM Serving, 2024.
  8. DeepSeek-AI. DeepSeek-V2 / V3 Technical Report(MLA 章节).
  9. Ainslie et al. GQA: Training Generalized Multi-Query Transformer Models, 2023.
  10. Zheng et al. SGLang: Efficient Execution of Structured Language Model Programs, 2024.
  11. Xiao et al. StreamingLLM / H2O / SnapKV 系列 KV 驱逐论文.
  12. NVIDIA TensorRT-LLM、HuggingFace TGI、vLLM、SGLang、LMDeploy、MindIE 官方文档.

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-04-22 · architecture / ai-infra

大模型基础设施工程

面向中国工程团队的大模型基础设施系列。从 GPU/CUDA/互联、训练框架与 3D 并行、vLLM/SGLang 推理引擎、量化与推测解码、RAG/Agent 到服务化、网关、可观测性与安全合规,覆盖 LLMOps 全链路。