跳转至

LLM 到底是如何工作的

原文:How LLMs Actually Work

这篇文章是对 LLM 工作原理的一次梳理。现代 LLM 大多是通过把 transformer 块层层堆叠起来的,所以理解了 transformer 的内部机制,你就理解了大部分内容。

我会讲现代基于 transformer 的 LLM 内部的核心机制,但不涉及那些粘手的数学细节。别误会,数学你还是该学的,但这篇可以当作入门读物。

大多数现代 LLM 都共享同一套 transformer 家族的骨架。它们之间的差异主要来自训练数据、规模与配置选择,以及在此基础上做的后训练。读完本文,你应该能够阅读许多现代 LLM 论文或模型卡,并知道每一节在讲架构的哪个部分。

路线如下:

  1. Token(词元),一段文本如何变成一串整数
  2. Embedding(嵌入),这些整数如何获得含义
  3. 位置编码,模型如何知道 token 的先后顺序
  4. Attention(注意力),token 之间如何交换信息
  5. 多头注意力,模型如何同时追踪多种关系
  6. 前馈网络,模型中存储结构的主要所在地
  7. 残差流与层归一化,是什么让深堆栈可训练
  8. 预测下一个 token,模型实际输出什么,生成循环如何运转
  9. 架构 vs 训练得到的权重,现代 LLM 之间哪些是共通的,哪些是不同的

从分词到下一 token 预测的 Transformer 流水线

全文穿插了一些小科普,任何背景的读者都能跟上。

分词(Tokenization)

模型并不直接读文本,它读的是整数 ID。把你的 prompt 转换成这样一串整数的步骤就叫分词。

这个转换步骤叫做分词。分词器接收一个字符串,输出一串整数,每个整数指向固定词表中的一个条目。现代 LLM 的词表通常有几万到几十万条。

小科普:token ID

token ID 是模型用来指代一个词表条目的整数。模型处理的是数字,而不是字面意义上的词。

token 通常并不是完整的词,而是子词片段。“tokenization” 可能被切成 [“token”, “ization”],“running” 可能被切成 [“run”, “ning”]。这样做是为了效率。整词词表太大,而且无法泛化到新词;字符级词表太小,会让模型从零开始学习最简单的模式。子词分词居于两者之间:最常见的片段成为单独的 token,罕见或新造的词由更小的片段拼出。

小科普:词表

词表是分词器中固定的片段列表。每个片段有一个 ID,模型只能直接接收来自这个列表的 ID。

这种取舍会出现在你意想不到的地方。经典例子:问 LLM “strawberry” 里有几个 R。LLM 以前常常答错。这不是模型不会数数,而是模型并不直接处理字母,只处理恰好拼出一个词的 token ID——而人类则会逐字母拆开看。

分词把文本变成 token ID

不同模型家族使用不同的分词器。GPT 系列用 Byte Pair Encoding 变体,SentencePiece 在 LLaMA 系模型中常见。这个选择对算力有影响(token 越少,工作越少),也影响多语言覆盖等,但基本形状是一样的:文本进,整数出。

现在 prompt 已经是一串整数了,下一步是给这些整数赋予含义。

嵌入(Embeddings)

1024 这样的 token ID 只是行索引,本身没有意义。赋予它意义的是一张巨大的表,叫做嵌入矩阵。

每个模型都有这样一个矩阵。词表里每一条对应一行,每行是一个长长的数值向量。每行的长度就是模型的隐藏维度。在很多 7B 级别的模型里,每个 token 对应 4096 个数。更大的模型通常用更宽的向量。

小科普:向量

向量就是一组数字。在 transformer 里,每个 token 变成一个向量,模型才能对它做运算。

当分词器把一个整数交给模型时,模型就去查那一行,用对应的向量替代它。这个向量就是 token 的嵌入。它是模型对这个 token “含义”的表示,是在训练中习得的。

小科普:嵌入矩阵

嵌入矩阵是一张查找表。token ID 进,学到的向量出。

