Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

推理技术综述

在第 11 到 14 章,我们实现了模型推理的四项基本功能:采样策略、停止条件、键值缓存和批量推理。它们回答了两个问题:怎样控制模型生成,怎样让模型生成得快一点。

但是,我们实现的基本功能和工业界的推理系统之间还有很大的差距。今天的大模型服务,动辄同时处理成千上万个请求,模型参数达到数百亿甚至上万亿,推理的每一个环节都经过了极致的优化。

本章我们来梳理一下模型推理领域的主流技术。

大模型技术演进很快,本章内容截至 2026 年 11 月。

推理为什么慢

每次推理包含两个不同的阶段:

几乎所有的推理优化,都围绕三个目标:

KV 缓存的显存占用

键值缓存是用空间换时间,但是空间也不免费。每个词元的缓存大小为:

size=2×L×Hkv×dhead×bytes\text{size} = 2 \times L \times H_{kv} \times d_{head} \times \text{bytes}

这里:

以 Llama-2-7B(32 层、32 个键值头、每头 128 维、FP16)为例,每个词元占用 512 KB,一个 4096 词元的序列就是 2 GB。同时服务 32 个请求,仅缓存就要 64 GB,比模型本身(约 14 GB)还大。

而 Llama-3-8B 使用 GQA,只有 8 个 KV 头,每个词元只需要 128 KB,同样的序列只要 0.5 GB。这就是为什么 GQA、MLA 能成为主流:它们直接缩小了缓存。

衡量指标

评价一个推理系统,需要从用户体验和系统效率两个角度:

这些指标之间存在权衡:批大小越大,吞吐越高,但是每个请求的延迟也越高。推理系统的设计,就是在延迟目标(SLO)约束下,把吞吐尽可能提高。

模型压缩

解码的瓶颈是读取数据,那么最直接的办法就是:让模型变小。

量化

量化(Quantization):用低精度的整数(INT8、INT4)代替 FP16 或者 FP32 表示参数。显然,量化可以减少显存占用,加速解码。常见的量化方案:

量化是有损的。通常 INT8 几乎无损,INT4 有轻微的精度损失。训练后量化(PTQ)最常用,需要更高精度时也可以在训练中引入量化(QAT)。

剪枝

剪枝(Pruning):去掉模型中不重要的部分:

注意力优化

注意力是推理中最特殊的部分:它的计算量随序列长度增长,缓存也随序列长度增长。

减少键值头

MQA、GQA、MLA 的原理在预训练技术综述中已经介绍。从推理的角度看,它们的价值是直接缩小 KV 缓存:GQA 把缓存缩小到 MHA 的几分之一,MLA 缓存的是低维的潜向量,可以压缩一个数量级以上。缓存变小意味着同样的显存能同时服务更多请求,每一步读取的数据也更少。

FlashAttention

FlashAttention 通过分块计算,避免把完整的注意力矩阵写入显存,同样适用于推理的预填充阶段。

解码阶段有一个特殊的问题:每步只有一个查询词元,在长序列上如果只沿查询方向并行,GPU 利用率很低。 Flash-Decoding 把键值沿序列长度方向切分,分别计算再合并,让长上下文的解码也能充分利用 GPU。

PagedAttention

最朴素的实现,是为每个请求预先分配一块能容纳最大长度的连续缓存。但是请求的实际长度事先未知,这会造成严重的显存浪费和碎片。

PagedAttention(由 vLLM 提出)借鉴操作系统的虚拟内存:把 KV 缓存切成固定大小的块(Block),用一张块表记录每个请求的逻辑块和物理块的对应关系,按需分配。这样几乎没有碎片,还可以让多个请求共享相同的块(比如共同的提示词),并且在需要修改时再复制(写时复制)。

前缀缓存

很多请求的开头是相同的:系统提示词、少样本示例、多轮对话的历史、智能体反复调用时不变的上下文。 前缀缓存(Prefix Caching)把这些公共前缀的 KV 缓存保存下来,新请求命中后可以直接跳过这部分的预填充,显著降低 TTFT 和成本。

