news 2026/10/21 18:18:37

多头注意力机制与KV Cache:从原理到显存占用计算

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
多头注意力机制与KV Cache:从原理到显存占用计算

你有没有遇到过这种情况:同一个大模型,第一次提问时响应还挺快,但只要把对话变长,或者一次性喂进一大段上下文,生成速度就像被什么东西拖住了,显存占用也肉眼可见地往上爬。如果你部署过推理服务、或者本地跑过开源模型,大概率已经听过两个名词:多头注意力机制(Multi-Head Attention)和 KV cache。这篇文章就把这两个东西一次性讲清楚,从它们到底解决什么问题,到每一层计算里到底发生了什么,再到显存占用怎么算、工程上常见的坑有哪些,全按我实际调试模型时的理解来写。

这个话题适合谁?如果你是刚开始接触大模型原理、正在做推理性能优化、或者单纯想搞清楚为什么长对话推理那么慢,这篇都值得读完。我不会堆公式,重要的数学部分会配合直观解释和实际数字一起讲,尽量做到看完之后,你能自己推导出一个模型的 KV cache 大概吃多少显存。

1. 先从一次模型推理说起:慢在哪里、费在何处

我在本地跑一个 7B 量级模型的时候,做过一个简单测试:让模型写一段 200 字的短文,输入只有一句话,模型生成得很快,显卡功耗也平稳。但同样是这个模型,当我给它的对话历史增加到 4000 token 之后,每个新词的生成时间明显变长,显存占用也涨了近 2GB。当时第一反应是"模型难道越长越笨?"其实不是模型变笨了,是注意力机制的计算特性和缓存策略导致的。

要理解这个问题,得先知道自回归模型是怎么生成内容的。以 ChatGPT 这类模型为例,它不是一个字一个字"想好"再输出,而是每预测一个词,都要把当前已有的整个序列重新过一遍模型。也就是说,生成第 100 个词时,模型要看前 99 个词;生成第 101 个词时,又要重新看前 100 个词。如果每次都把之前算过的内容全部重算一遍,计算量就会随着序列长度平方级增长。这显然不划算。

于是 KV cache 这个工程优化就出现了:既然前面那些词的 Key 和 Value 已经算过了,为什么不把它们缓存下来,下次直接复用?这个想法朴素但极其有效。后面我会详细展开它到底缓存了什么、省了多少计算、又带来了什么新的问题。先把注意力机制本身讲清楚,因为 KV cache 里的 K 和 V,正是从注意力机制里来的。

2. 注意力机制的基础:Q、K、V 从哪里来,要到哪里去

2.1 从"查找资料"理解 Query、Key、Value

说起注意力机制,很多人的第一反应是三个字母:Q、K、V。但 QKV 到底代表什么,很多人是背下来的,不是理解下来的。我常用一个类比:想象你在图书馆查资料。

假设你想了解"多头注意力的数学推导",你心里会形成一个"查询",这就是 Query。你会去目录里找关键词,目录里每个条目对应一个"键",这就是 Key。如果你找到了匹配的条目,你会把对应的那本书抽出来读,书里的内容就是 Value。注意力机制做的事就是根据 Query 和 Key 的匹配程度,决定从 Value 里取多少信息。

在 Transformer 里,每个 token 会生成三个向量:一个 Query,一个 Key,一个 Value。生成方式是对输入向量分别做三次线性变换。所谓线性变换,本质上就是拿输入向量乘三个不同的权重矩阵。这三个权重矩阵是模型在训练过程中学出来的参数。

如果一句话让你记住:Q 代表"我要找什么",K 代表"我有什么可被找到的标签",V 代表"我真正提供的信息内容"。打分决定了"我该关注谁",加权求和决定了"我最终从每个人那里拿到多少信息"。

2.2 缩放点积注意力:打分与加权求和

注意力机制的核心计算可以拆成四步:

第一步,对序列中每两个 token 计算 Q 和 K 的点积。点积的结果就是注意力分数,它表示"当前这个 token 应该对另一个 token 投入多少关注"。点积越大,说明两个向量在方向上的相关性越强。