这些嵌入有一个有趣性质:语义相近的 token 最终会有相近的向量。“king” 的向量在空间中离 “queen” 很近,“Paris” 的向量离 “France” 很近。这些都不是硬编码的,而是在足够多的文本上训练后自然涌现的——模型之所以学到这些位置,是因为它们能让模型更好地预测文本。

你还可以对嵌入做算术,有时还真管用。著名例子是 king − man + woman ≈ queen。嵌入空间的几何结构承载着真实的语义结构,尽管从没有人告诉模型要这样搭建。

嵌入空间类比,体现语义关系

有一点要讲清楚:到这一步,每个 token 都被它自己的嵌入替换了,但嵌入本身并不包含这个 token 在序列中位置的信息。“dog” 在第一个词还是在第五个词,对应的向量是同一个。这就成问题了。

这正是位置编码要补上的缺口。

位置编码

纯自注意力本身没有词序的内置表示。没有某种位置信号,它就没法直接判断 “dog” 是在 “bites” 之前还是之后。

词序会改变含义。所以模型需要再加一块:一种把每个 token 的位置注入到运算中的方式。

小科普:位置编码

位置编码是模型获得顺序信息的方式。它告诉模型每个 token 处在序列的哪个位置。

最初的 transformer 论文(Vaswani 等人,2017)是这样做的:给每个位置一套独立的数值模式,在做任何其他处理之前,把它直接加到该 token 的嵌入上。位置 1 有一种模式,位置 5 有另一种,位置 100 又是另一种。这些模式来自不同频率的正弦和余弦波。这样一来,位置 1 的 “dog” 嵌入就和位置 5 的 “dog” 嵌入不同,仅仅因为叠加的位置模式不同。

这是有效的,选正弦编码部分原因是它们能外推到训练时未见过的序列长度。但加法式位置方案有两个问题,随着模型规模扩大变得很关键。

第一,嵌入必须在同一组数字里同时承载含义和位置。能塞进去的东西有限。

第二,特别是学习式的绝对位置嵌入,泛化性不佳。如果你训练时最长 prompt 只到 2048 个 token,模型从没见过位置 5000,那个位置的嵌入并没有以同样方式被学过。

现代模型大多用另一种方案,叫旋转位置嵌入(Rotary Position Embeddings,RoPE),由 Su 等人 2021 年提出,如今用在 LLaMA、Mistral、Gemma、Qwen 以及大多数开源权重家族中。直觉是这样:RoPE 不再把位置信息加到每个 token 的向量上,而是根据 token 所在位置把 Query 和 Key 向量旋转一个角度。位置 1 的 token 转一点,位置 100 的 token 转得多一些。当两个 token 后来在注意力里被比较时,重要的是它们 Query 和 Key 旋转之间的差,这个差就编码了它们相隔多远。

小科普:RoPE

RoPE 是 Rotary Position Embeddings 的缩写。它不加上位置向量,而是旋转 Query 和 Key 向量,让相对距离在注意力中体现出来。

旋转位置嵌入按位置旋转向量

实际优势是切实的。RoPE 天然地编码了相对位置(更贴近注意力真正想要的),对更长上下文的泛化更好,而且不增加模型参数。

即便位置编码做得再好,现代 LLM 也有一个被记录在案的“中间迷失”问题(Liu 等人,2023):对长 prompt 中开头和结尾的信息,它们用得比埋在中间的信息更可靠。这就是为什么“把重要上下文放前面”或“在结尾重复关键信息”这类提示工程技巧真的有用。模型并不是同样好地使用 prompt 的每一部分。

token 的含义和位置都编码好之后,下一个问题是:token 之间到底怎么交换信息?

注意力(Attention)

这就是赋予这个架构名字的机制:注意力。

在每一个 transformer 层里,注意力只做一件事:让每个 token 看一看它被允许看到的其他 token,并判断哪些对接下来更重要。

它通过同时给每个 token 分配三种角色来完成这件事。每个 token 被变换成三个新向量,分别叫 Query、Key、Value(Q、K、V)。

小科普:Q、K、V

Query 表示“我在找什么”,Key 表示“我能和什么匹配”,Value 是当匹配很强时被复制过去的信息。

  • Query 问:“我想从其他 token 那里得到什么?”
  • Key 说:“这是我能提供给来看我的 token 的东西。”
  • Value 带着:“这是匹配发生时要传过去的内容。”