实现上,SGLang 的 RadixAttention 用前缀树(Radix Tree)管理缓存,vLLM 也提供自动前缀缓存。许多模型服务的 API 还提供“提示缓存”功能,命中缓存的输入词元价格更低。

KV 缓存压缩

当缓存还是太大时,可以进一步压缩:

淘汰类方法是有损的,需要在效果和显存之间权衡。

长上下文

上下文增长带来三重压力:预填充的计算量(与长度的平方相关)、KV 缓存的显存、解码时读取缓存的带宽。应对方法包括:位置编码外推(YaRN 等)、稀疏注意力和线性注意力(见预训练技术综述)、前面介绍的缓存压缩,以及用上下文并行把长序列切分到多张卡上。

需要注意:模型“支持”长上下文,不等于能有效利用。RULER 等评测发现,模型实际有效的上下文尺寸往往短于宣称的数值;“迷失在中间”(Lost in the Middle)的研究也发现,模型对位于文本中间位置的信息表现更差。

请求调度

连续批处理

我们实现的批量推理是静态批处理:一批请求一起开始,一起结束,短的请求要等待最长的请求完成。GPU 在等待中被浪费,新到的请求也必须排队等下一批。

连续批处理(Continuous Batching,也叫迭代级调度)以每个解码步为单位调度:某个请求一生成结束,就立刻把它换出,同时把等待中的新请求换入。批的组成在每一步都可以变化,GPU 始终保持满载。这是现代推理引擎的基础技术。

分块预填充

一个很长的提示词,预填充需要很长时间。如果把它和其他请求的解码放在同一批里,所有人的解码都要被它拖慢,用户会感到明显的卡顿。

分块预填充(Chunked Prefill)把长提示词切成若干小块,每一步只处理一块,并和解码请求混合在同一批里。这样既保持了较高的 GPU 利用率,又避免了解码被长预填充阻塞,TPOT 更平稳。

预填充和解码分离

预填充受算力限制,解码受带宽限制,两者对硬件的需求不同。把它们混在同一批 GPU 上,会互相干扰,也无法分别优化 TTFT 和 TPOT。

预填充和解码分离(Prefill-Decode Disaggregation)把它们放到不同的 GPU 池里:预填充池算完 KV 缓存后,通过高速网络传输给解码池。两个池可以独立扩缩容、选择不同的并行方式和硬件。代价是需要传输 KV 缓存,对网络带宽要求高。

目前 vLLM、SGLang、TensorRT-LLM 等主流引擎都已经支持这种部署方式,NVIDIA Dynamo、llm-d 等系统在上层提供多节点的编排,并根据 KV 缓存命中情况路由请求。

分布式并行

单张 GPU 放不下模型,或者需要更低的延迟时,就要把模型分布到多张卡上:

混合专家模型

MoE 模型的推理有它的特殊性。每个词元只激活少数专家,所以计算量小,但是全部专家的权重都必须驻留在显存里。解码阶段每一步读取的是被选中的专家,批大小很小的时候,几乎每个专家都只处理很少的词元,带宽利用很不划算;只有批大小足够大,每个专家才能分摊到足够多的词元。

所以 MoE 通常采用专家并行:把不同的专家放在不同的卡上,词元通过全对全通信(All-to-All)发送给对应的专家。需要同时处理专家负载不均衡、通信开销等问题。DeepSeek 等大规模 MoE 服务,往往使用几十甚至上百张卡做专家并行,并配合预填充和解码分离。

解码加速

自回归解码每次只生成一个词元,是推理延迟的根本瓶颈。能不能每一步生成多个词元?

投机解码

投机解码(Speculative Decoding)的核心思想是:用一个便宜的草稿(Draft)机制先猜测后面的若干个词元,再让大模型一次性并行验证这些猜测。大模型处理多个词元的成本,和处理一个词元几乎一样(因为解码受带宽限制,算力是闲置的),所以如果猜对了,一次前向就能得到多个词元。

验证时使用特殊的拒绝采样规则:保留符合大模型分布的前缀,在第一个被拒绝的位置按修正后的分布重新采样。这个规则保证了最终输出的分布和大模型逐个生成完全一致,是无损加速。

草稿的来源有多种:

