Yiek Heng 的知识空间
首页大模型2026-10-11
大模型推理图解

从 Transformer 到 AFD.

零基础也能看懂。从"Transformer 是什么"讲起,一直讲到为什么要把 Attention 和 FFN 拆到不同的芯片上。

紫色:token、向量这类"数据本身" 绿色:搬数据、存数据(内存) 铜色:算数据(计算)
“苹果” Attention FFN Attention FFN ⋮ 重复几十层 Attention FFN 读得多Attention 主要卡在内存 算得多FFN 主要卡在计算
整份讲解就围绕这张图:模型由几十层组成,每层都是"Attention + FFN"。两块的脾气完全不同,这就是 AFD 存在的理由。
第 0 部分

先认识 Transformer

Transformer 是一种神经网络的"结构设计"。今天几乎所有大模型,比如 GPT、Claude、DeepSeek、Qwen、Llama,都建立在它上面。先从最基本的说起。

1神经网络:训练和推理

神经网络说穿了就是两样东西:一大堆可以调整的数字(叫权重,第 8 节细讲),加上一套固定的计算步骤(主要是乘法和加法)。数字从一头进去,经过这些计算,从另一头出来。

它的一生分两个阶段:

训练(Training) 例子:“今天天气很” → 正确答案“好” 模型权重可以调 它猜:“冷” 对比答案:错了 把权重往对的方向调一点 用海量文字重复几万亿次 几千张卡、几个月,只做一次 推理(Inference) 用户输入:“今天天气很” 模型权重固定不动 输出:“好” 每个用户每次提问都要做 本文讲的全部是这一边

训练:给模型看大量例子,让它猜答案;猜错了,就按一定的数学方法(叫"反向传播"和"梯度下降")把权重往正确的方向微调一点。重复无数次后,权重里就"沉淀"出了知识。

推理:训练完成后,权重固定下来,只是拿来用。你每次和 AI 聊天,背后跑的都是推理。AFD 解决的是推理的效率问题,所以本文只讲推理。

2语言模型:只会做一件事,猜下一个词

语言模型(Language Model)的任务出奇地简单:给它一段文字,它告诉你"下一个词"是每个候选词的概率是多少。

今天天气很 语言模型猜下一个词 好45% 热20% 冷12% 不错8% 其他几万个词共 15%
挑出一个词(比如"好"),接到末尾,再猜下一个……聊天、写代码、翻译、总结,全都是"反复猜下一个词"。

所谓大语言模型(LLM,Large Language Model),就是参数特别多(几十亿到上万亿)、用海量文字训练出来的语言模型。它"猜"得足够准,看起来就像在理解和思考。

3为什么是 Transformer:和 RNN 对比

在 Transformer 出现之前,处理文字主要用 RNN(循环神经网络)。它像传话游戏:一个词一个词地读,把"到目前为止的理解"写在一张小纸条上,传给下一步。

RNN:一个接一个传话 我 今天 去 了 北京 必须按顺序算:第 5 个词要等前 4 个全部算完 句子一长,开头的信息在一路传话中慢慢丢失 Transformer:每个词直接看所有词 我 今天 去 了 北京 每条线就是一次Attention

2017 年,Google 的一篇论文《Attention Is All You Need》提出了 Transformer。它的核心想法是:不要传话了,让每个词直接去看所有其他的词(这个"看"的动作就是 Attention,第 11 节细讲)。带来两个好处:

一、远距离不丢信息。第 1 个词和第 1000 个词之间也是直接相连的。

二、可以并行计算。所有位置可以同时算,不用排队。这让人们能用成千上万张 GPU 一起训练超大模型,这是大模型时代能到来的关键。

小提醒:生成回答时,Transformer 仍然要一个词一个词地往外吐(第 20 节)。"并行"指的是读入一段已有文字时,所有位置能同时处理。

4Transformer 的三种形态与整体结构

原始的 Transformer 是为翻译设计的,分成两半:编码器(Encoder)负责读懂原文,解码器(Decoder)负责一个词一个词地写出译文。后来人们发现,只用其中一半也很好用:

只有编码器 Encoder 例:BERT 擅长理解:分类、搜索 编码器 + 解码器 Encoder Decoder 例:原始 Transformer、T5 擅长转换:翻译 只有解码器 Decoder 例:GPT、Claude、DeepSeek 今天的大模型几乎都是这种

本文说的 Transformer,指的都是只有解码器(Decoder-only)的形态。下面这张图是它从头到尾的完整流程,也是整份讲解的地图。每个方框旁边标了在哪一节细讲:

文本:“今天天气很” 切成 token 第 5 节 Embedding:查表变成向量 第 6、7 节 加入位置信息 第 17 节 Attention第 11–18 节 FFN第 26、27 节 一层(第 10 节) 同样的层叠 N 次,比如 32~128 层 输出层:给词表里每个词打分 第 19 节 采样:选出 “好” 第 19 节 接到末尾,再来一遍(第 20 节) 紫色是数据,绿色是 Attention,铜色是 FFN
后面每一部分,都是在放大这张图的某一块。迷路的时候,回来看看这张地图就行。
第 1 部分

