大模型推理为什么这么贵:从 KV Cache 到 PagedAttention 的工程细节
训练贵是一次性的,推理贵才是每天都在烧钱。

1.推理成本为什么比很多人想象中更重要
很多人第一次接触大模型时,注意力都放在训练上。
这也很正常。训练一个百亿、千亿参数模型,听起来就很吓人:几千张 GPU、海量语料、动辄几百万美元的成本,还有各种分布式训练、混合精度、梯度检查点、ZeRO、张量并行、流水线并行。这些东西天然就有一种“重工业”的感觉。
但如果把视角从实验室挪到线上业务,会发现另一个更现实的问题:训练虽然贵,但它不是每天都发生;推理才是每天都在烧钱的地方。
一个模型训练完之后,可能会被调用几百万次、几千万次,甚至更多。用户每发一句话,服务端就要做一次推理。用户多问几轮,多开几个会话,成本就会线性上涨。
尤其是大模型不像传统分类模型那样输入一次、输出一个结果就结束。它通常要一个 token 一个 token 地往外吐。你看到屏幕上文字逐渐出现的过程,背后其实是模型在不断重复执行前向计算。
所以,大模型工程里一个很核心的问题就是:模型已经训练好了,怎么让它在服务阶段尽可能便宜、尽可能快、尽可能稳定?
这篇文章不讲“AI 将改变世界”,也不讲大而空的趋势,只聊一个具体问题:大语言模型推理到底慢在哪里,为什么 KV Cache 会吃掉那么多显存,PagedAttention 又为什么能明显提升吞吐。
2. 大模型不是一次性生成答案
现在主流的大语言模型,比如 LLaMA、Qwen、Mistral、GPT 系列,基本都是 Decoder-only Transformer 架构。它们生成文本的方式是自回归的。
简单说,就是模型不会一次性生成完整答案,而是先根据已有上下文预测下一个 token,然后把这个 token 接到上下文后面,再继续预测下一个 token。
假设用户输入:
解释一下 KV Cache 的作用
Tokenizer 会先把这句话切成一串 token。模型读完这些 token 后,会预测下一个 token,比如“KV”。然后上下文变成:
解释一下 KV Cache 的作用 KV
接着模型再预测下一个 token。这个过程一直重复,直到生成结束符,或者达到最大输出长度。
这个机制决定了大模型推理天然分成两个阶段:一个是 prefill,一个是 decode。
Prefill 阶段处理用户输入的 prompt。比如用户一次输入了 2000 个 token,模型需要把这 2000 个 token 全部过一遍 Transformer,算出每一层里的中间状态。这个阶段通常比较“整齐”,因为可以把整段输入并行处理,GPU 的矩阵计算利用率往往还不错。
Decode 阶段就不一样了。生成阶段每次通常只生成一个新 token。每生成一个 token,都要再跑一遍模型的一部分计算。这个过程很碎,而且有强依赖:第 100 个输出 token 没生成出来之前,第 101 个 token 根本没法算。
这就是为什么大模型生成长文本时会贵。它不是一次前向计算结束,而是不断地执行“小步前向”。如果输出 1000 个 token,decode 至少要循环 1000 次。
从用户体验上看,我们关心的是“多久开始出字”和“出字速度多快”;从系统角度看,对应的是 TTFT 和 TPOT。TTFT 是 Time To First Token,也就是第一个 token 出来的时间;TPOT 是 Time Per Output Token,也就是平均生成一个 token 要多久。
很多线上系统卡,不一定是模型本身不行,而是 prefill、decode、batch 调度和 KV Cache 管理没做好。

3. Transformer 推理到底慢在哪里
Transformer 最核心的结构是 Attention。它的直觉很简单:当前 token 在理解上下文时,需要看前面哪些 token 更重要。
数学上可以写成:

