- 应用将系统说明、对话历史和当前问题整理成上下文;模型的“记忆”来自本轮再次提供的内容。
- 文本进入模型前会被拆成 Token,再转换成带有位置信息的向量。
- Transformer 中,Attention 通过 Q、K、V 找到并汇总相关信息,FFN 再进一步加工;这一过程会逐层重复。
- 模型据此预测下一个 Token,将它接回上下文后继续预测,直到组成完整回答。
LLM 是 Large Language Model 的缩写,中文通常译为“大语言模型”。
大:指它学习过海量的文字
语言:指它处理的是人类使用的文字、代码等信息
模型:则可以理解为一个从大量例子中学习语言规律的计算系统
简单来说,LLM 会根据已经看到的内容,预测接下来最合适出现的文字。ChatGPT 背后的模型就属于 LLM。
当我们在 ChatGPT 的聊天框里输入一句话,点击发送,几秒后就可能得到一段回答。
例如:
我们通常看不到发送和收到回复期间发生了什么,下面来逐步拆解。
一、消息与上下文
1. 通过 API 调用模型
日常说“我在和 ChatGPT 聊天”完全没有问题。但消息并不是从聊天框直接进入模型的:聊天应用需要把我们输入的内容整理成一次请求,交给模型,再接收模型返回的结果。这个来回通常通过 API 完成。
API(应用程序编程接口),可以先把它想成一家餐厅对外开放的“点餐窗口”。 餐厅里有后厨,软件里有处理事情的程序;点餐窗口让顾客不用走进后厨,也能使用餐厅做饭的能力。
这个窗口会摆出菜单,也有点单要求:比如可以点番茄面,需要说明大小份、要几碗。软件也会留出这样的入口,让另一个软件按规定提交需求、取得结果。这个供程序使用的入口,就是 API。
窗口接收点单,后厨负责做面。对应到软件:API 是对外提供的入口,背后的程序负责完成工作。
应用把“上海”作为查询条件发出去,拿到天气数据后,再显示成你熟悉的天气卡片。这里的数据仅作示例。
分清这三样就够了:API 是点餐窗口,请求(request)是递进去的点单,回复(response)是端出来的菜。 真正做菜的是后厨,对应软件里实际处理需求的程序。
这里的“窗口”是一个类比:API 不一定有能看见的页面或按钮,它是给程序调用的。接下来,以一次简化的聊天为例,看看问题和回答怎样经过这个窗口来回传递。
一次对话在聊天应用与模型之间的流动
2. 对话与记忆
我们可能会自然地期待:既然刚和 ChatGPT 说过第一性原理,它当然应该记得。但对话中的模型并不会像人一样,在回答完一句话后把这段经历留在脑中。它每次生成回答,都是根据这一次收到的消息重新开始;上一轮对话里的内容不会自动带到下一轮。模型也不会因为前面的对话就立即把聊天记录学进自己的知识库。
这不是产品少做了一项功能,而是大语言模型本来的工作方式:根据眼前的输入,预测接下来最合适的内容(有点类似于输入法预测下一个词,然后不断循环这个过程,直到回复结束。)
为了让对话能连贯地继续,像 ChatGPT 这样的聊天产品会保存前面的消息,并在发出新问题时,把相关的对话内容一同交给模型。于是,模型每一轮都像在回答前重新翻看一遍带来的聊天记录;它看起来“记得”,其实是每次都在利用这次看到的聊天记录回答。
继续刚才的对话。看完 ChatGPT 对第一性原理的解释后,我们可能会接着问:
如果模型只收到最后这句“能再举一个生活中的例子吗?”,其实有一个地方说不清楚:要为“什么”再举例?它之所以知道仍在谈论第一性原理,正是因为本轮请求中也带上了前面的对话。
下面的动态视图会播放三轮对话,并在每次发送新消息时展示请求携带了多少条记录。切换到“API 中的会话”,可以横向比较三次请求实际包含的问题、完整消息和回复。
对话越长,每次请求携带的内容也越多
同一段对话继续进行时,上一轮问答会成为下一轮的参考内容。
每次发送的是当前问题和此前相关对话组成的上下文,不只是最后输入的那句话。
每次追问,都把前面的对话再带一遍
三张卡片直接展示每次真正发送的内容。越往右,历史消息越多。
请用普通人能听懂的话解释一下第一性原理。
第一性原理,就是先把复杂问题拆解到最基本的事实,再从这些事实重新推导答案。
能再举一个生活中的例子吗?
比如买手机时,先确认自己真正需要拍照、续航还是性能,再根据需要选择,而不是直接照搬别人的推荐。
如果我想学习摄影,也可以这样做吗?
可以。先明确你想记录生活还是学习商业拍摄,再从用光、构图和曝光这些基本问题开始练习。
最重要的结论是:在 ChatGPT 这类产品中,有记忆其实是一种假象,其实是因为模型每次都能看到完整的聊天记录。
模型本次回答时能够看到的内容,就是上下文(context)。前面的对话被带上时,“再举一个”才有明确指向;否则,模型就不知道要为“什么”举例。
3. 系统说明与用户消息
上一节看到,为了接住一句“再举一个”,应用会把前面的对话也放进本轮请求。不过,除聊天记录之外,模型还会接收到其他信息。
仍以开头的对话为例。我们先输入“请用普通人能听懂的话解释一下第一性原理”,得到回答后,又追问“能再举一个生活中的例子吗?”。聊天界面里能看到这三条消息;但对模型来说,它收到的是一段拼接好的完整上下文——通常在这些聊天记录的最前面,还会包含一段产品在背后预先加入的说明。
继续追问时
聊天界面显示的内容,不一定是模型收到的全部内容
请用普通人能听懂的话解释一下第一性原理。
第一性原理,就是先把问题拆到最基本的事实,再从这些事实重新推导答案。
能再举一个生活中的例子吗?
模型结合以上信息,回答最后一个问题
这种交给模型的任务说明和参考信息,通常叫作 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可能会被拆成ham、bur、ger等几个 Token。 - 常见中文词语:有时作为一个整体,比如“世界”是 1 个 Token;有时也会被拆成单字,即“世”、“界”两个 Token。
- 空格与标点:英文里的空格常常和它后面的单词绑在一起,比如
Hello, world!可能会被拆分成Hello、,、world、!这样 4 个 Token。
直接在下面输入你想分析的内容,或者直接点击预先准备的示例,观察这个拆分的实际效果。
Tokenizer Playground
同一句话,在模型眼中会被拆成什么?
最多可输入 1000 个字符。
- 字符数
- 20 按屏幕上的字符计算
- Token 数
- 15 模型实际处理的单位
- 占 128K 上下文
- < 0.01% 仅作容量比例参考
输入一些内容后,这里会显示 Token。
为什么会这样拆?可以把 Tokenizer 想成一本提前整理好的“常见文字片段表”。经常一起出现的片段,可能会被收进表里,直接用一个 Token 表示;没有被收录成整体的内容,则需要拆成更小的片段。这个过程遵循已经确定的拆分规则,并不是模型临时读懂了句子之后才决定怎样切。
进阶:Token 编号、Emoji 与不同的编码可选阅读
每个 Token 还会对应一个整数编号,通常叫作 Token ID。例如上面每张 Token 卡片中的 ID 10941。这个编号只表示“去词表里的哪一项查找”,就像书本目录中的页码;数字大不代表意思更复杂,两个编号接近的 Token 在含义上也不一定接近。
电脑保存文字时,Emoji 等字符通常需要多个字节。Tokenizer 的切分边界有时会落在这些字节中间,于是单独拿出其中一个 Token,并不能还原成完整可见的字符。组件把这种情况标为“UTF-8 片段”,把相邻 Token 合起来后,原来的 Emoji 仍然能够完整还原。
不同模型不一定使用同一套 Tokenizer。词表和拆分规则不同,同一句话得到的 Token 数也可能不同。上面的组件使用 o200k_base 编码,适合用来观察 OpenAI 多种较新模型常见的拆分方式;它并不代表所有大语言模型都会得到完全相同的结果。想进一步核对具体编码,可以参考 OpenAI 开源的 tiktoken 项目。
前面提到,大模型看起来“有记忆”,其实是因为每次回答时,它都会重新收到此前的聊天记录。不过,这份聊天记录并不是无限长的:我们常说的 Context Window(上下文窗口),就是模型一次能接收和处理的内容上限;它限制的通常不是“多少个汉字”或“多少个单词”,而是多少个 Token。系统说明、历史对话、刚输入的问题以及模型生成的回答,都会共同占用这份空间。因此,同样看起来是“一页文字”,在不同语言、代码和符号组合下,占用的 Token 数可能并不相同。
账单里的“缓存输入”,指的是什么?
一些 API 价格表会把 Token 分成 Input(输入)、Cached Input(缓存输入) 和 Output(输出) 等计费类别。这里的“缓存输入”不是说模型永久记住了这些内容,也不是直接沿用了上一次的回答;它通常指这次请求开头的一段内容,与先前请求处理过的前缀相同,服务因而可以复用那部分计算结果。这种机制通常叫作 Prompt Cache(提示缓存)。
PROMPT CACHE
对话继续,缓存前缀也逐步变长
例如,围绕第一性原理连续追问时,第二次请求开头仍会带上相同的系统说明和首次提问。这段与第一次请求严格相同的开头可能命中缓存;随后附上的 AI 回答,以及最后新加入的“能再举一个生活中的例子吗?”,仍要作为新增输入正常处理。命中的 Token 仍属于本次输入和上下文,只是在账单中可能按较低的缓存输入价格计算;具体价格、前缀长度要求和有效时间取决于服务商与模型。
缓存命中也不表示这次输入免费。只要可匹配的前缀发生变化、缓存已经失效,或者服务没有提供相应能力,就不一定能够命中。以 OpenAI 的 Prompt Caching 文档 为例,服务复用的是相同输入前缀已经完成的处理。至于这些计算结果在模型内部是什么,以及它和后文的 KV Cache 有什么关系,会在讲生成速度时再解释。
拆成 Token 只是准备工作。这些Token将被送往模型进行下一步处理。
三、回答的生成
Token 被送进模型后,模型会把它们和当前的整段上下文一起看,再判断接下来最可能出现什么。它会为词表里的每个 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
- 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. | ||||
|---|---|---|---|---|---|
| Token | There | is | no | spoon | . |
| Token ID | 5632 | 382 | 860 | 68560 | 13 |
所以,在上面总结构图中,左下角的 Inputs(已分词) 是已经准备好的 Token ID。原文与参考译文都会先在 Transformer 外完成这一步;接下来先沿左侧路径追踪原文。
There is no spoon. [#5632, #382, #860, #68560, #13] 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 转换成向量后,文字中原本复杂的关系,就变成了数学上可以计算的坐标关系;模型可以据此做加减乘除等运算。
为了建立直觉,我们先抛开复杂的词汇,看一个最简单的二维向量(即只有两个数字的向量)。
回想数学里面学过的平面直角坐标系,但将X、Y轴变为“甜度”(负数代表苦,正数代表甜)和“温度”(负数代表冷,正数代表热),比如一个二维向量[a,b]在这里面,a就表示有多甜,b就表示有多热。
Semantic 2D Vector
带有实际意义的二维向量
为什么我们要把 Token 变成向量,而不是直接拿 Token ID 去计算呢?
这正是向量的魔力所在:它让原本孤立的概念变得“可计算”。如果只使用 Token ID,那它们仅仅像超市里的商品条码,ID 382 和 ID 383 尽管数字上相邻,但在含义上可能一个是“苹果”,另一个是“宇宙飞船”,数字本身并没有包含任何逻辑关联。
但当转化为向量后,模型就能通过计算空间坐标的距离和方向,直接理解词汇之间的关系。比如在上面的坐标系中,模型可以很容易地“算”出,“热可可”和“冰淇淋”在 X 轴(甜度)上距离很近,但在 Y 轴(温度)上完全相反。更有趣的是,我们甚至可以直接在概念之间进行数学推演,比如在模型眼中可能会发现这样一个等式:“黑咖啡” - “热” + “冷” ≈ “冰美式”。通过将文字映射到连续的意义空间中,自然语言就拥有了能被神经网络进行加减乘除和复杂变换的数学特征。
真实模型中的向量通常有成百上千个维度,远比二维复杂。在这个庞大的高维空间中,单个维度的数字通常并不像我们刚才举例的“甜度”或“温度”那样,能直接对应人类所理解的具体概念,这里只是为了帮助理解而做的一种简化和虚构,模型看待Token的角度与人类是不同的。真正起决定作用的,是整组数字共同确定的多维空间位置,以及词与词之间的相对距离和方向。
如何给Token 分配这么精妙的坐标?这其实是模型训练的结果。嵌入表(Embedding Table)并不是人类专家预先编写好的词义词典,它本身就是模型需要学习的重要参数。当模型从头训练时,表里最初只是一堆随机数。但在阅读海量文本、不断预测下一个词的过程中,模型会逐渐发现规律:经常出现在相似语境、承担相似语言作用的 Token,必须被安排在相近的位置才能更好地完成任务。于是表里的数字被不断调整,最终才沉淀出了那些包含丰富语义关系的坐标向量。
你可以通过下面这张交互图来感受这种“相对位置”。把高维空间强行压扁成二维后,尝试替换高亮词,观察它会移动到意义空间的哪个概念附近:
[#5632, #382, #860, #68560, #13] 0.14−0.620.37… −0.280.510.09… 0.730.12−0.46… −0.050.310.66… 0.39−0.110.24… 每一行对应一个 Token 的初始向量;下一步还要把位置写进这张矩阵。
到这里可以先建立一个很实用的对应关系:一个 Token 用一个向量表示;一串 Token 的向量上下叠放起来,就是一个矩阵。 在上面的例子中,句子有 5 个 Token,所以得到 5 行;如果每个 Token 用 8 个数字表示,这张表就是一个 5 × 8 (5行8列)的矩阵。把多行向量放到一起变成一个矩阵,仅仅是为了方便计算(可以同时对所有向量进行操作),并没有什么其他的实际意义。
5. Positional Encoding
只查表还不够。Embedding Table 看的是“这个 Token 是谁”,却不知道它排在第几个位置;于是“猫追狗”和“狗追猫”会得到同一组 Token 向量,只是顺序不同。
因此,原始 Transformer 会为每个位置准备一份 Positional Encoding(位置编码),再与该位置的 Input Embedding 相加。相加之后的向量既带有“这是哪个 Token”的信息,也带有“它在句中的什么位置”的信息,然后才进入 Encoder 的第一层。
位置编码还有一个容易被忽略的重要意义:它让 Transformer 不必为了知道先后顺序,而把已经存在的 Token 一个接一个地读取。模型可以同时处理多个位置,再借助位置编码区分谁在前、谁在后。这样,顺序信息和并行计算就能同时保留。需要注意的是,模型写回答时,后一个 Token 仍然要等前一个 Token 生成,因此输出过程依然是逐步进行的。
可以把位置编码想成叠在词向量上的一层“位置纹理”:每个位置都有自己的纹理,而且它们遵循同一套规律。模型从训练开始接触的就是这种带纹理的输入,会逐渐学会利用其中的规律,判断哪些词在前、哪些词在后,以及它们之间的距离怎样影响句意。
Encoder Input · Positional Encoding
同一个词放在不同位置,输入表示也会不同
正在讲解:Positional Encoding(位置编码)
[0.20, −0.45] 词是谁 [0.14, −0.99] 它排在哪里 [0.34, −1.44] 带有词和顺序 顺序信息不是从 Token ID 里猜出来的。 模型把位置编码加到词向量上,才知道 `spoon` 是句子中的第 3 个位置。
0.340.55…… 0.610.09…… 0.78−0.88…… 0.34−1.44…… −0.37−0.20…… 每一行都同时带着“这个 Token 是什么”和“它排在第几位”;五行合起来,才是送入 Encoder 第一层的完整矩阵。
表面上看,位置编码带来的现象很直观:同一个词换到不同位置,进入模型时的表示也会随之变化。 下面的进阶内容再展开这种“位置纹理”具体怎样生成、怎样与词向量结合,以及它如何进入后续计算。
进阶:位置编码的数学原理与计算过程可选阅读
为什么一串表示“词”的数字,还能装进“位置”? 向量本来就是一组数字,并没有规定它只能表达词义。我们可以把位置也变成同样长的一组数字,再把对应位置的数字逐个相加。这样,同一个词出现在不同位置时,交给模型的数字就会有所不同。
例如,假设 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 节 。
把上面的三个步骤连起来看:拖动位置滑块,观察曲线上的取值、相加后的向量,以及最后的计算结果如何一起变化。
移动一个词,看位置怎样进入计算
固定 spoon 的词向量,只改变位置。这里用完整的 4 维向量演示。
竖线与四条曲线的交点,组成当前位置的四个数字。实线是 sin,虚线是 cos。
两张图使用相同坐标尺度。慢曲线在这段范围内接近水平;它并非不变,只是周期更长。
沿着每一列往下看:同一维度的词信息与位置信息相加,得到新的输入表示。
| 向量 ↓ / 维度 → | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| 词向量 e · 固定 | 0.200 | -0.450 | 0.300 | 0.100 |
| + 位置向量 p | 0.141 | -0.990 | 0.030 | 1.000 |
| = 输入 h | 0.341 | -1.440 | 0.330 | 1.100 |
用固定权重 W = [0.5, −0.2, 0.4, 0.8] 将四个数加权求和,演示一次从 4 维到 1 维的线性变换。
所有结果先用完整精度计算,再显示三位小数。词向量和权重是教学设定;位置向量按上方公式计算(d = 4)。这里仅演示线性变换,不是完整注意力,也不表示模型会先拆出两个向量。
0.14−0.620.37… −0.280.510.09… 0.730.12−0.46… −0.050.310.66… 0.39−0.110.24… 0.340.55…… 0.610.09…… 0.78−0.88…… 0.34−1.44…… −0.37−0.20…… 矩阵的行数不变,仍然是一行一个 Token;变化的是每一行现在同时包含词和顺序。
6. 注意力机制
经过 Embedding 和位置编码后,每个位置已经有一行包含 Token 与顺序信息的向量。接下来要做的是让这些向量交换信息:一个位置需要知道,其他位置有哪些与自己有关的线索。Attention(注意力) 就是完成这件事的机制。
先看两个句子:
我把苹果洗干净后吃了。
“洗干净”和“吃”提供了水果的线索。
苹果发布了新款iPhone。
“发布”和“新款iPhone”则把含义引向了苹果公司。
两个句子里都有“苹果”,但它们的含义并不一样。“苹果”本身的词向量只能提供一个基础表示,真正帮助模型判断含义的,是它与周围词语之间的联系。
仅找出哪些词与“苹果”相关还不够。注意力机制可以让模型在理解“苹果”的同时,也能够通过一些手段去参考与它相关的词,从而判断这里的“苹果”究竟指水果还是公司:它通过一套计算规则,从相关 Token 的向量中取得信息,再与“苹果”原有的信息结合,得到一个新的向量,这个新的向量在保留了“苹果”自己本身的信息的情况下,还吸收其他 Token 提供的信息。
这个新向量仍然属于“苹果”这个位置,可以理解为“苹果”的一个全新版本。这样模型在理解“苹果”的同时,也能参考与它相关的词,从而判断这里的“苹果”究竟指水果还是公司。
先看注意力的目的
让“苹果”的向量带上周围的线索
现在来看如何从其他Token吸收信息:
看哪些Token?
全都看,包括自己。因为很简单的道理:你要先看过才知道对自己有没有影响。
怎么看?
有些Token对自身语义的影响很关键,有些则不然,所以对待每个Token的关注程度不能都一样,也就是对不同Token的注意力也不同。
注意力在做什么
一个 Token,会把注意力分给谁?
02 · 从当前 Token 出发,看它关注谁
如何吸收其他Token的信息
上一步已经看到,当前 Token 对不同 Token 的关注程度并不一样。你可以把它理解成一张“取用比例表”:更关注的 Token,会从它那里多拿一点信息;较少关注的,就少拿一点。模型再把这些信息按比例合在一起,得到当前 Token 的一个新版本向量。这个“取用比例”在数学里叫作权重。
这个过程是如何实现的
按照前面的思路,其实就是要做三件事:
- 计算出注意力的分配比例
- 根据这个比例去抓取相应的Token的信息
- 糅合到一起
要计算出刚才谈到的注意力的比例,我们需要三种辅助工具:
- Q(Query,查询):比如
spoon此刻想寻找什么信息? - K(Key,键):每个候选 Token 都标明“我能被怎样找到”,供 spoon 决定该关注谁、关注多少。
- V(Value,值):从候选 Token 中实际取回什么内容?
就像查资料时,我们先带着一个 问题(Q) 搜索,用 标题或关键词(K) 判断哪些结果相关,最后真正阅读的却是结果中的 正文(V)。
Q、K、V 不是三份预先存好的信息,而是同一个 Token 当前向量 x 的三种“用途版本”。 可以把 x 想成一份原始资料:为了判断“我在找什么”,模型把它加工成 Q;为了说明“我能和什么匹配”,加工成 K;为了准备“匹配后实际传递什么”,加工成 V。
这三个版本都来自完整的 x,只是采用了三套不同的数字配方:把 x 中的每个数字乘上相应系数,再将结果相加,组成一个新向量。这种运算叫作线性变换,也常叫投影。图中的 WQ、WK、WV 就是保存这三套配方的表:
QKV 的来源与分工
同一个输入,算出三种用途不同的向量
用它重新组合完整的 x
用它重新组合完整的 x
用它重新组合完整的 x
WQ、WK、WV 决定怎样组合输入;色块仅作示意。先用两个数字看懂一次线性变换
真实模型中,x 是前面步骤已经得到的 Token 当前向量,通常包含很多数字。为了只看清运算,这里把它缩成两个分量,并假设它们的值是 2 和 1,于是 x = [2, 1]。这两个数不是 Token ID,也不是某个词的固定数值,只是本例给定的计算起点。
同一份输入会分别乘以三组不同的权重矩阵,从而得到 Q、K、V。三条路径都使用完整的 x,但各自采用不同的配方:
同一份输入,按三套规则重新组合
20=202=201=120=221=32-1=1刚才三条路径做的是同一种操作,只是使用的数字表不同:输入按照 WQ 重新组合后得到 Q,按照 WK 重新组合后得到 K,按照 WV 重新组合后得到 V。
简单说,输入没有被切成三份,而是完整地分别计算了三次,得到三个新向量。 “矩阵乘法”只是把图中的“乘系数,再相加”集中写在一起。图里的系数是为了让计算容易核对;真实模型使用训练得到的数值,也可以改变向量的维度。
刚才的三张表,到底是什么?
刚才计算 Q、K、V 时看到的三张数字表,分别叫作权重矩阵 WQ、WK 和 WV。矩阵中的数字决定输入信息如何被重新组合: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 出发,带回上下文
匹配结果经 Softmax 变成关注比例,再用各自的比例缩放整份 V。
进阶:Q、K 分开后,怎样形成有方向的匹配,并算完一次注意力可选阅读
为什么不让 Q 和 K 用同一份向量?
用原向量做点积当然可以。但这样一来,x₁ 对 x₂ 的分数与 x₂ 对 x₁ 的分数总是相同。让 Q、K 使用同一套变换,也保留了这个限制。沿用正文的配方,两个方向的分数可以直接算出来:
交换查询方向,可以得到不同分数
直接用原向量
2×1 + 1×021×2 + 0×12交换方向,只是交换乘法顺序。
使用正文中不同的 Q、K 规则
x₁:q = [2, 2],k = [1, 2]
x₂:q = [1, 0],k = [0, 1]
2×0 + 2×121×1 + 0×21发出查询的一方用 Q,被查询的一方用 K。
分开 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%这里算出的关注比例叫作注意力权重,会随着输入变化;前面 WQ、WK、WV 中的数字则是训练学到的计算系数,正常使用时保持固定。两者都叫“权重”,但用途不同。
不用记住这些小数,重点是:同一个查询得到一组分数,Softmax 把它们变成一组分配比例;分数越高,比例越大。 这不是直接用原始分数除以总和,也不是预测下一个词的概率。下面沿用四舍五入后的 94.4% 和 5.6%,继续汇总信息。
③ 最后看传递:按刚才的比例汇总 V
现在取出位置 1 的 V [3, 1] 和位置 2 的 V [1, 1]。分别乘以 94.4% 和 5.6%,再把对应位置相加:
Q、K 决定取多少,V 决定取回什么
从位置 1 取值
94.4% × [3, 1][2.832, 0.944]从位置 2 取值
5.6% × [1, 1][0.056, 0.056]逐项相加
[2.888, 1]位置 1 的注意力输出可以把它想成查资料: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
计算方法相同,区别在于信息来自哪里
Q、K、V 都来自同一句话。候选范围包括 spoon 自己。
Q 与 K、V 来自不同来源。机器翻译的 Decoder 会用它读取原文。
刚才只更新了一个位置。其他位置也会各自拿自己的 Q,与所有可访问位置的 K 比较,再汇总 V。把这些结果逐行排起来,就得到输出矩阵:仍然一行对应一个 Token,只是每一行都结合了上下文。这些计算使用同一份输入,可以并行完成。
多头:让同一个位置有多种观察角度
一句话中可能同时存在否定、指代、语法搭配等多种关系。刚才用一套变换生成 Q、K、V,对每个查询位置得到一组关注比例。要让同一个位置同时使用多种关注方式,可以准备多套独立的变换。因此 Transformer 会并行进行多组自注意力计算,再把结果合在一起。每一组叫一个 Head(头),合起来就是 Multi-Head Attention(多头注意力)。
每个 Head 都会看到完整输入,使用自己的 WQ、WK、WV,分别走完刚才的计算。具体关注什么由训练形成,并不是预先给每个头指定“否定”或“语法”的任务。
为什么需要多头
同一句话,可以从不止一个角度观察
这个 Head 可能更关注 no 与 spoon 的语义联系。
得到多个结果后,模型先把同一位置的各头输出首尾拼接,再按一套学到的系数重新组合;后一步叫作输出投影。这样,不同 Head 提供的线索就能一起更新这个位置携带的信息。
7. Add & Norm
注意力已经让 spoon 找到并带回了 no 等位置的线索,但它还不能把“新结果”直接当作全部答案。否则每经过一层,Token 原本的含义和位置信息都可能被新计算覆盖;与此同时,反复堆叠计算也容易让内部数字的尺度越来越难控制。因此,在这里采用的原始 Transformer 结构中,注意力之后还有 Add & Norm(残差相加与层归一化)。
Add 把注意力输出与进入这一模块前的原向量逐项相加,让原输入也参与结果;这条绕过注意力计算、直接接到加法处的路径叫作残差连接。Norm 则对每个 Token 向量内部的数字做尺度调整,帮助后续计算保持稳定。
Attention 之后的下一步
新线索不能覆盖旧信息:先相加,再整理
正在讲解:注意力后的 Add & Norm
注意力产生的是一份“从上下文带回来的新信息”。Add & Norm 不会再次寻找 Token,它负责把新信息安全地并入原表示,并让结果适合继续向上堆叠。
不要把 Norm 理解成“把所有 Token 变得一样”。它只整理每个 Token 表示内部的数值尺度,不会抹掉 Token 之间的内容差异;其中的可学习参数还会保留和调整有用特征。
可以先记住它的分工:Add 为原输入保留直接参与计算的路径,Norm 把合并后的数字整理到更稳定的尺度。 它不负责寻找新的上下文关系,而是保护并整理注意力已经得到的结果。
串起来看:一个位置怎样汇总上下文线索
下面的演示把单头计算、多头合并和原输入的加入串在一起。先跟踪 spoon,再切换到其他 Token,看看每个位置怎样重复同样的流程。
阶段总结 · Multi-Head Self-Attention + Add & Norm
跟着 spoon,走完一次上下文信息之旅
正在讲解:多头自注意力与 Add & Norm
单个 Head 内前文内容的流程回顾
多头合并与整理汇合视角并保留原信息
一次注意力计算,可以压缩成四个动作
前文已经逐项解释过细节,这里只保留计算顺序和每一步的输入输出关系。
记住主线即可:先判断参考谁,再按比例取回信息。
多个 Head 从不同角度观察 spoon
它们都看到完整输入,但判断“什么叫相关”的方式可以不同。
多头不是同一道题重复多遍,而是让多套匹配方式同时存在。
多头结果合成后,再经过 Add & Norm
不同 Head 的结果先被拼接、投影;随后与原来的 spoon 表示残差相加(Add),再归一化(Norm)后交给下一模块。
阶段完成:每个 Token 都会经历同样的过程,因此整句话最终变成一组带有上下文的表示。
单头流程:先判断参考谁,再按比例取回信息。
到这里,你已经可以顺着数据走完一轮:输入生成 QKV → Q 与 K 打分 → 分数变成权重 → 按权重混合 V → 合并多头结果 → Add & Norm。 下面把这条流程对应到论文结构图和完整矩阵计算;不展开也可以继续看本节末尾的输入输出对照。
进阶:论文结构图、矩阵维度与完整计算链可选阅读
再把刚才的信息流与论文里的结构图对照。图从下往上读:左边是一轮单头计算,右边是多头如何组合。
Attention Architecture · 注意力结构
从一次匹配,到多个 Head 的合成
沿箭头,从下往上读缩放点积注意力
一个 Head 内部怎样计算
多头注意力
同一份输入,多套可学习的投影
- 左图: 第一个 MatMul(矩阵乘法)计算 Q 与 K 的匹配分数;Scale 缩放分数,Mask 按需要屏蔽不可访问的位置;Softmax 将分数变成权重,最后一个 MatMul 按权重混合 V。注意 V 的箭头绕过了打分过程,直接进入最后的内容汇总。
- 右图: 底部三组 Linear 就是各个 Head 的可学习投影;叠起来的框和
h表示多个 Head 并行计算。Concat 将各头结果拼接,顶部 Linear 再把它们混合成输出。
右图从同一份输入 X 出发:每个 Head 用自己的三组 Linear,生成 Qᵢ = XWQᵢ、Kᵢ = XWKᵢ、Vᵢ = XWVᵢ,再进入左图的计算流程。
矩阵形状与总公式
前面的例子用小写 q、k、v 表示一个位置的向量。把所有位置的向量逐行排起来,就得到大写 Q、K、V 矩阵。同理,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 来说,输入矩阵被分别乘以 WQ、WK、WV,得到三个 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),位置不同,但“注意力负责跨位置交流,残差负责保留并稳定信息”这个分工仍然成立。
可以把“单头”和“多头”的关系压缩成一句话:单头决定每个位置从哪里取信息;多头让同一个位置同时用多套表示空间寻找不同关系;输出投影再把这些关系合成下一层可以使用的表示。
0.340.55…… 0.610.09…… 0.78−0.88…… 0.34−1.44…… −0.37−0.20…… 0.480.31…… 0.520.27…… 0.91−0.64…… 0.76−0.93…… −0.21−0.08…… 左右仍然各有 5 行,一行对应一个 Token;注意力不会把 Token 合并或删掉,而是改写每一行内部的数字。右侧的每一行都已经混入整句话的相关线索,其中高亮的 spoon 行也吸收了 no 的否定信息。数值仅用于展示变化。
8. FFN
FFN:分别加工每个位置已经取得的信息
经过注意力之后,五个 Token 都已经不再“只知道自己”。例如,spoon 的表示里已经混入了 no 带来的否定线索。看起来模型似乎已经理解了上下文,为什么还需要一个 Feed-Forward Network ?
因为找到相关信息和消化这些信息是两件不同的事。
注意力主要解决的是“我应该去哪些位置取信息”。它能让 spoon 找到 no,把否定线索带回来;但仅仅把线索放在一起,并不等于已经把它们加工成最适合下一层使用的表示。模型还需要进一步处理“勺子”和“否定”同时出现时形成的组合,并决定哪些内部反应应该增强、哪些可以减弱。
这就是 Feed-Forward Network(FFN,前馈网络) 的工作。可以用一个不完全精确、但很直观的类比来理解:
- Attention 像一次小组讨论。每个人先去听其他人的发言,把相关线索记在自己的便签上;
- FFN 像讨论结束后的个人整理。每个人低头处理自己的便签,把刚获得的信息重新归纳成更有用的记录。
先分清两种工作
Attention 负责“互相交流”,FFN 负责“各自消化”
每个 Token 可以从其他位置取回相关信息。
每个 Token 独立整理自己已经拿到的信息。
这个区别非常重要:Attention 允许不同 Token 之间交换信息,FFN 则不让 Token 在这一阶段继续互相交流。 五个 Token 会各自拿着自己当前的那一行表示,分别经过同一个小型神经网络。
“分别经过”不代表模型真的按顺序处理完 There,再处理 is,最后处理 spoon。计算机实际上会把五行一起并行计算。这里只是从信息关系上说:FFN 处理 spoon 这一行时,不会直接读取 no 那一行。no 的影响如果已经通过注意力进入了 spoon 的表示,FFN 才能继续加工这份影响。
还有一个容易混淆的地方:每一行使用的是同一套 FFN 参数。 它不像五位不同的编辑分别使用五套规则,更像同一位编辑用同一套方法逐份处理五张内容不同的便签。因此:
- 输入不同,处理结果也会不同;
- 处理规则相同,所以模型可以用一致的方法面对任意位置的 Token;
- 不同 Encoder 层通常拥有各自的 FFN 参数,所以越往上层,负责的加工方式可以逐渐变化。
一行 Token 在 FFN 里经历什么?
先继续追踪 spoon。进入 FFN 之前,它的一行表示可以粗略理解为同时携带了“这是一个物体”“它位于句中第 4 个位置”“它受到 no 的否定”等线索。模型内部没有这些中文标签,真实内容仍是一长串训练得到的数字;这里写成人类语言,只是为了帮助我们观察信息变化。
这行数字接下来会经历三个核心动作:
- 展开到更宽的内部空间。 原来的一行表示先被变换成更多数字,相当于把一张压缩的便签摊开到更大的工作台;
- 用非线性激活筛选反应。 不同内部单元会对不同数字组合产生强弱不一的反应,让值得保留的组合更加突出;
- 重新压回原来的宽度。 网络把筛选后的许多反应重新组合,形成下一份 Token 表示,同时保持与其他模块约定好的接口宽度不变。
下面的组件可以切换 There、no 和 spoon,也可以逐步查看同一座 FFN 怎样处理不同内容。先不用计算任何数字,只观察“展开、筛选、压回”的方向。
Encoder · Position-wise Feed-Forward
选择一个 Token,看它如何独立经过同一座“加工站”
正在讲解:前馈网络(FFN)
FFN 先拿到 `There` 已经带有上下文的一行
这行信息来自注意力和 Add & Norm。它已经不只是词典里的 `There`,还带着当前句子的相关线索。
0.480.31−0.120.44…………FFN 每次只处理这一行,不会在这里重新读取 `There`、`is`、`no` 或其他行。
先把较窄的表示投影到更宽的内部空间
可以把它想成把一张压缩便签摊到更大的工作台上。空间变宽后,网络可以同时尝试更多种特征组合。
真实模型通常会扩到比输入更宽的维度。这里的 8 格和 16 格只是为了把“展开”画出来。
非线性激活像一排开关,让有用反应突出出来
展开后的内部单元会对不同输入组合产生强弱不一的反应。较强的保留下来,较弱的被压低。
在这个示意里,`There` 的结果是:强化它作为句子开头和存在结构引导词的作用。
把筛选后的许多反应重新组合成下一份 Token 表示
工作空间可以很宽,但送给下一模块的接口必须保持一致,所以最后会压回原来的表示宽度。
0.630.18−0.080.57…………输入和输出一样宽,但内容已经变了。接下来还会经过一次 Add & Norm,再交给 Encoder 的下一层。
FFN 先拿到 `no` 已经带有上下文的一行
这行信息来自注意力和 Add & Norm。它已经不只是词典里的 `no`,还带着当前句子的相关线索。
0.91−0.640.280.16…………FFN 每次只处理这一行,不会在这里重新读取 `There`、`is`、`no` 或其他行。
先把较窄的表示投影到更宽的内部空间
可以把它想成把一张压缩便签摊到更大的工作台上。空间变宽后,网络可以同时尝试更多种特征组合。
真实模型通常会扩到比输入更宽的维度。这里的 8 格和 16 格只是为了把“展开”画出来。
非线性激活像一排开关,让有用反应突出出来
展开后的内部单元会对不同输入组合产生强弱不一的反应。较强的保留下来,较弱的被压低。
在这个示意里,`no` 的结果是:强化否定信号,并整理它对后续名词的作用关系。
把筛选后的许多反应重新组合成下一份 Token 表示
工作空间可以很宽,但送给下一模块的接口必须保持一致,所以最后会压回原来的表示宽度。
1.08−0.420.510.09…………输入和输出一样宽,但内容已经变了。接下来还会经过一次 Add & Norm,再交给 Encoder 的下一层。
FFN 先拿到 `spoon` 已经带有上下文的一行
这行信息来自注意力和 Add & Norm。它已经不只是词典里的 `spoon`,还带着当前句子的相关线索。
0.76−0.930.350.62…………FFN 每次只处理这一行,不会在这里重新读取 `There`、`is`、`no` 或其他行。
先把较窄的表示投影到更宽的内部空间
可以把它想成把一张压缩便签摊到更大的工作台上。空间变宽后,网络可以同时尝试更多种特征组合。
真实模型通常会扩到比输入更宽的维度。这里的 8 格和 16 格只是为了把“展开”画出来。
非线性激活像一排开关,让有用反应突出出来
展开后的内部单元会对不同输入组合产生强弱不一的反应。较强的保留下来,较弱的被压低。
在这个示意里,`spoon` 的结果是:把“勺子”和“被否定”组合成更适合后续理解的内部表示。
把筛选后的许多反应重新组合成下一份 Token 表示
工作空间可以很宽,但送给下一模块的接口必须保持一致,所以最后会压回原来的表示宽度。
0.94−0.710.580.39…………输入和输出一样宽,但内容已经变了。接下来还会经过一次 Add & Norm,再交给 Encoder 的下一层。
接收一行:拿到一个已经吸收上下文的 Token 表示。
为什么要先变宽,再缩回来?
如果一开始就只在原来的少量数字之间来回变换,网络能够尝试的组合会比较有限。先把表示投影到更宽的空间,等于临时增加了一张更大的工作台,让更多内部单元可以从不同角度响应当前 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 = 512,d_ff = 2048,也就是中间宽度约为输入宽度的 4 倍。
把整句写成矩阵时,程序会一次处理所有行,而不是显式写五次循环。每一行都乘同一组 W₁、W₂,所以参数在 Token 位置之间共享;各行之间没有点积或加权求和,因此 FFN 本身不会产生新的跨 Token 信息交换。
0.480.31…… 0.520.27…… 0.91−0.64…… 0.76−0.93…… −0.21−0.08…… 0.630.18…… 0.690.14…… 1.08−0.42…… 0.94−0.71…… −0.120.06…… 左右仍各有 5 行,一行对应一个 Token。FFN 会独立改写每一行内部的数字,Add & Norm 再保留原信息并稳定结果;高亮的 spoon 行只是示例,真实计算会并行处理全部五行。数值仅用于展示变化。
9. N层重复
到这里,我们只走完了 一层 Encoder。下面先把它放回原始 Transformer 的总结构,再把内部细节收起,只看各层之间怎样传递。
Encoder · Layer stack
先在总结构中定位,再看一层怎样接着一层
图中所指“× N”表示外框里的整组结构会重复 N 次。
H⁰ 进入第 1 层前 H¹→ H²→ Hᴺ 图中的每张“层”卡片,都代表刚才讲过的完整一组 Attention、Add & Norm、FFN、Add & Norm。它们的结构相同,但各层拥有自己的参数;因此不是把同一份结果机械复制多次,而是让更新后的表示继续接受下一层加工。
整个过程中,五个 Token 仍然对应五行,表示宽度也保持为 d_model。改变的是每一行内部携带的信息。原始 Transformer Base 使用 6 层 Encoder;其他模型可以选择不同层数,所以 N 表示预先设定的层数,不会根据句子难度临时改变。
Encoder 不是直接把英文翻译成中文。 它交付的是一串带整句语境的 Token 表示:
There / is / no / spoon / . → 每个 Token 都吸收整句线索后的上下文表示 → 交给右侧 Decoder 参考
例如,spoon 的表示已经带有 no 所表达的否定关系,而不再只是词典里孤立的“勺子”。
至此,左侧 Encoder 才真正处理完成:它把最终的 Hᴺ 作为一组原文表示交出去,却还没有生成译文。接下来,视线可以移到右侧 Decoder:它会一边读取已经出现的中文内容,一边查询 Encoder 留下的这组原文表示,开始预测下一个 Token。
10. Decoder
现在转向原始 Transformer 的 Decoder。先把总结构图放回眼前:接下来关注的是右侧从 Output Embedding 向上经过注意力、FFN 和输出头的路径,以及 Encoder 输出进入 Cross-Attention 的紫色通道。
Transformer Architecture · Transformer 结构
原始 Transformer:从下向上流动的 Encoder–Decoder
在机器翻译训练中,Decoder 会参考 Encoder 对原文的理解,逐步学习生成中文译文。为了完成每一步的预测,它需要同时使用两类信息:
- 正确译文:训练数据中与原文对应的中文翻译;Decoder 在每一步具体能看到其中哪些内容,下一节再展开;
- Encoder 的输出:原句各个位置经过 Encoder 处理后的上下文表示。
Decoder 会依次完成六个动作:
- 右移参考译文
- 把中文内容变成向量并加入位置
- 用 Masked Self-Attention 整理中文内容
- 用 Cross-Attention 查询原文
- 用 FFN 加工
- 用输出头给出各位置的下一 Token 概率
把正确译文右移一位
现在用上样本的另一半:与原文对应的参考译文 勺子不存在。。
为便于观察,下面把“勺子”和“不存在”各当作一个 Token,具体切分仍由 Tokenizer 决定。先看前三个预测位置;完整样本还会把结束标记作为目标,让模型学习何时停止。
既然训练时正确译文已经存在,模型是不是可以直接偷看答案?不能。如果 Decoder 在学习预测“不存在”时已经看见了“不存在”,这道练习就失去了意义。因此,正确译文会先整体向右移动一个位置:
3.1 · Right shift
为什么 Decoder 的训练输入必须右移一格?
先看反例,再看正确做法训练时,正确译文“勺子不存在。”已经在手里。怎样把它交给 Decoder,又不让模型直接看见要猜的答案?
不右移:输入和答案完全重合
如果把正确译文原样交给 Decoder,每个位置一开始就拿到了自己应该预测的 Token。
例如,输入已经是“不存在”,答案也恰好是“不存在”。模型没有练习“看到勺子后,下一词是什么”。
右移一格:让输入永远落后答案一步
所有 Token 向右挪一格,空出的第一个位置放入 <开始>;原来的正确译文继续作为训练答案。
右移负责把“输入”和“答案”错开;因果遮罩负责挡住右侧未来内容,两者一起防止模型偷看。
于是,第一个位置拿着 <开始> 学习预测“勺子”;第二个位置拿着 <开始> / 勺子 学习预测“不存在”;第三个位置拿着 <开始> / 勺子 / 不存在 学习预测句号。一次训练计算可以同时练习多个位置,所以速度很快,但每个位置仍然必须遵守“不能看未来”的规则。
这种用正确译文提供前缀的训练方式叫作 Teacher Forcing。这里要抓住两个同时成立的事实:
- 多个位置可以并行练习;
- 每个位置又只能看到轮到它之前的正确前缀。
把译文前缀变成可计算的值
和 Encoder 的输入一样,<开始> / 勺子 / 不存在 不能以文字形式直接进入神经网络。每个 Token 会先经过 Output Embedding,变成一行数字,再加上位置编码。这里叫 Output Embedding,只是因为它位于原始 Transformer 的输出语言一侧;它做的事情与前面讲过的 Input Embedding 相同,都是把 Token ID 换成向量。
[<开始>, 勺子, 不存在] 0.180.42…… 0.57−0.16…… 0.310.74…… 目标语前缀有 3 个 Token,所以得到 3 行表示;一行对应一个位置。数值仅用于展示矩阵形状。
11. Masked Self-Attention
Decoder 最底部的注意力叫作 Masked Multi-Head Self-Attention(掩码多头自注意力)。它和前面讲过的 Encoder 自注意力有相同的 Q、K、V 基本机制,但多了一块因果遮罩。
这块遮罩的规则很简单:每个位置可以看自己和左边,不能看右边。
为什么要遮住未来?
因为在训练时,正确译文的完整内容已经存在于训练样本中。我们希望把整段右移后的译文一次送入 Decoder,让多个位置在同一轮里并行计算;但对其中任何一个位置来说,它又不能提前读取自己要预测的答案以及更后面的内容,否则模型只是在照抄答案,而不是学习“根据已有前缀预测下一个 Token”。
因果遮罩正好让这两件事同时成立:完整序列可以一起计算,但每个位置只能使用当前输入和左侧前缀。 这样既保留了 Transformer 并行训练的效率,又避免了未来答案泄漏。
为什么允许看自己?因为训练时每一行的输入和预测目标已经错开了一位。第二行输入是“勺子”,这一行负责预测的是下一个 Token“不存在”,所以看见当前输入“勺子”不等于看见答案。真正需要挡住的是右边尚未轮到的 Token。
实现上,遮罩不是把右侧 Token 从矩阵里删除。所有位置仍在同一张矩阵中,只是当某一行分配注意力时,右侧位置会被标记为不可用,获得的注意力权重为零。
Masked Self-Attention
点一个预测位置,看遮罩怎样挡住“未来答案”
正在讲解:掩码多头自注意力
只能看见当前位置及左侧
第一个位置只能看到开始标记,还不能偷看“勺子”和“不存在”。
只能看见当前位置及左侧
第二个位置可以看到开始标记和“勺子”,但“不存在”仍属于未来。
只能看见当前位置及左侧
第三个位置已经可以看到全部前缀,再据此预测句号。
遮罩不是删除后面的 Token。训练时它们仍在同一张矩阵里,只是当前位置无法把注意力分配给右侧位置。
以学习预测“不存在”的位置为例,它可以参考 <开始> 和“勺子”,但不能参考右侧已经写在训练样本里的“不存在”。只有这样,模型才会被迫从已有前缀和原文中找到足够线索,而不是复制未来答案。
和 Encoder 的自注意力一样,Masked Self-Attention 结束后也要经过一次 Add & Norm:把进入这一子层前的目标语表示沿残差路径加回来,再做层归一化。这样既保留了原有前缀信息,也让更新后的表示稳定地交给下一步 Cross-Attention。
0.180.42…… 0.57−0.16…… 0.310.74…… 0.240.36…… 0.690.08…… 0.480.61…… 行数没有改变。第一行只能参考自己,第二行可以参考前两行,第三行可以参考前三行;之后再经过残差相加与归一化。数值仅用于展示变化。
12. Cross-Attention
Masked Self-Attention 只整理了目标语前缀;如果不查询原文,Decoder 可能写出语法通顺却与原意无关的话。因此,它接着使用 Cross-Attention(交叉注意力)。
Self-Attention 是在同一组文字(原文)里找信息:Q、K、V 都来自这组文字。Cross-Attention 则连接两组文字(原文和译文):Decoder 带着当前需求提出 Q,再到 Encoder 已整理好的原文中寻找 K 和 V。 前者让模型理解“已经有什么”,后者让它把正在生成的内容与原文对应起来。
“Cross”表示信息跨越了两条路径:
- Query 来自 Decoder,表达“根据当前前缀,现在需要查找什么”;
- Key 来自 Encoder,让 Decoder 判断原文中的哪些位置与当前需求匹配;
- Value 也来自 Encoder,提供被选中位置真正携带的原文信息。
假设当前预测位置能看到的正确前缀是 <开始> / 勺子,接下来需要决定下一个 Token。它的 Query 可能会重点匹配 Encoder 中 no 的表示,同时也参考 spoon 保存的物体概念。这样,Decoder 当前这一行不仅知道中文前缀以“勺子”开头,也重新取得了原文中的否定关系。
Cross-Attention
Decoder 一边看自己已经写了什么,一边回头查阅 Encoder
正在讲解:交叉注意力
接下来该翻译原文中的哪部分?
Query:带着当前写作需求去查原文`no` 帮助确认否定关系,`spoon` 提供最直接的原文概念。多条线索会一起进入 Decoder 当前表示。
Self-Attention 和 Cross-Attention 都使用 Q、K、V,但信息来源不同。可以这样记:
- Masked Self-Attention 问:当前可见的目标语前缀中,哪些位置与预测有关?
- Cross-Attention 问:Encoder 读过的原文中,哪些位置与当前预测有关?
Cross-Attention 不会立刻输出“不存在”这个文字。它只是更新 Decoder 当前位置的内部表示,让这行数字同时带有目标语前缀和原文依据。真正把这行表示转换成词表候选,还要等到 Decoder 顶部的输出头。
Decoder 前缀表示 + Encoder 原文记忆 Y_cross:3 行带原文依据的表示 输出仍然对应 Decoder 的 3 个位置,不会变成 Encoder 的 5 行。Encoder 只负责提供可查询的 Key 和 Value,Decoder 决定每个目标位置取回哪些信息。
FFN:分别加工每个目标位置取得的信息
交叉注意力完成了又一次“信息交流”,接下来仍然需要 FFN 做“各自消化”。这和 Encoder 中的 FFN 是同一类工作:每个目标位置独立展开、激活并压回自己的表示,不在 FFN 内部继续跨位置读取。
例如,负责预测“不存在”的那一行现在同时拥有中文前缀“勺子”和原文 no 的信息。FFN 会进一步加工这些已经汇合的线索,之后再经过 Add & Norm。整个 Decoder 层会重复多次,让目标语前缀与原文信息在多层处理中逐渐形成更适合预测的表示。
0.410.28…… 0.830.17…… 0.720.69…… 0.550.21…… 0.960.34…… 0.880.52…… FFN 不改变目标序列的行数,只改写每行内部表示;所有数字均为教学示意。经过重复的 Decoder 层后,结果才送到输出头。
N 层重复
到这里也只走完了 一层 Decoder。和 Encoder 一样,Masked Self-Attention、Cross-Attention 与 FFN 组成的整层会重复 N 次,各层结构相同但参数独立;最后一层的结果才会送入输出头。
Decoder 单边结构
“输出头”就是接在 Decoder 主体上方的 Linear 与 Softmax
Linear + Softmax 是 Decoder 顶部的最后两步,不属于重复 N 次的 Decoder 层。
经过 N 层后,得到的仍是一组内部数字表示,不是文字。
13. 输出头
经过最后一层 Decoder 后,我们得到的仍然是若干行内部表示,不是可读文字。最上方的 Linear(线性层) 会把需要预测的位置映射成整个词表中每个 Token 的分数;Softmax(概率归一化) 再把这些分数转换成一组候选概率。
训练时,我们会使用多个有效位置的预测结果:第一行预测“勺子”,第二行预测“不存在”,第三行预测句号。每一行都有自己的一组词表概率,而不是整段输入只得到一个答案。
真实输出可以先看成下面这样的概率表:每一行表示模型当前能看到的译文前缀,每一列表示一种可能接在后面的 Token。
| 当前能看到的译文前缀 | 下一个是“勺子” | 下一个是“不存在” | 下一个是“。” | 其余词表列 |
|---|---|---|---|---|
<开始> | 72% | 1% | 1% | … |
<开始> / 勺子 | 3% | 10% | 2% | … |
<开始> / 勺子 / 不存在 | 1% | 3% | 61% | … |
读其中任意一个格子时,先看它在哪一行,再看它在哪一列。例如,第二行“不存在”列的 10% 表示:模型看到前缀 <开始> / 勺子 后,认为下一个 Token 是“不存在”的概率为 10%。真实的表会为词表中的每个 Token 都保留一列,完整一行的概率加起来等于 100%;这里仅展开三列,其余列用省略号表示。正确目标不在这张输出表里,而是在下一步计算损失时才拿来逐行对照。
0.550.21…… 0.960.34…… 0.880.52…… 勺子不存在。… 72%1%1%… 3%10%2%… 1%3%61%… 14. 损失与参数更新
只有预测,还不算完成学习。接下来,训练程序会把模型的预测与正确目标放在一起比较,算出两者之间有多大差距,再根据这个差距调整模型内部的参数。就像模型先提交了一份答卷,再与标准答案核对,看看答错了多少,并据此调整自己下次的答题方式。
训练程序会逐行检查:第一行的正确目标是“勺子”,第二行是“不存在”,第三行是句号。它不只检查模型最后选中了哪个 Token,而是查看模型给正确目标分配了多少概率。给正确目标的概率越高,这一行的预测与目标越接近;概率越低,差距越大。
各个有效位置的差距会被汇总成一个数,叫作损失(Loss)。损失越小,表示模型的预测整体上越接近训练目标。接下来发生的事不是“把正确译文存进模型”,而是利用这个数追查:刚才参与预测的许多内部参数,分别朝哪个方向轻轻调整,可能让下一次的损失更小?
一次训练怎样形成闭环
先比较三个位置的预测,再把同一份调整信号送回整条路径
把每个位置的预测与正确目标对照
正确目标得到的概率越高,表示这一行的预测越接近目标;概率越低,差距越大。
<开始><开始> / 勺子<开始> / 勺子 / 不存在(0.33 + 2.30 + 0.49) ÷ 3 ≈ 1.04这里取三个位置的平均值,用一个数表示这批预测整体上离正确目标有多远。
图中概率与损失均为教学示例,不是一条真实训练记录。示例使用最基础的交叉熵并保留两位小数;计算过程放在下方进阶阅读中。每一行来自不同的预测位置,三个百分比不需要相加为 100%。
沿着产生预测的路径往回查
损失不会把正确答案塞进模型。它只帮助训练程序判断,各处参数朝哪个方向微调,可能让下次损失更小。
- 1从损失开始
1.04 表示这一批预测仍有差距。
- 2先回到输出头
查看哪些词表分数需要改变。
- 3再逐层穿过 Decoder
沿着刚才生成概率的计算过程往回走。
- 4在 Cross-Attention 分路
一路继续检查 Decoder,一路沿 K、V 回到 Encoder。
- 5最后进入 Encoder
包括原文嵌入表在内的参数也得到调整方向。
图里的“变化信息”叫作梯度。计算这些梯度的过程叫作反向传播(Backpropagation)。它会经过输出头和 Decoder,在 Cross-Attention 处继续连到 Encoder。于是,预测虽然出现在 Decoder 一侧,Encoder 也能知道自己提供的原文信息是否有助于译文预测。
优化器(Optimizer)随后根据梯度调整参数。可以把参数想成模型内部数量庞大的小旋钮,梯度说明每个旋钮应该往哪边动,学习率则限制一次不要转得太猛。Encoder 的 Embedding、Self-Attention、FFN,以及 Decoder 中参与计算的参数都会因此得到调整。
训练程序会换一批样本,继续重复这个过程。一次调整不保证每个句子都立刻翻译得更好,大量练习才会逐渐形成稳定的规律。还要用没有参与参数更新的数据检查模型是否能处理新句子,而不只是记住训练材料。
把四个阶段合起来,一次完整训练就是:
- 原文经过 Encoder
- 正确译文右移后进入 Decoder
- 各位置预测
- 对照目标计算一份损失
- 梯度沿 Decoder 与 Cross-Attention 返回 Encoder
- 优化器更新两边参数。
进阶阅读:损失、梯度与原始论文的训练设置可选阅读
损失、梯度与原始论文的训练设置
如果某个位置的正确目标是 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 的完整使用流程
<开始>;每轮从顶部概率中选出一个 Token,再把它接回底部的当前前缀,重复经过 Decoder,直到选出结束标记。整个过程只使用训练好的参数,不计算损失,也不更新参数。 Encoder 编码原文
Encoder 先处理完整原文,得到各位置的上下文表示。这份结果可以在本次翻译的后续生成中反复使用,不必每生成一个中文 Token 就把原文重新编码一遍。
这里仍然会用到 Input Embedding、位置信息、自注意力和 FFN。它们的参数已经在训练中学到了有用的规律,本次翻译不通过损失和梯度去修改这些参数。保存的原文表示属于本次计算结果,不是又学出了一套新参数。
Step 01 · Encoder
把完整原文压成一份可反复查询的上下文表示
Thereisnospoon. 每个原文 Token 都对应一行理解了上下文的向量
Thereisnospoon.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
<开始>勺子72%这里13%没有9%不6%下一 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
勺子0.72→勺子 / 不存在0.50这里0.13→这里 / 没有0.08没有0.09→没有 / 勺子0.06每展开一步都重新比较路径总分,只留下较好的若干条继续。
直接选择概率最高的“勺子”,同样的输入会得到同样的结果。
循环生成,直到停止
接下来,Decoder 看到的前缀变成 <开始> / 勺子,再次参考原文,预测下一个 Token。假设选中“不存在”,就再把它接回去:
Step 04 · Autoregressive Generation
生成一个,接回前缀,再生成下一个
Thereisnospoon.
<开始>还没有生成正文 Token
选择策略还未运行
Decoder 正在读取当前前缀,并查询 Encoder 保存的原文表示。
这只是一条示意生成路径,模型也可能选出其他译法。句号本身不一定触发停止;生成结束标记,或达到外部设置的长度上限等条件,才会结束这次输出。
这种“生成一个,再接回输入”的过程叫作自回归生成。下一轮依赖上一轮的实际选择,因此不能像训练那样预先拿到所有正确前缀。
使用时仍然遵守因果规则,只是未来的译文尚未生成,根本没有现成的未来答案可看。如果某一步选错了,后续也会沿着这个实际生成的前缀继续,而不会自动换回训练时的正确答案。
使用的一轮:用已有前缀查询原文 → 得到整个词表的候选概率分布 → 选择一个 Token → 接回前缀 → 继续,直到停止。这个过程通常不更新模型参数。
这次生成期间,Encoder 提供的是同一份原文表示,Decoder 的前缀则逐步增长。两边仍然协作,但不会在每轮输出后执行训练时的参数更新。
到这里,原始 Transformer 的完整路径可以浓缩为:
- 原始输入
- Encoder 的上下文表示
- Decoder 最后位置的内部表示
- 整个词表的候选概率分布
- 选择策略取出实际的下一个 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
GPT 不需要把“待翻译原文”单独编码。系统说明、聊天历史和用户问题都可以放进将要续写的同一条序列,所以左侧的独立读取路径可以拿掉。
摘除 Cross-Attention
Cross-Attention 原本只负责查询 Encoder Output。Encoder 消失后,它既没有 K、V,也没有继续存在的意义,因此连同紫色跨塔通道一起摘除。
只保留一条因果生成主干
所有材料被排进同一条上下文。模型只用因果自注意力查看自己和前文,再经过 Feed-Forward 与输出层,循环预测下一个 Token。
Encoder编码器
DecoderDecoder-only解码器GPT 类生成主干
从两份材料到一份上下文
在翻译结构中,原文和译文前缀从两条路径进入模型。到了 GPT 类文本生成主干里,系统与开发者说明、聊天历史、用户问题、工具返回的文字,以及模型已经写出的回答,都会在进入生成主干前被组织成同一轮上下文。
可以把它想成:原始 Transformer 有一名专门读原文的同事和一名专门写译文的同事;GPT 类文本主干则把当前需要参考的材料按顺序铺在一张长桌上,由同一组网络层反复阅读和加工。
Attention 留在核心
当独立 Encoder 不再存在时,Decoder 自然也不需要跨到另一座塔里查询信息,所以原图中连接两侧的 Cross-Attention 通常不再出现在普通文本生成主干中。
不过,这不等于注意力机制被删除了。GPT 类模型会重复经过很多层因果自注意力:当前位置从前文寻找线索,同时用遮罩挡住未来。前面讲过的 Q、K、V、多头注意力、FFN、残差连接和归一化,仍然构成这些重复模块的基础。
从翻译到续写
原始 Decoder 接着目标语前缀写译文;GPT 类文本主干则接着整份上下文继续写。无论界面上看起来是在问答、改写、总结还是写代码,核心动作仍然是:
根据目前已经出现的 Token,预测紧接着的一个 Token。
ChatGPT 是一个产品,不只是一张神经网络结构图。它还可能包含多模态输入处理、安全检查、请求调度和其他模型外系统。OpenAI 的 模型文档 也展示了文字、图片、文件和对话状态等不同输入形态。
同时,ChatGPT 实际使用的模型会随产品更新而变化,完整内部结构不等于公开的经典 GPT 教学图。因此,上面的对比只回答一个问题:原始 Transformer 怎样演变成 GPT 类模型常见的文本生成主干。它不是对当前 ChatGPT 全部内部实现的精确复刻。
现代 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 FFN | SwiGLU 等门控 FFN | 让 FFN 更灵活地筛选和组合内部特征 |
| 每个 Query Head 都有各自的 Key、Value Head | MQA、GQA | 减少推理时需要保存和读取的 Key、Value |
| 按公式直接实现标准 Attention | FlashAttention 等优化实现 | 减少 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 模型里走一遍。用户在界面上看到的是分开的聊天气泡,但模型实际接收的不是几只气泡,也不是一个天然理解 system、user、assistant 含义的聊天程序。应用需要先按照模型约定的聊天模板,把不同角色的消息、消息边界和已有内容组织成一条序列,再交给 Tokenizer。
为了建立直觉,可以把整理后的内容想成下面这样:
<系统开始> 请用清楚、通俗的方式回答 <系统结束>
<用户开始> 请用普通人能听懂的话解释一下第一性原理 <用户结束>
<助手开始> 第一性原理,就是先把一个复杂问题拆解到最基本的事实…… <助手结束>
<用户开始> 能再举一个生活中的例子吗? <用户结束>
<助手开始>
这里的标记只是教学示意,不代表真实情况。完成分词后,系统说明、历史消息、当前问题和助手回答的起点就都成为同一条 Token 序列。
Decoder-only Chat Flow
一次现代聊天请求,怎样穿过模型与外部系统?
新 Token 接回前缀,继续预测下一 Token,直到生成结束标记或达到外部限制。
外部程序解析调用、执行函数,再把结果作为新消息放回上下文,模型随后继续生成。
同一组 Decoder-only 层处理整段上下文
这条序列进入 Token Embedding,再加入位置信息,随后反复经过因果自注意力、FFN、残差连接和归一化。它没有一座独立的 Encoder,也不需要 Cross-Attention:当前回答位置需要的材料,都已经排在同一条序列的左侧。
因果遮罩仍然发挥作用。系统说明的位置只能参考它前面的 Token;用户问题可以参考更早的系统说明;助手准备回答的位置则可以参考系统说明、历史记录和当前问题。这样,虽然训练时整条序列可以并行处理,每个位置仍然无法偷看位于自己右侧的内容。
训练时练习所有位置,使用时读取最后位置
在基础的因果语言模型训练中,程序可以让序列中的许多位置同时预测各自的下一个 Token:前一个位置预测后一个位置,损失再汇总起来更新参数。到了聊天式监督微调,训练样本会进一步包含用户消息和理想的助手回答;一些训练流程只对助手回答等指定区域计算损失,使模型重点学习“看到这些消息后,助手应该怎样继续”。具体保留哪些位置的损失,取决于数据格式和训练实现。
正式聊天时并没有现成的正确回答。模型处理上面这段完整前缀后,应用只需要读取当前最后一个有效位置的输出,经由输出头得到词表上的候选分布,再选择一个 Token。它可能先生成“比如”,再逐步写出买桌子、规划旅行或控制开店成本的例子。每个新 Token 都被接回序列,成为下一轮前缀的一部分;这个循环不断重复,屏幕上才逐渐出现完整回答。
五、从续写到对话
我们已经习惯了向 AI 提问,然后得到回答,很容易觉得“模型回答问题”是理所当然的。但先回到模型刚完成基础训练的状态:它并不知道眼前是一位用户,也不知道自己应该帮助对方。它只会根据曾经见过的文字规律,把前文继续写下去。
例如,看到:
北京是中国的首都,东京是日本的首都,巴黎是
模型很可能接出“法国的首都”。它不是在回答谁,只是发现这段文字符合某种规律,并顺着规律补全后文。
现在再给它一句看起来像问题的话:
请比较骑自行车和开车上班,各写一个优点。
这时的模型可能把它当成一道尚未写完的练习题,接着写:
要求结合实际情况说明理由。下一题:公共交通有哪些优势?
这段话续写得很自然,却没有替用户完成任务。它也可能因为以前见过问答、访谈之类的文字而碰巧给出答案,但并不稳定。
为了让它学会回答,训练者会准备大量这样的示范:
问题:请比较骑自行车和开车上班,各写一个优点。
理想回答:骑自行车能锻炼身体;开车不易受天气影响。
看过大量“问题后面跟着理想回答”的示范后,模型逐渐学会:遇到这类输入,更应该接任务的答案。于是同一句输入再次出现时,它更可能直接回答:
骑自行车的优点是能锻炼身体;开车的优点是不易受天气影响。
模型其实一直都在续写。后训练只是让它学会,在对话里最合适的续写,就是回答用户的问题。
前面已经解释了一次训练如何发生:模型做出预测,损失衡量预测与目标的差距,梯度再推动参数调整。现代 LLM 通常会经历目标不同的几个训练阶段,才从一组随机参数逐渐变成我们使用的聊天助手。
MODEL LIFECYCLE
从“学会语言”到“回答这一次问题”
生成下一个 Token 的能力始终位于核心。训练阶段通过不同信号改变模型,推理阶段只让已经训练好的模型处理眼前上下文。
预训练和后训练都会使用第四章讲过的“预测、计算损失、更新参数”,区别主要在于:模型看什么数据、使用什么学习信号,以及希望参数形成什么倾向。 到了推理阶段,参数通常固定下来,只处理眼前这一次输入。
1. 预训练
Pre-training(预训练) 通常是规模最大、成本最高的一段训练。训练程序把网页、书籍、论文、代码等大量材料清洗、去重并切成 Token 序列,让模型反复预测下一个 Token。
例如,模型看到“水在标准大气压下的沸点是”,训练目标可能就是后面实际出现的“100”;看到一段程序的前半部分,目标则是原文接下来的代码。答案直接来自文本本身,不需要人为给每句话逐一贴标签,因此这类过程常被称为自监督学习。
每次预测带来的参数变化都很小,但海量样本会反复推动模型调整 Embedding、Attention、FFN 等参数。模型由此逐渐形成语法、事实关联、文体、代码模式和一定的推理能力。训练材料并不是以一套可以逐条检索的文档原样装进参数;更接近于大量规律被分散地压缩进许多参数之间。不过,模型仍可能记住训练材料中的个别片段,这也是训练数据需要清洗、去重和治理的原因之一。
预训练结束得到的通常叫作基础模型(Base Model)。它已经很会续写,却未必知道用户是在下指令:面对“请比较两个方案”,它可能继续补写问题、模仿网页格式,或者给出不符合人类期待的内容。预训练解决的是“从大量文本中学会语言与模式”,不等于已经解决“怎样成为一个有用、安全、稳定的助手”。
2. 后训练
Post-training(后训练) 不是某一种单独算法,而是预训练完成后的一组训练过程。与预训练相比,它使用的数据通常少得多,但目标更集中:让模型更会遵循指令、组织回答、拒绝不合适的请求、调用工具,并让输出更符合人类偏好。
后训练常包含两个相互配合的部分:
- 监督式微调(Supervised Fine-Tuning,SFT):用上面这种“用户问题 → 理想回答”的示范继续训练模型。它学到的不只是答案内容,也包括怎样识别指令、采用合适格式和在何时结束回答。
- 偏好对齐:对同一个问题准备多个候选回答,让人类或其他评估系统指出哪些更好,再用这些比较继续调整模型。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,而是把它限制为两个较小矩阵 A、B 的乘积:
ΔW = B × A
如果原矩阵的输入、输出维度都很大,而 A、B 中间相接的维度 r 很小,需要训练的数字就会少很多。这个 r 常被称为 Rank(秩):它可以粗略理解为这条新增分支用多少个方向来表达参数变化。较大的 r 提供更多调整空间,同时也会增加训练和存储成本;但它不是一个单独决定效果的“质量旋钮”,数据、学习率、训练步数以及 LoRA 加在哪些层上同样重要。
训练时,损失和反向传播仍然照常发生,只是梯度主要用来更新 A、B 等少量新增参数,基础模型的原权重保持冻结。因此,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. 蒸馏
先用一个生活类比
把蒸馏想成:名厨带学徒
对应:教师模型给出答案
对应:概率、解释等丰富信号
对应:只更新学生模型的参数
对应:学生模型独立部署
Knowledge Distillation(知识蒸馏) 可以理解为“让一个模型当老师,训练另一个模型”。能力较强的 教师模型(Teacher Model) 先对大量问题给出答案或候选 Token 的概率分布;训练程序再把这些信号当作学习目标,更新 学生模型(Student Model) 的参数。学生通常更小、更便宜,目标是在保留尽可能多能力的同时,降低部署所需的内存、计算量和响应成本。
KNOWLEDGE DISTILLATION
老师不是被塞进学生体内,而是在训练时提供学习信号
为什么不只让学生学习标准答案?假设一道题有四个选项,普通标签只告诉它“B 正确”;教师模型给出的概率还可能透露“B 明显最好、C 有一点合理、A 和 D 基本不可能”。这种比单个正确标签更丰富的信号常被称为软目标(Soft Targets),它能帮助学生学习教师对不同候选结果的相对判断。现代 LLM 的蒸馏也常让教师直接生成回答、解释或指令数据,再用这些材料训练学生;不同方法使用的教师信号并不完全相同。
SOFT TARGET LAB
同样知道 B 正确,“软目标”还多告诉了学生什么?
蒸馏不是把教师的参数直接复制或压缩进学生。训练完成后,学生通常可以独立回答,不需要每次请求教师出场;它学到的是教师在训练样本上表现出的输出规律。蒸馏也不保证能力被完整搬走:学生容量更小,可能丢失长尾知识和复杂能力,也可能继承教师的错误与偏见。它在某个特定任务上甚至可能表现很好,但这不代表已经获得教师的全部通用能力。
它和前后几个概念的关系可以这样区分:
- 与微调:两者可以重叠。用教师生成的数据继续训练学生,通常也是一种微调;“蒸馏”强调学习信号来自教师,“微调”强调从已有模型出发继续更新参数。
- 与后训练:蒸馏可以用于后训练,把教师的指令遵循或偏好传给学生;它也可以用于其他训练阶段,因此不等同于后训练。
- 与量化:蒸馏通常要训练一个学生模型,让它改变参数并学习教师行为;第七章会讲到的量化主要是用更少的比特近似保存和计算已有权重。二者都可能降低部署成本,但路径不同。
知识蒸馏的经典论文 把这种思路概括为:用较大模型或模型集合形成的知识,训练一个更适合部署的小模型。
5. 对话纠错不会自动训练模型
用户指出错误后,模型可以根据当前对话调整后续回答,但这只是利用上下文,并不会更新模型参数。只有数据进入后续训练流程,才可能影响未来的模型版本。
六、局限和缺点
1. 上下文容量与记忆
人们常说模型有一个很大的 Context Window,也常把它直接叫作“记忆”。这个说法方便,却容易让人以为模型像人一样把整段经历永久保存在脑中。
更准确的直觉是:Context Window 就像一块黑板——模型每次回答时,只能读到黑板上还留着的内容。写满了就得擦掉旧的,腾出地方写新的。
系统说明、聊天历史、用户刚输入的问题、模型已经生成的内容,以及应用取回的文件片段或工具结果,都可能被写到这块黑板上。模型当前能够直接使用的,是此刻确实留在窗口里的 Token。
两种不同的“记忆”
可以先把两类东西分开:
- 模型参数:模型在训练中形成的大量模式。它们包含语言规律和训练中形成的知识,但不是可以逐条翻阅的聊天记录。就像你学会了骑自行车,身体记住了平衡的感觉,却说不出自己到底是第几次练习时学会的——知识已经融进了身体,而不是存成一本可以翻页的笔记;
- Context Window:本轮推理直接可见的 Token。它变化快、容量有限,更像一块随时会被擦写的黑板。
所以,“我上周告诉过模型一件事”并不等于“模型今天一定能直接看到它”。对话只存在于当前 Context Window 中;窗口关闭后,模型本身不会把旧对话保存在任何地方。至于应用层如何通过数据库、搜索等方式为模型补充外部资料,属于检索增强生成(RAG)等系统设计的范畴,留到后续文章讨论。
窗口装不下时
回到文章开头的例子。用户先要求”用普通人能听懂的话解释第一性原理”,模型给出了回答,接着用户又追问了几个新问题。
如果应用只保留最近的消息,最初那条”用普通人能听懂的话”的要求可能已经被移出窗口。模型接下来虽然仍能回答,却可能不再知道需要保持通俗的风格。
截断与摘要
当材料超过容量时,应用必须决定怎样整理。常见方法包括:
- 截断:直接移除一部分旧 Token。实现简单,但可能丢掉很早出现的关键条件;
- 摘要:把较长的旧对话压缩成更短的说明。可以节省空间,但摘要本身可能遗漏细节或改变原意。
下面可以缩小教学窗口,并切换两种常见的应用层整理思路。
Context Window Simulator
缩小上下文窗口,看看哪条信息会先消失
这些整理工作通常由模型外部的应用系统负责。模型本身不会因为窗口满了,就自动拥有完美的长期记忆。
看得见,不等于用得好
容量是一个限制,但不是唯一的限制。即使材料仍在窗口里,模型也不一定能用好。
“可见”只表示模型有机会通过注意力读取它,不表示模型必然抓住重点。材料过多、指令互相冲突、关键信息埋得太深或表达不清,都可能让模型忽略本来位于窗口中的内容。
其中一种典型现象叫作 Lost in the Middle(中间信息利用不足)。 相关研究 发现,在一些长文本问答和信息检索任务中,同一条关键信息放在上下文开头或结尾时,模型往往更容易正确使用;移到很长的上下文中间后,表现可能明显下降。这里的“Lost”并不是说中间的 Token 已经从窗口里消失,而是说模型对不同位置的信息利用并不总是同样可靠。
因此,Context Window 的大小表示容量上限,不等于等量的可靠理解能力。对真正关键的限制,清楚地写在靠近当前任务的位置,通常比把它埋在很长的历史记录中更稳妥。
进阶:Context 长度、输入长度和输出长度怎样计算可选阅读
Context Window 通常以 Token 数量计量。一次请求占用的不只是用户最后输入的文字,还包括系统消息、开发者消息、历史对话、工具结果、附件被转换后的内容,以及当前已经生成的 Token。
产品常把最大输入、最大输出和总 Context 上限分别设置。不同服务如何截断、是否自动摘要、工具结果怎样计入容量,都属于具体实现,不能从模型名称单独推断。
更长的上下文也不等于模型会平均重视所有位置。位置编码、注意力模式、训练方式和信息组织都会影响长上下文中的实际表现。
2. 知识的时效性
模型参数中的知识来自训练时见过的数据,而不是一套会随现实世界自动更新的数据库。训练结束后,参数在日常使用中通常是固定的;新闻刚刚发生、法规刚刚修改、软件刚刚发布新版本,都不会因为现实已经变化,就立刻写进模型参数。
因此,模型自带的知识更像是对一段历史资料的压缩,而不是实时世界的镜像。产品有时会给出一个“知识截止日期”,帮助用户粗略判断训练资料覆盖到什么时候;但这不是一条整齐的分界线:截止日期之前的事实也可能没有被训练数据收录,或者没有被模型可靠学会;截止日期之后的事情,模型也可能根据上下文进行猜测,但猜中不等于真正掌握了最新事实。
这个限制在询问“现在”的问题时尤其重要,例如:
- 某家公司目前的负责人是谁;
- 今天的汇率、票价或天气怎样;
- 一项法规现在是否仍然有效;
- 某个软件最新版本的接口如何使用。
对于这类问题,只让模型“再认真想一想”并不能补上训练结束后才出现的信息。用户在对话中纠正模型,也只会把新信息放进当前上下文,并不会当场改写模型参数。
搜索、RAG 和工具调用可以缓解这个问题。它们先从网页、数据库或其他来源取得较新的资料,再把结果放进当前上下文,让模型依据这些资料回答。但这时提供实时性的,是模型外部的系统,而不是模型参数突然更新了;回答是否可靠,仍取决于来源是否新鲜、准确并且适用于当前问题。关于外部资料如何被检索并交给模型,可以继续阅读 《从检索到回答:RAG 是怎样工作的》 。
如果系统没有取得最新证据,模型仍可能沿着训练中熟悉的表达给出一个流畅、肯定却已经过期的答案。这就连接到下一个限制:幻觉与证据。
3. 幻觉与证据
回到整篇文章最核心的一件事:语言模型训练和生成时反复练习的是根据已有上下文预测接下来最合适的 Token。
这个目标非常擅长学习语言模式。它让模型可以续写句子、解释概念、转换格式,也能模仿资料中常见的事实表达方式。但“接下来听起来合理”与“这件事已经得到可靠证据支持”不是同一个问题。
持续生成的机制
模型总能在词表中找到下一 Token 的候选。即使上下文没有提供答案,常见地名、时间表达、机构语气和数字组合仍然可能形成一条语言上顺畅的路径。
如果“图书馆通常在晚上……”已经出现,那么“八点”“九点”“闭馆”都可能是很自然的延续。生成机制本身不会自动停下来核对事实;只有当当前上下文中已经给出可靠证据,或者应用另有验证流程时,回答才多了一层事实约束。
模型有时会表达不确定,也是在根据上下文生成合适的语言。它不是一个永远准确的内部报警器。因此,语气很肯定不能证明正确,语气很谨慎也不能自动证明错误。
证据对回答的约束
下面使用一个虚构机构。问题始终相同,只改变放进 Context 的证据。依次查看没有资料、来源冲突和官方资料三个状态。
Evidence Lab
问题不变,只改变模型这一轮能看到的证据
阿斯特拉市图书馆周二几点闭馆?
没有营业时间证据阿斯特拉市图书馆周二通常在晚上 8 点闭馆。
更可靠的做法是明确说不知道,并请求查询资料。
我记得图书馆晚上 8 点关门。
时间较旧,身份不明好像延长到晚上 9 点了。
较新,但没有官方确认现有非官方资料分别写着晚上 8 点和 9 点,我无法确认周二的准确闭馆时间。建议查看官方通知。
可靠回答不一定给出一个数字,也可以诚实保留不确定性。
周一至周五 09:00-21:00;周六、周日 10:00-18:00。
直接来源,适用于当前日期根据图书馆本月公布的开放时间,周二晚上 9 点闭馆。
仍应让读者知道依据是什么,并保留日期范围。
这个例子说明,可靠回答不总是“给出一个明确数字”。
- 没有证据时,承认无法确认比编出一个常见营业时间更可靠;
- 来源冲突时,指出冲突并继续核验比随便挑一个说法更可靠;
- 有适用的直接来源时,回答应清楚说明结论来自哪里。
有资料仍然可能出错
从取得资料到生成回答,中间还有多处可能出错:
- 提供的资料可能与问题并不真正相关;
- 资料可能已经过期,或与当前日期和地区不匹配;
- 多个来源可能互相冲突;
- 模型可能误读表格、否定词、单位或适用条件;
- 回答可能正确引用了资料,却在没有证据的地方继续补充细节。
从外部取得资料并放入 Context,可以给模型补充当前证据,却不是一个“事实永远正确”的开关。怎样寻找、筛选和组织外部资料属于另一套系统问题,留到后续文章展开。对于医疗、法律、财务、安全等高风险问题,仍然需要查看原始来源和合格专业人员的判断。
让回答更容易核验
可以主动改变任务,而不是只要求“给我一个答案”:
- 提供与问题直接相关的原始材料;
- 要求把事实主张和依据对应起来;
- 要求明确区分已知、推测和无法确认;
- 对日期、数字、人物和引用回到原始来源检查;
- 在证据不足时允许模型停止,而不是强迫它必须给出完整结论。
这些方法不会改变语言模型的基本生成机制,但会让上下文包含更好的约束,也让用户更容易发现错误。
进阶:幻觉与校准可选阅读
“幻觉”通常泛指模型生成了无依据、与来源冲突或不可验证的内容。它不是某一个单独模块触发的故障,而可能来自训练数据、提示上下文、采样路径、知识时效、证据质量和模型推理错误等多种因素。
概率校准研究的是模型给出的置信程度与实际正确率是否匹配。下一 Token 概率本身并不是整项事实主张的校准置信度,把最高 Token 概率直接解释成答案正确率是不可靠的。
4. 推理与事实
这里说的“推理”是 Reasoning,也就是上一章推理模式试图加强的分析问题、连接线索的能力,不是表示整个使用阶段的 Inference。可以先把它和知识粗略分开:
- 知识或证据决定模型当前有哪些事实可用;
- 推理决定模型怎样连接、比较或转换这些信息。
REASONING ≠ KNOWLEDGE
“多想几步”能整理线索,但不能凭空增加事实
当一道题已经给出价格、预算和规则时,把任务拆成中间步骤,可能帮助模型减少遗漏。 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、对话历史和附带资料。
Decode 逐步生成
模型每轮预测一个新 Token,再把它接到已有内容后面,直到回答结束。
不要与模型结构混淆: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 却值得留下来反复复用。这就是它叫 KV Cache,而不是 QKV Cache 的原因。
拖动下面的序列长度,可以比较同一轮在有无缓存时分别发生什么。
KV Cache Visualizer
生成越往后,前面已经写过的内容就越多
为了得到本轮注意力需要的 Key 和 Value,前缀中已经出现的位置也要再次计算。
历史位置的 Key 和 Value 已经保存在缓存里,本轮只为新增 Token 计算一份。
它带来的交换很直观:
- 少做重复计算:历史 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
判断方法:问它保存的是“神经网络中间数值”“重复前缀的计算结果”,还是“以后要重新放回 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
不同输入,先翻译成模型能计算的向量
冰箱[1.2, −0.3, …]里[2.2, −0.4, …]还能[3.2, −0.5, …]做[4.2, −0.6, …]什么[5.2, −0.7, …]?[6.2, −0.8, …] 左上区域[1.2, −0.3, …]右上区域[2.2, −0.4, …]中间区域[3.2, −0.5, …]门架区域[4.2, −0.6, …]下层区域[5.2, −0.7, …]整体特征[6.2, −0.8, …] 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, …] 关键不是把图片硬变成一句描述,而是先把其中的视觉特征变成一组可参与计算的向量,再让它们与文字线索互相影响。图中的切块、数值和食物均为教学示意。
这些向量还不能直接和文字顺畅配合,通常需要经过一个连接器或投影层(Projector),把视觉、音频表示转换到语言主干能够接收的维度与表示空间。可以把它想成不同部门先把资料整理成同一种表格格式:内容没有变成文字,但已经能进入同一套计算流程。
进入模型后,不同模态的信息会通过 Self-Attention 或 Cross-Attention 互相取得线索。模型处理“照片里有哪些食材”时,文字中的“食材”可以把注意力分给图片里对应的区域;接着,语言解码器仍可沿用前文讲过的方式,逐 Token 生成“鸡蛋、牛奶和青菜可以……”这段回答。
以公开的 LLaVA 为例,它把预训练视觉编码器和语言模型用一个投影层连接起来,再用图文指令数据训练两边如何配合。这是理解视觉语言模型很清楚的样板,但不是唯一方案:有些模型在语言主干的中间插入 Cross-Attention,有些从预训练开始就在统一序列中混合多种模态;能够生成声音或图片的系统,还可能把输出交给音频解码器或扩散模型。
进阶阅读:多模态能力怎样训练出来?可选阅读
多模态训练通常要解决两个相连的问题。
第一步是对齐(Alignment)。模型需要从图片—文字、音频—文字等配对数据中学会:哪些视觉或声音模式与哪些语言概念相关。CLIP 使用对比学习,让匹配的图片与文字表示靠近、不匹配的组合远离;这建立了跨模态对应关系,但本身还不等于会对话。
第二步是多模态指令训练。训练样本不再只有“图片配一句标题”,还会包含针对图片提问、比较图表、按语音指令操作等任务。模型据此学习什么时候读取哪种模态,以及怎样把取得的线索组织成符合要求的回答。
“原生多模态”通常表示模型从较早的预训练阶段就共同学习多种模态;“拼接式”架构则可能先保留一个成熟语言模型,再增加视觉编码器与连接层。前者不必然在所有任务上更强,后者也不只是把图片先写成一句描述。两条路线都需要把不同来源的表示对齐,并通过 Attention 让它们在任务中交换信息。
若模型直接输出语音或图片,后半程也会变化。系统可以先生成文字,再由独立的语音或图像模型转换;也可以把音频、图像表示也离散成可预测的单元,让一个统一模型生成。厂商通常不会公开全部实现细节,因此不能仅凭产品界面判断它采用了哪条路线。
多模态扩大了模型可以利用的信息,却没有让它获得人的感官。图片在缩放、切块和编码时可能丢失细节;模型也可能认错小字、数量、空间关系,或对图中不存在的物体作出确信描述。它只是把文字之外的信息接入同一条推理链,并不等于完整、可靠地看懂了现实。
3. 参数规模
参数是训练过程中被不断调整的数字。参数更多,通常意味着模型有更大的容量,但也会增加训练、存储和运行成本。下面只保留 GPT、Grok、Llama、DeepSeek 等大众更熟悉、且至少有一个可核验参数节点的系列,用对数纵轴观察近几年的数量级变化。
代表模型参数规模 · 2018—2025
几条代表路线的参数增长
参数之外,还要看什么规模?
参数量只回答“模型内部有多少个可调数字”,不能单独说明这些数字接受过怎样的训练,也不能直接算出一次回答需要多少资源。要更完整地理解模型规模,至少还要同时看三件事:
| 观察维度 | 它描述什么 | 单独看它不能说明什么 |
|---|---|---|
| 训练数据 | 训练使用了多少 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 ×↓线索:数字 · 运算符 · 短答案 算出 6 × 的下一小步算出 6 × 的下一小步图里的“订单”“分单员”和“工作台”只是帮助理解的比喻:真实模型中,它们分别对应 Token、路由器(Router)和一组独立的 FFN 参数。这里给工作台写的“数字与符号”“短答案”等标签,是对它们可能形成的偏好的通俗描述;所谓 Expert 不是工程师预先命名的“数学专家”或“中文专家”,而是在训练中逐渐形成不同偏好的参数分支。
多工作台不等于每一单都会更快或更便宜。没有被选中的 Expert 仍要占存储空间;如果工作台分布在不同设备上,传递订单也会产生通信和调度成本。MoE 的重点是:用很大的总容量,换取每个 Token 只使用其中一小部分的计算。
进阶阅读:MoE 怎样选择 FFN?可选阅读
从稠密 FFN 到可选择的 FFN
普通 FFN 中,无论当前处理的是 There、no 还是 spoon,每个 Token 都会经过同一套 FFN 参数。这样的结构通常称为稠密(Dense):这层里的参数会按既定路径参与每个 Token 的计算。
但模型想容纳更多处理模式时,如果只是不断把这座 FFN 加宽,每个 Token 的计算量也会跟着增加。于是,一些现代模型采用 MoE:不再只放一座更大的 FFN,而是放置多座可选择的 FFN 分支,并在它们前面增加一个路由器(Router)。
一种常见的 MoE 计算过程是:
- 当前 Token 已经经过 Attention,带着它从上下文取得的线索来到 MoE 层;
- 路由器根据这行 Token 表示,为多个 Expert 分支计算分数;
- 只选择得分较高的一个或少数几个 Expert 来处理它;
- 被选中分支的结果按权重合并,再送回 Transformer 的主干。
这里的 Expert 通常就是一组独立的 FFN 参数。路由发生在 Token 层面,也发生在模型的不同 MoE 层中,因此同一句话里的不同 Token 可能被送往不同分支,同一个 Token 到了更高一层也可能走向另一组分支。
可以把普通 FFN 想成一家只有一座中央厨房的餐厅,所有订单都经过同一套设备;MoE 则像增加了多座工作台,由分单员为每张订单选择少数几座实际开工。餐厅拥有的设备总量可以很大,但做一张订单时,不必同时启动全部设备。
这个类比同时解释了 MoE 最重要的目的:增加模型的总参数容量,同时避免让每个 Token 都使用全部参数进行计算。这类只激活部分分支的方式也叫作稀疏激活(Sparse Activation)。它并不让计算免费——Attention、路由器、被选中的 Expert 和模型中的其他稠密部分仍然需要运行——但总参数量可以比每个 Token 实际用到的参数量大得多。
| 普通 Dense FFN | MoE 层 | |
|---|---|---|
| 每个 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 的平均分更高,不表示它在你的中文改写、表格抽取或特定专业资料上也一定更好。 HELM 采用多场景、多指标评估,正是为了让准确性、校准、稳健性、公平性与效率等不同权衡不被一个总分遮住。
排行榜还可能受到提示模板、评分模型、测试版本和样本是否接近训练材料等因素影响。分数差距很小时,也不应自动解释成用户一定能感受到的稳定差异。
选择模型时,更实用的方法是建立一个小型个人评测集:
- 收集十几个自己经常提出的真实任务;
- 保留过去最容易失败的例子,而不是只测简单样本;
- 预先写下合格标准,例如事实、格式、语气、延迟和成本;
- 在相同输入与设置下比较,并检查多次运行的稳定性。
Benchmark 更像一副观察模型的镜片,不是给所有使用场景排出的永久总名次。
参数里的经历
模型的参数不是凭空出现的;它们由一段段训练逐渐塑成。人与人之间的差异,也常常来自同样漫长而各不相同的经历。
每个人之所以不同,并不只因为眼前的信息不同,还有过去的经历、应对它们的方式,以及不断被反馈塑造的过程都不同。家庭、教育、朋友、读过的书、遇过的挫折、得到的鼓励,都会像漫长训练中的样本和信号一样,慢慢留下痕迹,调整我们的一个个“参数”。于是,即使面对同一个问题、听到同一段话、站在同一个选择面前,我们所作出的理解与反应,也总有各自的来处。
每个人都可能有自己的困境,只是它未必会显露在眼前。
这或许能解释一些让人困惑的事:为什么同一句话,有人听见的是善意,有人先感到防备;为什么同一条路,有人觉得值得冒险,有人更愿意绕开;为什么我们常常以为“这么明显的事,为什么他会那样想”。我们看到的是一次举止或一次决策,却很少能看见它背后那段漫长、具体而不可复制的“训练过程”。
当然,人始终比大模型更复杂,也更有趣。人会感受疼痛与快乐,会主动反思自己的过去,会改变想要成为的人,也要为自己的行动担责。这个类比只是为了提醒自己:每一个看似简单的反应背后,都可能隐藏着我们尚未了解的经验和理由。
理解模型之后,我看人也多了一点耐心。别人说一句话、做一个选择,眼前看到的只是当下的反应;他经历过什么,我通常无法获知。所以可以做判断,但没必要太早下结论。