文字怎么变成数字

计算机只会算数字。所以第一步,是把一句话变成一堆数字。

5Token(词元):把句子切成小块

模型不是一个字一个字、也不是一个词一个词地读,而是用一个叫分词器(Tokenizer)的工具,把文字切成它"认识"的小块,每块叫一个 token。

每个 token 在模型的"字典"里都有一个编号。这本字典叫词表(Vocabulary),通常有几万到十几万个条目。

他把苹果吃了,它很甜 分词器 他 把 苹果 吃 了 , 它 很 甜 872 2145 30981 1504 34 11 1960 2722 8823 ↑ 每个 token 在词表里的编号(数字仅为示意)
"苹果"这种常见词可能是一个 token,少见的词会被切得更碎。粗略地说,1 个 token 约等于 1 个汉字,或者 3/4 个英文单词。
记住:模型处理的最小单位是 token。后面所有的"长度""数量"都按 token 算。

6向量和 Embedding:给每个 token 一份"档案"

光有编号不够。编号 872 和 873 挨着,不代表意思相近。所以模型会把每个编号换成一串数字,这串数字叫向量(Vector)。

这一步叫 Embedding(嵌入)。做法很简单:模型里有一张大表,每个 token 占一行,按编号查表,把那一行取出来就行。

猫 查表 … 狗 猫 汽车 … 0.18 -0.40 0.93 … 0.25 -1.10 0.71 … 0.21 -1.30 0.67 … -0.90 0.44 0.05 … … Embedding 表:每个 token 一行 0.21 -1.3 0.67 … “猫”的向量

这张表的数字不是人写的,是训练出来的。训练的结果是:意思相近的词,向量也相近。如果把向量画成空间里的点,大概是这样:

猫 狗 兔子 汽车 卡车 火车 北京 上海 真实的向量有几千个维度,这里压扁成 2 维来示意

7隐藏维度:档案有多少个栏位

一个向量里有多少个数字,就叫隐藏维度(hidden dimension),常写作 d。小模型 d 大约 2048~4096,大模型可以到 7168、8192 甚至更多。

· · · · · · 第1个 第4096个 d = 4096:每个 token 用 4096 个数字来描述

为什么这个数重要?因为每个数字都要占内存。如果用常见的 BF16 格式,每个数字占 2 字节,那么一个 token 的向量就是 4096 × 2 = 8 KB。后面算 KV Cache 有多大时,就要用到这个数。

第 2 部分

模型本体是什么

一个大模型,本质上就是"一大堆固定的数字"加上"用这些数字做乘法的规则"。

8参数 / 权重:模型的"知识"

训练结束后,模型里有一大堆数字被固定下来,这些数字叫参数(parameters),也叫权重(weights)。"70B 模型"的意思就是有 700 亿个参数。B 是 billion,十亿。

推理(也就是用模型回答问题)时,权重一个都不改,只是拿来用。但它们全都得放在芯片能快速访问的内存里,这就带来第一个硬件问题:放不放得下。

一张 GPU:80 GB 7B 模型 14 GB,轻松放下 70B 模型 140 GB,要 2 张卡 671B 模型 ≈1.3 TB 按 BF16(每个参数 2 字节)计算:显存 ≈ 参数个数 × 2

9矩阵乘法:模型里 90% 以上的计算

权重通常排成一张二维的表,叫矩阵。模型最主要的动作,就是拿一个向量去乘一个矩阵,得到一个新的向量。你可以把它理解成"按一套固定配方,把输入的数字重新调配一遍"。

输入向量(4 个数) 1 2 0 1 × 权重矩阵 4×3 201 110 322 011 = 4 3 2 输出向量(3 个数) 输出的第 1 个数 = 输入和第 1 列 逐个相乘再相加 1×2 + 2×1 + 0×3 + 1×0 = 4
每算出一个输出数字,要做 4 次乘法、3 次加法。业内一般把"一次乘法 + 一次加法"算作 2 次运算(FLOP)。

真实模型里,向量有 4096 个数,矩阵是 4096×4096。一个 token 乘一次这样的矩阵,就要 4096 × 4096 × 2 ≈ 3300 万次运算,同时还要从内存里读出 4096 × 4096 × 2 字节 ≈ 32 MB 的权重。

请先记住这对数字:"算多少"和"读多少"。第 6 部分讲芯片时,整个故事就是这两者的比例。

10层:一层一层地加深理解

Transformer 模型由几十个结构相同的层(layer)叠起来,比如 32 层、61 层、80 层。token 的向量从最底层进去,每过一层被"加工"一次,最后从顶层出来,变成"下一个 token 是什么"的概率。

输入:token 向量 第 1 层 第 2 层 ⋮ 第 N 层 输出:下一个 token各候选的概率 “甜” 62% “好吃” 21% … 一层的内部 进入 归一化 Norm Attention + 残差 归一化 Norm FFN + 残差 送往下一层

每一层里有四样东西:

Attention:让这个 token 去看看句子里的其他 token,收集上下文信息。第 3 部分详细讲。

