可交互 AI

大语言模型的工作原理

2026年8月30日 约 42,000 字 约 146 分钟阅读

当我们在 ChatGPT 的聊天框里输入一句话,点击发送,几秒后就可能得到一段回答。

例如:

请用普通人能听懂的话解释一下第一性原理。

ChatGPT

第一性原理,就是先把一个复杂问题拆解到最基本的事实,再从这些事实重新推导答案,而不是直接照搬过去的经验。

我们通常看不到发送和收到回复期间发生了什么,下面来逐步拆解。

一、消息与上下文

1. 通过 API 调用模型

日常说“我在和 ChatGPT 聊天”完全没有问题。但消息并不是从聊天框直接进入模型的:聊天应用需要把我们输入的内容整理成一次请求,交给模型,再接收模型返回的结果。这个来回通常通过 API 完成。

一次对话在聊天应用与模型之间的流动

请用普通人能听懂的话解释一下第一性原理。

聊天页面把消息交给产品背后的程序

API 请求
产品背后的程序把这包信息交给模型
使用的模型
"gpt-4o-mini"
发送的消息
说话的人 “我”
消息内容 “请用普通人能听懂的话解释一下第一性原理。”
回答随机程度
0.7 0 更稳定,1 更多样
回复是否分段
完整回复准备好后一次显示
服务器上的大模型 根据请求生成回复。
API 回复
模型把结果交回产品
回复内容
“第一性原理,就是先把一个复杂问题拆解到最基本的事实,再从这些事实重新推导答案。”
结束原因
正常完成
Token 用量
输入 18,输出 28

聊天页面显示回复内容

ChatGPT
第一性原理,就是先把一个复杂问题拆解到最基本的事实,再从这些事实重新推导答案。
这是公开 API 格式的教学示例。字段名称换成了中文,方便观察聊天产品与模型之间传递了哪些信息。

2. 对话与记忆

我们可能会自然地期待:既然刚和 ChatGPT 说过第一性原理,它当然应该记得。但对话中的模型并不会像人一样,在回答完一句话后把这段经历留在脑中。它每次生成回答,都是根据这一次收到的消息重新开始;上一轮对话里的内容不会自动带到下一轮。模型也不会因为前面的对话就立即把聊天记录学进自己的知识库。

这不是产品少做了一项功能,而是大语言模型本来的工作方式:根据眼前的输入,预测接下来最合适的内容(有点类似于输入法预测下一个词,然后不断循环这个过程,直到回复结束。)

为了让对话能连贯地继续,像 ChatGPT 这样的聊天产品会保存前面的消息,并在发出新问题时,把相关的对话内容一同交给模型。于是,模型每一轮都像在回答前重新翻看一遍带来的聊天记录;它看起来“记得”,其实是每次都在利用这次看到的聊天记录回答。

继续刚才的对话。看完 ChatGPT 对第一性原理的解释后,我们可能会接着问:

请用普通人能听懂的话解释一下第一性原理。

ChatGPT

第一性原理,就是先把一个复杂问题拆解到最基本的事实,再从这些事实重新推导答案,而不是直接照搬过去的经验。

能再举一个生活中的例子吗?

如果模型只收到最后这句“能再举一个生活中的例子吗?”,其实有一个地方说不清楚:要为“什么”再举例?它之所以知道仍在谈论第一性原理,正是因为本轮请求中也带上了前面的对话。

下面的动态视图会播放三轮对话,并在每次发送新消息时展示请求携带了多少条记录。切换到“API 中的会话”,可以横向比较三次请求实际包含的问题、完整消息和回复。

对话越长,每次请求携带的内容也越多

同一段对话继续进行时,上一轮问答会成为下一轮的参考内容。

等待第一条消息 准备开始

每次发送的是当前问题和此前相关对话组成的上下文,不只是最后输入的那句话。

最重要的结论是:在 ChatGPT 这类产品中,有记忆其实是一种假象,其实是因为模型每次都能看到完整的聊天记录。

模型本次回答时能够看到的内容,就是上下文(context)。前面的对话被带上时,“再举一个”才有明确指向;否则,模型就不知道要为“什么”举例。

3. 系统说明与用户消息

上一节看到,为了接住一句“再举一个”,应用会把前面的对话也放进本轮请求。不过,除聊天记录之外,模型还会接收到其他信息。

仍以开头的对话为例。我们先输入“请用普通人能听懂的话解释一下第一性原理”,得到回答后,又追问“能再举一个生活中的例子吗?”。聊天界面里能看到这三条消息;但对模型来说,它收到的是一段拼接好的完整上下文——通常在这些聊天记录的最前面,还会包含一段产品在背后预先加入的说明。

继续追问时

聊天界面显示的内容,不一定是模型收到的全部内容

聊天界面里 你能看到

请用普通人能听懂的话解释一下第一性原理。

AI

第一性原理,就是先把问题拆到最基本的事实,再从这些事实重新推导答案。

能再举一个生活中的例子吗?

模型这一轮收到的信息 按角色排列
系统说明 system 应用预先加入

你是一名耐心的科普助手。使用简体中文,面向没有技术背景的读者,避免不必要的术语。

用户消息 user 第一次提问

请用普通人能听懂的话解释一下第一性原理。

助手消息 assistant 模型此前回答

第一性原理,就是先把问题拆到最基本的事实,再从这些事实重新推导答案。

用户消息 user 本轮新问题

能再举一个生活中的例子吗?

模型结合以上信息,回答最后一个问题

这是为了说明消息角色而简化的教学示例。系统说明不代表任何真实产品使用的原文;助手消息则是模型上一轮生成、在这一轮被再次附上的回答。

这种交给模型的任务说明和参考信息,通常叫作 Prompt,中文常译为“提示词”。上面例子中我们发送的问题就是Prompt的一部分,称作“User Prompt(用户提示词)”。

日常交流里,我们说“给 AI 写一个 Prompt”,通常指自己在聊天框中输入的那段话。但把一次完整请求拆开来看,其中可能有几种不同角色的消息:

  • System Prompt(系统提示词):由平台或应用预先提供,用来说明模型在这次对话中的角色、回答方式和需要遵守的规则。它通常不会直接显示在聊天气泡里,而且优先级高于普通的用户要求。(某些大模型聊天应用中,允许用户编辑 system prompt,比如指定AI的性格,聊天风格之类的。)
  • User Prompt(用户提示词):用户输入和发送的消息,例如“请用普通人能听懂的话解释一下第一性原理”。这是聊天界面里最容易看到的 Prompt。
  • Assistant Message(助手消息):大模型回复的消息。上图中标为 assistant 的“第一性原理,就是先把问题拆到最基本的事实……”就是一条 Assistant Message。它和更早的用户消息一起被再次附上时,会成为后续回答的上下文,让模型知道对话进行到了哪里。

所以在实际的使用中,模型会同时参考多层信息,这些信息都属于上下文,而我们刚刚输入的那句话只是其中一部分。

这也解释了为什么同一个问题放进不同 AI 产品,回答风格可能明显不同。除了模型本身不同,产品在背后提供的系统说明、附上的对话记录和其他参考材料,也都可能影响最终回答。


二、文本与 Token

大语言模型不能直接拿着“请用普通人能听懂的话解释一下第一性原理。”这句话开始分析。在消息进入模型之前,还要先进行一些预处理:把连续的文本拆成一小段一小段

负责拆分的工具叫作 Tokenizer,拆出来的每一小段叫作 Token。Token 是模型读写文本时使用的基本单位。

需要注意的是:一个 Token 不一定就是一个汉字,或者一个英文单词。实际上一个 Token 可能是一个汉字、几个连续的汉字、半个英文单词、标点和前面的空格,甚至只是组成某个 Emoji 的一部分。 例如:

  • 常见英文单词:通常会保持完整,比如 apple 就是 1 个 Token。
  • 较长或不常见的英文:会被拆成几个“子词”,比如 hamburger 可能会被拆成 hamburger 等几个 Token。
  • 常见中文词语:有时作为一个整体,比如“世界”是 1 个 Token;有时也会被拆成单字,即“世”、“界”两个 Token。
  • 空格与标点:英文里的空格常常和它后面的单词绑在一起,比如 Hello, world! 可能会被拆分成 Hello, world! 这样 4 个 Token。

直接在下面输入你想分析的内容,或者直接点击预先准备的示例,观察这个拆分的实际效果。

Tokenizer Playground

同一句话,在模型眼中会被拆成什么?

真实拆分 · o200k_base
快捷示例
20 / 1000 个字符

最多可输入 1000 个字符。

字符数
20
按屏幕上的字符计算
Token 数
15
模型实际处理的单位
占 128K 上下文
< 0.01%
仅作容量比例参考
拆分结果 每张卡片是一个 Token
“·”表示空格
#1 · ID 10394
#2 · ID 5615
普通 #3 · ID 88176
#4 · ID 1995
#5 · ID 6306
#6 · ID 37657
#7 · ID 69573
的话 #8 · ID 130892
解释 #9 · ID 120975
一下 #10 · ID 97667
第一 #11 · ID 30035
#12 · ID 8360
#13 · ID 14085
#14 · ID 5584
#15 · ID 788
拆分结果来自 OpenAI 模型常用的 o200k_base 编码。不同模型可能使用不同编码,因此同一句话的 Token 数不一定相同。

为什么会这样拆?可以把 Tokenizer 想成一本提前整理好的“常见文字片段表”。经常一起出现的片段,可能会被收进表里,直接用一个 Token 表示;没有被收录成整体的内容,则需要拆成更小的片段。这个过程遵循已经确定的拆分规则,并不是模型临时读懂了句子之后才决定怎样切。

进阶:Token 编号、Emoji 与不同的编码可选阅读

每个 Token 还会对应一个整数编号,通常叫作 Token ID。例如上面每张 Token 卡片中的 ID 10941。这个编号只表示“去词表里的哪一项查找”,就像书本目录中的页码;数字大不代表意思更复杂,两个编号接近的 Token 在含义上也不一定接近。

不同模型不一定使用同一套 Tokenizer。词表和拆分规则不同,同一句话得到的 Token 数也可能不同。上面的组件使用 o200k_base 编码,适合用来观察 OpenAI 多种较新模型常见的拆分方式;它并不代表所有大语言模型都会得到完全相同的结果。想进一步核对具体编码,可以参考 OpenAI 开源的 tiktoken 项目。

前面提到,大模型看起来“有记忆”,其实是因为每次回答时,它都会重新收到此前的聊天记录。不过,这份聊天记录并不是无限长的:我们常说的 Context Window(上下文窗口),就是模型一次能接收和处理的内容上限;它限制的通常不是“多少个汉字”或“多少个单词”,而是多少个 Token。系统说明、历史对话、刚输入的问题以及模型生成的回答,都会共同占用这份空间。因此,同样看起来是“一页文字”,在不同语言、代码和符号组合下,占用的 Token 数可能并不相同。

账单里的“缓存输入”,指的是什么?

一些 API 价格表会把 Token 分成 Input(输入)Cached Input(缓存输入)Output(输出) 等计费类别。这里的“缓存输入”不是说模型永久记住了这些内容,也不是直接沿用了上一次的回答;它通常指这次请求开头的一段内容,与先前请求处理过的前缀相同,服务因而可以复用那部分计算结果。这种机制通常叫作 Prompt Cache(提示缓存)

PROMPT CACHE

对话继续,缓存前缀也逐步变长

相同输入前缀
请求 A 第一次出现
系统说明 用户解释第一性原理
处理本轮输入 随后生成回答
请求 B 再次出现
系统说明 用户解释第一性原理 缓存命中
AI上一轮的回答 用户再举一个生活例子
前两段复用 后两段正常处理
请求 C 继续追问
系统说明 用户解释第一性原理 AI上一轮的回答 用户再举一个生活例子 缓存命中
AI第二轮的回答 用户换一个工作例子
前四段复用 最后两段正常处理
相同前缀Cached Input
新增内容Input
仍会重新生成 Output
上下对齐、从开头连续相同的部分才是缓存前缀。随着对话继续,下一次请求可复用的前缀也可能变长;每轮新加入的回答和问题仍先按普通输入处理。

例如,围绕第一性原理连续追问时,第二次请求开头仍会带上相同的系统说明和首次提问。这段与第一次请求严格相同的开头可能命中缓存;随后附上的 AI 回答,以及最后新加入的“能再举一个生活中的例子吗?”,仍要作为新增输入正常处理。命中的 Token 仍属于本次输入和上下文,只是在账单中可能按较低的缓存输入价格计算;具体价格、前缀长度要求和有效时间取决于服务商与模型。

缓存命中也不表示这次输入免费。只要可匹配的前缀发生变化、缓存已经失效,或者服务没有提供相应能力,就不一定能够命中。以 OpenAI 的 Prompt Caching 文档 为例,服务复用的是相同输入前缀已经完成的处理。至于这些计算结果在模型内部是什么,以及它和后文的 KV Cache 有什么关系,会在讲生成速度时再解释。

拆成 Token 只是准备工作。这些Token将被送往模型进行下一步处理。

三、回答的生成

Token 被送进模型后,模型会把它们和当前的整段上下文一起看,再判断接下来最可能出现什么。它会为词表里的每个 Token 算一个分数:分数越高,表示这个 Token 拼接到当前位置后,使整段内容看起来更合理。接着把拼接好的新内容再次丢回给大模型,模型在此基础上再预测下一个 Token。不断重复这个过程,直到它觉得应该收尾了。

接上刚生成的文字,再算下一个 Token

第 1 / 35 轮
  1. 01 拼接到末尾
  2. 02 送入完整上下文
  3. 03 计算下一个 Token
当前上下文已生成 0 Token
用户问题

请用普通人能听懂的话解释一下第一性原理。

回答在末尾追加 ↓
等待第一个 Token+
用户问题+已生成的回答
模型等待输入
本轮输入

请用普通人能听懂的话解释一下第一性原理。

(还没有生成文字)
等待完整上下文
下一个 Token?

先把用户问题送入模型

推理强度

一些 AI 产品会提供“推理强度”之类的选项,常见的档位有低、中、高。可以先把它理解成:**面对一个问题时,允许模型使用多少推理时计算预算去梳理条件、尝试解法和检查结果。**等待时间是这种选择可能带来的结果,但不是“推理强度”本身;具体怎样分配计算,也取决于模型与产品实现。

对于改写一句话、提取固定信息、回答简单问题等任务,较低的强度通常已经够用,等待时间也更短。面对需要同时考虑很多条件的规划、复杂计算或代码排错时,调高强度往往更有帮助,但回答通常会来得更慢;在一些按用量计费或限制使用次数的产品中,也可能消耗更多额度。

推理强度并不是“答案详细程度”。调到高档,不代表回答一定更长;如果希望得到更详细或更简短的回答,仍然应该在问题中直接说明。它也不是“正确率开关”:更高的强度只是让 AI 有更多机会认真处理复杂问题,不能保证结论正确,也无法补齐没有提供或无法取得的信息。

日常使用时,不必把所有问题都调到最高。一个简单的判断方法是:任务越复杂、条件越多、出错后的影响越大,就越值得选择更高的推理强度,并对结果进行核对。

进阶:推理模型、推理时计算与可见步骤可选阅读

“推理”在大模型领域里经常指两件不同的事。第四章还会出现的 Inference(推理阶段),是指模型训练完成后的整个使用过程:参数通常保持不变,模型读取输入并生成输出。这里讨论的 Reasoning(推理能力),则是模型为了解决问题而分解条件、比较方案、尝试路径和检查结果。一次 Inference 可以只做简单改写,也可以包含复杂 Reasoning,二者不能当成同义词。

推理模型与普通生成有什么不同?

普通自回归模型和推理模型最终都要逐 Token 生成内容,区别不在于是否使用 Transformer,也不在于是否预测下一个 Token。更重要的区别是:推理模型经过专门的后训练后,更倾向于在给出最终回答前使用额外计算来展开和修正解题过程。OpenAI 在介绍 o1 时指出,其强化学习训练会塑造模型的推理策略,而且效果会同时随训练计算量和测试时思考计算量变化; DeepSeek-R1 技术报告 也展示了用强化学习发展推理行为的一条路线。不同模型的训练配方并不相同,因此“推理模型”描述的是一类能力与使用方式,不是一张完全统一的内部结构图。

这里可以分清两个阶段:

  • **训练时计算(Train-time Compute)**改变参数,帮助模型学会哪些推理策略更可能成功;
  • **推理时计算(Test-time Compute)**不再训练当前模型,而是在这一次回答中给已有策略更多执行空间。

产品里的“低、中、高”通常主要调节后者。它可能允许模型在输出最终回答前使用更多推理 Token,也可能配合候选生成、检查、搜索或工具调用等过程;具体机制属于产品实现,不能只凭界面上的档位名称推断。可以参考 OpenAI 对 推理模型与测试时计算 的说明。

内部计算不等于屏幕上的解释

模型用于推理的中间内容,不一定逐字显示给用户。有些产品只返回最终答案,有些会提供压缩后的推理摘要;而模型在回答中写出的“第一步、第二步”也只是可见输出,不能自动视为内部计算的完整记录。

因此,**推理 Token 与可见回答 Token 是两类用量,推理强度与回答篇幅也是两个不同的控制目标。**提高推理强度可能让模型在最终回答出现前做更多内部计算,却仍然给出一段很短的答案;反过来,要求“写得详细”可以让普通回答变长,却不等于增加了同等程度的推理时计算。

更多计算为什么仍不保证正确?

额外预算提供的是更多尝试机会,不是正确性证明。模型可能沿着错误前提反复推导,也可能把更多计算花在没有帮助的路径上;如果关键事实根本不在参数、上下文或外部工具中,继续推理也不会凭空取得它。不同任务从额外计算中获得的收益也不同,并且通常存在边际收益下降。

所以,更可靠的使用方式不是一律选择最高强度,而是把推理预算与任务难度、错误代价和实际评测结合起来:简单任务优先速度,复杂任务增加预算,高风险结论仍要用资料、计算工具或人工复核验证。

从 Token 回到文字

模型每一步选出的是一个 Token ID——一个整数编号,不是我们能直接阅读的文字。要变成屏幕上的回答,还需要反向查一次词表:拿着这个编号,找到它对应的文字片段。这个过程叫作 Detokenization,和第二章介绍的 Tokenization 正好方向相反:Tokenization 把文字拆成 Token,Detokenization 把 Token 拼回文字。

绝大多数情况下,一个 Token 还原出来就是一段可读的文字。不过第二章提到,Tokenizer 有时会把一个 Emoji 拆成几段 UTF-8 片段;反过来拼接时,只有当相邻的片段都已经生成、对应的字节凑齐了,才能还原出完整的字符。

在许多聊天产品中,模型并不是把整段回答全部生成完才一次性显示。每生成一个或一小批 Token,应用就可以先把它还原成文字,立刻推送到屏幕上。这就是为什么回答常常像是一个字一个字”打”出来的,而不是等待很久后突然出现一整段。

至此,从输入到输出的高层链路已经完整:用户输入文字 → 整理成上下文 → 拆成 Token → 模型逐个预测下一个 Token → 每个 Token 还原回文字 → 显示在屏幕上。 接下来进入模型内部,看看”预测下一个 Token”这一步里究竟发生了什么。

四、模型内部的处理

1. Attention Is All You Need

2017 年,Google Brain 和 Google Research 的研究者发表了论文 《Attention Is All You Need》 。 它提出了一种后来被称作 Transformer 的模型结构。论文当时要解决的是机器翻译:把一种语言的句子读进去,再逐步写出另一种语言的句子。

在它之前,处理一句话的模型常常像读字一样按顺序推进:读完前一个位置,再读下一个位置。但一句话里的词并不能总是各自独立地理解。例如“我把书放到柜子里,因为它太高了”里的“它”,要结合前面的“柜子”才能知道指什么;而且这两个词之间还隔着好几个词。句子一长,这种关联就得绕过中间许多位置,才能传递过去。

Transformer 的关键想法是:让一句话中的各个位置通过 Attention(注意力) 直接参考彼此,再反复加工这些信息。

例如,在“我吃了一个苹果”中,Attention 会让“苹果”更多地关联“吃”,从而理解它是一种水果;而在“苹果发布了新款手机”中,“苹果”会更多地关联“发布”和“手机”,从而判断它指的是苹果公司。正是这种根据上下文动态关注相关词语的能力,让模型能够准确理解同一个词在不同语境中的含义。

2. Transformer

原始论文中的 Transformer 是一个“读输入、写输出”的 Encoder-Decoder 结构。它之所以分成两边,是因为机器翻译同时面对完整的源语言句子要逐步写出的目标语言句子:左边负责读原文,右边负责写译文。这个出发点很重要。我们会先看两边的分工,再沿训练流程展开内部计算,最后看训练好的两边怎样共同完成一次翻译。

Transformer Architecture · Transformer 结构

原始 Transformer:从下向上流动的 Encoder–Decoder

2017 论文的简化结构
原始 Transformer 的 Encoder-Decoder 结构和数据流 输入和右移后的输出分别经过嵌入、位置编码相加、重复的注意力和前馈网络层;Encoder 的输出以 Key 和 Value 的形式流向 Decoder 的交叉注意力层。 Encoder(编码器) 读懂输入 Decoder(解码器) 逐步生成输出 × N 层重复 Add & Norm残差连接+层归一化 Feed-Forward Network前馈网络 Add & Norm残差连接+层归一化 Multi-Head Attention多头注意力(自注意力) Positional Encoding位置编码 Input Embedding输入嵌入 Inputs(已分词)There / is / no / spoon / . Encoder Stack 的输出作为 Key 和 Value → 输出概率Output Probabilities Softmax概率归一化 Linear线性层 × N 层重复 Add & Norm残差连接+层归一化 Feed-Forward Network前馈网络 Add & Norm残差连接+层归一化 Multi-Head Attention多头注意力(交叉注意力)Q:Decoder K、V:Encoder Add & Norm残差连接+层归一化 Masked Multi-Head Attention掩码多头注意力(自注意力) Positional Encoding位置编码 Output Embedding输出嵌入 Outputs (shifted right)右移目标:<开始> / 勺子 / 不存在
Tokenizer 在 Transformer 外:图中的 Inputs 已经是分词并编号后的 Token 序列。箭头表示数据实际流动的方向:两侧都从输入开始向上处理;紫色箭头特别标出 Encoder 的输出怎样进入 Decoder 的交叉注意力层。
  • Encoder(编码器) 读取完整原文,把各个 Token 加工成带有上下文的表示,供右侧查询。
  • Decoder(解码器) 结合原文表示和当前可见的译文前缀,预测下一个目标语 Token。
阶段Encoder 做什么Decoder 做什么预测之后
训练读取样本原文,提供上下文表示参考原文和正确译文前缀,预测各位置的下一个 Token计算损失,更新两边的参数
使用读取待翻译的原文,提供上下文表示参考原文和自己已经生成的前缀,逐步预测选出 Token,接回前缀继续生成