Q、K、V 分别是 Query、Key、Value。每个 token 会产生自己的 Q、K、V。Attention 做的事情,就是用 Q 去和所有历史 K 计算相关性,再用这个相关性加权 V。
问题在于,如果序列长度是 N,那么标准 Attention 里的注意力矩阵规模就是 N × N。序列越长,计算和显存压力增长越快。
这也是为什么长上下文模型看起来很诱人,但真正部署时会让人头疼。4K 上下文扩到 32K,上下文长度是 8 倍,但很多地方的压力不是简单增加 8 倍。尤其是 Attention 相关的计算和缓存,在长序列下会非常敏感。
不过推理阶段有一个特殊性:它是自回归生成。历史 token 的 K 和 V 一旦算出来,后面其实不会变。比如用户 prompt 里的第一个 token,它在第 1 层、第 2 层、第 30 层里对应的 Key 和 Value,在整个生成过程中都是固定的。既然不变,就没必要每次都重新算。
KV Cache 就是利用了这个事实。
4. KV Cache:推理优化里最基础也最致命的东西
如果没有 KV Cache,模型每生成一个 token 都要把完整上下文重新算一遍。
举个例子,prompt 长度是 1000,模型已经生成了 500 个 token。现在要生成第 501 个 token。如果没有缓存,模型就得把前面 1500 个 token 从头到尾再跑一遍 Transformer。下一步生成第 502 个 token,又要重新计算 1501 个 token。
这会浪费大量计算,因为历史 token 的很多中间结果其实早就算过了。
KV Cache 的做法是,在每一层 Transformer 里,把历史 token 对应的 Key 和 Value 存起来。后面生成新 token 时,只计算新 token 的 Q、K、V,然后把新 token 的 K/V 追加到缓存里。当前 token 的 Q 再去和缓存里的历史 K 做 Attention。
这样就把重复计算变成了增量计算。
这件事听起来很自然,但代价也很明显:显存消耗会大幅增加。
KV Cache 的大小大致和这些因素相关:
层数 × batch size × 序列长度 × hidden size × KV heads × 数据类型大小
对于小模型,这可能还好。但到了 30B、70B 这种量级,再加上长上下文和并发请求,KV Cache 会变成非常大的显存开销。
很多人部署大模型时会发现一个现象:模型权重明明能放进显存,但服务一跑起来,稍微多几个并发就 OOM。原因往往不是权重本身,而是 KV Cache 被撑爆了。
比如一个模型使用 FP16,单个数值 2 字节。每一层都要缓存 K 和 V,两份;每个请求的上下文越长,缓存越大;batch 里同时有多少请求,缓存又要乘上多少倍。如果还支持 8K、16K、32K 上下文,显存压力会非常夸张。
所以 KV Cache 是一个很典型的工程问题:它极大加速了推理,但也把瓶颈从计算转移到了显存管理。

5. 为什么普通 Batch 不适合大模型在线推理
传统深度学习推理里,batch 是很常见的优化手段。比如图像分类,把 32 张图拼成一个 batch,一次送进 GPU,吞吐会明显提升。
大模型当然也需要 batch,但它的 batch 不能照搬传统模型的做法。
原因是 LLM 请求之间差异太大。一个用户可能只输入几十个 token,输出也很短;另一个用户可能贴进来一大段文档,让模型总结;还有人让模型写代码,一写就是几千 token。
如果用静态 batch,把一批请求凑齐后一起执行,就会遇到一个很尴尬的问题:短请求早就结束了,长请求还在生成。整个 batch 的生命周期被最长的请求拖住,GPU 利用率就会变差。
更麻烦的是,在线服务不是离线跑分。用户请求是不断进来的,不可能等当前 batch 全部结束之后再处理下一批。否则延迟会很难看。
这就是 Continuous Batching 出现的原因。
6. Continuous Batching:让 GPU 尽量别空等
Continuous Batching 的思路是,batch 不是固定的一组请求,而是动态变化的。每一轮 decode 后,系统都会检查哪些请求已经完成,把它们移出去;同时看看有没有新请求可以加入进来。
这样 GPU 不会因为几个短请求结束而空出一块资源,也不会因为等待凑 batch 而增加太多延迟。
这个机制听起来像调度策略,但它和 KV Cache 管理是绑在一起的。因为每个请求加入 batch 后,都要占用 KV Cache;请求结束后,KV Cache 要及时释放;如果释放不及时或者碎片太多,新请求就进不来。
所以线上推理服务的性能,很多时候不是单纯看模型算得快不快,而是看调度器能不能把显存和计算资源安排明白。