FFN(前馈网络):每个 token 自己单独"消化思考",不看别人。第 5 部分详细讲。

归一化(Norm):把数字的大小调整到一个合适的范围,防止越算越大或越算越小。计算量很小,可以忽略。

残差(Residual):图里的虚线。把"加工前"的向量直接加到"加工后"的结果上。好处是每一层只需要学"在原来基础上改一点",而不是推倒重来,几十层叠起来也不会把信息丢光。

第 3 部分

Attention:词与词互相参考

"他把苹果吃了,它很甜。"读到"它"时,你知道它指的是苹果。模型靠 Attention 做到这一点。

11Q、K、V:每个 token 准备三样东西

每个 token 的向量,会分别乘上三张不同的权重矩阵(就是第 9 节的矩阵乘法),变出三个新向量:

“它”的向量 × W_Q × W_K × W_V Q K V Query 查询“我想找什么样的信息?” Key 标签“我身上有什么信息?”给别人来匹配 Value 内容“如果你选中我,我交给你的内容”

图书馆比喻:你带着一张写着需求的纸条(Q)去图书馆。每本书的书脊上贴着标签(K),书里面是内容(V)。你拿纸条去对比每本书的标签,越匹配的书,你就越多地参考它的内容。

12打分、Softmax、加权混合

以"它"为例,Attention 分三步:

第一步:打分。用"它"的 Q,和前面每个 token 的 K 做"点积"(逐个相乘再相加,和第 9 节一样)。越相关,分数越高。

第二步:Softmax。把分数变成加起来等于 100% 的百分比。做法是先对每个分数取 e 的幂次(让高分更突出、负分也变成正数),再除以总和。

第三步:加权混合。按这些百分比,把各个 token 的 V 混合起来,结果就是"它"在这句话里的新含义。

前面的词 他 把 苹果 吃 了 , 它 Q·K 打分 1.3-1.03.00.2-1.5-2.00.5 Softmax 后 13% 1% 73% 5% 1% 0.5% 6% 新的“它” ≈ 73% 苹果的 V + 13% 他的 V + 6% 它自己的 V + …
打分的数字是示意的。注意"它"只能看到自己和前面的词,看不到后面的,因为后面的词还没生成出来。

自己试试:拖动打分,看百分比怎么变

3.0
1.3
0.2

你会发现:分数差 1 分,比例就差约 2.7 倍。Softmax 会把最高分放大,让模型"把注意力集中"。

关键观察:为了算"它",必须读出前面所有 token 的 K 和 V。句子越长,要读的就越多。这句话是理解 KV Cache 和 AFD 的钥匙。

13为什么要分成 Q、K、V 三个?

同一个词,在 Attention 里要同时扮演三种角色:它要提问(我需要什么信息),要被别人找到(我是什么),还要交出内容(被选中时给什么)。一份向量很难同时把三件事都做好,所以用三张不同的矩阵 W_Q、W_K、W_V,从同一个词里"提炼"出三个不同用途的向量。可以把这三张矩阵想成三副不同的眼镜。

最容易混淆的是 K 和 V:它们都来自"被看的那个词",为什么要分开?看搜索引擎就明白了:

搜索引擎比喻 Q:搜索词“苹果 营养” 比对 K:网页标题“苹果的五大营养价值” 选中 V:网页正文真正读到、带走的内容 用来“匹配”的东西(标题)和真正“带走”的东西(正文)不一样,所以 K 和 V 要分开 以“苹果”这个词为例 苹果 Q K V 轮到“苹果”去看别人时,它问:“谁对我做了什么?” (于是会去找“吃”“把”) 它贴给别人看的标签:“我是一个能吃的物品” (方便被“它”“甜”“吃”找到) 被选中时交出去的内容:“水果、红色、甜、圆……” (比标签丰富得多)
一句话记住:Q 是我的问题,K 是我的标签,V 是我的内容。拿我的 Q 去和别人的 K 比,比完按比例拿走别人的 V。

14亲手算一遍:用两个数字的小向量

真实模型里每个头的 Q、K、V 各有 128 个数字,而且每个数字的含义人类看不懂。为了看清楚原理,我们把向量缩小到只有 2 个数,并且人为规定它们的含义:

K 的两个数:第 1 个表示"我像不像一个物品",第 2 个表示"我像不像一个人"。
V 的两个数:第 1 个是"水果的含义",第 2 个是"人物的含义"。

像物品 像人 K 他 [0, 1] K 苹果 [1, 0] K 吃 [0.3, 0.3] Q 它 [3, 0] 点积 = 方向有多一致 Q 它 · K 苹果 = 3×1 + 0×0 = 3 Q 它 · K 吃  = 3×0.3 + 0×0.3 = 0.9 Q 它 · K 他  = 3×0 + 0×1 = 0 “它”的 Q 指向“物品”方向, 和“苹果”的 K 方向一致, 和“他”的 K 互相垂直。

接下来的 Softmax 和加权混合,用下面的计算器一步步看。先点"它",再点"吃",对比一下:K 和 V 完全没变,只是换了一个 Q,结果就完全不同。这说明"去看谁"是由提问的那个词(Q)决定的。