同一个 token 同时扮演这三种角色。Q、K、V 的变换是学习到的矩阵,所以模型在训练中自己搞清楚每个 token 该找什么、该提供什么。

匹配通过相似度分数完成。每个 token 的 Query 与它能看到的所有 token 的 Key 做比较,用缩放点积衡量。直觉上,这度量两个向量有多对齐。缩放是为了在 softmax 之前让数值保持稳定。

小科普:点积

点积是一种衡量两个向量对齐程度的简单方法。对齐越高,匹配越强。

然后这些匹配分数经过 softmax 转成权重。softmax 把任意一组数转成一种类概率分布,加起来等于 1。匹配分高的 token 权重大,权重再用来对 value 向量做加权平均。

小科普:softmax

softmax 把原始分数转成加起来等于 1 的权重。大分数得大权重,小分数得小权重。

举个例子。考虑句子 “The cat that I saw yesterday was sleeping.”。模型处理 “was” 时,需要搞清楚是谁在睡觉。“was” 的 Query 向量被拿来和它能看到的所有 token 的 Key 比较。和 “cat” 的点积很高,因为模型学到 “was” 这样的动词需要主语,而 “cat” 这样的主语会产生与之对齐得好的 Key 向量。和 “yesterday” 的点积低。softmax 把这些分数转成权重,“cat” 权重高,“yesterday” 权重低。然后模型对相应的 value 向量做加权和,“cat” 的 value 主导了结果。“was” 的新表示主要由 “cat” 的 value 塑造。这就是一个相隔好几个位置的 token 成为指代对象的方式。

GPT 式语言模型有一个特殊约束:它们从左到右生成文本。位置 5 的 token 只能注意到位置 1 到 5,不能注意位置 6、7、8,因为那些 token 还没生成。这叫因果掩码(causal masking)。实现很简单:未来 token 的匹配分被设得极低,softmax 之后权重几乎为零。

小科普:因果掩码

因果掩码把未来的 token 隐藏起来。它让纯解码器语言模型在预测下一个 token 时不能往前偷看。

注意力热图,体现因果掩码和对 cat 的高注意力

可解释性研究中一个很有意思的发现,是关于一种专门化的注意力头,叫归纳头(induction heads),由 Anthropic 在 2022 年发现。这些头学会识别 prompt 中形如 “A B … A” 的模式,并预测接下来是 B。当模型第二次看到 “A” 时,归纳头回溯到 “A” 上一次出现的位置,看后面跟着什么,然后复制过来。它们是上下文学习背后最清晰的已知机制之一——所谓上下文学习,就是 LLM 能从你的 prompt 里抓住一个模式并继续下去的能力。

小科普:归纳头

归纳头是一种会注意 prompt 中重复模式并帮助延续它的注意力头。

注意力有一个大代价。在完整注意力中,每个 token 都要和它能看到的全部 token 比较,所以 prompt 长度翻倍,工作量大约变成四倍。这就是长 prompt 跑起来贵的原因,也是近期大量研究集中在让注意力更高效上的原因(FlashAttention、稀疏注意力、线性注意力)。

但单个注意力头只给模型一种关于这些关系的学习视角。

单次注意力只给模型一种判断“哪些 token 对哪些 token 重要”的方式。这不够。语言里同时发生着许多关系:主谓一致、代词与其指代的名字、跨句的长程指代、词序与局部短语。

多头注意力通过并行多次运行注意力来解决这个问题,每次并行在各自更小的空间里运算。每次并行叫一个头。

小科普:注意力头

注意力头是一次独立的注意力运算,有自己学习到的投影。

这里有一个常被讲错的地方,包括很多教程也讲错。每个头并不是从原始 token 向量里切下一块字面意义上的切片。每个头有自己学习到的投影矩阵,把完整的 token 向量投影到它自己更小的 Q、K、V 向量空间。所以如果一个模型每个 token 有 4096 个数、32 个头,每个头通常在 128 维空间里工作,但那 128 个数是对完整 4096 维的学习投影,而不是固定的切片。是同一个 token 的不同“视角”,不是它的不同块。