7. KV Cache 真正麻烦的地方:不是大,而是碎
如果所有请求长度都一样,输出长度也一样,KV Cache 管理其实没那么复杂。麻烦就麻烦在真实请求长度完全不可控。
一个请求可能最终生成 100 个 token,另一个请求生成 3000 个 token。系统如果一开始就按最大长度给每个请求预留显存,浪费会非常严重。比如最大输出长度配置成 4096,但大部分请求实际只生成几百个 token,那大量显存就被空占着。
如果不预留,按需增长,又会遇到显存碎片问题。
传统方式通常希望一个请求的 KV Cache 在显存中是连续的。可在线服务里,请求不断进入、退出,缓存不断申请、释放,时间长了以后显存就会变得像一块被反复切割的土地:总面积可能还够,但很难找到一整块连续空间。
这时候新请求进来,即使理论上剩余显存足够,也可能因为没有合适的连续空间而分配失败。
PagedAttention 解决的就是这个问题。
8. PagedAttention:把操作系统分页思想搬到显存管理里
PagedAttention 的名字很直接,它把 KV Cache 管理做得有点像操作系统里的虚拟内存分页。
操作系统不会要求一个进程的虚拟内存在物理内存里连续存放,而是把内存切成固定大小的页,通过页表维护虚拟地址到物理地址的映射。这样做的好处是,物理内存可以分散使用,碎片问题会轻很多。
PagedAttention 对 KV Cache 做了类似处理。它不要求一个请求的 KV Cache 在显存中连续,而是把缓存切成固定大小的 block。对请求来说,它看到的是一段连续的 token 序列;但在物理显存里,这些 token 对应的 KV block 可以分散存放。
框架内部维护一个映射关系,大概可以理解为:
请求 A 的第 0 个逻辑 block -> 显存里的物理 block 17
请求 A 的第 1 个逻辑 block -> 显存里的物理 block 42
请求 A 的第 2 个逻辑 block -> 显存里的物理 block 8
这带来的好处非常直接。请求需要多少 cache,就分配多少 block,不需要一开始按最大长度预留。请求结束后,它占用的 block 可以立刻回收到池子里,供其他请求复用。即使物理 block 不连续,也不影响逻辑上的 Attention 计算,因为映射表会告诉 kernel 去哪里读对应的 K/V。
这就是 vLLM 能在高并发场景下表现很好的关键原因之一。它不是简单地“模型跑得快”,而是 KV Cache 管得更细。
当然,PagedAttention 不是没有代价。它引入了额外的 block table 查询,kernel 实现也更复杂。但在长上下文和高并发场景下,节省下来的显存和提升的 batch 容量,通常远大于这点开销。

9. FlashAttention:Attention 慢,有时候不是算不动,而是搬太多
KV Cache 和 PagedAttention 主要处理推理阶段的缓存和显存管理。FlashAttention 则更多是从 Attention 计算本身入手。
很多人看 Attention 的公式,会觉得瓶颈主要是矩阵乘法。但在 GPU 上,计算不一定是唯一瓶颈,显存读写同样可能很贵。
标准 Attention 往往会显式生成一个注意力矩阵。序列长度一大,这个矩阵就非常夸张。更重要的是,中间结果要写到 HBM,再读回来继续算 softmax 和后续乘法。HBM 虽然带宽很高,但和 GPU 片上 SRAM 相比还是慢得多。
FlashAttention 的思路是分块计算,不把完整注意力矩阵落到显存里。它把 Q、K、V 分块加载到更快的片上存储里,在小块范围内完成计算,并通过 online softmax 保持数值稳定。
这样做减少了大量显存读写,所以在长序列场景下效果明显。
如果说 PagedAttention 是“怎么更聪明地存 KV Cache”,FlashAttention 更像是“怎么更少地搬运 Attention 中间结果”。一个偏系统内存管理,一个偏 kernel 和 IO 优化。它们解决的问题不同,但都指向同一个目标:让 GPU 少浪费时间在不必要的等待上。