QKV 计算器

被看的词它的 K① 打分 Q·K② Softmax 比例它的 V

③ 加权混合:

真实模型和这个小例子只有三点不同:向量是 128 维而不是 2 维;打分后会先除以 √128 ≈ 11.3,防止分数太大、Softmax 变得过于极端;每个数字的含义是训练自己学出来的,不是人规定的。步骤完全一样。

15不同的词,问不同的问题

同一句话里,每个词都会用自己的 Q 去提问,问题各不相同,所以它们关注的对象也不一样。点下面的词,看看它在"问什么"、最后"看向了谁":

“他把苹果吃了,它很甜”:选一个词来提问

三个观察:

一、灰色虚线的词"看不到",因为它们在提问词的后面(第 18 节的因果掩码)。

二、同一个"苹果",被"吃"找到是因为它的 K 写着"能被吃",被"它"找到是因为它的 K 写着"是一个物品"。一份 K 可以回应很多种问题。

三、一个词其实不止问一个问题。"吃"既想知道谁在吃,也想知道吃的是什么。真实模型用多个"头"来同时问多个问题,下一节就讲这个。

(这里的比例是为了讲解而设定的示意值。真实模型里,每个头学到的"问题"更抽象,不一定能用一句话说清。)

16多头注意力与 GQA

多头注意力(Multi-Head Attention,MHA):一句话里需要关注的关系不止一种。"它"指代谁是一种关系,"甜"在形容谁是另一种。所以模型会把向量切成好几份,每份叫一个头(head),各自独立做一遍 Attention,各看各的关系,最后再拼起来。比如 32 个头,每个头 128 维,32 × 128 = 4096。

GQA(分组查询注意力):后来人们发现,K 和 V 不需要每个头都有一份,几个 Q 头可以共用同一组 K、V。这样要存和要读的 K、V 就少了好几倍。现在大多数模型都用 GQA 或类似的压缩办法(DeepSeek 用的 MLA 也是同一个目的)。

MHA:每个头自己一份 KV 8 个 Q 头8 组 KV GQA:4 个头共用一份 GQA 要存的 K、V 只有 MHA 的 1/4,读一次 KV 还能给 4 个头用 (第 29 节会说明:“读一次用几次”正是芯片最在乎的事)

17位置编码:告诉模型谁在前、谁在后

Attention 有个天生的毛病:它只看"有哪些词、彼此有多相关",不知道词的顺序。对它来说,下面两句话是一模一样的一堆词:

狗 咬 人 人 咬 狗 ≠ 意思完全相反,但没有位置信息的话,Attention 分不出来 现在常用的办法 RoPE(旋转位置编码):第几个位置,就把向量转几格 第 1 个词 第 2 个 第 3 个 第 4 个 每次转 30°

解决办法是在 Q 和 K 里"掺进"位置信息,这叫位置编码(Positional Encoding)。最早的做法是给每个位置准备一个固定的向量,直接加到 token 向量上。

现在主流的做法是 RoPE(旋转位置编码):把 Q 和 K 向量按它所在的位置"旋转"一个角度,越靠后转得越多。两个词做点积打分时,结果就只和它们相隔多远有关。就像两根钟表指针,你看它们之间的夹角,就知道隔了几格。

位置编码的计算量很小,对硬件来说几乎可以忽略,但它决定了模型能不能分清语序。

18因果掩码:只能往前看,不能偷看后面

在生成文字时,后面的词还没写出来,当然看不到。为了让训练和使用时的规则一致,Decoder 规定:每个词只能看自己和前面的词。实现方法是给 Attention 的打分表盖上一块"遮罩",把右上角全部挡住,这叫因果掩码(Causal Mask)。

被看的词(提供 K、V) 他把苹果吃了他把苹果吃了 × × × × × × × × × × 正在看的词(提供 Q) 能看 被遮住 “吃”只能看到:他、把、苹果、吃 → 前面的词永远不受后面的词影响 → 它们的 K、V 算出来就固定不变 → 所以可以存起来反复用   这就是 KV Cache(第 22 节)

顺带区分两个名字:自注意力(Self-Attention)是一段文字内部的词互相看,本文讲的都是这种;交叉注意力(Cross-Attention)是解码器去看编码器的结果,只出现在"编码器 + 解码器"的形态里(比如翻译模型)。

第 4 部分

模型怎么一个字一个字地写

这一部分会引出整篇最重要的概念:KV Cache。

19输出层与采样:从一串数字变回一个词

穿过最后一层后,我们手里只有一个 4096 维的向量。怎么把它变成"下一个词"?

最后的向量4096 维 × 输出层矩阵4096 × 12.8 万 12.8 万个分数(logits) Softmax 每个词的概率 采样 好 词表有多大,输出层就要算出多少个分数。这个矩阵本身也有约 1 GB(BF16)

输出层(LM Head):一个大矩阵,形状是"隐藏维度 × 词表大小"。向量乘上它,就得到词表里每个词一个分数,这些分数叫 logits。