3. 训练阶段的准备

先回明确一个点:Encoder 和 Decoder 不是分开学的。 一条训练样本会先经过 Encoder,再经过 Decoder,最后只产生一份衡量翻译好坏的损失。反向传播会沿着整条计算路径返回,因此两边的参数会在同一次训练中一起调整。

先简单了解一下总的流程

成对文本 → 分词与编号 → Encoder 整理原文 → Decoder 根据原文和正确前缀预测 → 计算损失 → 梯度传回两边。

训练样本包含两部分:一边是交给 Encoder 的原文,另一边是参考译文。沿用 “There is no spoon.” 这个例子,下面把参考译文简化为“勺子不存在。”。

样本中的材料进入哪里在训练中的作用
原文There is no spoon.Encoder形成可供查询的原文表示
参考译文勺子不存在。Decoder 与损失函数右移后提供正确前缀;原序列作为预测目标

这里先讲 Encoder、再讲 Decoder,只是在顺着数据流阅读一次处理过程,不是先把 Encoder 单独训练好,再训练 Decoder。

先划清一条很重要的边界:Tokenizer(分词器)在 Transformer 外面。 它先把原始文字切成小片段,再把每个片段换成编号;Transformer 并不直接接收字母或汉字。用这个例子说,就是:

原文There is no spoon.
TokenThereisnospoon.
Token ID56323828606856013

所以,在上面总结构图中,左下角的 Inputs(已分词) 是已经准备好的 Token ID。原文与参考译文都会先在 Transformer 外完成这一步;接下来先沿左侧路径追踪原文。

4. Embedding

Token ID 只是词表里的编号,像商品条码,本身没有大小或语义关系。因此,ID 为 68560 的 Token 并不比 ID 为 860 的 Token “更大”或“更重要”。模型首先要把每个 ID 换成一长串可参与计算的数字。

Input Embedding(输入嵌入) 做的事很朴素:拿每个 Token ID 去模型当前的 Embedding Table(嵌入表)里,取出对应的一个向量,才是 Transformer 接下来能够计算的输入。

例如,Embedding Table 的局部可以想成下面这样:

Token ID对应的 Embedding 向量
#382[−0.28, 0.51, 0.09, …]
#860[0.73, 0.12, −0.46, …]
#68560[−0.05, 0.31, 0.66, …]

表格实际会有词表大小那么多行;输入一个 Token ID,就取出它所在行的一整串数字。上面的数字仅作教学示意,真实向量通常有数百至数千个维度。

向量”(Vector)看起来像一串数字,例如 [0.18, -0.73, 0.04, 0.51, …]。把 Token 转换成向量后,文字中原本复杂的关系,就变成了数学上可以计算的坐标关系;模型可以据此做加减乘除等运算。

Encoder Input · Input Embedding

Token ID 不等于意思:先查表,得到一串初始向量

教学示意
原始 Transformer 的翻译示例

“There is no spoon.” → “勺子不存在。”

这里用本文前面相同的 o200k_base 演示 Tokenizer;· 表示这个 Token 前含一个空格。换一种 Tokenizer,切分和编号都可能不同。
选择一个片段
Token 片段 There
Token ID(示例) #5632 词表中的编号
Embedding Table 查找对应的一行 训练中不断调整
初始 Embedding [0.14, −0.62, 0.37, …] 句子的起始位置
完整输出 Embedding Matrix E ∈ R5 × d_model
5 个 Token · 每行一条向量
0There0.14−0.620.37
1·is−0.280.510.09
2·no0.730.12−0.46
3·spoon−0.050.310.66
4.0.39−0.110.24

一行对应一个 Token,五行合在一起,才是这句话经过 Input Embedding 后交给下一步的完整输出。

这些编号来自 o200k_base,只为展示“编号 → 查表”这一步;原始 Transformer 和其他模型可以使用不同词表。无论编号是多少,spoon 在这句话里是否被 no 否定,都要等位置编码和后面的注意力层结合上下文后才能逐渐确定。
这一步的完整输出不是一个向量,而是一张矩阵:句子里有多少个 Token,就有多少行;每一行都是对应 Token 的 Embedding 向量。这里展示的只是其中一行。

为了建立直觉,我们先抛开复杂的词汇,看一个最简单的二维向量(即只有两个数字的向量)。

回想数学里面学过的平面直角坐标系,但将X、Y轴变为“甜度”(负数代表苦,正数代表甜)和“温度”(负数代表冷,正数代表热),比如一个二维向量[a,b]在这里面,a就表示有多甜,b就表示有多热。

Semantic 2D Vector

带有实际意义的二维向量

语义空间示意
甜度 (甜) (苦) 温度 (热) (冷) 0 热可可 [2.5, 2] 黑咖啡 [-1.5, 2.5] 冰美式 [-2.5, -1.5] 冰淇淋 [2, -2.5]

为什么我们要把 Token 变成向量,而不是直接拿 Token ID 去计算呢?

这正是向量的魔力所在:它让原本孤立的概念变得“可计算”。如果只使用 Token ID,那它们仅仅像超市里的商品条码,ID 382 和 ID 383 尽管数字上相邻,但在含义上可能一个是“苹果”,另一个是“宇宙飞船”,数字本身并没有包含任何逻辑关联。

但当转化为向量后,模型就能通过计算空间坐标的距离和方向,直接理解词汇之间的关系。比如在上面的坐标系中,模型可以很容易地“算”出,“热可可”和“冰淇淋”在 X 轴(甜度)上距离很近,但在 Y 轴(温度)上完全相反。更有趣的是,我们甚至可以直接在概念之间进行数学推演,比如在模型眼中可能会发现这样一个等式:“黑咖啡” - “热” + “冷” ≈ “冰美式”。通过将文字映射到连续的意义空间中,自然语言就拥有了能被神经网络进行加减乘除和复杂变换的数学特征。

真实模型中的向量通常有成百上千个维度,远比二维复杂。在这个庞大的高维空间中,单个维度的数字通常并不像我们刚才举例的“甜度”或“温度”那样,能直接对应人类所理解的具体概念,这里只是为了帮助理解而做的一种简化和虚构,模型看待Token的角度与人类是不同的。真正起决定作用的,是整组数字共同确定的多维空间位置,以及词与词之间的相对距离和方向。

如何给Token 分配这么精妙的坐标?这其实是模型训练的结果。嵌入表(Embedding Table)并不是人类专家预先编写好的词义词典,它本身就是模型需要学习的重要参数。当模型从头训练时,表里最初只是一堆随机数。但在阅读海量文本、不断预测下一个词的过程中,模型会逐渐发现规律:经常出现在相似语境、承担相似语言作用的 Token,必须被安排在相近的位置才能更好地完成任务。于是表里的数字被不断调整,最终才沉淀出了那些包含丰富语义关系的坐标向量。

你可以通过下面这张交互图来感受这种“相对位置”。把高维空间强行压扁成二维后,尝试替换高亮词,观察它会移动到意义空间的哪个概念附近:

Semantic Space Playground

换一个词,它在“意义空间”中的位置也会改变

二维教学投影
替换高亮词
含义相近的词,往往落在相近区域
动物
交通工具
水果
老虎
汽车
火车
苹果
图中的位置由教学目的手工安排,只用于表达“相近概念通常更接近”的直觉。真实模型使用的是远高于二维的空间,不能从这张图读取真实距离或方向。

5. Positional Encoding

只查表还不够。Embedding Table 看的是“这个 Token 是谁”,却不知道它排在第几个位置;于是“猫追狗”和“狗追猫”会得到同一组 Token 向量,只是顺序不同。

因此,原始 Transformer 会为每个位置准备一份 Positional Encoding(位置编码),再与该位置的 Input Embedding 相加。相加之后的向量既带有“这是哪个 Token”的信息,也带有“它在句中的什么位置”的信息,然后才进入 Encoder 的第一层。

可以把位置编码想成叠在词向量上的一层“位置纹理”:每个位置都有自己的纹理,而且它们遵循同一套规律。模型从训练开始接触的就是这种带纹理的输入,会逐渐学会利用其中的规律,判断哪些词在前、哪些词在后,以及它们之间的距离怎样影响句意。

Encoder Input · Positional Encoding

同一个词放在不同位置,输入表示也会不同

交互示意
点击一个 Token,看它所在的位置
当前选择 spoon 它在句子中的位置:3
Token Embedding [0.20, −0.45] 词是谁
Position Encoding [0.14, −0.99] 它排在哪里
输入表示 [0.34, −1.44] 带有词和顺序

顺序信息不是从 Token ID 里猜出来的。 模型把位置编码加到词向量上,才知道 `spoon` 是句子中的第 3 个位置。

完整输出 Position-aware Matrix H0 = E + P ∈ R5 × d_model
5 个 Token · 每行一条带位置信息的向量
0There0.340.55
1is0.610.09
2no0.78−0.88
3spoon0.34−1.44
4.−0.37−0.20

每一行都同时带着“这个 Token 是什么”和“它排在第几位”;五行合起来,才是送入 Encoder 第一层的完整矩阵。

上面的数字是二维教学示意;原始 Transformer 使用更高维的位置编码。关键关系只有一个:同一个词的 Embedding 可以相同,但换了位置,就会加上不同的位置编码。
对整句话来说,输出仍然是一张矩阵:每一行对应一个 Token,每一行都是“Token Embedding + 对应位置编码”后的输入表示;这里为了看清楚,只展开其中一行。

表面上看,位置编码带来的现象很直观:同一个词换到不同位置,进入模型时的表示也会随之变化。 下面的进阶内容再展开这种“位置纹理”具体怎样生成、怎样与词向量结合,以及它如何进入后续计算。

进阶:位置编码的数学原理与计算过程可选阅读

为什么一串表示“词”的数字,还能装进“位置”? 向量本来就是一组数字,并没有规定它只能表达词义。我们可以把位置也变成同样长的一组数字,再把对应位置的数字逐个相加。这样,同一个词出现在不同位置时,交给模型的数字就会有所不同。

例如,假设 spoon 的词向量只有两个数 [0.20, −0.45]。给第一个位置准备的编码是 [0.00, 1.00],相加就得到 [0.20, 0.55];给第二个位置准备的编码是 [0.84, 0.54],相加就得到 [1.04, 0.09]词没有换,但位置变了,输入表示也跟着变了。 这里把同一个词放在两个位置做比较,数字仅用于教学;真实向量通常长得多。

你可能仍会疑惑:加上另一组数,不会把原来的词义弄乱吗? 相加确实改变了原向量,所以它能起作用,还需要 Embedding 和后续网络在训练中一起适应这种表示。模型的任务是从混合后的数字中学会利用词和位置的线索,并不要求先把两份原始向量完整拆回来。仅靠一次加法,模型不会自动理解顺序;位置编码提供线索,训练让模型学会使用线索。

位置编码怎样生成,相加后又怎样参与计算?

1. 把位置编号变成向量。 原始 Transformer 用不同频率的正弦、余弦函数生成位置编码。设位置编号为 pos(从 0 开始),向量长度为 d,第 i 对维度的编码是:

P(pos, 2i) = sin(pos / 10000^(2i/d))

P(pos, 2i + 1) = cos(pos / 10000^(2i/d))

每一对维度都随位置呈周期变化,有的变化快,有的变化慢,组合起来提供多种尺度的位置线索。对于固定距离 k,三角函数的加法公式可以把位置 pos + k 的每对编码写成位置 pos 那对编码的线性变换;这为学习相对距离提供了结构,但不保证模型一定能处理任意长的文本。

2. 逐项相加,保持维度不变。 设位置 t 的词向量为 eₜ、位置向量为 pₜ,输入表示就是 hₜ = eₜ + pₜ。两者长度相同,相加后仍是 d 个数。原始论文还会先将查表得到的词向量乘以 √d;这里的 eₜ 指完成该缩放后的向量。

3. 后续计算会同时受到词和位置的影响。 后面将介绍的注意力机制,会先对输入向量做可学习的线性变换。用 W 表示其中一组权重,就有:

hₜW = (eₜ + pₜ)W = eₜW + pₜW

因此,位置带来的变化会进入后续计算。训练会调整词向量和这些权重,让混合表示有助于完成任务。这个等式解释了位置怎样影响计算,并不意味着任意两组向量相加后都能被唯一分离:只知道一个和,通常无法恢复两个加数。

公式与原始架构设置见 《Attention Is All You Need》第 3.4–3.5 节

把上面的三个步骤连起来看:拖动位置滑块,观察曲线上的取值、相加后的向量,以及最后的计算结果如何一起变化。

POSITION → VECTOR → COMPUTATION

移动一个词,看位置怎样进入计算

固定 spoon 的词向量,只改变位置。这里用完整的 4 维向量演示。

01 / 从曲线上取数

竖线与四条曲线的交点,组成当前位置的四个数字。实线是 sin,虚线是 cos。

维度 0、1:变化快 · sin(pos)、cos(pos)
10-1 081624位置 pos
维度 2、3:变化慢 · sin(pos/100)、cos(pos/100)
10-1 081624位置 pos

两张图使用相同坐标尺度。慢曲线在这段范围内接近水平;它并非不变,只是周期更长。

02 / 对齐维度,逐项相加

沿着每一列往下看:同一维度的词信息与位置信息相加,得到新的输入表示。

向量 ↓ / 维度 →0123
词向量 e · 固定0.200-0.4500.3000.100
+ 位置向量 p0.141-0.9900.0301.000
= 输入 h0.341-1.4400.3301.100
03 / 位置变化传到下一步

用固定权重 W = [0.5, −0.2, 0.4, 0.8] 将四个数加权求和,演示一次从 4 维到 1 维的线性变换。

词的贡献 eW0.390保持不变
位置的贡献 pW1.080随位置变化
计算结果 hW1.470也随之变化

所有结果先用完整精度计算,再显示三位小数。词向量和权重是教学设定;位置向量按上方公式计算(d = 4)。这里仅演示线性变换,不是完整注意力,也不表示模型会先拆出两个向量。

6. 注意力机制

经过 Embedding 和位置编码后,每个位置已经有一行包含 Token 与顺序信息的向量。接下来要做的是让这些向量交换信息:一个位置需要知道,其他位置有哪些与自己有关的线索。Attention(注意力) 就是完成这件事的机制。

先看两个句子:

我把苹果洗干净后吃了。

“洗干净”和“吃”提供了水果的线索。

苹果发布了新款iPhone。

“发布”和“新款iPhone”则把含义引向了苹果公司。

两个句子里都有“苹果”,但它们的含义并不一样。“苹果”本身的词向量只能提供一个基础表示,真正帮助模型判断含义的,是它与周围词语之间的联系。

仅找出哪些词与“苹果”相关还不够。注意力机制可以让模型在理解“苹果”的同时,也能够通过一些手段去参考与它相关的词,从而判断这里的“苹果”究竟指水果还是公司:它通过一套计算规则,从相关 Token 的向量中取得信息,再与“苹果”原有的信息结合,得到一个新的向量,这个新的向量在保留了“苹果”自己本身的信息的情况下,还吸收其他 Token 提供的信息。

这个新向量仍然属于“苹果”这个位置,可以理解为“苹果”的一个全新版本。这样模型在理解“苹果”的同时,也能参考与它相关的词,从而判断这里的“苹果”究竟指水果还是公司。

先看注意力的目的

让“苹果”的向量带上周围的线索

原来的向量 苹果
主要包含自身的信息
从其他 Token 获取线索
洗干净
得到新的向量 结合上下文后的“苹果”
交给后续步骤继续处理
这里先看信息怎样流动。至于怎样判断哪些线索相关、怎样组合这些向量,后面再一步步展开。

现在来看如何从其他Token吸收信息:

看哪些Token?

全都看,包括自己。因为很简单的道理:你要先看过才知道对自己有没有影响。

怎么看?

有些Token对自身语义的影响很关键,有些则不然,所以对待每个Token的关注程度不能都一样,也就是对不同Token的注意力也不同。

注意力在做什么

一个 Token,会把注意力分给谁?

教学权重

01 · 选择当前正在理解的 Token

02 · 从当前 Token 出发,看它关注谁

当前 Tokenspoon
连线越粗,分配的注意力越多合计 100%
为什么会关注自己?理解一个词时,不能只看周围的内容,也要保留它自己原本表达的信息。关注自己,就像是在吸收上下文的同时,不把自己弄丢。这里的比例只用于帮助理解,不代表真实模型的语言规律。

如何吸收其他Token的信息

上一步已经看到,当前 Token 对不同 Token 的关注程度并不一样。你可以把它理解成一张“取用比例表”:更关注的 Token,会从它那里多拿一点信息;较少关注的,就少拿一点。模型再把这些信息按比例合在一起,得到当前 Token 的一个新版本向量。这个“取用比例”在数学里叫作权重

这个过程是如何实现的

按照前面的思路,其实就是要做三件事:

  1. 计算出注意力的分配比例
  2. 根据这个比例去抓取相应的Token的信息
  3. 糅合到一起

要计算出刚才谈到的注意力的比例,我们需要三种辅助工具:

  1. Q(Query,查询):比如spoon 此刻想寻找什么信息?
  2. K(Key,键):每个候选 Token 都标明“我能被怎样找到”,供 spoon 决定该关注谁、关注多少。
  3. V(Value,值):从候选 Token 中实际取回什么内容?

就像查资料时,我们先带着一个 问题(Q) 搜索,用 标题或关键词(K) 判断哪些结果相关,最后真正阅读的却是结果中的 正文(V)

Q、K、V 不是三份预先存好的信息,而是同一个 Token 当前向量 x 的三种“用途版本”。 可以把 x 想成一份原始资料:为了判断“我在找什么”,模型把它加工成 Q;为了说明“我能和什么匹配”,加工成 K;为了准备“匹配后实际传递什么”,加工成 V。

这三个版本都来自完整的 x,只是采用了三套不同的数字配方:把 x 中的每个数字乘上相应系数,再将结果相加,组成一个新向量。这种运算叫作线性变换,也常叫投影。图中的 WQWKWV 就是保存这三套配方的表:

QKV 的来源与分工

同一个输入,算出三种用途不同的向量

一个 Token 当前的向量输入 x
一套数字配方WQ

用它重新组合完整的 x

一套数字配方WK

用它重新组合完整的 x

一套数字配方WV

用它重新组合完整的 x

交互提示 点击上方 Q、K 或 V 查看分工
Q
当前角色Query · 查询
核心问题我现在需要找什么?
注意力机制中的作用 spoon 寻找能改变自身含义的线索
WQWKWV 决定怎样组合输入;色块仅作示意。

先用两个数字看懂一次线性变换

真实模型中,x 是前面步骤已经得到的 Token 当前向量,通常包含很多数字。为了只看清运算,这里把它缩成两个分量,并假设它们的值是 21,于是 x = [2, 1]。这两个数不是 Token ID,也不是某个词的固定数值,只是本例给定的计算起点。

同一份输入会分别乘以三组不同的权重矩阵,从而得到 Q、K、V。三条路径都使用完整的 x,但各自采用不同的配方:

01 / 数字怎样变换

同一份输入,按三套规则重新组合

假设当前输入 x =
21
Q · 用来查询
按顺序只看一个计算动作
WQ
1002
计算第 1 个新数字
2×12
1×00
把上面两项相加202
计算第 2 个新数字
2×00
1×22
把上面两项相加022
合成新的二维向量[2, 2]
绿色贯穿第 1 列及“新数字 1”的计算,紫色贯穿第 2 列及“新数字 2”的计算。颜色只帮助读图,不代表模型中的特殊含义。示例省略偏置。

刚才三条路径做的是同一种操作,只是使用的数字表不同:输入按照 WQ 重新组合后得到 Q,按照 WK 重新组合后得到 K,按照 WV 重新组合后得到 V。

简单说,输入没有被切成三份,而是完整地分别计算了三次,得到三个新向量。 “矩阵乘法”只是把图中的“乘系数,再相加”集中写在一起。图里的系数是为了让计算容易核对;真实模型使用训练得到的数值,也可以改变向量的维度。

刚才的三张表,到底是什么?

刚才计算 Q、K、V 时看到的三张数字表,分别叫作权重矩阵 WQWKWV。矩阵中的数字决定输入信息如何被重新组合:WQ 把输入组合成 Q,WK 把输入组合成 K,WV 把输入组合成 V。在这个例子中,每一列分别决定输出向量中的一个数字。

在同一层、同一个注意力头中,每个 Token 都会使用同一组权重矩阵;但因为输入不同,最终得到的 Q、K、V 也会不同。图里只用一个向量,是为了先把“乘系数、再相加”这件事看清楚。

数字是怎么来的?

它们是模型在训练中学出来的。开始时通常随机填写;训练时,模型根据预测与正确答案的差距,一点点调整这些数字,让后续预测更准确。

训练完成后,表里的数字就不会再变了;但每次输入不同,Q、K、V 都会根据这次输入重新算出来。也就是说,模型记住的参数是固定的,实际计算出的结果会随输入而变化。可以理解为:公式不变,代入的数据变了,结果自然也会变。

为什么要用三张表

因为三个任务需要的信息侧重点不同:

  • Q 表达当前 Token 要找什么
  • K 给其他 Token 提供匹配线索
  • V 提供实际传递的内容

分别用一张表,模型就能针对每种用途学出合适的计算规则。

这里的“要找什么”“提供线索”只是在解释数字的用途,实际算出来的 Q、K、V 都是向量。

回到原句:spoon 怎样吸收 no 的信息

把刚才的数字过程放回 There / is / no / spoon / .spoon 的 Q 会与句中所有 Token 的 K 分别比较。Q 和某个 K 越匹配,对应位置就越值得关注。模型把分数转换成一组总和为 100% 的比例,再按比例混合所有位置的 V。如果 no 得到较高比例,它携带的否定信息就会被 spoon 吸收。

跟着信息走一遍

从 spoon 的 Q 出发,带回上下文

1 / 3

匹配结果经 Softmax 变成关注比例,再用各自的比例缩放整份 V。

当前正在更新的位置:spoonQspoon用这个查询,寻找相关信息
There
KThere匹配较弱
VThere携带的信息
V 的贡献较小
is
Kis匹配较弱
Vis携带的信息
V 的贡献较小
no
Kno匹配最强
Vno携带的信息
V 的贡献最大
spoon
Kspoon匹配较强
Vspoon携带的信息
V 的贡献较大
.
K.匹配较弱
V.携带的信息
V 的贡献较小
把缩放后的各份 V 相加
spoon 的新上下文向量汇入了其他位置的信息,仍然属于 spoon 这个位置

这里只跟踪一个 Head 中的 spoon。匹配强弱和权重大小均为教学示意,不代表真实模型结果;V 的贡献条表示缩放比例,不是向量长度或取走的维度数量。完整计算会对所有位置进行。V 是向量,图中并未给某个维度指定“否定”等含义。
进阶:Q、K 分开后,怎样形成有方向的匹配,并算完一次注意力可选阅读