加速比取决于接受率(草稿被接受的比例)和草稿本身的开销。批大小很大时,GPU 算力不再闲置,投机解码的收益会缩小,甚至变成负收益,所以上线前必须在真实流量下测量接受率。

非自回归生成

另一个方向是彻底放弃从左到右的逐个生成。扩散语言模型(比如 LLaDA、Mercury、Gemini Diffusion)从一串被遮蔽的词元出发,每一轮并行预测多个位置,迭代若干步后得到完整文本。

它的优势是天然的并行,缺点也很明显:整体质量通常落后于同规模的自回归模型,需要多轮去噪才能收敛,并且无法直接使用标准的 KV 缓存。目前更实用的方式,是把扩散模型当作投机解码的草稿生成器。

生成控制

更多采样策略

我们实现了温度、Top-k、Top-p。还有一些常见的策略:

关于“确定性”还有一个常被忽略的问题:即使把温度设为 0,不同批大小下,推理的结果也可能略有差异。原因是 GPU 算子对批大小不是“不变”的,不同批大小会改变浮点运算的累加顺序。在需要严格复现的场景,比如强化学习训练中对比训练和推理的概率,必须使用批大小不变的算子。

受限解码

很多应用需要模型输出严格符合某种格式:JSON、特定的函数调用格式、符合某个语法的代码。靠提示词只能提高概率,不能保证。

受限解码(Constrained Decoding)在每一步生成时,根据当前已经生成的内容,计算出下一个“合法”的词元集合,把其余词元的逻辑值设为负无穷,再做采样。这样输出就一定符合要求。

约束通常用正则表达式、上下文无关文法或者 JSON Schema 描述,编译成自动机,再和词表对齐,得到每一步的合法词元。常用的工具有 llama.cpp 的 GBNF 语法、Outlines、XGrammar 等,vLLM 和 SGLang 也内置了结构化输出。

受限解码保证了格式,但是不保证内容正确,而且过强的约束可能影响内容质量。

推理时扩展

传统上,提升模型能力的方法是训练更大的模型。推理时扩展(Test-time Scaling)提供了另一条路: 在推理时花更多的计算,换更好的答案。

推理时扩展改变了推理系统的负载特征:思维链让每个请求的输出长度大幅增加,KV 缓存和解码吞吐成为更突出的瓶颈。这也是前面所有推理优化技术越来越重要的原因。

推理引擎

前面介绍的技术,被集成在各种推理引擎中,用户不需要从头实现:

引擎特点
vLLM最广泛使用的开源引擎,PagedAttention、连续批处理、前缀缓存、投机解码、多种量化,生态最完整
SGLang强调 RadixAttention 前缀缓存和结构化输出,多轮对话、智能体场景表现突出
TensorRT-LLMNVIDIA 官方引擎,针对自家 GPU 深度优化,峰值性能高,部署更复杂
llama.cppC/C++ 实现,支持 CPU、Apple 芯片和各类消费级设备,GGUF 格式的来源
Ollama、LM Studio在 llama.cpp 等引擎之上封装的本地运行工具,开箱即用
MLXApple 为自家芯片开发的数组框架,适合 Mac 上的推理和微调
NVIDIA Dynamo、llm-d位于引擎之上的多节点编排层,负责预填充和解码分离、KV 感知的路由;Dynamo 可搭配 vLLM、SGLang、TensorRT-LLM,llm-d 基于 vLLM 和 Kubernetes

Hugging Face 的 TGI 已经进入维护模式,官方建议新部署改用 vLLM、SGLang,或者 llama.cpp、MLX 等本地引擎。

选择的一般原则:在个人电脑上本地运行,选择 llama.cpp、Ollama 或 MLX;在 GPU 服务器上提供服务,选择 vLLM 或 SGLang;追求 NVIDIA 硬件上的极致性能,考虑 TensorRT-LLM。


本章,我们梳理了推理领域的主流技术:

我们实现的 KV 缓存和批量推理,是这些技术的起点:缓存解决了重复计算,批处理提升了并行度。工业级的推理系统,是在这个基础上围绕“显存带宽”这个核心瓶颈,一层一层优化出来的。

参考资料