Softmax:和第 12 节一样,把分数变成加起来是 100% 的概率。

采样(Sampling):按概率挑出一个词。最简单的做法是永远挑概率最高的(叫贪心)。但这样回答会很死板,所以通常会引入一些随机性,并用两个旋钮来调:

温度(Temperature):温度低,高分的词会更占优势,回答稳定、保守;温度高,概率被拉平,冷门词也有机会,回答更有创意,也更容易跑偏。

Top-p:只在"累计概率达到 p(比如 90%)的那几个最可能的词"里挑,直接排除长尾的怪词。

拖动温度,看“今天天气很 ___”的概率怎么变

20自回归:一次只生成一个 token

模型每完整跑一遍(从第 1 层到第 N 层),只能得到一个新 token。然后把这个新 token 接到末尾,再跑一遍,得到下一个。这种"用自己的输出当下一次输入"的方式叫自回归(Autoregressive)。

点一下按钮 = 模型完整跑一遍所有层

所以,写一段 500 个 token 的回答,模型就要从头到尾跑 500 遍。这也是为什么大模型的回答是一个字一个字"蹦"出来的。

21Prefill 和 Decode:两个阶段

一次对话请求分成两个阶段:

Prefill 预填充 把你的整段问题一次性读完 1000 个 token 同时进去 只跑 1 遍 计算量大 → 主要卡在算力 Decode 解码 一个一个吐出回答 · · · 第1步第2步第3步第4步第500步 每步只有 1 个新 token 要跑 500 遍 每步都要读一大堆数据 → 主要卡在内存

Prefill:几千个 token 一起算,权重读一次就能给几千个 token 用,芯片的计算单元很忙,效率高。

Decode:每一步只有 1 个新 token,却要把整个模型的权重、以及前面所有 token 的 K/V 都读一遍。读了很多,算得很少,计算单元大部分时间在等数据。

一次对话中,绝大部分时间花在 Decode 上。AFD 优化的就是 Decode 阶段。

22KV Cache:用空间换时间

回忆第 12 节:生成第 n 个 token 时,要用到前面所有 token 的 K 和 V。如果每一步都重新算一遍前面所有 token 的 K、V,就太浪费了。

好在一个 token 的 K 和 V 一旦算出来就永远不会变。所以办法很简单:算一次,存起来,以后直接读。存它们的地方就叫 KV Cache(KV 缓存)。

没有 KV Cache 有 KV Cache 第1步第2步第3步第4步 每步都把所有 K、V 重新算一遍 计算量越来越大 第1步第2步第3步第4步 只算新 token,旧的从缓存里读 计算省了,但要存、要读的越来越多 新算 从缓存读

代价是:KV Cache 非常占内存。要存的量是:

KV Cache = 2(K 和 V)× 层数 × KV 维度 × token 数 × 每个数字的字节数 × 用户数

KV Cache 计算器

每个 token
每个用户
总共

对比一张 80 GB 的 GPU(还要留地方放模型权重):

两个要点:
1. KV Cache 常常比模型权重本身还大。
2. 每个用户的 KV Cache 是私有的,A 的对话记录对 B 毫无用处。第 25 节会讲,这一点决定了 Attention 的性能瓶颈。

23回头再看 Q、K、V:在每个场景里各干什么

学完 Prefill、Decode 和 KV Cache,再回头看 Q、K、V,就能看清它们在不同场景里的"命运"完全不同。

Prefill:读入问题 今天 天气 很 好 QKV 4 个 Q 各自和左边的 K 打分(一次全算完) K、V 存进 KV Cache Q:打完分就扔掉 Decode:生成第 5 个词 , 新 token q k v KV Cache KV 新的 k、v 追加进去 q 和缓存里全部 5 个 K 打分, 按比例混合 5 个 V → 输出 新算:1 份 q、k、v(很少) 要读:全部旧 K、V(很多)

为什么只缓存 K、V,不缓存 Q?因为一个词的 Q 只在"轮到它自己提问"的那一刻用一次。而它的 K 和 V 会被后面每一个新词拿来查询,生成 500 个词就要被查 500 次,所以值得存起来。

把各个场景放在一起对比:

场景Q 从哪来K、V 从哪来值得注意的地方
训练 / Prefill每个 token 都算一个每个 token 都算一份所有位置同时算,计算量大;K、V 存进缓存
Decode只有新 token 的 1 个新算 1 份 + 读出所有旧的算得少、读得多,这就是 Attention 内存受限的根源
多头注意力每个头一个每个头一份每个头问不同的问题(第 16 节)
GQA每个头一个几个头共用一份KV Cache 变小,读一次 KV 给几个头用
交叉注意力(翻译)正在写的译文原文只在编码器 + 解码器结构里出现,见下图
AFD 部署在 Attention 机器上算存在 Attention 机器上Q、K、V 和 KV Cache 都不离开 Attention 机器,送去 FFN 机器的只有 Attention 的输出
原文(编码器读完的结果) I love apples 提供 K 和 V 译文(解码器正在写) 我 爱 ? 提供 Q “我下一个该写什么?” → 最匹配 apples 的 K → 拿走它的 V → 写出“苹果”
总结:Q 是当前这个词提出的问题,问完就扔。K 是每个词贴的标签,V 是每个词能交出的内容,它们会被后面所有的词反复查询,所以要存进 KV Cache。在 Decode 阶段,Q 只有 1 个,而 K、V 有成千上万个。这种"一问对万答"的不对称,正是 Attention 卡在内存上的原因。