第二步,对这些分数进行缩放,也就是除以根号下头的维度大小。这一步不是玄学,后面单独解释。

第三步,沿着序列维度做 Softmax,把分数变成一组和为 1 的概率分布。这样可以保证所有历史 token 的注意力权重非负、且总量为 1。

第四步,用这些权重对不同位置的 Value 做加权求和,得到最终的注意力输出。

用一个具体的数字感受一下:假设输入序列有 5 个 token,每个 token 的维度是 512。那么注意力分数矩阵就是 5×5 的矩阵,Softmax 之后依然 5×5,最终用它对 5×512 的 Value 矩阵做加权,得到 5×512 的输出。

这个过程中的计算量是序列长度的平方关系。序列越长,注意力矩阵越大,计算和显存开销都会明显上涨。这也是长上下文推理迟迟难以做快的重要原因。

2.3 为什么点积要缩放:一个看似微小但影响巨大的细节

很多人初看注意力公式时,会对那个"除以根号 d_k"感到迷惑。为什么不直接除以 d_k?为什么偏偏是根号?

原因在于方差控制。Q 和 K 都是多维向量,如果每个维度是均值为 0、方差为 1 的随机变量,那么两个向量的点积,其方差会随着维度数 d_k 线性增长。也就是说维度越大,点积结果的绝对值越大。等你把它送进 Softmax 时,容易进入梯度极小的饱和区,导致训练时候梯度消失,模型学不动。

除以根号 d_k 之后,点积的方差被拉回 1 左右,Softmax 的输入分布就不会因为维度过大而过于极端。

我曾经自己跑过一个对比实验:同样的模型结构,去掉缩放项,训练时损失收敛明显变慢,而且容易不稳定。加上缩放之后,训练曲线立刻正常。这个细节在调试自定义 Transformer 时非常值得注意。

3. 多头注意力:一个头不够,那就多来几个

3.1 多头到底在做一件什么事

单头注意力的能力是有限的。它就像一个学生读文章时,只能从一种角度理解内容。但一篇文章里的关系是多样的:有的词之间是语法关系,有的词之间是语义关联,有的词之间有指代关系。单头注意力很难同时捕捉这么多层次的信息。

多头注意力的思路很简单:不搞一个头,而是把模型维度切分成多份,让每个头在低维子空间里独立做注意力。每个头可以学习到不同的关系模式,最后把所有头的输出拼起来,再做一次线性投影。

一个好的说法是:多头注意力不是让一个模型学得更用力,而是让多个"小专家"从不同角度观察同一个序列,再把意见汇总。

3.2 完整的计算流程与矩阵维度

假设模型的隐藏维度 d_model 是 512,头数 h 是 8,那么每个头的维度 d_k 就是 512 / 8 = 64。

具体流程如下:

第一步,输入向量分别经过三组线性变换生成 Q、K、V,各自的维度都是 512。

第二步,把 Q、K、V 按头数切分。512 维向量切成 8 份,每份 64 维。于是第 i 个头拿到的 Q_i、K_i、V_i 维度都是 64。

第三步,每个头独立计算注意力输出,得到维度为 64 的输出向量。

第四步,把 8 个头的输出拼接起来,恢复成 512 维。

第五步,经过输出投影矩阵 Wo(形状是 512×512),得到最终的多头注意力输出。

如果写成伪代码,可以这样看:

def multi_head_attention(x, W_q, W_k, W_v, W_o, num_heads): # x: [seq_len, d_model] Q = x @ W_q # [seq_len, d_model] K = x @ W_k V = x @ W_v d_model = x.shape[-1] d_head = d_model // num_heads # 拆头: [seq_len, num_heads, d_head] Q = Q.reshape(seq_len, num_heads, d_head).transpose(0, 1) K = K.reshape(seq_len, num_heads, d_head).transpose(0, 1) V = V.reshape(seq_len, num_heads, d_head).transpose(0, 1) # 每头独立做注意力 scores = Q @ K.transpose(-2, -1) / sqrt(d_head) weights = softmax(scores, dim=-1) out = weights @ V # [num_heads, seq_len, d_head] # 合并头,再做输出投影 out = out.transpose(0, 1).reshape(seq_len, d_model) out = out @ W_o return out