为什么不让 Q 和 K 用同一份向量?

用原向量做点积当然可以。但这样一来,x₁x₂ 的分数与 x₂x₁ 的分数总是相同。让 Q、K 使用同一套变换,也保留了这个限制。沿用正文的配方,两个方向的分数可以直接算出来:

进阶 / 为什么 Q、K 分开

交换查询方向,可以得到不同分数

输入 x₁ = [2, 1]输入 x₂ = [1, 0]
直接用原向量
x₁ → x₂2×1 + 1×02
x₂ → x₁1×2 + 0×12

交换方向,只是交换乘法顺序。

使用正文中不同的 Q、K 规则

x₁:q = [2, 2],k = [1, 2]
x₂:q = [1, 0],k = [0, 1]

x₁ → x₂2×0 + 2×12
x₂ → x₁1×1 + 0×21

发出查询的一方用 Q,被查询的一方用 K。

箭头表示查询方向,不是内容传递方向。数字是缩放、Mask 和 Softmax 之前的点积分数,不是注意力百分比。

分开 Q 和 K,模型就能形成有方向的匹配关系。 例如,“名词寻找修饰自己的线索”和“修饰词寻找相关名词”,可以采用不同的匹配方式。这里说的是训练后可能形成的关系,不是人为给向量维度贴上的标签。

用公式写,就是 qᵢ · kⱼ = xᵢ WQ WKᵀ xⱼᵀ。中间的 WQ WKᵀ 决定“这一方的哪些特征,与另一方的哪些特征匹配”。两套权重分开后,交换方向的分数可以不同,但不保证一定不同。另外,即使原始分数对称,经过逐行 Softmax 后,注意力权重也未必对称。

线性变换的系数可以在训练中自动调整,运算本身也可以参与反向传播;同时,它还能通过矩阵乘法高效处理所有位置。因此,线性变换成为这一架构中的基础工具。使用三套变换是为了增加表达能力,但并非唯一选择。原始定义见 《Attention Is All You Need》第 3.2 节

再加入一个输入,算完一次注意力

如果想继续看完整计算,再加入第二个二维输入:x₁ = [2, 1]x₂ = [1, 0]。沿用上面的三套配方,x₁ 得到 Q [2, 2]、K [1, 2]、V [3, 1]x₂ 得到 Q [1, 0]、K [0, 1]、V [1, 1]。下面只计算位置 1 的注意力输出。

② 再看匹配:Q 与 K 怎样变成关注比例?

模型先用点积打分:把 Q 和 K 对应位置的数字相乘,再把乘积相加。x₁ 的 Q 是 [2, 2],所以:

  • 与位置 1 的 K [1, 2] 比较:2 × 1 + 2 × 2 = 6
  • 与位置 2 的 K [0, 1] 比较:2 × 0 + 2 × 1 = 2

x₁ 得到的分数是 [6, 2],顺序对应位置 1、位置 2。

分数还不是关注比例。 标准注意力先把它们除以 Q、K 维度的平方根。本例是 2 维,除以 √2 ≈ 1.414 后,得到约 [4.243, 1.414]。这一步叫缩放,用来控制分数的尺度。

接着,Softmax 把这组分数变成非负、总和为 1 的比例。它先把每个分数变成正数 e 的相应次方,再除以这些正数的总和。这里 e ≈ 2.718,是一个数学常数:

变成正数:e^4.243 ≈ 69.62,e^1.414 ≈ 4.11
位置 1 的比例:69.62 ÷ (69.62 + 4.11) ≈ 94.4%
位置 2 的比例:4.11 ÷ (69.62 + 4.11) ≈ 5.6%

这里算出的关注比例叫作注意力权重,会随着输入变化;前面 WQWKWV 中的数字则是训练学到的计算系数,正常使用时保持固定。两者都叫“权重”,但用途不同。

不用记住这些小数,重点是:同一个查询得到一组分数,Softmax 把它们变成一组分配比例;分数越高,比例越大。 这不是直接用原始分数除以总和,也不是预测下一个词的概率。下面沿用四舍五入后的 94.4% 和 5.6%,继续汇总信息。

③ 最后看传递:按刚才的比例汇总 V

现在取出位置 1 的 V [3, 1] 和位置 2 的 V [1, 1]。分别乘以 94.4% 和 5.6%,再把对应位置相加:

03 / 为什么还需要 V

Q、K 决定取多少,V 决定取回什么

位置 1 · 94.4%位置 2 · 5.6%
从位置 1 取值
94.4% × [3, 1][2.832, 0.944]
从位置 2 取值
5.6% × [1, 1][0.056, 0.056]
逐项相加
[2.888, 1]位置 1 的注意力输出
沿用正文中点积、缩放、Softmax 得到的比例,四舍五入为 94.4% / 5.6% 后计算。

可以把它想成查资料:Q 是查询,K 是用来匹配的索引,V 是取回的正文。 索引负责“能否找到”,正文负责“提供什么”。将 K 和 V 分开,就能分别处理“怎样判断是否相关”和“相关之后取回什么”这两个问题。

算出的 [2.888, 1] 就是位置 1 在这一次注意力计算中的输出。它仍然只有两个数字,来自两个位置的 V 加权求和;没有把两个输入拼接起来,也没有直接生成新词。

如果保持输入、Q 和 K 不变,只改变生成 V 的配方,关注比例仍然相同,取回的结果却会改变。这能帮助我们分清:Q、K 决定取多少,V 决定取什么。

因为这里的 Q、K、V 都来自同一句输入(“There is no spoon.”),这个过程叫作 Self-Attention(自注意力)。“Self”描述的是信息来源,不是说一个 Token 只关注自己。标准自注意力是在同一组输入内部进行 Attention,所以一个 Token 通常会比较这组输入中的所有位置,包括它自己。

生成文字时,模型还要限制可参考的范围:当前位置可以看自己和前文,不能看后面的答案。这种屏蔽叫作因果遮罩(Mask);被屏蔽的位置得到 0 权重。它仍然是自注意力,因为 Q、K、V 仍来自同一组输入。

如果查询来自一组输入,而 K、V 来自另一组,就叫交叉注意力(Cross-Attention)。例如翻译时,负责写译文的 Decoder(解码器)可以查询负责读原文的 Encoder(编码器)输出。下面的切换只比较信息来源,计算主线仍是“打分、分配比例、汇总 V”。

Attention 和 Self-Attention

计算方法相同,区别在于信息来自哪里

切换来源
Self-Attention在同一组输入内部读取
谁提出查询spoon 的 Q
去哪里匹配并取回信息There / is / no / spoon / . 的 K 和 V

Q、K、V 都来自同一句话。候选范围包括 spoon 自己。

Cross-Attention从另一组输入中读取
谁提出查询Decoder 当前状态的 Q
去哪里匹配并取回信息Encoder 输出的 K 和 V

Q 与 K、V 来自不同来源。机器翻译的 Decoder 会用它读取原文。

Self 的真正含义不是“只关注自己”,而是“在自己所在的这组输入内部做 Attention”。

刚才只更新了一个位置。其他位置也会各自拿自己的 Q,与所有可访问位置的 K 比较,再汇总 V。把这些结果逐行排起来,就得到输出矩阵:仍然一行对应一个 Token,只是每一行都结合了上下文。这些计算使用同一份输入,可以并行完成。

多头:让同一个位置有多种观察角度

一句话中可能同时存在否定、指代、语法搭配等多种关系。刚才用一套变换生成 Q、K、V,对每个查询位置得到一组关注比例。要让同一个位置同时使用多种关注方式,可以准备多套独立的变换。因此 Transformer 会并行进行多组自注意力计算,再把结果合在一起。每一组叫一个 Head(头),合起来就是 Multi-Head Attention(多头注意力)

每个 Head 都会看到完整输入,使用自己的 WQWKWV,分别走完刚才的计算。具体关注什么由训练形成,并不是预先给每个头指定“否定”或“语法”的任务。

为什么需要多头

同一句话,可以从不止一个角度观察

切换观察角度
Head A寻找否定关系

这个 Head 可能更关注 no 与 spoon 的语义联系。

There
is
no
spoon
.
完整的 8 维输入Head A 全部看到独立投影成 4 维
完整的 8 维输入Head B 全部看到独立投影成 4 维
两份观察结果4 维 + 4 维拼接后回到 8 维
同一份输入多套 Q / K / V多组上下文结果拼接并重新混合
多头不是把输入机械切成几段,也不是把同一道题重复做几遍。每个 Head 都看到完整输入,并学习自己对“什么叫相关”的定义。图中的关系名称只是教学例子,具体分工由训练形成。

得到多个结果后,模型先把同一位置的各头输出首尾拼接,再按一套学到的系数重新组合;后一步叫作输出投影。这样,不同 Head 提供的线索就能一起更新这个位置携带的信息。

7. Add & Norm

注意力已经让 spoon 找到并带回了 no 等位置的线索,但它还不能把“新结果”直接当作全部答案。否则每经过一层,Token 原本的含义和位置信息都可能被新计算覆盖;与此同时,反复堆叠计算也容易让内部数字的尺度越来越难控制。因此,在这里采用的原始 Transformer 结构中,注意力之后还有 Add & Norm(残差相加与层归一化)

Add 把注意力输出与进入这一模块前的原向量逐项相加,让原输入也参与结果;这条绕过注意力计算、直接接到加法处的路径叫作残差连接Norm 则对每个 Token 向量内部的数字做尺度调整,帮助后续计算保持稳定。

Attention 之后的下一步

新线索不能覆盖旧信息:先相加,再整理

直观理解

注意力产生的是一份“从上下文带回来的新信息”。Add & Norm 不会再次寻找 Token,它负责把新信息安全地并入原表示,并让结果适合继续向上堆叠。

原来的 spoon 保留自身底稿
勺子位置 4
注意力带回 加入上下文批注
no否定关系
Add · 残差相加 底稿和批注合在一起 原来的信息有一条直达通道,不必全部依赖注意力重新生成。
自身含义位置否定线索
Norm · 层归一化 把数字尺度整理稳定 避免层层计算后,有些数字越来越大、有些越来越小,导致后续处理难以稳定。
交给下一子层的 spoon 仍然知道“我是谁”已经吸收“no”的线索数值尺度更稳定

不要把 Norm 理解成“把所有 Token 变得一样”。它只整理每个 Token 表示内部的数值尺度,不会抹掉 Token 之间的内容差异;其中的可学习参数还会保留和调整有用特征。

可以先记住它的分工:Add 为原输入保留直接参与计算的路径,Norm 把合并后的数字整理到更稳定的尺度。 它不负责寻找新的上下文关系,而是保护并整理注意力已经得到的结果。

串起来看:一个位置怎样汇总上下文线索

下面的演示把单头计算、多头合并和原输入的加入串在一起。先跟踪 spoon,再切换到其他 Token,看看每个位置怎样重复同样的流程。

阶段总结 · Multi-Head Self-Attention + Add & Norm

跟着 spoon,走完一次上下文信息之旅

总览 · 1–4
选择一个 Token 跟踪
真实模型会同时更新所有 Token。这里一次跟踪一个位置;点击上方任意 Token,即可切换观察对象。

单个 Head 内前文内容的流程回顾

多头合并与整理汇合视角并保留原信息

1–4
单头流程
一次注意力计算,可以压缩成四个动作

前文已经逐项解释过细节,这里只保留计算顺序和每一步的输入输出关系。

1准备 Q / K / V明确寻找、匹配和传递信息的角色
2Q 与所有 K 匹配找出更值得参考的位置
3转换为注意力权重决定从各处读取多少
4加权汇总 V得到一个 Head 的上下文结果
输入spoon 的当前表示输出一个 Head 眼中的上下文 spoon

记住主线即可:先判断参考谁,再按比例取回信息。

5
多头观察
多个 Head 从不同角度观察 spoon

它们都看到完整输入,但判断“什么叫相关”的方式可以不同。

Head A更关注语义关系
nospoon
例如发现否定和对象的联系
Head B更关注句子结构
There · isspoon
例如观察当前 Token 在句子中的位置
更多 Head保留更多观察角度
位置指代搭配
具体分工由训练形成

多头不是同一道题重复多遍,而是让多套匹配方式同时存在。

6
汇总线索
多头结果合成后,再经过 Add & Norm

不同 Head 的结果先被拼接、投影;随后与原来的 spoon 表示残差相加(Add),再归一化(Norm)后交给下一模块。

多头合成对象含义线索 + 其他视角拼接并经过输出投影
Add · 残差相加注意力输出 + spoon 原输入保留原信息并加入上下文线索
Norm · 归一化后送往下一模块理解了当前语境的 spoon整理数值尺度,得到这一子层的输出

阶段完成:每个 Token 都会经历同样的过程,因此整句话最终变成一组带有上下文的表示。

单头流程:先判断参考谁,再按比例取回信息。

图中的关注强弱和 Head 分工只用于帮助理解,不代表某个真实模型的内部结果。

到这里,你已经可以顺着数据走完一轮:输入生成 QKV → Q 与 K 打分 → 分数变成权重 → 按权重混合 V → 合并多头结果 → Add & Norm。 下面把这条流程对应到论文结构图和完整矩阵计算;不展开也可以继续看本节末尾的输入输出对照。

进阶:论文结构图、矩阵维度与完整计算链可选阅读

再把刚才的信息流与论文里的结构图对照。图从下往上读:左边是一轮单头计算,右边是多头如何组合。

Attention Architecture · 注意力结构

从一次匹配,到多个 Head 的合成

沿箭头,从下往上读
缩放点积注意力

一个 Head 内部怎样计算

缩放点积注意力计算链Q 和 K 先相乘,经过缩放、可选遮罩与 Softmax,再用权重混合 V。V 沿右侧直接进入最后一次矩阵乘法。 MatMul · 匹配QKᵀ → 每对位置的分数Scale · 缩放除以 √dₖ,调整分数尺度Mask · 遮罩(可选)屏蔽不允许访问的位置Softmax · 分配每行权重之和为 1MatMul · 汇总注意力权重 × V 单头输出 ZQKV 内容直达汇总步骤
多头注意力

同一份输入,多套可学习的投影

自注意力中的多头结构输入 X 经过每个 Head 独立的 WQ、WK、WV 投影,再进行左图的注意力计算。多个 Head 的结果拼接,经过输出投影 WO 得到最终输出。 Scaled Dot-Product Attention每个 Head 都执行左图的计算 h 个 Head 并行 LinearWQᵢQᵢ LinearWKᵢKᵢ LinearWVᵢVᵢ Concat · 拼接保留各个 Head 的结果 Linear · 输出投影Wᴼ 混合各头的信息 多头注意力输出同一份输入 X每个 Head 使用自己的 WQᵢ、WKᵢ、WVᵢ
左图的 Q、K、V 已完成投影;右图从自注意力的共同输入 X 开始,明确展示它们如何生成。结构参考 《Attention Is All You Need》图 2
  • 左图: 第一个 MatMul(矩阵乘法)计算 Q 与 K 的匹配分数;Scale 缩放分数,Mask 按需要屏蔽不可访问的位置;Softmax 将分数变成权重,最后一个 MatMul 按权重混合 V。注意 V 的箭头绕过了打分过程,直接进入最后的内容汇总。
  • 右图: 底部三组 Linear 就是各个 Head 的可学习投影;叠起来的框和 h 表示多个 Head 并行计算。Concat 将各头结果拼接,顶部 Linear 再把它们混合成输出。

右图从同一份输入 X 出发:每个 Head 用自己的三组 Linear,生成 Qᵢ = XWQᵢKᵢ = XWKᵢVᵢ = XWVᵢ,再进入左图的计算流程。

矩阵形状与总公式

前面的例子用小写 qkv 表示一个位置的向量。把所有位置的向量逐行排起来,就得到大写 QKV 矩阵。同理,X 表示整组输入;本节第一层的输入也记作 H⁰

这里使用一个刻意缩小的教学模型:句子有 5 个 Token,每个 Token 暂时用 8 个数字表示,并使用 2 个 Head。每个 Head 把完整输入投影成自己的 4 维 Q、K、V:

H⁰ ∈ ℝ⁵ˣ⁸          5 行 = 5 个 Token,8 列 = 每个 Token 的表示
WQ, WK, WV ∈ ℝ⁸ˣ⁴  把 8 维输入分别投影成 4 维的 Q、K、V

下面 ℝ⁵ˣ⁸ 表示由实数组成的 5 行 8 列矩阵。总公式中的 Kᵀ 表示把 K 的行列互换,以便一次算出所有 Q 与 K 的点积;d_head 是每个头的 Q、K 维度,Concat 表示拼接,Wᴼ 是输出投影的权重矩阵。

完整的多头注意力可以写成:

headᵢ = softmax((QᵢKᵢᵀ) / √d_head) Vᵢ
MHA(H⁰) = Concat(head₁, head₂, …, headₕ) Wᴼ

其中 Qᵢ = H⁰WQᵢKᵢ = H⁰WKᵢVᵢ = H⁰WVᵢ。下标 i 表示第几个 Head。两个 Head 都会看到完整的 8 维输入,再分别通过自己的投影得到 4 维结果,并不是把原向量机械地切成前 4 维和后 4 维。

把交互演示还原成一条完整计算链

第 1 步:准备输入矩阵。 位置编码已经和 Embedding 相加,得到 H⁰spoon 是其中一行 x₄,但注意力不会只拿这一行计算;它会把整张 5 × 8 的矩阵交给每个 Head。

第 2 步:用三组线性层产生 Q、K、V。 对某一个 Head 来说,输入矩阵被分别乘以 WQWKWV,得到三个 5 × 4 矩阵。第 4 行的 q₄ 是“我想找什么”,所有行的 kⱼ 是“我能用什么特征被找到”,而 vⱼ 是“如果被参考,我要提供什么内容”。Q、K、V 不是三份不同输入,而是同一份输入的三种可学习投影。

第 3 步:让 q₄ 和每个 kⱼ 做点积。 可以先把点积理解成一个便宜而高效的“契合度打分器”:Q 表示要寻找的模式,K 表示可供匹配的模式,两组数字越能对上,通常就会得到越高的分数。q₄ 与五个 kⱼ 分别计算后,会产生一个长度为 5 的分数向量:[score₄,₁, score₄,₂, …, score₄,₅]。把所有 Token 的行都算上,就得到一个 5 × 5 的分数矩阵;第 i 行只负责说明第 i 个 Token 应该参考谁。

第 4 步:除以 √d_head,必要时施加遮罩。 点积的维度越高,数值的波动可能越大,Softmax 也会变得过于尖锐,所以要除以 √d_head,让训练更稳定。Encoder 的自注意力通常只需要遮住 Padding;Decoder 的因果遮罩还会把当前位置右侧的分数设为负无穷,使它们经过 Softmax 后变成 0。当前这条 Encoder 示例没有遮住未来位置。

第 5 步:对每一行做 Softmax。 Softmax 把分数变成非负权重,并且让同一行的权重加起来等于 1。spoon 这一行可能得到类似 [9%, 15%, 38%, 29%, 9%] 的结果;这表示它怎样分配参考量,而不是模型对五个词给出的最终概率,更不是一张完整的“解释图”。

第 6 步:用权重混合 Value。spoon 这一行的权重分别乘上五个 vⱼ,再相加:z₄ = α₄,₁v₁ + α₄,₂v₂ + … + α₄,₅v₅。权重决定“拿多少”,Value 决定“拿什么”。因此 z₄ 仍然是一个 4 维向量,但已经不再只来自 spoon 自己,而是混入了 no 等位置的信息。

第 7 步:所有行、所有 Head 并行重复。 刚才只算了一个 Head 的一行。真实计算会同时得到这个 Head 的全部 5 × 4 输出,也会用另一套参数算出第二个 Head 的 5 × 4 输出。一个 Head 可以更擅长捕捉否定关系,另一个可以更擅长捕捉位置、指代或句法关系;这些不是人为写死的标签,而是训练中逐渐形成的分工。

第 8 步:拼接各个 Head。 对每一个 Token,把两个 Head 的 4 维结果首尾拼起来:[zᵢ^(1) ; zᵢ^(2)],于是每行回到 8 维,整体得到 5 × 8 的矩阵。拼接不是把两个结果取平均;平均会丢掉不同 Head 的独立信息。

第 9 步:经过输出投影 Wᴼ 拼接后的矩阵再乘一个 8 × 8 的可学习矩阵,把不同 Head 的通道重新混合,得到仍为 5 × 8 的注意力输出。这个步骤让 Head 之间不只是“并排放着”,而是可以在下一步共同影响每个 Token 的表示。

第 10 步:残差相加与归一化。 原始 Transformer 的 Post-LN 写法可以表示为 LayerNorm(H⁰ + Dropout(MHA(H⁰))):把注意力产生的新信息加回原输入,再做归一化。这样即使注意力这一支没有学到有用关系,原始路径仍然保留;下一层 FFN 接收到的仍是 5 × 8 的矩阵。现代模型常把 LayerNorm 放到子层之前(Pre-LN),位置不同,但“注意力负责跨位置交流,残差负责保留并稳定信息”这个分工仍然成立。

可以把“单头”和“多头”的关系压缩成一句话:单头决定每个位置从哪里取信息;多头让同一个位置同时用多套表示空间寻找不同关系;输出投影再把这些关系合成下一层可以使用的表示。

8. FFN

FFN:分别加工每个位置已经取得的信息

经过注意力之后,五个 Token 都已经不再“只知道自己”。例如,spoon 的表示里已经混入了 no 带来的否定线索。看起来模型似乎已经理解了上下文,为什么还需要一个 Feed-Forward Network

因为找到相关信息消化这些信息是两件不同的事。

注意力主要解决的是“我应该去哪些位置取信息”。它能让 spoon 找到 no,把否定线索带回来;但仅仅把线索放在一起,并不等于已经把它们加工成最适合下一层使用的表示。模型还需要进一步处理“勺子”和“否定”同时出现时形成的组合,并决定哪些内部反应应该增强、哪些可以减弱。

这就是 Feed-Forward Network(FFN,前馈网络) 的工作。可以用一个不完全精确、但很直观的类比来理解:

  • Attention 像一次小组讨论。每个人先去听其他人的发言,把相关线索记在自己的便签上;
  • FFN 像讨论结束后的个人整理。每个人低头处理自己的便签,把刚获得的信息重新归纳成更有用的记录。

先分清两种工作

Attention 负责“互相交流”,FFN 负责“各自消化”

刚刚完成 Attention

每个 Token 可以从其他位置取回相关信息。

Thereisnospoon.
spoon吸收整句中的相关线索
发生跨 Token 的信息交换
接下来 Feed-Forward Network

每个 Token 独立整理自己已经拿到的信息。

There同一套 FFN整理后的信息
is同一套 FFN整理后的信息
no同一套 FFN整理后的信息
spoon同一套 FFN整理后的信息
.同一套 FFN整理后的信息
各行使用同一套参数,但彼此不交换信息