24上下文窗口:模型一次能“记住”多长

模型一次能处理的最长 token 数,叫上下文窗口(Context Window),也叫上下文长度。你的问题、上传的文档、之前的对话、模型自己写的回答,全部都要装在这个窗口里。现在常见的是 128K 个 token,大约相当于十万汉字;有的模型能到 100 万。

为什么不能无限长?看看长度翻倍时会发生什么:

4 个 token 10 次打分 8 个 token 36 次打分 长度 ×2 时: 读入时的打分次数 ≈ ×4 (每个词都要和前面所有词比) KV Cache ×2 (每个词存一份 K、V) 生成每个新词要读的数据 ×2 计算量随长度“平方”增长,内存随长度“线性”增长

所以上下文越长,Attention 越吃内存,而 FFN 的工作量和上下文长度无关,每个 token 都一样。长上下文让 Attention 和 FFN 的需求差距变得更大,这也是 AFD 越来越受重视的原因之一。

第 5 部分

同时服务很多人

一台推理服务器不会只服务一个人。把很多人的请求一起处理,是省钱的关键。

25Batch:打包一起算

把多个用户的请求凑在一起同时计算,叫批处理(batching),凑在一起的人数叫 batch size。Decode 阶段每个用户每步出 1 个 token,所以 batch size = 16,就是一步同时算 16 个 token。

各自私有 大家共用 A 的 KV Cache B 的 KV Cache C 的 KV Cache D 的 KV Cache 用户 A 用户 B 用户 C 用户 D FFN 权重 读 1 次,给 4 个人用 batch 越大:FFN 越划算;Attention 只是要读的 KV 越多

对 FFN:权重是所有人共用的。从内存读一次权重,可以同时给 batch 里的所有 token 用。batch 从 1 变成 100,读的量不变,干的活多了 100 倍。batch 越大越划算。

对 Attention(具体说是 Q 和 KV Cache 做运算的那部分):每个人只能读自己的 KV Cache。batch 从 1 变成 100,要读的量也变成 100 倍。batch 再大也不会更划算。

26FFN:每个 token 自己"消化"

FFN(Feed-Forward Network,前馈网络)的结构是:先把向量放大到更宽(通常 3~4 倍),经过一个非线性的"激活函数",再缩回原来的宽度。

4096 放大W_up 16384 激活函数 缩回W_down 4096
"放大再缩回"让模型有足够的空间做复杂的组合判断。激活函数是一个简单的非线性变换(比如把负数压小),没有它,叠多少层都等于一次矩阵乘法。

FFN 的权重矩阵很大。一般来说,模型约 三分之二的参数在 FFN 里,模型学到的"知识"也主要存在这里。可以这样分工:Attention 负责收集信息,FFN 负责思考加工。

FFN 里每个 token 都是独立计算的,不需要看别人,也不需要 KV Cache。这让它很适合拆出去单独放。

27MoE:很多个小专家

MoE(Mixture of Experts,混合专家)把一个大 FFN 换成很多个小 FFN,每个叫一个专家(Expert)。每个 token 来了,由一个小小的路由器(Router)决定它该去找哪几个专家,只挑分数最高的 k 个,这叫 Top-k。

“甜” 路由器选 2 个 专家 1 专家 2 专家 3 专家 4 专家 5 专家 6 专家 7 专家 8 权重 0.7权重 0.3 结果 = 0.7×专家2   + 0.3×专家6

好处:模型总参数可以非常大(知识多),但每个 token 实际只用一小部分专家,计算量小,速度快。例如 DeepSeek-V3 总共 6710 亿参数,每个 token 只激活约 370 亿。

坏处:batch 被"摊薄"了。假设一步有 256 个 token,有 256 个专家,每个 token 选 8 个,那么平均每个专家只分到 256 × 8 ÷ 256 = 8 个 token。对专家来说,batch 只有 8,又回到了"读一次权重只给几个人用"的低效状态。

MoE 让 FFN 更"饿"了。这就是为什么 AFD 对 MoE 模型特别有价值。
第 6 部分

芯片的视角

前面讲的是模型"要做什么"。这部分讲芯片"做得怎么样",以及卡在哪里。

28GPU、显存、带宽、算力

一颗 AI 芯片(以 NVIDIA H100 为例)可以简化成两部分:存数据的地方和算数据的地方,中间连着一条"路"。

HBM 显存仓库 HBM 显存仓库 HBM 显存仓库 HBM 显存仓库 计算核心 厨师 约 1000 万亿次运算/秒 显存合计 80 GB(容量) · 带宽约 3.35 TB/秒(搬运速度) 以 H100 SXM、BF16 为例,数字为约数

