你以为模型在思考,其实它在算账
每次你打开聊天界面,盯着屏幕,等第一个字蹦出来,那几百毫秒的沉默,其实是显卡在拼命烧算力。
第一个词慢得让人怀疑人生,后面的词却像机关枪一样哒哒哒往外冒。
这背后没有神秘学,没有玄学,只有一个朴素到爆炸的工程决策:缓存。
大型语言模型的核心结构叫“Transformer结构”:听着像变形金刚很酷,干的活其实很朴实——它接收一串词元token,把每个词元token变成一串数字向量,然后通过一层层计算,给出下一个词元token的概率。
Transformer其实是个流水线工厂在批量生产隐藏状态
Transformer是一个超级流水线工厂,输入的每个词元token都会在这条流水线上走一遭,最终每个词元token都拿到属于自己的隐藏状态包裹。
这些隐藏状态接着被投射到词汇表空间,变成一堆叫logits的分数,每个分数对应词汇表里一个词元token的当选概率。
听起来很公平对吧,每个词元token都有发言权。但真相残酷得很,只有最后一个词元token的logits才真正有用。
从这个概率分布里采样出下一个词元token,把这个新词元token贴到输入序列末尾,然后整个流程再来一遍。
这就是自回归生成的本质,一个贪吃蛇一样不断吞噬自己尾巴的过程。
关键洞察在这里炸裂,要生成下一个词,你只需要最新那个词元的隐藏状态,其他所有隐藏状态都是中间过程的工业废料,用完就该扔的那种。
整个流程可以这么理解:
- 输入一堆词元token :
每个词元token先变成向量,这叫嵌入向量。可以理解为把文字翻译成数字坐标。 - 每个词元token 变成一个隐藏状态:
嵌入向量经过多层计算后,逐层演化成隐藏状态。隐藏状态不是一开始就存在的,它是“计算过程中的中间表示”。
当一个词元进入模型:
第一步查嵌入矩阵得到一个固定向量这个向量只是静态表示,还没有上下文信息。
第二步加上位置信息因为模型本身只看到一堆向量,它需要知道顺序。
第三步进入第一层。
从这一层开始,每一层都会输出一组新的向量。这些向量就叫隐藏状态。
关键点来了:隐藏状态是“层级演化”的。
第一层有一组隐藏状态第二层又产生新的隐藏状态一直到最后一层
每一层都会让每个词元的表示更“理解上下文”。隐藏状态本质是“当前层对每个词元的上下文编码”。 - 隐藏状态再映射成词元表分数
- 从最后一个词元token 的分数里抽样
- 得到下一个词元token
- 把新词元token 拼回去
- 继续循环
听着像流水线工厂对吧?没错,就是流水线。
关键点来了:真正决定下一个词元的,只是“最后那个词元”的输出结果。前面所有词元的输出都属于中间产物。
你每天看到的“智能涌现”,底层全是重复劳动。
模型以前每生成一个新词元token,都把前面所有词的中间结果再算一遍。
显卡累得想罢工。
注意力机制到底在干嘛
注意力机制里的三角恋关系决定了谁该被记住
钻进Transformer的每一层内部,每个词元token都会被强制分配三个身份向量,一个叫查询Query向量,一个叫键Key向量,一个叫值Value向量。
可以把它想成:
- Q 是“我想找什么”
- K 是“我有什么标签”
- V 是“我携带的信息”
注意力机制的计算过程就像一场大型相亲现场,查询向量跑去跟所有键向量打分配对,然后用这些配对分数给值向量加权求和。
计算方式是:
- 用 Q 去和所有 K 做匹配打分
- 打分结果加权所有 V
- 得到输出
现在把目光死死锁定在最后一个词元token身上,这家伙的注意力计算只关心两件事,自己的查询Query向量长什么样,以及整个序列里所有键Key向量和值Value向量的集体记忆。
所以真相浮出水面了,要算出我们唯一关心的那个隐藏状态,每个注意力层只需要最新词元token的查询向量,加上所有历史词元token的键值对。
生成第 50 个词元token时,需要第 1 到 50 个元token的 K 和 V。
生成第 51 个词元token时,需要第 1 到 51 个词元token的 K 和 V。
前面 1 到 49 个词元token的 K 和 V 早就算过,而且输入没有变化,结果当然不会变化。
但愚蠢的模型每次都要重新算一遍,这就是赤裸裸的重复劳动。
这是标准的 O(n²) 级别重复开销!序列越长,浪费越离谱。
工程师看到这种重复劳动,血压自然飙升。
KV 缓存的核心思想
工程师们拍案而起,这不行,得把键值向量存起来。于是KV缓存横空出世,核心逻辑简单粗暴,每来一个新词元,只计算它自己的查询键值三个向量,然后把新的键值向量塞进缓存仓库,再从仓库里调取所有历史键值向量,用新的查询向量去跟完整的缓存键值大军做注意力计算。
思路简单到让人拍大腿:
- 每个词元的 K 和 V 只算一次。
- 算完存起来。
- 后面直接拿来用。
流程变成这样:
- 新词元进来
- 只计算这个新词元的 Q、K、V
- 把新的 K、V 追加进缓存
- 用新词元的 Q 去匹配所有缓存里的 K、V
- 得到结果
整个历史部分直接复用。
于是推理阶段变成:
- 一次完整前向计算
- 后面每步只算一个词元
速度直接起飞。
这就是KV缓存的全部秘密,每层每步只需要新增一个键和一个值,其他全部从内存读取。注意力计算本身还是要随着序列长度增长而增长,毕竟你要跟所有历史键值对视,但昂贵的投影计算从每步一次变成了每词元一次,这就是五倍速的来源。
为什么第一个词慢? 预填充阶段是显存地狱的入口
当你发出一段提示词,模型会一次性处理全部输入,这叫预填充阶段。
现在你能理解为什么第一个字慢得像蜗牛了。当你发送提示词的时候,模型必须在一趟前向传播里处理整个输入序列,计算并缓存每个词元的键值向量,这个阶段叫预填充,是整个请求里最吃算力的部分。
预填充阶段会:
- 计算每个词元的 Q、K、V
- 把所有 K、V 存入缓存
这一步计算量最大。提示越长,算得越久。
一旦缓存热起来,每个后续词元只需要单趟前向传播处理一个词元,速度直接起飞。那个让人抓狂的初始延迟有个专业名字叫首字延迟时间TTFT。
提示词越长,预填充越久,等待越煎熬。
优化TTFT的技术栈深不见底,分块预填充、投机解码、提示词缓存都是独立的大话题,但底层逻辑永远不变,构建缓存昂贵如黄金,读取缓存便宜如白菜。
这就是算力与时间的博弈。
显存爆炸催生了注意力机制的偷工减料艺术
缓存节省了计算,代价是占用显存。
每一层都要为每个词元存储 K 和 V。
模型层数越多,隐藏维度越大,缓存就越恐怖。
KV缓存的本质是用内存换计算速度。
每层都要为每个词元存储键值向量,拿Qwen 2.5 72B模型举例,80层网络,32K上下文长度,隐藏维度8192,单个请求的KV缓存就能吃掉好几GB的GPU显存。
当并发请求堆到几百个的时候,缓存占用的显存往往超过模型权重本身。
这就是分组查询注意力GQA和多查询注意力MQA存在的意义,让多个查询头共享键值头,大幅削减内存占用,质量损失微乎其微。
这也解释了为什么翻倍上下文长度这么难,窗口长度加倍,每个请求的KV缓存跟着加倍,能同时服务的用户数量直接腰斩。
内存成了规模化部署的紧箍咒,vLLM、TGI、TensorRT-LLM这些推理框架都在围绕KV缓存玩命优化。
你以为大模型吃算力,真正吃的是显存。显卡容量成了生死线。
本质总结
KV缓存干掉了自回归生成中的冗余计算,历史词元的键值向量永远不变,所以算一次存起来就完事。每个新词元只需要自己的查询键值三元组,然后注意力计算在完整缓存上跑一遍。实践中五倍速提升轻轻松松,代价是GPU显存成为规模化的瓶颈约束。
每个大模型服务栈都建立在这个思想之上,这就是现代AI工程的地基。
一句话点醒
生成式模型的效率革命,来源于“停止重复劳动”。
缓存带来速度。
显存带来成本。
工程永远在权衡。