这里有一个容易搞混的点:在推理时我们经常说"一个 32 层模型有 32 个头",其实指的是每层有 32 个注意力头,而不是整个模型只有 32 个头。层数、头数、头的维度是三个独立的超参数。

3.3 多头带来的实际价值:不同头关注不同的内容

我在训练一个自研小模型时,曾经把注意力头的权重可视化出来。比较明显的是一个规律:有的头高度关注相邻词之间的关系,表现出很强的局部性;有的头会跨越很长距离去关注句子开头的主题词,表现出全局依赖;还有的头会关注句法结构里的主语和谓语。这些模式不是人为设计的,是模型在训练中自然形成的。

这也是多头相对单头的核心优势:并行的多个子空间让模型有容量去同时建模不同类型的依赖关系。在机器翻译、文本分类、代码生成等任务上,多头基本上都比单头有效。

不过头数也不是越多越好。头数太多,每个头的维度被压缩得很小,单个头的表达能力反而下降。而且头与头之间可能出现冗余。在工程实践中,8 头到 32 头是比较常见的范围,具体头数需要结合模型规模和任务类型来调。

4. KV Cache:推理加速背后的"缓存魔法"

4.1 生成式模型的两场"考试":预填充与逐字生成

理解了注意力机制,就可以解释 KV cache 了。在自回归生成中,模型把一个输入序列从头到尾计算一遍,称为 Pre-fill 阶段,也就是预填充阶段。这个阶段把用户输入一次性处理完,生成第一个输出 token。

之后进入 Decode 阶段,也就是逐字生成阶段:每生成一个 token,都要把新 token 拼到已有序列后面,再做一次完整的前向计算。

如果没有 KV cache,每次生成新 token 时,模型都要对历史所有 token 重新计算一遍 Key 和 Value。这就像考试时每做一道新题,都要把前面所有题目重新读一遍,而不是只看一眼自己的草稿纸。这种做法既浪费计算,又消耗时间。

KV cache 做的事就是:在预填充阶段算完所有历史 token 的 K 和 V 后,把它们缓存住。后续每生成一个新 token,只需要计算当前 token 的 Q、K、V,用它的 Q 去和缓存的所有 K 做注意力,再用注意力权重对缓存的所有 V 加权求和。历史 token 的 K、V 完全不需要重算。

4.2 KV Cache 到底缓存了什么、放在哪里

KV cache 缓存的具体内容是每一层 Transformer 中注意力模块的 Key 和 Value 矩阵。模型每一层都有自己的 K 和 V,所以缓存也是分层存的。

形状方面,假设 batch size 为 1,当前序列长度为 S,模型有 L 层,每层有 H 个头,每个头的维度为 D,那么每层的 K 缓存形状是 [S, H, D],V 缓存同样为 [S, H, D]。全部层加起来就是 2 × L × S × H × D。

很多人会忽略的一个点是 KV cache 的存储位置。它一般不在显存中单独划一块"高速缓存区",而是分配在推理框架管理的内存池里。比如当前主流的推理引擎,在启动时会预先分配一块显存,专门用于存放不断增长的 KV cache,而不是每次动态申请。这样做的原因是动态申请显存开销大,而且容易产生碎片。

我在本地跑推理时观测到,当对话长度逐渐变长时,显存占用会稳步上升,而上升的这部分几乎全部来自 KV cache 的增长。模型参数占用的显存是固定的,差异完全在 KV 缓存上。

4.3 显存占用怎么算:带数字的实战计算

这个计算是理解 KV cache 开销的必修课。公式如下:

KV cache 显存 = 2 × 层数 × 头数 × 头维度 × 序列长度 × batch size × 字节数