这个区别非常重要:Attention 允许不同 Token 之间交换信息,FFN 则不让 Token 在这一阶段继续互相交流。 五个 Token 会各自拿着自己当前的那一行表示,分别经过同一个小型神经网络。

“分别经过”不代表模型真的按顺序处理完 There,再处理 is,最后处理 spoon。计算机实际上会把五行一起并行计算。这里只是从信息关系上说:FFN 处理 spoon 这一行时,不会直接读取 no 那一行。no 的影响如果已经通过注意力进入了 spoon 的表示,FFN 才能继续加工这份影响。

还有一个容易混淆的地方:每一行使用的是同一套 FFN 参数。 它不像五位不同的编辑分别使用五套规则,更像同一位编辑用同一套方法逐份处理五张内容不同的便签。因此:

  • 输入不同,处理结果也会不同;
  • 处理规则相同,所以模型可以用一致的方法面对任意位置的 Token;
  • 不同 Encoder 层通常拥有各自的 FFN 参数,所以越往上层,负责的加工方式可以逐渐变化。
一行 Token 在 FFN 里经历什么?

先继续追踪 spoon。进入 FFN 之前,它的一行表示可以粗略理解为同时携带了“这是一个物体”“它位于句中第 4 个位置”“它受到 no 的否定”等线索。模型内部没有这些中文标签,真实内容仍是一长串训练得到的数字;这里写成人类语言,只是为了帮助我们观察信息变化。

这行数字接下来会经历三个核心动作:

  1. 展开到更宽的内部空间。 原来的一行表示先被变换成更多数字,相当于把一张压缩的便签摊开到更大的工作台;
  2. 用非线性激活筛选反应。 不同内部单元会对不同数字组合产生强弱不一的反应,让值得保留的组合更加突出;
  3. 重新压回原来的宽度。 网络把筛选后的许多反应重新组合,形成下一份 Token 表示,同时保持与其他模块约定好的接口宽度不变。

下面的组件可以切换 Therenospoon,也可以逐步查看同一座 FFN 怎样处理不同内容。先不用计算任何数字,只观察“展开、筛选、压回”的方向。

Encoder · Position-wise Feed-Forward

选择一个 Token,看它如何独立经过同一座“加工站”

1 / 4
先选择要观察的一行
其他 Token 也会同时经过 FFN。这里一次只展开一行,方便看清。
接收一行
FFN 先拿到 `There` 已经带有上下文的一行

这行信息来自注意力和 Add & Norm。它已经不只是词典里的 `There`,还带着当前句子的相关线索。

There
0.480.31−0.120.44
句首位置存在句开头连接后文

FFN 每次只处理这一行,不会在这里重新读取 `There`、`is`、`no` 或其他行。

展开空间
先把较窄的表示投影到更宽的内部空间

可以把它想成把一张压缩便签摊到更大的工作台上。空间变宽后,网络可以同时尝试更多种特征组合。

输入宽度8 格
内部工作空间16 格

真实模型通常会扩到比输入更宽的维度。这里的 8 格和 16 格只是为了把“展开”画出来。

筛选模式
非线性激活像一排开关,让有用反应突出出来

展开后的内部单元会对不同输入组合产生强弱不一的反应。较强的保留下来,较弱的被压低。

句首结构反应较强
存在句式反应较强
否定对象反应较弱
标点收束反应较弱
引出后文有些反应
名词属性反应较弱
时态线索有些反应
其他候选反应较弱

在这个示意里,`There` 的结果是:强化它作为句子开头和存在结构引导词的作用。

压回原宽度
把筛选后的许多反应重新组合成下一份 Token 表示

工作空间可以很宽,但送给下一模块的接口必须保持一致,所以最后会压回原来的表示宽度。

许多中间反应
There 整理后的信息0.630.18−0.080.57

输入和输出一样宽,但内容已经变了。接下来还会经过一次 Add & Norm,再交给 Encoder 的下一层。

接收一行
FFN 先拿到 `no` 已经带有上下文的一行

这行信息来自注意力和 Add & Norm。它已经不只是词典里的 `no`,还带着当前句子的相关线索。

no
0.91−0.640.280.16
否定信号位于名词前关联 spoon

FFN 每次只处理这一行,不会在这里重新读取 `There`、`is`、`no` 或其他行。

展开空间
先把较窄的表示投影到更宽的内部空间

可以把它想成把一张压缩便签摊到更大的工作台上。空间变宽后,网络可以同时尝试更多种特征组合。

输入宽度8 格
内部工作空间16 格

真实模型通常会扩到比输入更宽的维度。这里的 8 格和 16 格只是为了把“展开”画出来。

筛选模式
非线性激活像一排开关,让有用反应突出出来

展开后的内部单元会对不同输入组合产生强弱不一的反应。较强的保留下来,较弱的被压低。

句首结构反应较弱
存在句式有些反应
否定信号反应较强
标点收束反应较弱
作用范围反应较强
物体概念有些反应
肯定语气反应较弱
其他候选反应较弱

在这个示意里,`no` 的结果是:强化否定信号,并整理它对后续名词的作用关系。

压回原宽度
把筛选后的许多反应重新组合成下一份 Token 表示

工作空间可以很宽,但送给下一模块的接口必须保持一致,所以最后会压回原来的表示宽度。

许多中间反应
no 整理后的信息1.08−0.420.510.09

输入和输出一样宽,但内容已经变了。接下来还会经过一次 Add & Norm,再交给 Encoder 的下一层。

接收一行
FFN 先拿到 `spoon` 已经带有上下文的一行

这行信息来自注意力和 Add & Norm。它已经不只是词典里的 `spoon`,还带着当前句子的相关线索。

spoon
0.76−0.930.350.62
勺子概念来自 no 的否定句中补足成分

FFN 每次只处理这一行,不会在这里重新读取 `There`、`is`、`no` 或其他行。

展开空间
先把较窄的表示投影到更宽的内部空间

可以把它想成把一张压缩便签摊到更大的工作台上。空间变宽后,网络可以同时尝试更多种特征组合。

输入宽度8 格
内部工作空间16 格

真实模型通常会扩到比输入更宽的维度。这里的 8 格和 16 格只是为了把“展开”画出来。

筛选模式
非线性激活像一排开关,让有用反应突出出来

展开后的内部单元会对不同输入组合产生强弱不一的反应。较强的保留下来,较弱的被压低。

物体概念反应较强
被否定对象反应较强
句首结构反应较弱
标点收束反应较弱
存在句补足有些反应
肯定存在反应较弱
名词属性有些反应
其他候选反应较弱

在这个示意里,`spoon` 的结果是:把“勺子”和“被否定”组合成更适合后续理解的内部表示。

压回原宽度
把筛选后的许多反应重新组合成下一份 Token 表示

工作空间可以很宽,但送给下一模块的接口必须保持一致,所以最后会压回原来的表示宽度。

许多中间反应
spoon 整理后的信息0.94−0.710.580.39

输入和输出一样宽,但内容已经变了。接下来还会经过一次 Add & Norm,再交给 Encoder 的下一层。

接收一行:拿到一个已经吸收上下文的 Token 表示。

共同参数,独立处理:五行会并行通过同一个 FFN;“同一个”指参数共享,“独立”指这一阶段不会跨行取信息。
为什么要先变宽,再缩回来?

如果一开始就只在原来的少量数字之间来回变换,网络能够尝试的组合会比较有限。先把表示投影到更宽的空间,等于临时增加了一张更大的工作台,让更多内部单元可以从不同角度响应当前 Token 携带的信息。

spoon 为例,某些内部反应可能更容易被“物体概念”触发,某些可能对“否定词影响后续名词”这样的组合更敏感,还有许多反应在当前句子里并不重要。激活函数会让这些反应产生明显的强弱差异,第二次变换再把有用反应重新组合起来。

这里必须谨慎一点:这并不意味着我们可以打开模型,找到一个明确写着“被否定的勺子”的神经元。真实模型通常把信息分散在许多维度和许多层中。图中的“物体概念”“否定关系”等标签是教学类比,用来说明 FFN 可以对输入中的组合模式作出不同反应,而不是声称每个内部单元都有固定的人类含义。

激活函数为什么不可缺少?

可以先把普通的线性变换理解为一条固定的算分规则。比如,先给每个人的成绩统一加 5 分,再统一打九折。无论原来考了多少分,每个人都套用同一个公式:(原成绩 + 5) × 0.9。即使再连续做几次这样的换算,本质上也仍是用一条统一规则处理所有成绩。

激活函数则像第一次换算完成后,先检查一次成绩。假设 60 分是及格线:59 分和 60 分只差 1 分,却会被分到不同结果——前者是“未通过”,后者是“通过”。之后再继续换算时,这两类成绩就不再完全按照同一种方式处理。这个例子说明,激活函数会先看当前结果,再决定后面怎样处理它。网络因此不再只能用同一种方式处理所有信息,而能根据输入情况作出不同反应,表达更复杂的条件组合。

这也是为什么 FFN 不只是“把向量放大再缩小”。真正有用的部分,是它在更宽的空间里产生了许多候选反应,并通过非线性机制决定当前输入应该保留哪些反应。

FFN 处理完,Token 数量会改变吗?

不会。输入仍然是五行,输出也仍然是五行,一行继续对应一个 Token。中间虽然暂时变宽,最终仍会压回原来的宽度,也就是每个 Token 原本用多少个数字来表示。这样输出才能继续与残差路径相加,并顺利进入下一层。

和注意力之后一样,FFN 的输出还会经过一次 Add & Norm:原输入通过残差路径加回来,合并结果再进行归一化。于是每个 Token 既不会轻易丢掉进入 FFN 之前的信息,也获得了 FFN 新加工出的内容。

进阶:FFN 的公式与维度可选阅读

对于某个 Token 的一行输入 x,原始 Transformer 的位置前馈网络可以写成:

FFN(x) = ReLU(xW₁ + b₁)W₂ + b₂

第一组参数 W₁ 把表示从 d_model 投影到更宽的 d_ff,ReLU 提供非线性,第二组参数 W₂ 再把它投影回 d_model。在原始 Transformer Base 模型中,d_model = 512d_ff = 2048,也就是中间宽度约为输入宽度的 4 倍。

把整句写成矩阵时,程序会一次处理所有行,而不是显式写五次循环。每一行都乘同一组 W₁W₂,所以参数在 Token 位置之间共享;各行之间没有点积或加权求和,因此 FFN 本身不会产生新的跨 Token 信息交换。

9. N层重复

到这里,我们只走完了 一层 Encoder。下面先把它放回原始 Transformer 的总结构,再把内部细节收起,只看各层之间怎样传递。

Encoder · Layer stack

先在总结构中定位,再看一层怎样接着一层

01 · 先定位 总结构图中的 Encoder 重复区域

图中所指“× N”表示外框里的整组结构会重复 N 次。

02 · 再看传递 上一层的输出,直接成为下一层的输入 H⁰ = Embedding + 位置编码,是整句话进入第 1 层前的表示。
整层重复不是只重复 FFN
逐层接力不从原句重新开始
形状不变内容持续被更新
一句话记住:同一套层结构堆叠 N 次,每一层接着加工上一层的结果;最后的 Hᴺ 才交给 Decoder。

图中的每张“层”卡片,都代表刚才讲过的完整一组 Attention、Add & Norm、FFN、Add & Norm。它们的结构相同,但各层拥有自己的参数;因此不是把同一份结果机械复制多次,而是让更新后的表示继续接受下一层加工。

整个过程中,五个 Token 仍然对应五行,表示宽度也保持为 d_model。改变的是每一行内部携带的信息。原始 Transformer Base 使用 6 层 Encoder;其他模型可以选择不同层数,所以 N 表示预先设定的层数,不会根据句子难度临时改变。

至此,左侧 Encoder 才真正处理完成:它把最终的 Hᴺ 作为一组原文表示交出去,却还没有生成译文。接下来,视线可以移到右侧 Decoder:它会一边读取已经出现的中文内容,一边查询 Encoder 留下的这组原文表示,开始预测下一个 Token。

10. Decoder

现在转向原始 Transformer 的 Decoder。先把总结构图放回眼前:接下来关注的是右侧从 Output Embedding 向上经过注意力、FFN 和输出头的路径,以及 Encoder 输出进入 Cross-Attention 的紫色通道。

Transformer Architecture · Transformer 结构

原始 Transformer:从下向上流动的 Encoder–Decoder

2017 论文的简化结构
原始 Transformer 的 Encoder-Decoder 结构和数据流 输入和右移后的输出分别经过嵌入、位置编码相加、重复的注意力和前馈网络层;Encoder 的输出以 Key 和 Value 的形式流向 Decoder 的交叉注意力层。 Encoder(编码器) 读懂输入 Decoder(解码器) 逐步生成输出 × N 层重复 Add & Norm残差连接+层归一化 Feed-Forward Network前馈网络 Add & Norm残差连接+层归一化 Multi-Head Attention多头注意力(自注意力) Positional Encoding位置编码 Input Embedding输入嵌入 Inputs(已分词)There / is / no / spoon / . Encoder Stack 的输出作为 Key 和 Value → 输出概率Output Probabilities Softmax概率归一化 Linear线性层 × N 层重复 Add & Norm残差连接+层归一化 Feed-Forward Network前馈网络 Add & Norm残差连接+层归一化 Multi-Head Attention多头注意力(交叉注意力)Q:Decoder K、V:Encoder Add & Norm残差连接+层归一化 Masked Multi-Head Attention掩码多头注意力(自注意力) Positional Encoding位置编码 Output Embedding输出嵌入 Outputs (shifted right)右移目标:<开始> / 勺子 / 不存在
Tokenizer 在 Transformer 外:图中的 Inputs 已经是分词并编号后的 Token 序列。箭头表示数据实际流动的方向:两侧都从输入开始向上处理;紫色箭头特别标出 Encoder 的输出怎样进入 Decoder 的交叉注意力层。

在机器翻译训练中,Decoder 会参考 Encoder 对原文的理解,逐步学习生成中文译文。为了完成每一步的预测,它需要同时使用两类信息:

  1. 正确译文:训练数据中与原文对应的中文翻译;Decoder 在每一步具体能看到其中哪些内容,下一节再展开;
  2. Encoder 的输出:原句各个位置经过 Encoder 处理后的上下文表示。

Decoder 会依次完成六个动作:

  1. 右移参考译文
  2. 把中文内容变成向量并加入位置
  3. 用 Masked Self-Attention 整理中文内容
  4. 用 Cross-Attention 查询原文
  5. 用 FFN 加工
  6. 用输出头给出各位置的下一 Token 概率

把正确译文右移一位

现在用上样本的另一半:与原文对应的参考译文 勺子不存在。

为便于观察,下面把“勺子”和“不存在”各当作一个 Token,具体切分仍由 Tokenizer 决定。先看前三个预测位置;完整样本还会把结束标记作为目标,让模型学习何时停止。

既然训练时正确译文已经存在,模型是不是可以直接偷看答案?不能。如果 Decoder 在学习预测“不存在”时已经看见了“不存在”,这道练习就失去了意义。因此,正确译文会先整体向右移动一个位置:

3.1 · Right shift

为什么 Decoder 的训练输入必须右移一格?

先看反例,再看正确做法
核心问题

训练时,正确译文“勺子不存在。”已经在手里。怎样把它交给 Decoder,又不让模型直接看见要猜的答案?

错误示范
不右移:输入和答案完全重合

如果把正确译文原样交给 Decoder,每个位置一开始就拿到了自己应该预测的 Token。

位置 1位置 2位置 3
Decoder 输入勺子不存在
正确答案勺子不存在
后果
模型只要照抄当前位置,就能得到满分。

例如,输入已经是“不存在”,答案也恰好是“不存在”。模型没有练习“看到勺子后,下一词是什么”。

正确做法
右移一格:让输入永远落后答案一步

所有 Token 向右挪一格,空出的第一个位置放入 <开始>;原来的正确译文继续作为训练答案。

位置 1位置 2位置 3
Decoder 输入<开始>勺子不存在
要预测勺子不存在
任务 1<开始>勺子 任务 2… 勺子不存在 任务 3… 不存在
结果
每个位置只能用已经出现的内容,去预测下一个 Token。

右移负责把“输入”和“答案”错开;因果遮罩负责挡住右侧未来内容,两者一起防止模型偷看。

一句话记住:

不右移是在练“复制当前词”;右移一格才是在练“根据前文猜下一个词”。

于是,第一个位置拿着 <开始> 学习预测“勺子”;第二个位置拿着 <开始> / 勺子 学习预测“不存在”;第三个位置拿着 <开始> / 勺子 / 不存在 学习预测句号。一次训练计算可以同时练习多个位置,所以速度很快,但每个位置仍然必须遵守“不能看未来”的规则。

这种用正确译文提供前缀的训练方式叫作 Teacher Forcing。这里要抓住两个同时成立的事实:

  • 多个位置可以并行练习;
  • 每个位置又只能看到轮到它之前的正确前缀。

把译文前缀变成可计算的值

和 Encoder 的输入一样,<开始> / 勺子 / 不存在 不能以文字形式直接进入神经网络。每个 Token 会先经过 Output Embedding,变成一行数字,再加上位置编码。这里叫 Output Embedding,只是因为它位于原始 Transformer 的输出语言一侧;它做的事情与前面讲过的 Input Embedding 相同,都是把 Token ID 换成向量。

11. Masked Self-Attention

Decoder 最底部的注意力叫作 Masked Multi-Head Self-Attention(掩码多头自注意力)。它和前面讲过的 Encoder 自注意力有相同的 Q、K、V 基本机制,但多了一块因果遮罩。

这块遮罩的规则很简单:每个位置可以看自己和左边,不能看右边。

为什么要遮住未来?

因为在训练时,正确译文的完整内容已经存在于训练样本中。我们希望把整段右移后的译文一次送入 Decoder,让多个位置在同一轮里并行计算;但对其中任何一个位置来说,它又不能提前读取自己要预测的答案以及更后面的内容,否则模型只是在照抄答案,而不是学习“根据已有前缀预测下一个 Token”。

因果遮罩正好让这两件事同时成立:完整序列可以一起计算,但每个位置只能使用当前输入和左侧前缀。 这样既保留了 Transformer 并行训练的效率,又避免了未来答案泄漏。

为什么允许看自己?因为训练时每一行的输入和预测目标已经错开了一位。第二行输入是“勺子”,这一行负责预测的是下一个 Token“不存在”,所以看见当前输入“勺子”不等于看见答案。真正需要挡住的是右边尚未轮到的 Token。

实现上,遮罩不是把右侧 Token 从矩阵里删除。所有位置仍在同一张矩阵中,只是当某一行分配注意力时,右侧位置会被标记为不可用,获得的注意力权重为零。

Masked Self-Attention

点一个预测位置,看遮罩怎样挡住“未来答案”

1 / 3
位置 1 · 注意力窗口
只能看见当前位置及左侧
1 / 3 个 token 可见
01
<开始> 当前输入
02
勺子 未来 · 已遮罩
03
不存在 未来 · 已遮罩
模型此刻拿到「<开始>」及其左侧前缀
据此学习预测「勺子」

第一个位置只能看到开始标记,还不能偷看“勺子”和“不存在”。

位置 2 · 注意力窗口
只能看见当前位置及左侧
2 / 3 个 token 可见
01
<开始> 可以回看
02
勺子 当前输入
03
不存在 未来 · 已遮罩
模型此刻拿到「勺子」及其左侧前缀
据此学习预测「不存在」

第二个位置可以看到开始标记和“勺子”,但“不存在”仍属于未来。

位置 3 · 注意力窗口
只能看见当前位置及左侧
3 / 3 个 token 可见
01
<开始> 可以回看
02
勺子 可以回看
03
不存在 当前输入
模型此刻拿到「不存在」及其左侧前缀
据此学习预测「。」

第三个位置已经可以看到全部前缀,再据此预测句号。

遮罩不是删除后面的 Token。训练时它们仍在同一张矩阵里,只是当前位置无法把注意力分配给右侧位置。

以学习预测“不存在”的位置为例,它可以参考 <开始> 和“勺子”,但不能参考右侧已经写在训练样本里的“不存在”。只有这样,模型才会被迫从已有前缀和原文中找到足够线索,而不是复制未来答案。

和 Encoder 的自注意力一样,Masked Self-Attention 结束后也要经过一次 Add & Norm:把进入这一子层前的目标语表示沿残差路径加回来,再做层归一化。这样既保留了原有前缀信息,也让更新后的表示稳定地交给下一步 Cross-Attention。

12. Cross-Attention

Masked Self-Attention 只整理了目标语前缀;如果不查询原文,Decoder 可能写出语法通顺却与原意无关的话。因此,它接着使用 Cross-Attention(交叉注意力)

“Cross”表示信息跨越了两条路径:

  • Query 来自 Decoder,表达“根据当前前缀,现在需要查找什么”;
  • Key 来自 Encoder,让 Decoder 判断原文中的哪些位置与当前需求匹配;
  • Value 也来自 Encoder,提供被选中位置真正携带的原文信息。

假设当前预测位置能看到的正确前缀是 <开始> / 勺子,接下来需要决定下一个 Token。它的 Query 可能会重点匹配 Encoder 中 no 的表示,同时也参考 spoon 保存的物体概念。这样,Decoder 当前这一行不仅知道中文前缀以“勺子”开头,也重新取得了原文中的否定关系。

Cross-Attention

Decoder 一边看自己已经写了什么,一边回头查阅 Encoder

Decoder 当前已有前缀<开始> / 勺子

接下来该翻译原文中的哪部分?

Query:带着当前写作需求去查原文
Encoder 已读完的原句Key 用于匹配,Value 提供实际内容
There句式线索
is存在关系
no否定线索
spoon目标概念
.边界
取回相关 Value 后“勺子”之后,很可能需要写出与 `no` 对应的“不存在”

`no` 帮助确认否定关系,`spoon` 提供最直接的原文概念。多条线索会一起进入 Decoder 当前表示。

Masked Self-Attention回看已经写出的目标语前缀
Cross-Attention查阅 Encoder 保存的原文信息
最容易记住的区别:Self-Attention 在 Decoder 自己的文字里找关系,Cross-Attention 到 Encoder 的结果里找原文依据。

Self-Attention 和 Cross-Attention 都使用 Q、K、V,但信息来源不同。可以这样记:

  • Masked Self-Attention 问:当前可见的目标语前缀中,哪些位置与预测有关?
  • Cross-Attention 问:Encoder 读过的原文中,哪些位置与当前预测有关?

Cross-Attention 不会立刻输出“不存在”这个文字。它只是更新 Decoder 当前位置的内部表示,让这行数字同时带有目标语前缀和原文依据。真正把这行表示转换成词表候选,还要等到 Decoder 顶部的输出头。

