今天读到一篇深度技术好文《How LLMs Actually Work》,它完美绕开了复杂的数学公式,用极其直观的方式把大语言模型(LLM)的工作原理拆解清楚了。如果你一直觉得LLM像个黑盒、难以理解,那这篇文章正好能帮你打开它——前提是别被那些专业术语吓到,我们来一句一句拆开看。
一张图就能说明白:LLM要理解一句话并生成回答,大致需要经历这么几步——先把文本切分成 token ID,再映射成高维向量,加入位置信息编码,通过多层 Transformer 让 token 之间互相交换语义信息,做局部和全局变换,最后把最后一个 token 的向量映射成下一个 token 的概率分布,然后一个 token 一个 token 地采样生成完整回答。听起来有点绕?别急,我们一步一步来。
1. Tokenization:模型不读文字,只读整数
这个环节非常关键:LLM 并不直接处理自然语言文字。它首先通过 tokenizer(分词器)把文本变成一串整数 ID,每个整数对应词表里的一个 token。注意,token 不一定是单词,也不一定是字符,更多时候是子词(subword)。比如tokenization可能会被切分成["token", "ization"],running会被切分成["run", "ning"]。为什么要这么设计?因为如果直接用整个单词作词表,词表会大到爆炸,而且遇到新词时泛化能力很差;如果降到字符级别,词表虽然小了,但序列长度会变得非常长,导致计算成本飙升。因此,subword 方案是一种优选的折中策略。
这也正好解释了 LLM 中的一个经典现象:不同模型家族的 tokenizer 各不相同。GPT 系列常用 BPE(字节对编码)变体,LLaMA 风格模型则偏爱 SentencePiece 算法。简单来说,tokenizer 就像是 LLM 世界的“输入法 + 压缩器 + 词表协议”,它直接决定了模型如何“看待”你输入的每一个字。
2. Embeddings:整数本身没意义,要变成向量
上面提到的 token ID 只是一个索引,比如1024这个数字本身不代表任何语义。模型真正处理的是 embedding(嵌入)——从 embedding matrix(嵌入矩阵)里查出来的一行高维向量。看到“hidden size = 4096”时不必慌张,我们可以把它理解成模型给每个 token 画了一张“含义雷达图”。
比如 token 是“苹果”,人看到这个词会联想到:水果、甜的、红色/绿色、Apple公司、手机、乔布斯、可以吃、科技品牌……但模型不会用中文标签写这些特征,它会把“苹果”变成一串数字,大约长这样:[0.12, -0.48, 0.03, 1.27, ...],一共 4096 个数字。这串数字不是随机的,而是训练出来的“含义坐标”。如果只用一维描述,“苹果=8,香蕉=7,手机=3”;二维的话就是“甜度、科技感”两个轴——苹果水果可能是甜度9、科技感1,而 Apple 公司则是甜度0、科技感10。4096 维就是超级复杂的雷达图,可以同时看几千个隐含方向:是不是名词?是不是品牌?是不是食物?和水果相关程度?和手机相关程度?和颜色相关程度?和上下文相关程度?等等。当然,真实维度不是人工命名的,但理解思路就是这样的。
还有一点需要注意:embedding 不是静态的。随着 Transformer 一层层处理,这个 4096 维向量会不断变化。经典的例子是king - man + woman ≈ queen,这正是向量空间中语义关系的体现。不过,embedding 只管语义,不管位置——“dog”出现在句首还是句尾,查出来的基础 embedding 是一样的,所以才需要后续的“位置编码”。
3. Positional Encoding:模型怎么知道顺序?
纯 self-attention(自注意力)机制本身并不天然知道词序。如果不给它位置信息,它就无法区分“dog bites man”和“man bites dog”。因此,模型必须把 token 的“位置”注入进去。早期的方法是把位置信息直接加进 embedding 里,但这种方法有两个问题:一是语义和位置被塞进同一组数字里,容量有限;二是绝对位置 embedding 对长上下文泛化不好——如果模型训练时只见过 2048 长度的序列,位置 5000 处的特征就没有被充分学习过。
现代 LLM 大多使用 RoPE(旋转位置编码,Rotary Position Embeddings)。它不是把位置向量加到 embedding 上,而是在 attention 的计算过程中旋转 Query 和 Key 向量,旋转的角度取决于 token 的位置。这样做的好处是能更好地处理相对位置关系,也更适合长上下文场景。
不过,即使用了 RoPE,LLM 仍然有一个“lost in the middle”(迷失在中间)的问题:长 prompt 中开头和结尾的信息更容易被模型利用,中间部分经常被忽略。所以,不要以为上下文长了就万事大吉——能塞进去,不代表模型能同等权重地用上。
4. Attention:token 之间怎么交换信息?
每个 token 会被投影成三种向量:Query(我在找什么?)、Key(我能被什么匹配?)、Value(如果我被匹配上,我传递什么信息?)。Query 和 Key 做点积,得到匹配分数,再通过 softmax 变成权重,最后用这些权重对 Value 做加权平均——高权重的 token 的 Value 会更强地影响当前 token 的新表示。
说人话就是:每个 token 都会去上下文里“找相关信息”,先判断谁和自己最相关,再按相关程度把它们的语义信息混合进自己。Query 负责提问,Key 负责被匹配,Value 负责提供内容,匹配度越高,内容影响越大。
举个例子:句子"The cat that I saw yesterday was sleeping."当模型处理"was"时,它需要知道谁"was sleeping"。"was"的 Query 会和前面 token 的 Key 做比较——和"cat"的匹配度较高,和"yesterday"的匹配度较低。softmax 后"cat"得到更高权重,于是"cat"的 Value 对"was"的新表示影响更大,模型就能把几个位置之前的主语关联过来。
对于 GPT 风格的 decoder-only 模型,还有一个限制:生成是从左到右的,所以当前位置不能看未来的 token,这叫做因果掩码(causal masking)。实现上就是把未来 token 的注意力分数设得极低,softmax 后权重几乎为 0。
另外,attention 的计算代价很高。在 full attention(全注意力)模式中,每个 token 要和所有可见 token 做比较,所以 prompt 长度翻倍,计算量大致会变成四倍——这就是长上下文场景下推理成本高、速度慢、资源占用量大的底层原因之一,也催生了 FlashAttention、sparse attention(稀疏注意力)、linear attention(线性注意力)等研究方向。
5. Multi-head Attention
单个 attention head 只能学一种关系,但语言里同时存在很多种关系:主谓一致、代词指代、局部短语、长距离引用、位置模式……所以 Transformer 会并行运行多个 attention head。每个 head 不是简单拿原始 token 向量的一小段切片,而是用自己的一套“滤镜”,从完整的 4096 维向量里提取它关心的 128 维信息。
每个 head 独立做 attention,输出再拼接起来,经过一个最终线性层混合回完整向量。这种多头设计让模型能同时捕捉多种不同的语义关系。
这里有一个实际工程上的重要概念:KV cache(键值缓存)。在生成时,模型不希望每生成一个 token 就重算整个 prefix 的注意力,所以会缓存过去 token 的 Key 和 Value。新 token 到来时,可以直接复用旧的 K/V。但 KV cache 是长上下文推理的主要内存成本之一。现代 decoder-only LLM 常用 GQA(分组查询注意力,Grouped-Query Attention),让多个 query head 共享较少数量的 key/value head,从而有效减少 KV cache 的内存压力。
打个比方:正常多头注意力可以理解为 32 个 head = 32 套 Query + 32 套 Key + 32 套 Value。问题是生成时要缓存历史 K/V,上下文越长、层数越多、Key/Value 头越多,占用的显存就越大。GQA 改成:32 个 Query head 但只有 8 个 Key/Value head,每 4 个 Query head 共用 1 组 Key/Value。这就像公司里多个项目组共享同一套后勤团队,既保留了不同的观察角度,又大幅减少了需要缓存的数据量。实际例子:LLaMA-2 70B 有 64 个 query heads 但只有 8 个 key/value heads,Mistral 7B 则是 32 个 query heads 和 8 个 key/value heads。
6. Feed-Forward Network
前馈网络(FFN,Feed-Forward Network)是模型“存储知识”的重要大仓库之一。Attention 负责 token 之间的信息交换,但每个 Transformer layer 还有一个重要的模块:FFN。Attention 是 token 之间“互相看”,而 FFN 是每个 token 独立做进一步加工,不混合其他 token。
FFN 通常有三步:先把向量从一个较小的维度(比如 4096)扩展到更大的维度(比如 11008),然后通过一个非线性激活函数做选择,最后压缩回原来的维度。这个过程可以理解成:Attention 负责“从上下文里找信息”,FFN 负责“把找到的信息在自己脑子里再加工一遍”。
- Attention:这个 token 去看别的 token——“我该参考谁?”“前面哪个词和我有关?”“这个代词指的是谁?”
- FFN:这个 token 不再看别人,而是在自己内部加工——“结合刚才拿到的信息,我现在应该更像什么?”“我是水果苹果,还是 Apple 公司?”“我是变量名,还是普通英文单词?”
简单说:Attention 是信息搬运/上下文关联,FFN 是信息加工/知识变换。原始 Transformer 用 ReLU,GPT/BERT 常用 GELU,现代 LLaMA、Mistral、PaLM 等常用 SwiGLU。核心结构仍然是 expand-transform-compress(扩展-变换-压缩),变化主要在非线性函数上。
值得注意的是,dense transformer 里大量参数其实分布在 FFN,而不是 attention。FFN 里存储着很多模型的事实和语义结构,一些 neuron(神经元)会对特定概念产生强激活,比如埃菲尔铁塔、编程语言、过去时动词等。而 MoE(混合专家模型,Mixture of Experts)不是每层只有一个 FFN,而是有多个 expert FFN,再由 router(路由器)为每个 token 选择少数几个 expert,这样可以在不增加计算量的情况下大幅扩展模型容量。
7. Residual Stream 和 LayerNorm
Transformer 里不是每层都把旧表示完全替换掉,而是把 attention 或 FFN 的输出加回原向量:new vector = old vector + block output。这叫做残差连接(residual connection)。多层堆叠后,每层贡献都会累积在一个持续流动的表示里,这个运行中的总和就是残差流(residual stream)。
例如,一个 token 一开始只是“苹果”。经过 attention 后,它可能从上下文里拿到信息——“前面刚提到了 Apple 公司”。这一层不会把“苹果”原来的表示整个删掉,而是把新信息加进去,让它越来越像“Apple 公司”。再经过下一层 FFN,它又补上一些更深的知识——“Apple 公司总部在硅谷”。残差连接就像给信息修了一条高速公路,让深层网络能够顺利地把信号传递下去。
这个技巧来自 ResNet,最初是为了解决深层图像网络训练困难的问题。残差连接解决的是“深层网络怎么把信息和训练信号一路传下去”,而 LayerNorm(层归一化)/RMSNorm(均方根归一化)解决的是“传下去的时候,数值不会出问题”。
- 残差连接让 Transformer 每层只做“增量修改”,旧信息可以沿着 residual stream 一路传下去,所以深层网络更容易训练。
- 残差流就像模型内部的公共黑板,attention、FFN 和最终输出层都在上面读写。
- LayerNorm/RMSNorm 则是数值稳定器,防止这块黑板上的向量经过几十层相加后爆炸或塌缩。
8. Next-token Prediction
所有 Transformer 层处理完后,模型会得到每个 token 的最终向量。生成下一个 token 时,模型通常只取最后一个 token 的最终向量,把它映射成词表大小的一组分数——比如词表有 100,000 个 token,就得到 100,000 个 raw scores,这些叫做 logits。
logits 还不是概率,经过 softmax 后才能变成“下一个 token 是每个候选 token 的概率分布”。模型通常不会永远选概率最高的 token,temperature、top-k、top-p 等解码参数会影响采样行为:低 temperature 更保守、更确定,高 temperature 更随机、更发散,top-k/top-p 会限制候选 token 集合,只从比较合理的范围里采样。
一旦选出一个 token,它就被追加到输入后面。模型再基于更长的序列预测下一个 token,这个循环一直持续,直到生成 EOS token 或达到长度限制。整段回答就是这个循环反复运行的结果。
举个例子:当前输入是“苹果发布了新”,模型处理完所有 Transformer 层后,会拿最后一个 token“新”的最终向量去预测后面最可能接什么。它不会直接说“芯片”,而是先给整个词表里的所有 token 打分:“芯片:12.8”“手机:10.4”“产品:9.7”“系统:8.9”“电影:-3.2”“香蕉:-6.5”……这些原始分数就是 logits。softmax 后变成概率:“芯片:45%”“手机:18%”“产品:12%”“系统:8%”……然后模型才开始“选下一个 token”。temperature 控制模型敢不敢冒险,top-k 是只看前 k 个候选,top-p 是只看累计概率达到 p 的候选集合。
基础 LLM 的核心训练目标就是 next-token prediction(下一个 token 预测)——它并不是直接以“事实正确”“会聊天”“会推理”“会写代码”为目标训练的,而是在海量文本上学习预测下一个 token。后训练(指令微调、RLHF 等)才让模型变得像助手,但底层生成机制仍然是基于概率分布逐 token 采样。
顺便一提,有些模型会采用推测解码(speculative decoding):小模型先快速草拟多个 token,大模型并行验证,如果草拟的 token 在大模型概率下可接受就直接采用,否则回退到大模型生成。做得好时,输出分布可以等价于单独运行大模型,但速度快得多。
9. Architecture vs Weights
最后聊聊 GPT、Claude、Gemini、LLaMA 这些模型到底差在哪里。闭源模型不会公布所有结构选择,但在这篇文章讨论的层面,它们大多都处于 Transformer-family(Transformer 家族)的设计空间中。现代 Transformer LLM 的共同骨架大体包括前面提到的那些东西:tokenization、embedding、positional encoding、stacked transformer layers、multi-head attention、feed-forward network、residual stream、normalization、next-token prediction。
真正让模型不同的主要来自三个方面:
- 第一,训练出来的权重(weights)——不同数据、规模、训练流程学出来的参数。
- 第二,配置差异——层数、词表大小、head 数、参数量、dense 还是 MoE 等。
- 第三,后训练(post-training)——指令微调、人类偏好学习、安全控制、对话行为塑造等。
最后
简单总结几个关键点:
- token 是一切的入口。模型不是按人类自然理解的字、词、句直接思考,而是处理 tokenizer 给出的 ID 序列。tokenization 会影响成本、长上下文、多语言表现、字符级任务表现。
- embedding 是语义空间。token ID 通过 embedding matrix 变成向量,语义关系是训练中学出来的几何结构。
- RoPE 不是简单“告诉模型第几个词”,而是在 attention 的 Q/K 比较中编码相对位置,这也是它比早期绝对位置 embedding 更适合现代长上下文模型的原因。
- attention 是信息路由,不是知识本体。它决定当前 token 应该从哪些上下文 token 吸收信息,但 dense 模型中大量参数和事实结构其实在 FFN 里。
- KV cache 是推理成本的核心之一。很多人只知道“上下文越长越贵”,但原因不只是输入 token 变多,还包括每层要保存历史 K/V,生成时持续复用。
- FFN 很可能是“模型知识仓库”的重要部分。事实、概念、语义结构大量分布在 FFN 权重和激活中,MoE 本质上也是围绕 FFN 容量和计算成本做稀疏扩展。
- 残差流是现代 interpretability(可解释性)的核心对象。每个模块都在 residual stream 上读写,模型不是一层层覆盖旧信息,而是在不断累积和修改表示。
- base model 的训练目标不是“正确回答”,而是 next-token prediction。后训练让模型变得像助手,但底层生成机制仍然是基于概率分布逐 token 采样。