每个头独立跑自己的注意力,然后所有头的输出被拼接起来,过最后一个线性层,混合回一个完整大小的向量。这个最终混合也是模型学出来的。

多头注意力把专门化的注意力头组合起来

有意思的是,不同的头往往会部分专门化。模型从没被告知每个头该做什么,专门化是训练中自然涌现的。研究者发现有的头追踪语法(把动词和宾语连接、冠词和名词连接),有的头搞清代词指代哪个名字,有的头追踪位置模式,还有归纳头等等。一个 transformer 层可能有 32 个头,一个现代前沿模型有几十层。所以一个典型的 LLM 总共有几千个注意力头,每个都贡献自己学到的视角。

有一个实际成本问题推动了一次近期的架构变化。每个头需要把已经生成的所有 token 的 Key 和 Value 向量留在内存里,这样生成新 token 时就不必从头重新计算一切。这叫 KV 缓存,是长上下文下运行 LLM 的主要内存开销。

小科普:KV 缓存

KV 缓存在生成过程中保存旧的 Key 和 Value 向量。它让模型每加一个 token 都不必重算整个 prompt。

现代纯解码器 LLM 大多用一种变体,叫分组查询注意力(Grouped-Query Attention,GQA)。不是每个头都有自己的 key 和 value,而是几组头共享同一组 key/value 头。LLaMA-2 70B 有 64 个 query 头但只有 8 个 key/value 头,Mistral 7B 有 32 个 query 头和 8 个 key/value 头。结果是与完整多头注意力几乎一样的精度,但内存压力和推理成本低得多。

小科普:GQA

分组查询注意力让多个 query 头共享更少的 key/value 头。这削减了 KV 缓存内存,同时保留很多 query 视角。

前馈网络

注意力完成 token 之间的信息混合后,每一层还有第二步,没什么人提:前馈网络。

注意力是 token 之间互相交流,前馈网络则是每个 token 各自做进一步处理。它独立地作用于每个 token 的向量,没有跨 token 的混合。

前馈网络按顺序做三件事:

  1. 把 token 的向量扩展到更大尺寸(原始 transformer 用 4 倍,现代 SwiGLU 模型常用不同的扩展尺寸)。
  2. 应用一个非线性函数。
  3. 把向量压缩回原来的尺寸。

前馈网络对每个 token 向量做扩展、变换、压缩

中间那个非线性步骤做的事很具体,值得理解一下。非线性是一个会把输入“弯曲”的函数。最简单的 ReLU 对任何负数输出零,对正数原样输出。

小科普:非线性

非线性是一种防止网络坍缩成一个大线性变换的函数。

没有它,FFN 就只是两个线性层叠在一起,而纯线性数学堆叠会坍缩。两层线性在数学上等价于一层线性,一百层线性仍然等价于一层。非线性阻止了这种坍缩,也是 FFN 能比单次矩阵乘法做更丰富事情的原因。

原始 transformer 用 ReLU,GPT 和 BERT 改用 GELU,LLaMA、Mistral、PaLM 等现代模型用 SwiGLU。“扩展再压缩”的结构没变,变的是非线性本身。

稠密 transformer 模型的大部分参数并不在注意力里,而在 FFN 里。很大一部分权重都位于前馈层。

而且这些参数不是通用的。模型中存储的大量事实和语义结构就位于这里。研究者发现 FFN 内部某些神经元与特定概念或事实强烈相关。一个神经元可能在涉及埃菲尔铁塔的文本上强烈激活,另一个在编程语言上,另一个在动词过去式上。当模型“知道”巴黎是法国首都时,这个事实就分布在特定层的 FFN 权重和激活里。

这种存储式记忆属性带来一个有趣后果。研究者已经搞清楚如何在不重新训练的情况下,直接编辑已训练模型中的某些事实。像 ROME(Rank-One Model Editing)这样的方法,能通过对特定 FFN 权重矩阵做一个低秩定向编辑,把“埃菲尔铁塔在巴黎”改成“埃菲尔铁塔在罗马”。改完之后,模型往往就会生成与编辑后的关联一致的文本。