FFN:分别加工每个目标位置取得的信息

交叉注意力完成了又一次“信息交流”,接下来仍然需要 FFN 做“各自消化”。这和 Encoder 中的 FFN 是同一类工作:每个目标位置独立展开、激活并压回自己的表示,不在 FFN 内部继续跨位置读取。

例如,负责预测“不存在”的那一行现在同时拥有中文前缀“勺子”和原文 no 的信息。FFN 会进一步加工这些已经汇合的线索,之后再经过 Add & Norm。整个 Decoder 层会重复多次,让目标语前缀与原文信息在多层处理中逐渐形成更适合预测的表示。

N 层重复

到这里也只走完了 一层 Decoder。和 Encoder 一样,Masked Self-Attention、Cross-Attention 与 FFN 组成的整层会重复 N 次,各层结构相同但参数独立;最后一层的结果才会送入输出头。

Decoder 单边结构

“输出头”就是接在 Decoder 主体上方的 Linear 与 Softmax

一句话记住:Decoder 主体负责理解前缀并结合原文,输出头负责把最后得到的内部表示转换成整个词表上的候选概率。

13. 输出头

经过最后一层 Decoder 后,我们得到的仍然是若干行内部表示,不是可读文字。最上方的 Linear(线性层) 会把需要预测的位置映射成整个词表中每个 Token 的分数;Softmax(概率归一化) 再把这些分数转换成一组候选概率。

训练时,我们会使用多个有效位置的预测结果:第一行预测“勺子”,第二行预测“不存在”,第三行预测句号。每一行都有自己的一组词表概率,而不是整段输入只得到一个答案。

真实输出可以先看成下面这样的概率表:每一行表示模型当前能看到的译文前缀,每一列表示一种可能接在后面的 Token。

当前能看到的译文前缀下一个是“勺子”下一个是“不存在”下一个是“。”其余词表列
<开始>72%1%1%
<开始> / 勺子3%10%2%
<开始> / 勺子 / 不存在1%3%61%

读其中任意一个格子时,先看它在哪一行,再看它在哪一列。例如,第二行“不存在”列的 10% 表示:模型看到前缀 <开始> / 勺子 后,认为下一个 Token 是“不存在”的概率为 10%。真实的表会为词表中的每个 Token 都保留一列,完整一行的概率加起来等于 100%;这里仅展开三列,其余列用省略号表示。正确目标不在这张输出表里,而是在下一步计算损失时才拿来逐行对照。

14. 损失与参数更新

只有预测,还不算完成学习。接下来,训练程序会把模型的预测与正确目标放在一起比较,算出两者之间有多大差距,再根据这个差距调整模型内部的参数。就像模型先提交了一份答卷,再与标准答案核对,看看答错了多少,并据此调整自己下次的答题方式。

训练程序会逐行检查:第一行的正确目标是“勺子”,第二行是“不存在”,第三行是句号。它不只检查模型最后选中了哪个 Token,而是查看模型给正确目标分配了多少概率。给正确目标的概率越高,这一行的预测与目标越接近;概率越低,差距越大。

各个有效位置的差距会被汇总成一个数,叫作损失(Loss)。损失越小,表示模型的预测整体上越接近训练目标。接下来发生的事不是“把正确译文存进模型”,而是利用这个数追查:刚才参与预测的许多内部参数,分别朝哪个方向轻轻调整,可能让下一次的损失更小?

一次训练怎样形成闭环

先比较三个位置的预测,再把同一份调整信号送回整条路径

把每个位置的预测与正确目标对照

正确目标得到的概率越高,表示这一行的预测越接近目标;概率越低,差距越大。

已知前缀<开始>
正确目标勺子
模型给它的概率72%差距较小示例损失 0.33
已知前缀<开始> / 勺子
正确目标不存在
模型给它的概率10%差距较大示例损失 2.30
已知前缀<开始> / 勺子 / 不存在
正确目标
模型给它的概率61%差距中等示例损失 0.49
汇总所有有效位置的差距示例总损失:1.04(0.33 + 2.30 + 0.49) ÷ 3 ≈ 1.04

这里取三个位置的平均值,用一个数表示这批预测整体上离正确目标有多远。

图中概率与损失均为教学示例,不是一条真实训练记录。示例使用最基础的交叉熵并保留两位小数;计算过程放在下方进阶阅读中。每一行来自不同的预测位置,三个百分比不需要相加为 100%。

沿着产生预测的路径往回查

损失不会把正确答案塞进模型。它只帮助训练程序判断,各处参数朝哪个方向微调,可能让下次损失更小。

ENCODER · 读原文 DECODER · 组织译文 损失 1.04回查从这里开始 1 输出头给出词表概率 2 Encoder Output原文记忆 × N 层重复 Add & Norm Feed-Forward前馈网络 Add & Norm Self-Attention读取原文上下文 Input Embedding原文嵌入 Source Tokens Token ID 固定,不参与调整 5 × N 层重复 Add & Norm Feed-Forward前馈网络 Add & Norm Cross-Attention连接 Decoder 与 Encoder 4 Masked Attention回看目标语前缀 Output Embedding目标语嵌入 Shifted Outputs Token ID 固定,不参与调整 3 在这里分成两路 沿 K、V 返回
  1. 1
    从损失开始

    1.04 表示这一批预测仍有差距。

  2. 2
    先回到输出头

    查看哪些词表分数需要改变。

  3. 3
    再逐层穿过 Decoder

    沿着刚才生成概率的计算过程往回走。

  4. 4
    在 Cross-Attention 分路

    一路继续检查 Decoder,一路沿 K、V 回到 Encoder。

  5. 5
    最后进入 Encoder

    包括原文嵌入表在内的参数也得到调整方向。

回查的不是答案文字。紫色路径表示训练程序逐站计算:这里发生一点变化,会让最终损失变大还是变小?
反向传播负责算出各个“旋钮”该往哪边动
优化器负责把 Encoder 和 Decoder 的参数都轻轻调一下
只要记住:预测结果汇成一份损失,这份损失沿连接两边的计算路径返回,所以 Encoder 和 Decoder 会在同一次训练中一起调整。

图里的“变化信息”叫作梯度。计算这些梯度的过程叫作反向传播(Backpropagation)。它会经过输出头和 Decoder,在 Cross-Attention 处继续连到 Encoder。于是,预测虽然出现在 Decoder 一侧,Encoder 也能知道自己提供的原文信息是否有助于译文预测。

优化器(Optimizer)随后根据梯度调整参数。可以把参数想成模型内部数量庞大的小旋钮,梯度说明每个旋钮应该往哪边动,学习率则限制一次不要转得太猛。Encoder 的 Embedding、Self-Attention、FFN,以及 Decoder 中参与计算的参数都会因此得到调整。

训练程序会换一批样本,继续重复这个过程。一次调整不保证每个句子都立刻翻译得更好,大量练习才会逐渐形成稳定的规律。还要用没有参与参数更新的数据检查模型是否能处理新句子,而不只是记住训练材料。

把四个阶段合起来,一次完整训练就是:

  1. 原文经过 Encoder
  2. 正确译文右移后进入 Decoder
  3. 各位置预测
  4. 对照目标计算一份损失
  5. 梯度沿 Decoder 与 Cross-Attention 返回 Encoder
  6. 优化器更新两边参数。
进阶阅读:损失、梯度与原始论文的训练设置可选阅读
损失、梯度与原始论文的训练设置

如果某个位置的正确目标是 y_t,模型给它的概率是 pθ(y_t | x, y_<t),最基本的单目标交叉熵为:

L_t = -ln pθ(y_t | x, y_<t)

因此,正确目标的概率从 0.1 提高到 0.6 时,该位置的损失会从约 2.303 降到约 0.511。实际训练会忽略 Padding 等无效位置,并对一个批次中的有效 Token 求和或取平均。写成一个常见的平均形式是:

L = -(1/N) Σ_t ln pθ(y_t | x, y_<t)

反向传播使用链式法则计算 ∂L/∂θ。对 Encoder 参数而言,梯度会经过 Decoder 的 Cross-Attention 中由 Encoder 输出形成的 Key、Value 路径继续返回;对 Decoder 参数而言,梯度沿输出头、Decoder 各子层和目标语 Embedding 返回。反向传播负责得到梯度,优化器才负责执行参数更新,两者不是同一步。

原始 Transformer 使用 Adam 优化器、带 Warmup 的学习率计划和标签平滑。标签平滑意味着训练目标并非把全部概率质量都压在唯一正确 Token 上,所以真实目标比上面的单目标示意更复杂。具体设置见 原始论文第 5 节

15. 推理过程

“推理”指模型训练完成后正式使用它的阶段:模型带着训练得到的参数处理新的输入,并产生结果。在这里,我们把一句新的英文原文 There is no spoon. 交给模型,让它逐步生成对应的中文译文。这次没有正确译文可供右移,也没有标准答案用于更新参数。Encoder 和 Decoder 都使用训练好的参数,完成这一次翻译。

下面先用一张完整结构图把使用阶段放回 Transformer 全貌中。图里的模块与训练阶段相同,真正变化的是数据怎样进入、预测结果怎样被使用,以及参数不再更新。

Transformer Inference · 使用阶段

原始 Transformer 的完整使用流程

参数保持不变
原始 Transformer 在使用阶段的 Encoder-Decoder 结构和数据流 原文经过 Encoder 得到可反复使用的表示;Decoder 接收起始标记和已经生成的前缀,查询 Encoder 表示;输出头形成整个词表的候选概率分布,再由选择策略确定下一个 Token。 Encoder(编码器) 读懂输入 Decoder(解码器) 逐步生成输出 × N 层重复 Add & Norm残差连接+层归一化 Feed-Forward Network前馈网络 Add & Norm残差连接+层归一化 Multi-Head Attention多头注意力(自注意力) Positional Encoding位置编码 Input Embedding输入嵌入 Inputs(已分词)There / is / no / spoon / . Encoder Stack 的输出作为 Key 和 Value → 下一 Token 概率Output Probabilities Softmax概率归一化 Linear线性层 × N 层重复 Add & Norm残差连接+层归一化 Feed-Forward Network前馈网络 Add & Norm残差连接+层归一化 Multi-Head Attention多头注意力(交叉注意力)Q:Decoder K、V:Encoder Add & Norm残差连接+层归一化 Masked Multi-Head Attention掩码多头注意力(自注意力) Positional Encoding位置编码 Output Embedding输出嵌入 Current generated prefix当前前缀:<开始> / 模型已生成的 Token
使用时这样读图:左侧 Encoder 先把完整原文处理一次,紫色路径把这份表示持续提供给 Decoder。右侧第一次只输入 <开始>;每轮从顶部概率中选出一个 Token,再把它接回底部的当前前缀,重复经过 Decoder,直到选出结束标记。整个过程只使用训练好的参数,不计算损失,也不更新参数。

Encoder 编码原文

Encoder 先处理完整原文,得到各位置的上下文表示。这份结果可以在本次翻译的后续生成中反复使用,不必每生成一个中文 Token 就把原文重新编码一遍。

这里仍然会用到 Input Embedding、位置信息、自注意力和 FFN。它们的参数已经在训练中学到了有用的规律,本次翻译不通过损失和梯度去修改这些参数。保存的原文表示属于本次计算结果,不是又学出了一套新参数。

Step 01 · Encoder

把完整原文压成一份可反复查询的上下文表示

本次翻译只计算一次
工作:用训练好的参数读取完整原文并建立上下文关系。产出:一组 Encoder 表示;它是本次翻译的中间计算结果,不是新参数。

Decoder 从起始标记开始

Decoder 最初只收到 <开始>。它仍然经过前面讲过的 Embedding、位置编码、Masked Self-Attention、Cross-Attention 和 FFN;其中 Cross-Attention 负责查询 Encoder 的原文表示。

选择下一个 Token

训练时我们会对多个位置评分;使用时,为了决定“现在接着写什么”,只读取当前前缀最后一个位置的结果。

这里要严格区分三层产出:Decoder 主体产出的是最后一个位置的内部表示;Linear 和 Softmax 把它变成整个词表上的候选 Token 概率分布;选择策略才从这组分布中取出一个确定的 Token。所以,Decoder 后面算出的并不是已经确定的下一个 Token,而是一大组“每个 Token 成为下一项的可能性”。假设选择策略最终取出“勺子”,它才成为实际输出的一部分。

Step 03 · Output Head

只读取最后一个位置,选出接下来要写的 Token

每轮产出 1 个 Token
两种产出不要混淆:Linear + Softmax 产出整张词表的候选概率分布;后面的选择策略才产出一个确定的下一 Token。概率分布本身不会直接成为文字输出。
进阶:选择策略在哪里执行,又如何决定下一个 Token?可选阅读

选择策略在哪里执行?

选择策略通常不属于 Transformer 的网络结构。Transformer 和输出头负责根据当前前缀算出候选 Token 的分数或概率;推理程序再读取这组结果,选出一个 Token,接回前缀后发起下一轮计算。它常被写在模型库的生成函数或推理服务的循环里,也可能和模型一起在 GPU 上运行,但概念上仍是网络外层的生成控制步骤,不是 Attention、FFN 那样一层可学习的模块。

训练时,Decoder 通常直接接收正确译文的前缀,因此不必真的从候选中选出一个 Token。只有正式生成时,选择策略才会影响模型实际写出的后续内容。

几种常见的选择策略

  • 贪心选择(Greedy):每一步都选概率最高的 Token。它速度快、结果固定,但可能因为某一步的局部最优选择而错过更好的整句表达。
  • 随机采样(Sampling):按候选概率随机抽取 Token。概率高的词更容易被抽到,但较低概率的候选也有机会出现,因此结果会有变化。
  • Temperature:在采样前调整概率分布的尖锐程度。较低的 Temperature 会让高概率候选更占优势,输出更稳定;较高的 Temperature 会让分布更平,输出更多样,但也更容易偏离主题。它通常与采样配合使用。
  • Top-k 与 Top-p:两者都会先剔除过于不可能的候选,再进行采样。Top-k 只保留概率最高的 k 个候选;Top-p 则保留累计概率达到 p 的最小候选集合,因此候选数量会随当前分布变化。
  • Beam search(束搜索):同时保留多条候选生成路径,继续展开并比较整条路径的总分。它常用于机器翻译等更强调整体一致性的任务,但计算量更大,也不一定总能生成最自然的表达。

同一组概率,不同的取法

选择策略怎样改变下一 Token

教学示意
TRANSFORMER + OUTPUT HEAD产出候选概率
推理程序只看最高概率
本轮输出勺子
图中的概率用于解释策略,不代表真实翻译模型的输出。Top-k、Top-p 和 Temperature 通常会组合使用。

循环生成,直到停止

接下来,Decoder 看到的前缀变成 <开始> / 勺子,再次参考原文,预测下一个 Token。假设选中“不存在”,就再把它接回去:

Step 04 · Autoregressive Generation

生成一个,接回前缀,再生成下一个

第 1 轮
1读取前缀与原文
2计算候选概率
3选出 Token
4接回前缀
ENCODER MEMORY本轮固定复用
原文表示

Thereisnospoon.

DECODER INPUT 当前可见前缀
<开始>

还没有生成正文 Token

OUTPUT HEAD 下一 Token 候选
SELECTED 等待计算

选择策略还未运行

Decoder 正在读取当前前缀,并查询 Encoder 保存的原文表示。

动画会在结束标记处自动停止
本步产出:尚未完成

这只是一条示意生成路径,模型也可能选出其他译法。句号本身不一定触发停止;生成结束标记,或达到外部设置的长度上限等条件,才会结束这次输出。

这种“生成一个,再接回输入”的过程叫作自回归生成。下一轮依赖上一轮的实际选择,因此不能像训练那样预先拿到所有正确前缀。

使用时仍然遵守因果规则,只是未来的译文尚未生成,根本没有现成的未来答案可看。如果某一步选错了,后续也会沿着这个实际生成的前缀继续,而不会自动换回训练时的正确答案。

使用的一轮:用已有前缀查询原文 → 得到整个词表的候选概率分布 → 选择一个 Token → 接回前缀 → 继续,直到停止。这个过程通常不更新模型参数。

这次生成期间,Encoder 提供的是同一份原文表示,Decoder 的前缀则逐步增长。两边仍然协作,但不会在每轮输出后执行训练时的参数更新。

到这里,原始 Transformer 的完整路径可以浓缩为:

  1. 原始输入
  2. Encoder 的上下文表示
  3. Decoder 最后位置的内部表示
  4. 整个词表的候选概率分布
  5. 选择策略取出实际的下一个 Token

16. 现代LLM的结构

前面我们以原始 Transformer 的翻译任务为例,梳理了模型从输入到输出的计算过程。这个 2017 年论文中的经典架构采用 Encoder–Decoder 结构:Encoder 读完整原文,Decoder 一边写译文,一边通过 Cross-Attention 查询原文。不过,日常聊天中的模型并不只做机器翻译,许多现代文本生成 LLM 使用的是 Decoder-only 主干;最熟悉的例子是 ChatGPT 所基于的 GPT 系列,此外 Llama、Qwen 和 DeepSeek-V3 等开放权重模型也属于这一类。接下来将两种结构放在一起,进一步看看它们的差别。

下面把两种结构放在一起,先看三个最基础的变化。

从 Transformer 到 Decoder-only

从原始结构中,一步步摘出 GPT 的主干

起点
原始 Transformer 为什么分成两边?

它最初服务于翻译:左边先读完原文,右边一边写译文,一边回头查询左边留下的原文记忆。紫色连线就是两边交换信息的通道。

Encoder编码器
Encoder Output原文记忆
× N 层重复
Add + Norm
Feed-Forward
Add + Norm
Self-Attention
Position
Input Embedding原文嵌入
Source Tokens
Encoder Output作为 K、V
DecoderDecoder-only解码器GPT 类生成主干
Output Probabilities下一个 Token 的概率
Linear+Softmax
Decoder × NGPT Block × N
Add + Norm
Feed-Forward
Add + Norm
Cross-Attention查询 Encoder Output
Add + Norm
Masked Self-AttentionCausal Self-Attention只能关注当前位置及之前的 Token,不能看到后文
Position
Output EmbeddingToken Embedding右移后的目标序列统一上下文序列
<开始>勺子不存在
说明历史问题回答前缀
结构图只保留理解变化所需的模块。每次点击后,图中的拓扑关系与下方解释会同步变化,不会自动播放。

从两份材料到一份上下文

在翻译结构中,原文和译文前缀从两条路径进入模型。到了 GPT 类文本生成主干里,系统与开发者说明、聊天历史、用户问题、工具返回的文字,以及模型已经写出的回答,都会在进入生成主干前被组织成同一轮上下文。

可以把它想成:原始 Transformer 有一名专门读原文的同事和一名专门写译文的同事;GPT 类文本主干则把当前需要参考的材料按顺序铺在一张长桌上,由同一组网络层反复阅读和加工。

Attention 留在核心

当独立 Encoder 不再存在时,Decoder 自然也不需要跨到另一座塔里查询信息,所以原图中连接两侧的 Cross-Attention 通常不再出现在普通文本生成主干中。

不过,这不等于注意力机制被删除了。GPT 类模型会重复经过很多层因果自注意力:当前位置从前文寻找线索,同时用遮罩挡住未来。前面讲过的 Q、K、V、多头注意力、FFN、残差连接和归一化,仍然构成这些重复模块的基础。

从翻译到续写

原始 Decoder 接着目标语前缀写译文;GPT 类文本主干则接着整份上下文继续写。无论界面上看起来是在问答、改写、总结还是写代码,核心动作仍然是:

根据目前已经出现的 Token,预测紧接着的一个 Token。

现代 LLM 还可能改动哪些地方?

Decoder-only 只是最容易看见的结构变化。不同现代模型还会调整归一化放在子层之前还是之后、使用什么位置编码、注意力如何共享 Key 和 Value,以及 FFN 怎样扩展。

这些实现会影响速度、容量和训练方式,但不会改变本节最重要的直觉:GPT 类文本主干把上下文放进一条因果序列,并不断预测下一个 Token。

进阶:从原始 Transformer 到现代 LLM,内部模块还发生了什么变化?可选阅读

前文详细拆解的是 2017 年原始 Transformer。现代 LLM 仍然沿用“Attention 负责跨位置交换信息,FFN 负责逐位置加工,残差连接让信息沿深层网络传递”的主干,但许多模块已经换成更适合大规模训练和自回归生成的实现。

原始 Transformer 中的做法现代 LLM 中的常见变化主要解决什么问题
正弦、余弦位置编码与 Token Embedding 相加RoPE 等相对位置方案让 Attention 更直接地利用 Token 之间的相对距离
子层计算后再做 LayerNorm(Post-Norm)Pre-Norm、RMSNorm改善深层模型的训练稳定性,并简化归一化计算
ReLU FFNSwiGLU 等门控 FFN让 FFN 更灵活地筛选和组合内部特征
每个 Query Head 都有各自的 Key、Value HeadMQA、GQA减少推理时需要保存和读取的 Key、Value
按公式直接实现标准 AttentionFlashAttention 等优化实现减少 GPU 显存读写,提高训练和推理效率

RoPE:不再把位置简单加到输入上。 原始 Transformer 先生成一组正弦、余弦位置向量,再与 Token Embedding 相加。Rotary Position Embedding(旋转位置编码,RoPE)则在 Attention 中根据位置旋转 Query 和 Key,使两者的匹配分数能够带上相对位置信息。它改变了位置怎样进入计算,却没有改变“Q 与 K 打分,再按权重汇总 V”的主线。具体方法见 RoFormer 论文

Pre-Norm 与 RMSNorm:改变归一化的位置和算法。 原始结构在 Attention 或 FFN 计算完成、加上残差后再做 LayerNorm,属于 Post-Norm。许多现代模型会先归一化,再进入 Attention 或 FFN,也就是 Pre-Norm;这样通常更有利于深层网络中的梯度传播。另一些模型还用 RMSNorm 代替 LayerNorm:它主要按照向量的均方根调整尺度,不再单独减去均值,计算更简单。RMSNorm 的定义与实验见 原始论文

SwiGLU:FFN 不只是一条 ReLU 路径。 前文用 ReLU(xW₁ + b₁)W₂ + b₂ 解释原始 FFN。一些现代 LLM 使用带有门控的 FFN:输入产生两组中间结果,其中一组像“门”一样调节另一组有多少信息通过。SwiGLU 是常见变体之一。它仍然逐 Token 独立工作,仍会先扩展再压回原宽度,只是内部筛选信息的方式发生了变化。相关比较见 GLU Variants Improve Transformer

MQA 与 GQA:减少重复保存的 Key 和 Value。 标准多头注意力为每个 Head 分别生成 Q、K、V。Multi-Query Attention(MQA)让多个 Query Head 共享一组 Key、Value;Grouped-Query Attention(GQA)则让若干 Query Head 组成一组,共享同组的 Key、Value。Query 仍可以保留多个观察角度,但需要写入 KV Cache 的 Key、Value Head 更少,因此能够降低 Decode 阶段的内存占用与数据读取压力。GQA 在共享程度与模型效果之间提供了一个中间选择,见 GQA 论文