公式里的 2 是因为 K 和 V 各占一份。字节数取决于精度,FP16 是 2 字节,FP32 是 4 字节,INT8 是 1 字节。

用实际例子来感受一下。假设模型是 7B 参数,32 层,每层 32 个头,每个头维度 128。那么隐藏维度就是 32 × 128 = 4096。如果序列长度是 2048,batch size 是 1,使用 FP16:

每个 token 在每层需要的 K 缓存大小是 32 × 128 × 2 字节 = 8192 字节,V 缓存同样 8192 字节。一层加起来是 16384 字节。32 层就是 524288 字节,约 0.5 MB。再乘以序列长度 2048,得到约 1 GB。

所以一个 7B 模型,在序列长度 2048 时,单条请求的 KV cache 大约占 1 GB 显存。如果你用 batch size 8 同时处理 8 个请求,那就是 8 GB。这时候你会意识到:模型权重可能只占 14 GB 左右,但一批并发请求的 KV cache 就能轻松超过权重大小。

这个计算还可以反推:如果你想在 24 GB 显存的卡上跑 7B 模型,模型权重 FP16 约 14 GB,剩下约 10 GB 给 KV cache,大概能支撑 batch size 4、序列长度 2048 的并发。超过这个规模,就得用量化或者其他优化手段。

5. KV Cache 在实际工程中的常见问题与优化思路

5.1 显存不够怎么办:浅谈 GQA、PagedAttention 与量化

KV cache 最大的工程问题就是显存。为了解决它,业界出现了几个有代表性的优化方向。

第一个方向是减少 KV 缓存本身的数量。典型做法是分组查询注意力(GQA)和多查询注意力(MQA)。在标准多头注意力中,每个头都有自己的 K 和 V。GQA 的做法是让多个 Query 头共享一组 K、V 头。比如 32 个 Query 头共享 8 组 K、V 头。这样 KV cache 直接缩小到原来的四分之一。很多开放权重模型的 32B、70B 版本都在用这种结构。

第二个方向是解决显存碎片和动态分配问题。PagedAttention 的思路借鉴了操作系统的虚拟内存分页机制,把 KV cache 分成固定大小的块,按需分配,避免碎片化。这在长上下文场景里提升明显。

第三个方向是 KV cache 量化。把 FP16 的 K、V 值压缩成 INT8 甚至 INT4,能大幅减少显存占用。代价是有一定精度损失,需要根据任务实测效果决定是否可用。

我在实际部署中用过 INT8 的 KV cache 量化,对于通用对话任务,效果差别很小,生成速度没有明显变化,但显存占用降低了不少。对于需要精确推理的任务,比如数学推理或代码生成,建议先做验证再启用。

5.2 长上下文场景下,KV cache 的另一个隐患

长上下文场景里,KV cache 不只是显存变大这么简单。还有一个容易被忽视的问题:随着序列变长,模型需要从更多的历史信息里检索相关内容,注意力分布会被稀释。而 KV cache 存储的 Key 与 Value 是原始精度的,一旦量化过度,长距离依赖信息会丢失,模型的表现会有明显下降。

另外,某些推理框架为了快速访问 KV cache,会把 K、V 矩阵搬运到 GPU 上做计算,而 KV cache 本身也在显存里。即使显存够用,带宽也可能成为瓶颈。序列越长,每次生成新 token 需要读取的 KV cache 数据量越大,这块读取时间往往超过了计算本身。

我调过一个长文档问答项目,输入文档 8000 token,生成第一个 token 之前,预填充阶段耗时约 2 秒,但之后每个 token 的生成延迟也明显比短文本高。后来用 profiling 工具一看,时间大部分花在了从 KV cache 读取数据上。这种场景下,单纯增加计算卡解决不了问题,瓶颈在内存带宽。

5.3 给实际部署和实验的一些建议

根据我个人踩坑的经验,如果你要在自己的项目里用到 KV cache,下面这几条建议很有参考价值。