一些现代前沿模型已经开始用一种叫混合专家(Mixture of Experts,MoE)的结构替换稠密 FFN。每一层不再只有一个前馈网络,而是有多个并行 FFN(叫专家),加一个微型路由网络来挑选哪些专家处理每个 token。Mixtral 8x7B 每层 8 个专家,任一 token 只激活 2 个。总参数量大幅上升,但每个 token 的算力增长慢得多,因为只有少数专家在跑。这就是在扩大参数量的同时不让推理成本等比例增长的方式。

小科普:MoE

混合专家指模型有多个前馈网络,每个 token 只被路由到其中几个。

Mixtral 8x7B 总参数 467 亿,但每个 token 只用约 129 亿。这已经成为超大型模型的常见选择,因为它让你能继续放大参数量而推理成本不随之等比增长。

残差流与层归一化

残差流让模型是“累加”而不是“替换”。注意力跑完之后,或前馈网络跑完之后,结果通常不替换 token 的向量,而是加到它上面。逐位置地加。新向量等于旧向量加上子块的输出。

小科普:残差连接

残差连接把一个块的输出加回它开始时的向量。它给信息和梯度在网络中提供了一条捷径。

跨三十、五十、一百层,每层的贡献是累积的,而不是简单覆盖前一个向量。这个累加和叫残差流,它有一个奇怪性质:原始输入嵌入仍然有一条直接的加法路径通向后面的层,沿途与每个子块的贡献混在一起。

残差流累积注意力和前馈的输出

残差连接不是为 transformer 发明的,它来自 ResNet(何等人,2015),最初用于图像识别。动机是:深网络根本训不动。训练信号反向传过很多层后会变得太弱(有时太强),模型实际上无法从自己的错误中学习。加一条捷径让信号能从输出直接流回输入,于是突然间你能训几百层的网络了。transformer 继承了这一招。

在现代可解释性研究中,残差流成了核心对象。每个组件、每个注意力头、每个前馈网络,甚至最后的反嵌入步骤,都从残差流读、也写回残差流。

第二块,层归一化,存在的原因务实得多。没有它,残差流不会稳定。数字经过几十次相加要么向上爆炸,要么向零坍缩,无论哪种训练都失败。层归一化在每个子块之间把每个 token 的向量重新缩放回一个受控范围。

小科普:层归一化

层归一化重新缩放 token 向量,让它的数值在训练时保持在稳定范围内。

2017 年原版 transformer 在每个子块之后做归一化(post-norm)。这对浅层模型有效,但深度增加后训练稳定性变差。现代 transformer(GPT-2 之后、LLaMA、Mistral)通常在每个子块之前做归一化(pre-norm)。这是让很深的 transformer 更容易训的改动之一。

归一化函数本身也变了。很多现代开源模型(LLaMA、Mistral、Gemma、Phi)用一个更简单的变体叫 RMSNorm。原版层归一化同时做两件事:把每个向量朝零平移,再缩放数值大小。RMSNorm 丢掉平移步骤,只保留缩放。经验上,缩放带来了大部分好处,而且计算更便宜。

小科普:RMSNorm

RMSNorm 是更便宜的归一化方法,它缩放向量大小但不先减去均值。

这就是那些不起眼的机制。没有残差连接,很深的模型难训得多;没有层归一化,累加和会爆炸或坍缩。两者都有,你就能训出几百层深的模型。

预测下一个 token

所有注意力层和前馈层处理完之后,模型对序列中每个 token 都有一个向量。生成时为了预测下一个词,它只取最后一个 token 的最终向量。

这最后一个向量被转换成每个可能的下一个 token 对应一个数。如果词表有 10 万个 token,那就是 10 万个数。这些数叫 logits,还不是概率,可以是任意大小,正负皆可。

小科普:logits

logits 是每个可能的下一个 token 的原始分数。只有经过 softmax 才变成概率。

softmax 把这些 logits 转成模型在可能下一个 token 上的概率分布。和之前的操作一样,只是出现在模型的不同位置。