FlashAttention:优化的是计算方式,不是另一种注意力含义。 标准 Attention 的公式没有改变,但如果程序把巨大的中间矩阵频繁写入和读出 GPU 显存,实际运行会受到内存访问限制。FlashAttention 通过分块计算,让更多中间结果留在更快的片上存储中,减少显存读写;它计算的仍然是标准 Attention,而不是删掉一部分 Token 的近似注意力。具体方法见 FlashAttention 论文

这些变化分别作用于位置、归一化、FFN、KV Cache 和底层计算效率,不能简单合并成“模型换了一种架构”。对于本文建立的主线,可以继续沿用同一张地图:Token 变成向量 → 位置信息进入计算 → Attention 交换信息 → FFN 加工信息 → 输出头预测下一个 Token。现代实现主要改变这条路径上的具体零件和成本。

17. 一次现代聊天生成的完整链路

现在可以把文章开头的聊天请求,重新放回 Decoder-only 模型里走一遍。用户在界面上看到的是分开的聊天气泡,但模型实际接收的不是几只气泡,也不是一个天然理解 systemuserassistant 含义的聊天程序。应用需要先按照模型约定的聊天模板,把不同角色的消息、消息边界和已有内容组织成一条序列,再交给 Tokenizer。

为了建立直觉,可以把整理后的内容想成下面这样:

<系统开始> 请用清楚、通俗的方式回答 <系统结束>
<用户开始> 请用普通人能听懂的话解释一下第一性原理 <用户结束>
<助手开始> 第一性原理,就是先把一个复杂问题拆解到最基本的事实…… <助手结束>
<用户开始> 能再举一个生活中的例子吗? <用户结束>
<助手开始>

这里的标记只是教学示意,不代表真实情况。完成分词后,系统说明、历史消息、当前问题和助手回答的起点就都成为同一条 Token 序列。

Decoder-only Chat Flow

一次现代聊天请求,怎样穿过模型与外部系统?

01 · 应用层 整理角色消息
systemuserassistant
加入“解释第一性原理”的回答与“再举一个例子”的追问
02 · 模型输入 拼成一条序列
SU第一性A解释U举例?
角色与边界也要用模型能识别的形式表示
03 · Decoder-only 处理当前前缀
因果 Attention FFN × N
每个位置只能读取自己及左侧已经出现的 Token
04 · 输出头 决定下一步输出
文字 Token 工具调用
两者都是模型生成的输出,不是模型亲自执行程序
生成文字

新 Token 接回前缀,继续预测下一 Token,直到生成结束标记或达到外部限制。

调用工具

外部程序解析调用、执行函数,再把结果作为新消息放回上下文,模型随后继续生成。

核心边界:模型负责根据上下文生成“下一步”;聊天应用负责组织上下文、执行工具、保存状态并把结果显示给用户。

同一组 Decoder-only 层处理整段上下文

这条序列进入 Token Embedding,再加入位置信息,随后反复经过因果自注意力、FFN、残差连接和归一化。它没有一座独立的 Encoder,也不需要 Cross-Attention:当前回答位置需要的材料,都已经排在同一条序列的左侧。

因果遮罩仍然发挥作用。系统说明的位置只能参考它前面的 Token;用户问题可以参考更早的系统说明;助手准备回答的位置则可以参考系统说明、历史记录和当前问题。这样,虽然训练时整条序列可以并行处理,每个位置仍然无法偷看位于自己右侧的内容。

训练时练习所有位置,使用时读取最后位置

在基础的因果语言模型训练中,程序可以让序列中的许多位置同时预测各自的下一个 Token:前一个位置预测后一个位置,损失再汇总起来更新参数。到了聊天式监督微调,训练样本会进一步包含用户消息和理想的助手回答;一些训练流程只对助手回答等指定区域计算损失,使模型重点学习“看到这些消息后,助手应该怎样继续”。具体保留哪些位置的损失,取决于数据格式和训练实现。

正式聊天时并没有现成的正确回答。模型处理上面这段完整前缀后,应用只需要读取当前最后一个有效位置的输出,经由输出头得到词表上的候选分布,再选择一个 Token。它可能先生成“比如”,再逐步写出买桌子、规划旅行或控制开店成本的例子。每个新 Token 都被接回序列,成为下一轮前缀的一部分;这个循环不断重复,屏幕上才逐渐出现完整回答。

五、从续写到对话

我们已经习惯了向 AI 提问,然后得到回答,很容易觉得“模型回答问题”是理所当然的。但先回到模型刚完成基础训练的状态:它并不知道眼前是一位用户,也不知道自己应该帮助对方。它只会根据曾经见过的文字规律,把前文继续写下去。

例如,看到:

北京是中国的首都,东京是日本的首都,巴黎是

模型很可能接出“法国的首都”。它不是在回答谁,只是发现这段文字符合某种规律,并顺着规律补全后文。

现在再给它一句看起来像问题的话:

请比较骑自行车和开车上班,各写一个优点。

这时的模型可能把它当成一道尚未写完的练习题,接着写:

要求结合实际情况说明理由。下一题:公共交通有哪些优势?

这段话续写得很自然,却没有替用户完成任务。它也可能因为以前见过问答、访谈之类的文字而碰巧给出答案,但并不稳定。

为了让它学会回答,训练者会准备大量这样的示范:

问题:请比较骑自行车和开车上班,各写一个优点。
理想回答:骑自行车能锻炼身体;开车不易受天气影响。

看过大量“问题后面跟着理想回答”的示范后,模型逐渐学会:遇到这类输入,更应该接任务的答案。于是同一句输入再次出现时,它更可能直接回答:

骑自行车的优点是能锻炼身体;开车的优点是不易受天气影响。

模型其实一直都在续写。后训练只是让它学会,在对话里最合适的续写,就是回答用户的问题。

前面已经解释了一次训练如何发生:模型做出预测,损失衡量预测与目标的差距,梯度再推动参数调整。现代 LLM 通常会经历目标不同的几个训练阶段,才从一组随机参数逐渐变成我们使用的聊天助手。

MODEL LIFECYCLE

从“学会语言”到“回答这一次问题”

选择阶段查看变化
送入什么大规模文本中的 Token 序列
发生什么反复预测紧接着的 Token,并用误差更新参数
留下什么形成语言规律、模式与参数知识
最关键的区别参数持续更新
送入什么指令示范、回答比较与安全反馈
发生什么通过 SFT、偏好对齐等方法继续调整参数
留下什么基础模型变得更会遵循指令
最关键的区别微调等方法让参数继续更新
送入什么当前上下文与已经生成的 Token
发生什么用固定参数计算候选分布,再逐 Token 生成
留下什么得到本次回答
最关键的区别参数通常不更新

生成下一个 Token 的能力始终位于核心。训练阶段通过不同信号改变模型,推理阶段只让已经训练好的模型处理眼前上下文。

预训练和后训练都会使用第四章讲过的“预测、计算损失、更新参数”,区别主要在于:模型看什么数据、使用什么学习信号,以及希望参数形成什么倾向。 到了推理阶段,参数通常固定下来,只处理眼前这一次输入。

1. 预训练

Pre-training(预训练) 通常是规模最大、成本最高的一段训练。训练程序把网页、书籍、论文、代码等大量材料清洗、去重并切成 Token 序列,让模型反复预测下一个 Token。

例如,模型看到“水在标准大气压下的沸点是”,训练目标可能就是后面实际出现的“100”;看到一段程序的前半部分,目标则是原文接下来的代码。答案直接来自文本本身,不需要人为给每句话逐一贴标签,因此这类过程常被称为自监督学习

每次预测带来的参数变化都很小,但海量样本会反复推动模型调整 Embedding、Attention、FFN 等参数。模型由此逐渐形成语法、事实关联、文体、代码模式和一定的推理能力。训练材料并不是以一套可以逐条检索的文档原样装进参数;更接近于大量规律被分散地压缩进许多参数之间。不过,模型仍可能记住训练材料中的个别片段,这也是训练数据需要清洗、去重和治理的原因之一。

预训练结束得到的通常叫作基础模型(Base Model)。它已经很会续写,却未必知道用户是在下指令:面对“请比较两个方案”,它可能继续补写问题、模仿网页格式,或者给出不符合人类期待的内容。预训练解决的是“从大量文本中学会语言与模式”,不等于已经解决“怎样成为一个有用、安全、稳定的助手”。

2. 后训练

Post-training(后训练) 不是某一种单独算法,而是预训练完成后的一组训练过程。与预训练相比,它使用的数据通常少得多,但目标更集中:让模型更会遵循指令、组织回答、拒绝不合适的请求、调用工具,并让输出更符合人类偏好。

后训练常包含两个相互配合的部分:

  1. 监督式微调(Supervised Fine-Tuning,SFT):用上面这种“用户问题 → 理想回答”的示范继续训练模型。它学到的不只是答案内容,也包括怎样识别指令、采用合适格式和在何时结束回答。
  2. 偏好对齐:对同一个问题准备多个候选回答,让人类或其他评估系统指出哪些更好,再用这些比较继续调整模型。RLHF(基于人类反馈的强化学习)是其中一类方法,直接偏好优化等方法也能利用回答之间的偏好信号。它们的共同目标,是让更有帮助、更符合约束的回答更容易被模型生成。

所以,聊天助手的能力不是由 System Prompt 临时“扮演”出来的。Prompt 会在本次请求中引导已有能力;后训练则改变参数,让模型长期更倾向于按指令回答。与此同时,后训练也不是把“有用”“诚实”“安全”三个按钮调到最大:这些目标有时会互相拉扯,数据和评估方式也会影响最终行为。

3. 微调与后训练的关系?

这两个词经常被并列使用,但它们不在同一个分类层级:

  • 后训练描述的是阶段:预训练之后,为了把基础模型进一步塑造成助手而进行的一组工作。
  • 微调(Fine-tuning)描述的是方法:从一个已经训练好的模型继续出发,用新的数据更新它的部分或全部参数。

因此,SFT 既是微调,也通常属于后训练。企业用客服对话让通用模型更熟悉固定回复格式,也是在微调;用大量医学文本继续做下一个 Token 预测,则常叫作 持续预训练(Continued Pre-training) 或领域适配。广义上它同样是在已有参数上继续训练,却不一定属于“把模型对齐成助手”的那一步。

完整微调会更新模型的大部分或全部参数,成本较高;LoRA 等 参数高效微调(PEFT) 方法只训练少量新增参数或低秩变化,所需计算和存储更少。二者都会留下可复用的参数变化,这一点与只修改当前 Prompt 不同。

做法改变模型参数吗?主要解决什么问题
预训练是,通常从随机参数开始大规模更新获得通用语言规律、知识关联与基础能力
后训练是,在基础模型上继续更新指令遵循、偏好、安全与工具使用等助手行为
微调是,对已有模型做定向更新适配特定任务、领域、风格或输出格式
Prompt / 上下文学习告诉模型这一次具体怎样做
RAG / 工具调用通常否在使用时补充外部资料或计算能力

这里最容易混淆的是“给了模型几个例子”。例子如果只放在当前上下文里,属于上下文学习,结束请求后参数不变;如果训练程序用这些例子计算损失并反向传播,才属于微调。

LoRA:冻结原模型,只学习低秩变化

完整微调虽然直接,但代价也很高。一个线性层里的权重可以写成矩阵 W;完整微调会直接调整这个大矩阵中的许多数值,训练时还要为它们保存梯度和优化器状态。模型越大,需要参与训练和保存的数字通常越多。

**LoRA(Low-Rank Adaptation,低秩适配)**换了一种做法:先冻结原来的 W,不再直接改动它;然后在旁边增加一条较小的可训练分支,用它学出这次任务需要的变化 ΔW。模型实际计算时使用的效果可以简化写成:

新的权重效果 = 原权重 + 本次任务学到的变化
W' = W + ΔW

关键在于,LoRA 不直接训练一个与 W 同样大的 ΔW,而是把它限制为两个较小矩阵 AB 的乘积:

ΔW = B × A

如果原矩阵的输入、输出维度都很大,而 AB 中间相接的维度 r 很小,需要训练的数字就会少很多。这个 r 常被称为 Rank(秩):它可以粗略理解为这条新增分支用多少个方向来表达参数变化。较大的 r 提供更多调整空间,同时也会增加训练和存储成本;但它不是一个单独决定效果的“质量旋钮”,数据、学习率、训练步数以及 LoRA 加在哪些层上同样重要。

训练时,损失和反向传播仍然照常发生,只是梯度主要用来更新 AB 等少量新增参数,基础模型的原权重保持冻结。因此,LoRA 仍然是训练,不是 Prompt 技巧;它只是把“需要训练哪些参数”缩小了。Attention 中生成 Query、Key、Value 和输出的投影,以及 FFN 中的线性变换,都可能成为 LoRA 的目标模块,具体选择取决于模型和训练方案。

训练完成后,保存下来的通常是一份较小的 LoRA Adapter。它记录相对于某个基础模型的增量,而不是一套能够独立运行的完整模型:加载时仍需匹配的 Base Model。应用可以按任务切换不同 Adapter,也可以把增量合并进基础权重后再部署。小文件的优势主要是训练与分发方便,并不意味着推理时完全不需要加载基础模型。

完整微调LoRA
基础权重更新大部分或全部原参数通常冻结原参数
训练对象原模型中的大量权重少量低秩增量参数
保存结果通常是一份完整的新模型权重通常是依赖基础模型的 Adapter
主要优势调整空间充分降低训练内存、存储和多任务适配成本
需要注意训练和保存成本较高容量受 Rank、目标模块和训练配置限制

LoRA 属于更大的 **PEFT(Parameter-Efficient Fine-Tuning,参数高效微调)**家族。PEFT 的共同目标是:复用已经训练好的基础模型,只调整相对少量的参数来适配新任务;LoRA 是其中最常见的实现之一,而不是 PEFT 的另一个名字。 LoRA 原始论文 给出了这种低秩更新方法。

进阶阅读:Adapter、Prompt Tuning 与 QLoRA 有什么区别?可选阅读

“Adapter”有广义和狭义两种用法。 在模型分享和部署工具里,人们常把 LoRA 保存出的增量文件统称为 Adapter;更狭义的 Adapter Tuning 则是在网络层之间插入小型可训练模块。两者都属于 PEFT,但内部结构并不相同。

Prompt Tuning 和 Prefix Tuning 训练的是软提示。 它们不直接修改普通自然语言 Prompt,而是学习一小组连续向量,把这些向量作为额外输入或前缀交给模型。普通 Prompt 由用户在上下文中写入文字,请求结束后不会留下参数变化;软提示则需要训练,并可以作为参数保存。

QLoRA 把 LoRA 与量化组合起来。 LoRA 已经减少了需要训练的参数,但训练时仍要把体积很大的基础模型放进内存。QLoRA 进一步用较低比特表示冻结的基础权重,再训练通常保持较高精度的 LoRA 参数,从而继续降低微调的内存门槛。 QLoRA 论文 展示了使用 4-bit 量化基础模型进行高效微调的方法。

这也说明 LoRA 和量化解决的不是同一个问题:LoRA 决定哪些参数需要训练,量化决定权重用多少比特表示。 LoRA 可以不配合量化使用,量化也可以用于没有 LoRA 的模型;QLoRA 才是二者的一种组合。第七章会继续解释量化对存储、内存和效果的影响。

还有一个常见误解是“可训练参数更少,所以推理一定按同样比例加速”。LoRA 主要节省的是训练时的梯度、优化器状态以及每个任务的权重存储;推理速度还取决于 Adapter 是否合并、基础模型大小、量化方式、硬件和推理框架,不能只从 LoRA 参数量直接推出。

4. 蒸馏

先用一个生活类比

把蒸馏想成:名厨带学徒

类比不是实际训练过程
01 · 名厨先做 展示怎样处理一道菜

对应:教师模型给出答案

02 · 不只说对错 还告诉学徒哪里接近

对应:概率、解释等丰富信号

03 · 学徒练习 按反馈调整自己的做法

对应:只更新学生模型的参数

04 · 独立出餐 小店不必再请名厨到场

对应:学生模型独立部署

没有发生的事:不是把名厨“缩小”塞进学徒脑中。学徒学到的是名厨在练习中表现出的判断与做法,而且未必能学会名厨的全部本领。

Knowledge Distillation(知识蒸馏) 可以理解为“让一个模型当老师,训练另一个模型”。能力较强的 教师模型(Teacher Model) 先对大量问题给出答案或候选 Token 的概率分布;训练程序再把这些信号当作学习目标,更新 学生模型(Student Model) 的参数。学生通常更小、更便宜,目标是在保留尽可能多能力的同时,降低部署所需的内存、计算量和响应成本。

KNOWLEDGE DISTILLATION

老师不是被塞进学生体内,而是在训练时提供学习信号

训练与部署对照
训练样本 “这道题应该选什么?”
TEACHER · 大模型 先给出判断
ABCD
STUDENT · 小模型 根据差距更新参数
训练时:教师提供目标,训练程序只更新学生的参数;教师参数不会被复制过去。

为什么不只让学生学习标准答案?假设一道题有四个选项,普通标签只告诉它“B 正确”;教师模型给出的概率还可能透露“B 明显最好、C 有一点合理、A 和 D 基本不可能”。这种比单个正确标签更丰富的信号常被称为软目标(Soft Targets),它能帮助学生学习教师对不同候选结果的相对判断。现代 LLM 的蒸馏也常让教师直接生成回答、解释或指令数据,再用这些材料训练学生;不同方法使用的教师信号并不完全相同。

SOFT TARGET LAB

同样知道 B 正确,“软目标”还多告诉了学生什么?

拖动训练进度观察
学习目标教师的完整判断
A5%
B70%
C20%
D5%
学生当前输出正在学习相对关系
A25%
B25%
C25%
D25%
C 虽然不是答案,却比 A、D 更合理。 学生因此能学到“候选之间的相似程度”。
关键差别:硬标签只给一个结果;软目标给出一整套相对判断。图中概率仅用于解释概念,不代表真实模型输出。

蒸馏不是把教师的参数直接复制或压缩进学生。训练完成后,学生通常可以独立回答,不需要每次请求教师出场;它学到的是教师在训练样本上表现出的输出规律。蒸馏也不保证能力被完整搬走:学生容量更小,可能丢失长尾知识和复杂能力,也可能继承教师的错误与偏见。它在某个特定任务上甚至可能表现很好,但这不代表已经获得教师的全部通用能力。

它和前后几个概念的关系可以这样区分:

  • 与微调:两者可以重叠。用教师生成的数据继续训练学生,通常也是一种微调;“蒸馏”强调学习信号来自教师,“微调”强调从已有模型出发继续更新参数。
  • 与后训练:蒸馏可以用于后训练,把教师的指令遵循或偏好传给学生;它也可以用于其他训练阶段,因此不等同于后训练。
  • 与量化:蒸馏通常要训练一个学生模型,让它改变参数并学习教师行为;第七章会讲到的量化主要是用更少的比特近似保存和计算已有权重。二者都可能降低部署成本,但路径不同。

知识蒸馏的经典论文 把这种思路概括为:用较大模型或模型集合形成的知识,训练一个更适合部署的小模型。

5. 对话纠错不会自动训练模型

用户指出错误后,模型可以根据当前对话调整后续回答,但这只是利用上下文,并不会更新模型参数。只有数据进入后续训练流程,才可能影响未来的模型版本。

六、局限和缺点

1. 上下文容量与记忆

人们常说模型有一个很大的 Context Window,也常把它直接叫作“记忆”。这个说法方便,却容易让人以为模型像人一样把整段经历永久保存在脑中。

更准确的直觉是:Context Window 就像一块黑板——模型每次回答时,只能读到黑板上还留着的内容。写满了就得擦掉旧的,腾出地方写新的。

系统说明、聊天历史、用户刚输入的问题、模型已经生成的内容,以及应用取回的文件片段或工具结果,都可能被写到这块黑板上。模型当前能够直接使用的,是此刻确实留在窗口里的 Token。

两种不同的“记忆”

可以先把两类东西分开:

  1. 模型参数:模型在训练中形成的大量模式。它们包含语言规律和训练中形成的知识,但不是可以逐条翻阅的聊天记录。就像你学会了骑自行车,身体记住了平衡的感觉,却说不出自己到底是第几次练习时学会的——知识已经融进了身体,而不是存成一本可以翻页的笔记;
  2. Context Window:本轮推理直接可见的 Token。它变化快、容量有限,更像一块随时会被擦写的黑板。

所以,“我上周告诉过模型一件事”并不等于“模型今天一定能直接看到它”。对话只存在于当前 Context Window 中;窗口关闭后,模型本身不会把旧对话保存在任何地方。至于应用层如何通过数据库、搜索等方式为模型补充外部资料,属于检索增强生成(RAG)等系统设计的范畴,留到后续文章讨论。

窗口装不下时

回到文章开头的例子。用户先要求”用普通人能听懂的话解释第一性原理”,模型给出了回答,接着用户又追问了几个新问题。

如果应用只保留最近的消息,最初那条”用普通人能听懂的话”的要求可能已经被移出窗口。模型接下来虽然仍能回答,却可能不再知道需要保持通俗的风格。

截断与摘要

当材料超过容量时,应用必须决定怎样整理。常见方法包括:

  • 截断:直接移除一部分旧 Token。实现简单,但可能丢掉很早出现的关键条件;
  • 摘要:把较长的旧对话压缩成更短的说明。可以节省空间,但摘要本身可能遗漏细节或改变原意。

下面可以缩小教学窗口,并切换两种常见的应用层整理思路。

Context Window Simulator

缩小上下文窗口,看看哪条信息会先消失

教学单位,不是真实 Token 数
向左拖动,模拟可用上下文变小
完整对话记录越往下越新
System

你是一个知识科普助手,请用通俗易懂的语言回答。

7 单位
User

请用普通人能听懂的话解释一下第一性原理。

8 单位
Assistant

第一性原理,就是先把问题拆到最基本的事实,再重新推导答案。

7 单位
User

能再举一个生活中的例子吗?

7 单位
User

顺便也解释一下演绎推理。

7 单位
这一轮真正交给模型的内容已使用 0 / 36
Context Window 不是永久记忆。早期的对话一旦被移出窗口,模型就无法仅凭"之前聊过"看到那些内容。

这些整理工作通常由模型外部的应用系统负责。模型本身不会因为窗口满了,就自动拥有完美的长期记忆。

看得见,不等于用得好

容量是一个限制,但不是唯一的限制。即使材料仍在窗口里,模型也不一定能用好。

“可见”只表示模型有机会通过注意力读取它,不表示模型必然抓住重点。材料过多、指令互相冲突、关键信息埋得太深或表达不清,都可能让模型忽略本来位于窗口中的内容。