第一,先估算后优化。在你选推理框架之前,用前面那个公式算一下自己业务的 KV cache 峰值。别等跑起来显存爆了再查,那时定位问题会非常痛苦。

第二,别盲目上量化。KV cache 量化确实省显存,但它和模型权重量化叠加时,效果需要实测。我建议先用原始精度跑通业务,再做量化对比,用业务指标说话。

第三,留意框架的预分配策略。有些推理框架默认预留大量显存给 KV cache,即使当前请求的序列很短。如果你同时要做模型训练或多路推理服务,记得调整内存池的上限,否则会出现"明明显存还很多,但新请求被拒绝"的现象。

第四,如果序列长度差异很大,考虑动态批处理。把不同长度的请求打包在一起跑,能显著提升 GPU 利用率,但会增加调度复杂度。

6. 最后说点实践里的真实感受

多头注意力和 KV cache 这两个机制,一个是模型能力的地基,一个是推理效率的骨架,二者原理上各自独立,工程上却紧密耦合。模型的层数、头数、头维度决定了 KV cache 的体量,而 KV cache 又反过来限制了你能处理多长的序列、能支撑多大的并发。

我在不少项目里看到过类似的误区:有人一味增大模型规模,却不关注注意力头的配置是否合理;有人把 KV cache 当作黑盒,只管开大缓存池,结果显存余量被吃得干干净净。其实只要把这两个机制的核心逻辑想明白,大半性能问题都能提前规避。

最后送一个实操小技巧:调试模型推理性能时,先把 KV cache 的显存占用单独打点打印出来,同时记录每轮生成的 token 延迟。把这两条曲线叠在一起看,你往往能找到性能拐点。之前我就是在一条增长曲线里发现某个推理框架在序列超过 4096 后会有异常的带宽抖动,换掉之后速度立刻恢复正常。这些散落在实现细节里的坑,不亲手踩一遍,光看文档是体会不到的。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 18:18:26

AGENTS.md 编写指南:给 AI 编程助手的项目边界说明书

1. 先搞清楚:AGENTS.md 到底是谁在看1.1 从一次改错文件开始的反思前阵子我接手了一套跨平台数据同步系统的日常维护,代码规模不算大,但结构很绕,平台相关逻辑和公共逻辑混在一起,生成文件也不少。为了提升效率&#x…

作者头像 李华
网站建设 2026/10/21 18:18:03

微信校验文件不用文件

H5 站刚迁完,HTTPS 正常,接口正常,页面也正常。接下来要接微信,第一道门槛是「业务域名」。 业务域名,是微信允许你在微信内置浏览器里直接打开的域名白名单。不在白名单里的域名,用户在微信里点开就会被微…

作者头像 李华
网站建设 2026/10/21 18:15:27

自建代码评审自动化流水线:从人工失效到机器兜底

1. 从“代码评审”四个字说起:为什么我又造了一个轮子“open-code-review”这个标题,乍一看像是一个开源项目名,实际上它更像是一类需求的统称——把代码评审这件事,从“靠人盯”变成“靠流程和工具兜底”。我最初动这个念头&…

作者头像 李华
网站建设 2026/10/21 18:13:13

私域运营新范式:“七新”增长模型重塑用户资产与增长战略

1. 内容整体设计与思路拆解 1.1 为什么“七新”增长模型此刻被推上台前 过去三四年,我见过太多品牌方在私域里反复折腾同一个问题:加了十几万好友、建了几百个群,结果销售额没见涨多少,反而是运营团队越做越累、用户越来越沉默、…

作者头像 李华
网站建设 2026/10/21 18:13:01

AIGC检测率过高?九款降AI率工具实测与选型指南

如果你正在读专科,对这套流程应该不陌生:AI辅助写了初稿,交之前顺手测一下AIGC检测率,弹出的数字直接让人心凉半截——38%、46%,甚至超过60%。熬夜改到凌晨两点的结果,往往只是把几个词换成同义词&#xff…

作者头像 李华