模型通常不会每次都挑概率最高的那个 token。解码设置控制输出的确定性与多样性。温度改变分布的尖锐程度,top-k 和 top-p 把选择限制在最靠谱的几个 token 上。这就是同一个模型在一个设置下显得精确、在另一个设置下显得有创意的原因。

小科普:温度

温度控制采样时的随机性。低温让模型更保守,高温让它更多样。

挑出一个 token 后,它被加到输入里。模型在更长的序列上跑下一步,通常复用 KV 缓存,不必从头重算整个前缀。新 token 做新的注意力,新的前馈,新的最终向量,新的预测。循环继续,直到模型发出序列结束 token 或撞到长度上限。一整段话就是这样一个 token 一次地循环出来的。

预测下一个 token 这单一目标,就是基础 LLM 的核心训练信号。基础模型并不直接训练事实准确性、对话能力、推理或编程,它训练的是在海量文本里预测下一个 token。随后的后训练再针对指令遵循、偏好、安全、对话行为做调优。

有一项重要的效率创新值得知道,叫投机解码(speculative decoding)。一个小而快的模型提前猜几个 token,大模型并行地验证它们。如果猜的 token 在大模型的概率下被接受,就采用;否则回退到大模型。做得对的话,输出分布与单独跑大模型一致,但循环可以快得多。

小科普:投机解码

投机解码用一个小草稿模型提前猜,然后让大模型一次验证多个猜的 token。

预测下一个 token 的循环是架构里最简单的部分,但正是它让整个东西跑起来。

架构 vs 训练得到的权重

我们已经走过了核心机制:token、嵌入、位置编码、注意力、多头注意力、前馈网络、残差流与归一化,以及输出端的下一个 token 循环。这就是一遍走下来的基本架构。

那么 GPT、Claude、Gemini、LLaMA 之间到底有什么不同?公开信息有限,专有模型并未公布每一个架构选择。但在本文覆盖的层面,它们大体落在同一个 transformer 家族的设计空间里。

大多数基于 transformer 的现代 LLM 用的是同样的整体结构:分词、嵌入、位置编码、堆叠的 transformer 层(每层含多头注意力和一个前馈网络)、残差流、层归一化、下一个 token 预测。

模型之间变化的是:

  1. 训练得到的权重本身,在不同规模下从不同训练数据学得。
  2. 配置:层数、词表大小、头数、参数量、MoE 还是稠密。
  3. 后训练:指令调优、人类反馈学习、在基础模型之上施加的安全控制。

小科普:权重

权重是模型内部学到的数字。训练不断改变这些数字,直到模型能很好地预测文本。

2023 到 2025 年的“现代 transformer”技术栈,在许多严肃的前沿与开源权重模型上收敛到了一组共同选择,尽管不同团队是独立抵达的:pre-norm 摆放、RMSNorm、RoPE、SwiGLU、分组查询注意力、在一些最大模型里的混合专家。这些都不是一次发明的,而是在 2017 年原版设计之上约五年迭代累积而来。

接下来会怎样

transformer 家族架构的这种收敛,在机器学习史上并不寻常。在这个领域的大部分时间里,每个问题都有自己的专用网络:图像识别用一种,语言用另一种,音频用第三种,视觉团队和语言团队几乎不共享方法。

现在 transformer 式模型横跨语言、视觉、音频、多模态系统。transformer 吞下了这个领域的一大块。

这可能会变。Mamba 等状态空间模型是可信的替代方案,尤其对超长序列。混合架构在被探索。混合专家已经改变了前沿上“架构”的含义,方式在五年前会被视为异类。

但本文里的核心机制(token、嵌入、位置编码、注意力、前馈网络、残差流与归一化、下一个 token 预测)是持久的部分。即使架构变了,这些也是任何序列模型在某种形式上都必须解决的问题。

如果你读到这里,你就能读很多现代 transformer 论文或模型卡,知道每一节在讲哪一块。这就是目标。

非常欢迎反馈。如果其中任何内容让你感兴趣,请在 X 上联系我。我喜欢交新朋友。

评论