其中一种典型现象叫作 Lost in the Middle(中间信息利用不足) 相关研究 发现,在一些长文本问答和信息检索任务中,同一条关键信息放在上下文开头或结尾时,模型往往更容易正确使用;移到很长的上下文中间后,表现可能明显下降。这里的“Lost”并不是说中间的 Token 已经从窗口里消失,而是说模型对不同位置的信息利用并不总是同样可靠。

因此,Context Window 的大小表示容量上限,不等于等量的可靠理解能力。对真正关键的限制,清楚地写在靠近当前任务的位置,通常比把它埋在很长的历史记录中更稳妥。

进阶:Context 长度、输入长度和输出长度怎样计算可选阅读

Context Window 通常以 Token 数量计量。一次请求占用的不只是用户最后输入的文字,还包括系统消息、开发者消息、历史对话、工具结果、附件被转换后的内容,以及当前已经生成的 Token。

产品常把最大输入、最大输出和总 Context 上限分别设置。不同服务如何截断、是否自动摘要、工具结果怎样计入容量,都属于具体实现,不能从模型名称单独推断。

更长的上下文也不等于模型会平均重视所有位置。位置编码、注意力模式、训练方式和信息组织都会影响长上下文中的实际表现。

2. 知识的时效性

模型参数中的知识来自训练时见过的数据,而不是一套会随现实世界自动更新的数据库。训练结束后,参数在日常使用中通常是固定的;新闻刚刚发生、法规刚刚修改、软件刚刚发布新版本,都不会因为现实已经变化,就立刻写进模型参数。

因此,模型自带的知识更像是对一段历史资料的压缩,而不是实时世界的镜像。产品有时会给出一个“知识截止日期”,帮助用户粗略判断训练资料覆盖到什么时候;但这不是一条整齐的分界线:截止日期之前的事实也可能没有被训练数据收录,或者没有被模型可靠学会;截止日期之后的事情,模型也可能根据上下文进行猜测,但猜中不等于真正掌握了最新事实。

这个限制在询问“现在”的问题时尤其重要,例如:

  • 某家公司目前的负责人是谁;
  • 今天的汇率、票价或天气怎样;
  • 一项法规现在是否仍然有效;
  • 某个软件最新版本的接口如何使用。

对于这类问题,只让模型“再认真想一想”并不能补上训练结束后才出现的信息。用户在对话中纠正模型,也只会把新信息放进当前上下文,并不会当场改写模型参数。

搜索、RAG 和工具调用可以缓解这个问题。它们先从网页、数据库或其他来源取得较新的资料,再把结果放进当前上下文,让模型依据这些资料回答。但这时提供实时性的,是模型外部的系统,而不是模型参数突然更新了;回答是否可靠,仍取决于来源是否新鲜、准确并且适用于当前问题。关于外部资料如何被检索并交给模型,可以继续阅读 《从检索到回答:RAG 是怎样工作的》

如果系统没有取得最新证据,模型仍可能沿着训练中熟悉的表达给出一个流畅、肯定却已经过期的答案。这就连接到下一个限制:幻觉与证据。

3. 幻觉与证据

回到整篇文章最核心的一件事:语言模型训练和生成时反复练习的是根据已有上下文预测接下来最合适的 Token

这个目标非常擅长学习语言模式。它让模型可以续写句子、解释概念、转换格式,也能模仿资料中常见的事实表达方式。但“接下来听起来合理”与“这件事已经得到可靠证据支持”不是同一个问题。

持续生成的机制

模型总能在词表中找到下一 Token 的候选。即使上下文没有提供答案,常见地名、时间表达、机构语气和数字组合仍然可能形成一条语言上顺畅的路径。

如果“图书馆通常在晚上……”已经出现,那么“八点”“九点”“闭馆”都可能是很自然的延续。生成机制本身不会自动停下来核对事实;只有当当前上下文中已经给出可靠证据,或者应用另有验证流程时,回答才多了一层事实约束。

模型有时会表达不确定,也是在根据上下文生成合适的语言。它不是一个永远准确的内部报警器。因此,语气很肯定不能证明正确,语气很谨慎也不能自动证明错误。

证据对回答的约束

下面使用一个虚构机构。问题始终相同,只改变放进 Context 的证据。依次查看没有资料、来源冲突和官方资料三个状态。

Evidence Lab

问题不变,只改变模型这一轮能看到的证据

虚构机构教学示例
用户问题阿斯特拉市图书馆周二几点闭馆?
放进 Context 的资料1 条
当前上下文只有用户的问题

阿斯特拉市图书馆周二几点闭馆?

没有营业时间证据
更合理的回答方式无依据
阿斯特拉市图书馆周二通常在晚上 8 点闭馆。
关键事实检查“晚上 8 点”没有来源支持语言通顺,不代表事实成立

更可靠的做法是明确说不知道,并请求查询资料。

放进 Context 的资料2 条
社区帖子两年前的游客留言

我记得图书馆晚上 8 点关门。

时间较旧,身份不明
问答评论近期匿名回复

好像延长到晚上 9 点了。

较新,但没有官方确认
更合理的回答方式仍需核验
现有非官方资料分别写着晚上 8 点和 9 点,我无法确认周二的准确闭馆时间。建议查看官方通知。
关键事实检查准确呈现了来源冲突有信息,但证据还不足以定论

可靠回答不一定给出一个数字,也可以诚实保留不确定性。

放进 Context 的资料1 条
官方开放时间图书馆本月公告

周一至周五 09:00-21:00;周六、周日 10:00-18:00。

直接来源,适用于当前日期
更合理的回答方式可以回答
根据图书馆本月公布的开放时间,周二晚上 9 点闭馆。
关键事实检查“周二晚上 9 点”可由公告直接推出回答与当前证据一致

仍应让读者知道依据是什么,并保留日期范围。

语言模型的生成问题下一段文字怎样接最自然?
事实验证问题这项主张有什么可靠证据?
外部资料不会自动消灭错误。来源可能过期、互相冲突或被模型误读;可靠性来自证据质量、正确引用和必要时承认无法确认。

这个例子说明,可靠回答不总是“给出一个明确数字”。

  • 没有证据时,承认无法确认比编出一个常见营业时间更可靠;
  • 来源冲突时,指出冲突并继续核验比随便挑一个说法更可靠;
  • 有适用的直接来源时,回答应清楚说明结论来自哪里。

有资料仍然可能出错

从取得资料到生成回答,中间还有多处可能出错:

  1. 提供的资料可能与问题并不真正相关;
  2. 资料可能已经过期,或与当前日期和地区不匹配;
  3. 多个来源可能互相冲突;
  4. 模型可能误读表格、否定词、单位或适用条件;
  5. 回答可能正确引用了资料,却在没有证据的地方继续补充细节。

从外部取得资料并放入 Context,可以给模型补充当前证据,却不是一个“事实永远正确”的开关。怎样寻找、筛选和组织外部资料属于另一套系统问题,留到后续文章展开。对于医疗、法律、财务、安全等高风险问题,仍然需要查看原始来源和合格专业人员的判断。

让回答更容易核验

可以主动改变任务,而不是只要求“给我一个答案”:

  • 提供与问题直接相关的原始材料;
  • 要求把事实主张和依据对应起来;
  • 要求明确区分已知、推测和无法确认;
  • 对日期、数字、人物和引用回到原始来源检查;
  • 在证据不足时允许模型停止,而不是强迫它必须给出完整结论。

这些方法不会改变语言模型的基本生成机制,但会让上下文包含更好的约束,也让用户更容易发现错误。

进阶:幻觉与校准可选阅读

“幻觉”通常泛指模型生成了无依据、与来源冲突或不可验证的内容。它不是某一个单独模块触发的故障,而可能来自训练数据、提示上下文、采样路径、知识时效、证据质量和模型推理错误等多种因素。

概率校准研究的是模型给出的置信程度与实际正确率是否匹配。下一 Token 概率本身并不是整项事实主张的校准置信度,把最高 Token 概率直接解释成答案正确率是不可靠的。

4. 推理与事实

这里说的“推理”是 Reasoning,也就是上一章推理模式试图加强的分析问题、连接线索的能力,不是表示整个使用阶段的 Inference。可以先把它和知识粗略分开:

  • 知识或证据决定模型当前有哪些事实可用;
  • 推理决定模型怎样连接、比较或转换这些信息。

REASONING ≠ KNOWLEDGE

“多想几步”能整理线索,但不能凭空增加事实

切换资料状态
可用事实
A 方案每月 80 元
B 方案每月 60 元
预算上限 70 元
推理步骤
1比较两个价格
2排除超过预算的 A
3选择 B
可支持的结论B 方案符合预算推理可以连接已有事实
可用事实
预算上限 70 元
A、B 的最新价格未提供
推理步骤
1无法完成价格比较
2继续写步骤也不会产生最新价格
3需要先取得价格来源
可支持的结论目前无法判断更多推理不能补出缺失证据
日常用法:计算、比较、归纳出错时,可以要求拆分步骤;涉及“最新、真实、具体是谁”等事实时,应先补资料和来源。两种问题需要不同的补救方式。

当一道题已经给出价格、预算和规则时,把任务拆成中间步骤,可能帮助模型减少遗漏。 Chain-of-Thought 论文 显示,提供包含中间推理步骤的示例,可以改善一些算术、常识和符号推理任务的表现。但这类结果不意味着只要要求“再认真想想”,模型就能知道今天刚变化的价格、未提供的文件内容或从未取得的事实。

这个区别也解释了为什么两类错误需要不同处理:

  • 如果资料齐全,但模型在比较、计算或约束组合上出错,可以拆分问题、列出中间结果并逐项检查;
  • 如果关键事实缺失或已经过期,需要补充原始材料、查询可靠来源,并标明日期与适用范围。

推理过程写得很长,也可能建立在错误前提上。可读的步骤方便检查,不是正确性的证明。尤其涉及真实世界事实时,应同时检查“推导是否合理”和“前提是否有依据”。

5. 精确计算

LLM 的基本任务是根据当前上下文预测下一个 Token,而不是像计算器那样按照一套确定的运算规则执行每一步。因此,它可以从训练数据中学会许多数学表达和解题模式,也能答对不少计算题,但生成出“看起来像正确答案”的数字,并不等于内部已经完成了可验证的精确计算。

简单、常见的算式可能直接命中模型熟悉的模式;当数字变长、步骤增多,或问题涉及小数、单位换算和连续中间结果时,一个位置预测错误就可能影响后续整条生成。数字还会被 Tokenizer 切分成若干 Token,模型处理的是这些符号片段之间的关系,而不是天然保存为可直接运算的数值。

这并不意味着 LLM 完全不能处理数学问题。它仍然适合解释概念、选择解题方法、把问题拆成步骤,或把自然语言转换成公式;但如果任务要求精确、可重复的数值结果,更可靠的做法是让模型调用计算器、代码或专门的数学工具,再对结果进行校验。尤其在财务、工程、科研等容错率很低的场景中,不应只凭语言模型直接生成的数字作出判断。

6. 输入长度与生成速度

前面已经看到,完整回答来自一次次“预测一个 Token,再把它接回上下文”。这意味着回答越长,生成轮数就越多,而且每一轮可见的历史前缀都比上一轮更长。

仍以文章开头的问答为例。假设模型已经写出:

第一性原理,就是先

现在要预测下一个 Token,新 Token 必须参考前面的“第一性原理”“就是”“先”等内容,才可能继续生成“把一个复杂问题拆解到最基本的事实……”。下一轮又会多一个位置。模型不但要走更多轮,每轮需要查阅的历史也在增长。

Prefill 与 Decode

先排除一个很容易产生的误会:Prefill 不等于 Encoder,Decode 也不等于 Decoder。

前文的 Encoder / Decoder 是模型的结构名称,回答“模型由哪些模块组成”;这里的 Prefill / Decode 是一次生成的运行阶段,回答“同一套模型先做什么、后做什么”。以 GPT 这类只有 Decoder 的模型为例,Prefill 和 Decode 都由同一套 Decoder 层完成,并不存在先经过 Encoder、再经过 Decoder 的对应关系。

一次回答会前后经过两个运行阶段:

  • Prefill(输入处理):回答出现之前,模型先读完并处理这次请求中已有的全部内容,包括 Prompt、对话历史和附带资料;
  • Decode(逐步生成):回答开始之后,模型反复预测下一个 Token,一次接一个,直到生成结束。

两者的区别不在于换了一套模型,而在于同一套模型处理 Token 的方式不同:Prefill 面对的是已经存在的整段输入,Decode 面对的是尚未生成的下一个 Token。

同一套模型,两个运行阶段

先处理已有输入,再生成新的 Token

回答出现之前
Prefill 输入处理

模型先处理请求里已有的全部内容:Prompt、对话历史和附带资料。

输入越长通常越晚开始回答
分界点首个 Token 出现此时用户开始看到回答
回答开始之后
Decode 逐步生成

模型每轮预测一个新 Token,再把它接到已有内容后面,直到回答结束。

输出越长通常越晚生成完毕
Prefill 主要影响要等多久,回答才开始出现
Decode 主要影响回答开始后,内容生成得有多快

不要与模型结构混淆:Prefill ≠ Encoder,Decode ≠ Decoder。这里展示的是一次生成的前后阶段。

Prefill 阶段,输入越长,需要完成的初始计算通常越多,因此用户可能更晚看见第一个 Token。在 Decode 阶段,输出越长,需要依次进行的生成轮数越多,因此完整回答需要更久。

因此,比较等待体验时,可以分开看:多久开始回答,以及开始回答后内容出现得有多快。 Splitwise 论文 也将生成式 LLM 推理区分为计算密集的 Prompt 处理阶段与内存密集的 Token 生成阶段,并指出它们有不同的延迟与资源特征。

为什么输出 Token 通常更贵?

理解了 Prefill 和 Decode,账单中 Input 与 Output 的价格差异也更容易理解。**输入 Token 都已经存在,模型可以在 Prefill 阶段并行处理其中许多位置;输出 Token 却还不存在,只能在 Decode 阶段一个接一个生成。**后一个 Token 依赖前一个 Token 的结果,因此无法提前把整段回答一次算完。

这意味着,同样是一个 Token,生成输出时往往需要让模型和硬件等待更多轮:每生成一个新 Token,都要再次经过模型各层,并读取越来越长的历史信息。这个过程的并行度较低,也会持续占用计算和内存带宽资源。因此,服务商通常把 Output Token 定得比 Input Token 更贵;从 OpenAI 的模型价格表 也可以看到,许多文本模型会分别列出 Input、Cached Input 和 Output 的价格。

不过,价格不是硬件成本的精确换算。它还会受到模型、上下文长度、服务方式和商业策略影响,所以更准确的说法是:逐 Token 串行生成解释了输出通常更昂贵的重要技术原因,但不代表所有服务商、所有模型都必须采用同一种价差。

KV Cache:给已经读过的内容留下“计算笔记”

前文讲 Q、K、V 时提到:一个位置会用自己的 Query 去匹配其他位置的 Key,再按照匹配结果读取 Value。生成回答时,这件事会一轮又一轮发生。

假设模型已经写出“第一性原理,就是先”。为了继续生成“把一个复杂问题拆解……”,新位置需要查询“第一性原理”“就是”“先”等历史位置的 Key 和 Value。这些旧位置的 Key 和 Value 在上一轮已经计算过。如果不把它们保存下来,模型每生成一个新 Token,都得为此前所有 Token 重新计算一遍 Key 和 Value;前文越长,重复计算就越多。

KV Cache(Key-Value Cache)就是模型在本次生成中的计算笔记。 一个 Token 第一次经过每一层的 Attention 时,系统把它得到的 Key 和 Value 保存起来。下一轮生成时,旧位置直接读取这些结果,只为新位置计算新的 Key 和 Value,再把新结果追加进缓存。

为什么缓存 K 和 V,却不缓存 Q?

先记住一句:Q 属于“这一轮的提问”,K 和 V 属于“可被后续反复查阅的历史资料”。

这一轮 当前位置「先」
新建 Q 接下来该关注什么?
查阅历史 K / V
第一性原理就是
得到「把」
Q 用完,任务结束历史 K / V 留在缓存
下一轮 当前位置变成「把」
换一个新 Q 现在又该关注什么?
继续查阅同一批历史 K / V
第一性原理就是+把
继续生成
上一轮的 Q 不再参与旧 K / V 复用,新 K / V 追加
Q每轮都会换用完即止,不值得缓存
K + V随着历史累积后续每轮都可能再用

图里最关键的变化是:生成位置从“先”移到“把”以后,模型会提出一个新的 Q;但刚才查阅过的历史 K 和 V 仍是下一轮的资料。所以 Q 只需当场使用,K 和 V 却值得留下来反复复用。这就是它叫 KV Cache,而不是 QKV Cache 的原因。

拖动下面的序列长度,可以比较同一轮在有无缓存时分别发生什么。

KV Cache Visualizer

生成越往后,前面已经写过的内容就越多

比较当前这一轮
不使用 KV Cache旧材料再次准备

为了得到本轮注意力需要的 Key 和 Value,前缀中已经出现的位置也要再次计算。

本轮重复准备旧位置8 个
使用 KV Cache旧材料直接复用

历史位置的 Key 和 Value 已经保存在缓存里,本轮只为新增 Token 计算一份。

本轮新增计算1 个位置
节省了什么旧 Token 的 K、V 不必反复重算
付出了什么需要保存 8 组历史 K、V
但注意力仍要查阅历史。新 Token 的 Query 仍会与缓存中的历史 Key 比较,并按权重读取 Value。序列越长,可查阅的位置仍然越多。
KV Cache 是推理缓存,不是聊天记忆。它保存本次生成可复用的中间计算,不能扩大 Context Window,也不会让模型永久记住对话。

它带来的交换很直观:

  • 少做重复计算:历史 Token 的 Key 和 Value 不必一轮轮重新生成;
  • 多占一些内存:模型的每一层都要为当前请求保存这些中间结果,内容越长,缓存通常越大。

所以 KV Cache 常被概括为“用内存换计算”。它不会替模型记住一段话的意义,也不会扩大 Context Window;它只是让模型在现有上下文里,不必反复制作已经算过的 Key 和 Value。一次生成结束后,这份缓存通常也可以被释放。

它和前文账单里的 Prompt Cache 有技术联系,但范围不同:这里的 KV Cache 主要服务于同一次回答内部,Prompt Cache 则让服务有机会在不同请求之间复用相同输入前缀的计算结果。Cached Input 是这种跨请求复用在账单上的计费名称,不能把一次生成中使用的所有 KV Cache 都算作“缓存输入”。

缓存也不是产品记忆

“模型记住了”“这轮不用重算”“账单显示缓存命中”听起来都像系统保留了某些东西,但保存对象和生命周期并不相同。

THREE DIFFERENT LAYERS

名字都像“记住了”,实际保存的东西完全不同

跨越范围示意
一次生成内部
KV Cache
保存什么
各层历史 Token 的 Key / Value 数值
能留多久
通常随本次生成结束而释放
解决什么
下一个 Token 不必重算旧位置的 K、V
不是聊天记忆
相似请求之间
Prompt Cache
保存什么
服务识别出的相同输入前缀计算结果
能留多久
由服务商策略决定,可能有时限
解决什么
相同长前缀再次出现时减少重复处理
不是把回答写进模型参数
应用或产品层
产品记忆
保存什么
被保存的偏好、对话或资料
能留多久
按产品设置跨轮次或跨会话保存
解决什么
未来取回相关内容,再放进 Context
不是 Attention 的计算缓存

判断方法:问它保存的是“神经网络中间数值”“重复前缀的计算结果”,还是“以后要重新放回 Context 的内容”。三者用途不同,也都不会自动让事实变正确。

产品记忆属于应用层的保存与取回:产品可能保存用户偏好或旧对话,并在未来把相关文字重新放入 Context。模型之所以能使用它,是因为应用重新提供了内容,而不是因为 Attention 一直保留着某次回答的 KV Cache。

进阶阅读:KV Cache 的计算边界、规模与优化可选阅读
有缓存,生成仍然不是固定成本

缓存省去的是历史位置重复生成 Key 和 Value 的过程,不是整个 Attention。Decode 阶段的新 Query 仍需与缓存中的历史 Key 比较,再读取相应的 Value;可访问的历史越长,需要比较和读取的位置通常越多。

此外,自回归生成存在天然的先后顺序:第 20 个新 Token 依赖前 19 个生成结果,因此不能在一开始就把未知的未来 Token 全部并行算完。这也是长回答仍然需要等待的根本原因之一。

Prefill、Decode 和缓存规模

Prefill 会并行处理请求中已经存在的整段输入,并在每一层建立 KV Cache;Decode 再逐 Token 生成,每一轮向缓存追加一个位置。缓存规模会随层数、缓存的序列长度、KV Head 数量、每个 Head 的维度、批量大小和数值精度增长。

因此,并发请求很多或上下文很长时,KV Cache 会形成明显的显存压力。MQA、GQA、滑动窗口、KV Cache 量化和分页式缓存等方案,会从不同方向减少保存量、限制可访问范围或改善内存管理;它们没有改变“当前 Query 读取历史 Key 和 Value”这条主线。

具体显存占用和速度还取决于模型架构、硬件与推理框架,所以正文的交互组件只比较需要计算或保存的位置数量,不把教学示意当成某块硬件上的真实耗时。


七、LLM 的发展

1. 从语言模型到通用助手

2017 年的原始 Transformer 使用 Encoder–Decoder 结构完成翻译。随后的 GPT 系列采用 Decoder-only 主干,把任务说明、参考材料和已有文本放进同一段上下文中继续生成。具体结构前文已经展开,这里只保留这次关键转向,再看预训练和使用方式怎样继续改变模型的用途:

  • 2018 年, GPT BERT 展示了两条影响深远的预训练路线。GPT 用自回归预训练支持多种下游任务;BERT 则强化了双向语境理解,推动了分类、问答和信息抽取等任务的发展。
  • 2020 年, GPT-3 展示了上下文学习:只在输入中提供任务说明或少量示例,模型就能尝试新任务,不必为每项任务单独更新参数。
  • 2022 年, InstructGPT 与 ChatGPT 让指令微调和人类反馈走向主流。模型不只续写文本,也更能理解用户意图,并以对话形式完成任务。
  • 此后,检索、工具调用和多模态输入逐渐进入产品系统。语言模型仍负责生成,但它能使用外部资料、调用程序,并处理文字之外的信息。

因此,今天看到的 AI 助手并不是单独一个“续写模型”。它通常由语言模型、后训练方法和外部系统共同组成。

2. 多模态:让模型同时看见与听见

当我们上传一张冰箱照片,再问“这些食材能做什么”,问题里其实有两种信息:图片告诉模型“有什么”,文字告诉模型“要做什么”。能在一次任务中处理这类不同形式信息的模型,通常被称为多模态模型(Multimodal Model)。这里的“模态”就是信息的形式,例如文字、图片、声音和视频。