10. 显存不够时最常见的妥协
除了 KV Cache,模型权重本身也是大头。
一个 7B 模型,如果用 FP16 存,权重大约是 14GB。一个 70B 模型,光权重就要 140GB 左右,还没算 KV Cache、临时 buffer 和运行时开销。单卡部署基本不现实,多卡部署成本又会上去。
量化就是把权重用更低 bit 表示。FP16 是 16 bit,INT8 是 8 bit,INT4 是 4 bit。理论上,从 FP16 到 INT4,权重显存可以降到四分之一。
但量化不是简单地把浮点数强行转整数。模型权重分布并不均匀,不同层、不同通道对输出质量的影响也不同。粗暴量化很容易导致模型回答质量下降,尤其是数学、代码、长链路推理这类任务。
GPTQ、AWQ、SmoothQuant 这些方法,本质上都是在解决“怎么压缩得更聪明”。
GPTQ 会利用近似二阶信息,尽量让量化后的权重对输出影响更小。AWQ 关注的是少数重要权重或通道,认为这些部分对模型能力影响很大,需要重点保护。SmoothQuant 则试图处理 activation 中的异常值,把一部分量化难度从激活迁移到权重上。
生产环境里选量化方案,不能只看压缩率。更现实的做法是拿业务数据评测。比如客服场景、代码补全场景、RAG 问答场景,对精度下降的敏感程度不一样。一个 INT4 模型在通用聊天里看起来没问题,不代表它在结构化抽取或复杂推理里也没问题。
还有一个容易被忽略的点:量化以后不一定必然更快。要看硬件是否支持对应的数据类型,kernel 是否优化到位,反量化开销是否被抵消。如果只是显存降了,但计算路径不高效,实际延迟可能并不好看。
11. 投机解码:让小模型帮大模型打草稿
自回归生成最麻烦的地方,是一次只能往前走一个 token。投机解码想解决这个问题。
它的做法是引入一个小模型。小模型先快速生成几个候选 token,大模型再一次性验证这些 token 是否符合自己的分布。如果大模型接受了其中一串 token,就相当于一次 decode 前进了多步。
可以把它理解成“小模型打草稿,大模型审稿”。
这个方法能不能加速,关键看接受率。如果小模型和大模型的输出分布比较接近,大模型经常接受小模型给出的 token,那 decode step 就会减少,速度自然提升。如果小模型经常猜错,大模型频繁回退,那收益就很有限。
投机解码在一些场景下很有价值,但工程上也麻烦。你需要维护两个模型,还要处理采样一致性、batch 调度、显存分配等问题。对于短输出任务,它未必划算;对于长文本生成,如果接受率足够高,收益会更明显。
12. 线上推理服务真正要看的指标
如果只是本地跑 demo,能把模型加载起来、能输出结果就算成功。但线上服务要看的东西多得多。
最直接的是延迟。用户点一下发送,如果三四秒还没有第一个字出来,体验就会很差。所以 TTFT 很重要。TTFT 受 prompt 长度、排队时间、prefill 速度影响很大。很多 RAG 应用把一堆检索结果塞进 prompt,TTFT 上升就是必然的。
然后是输出速度。也就是用户看到文字流出来的速度。这个主要和 decode 阶段有关。decode 慢,长回答就会显得拖沓。
再然后是吞吐。服务端当然希望同一张 GPU 能服务更多请求。但吞吐和延迟经常互相拉扯。batch 做大,吞吐上去了,单个请求排队时间可能也上去了;batch 做小,延迟好看,但 GPU 利用率可能很低。
还有尾延迟。平均延迟好看没用,P95、P99 才更接近线上真实体感。几个超长 prompt 或超长输出请求,就可能把调度队列拖得很难看。
显存监控也非常关键。只看 GPU 利用率不够,还要看 KV Cache block 使用情况、活跃请求数、平均上下文长度、最大上下文长度、请求取消后的缓存释放是否及时。很多线上 OOM 是慢慢积累出来的,不是某一个请求突然把模型打爆。

13. 工程上怎么取舍
如果要做一个真正能用的大模型推理服务,我一般不会建议一开始就自己写推理引擎。可以先从成熟框架开始,比如 vLLM、TensorRT-LLM、SGLang、TGI、LMDeploy 或 llama.cpp。
不同框架适合的场景不一样。vLLM 的优势在高并发服务和 KV Cache 管理;TensorRT-LLM 更偏 NVIDIA GPU 上的极致优化;llama.cpp 适合本地和端侧部署;TGI 在服务化上比较成熟。选型时不要只看 benchmark,要看你的请求形态。
如果业务里大部分请求都是短 prompt、短输出,优化重点和长文档总结完全不同。前者更关注调度开销和 TTFT,后者更关注长上下文、KV Cache 和 prefill 吞吐。代码生成又是另一类,它经常输出很长,decode 性能会非常重要。
模型大小也要实际一点。不是所有业务都需要 70B。很多垂直任务里,一个 7B 或 14B 模型,配合好的 prompt、RAG、微调和后处理,效果已经够用。模型越大,部署成本、延迟、扩容难度都会上去。模型能力提升带来的收益,必须能覆盖这些成本。
还有输入输出长度一定要限制。线上系统如果不限制 max input tokens 和 max output tokens,很容易被少数异常请求拖垮。用户粘贴一整本手册进来,或者让模型无限续写,都会让 KV Cache 快速膨胀。工程系统不能只假设用户正常使用。
取消请求也要处理好。用户关掉页面、网络断开、前端停止接收时,后端应该尽快释放对应请求的 KV Cache。否则这些“幽灵请求”会继续占显存,最后导致服务莫名其妙 OOM。
14. 模型能跑起来,只是第一步
大模型推理优化不是单点技巧,而是一堆细节叠加出来的结果。
KV Cache 让自回归生成不用反复计算历史 token,但它把显存压力抬了上来。Continuous Batching 让 GPU 尽量保持忙碌,但调度器要处理不同长度请求的进出。PagedAttention 通过 block 化管理 KV Cache,减少显存浪费和碎片。FlashAttention 从 IO 角度优化 Attention,减少中间结果搬运。量化降低权重显存,但需要在质量、速度和硬件支持之间权衡。投机解码试图减少 decode 步数,但收益取决于小模型猜得准不准。
这些东西单独看都不算神秘,真正难的是放到一个线上系统里一起工作。
如果你只是调 API,这些细节可以暂时不用管。但只要你开始自己部署模型,尤其是要控制成本、支撑并发、保证延迟,就迟早会碰到这些问题。
模型能不能跑起来只是第一步。
跑得稳、跑得快、跑得便宜,才是推理系统真正的难点。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)