显存(HBM):芯片旁边的高速内存,放模型权重和 KV Cache。它有两个指标:容量(能放多少,比如 80 GB)和带宽(每秒能搬多少到计算核心,比如 3.35 TB/s)。

算力(FLOPS):计算核心每秒能做多少次运算。H100 做 BF16 矩阵乘法大约是每秒 989 万亿次(989 TFLOPS)。

厨房比喻:显存是仓库,带宽是仓库到厨房的传送带,算力是厨师的手速。厨师再快,传送带送不来菜,也只能干等。

29算术强度:读一次,用几次

算一下 H100 的"胃口":989 万亿次运算 ÷ 3.35 万亿字节 ≈ 295。意思是:每从显存读 1 个字节,至少要做约 295 次运算,厨师才不会闲着。

一个任务"每读 1 字节做多少次运算",就叫它的算术强度(Arithmetic Intensity)。

Attention(读 KV Cache) 1 份数据 厨师 读一次,只用一次 算术强度 ≈ 1(GQA 时 ≈ 共用的头数) 远低于 295 → 内存受限 厨师 99% 的时间在等菜 FFN(batch = 256) 1 份权重 厨师 读一次,给 256 个 token 用 算术强度 ≈ batch 大小 = 256 接近 295 → 快要喂饱了 batch 只有 8 时,强度也只有 8 强度低于芯片的“胃口” → 内存受限(memory-bound);高于 → 计算受限(compute-bound)

为什么 FFN 的算术强度 ≈ batch 大小?一个 d×m 的权重矩阵,读它要 d×m×2 字节。给 B 个 token 各乘一遍,要做 2×B×d×m 次运算。两者一除,正好是 B。

为什么 Attention 的强度 ≈ 1?读一个 K 值(2 字节),只和 Q 做一次乘加(2 次运算);V 也一样。没有任何复用,所以强度约为 1。GQA 让几个头共用一份 KV,强度就提高到共用的头数(比如 4 或 8),但离 295 还差很远。

30Roofline:一张图看出卡在哪

Roofline(屋顶线模型)把上面的道理画成一张图。横轴是算术强度,纵轴是实际能跑出的速度。斜的那段是"被带宽限制",平的那段是"被算力限制",形状像屋顶,所以叫屋顶线。

拖动 batch 大小,看 FFN 的点往哪走

FFN 算力利用率
Attention 利用率
≈0.3%

这张图告诉我们:Attention 不管 batch 多大都钉在左下角,它需要的是带宽和容量;FFN 只要 batch 够大就能爬上屋顶,它需要的是凑够足够多的 token。

第 7 部分

AFD:把两块拆开

有了前面所有的概念,AFD 就很好懂了。

31放在一起时的矛盾

传统做法是 Attention 和 FFN 在同一张 GPU 上跑。问题来了:

一张 80 GB 的 GPU 显存 模型权重 30 GB KV Cache 45 GB(每人约 2 GB) 剩 显存满了,最多只能同时服务约 20 人 FFN 每次只能处理 20 个 token,强度 ≈ 20,远低于 295 ≈ 7% FFN 利用率

FFN 想要大 batch,但 batch 的上限是被同一张卡上的 KV Cache 容量卡住的。结果就是:花大价钱买的算力,绝大部分在闲置。如果是 MoE,每个专家分到的 token 更少,浪费更严重。

32AFD 的做法:两种机器,各干各的

AFD(Attention-FFN Disaggregation,注意力与前馈分离):把 Attention 和 FFN 放到不同的 GPU上。

Attention 机器 × M FFN 机器 × N Attention GPU 1存 20 人的 KV Cache Attention GPU 2存 20 人的 KV Cache Attention GPU 3存 20 人的 KV Cache FFN GPU 只放 FFN 权重 一次处理 60 个 token 利用率 ≈ 20%(3 倍) 送去:hidden 向量 送回:FFN 的结果(每一层都来回一次)
M : N 的比例可以自由调,比如 3:1、8:2。Attention 机器越多,FFN 一次能凑到的 token 越多。

Attention 机器:存 KV Cache,做 Attention 计算。它需要显存大、带宽高,算力弱一点没关系。可以选更便宜、显存更大的卡。

FFN 机器:只放 FFN(或 MoE 专家)的权重,不存 KV Cache。好几台 Attention 机器的 token 汇集过来,batch 变大,算力终于能用起来。它需要算力强。

这样一来,两边可以独立扩容:上下文变长了,就多加 Attention 机器;模型变大了,就多加 FFN 机器。甚至可以用不同型号的芯片。

33代价:来回通信,以及流水线

拆开以后,每一层都要在两种机器之间传两次数据(去一次、回一次)。一个 token 的 hidden 向量如果是 7168 维、BF16,就是约 14 KB;乘上 batch 和几十层,通信量很可观,而且它挡在计算的必经之路上。

解决办法是流水线(pipelining),也叫 ping-pong:把一批 token 切成两个"小批"(micro-batch),让两边轮流干活。小批 1 在 FFN 机器上算的时候,Attention 机器正在算小批 2,谁都不闲着。