多模态并不只表示“可以上传文件”,还要分清输入输出。一个模型可能能看图,却只能用文字回答;语音聊天也可能由“语音转文字 → 语言模型 → 文字转语音”三个模型接力完成。对用户来说它们都像一个会听会看的助手,但内部不一定是同一个模型从头处理到尾。

从图文对齐到通用助手

图像与语言的结合早于聊天助手。下面不是完整的多模态研究史,而是几次容易辨认的转折:

  • 2021 年, CLIP 从大量图片与文字配对中学习“哪段文字更像在描述哪张图”。它不是聊天模型,却展示了图片和文字可以被映射到可比较的表示空间,为后来的视觉语言模型提供了重要基础。
  • 2023 年 3 月, GPT-4 发布时已经展示图片与文字共同输入;同年 9 月,图片理解开始 向 ChatGPT 用户推出 。如果把范围限定在大众熟悉的通用聊天助手,这是多模态从研究演示走向日常使用的一次代表性节点。
  • 2023 年 12 月, Gemini 1.0 以“原生多模态”发布,从预训练阶段就同时接触文字、图片、音频等信息,而不是只在已经训练好的语言模型外面增加视觉模块。
  • 2024 年 3 月, Claude 3 全系列加入图片理解,可分析照片、图表和技术图;同年 5 月, GPT-4o 进一步把文字、视觉与音频放进同一个端到端模型中。
  • 2024 年 9 月, Llama 3.2 发布 11B 与 90B 两个视觉版本,让可下载、可自行部署的 Llama 系列也拥有图文理解能力;同系列的 1B 与 3B 仍是纯文字模型。

因此,“GPT、Gemini、Claude、Llama 支持多模态”只是一种家族级简称。真正决定能力的是具体版本:它接收哪些模态、能输出哪些模态,以及产品有没有开放这些入口。例如 GPT-4 能接收图片并输出文字,GPT-4o 才把实时音频等模态更深入地放进同一模型;Claude 3 的多模态重点是图片输入、文字输出;Llama 3.2 也只有标明 Vision 的 11B 和 90B 版本能看图。产品能力还会分批上线,所以“模型已经支持”与“每位用户今天都能使用”不是一回事。

不同信息先变成共同的计算语言

前文已经看到,文字会被拆成 Token,再变成向量。图片和声音走的是相似但不完全相同的入口:图片可以被切成许多小块,声音可以被分成连续的时间片段;各自的编码器(Encoder)负责提取颜色、形状、音高、节奏等线索,并把它们变成一组数字向量。

切换下面的输入,观察变化发生在哪里。组件展示的是一类常见架构的简化流程,不代表每个商业模型的内部实现都完全相同。

MULTIMODAL INPUT

不同输入,先翻译成模型能计算的向量

切换输入,观察入口怎样变化
原始输入 “冰箱里还能做什么?”
各自的入口 Tokenizer + Embedding 文字先被拆成 Token,再查表变成向量。
对齐后的表示
冰箱[1.2, −0.3, …][2.2, −0.4, …]还能[3.2, −0.5, …][4.2, −0.6, …]什么[5.2, −0.7, …][6.2, −0.8, …]
原始输入 一张冰箱内部的照片
各自的入口 Vision Encoder(视觉编码器) 图片通常被切成小块;视觉编码器把局部和整体线索变成一组向量。
对齐后的表示
左上区域[1.2, −0.3, …]右上区域[2.2, −0.4, …]中间区域[3.2, −0.5, …]门架区域[4.2, −0.6, …]下层区域[5.2, −0.7, …]整体特征[6.2, −0.8, …]
原始输入 一段口述问题的录音
各自的入口 Audio Encoder(音频编码器) 声音按时间片段处理;编码器把语音、停顿等线索变成向量。
对齐后的表示
0–0.4s[1.2, −0.3, …]0.4–0.8s[2.2, −0.4, …]0.8–1.2s[3.2, −0.5, …]1.2–1.6s[4.2, −0.6, …]1.6–2.0s[5.2, −0.7, …]语音特征[6.2, −0.8, …]
共同的后半程 不同模态的向量 + 文字问题 Transformer 联合处理上下文 逐步生成回答

关键不是把图片硬变成一句描述,而是先把其中的视觉特征变成一组可参与计算的向量,再让它们与文字线索互相影响。图中的切块、数值和食物均为教学示意。

这些向量还不能直接和文字顺畅配合,通常需要经过一个连接器或投影层(Projector),把视觉、音频表示转换到语言主干能够接收的维度与表示空间。可以把它想成不同部门先把资料整理成同一种表格格式:内容没有变成文字,但已经能进入同一套计算流程。

进入模型后,不同模态的信息会通过 Self-Attention 或 Cross-Attention 互相取得线索。模型处理“照片里有哪些食材”时,文字中的“食材”可以把注意力分给图片里对应的区域;接着,语言解码器仍可沿用前文讲过的方式,逐 Token 生成“鸡蛋、牛奶和青菜可以……”这段回答。

以公开的 LLaVA 为例,它把预训练视觉编码器和语言模型用一个投影层连接起来,再用图文指令数据训练两边如何配合。这是理解视觉语言模型很清楚的样板,但不是唯一方案:有些模型在语言主干的中间插入 Cross-Attention,有些从预训练开始就在统一序列中混合多种模态;能够生成声音或图片的系统,还可能把输出交给音频解码器或扩散模型。

进阶阅读:多模态能力怎样训练出来?可选阅读

多模态训练通常要解决两个相连的问题。

第一步是对齐(Alignment)。模型需要从图片—文字、音频—文字等配对数据中学会:哪些视觉或声音模式与哪些语言概念相关。CLIP 使用对比学习,让匹配的图片与文字表示靠近、不匹配的组合远离;这建立了跨模态对应关系,但本身还不等于会对话。

第二步是多模态指令训练。训练样本不再只有“图片配一句标题”,还会包含针对图片提问、比较图表、按语音指令操作等任务。模型据此学习什么时候读取哪种模态,以及怎样把取得的线索组织成符合要求的回答。

“原生多模态”通常表示模型从较早的预训练阶段就共同学习多种模态;“拼接式”架构则可能先保留一个成熟语言模型,再增加视觉编码器与连接层。前者不必然在所有任务上更强,后者也不只是把图片先写成一句描述。两条路线都需要把不同来源的表示对齐,并通过 Attention 让它们在任务中交换信息。

若模型直接输出语音或图片,后半程也会变化。系统可以先生成文字,再由独立的语音或图像模型转换;也可以把音频、图像表示也离散成可预测的单元,让一个统一模型生成。厂商通常不会公开全部实现细节,因此不能仅凭产品界面判断它采用了哪条路线。

多模态扩大了模型可以利用的信息,却没有让它获得人的感官。图片在缩放、切块和编码时可能丢失细节;模型也可能认错小字、数量、空间关系,或对图中不存在的物体作出确信描述。它只是把文字之外的信息接入同一条推理链,并不等于完整、可靠地看懂了现实。

3. 参数规模

参数是训练过程中被不断调整的数字。参数更多,通常意味着模型有更大的容量,但也会增加训练、存储和运行成本。下面只保留 GPT、Grok、Llama、DeepSeek 等大众更熟悉、且至少有一个可核验参数节点的系列,用对数纵轴观察近几年的数量级变化。

代表模型参数规模 · 2018—2025

几条代表路线的参数增长

GPT Llama DeepSeek Grok 虚线 = 外部估算
实点与实线为厂商公开数据;空心点与虚线为第三方估算。 GPT-4 的约 1.8T 来自 SemiAnalysis,Grok 4 的约 3T 来自 Epoch AI;两者都不是厂商官方规格。GPT-5 暂无可靠参数值,因此不在纵轴上绘点。MoE 型号同时标注总参数与每 Token 激活参数。
  1. 2018 年 GPT-1:0.117B(官方)。
  2. 2019 年 GPT-2:1.5B(官方)。
  3. 2020 年 GPT-3:175B(官方)。
  4. 2023 年 GPT-4:约 1,800B 总参数(SemiAnalysis 估算)。
  5. 2023 年 LLaMA:65B(官方)。
  6. 2025 年 Llama 4 Maverick:400B 总参数 / 17B 激活(官方)。
  7. 2024 年 DeepSeek-V3:671B 总参数 / 37B 激活(官方)。
  8. 2024 年 Grok-1:314B 总参数 / 约 79B 激活(官方)。
  9. 2025 年 Grok 4:约 3,000B(Epoch AI 估算)。

参数之外,还要看什么规模?

参数量只回答“模型内部有多少个可调数字”,不能单独说明这些数字接受过怎样的训练,也不能直接算出一次回答需要多少资源。要更完整地理解模型规模,至少还要同时看三件事:

观察维度它描述什么单独看它不能说明什么
训练数据训练使用了多少 Token,以及语言、代码、专业资料等数据怎样配比、清洗和去重数据更多不等于质量更高,也不保证覆盖某项具体知识
训练计算量参数在多少批次上经历了多少次计算与更新,通常会用 FLOPs 等指标估算花费更多计算不等于方法更好,也不等于每次使用都同样昂贵
推理计算量一次请求实际激活哪些参数、处理多长输入、生成多少 Token,以及使用多少推理时计算预算它会随任务和运行设置变化,不是模型发布时就固定的一项能力分数

这三个维度彼此制约。参数很多但训练数据不足,许多容量可能没有得到充分利用;数据很多但模型过小,也可能难以吸收其中的模式。Chinchilla 的 计算最优训练研究 说明,在给定训练计算预算下,参数量和训练 Token 数需要合理配合,而不是只把参数做得越大越好。

到了使用阶段,还要区分“模型总共拥有多少参数”和“这次生成实际用了多少计算”。Dense 模型通常让各层参数按固定路径参与每个 Token 的计算;MoE 模型则只激活部分 Expert。输入越长,Prefill 工作越多;输出越长,Decode 轮数越多;提高推理强度还可能增加额外的推理时计算。因此,两个参数量相近的模型,一次回答的速度、显存需求和成本仍可能明显不同。

可以把它们压缩成四个问题:有多少容量?用什么数据训练?训练时投入多少计算?回答这一次问题时又投入多少计算?参数量只是其中第一个答案。

4. MoE:可选择的 FFN

可以先把模型这一小段处理过程想成餐厅后厨。每个 Token(文字被拆成的小片段)像一张刚送进后厨的订单:它已经带着前文提供的线索,接下来需要被进一步加工。

普通的 Dense 模型像只有一座中央工作台的后厨。无论订单是什么,每一张都会经过同一座工作台。MoE(专家混合)则像多摆了许多工作台,并安排一位分单员:每张订单来了,分单员只叫其中一两座最合适的工作台开工。其他工作台仍在店里,只是这一单没有用到它们。

这就是为什么 MoE 的总参数量和一次回答中实际使用的激活参数量是两回事:前者像店里全部设备的总量,后者像这一单真正启动的设备。时间线中的 DeepSeek-V3 与 gpt-oss-120b 正是这类模型:它们拥有很大的参数容量,却不要求每个 Token 都经过全部参数。

下面用“算出 6 × 后面该接什么”作为一张订单。它带有数字、运算符和“要接一个很短答案”的线索;图中点亮的工作台,表示它们更容易处理这类模式。

一张订单,进入两种不同的后厨

普通后厨走固定工作台;MoE 先挑少数工作台开工

订单:算出 6 ×线索:数字 · 运算符 · 短答案
普通后厨Dense FFN
一张订单算出 6 × 的下一小步
中央工作台同一座 FFN每张订单都走这里
加工后的订单这座工作台的参数都参与
无论订单内容如何,都经过同一座工作台。
有分单员的后厨MoE layer
一张订单算出 6 × 的下一小步
分单员(Router)根据订单线索给工作台打分优先叫更适合数字与短答案模式的少数开工
数字与符号72%开工 · Expert 1
代码格式11%本次休息 · Expert 2
短答案续写28%开工 · Expert 3
长句表达06%本次休息 · Expert 4
按分数加权合并“数字与符号”与“短答案续写”的结果
这张订单的线索,更容易点亮其中两座“对路”的工作台。
看差异的关键:MoE 不只是随机挑工作台,而是根据当前订单的线索,优先调用更“对路”的少数分支。工作台的名字与分数均为教学示意;真实 Expert 没有这样固定、人工命名的分工。

图里的“订单”“分单员”和“工作台”只是帮助理解的比喻:真实模型中,它们分别对应 Token、路由器(Router)和一组独立的 FFN 参数。这里给工作台写的“数字与符号”“短答案”等标签,是对它们可能形成的偏好的通俗描述;所谓 Expert 不是工程师预先命名的“数学专家”或“中文专家”,而是在训练中逐渐形成不同偏好的参数分支。

多工作台不等于每一单都会更快或更便宜。没有被选中的 Expert 仍要占存储空间;如果工作台分布在不同设备上,传递订单也会产生通信和调度成本。MoE 的重点是:用很大的总容量,换取每个 Token 只使用其中一小部分的计算。

进阶阅读:MoE 怎样选择 FFN?可选阅读

从稠密 FFN 到可选择的 FFN

普通 FFN 中,无论当前处理的是 Thereno 还是 spoon,每个 Token 都会经过同一套 FFN 参数。这样的结构通常称为稠密(Dense):这层里的参数会按既定路径参与每个 Token 的计算。

但模型想容纳更多处理模式时,如果只是不断把这座 FFN 加宽,每个 Token 的计算量也会跟着增加。于是,一些现代模型采用 MoE:不再只放一座更大的 FFN,而是放置多座可选择的 FFN 分支,并在它们前面增加一个路由器(Router)

一种常见的 MoE 计算过程是:

  1. 当前 Token 已经经过 Attention,带着它从上下文取得的线索来到 MoE 层;
  2. 路由器根据这行 Token 表示,为多个 Expert 分支计算分数;
  3. 只选择得分较高的一个或少数几个 Expert 来处理它;
  4. 被选中分支的结果按权重合并,再送回 Transformer 的主干。

这里的 Expert 通常就是一组独立的 FFN 参数。路由发生在 Token 层面,也发生在模型的不同 MoE 层中,因此同一句话里的不同 Token 可能被送往不同分支,同一个 Token 到了更高一层也可能走向另一组分支。

可以把普通 FFN 想成一家只有一座中央厨房的餐厅,所有订单都经过同一套设备;MoE 则像增加了多座工作台,由分单员为每张订单选择少数几座实际开工。餐厅拥有的设备总量可以很大,但做一张订单时,不必同时启动全部设备。

这个类比同时解释了 MoE 最重要的目的:增加模型的总参数容量,同时避免让每个 Token 都使用全部参数进行计算。这类只激活部分分支的方式也叫作稀疏激活(Sparse Activation)。它并不让计算免费——Attention、路由器、被选中的 Expert 和模型中的其他稠密部分仍然需要运行——但总参数量可以比每个 Token 实际用到的参数量大得多。

普通 Dense FFNMoE 层
每个 Token 经过什么同一座 FFN路由器选中的少数 Expert
总参数与本次计算这层参数按固定路径参与计算拥有很多 Expert,但本次只激活一部分
新增的难题主要是扩大网络后的计算成本路由均衡、跨设备通信、训练稳定性与部署复杂度
“专家”的分工

Expert 的分工来自训练,不是工程师事先建立“数学部”“代码部”“中文部”,再把问题按学科派过去。研究者有时能观察到某些分支对特定 Token、语言或模式更常被选择,但这种偏好通常是统计性的、分布式的,也可能很难用人类概念准确命名。

所以,在 Prompt 里写“请让数学专家回答”,并不等于用户取得了路由器的控制权,更不代表模型内部真的存在一位可以单独召唤的数学人格。路由器看到的是当前 Token 的内部数字表示,选择的是参数分支。

MoE 的实际影响

看到模型标注“几千亿参数”时,需要继续问:这是总参数量,还是每个 Token 大约会用到的激活参数量?Dense 模型和 MoE 模型只比较总参数量,往往不能直接说明单次生成的计算成本、速度或实际能力。以公开的 DeepSeek-V3 技术报告 为例,它列出 6710 亿总参数,同时说明每个 Token 激活 370 亿参数;这两个数字描述的是不同层面的规模。

稀疏激活不等于模型一定更快、更便宜,也不等于它能轻松放进更小的设备。未被当前 Token 激活的 Expert 仍然需要存储,Token 被分配到不同设备上的 Expert 时还会带来通信和负载均衡成本。 Switch Transformer 等研究所解决的,正包括这些工程难题。

MoE 改变的是模型内部如何分配计算,没有改变这篇文章已经建立的核心链路:Token 仍然带着上下文逐层加工,输出头仍然给出下一 Token 的候选分布,生成过程仍然一次选择一个 Token。因此,MoE 本身不会让事实自动可靠,也不是回答出现变化的直接解释;对日常使用来说,选择模型时观察实际效果、速度、价格和适用场景,比单看“专家数”或总参数量更有意义。

5. 量化:用更少的比特

量化像结账时不再精确到“分”,而是四舍五入到“角”甚至“元”。数字更简单、更省计算,但结果会产生少量误差。用较少的空间近似保存这些数字。模型可能更容易装进有限内存,但也可能损失一些效果。它与 MoE 都涉及容量、计算与效果的取舍,却解决不同的问题:MoE 决定一个 Token 激活哪些参数分支;量化决定这些参数用什么精度保存和计算。

进阶阅读:量化会怎样影响部署?可选阅读

下载或部署开放模型时,经常会看见同一模型后面跟着 FP16、INT8、4-bit 或其他格式。它们不一定代表重新训练出的不同模型,也不表示参数数量简单地变少了。很多时候,区别来自 Quantization(量化):用更少的数字刻度近似表示原本精度更高的权重。

QUANTIZATION

把连续刻度变少,模型更省空间,也会引入近似

概念示意
原始权重
许多可表示数值
量化后的表示
更多刻度
原始权重
许多可表示数值
量化后的表示
较少刻度
原始权重
许多可表示数值
量化后的表示
很少刻度
量化不是删掉一半参数。它通常用更少的比特近似表示权重。实际速度还取决于硬件、推理框架和量化方法。

可以把模型权重想成大量刻度很细的旋钮。量化把每个旋钮允许停留的位置变少,于是保存这些位置所需的比特更少,内存占用和数据搬运压力也可能下降。代价是原数值被舍入到附近刻度,会产生近似误差。

GPTQ 论文 展示了训练完成后再压缩权重的一类方法。不同量化算法会选择不同的分组、校准数据和误差补偿方式,因此“都是 4-bit”也不保证速度和效果完全相同。

对普通用户而言,量化最重要的含义是:

  • 精度更低的版本通常更容易装入有限内存,但能力可能受到不同程度影响;
  • 文件更小不保证生成一定更快,硬件是否擅长该格式同样重要;
  • 模型名字相同,也要继续核对量化方法、上下文长度和运行设置。

6. 把开放模型运行在本地

前面介绍的 API 通常把问题交给云端服务。不过,也可以把已经训练好的模型放到自己的电脑或服务器上运行,这通常叫作本地部署。它不是重新训练模型,而是让设备加载模型已有的参数,并在本地完成一次次 Token 的生成。

人们常把这类模型称作“开源模型”;更准确地说,许多是开放权重模型:开发者公开了训练完成的参数文件,供其他人下载和运行。模型通常可从发布者的官网、代码托管平台或模型社区获取。下载时需要留意模型大小、许可证,以及自己的设备是否有足够的存储和运行能力。

部署完成后,使用起来仍像在线聊天:可以打开本地的聊天界面直接提问,也可以让自己的程序向本机提供的 API 发送请求。变化只在于,回答主要由自己的设备生成,而不是交给远程平台的服务器。

7. 基准测试与实际表现

Benchmark(基准测试)是用一组固定任务和评分规则测量模型。它很有价值,但每个分数都隐含了“测什么”和“怎样算好”的选择。

BENCHMARK IS A LENS

更换任务,所谓“第一名”也会改变

虚构评测示意
模型 A写作更自然
模型 B引用更忠实
模型 C速度更快
模型 A偶尔增添说明
模型 B复杂问题更强
模型 C输出边界稳定
模型 A文风最贴合
模型 B表达偏严谨
模型 C简短但单一
不要问“哪个模型总分最高”。先拿自己的典型任务、失败案例和可接受成本,组成一个小型评测集。

例如,一个测试可能重视数学题正确率,另一个重视长文问答、格式遵循、速度或安全性。模型 A 的平均分更高,不表示它在你的中文改写、表格抽取或特定专业资料上也一定更好。 HELM 采用多场景、多指标评估,正是为了让准确性、校准、稳健性、公平性与效率等不同权衡不被一个总分遮住。

排行榜还可能受到提示模板、评分模型、测试版本和样本是否接近训练材料等因素影响。分数差距很小时,也不应自动解释成用户一定能感受到的稳定差异。

选择模型时,更实用的方法是建立一个小型个人评测集:

  • 收集十几个自己经常提出的真实任务;
  • 保留过去最容易失败的例子,而不是只测简单样本;
  • 预先写下合格标准,例如事实、格式、语气、延迟和成本;
  • 在相同输入与设置下比较,并检查多次运行的稳定性。

Benchmark 更像一副观察模型的镜片,不是给所有使用场景排出的永久总名次。


参数里的经历

模型的参数不是凭空出现的;它们由一段段训练逐渐塑成。人与人之间的差异,也常常来自同样漫长而各不相同的经历。

每个人之所以不同,并不只因为眼前的信息不同,还有过去的经历、应对它们的方式,以及不断被反馈塑造的过程都不同。家庭、教育、朋友、读过的书、遇过的挫折、得到的鼓励,都会像漫长训练中的样本和信号一样,慢慢留下痕迹,调整我们的一个个“参数”。于是,即使面对同一个问题、听到同一段话、站在同一个选择面前,我们所作出的理解与反应,也总有各自的来处。

每个人都可能有自己的困境,只是它未必会显露在眼前。

这或许能解释一些让人困惑的事:为什么同一句话,有人听见的是善意,有人先感到防备;为什么同一条路,有人觉得值得冒险,有人更愿意绕开;为什么我们常常以为“这么明显的事,为什么他会那样想”。我们看到的是一次举止或一次决策,却很少能看见它背后那段漫长、具体而不可复制的“训练过程”。

当然,人始终比大模型更复杂,也更有趣。人会感受疼痛与快乐,会主动反思自己的过去,会改变想要成为的人,也要为自己的行动担责。这个类比只是为了提醒自己:每一个看似简单的反应背后,都可能隐藏着我们尚未了解的经验和理由。

理解模型之后,我看人也多了一点耐心。别人说一句话、做一个选择,眼前看到的只是当下的反应;他经历过什么,我通常无法获知。所以可以做判断,但没必要太早下结论。