不用流水线 Attention通信FFN 算 算 算 闲着… 闲着…闲着… Ping-pong 流水线 Attention通信FFN 小批1 小批2 小批1 小批2 小批1 小批1 小批2 小批1 小批2 时间(示意)

流水线要顺畅,需要满足两个条件:两边每段的耗时差不多(所以要调 M:N 的比例),而且通信时间比计算时间短(所以需要很快的芯片间互连,比如 NVLink 或高速网络,以及专门优化过的通信库)。

34AFD 和 PD 分离有什么不同

你可能还会听到 PD 分离(Prefill-Decode Disaggregation)。两者都是"拆",但拆的方向不一样:

PD 分离:按“时间阶段”拆 Prefill 机器读问题 传 KV Cache Decode 机器写回答 AFD:在 Decode 里按“模型结构”拆 Attention 机器KV Cache FFN 机器FFN / 专家 两者可以叠加使用:先做 PD 分离,再在 Decode 里做 AFD

PD 分离:Prefill 是计算受限,Decode 是内存受限,所以把两个阶段放到不同的机器上。

AFD:即使都在 Decode 阶段,Attention 和 FFN 的瓶颈也不一样,所以再按模型结构拆一次。字节跳动的 MegaScale-Infer、阶跃星辰的 Step-3 推理系统都采用了这种思路。

一句话总结整篇:芯片的核心是平衡"搬数据"和"算数据"。Attention 被 KV Cache 拖在搬数据这一边,FFN 需要大 batch 才能用满算力。AFD 让两者各用合适的机器,再用快速互连和流水线把它们接起来。

术语速查表

术语一句话解释节
神经网络一大堆可调的数字 + 固定的计算步骤1
训练 / 推理调整权重让模型变聪明 / 权重固定,拿来回答问题1
语言模型 / LLM根据前文猜下一个词的模型;参数巨大的就叫大语言模型2
RNN旧式结构,一个词一个词按顺序传话,慢且易遗忘3
Transformer2017 年提出的结构,每个词通过 Attention 直接看所有词,可并行3
Encoder / Decoder负责读懂的编码器 / 负责逐词生成的解码器;大模型多为 Decoder-only4
Token模型处理文字的最小单位,约一个汉字5
向量 / Embedding代表一个 token 的一串数字;把 token 换成向量的查表过程6
隐藏维度 d向量里有多少个数字,如 40967
参数 / 权重训练好的固定数字,模型的知识;70B = 700 亿个8
矩阵乘法用一张权重表把向量变换成新向量,占绝大部分计算9
FLOP一次浮点运算;一次乘加算 2 FLOP9
层 Layer重复几十次的基本单元,每层 = Attention + FFN10
残差 / Norm把输入直接加回输出 / 把数字大小调到合适范围10
Q / K / V查询 / 标签 / 内容,Attention 用来匹配和取信息11
点积两个向量逐个相乘再相加;方向越一致,结果越大14
Softmax把打分变成加起来为 100% 的比例12
多头 / GQA多组独立的 Attention / 多个 Q 头共用一组 KV,省内存16
位置编码 / RoPE让模型知道词的顺序 / 按位置旋转 Q、K 的主流做法17
因果掩码每个词只能看自己和前面的词18
自注意力 / 交叉注意力同一段文字内部互看 / 解码器看编码器的结果18
输出层 / Logits把最后的向量变成词表里每个词的分数19
采样 / 温度 / Top-p按概率挑词 / 调随机程度 / 只在最可能的几个词里挑19
自回归一次生成一个 token,再接到末尾继续生成20
Prefill / Decode一次读完问题(算力受限)/ 逐个吐出回答(内存受限)21
KV Cache存下已算过的 K、V,避免重算;每人私有,很占显存22
上下文窗口一次能处理的最长 token 数,如 128K24
Batch把多个请求打包一起算25
FFN每个 token 独立的"放大-激活-缩回"计算,存着大部分知识26
MoE / 专家 / 路由器 / Top-kFFN 拆成很多小专家,路由器为每个 token 挑 k 个27
HBM / 显存芯片旁的高速内存,放权重和 KV Cache28
带宽每秒能从显存搬多少数据到计算核心28
算力 FLOPS每秒能做多少次运算28
算术强度每读 1 字节做多少次运算29
内存受限 / 计算受限卡在搬数据 / 卡在算数据29
Roofline用"屋顶"形状的图判断任务卡在哪30
AFD把 Attention 和 FFN 放到不同机器上分别运行32
M:N 比例Attention 机器和 FFN 机器的数量配比32
Micro-batch / Ping-pong把一批切成小批轮流跑,用计算掩盖通信33
PD 分离把 Prefill 和 Decode 放到不同机器上34

文中的硬件数字以 NVIDIA H100 SXM(BF16)为参考,模型数字为常见量级的示意,便于理解,不代表某个具体产品的精确规格。

文中的硬件数字以 NVIDIA H100 SXM(BF16)为参考,模型数字为常见量级的示意,不代表某个具体产品的精确规格。 内容更新至 2026-10。