按依赖顺序讲解 token、attention、MoE 等 LLM 核心概念,避免传统字典式查询的反复跳转。面向非 ML 背景工程师编写,用网络和控制论类比降低理解门槛。
简要版 — 一份词汇表,让你真正理解阅读 LLM 相关内容时碰到的术语:token(令牌)、embedding(嵌入)、attention(注意力)、KV cache、GQA、MoE(混合专家)、quantization(量化)等等。但这不是按字母顺序排列的 — 而是按依赖关系排列的:每个条目只用到前面已经解释过的概念,所以如果你从头到尾读一遍,当你读到"混合专家"时,你已经掌握了所有的核心概念。类比来自这个博客周围的人们的世界:网络、缓存、PID 控制器、调优。除了"我知道什么是数组"之外,无需数学基础。
按字母顺序的 LLM 词汇表有个结构性缺陷:'attention' 条目把你送到 'softmax',它在下面一些,而 'softmax' 又用到 'logits','logits' 在前面但需要理解 'vocabulary'。你最后会像凌晨两点浏览维基百科一样四处跳跃。
这份文件采用了相反的方式构建:像一条链。这是我希望拥有的词汇表,这些类比是让概念对我豁然开朗的方式 — 我来自系统管理、网络和控制系统领域,而不是 ML 博士。如果你也用缓存、错误信号和控制回路的方式思考,那你来对地方了。
开始前先说句老实话。这份词汇表不是来自我已有的知识。它来自我向 LLM 提出的问题 — "给我解释一下 KV cache"、"为什么是那个 √d?" — 以及它为我写下的解释。有些理解了,有些我还在咀嚼:我不能完全掌握这里的一切,也不会假装可以。我需要的是一个单一的地方,可以随时回头翻 — 当我在阅读相关主题的东西时碰到这些术语时,可以重新打开笔记,慢慢重读,用那些对我有效的类比来理解它。我把它放在这里,希望对你也有用;但首先和最重要的是,它是我自己的。
为了脚踏实地,我会用一个小但具有现代架构的模型的数字作为贯穿始终的线索:1.8 亿个参数、32,768 的令牌词汇表、内部大小 640、20 层。足够小可以在普通硬件上训练,足够现代包含 2026 年所有重要的概念。
整个游戏就是一件事:给定一个文本序列,预测下一个片段。"The cat is on the" → 可能是 "roof" 或 "couch",不太可能是 "carburetor"。LLM 是一个巨大的函数,接收文本并返回下一个片段的概率分布。一切其他的 — 聊天、推理、代码 — 都来自于这个任务在人类文本上重复数十亿次。当它"生成"时,模型预测一个片段,把它追加到输入中,预测下一个,以此类推。就像自动补全被推向了荒谬的极端。
上面的"片段"。不是一个词也不是一个字符:介于两者之间的东西,一个常见的片段。"computing" 可能是一个单独的令牌;"quadcopter" 可能分割成 "quad"+"cop"+"ter"。分词器是将文本和整数序列相互转换的组件(每个令牌有一个 ID)。想象一个查找表:令牌 4521 = " cat"。词汇表是所有已知令牌的集合 — 在我们的参考模型中,32,768 个。
类比:它是一种字典压缩形式,在概念上是 LZ 的表亲 — 频繁出现的序列变成单个符号。
构建该字典的算法:在现代字节级变体中,你从单个字节开始,在语料库中找到最常见的相邻对,将其合并为一个新符号,重复直到达到所需的词汇表大小。纯粹的统计,没有语言魔法。
向量是浮点数数组。矩阵是二维数组。张量是推广到 N 维 — 在 PyTorch 代码中你看到的形状像 (B, T, D) = (批次、序列中的位置、每个位置的维度)。没有什么神秘的:多维数组和向量化操作,像 NumPy 一样。
所有深度学习的基本操作:y = W @ x。取一个向量 x,乘以一个权重矩阵 W,得到一个新向量 y。y 的每个元素都是 x 所有元素的加权组合。矩阵 = 从一个空间到另一个空间的一个学到的转换。LLM,简单粗暴地说,就是数百个这样的乘法连在一起,中间有一点非线性。当你读到"线性层"或 nn.Linear 时:就是这个。当你读到 GEMM:通用矩阵乘法,GPU 计算的内核。
那些矩阵内的数字。"1.8 亿参数模型" = 所有矩阵的所有元素之和是 1.8 亿个浮点数。正是这些参数在训练期间被修改 — 模型的"知识"完全存在于此。架构是印制电路板;参数是训练焊接的元件值。
令牌(整数)和数学(向量)之间的桥梁。一个表:第 4521 行 = 代表 " cat" 的 640 浮点向量。深的部分:在训练期间,这些向量按意义在几何上组织自己 — "cat" 和 "feline" 在这个 640 维空间中最后靠得很近,"cat" 和 "lathe" 很远。方向本身编码了语义关系。如果你曾经使用过 pgvector 或向量数据库进行语义搜索,这正是同样的概念 — 只是这里的嵌入是模型内部的,与所有其他内容一起学习。
模型的"宽度":有多少个浮点数代表每个令牌在网络中流动。在我们的参考模型中,640。每个令牌以 640 向量的形式进入,以 640 向量的形式从每一层退出(由上下文丰富),一直到最后。这是系统的数据总线。
矩阵乘法后,你逐元素应用非线性函数(ReLU、SiLU、GELU — 同样主题的变体:压缩或放过)。为什么需要它:纯线性操作的链在数学上会塌缩成一个线性操作 — 你什么复杂的东西都学不了。非线性是让网络能够表示任意函数的东西。当你在代码中看到 F.silu 时:就是这个。
一个函数,接收一个任意数字的向量("分数"或 logits)并将其转换为概率分布:都是正数,总和为 1,高分数占主导。这是选择下一个令牌之前的最后一步,也出现在注意力内部。Sigmoid 是它在单个情况下的表亲:它独立地将一个数字压缩到 (0,1)。
从输入到输出运行通过模型。文本 → 令牌 → 嵌入 → 20 层转换 → 下一个令牌的概率。只是计算,没有学习。
说"你有多错"的数字。对于 LLM,它是交叉熵:取训练文本中实际跟随的令牌,查看模型给它分配的概率,损失是该概率的 -log。如果模型给正确的令牌概率 0.9 → 低损失。如果给它 0.001 → 高损失。这是唯一的学习信号:所有训练都是"降低这个数字"。
从业者在第一步使用的健全性检查:新初始化的模型随机发射,对每个令牌的均匀概率 1/32,768,所以初始损失必须是 -log(1/32768) ≈ 10.4。如果从那里开始,初始化是健康的。你在评估中找到的困惑度只是 e^loss — "模型有效地在多少个令牌之间犹豫"。
梯度回答:"如果我将这个参数移动一个 epsilon,损失会上升还是下降,上升或下降多少?"。反向传播是算法(来自微积分的链式法则的系统应用),它在一次计算中为所有 1.8 亿个参数计算这个导数,从最终错误向后传播。在 PyTorch 中,它是 loss.backward() 这一行:自动的,你永远不需要手动实现。
一旦你知道往哪个方向移动每个参数,你就朝那个方向小步移动所有参数。重复数百万次。学习率(LR)是步长 — 单一最重要的超参数。太高:损失爆炸或振荡。太低:学习速度如蜗牛般。
对于任何接触过 PID 循环的人的类比:梯度是错误信号,学习率是增益。增益太高 = 振荡和崩溃,太低 = 反应迟钝。
优化器是使用梯度的策略:基本的(SGD)只是应用它们;AdamW 保持梯度(第一时刻)及其平方(第二时刻)的移动平均值 — 正是后者使得步长能够按参数自适应;Muon(2025 年的新来者)在使用矩阵梯度之前对其应用几何"清理"。现在不需要理解怎么做 — 只需要知道优化器是你如何下降的方式,以及有些下降速度更快。
你不会一次只处理一个样本:你把 N 个序列打包在一起(批处理),然后对梯度求平均。原因有两个:第一,GPU 靠并行计算生活。第二,在 50 万个 token 上平均的梯度比单个序列的梯度噪声小得多。梯度积累(代码中的 grad_accum)是一个技巧,当 VRAM 不足时用来模拟巨大的批处理:你累积 N 个微批处理的梯度,然后再更新参数。
Step = 一次参数更新。Epoch = 在数据集上的一次完整遍历(在 LLM 预训练中,历史上你通常甚至不会完成一个 epoch——数据集比计算预算更大;现在,随着精选的高质量数据,再看一遍数据是常见的)。Checkpoint = 参数到磁盘的一个快照,你的状态保存。
如果模型记忆了训练集而不是学会泛化,训练损失会下降,但在未见过的数据上不会。这就是为什么你保留一个验证集(从不展示过的数据)作为诚实的度量。这个现象还有一个反向用途,一个经典的健全性检查:故意过拟合单个批次。如果模型甚至不能记忆 32 个序列,那代码里有 bug。
你选择但没有学到的一切:学习率、维度、层数、批处理大小......超参数调优相当于在无人机上调参 Betaflight:有理论、有合理的起始值,然后就是经验。
一个 token 本身是模糊的("bank":河岸、银行、飞机侧飞?)。含义取决于上下文。架构问题是:位置 500 处的 token 表示如何包含前面 token 的信息?2017 年前的答案是循环网络(RNN),它按顺序读取并把所有内容压缩成一个状态——这是一个瓶颈,而且没有并行性。Transformer 的答案(2017 年,《Attention Is All You Need》)是注意力机制。
每个 token 向其他 token 的"数据库"发出"查询"。机制上,从每个 token 你推导出三个向量(三个矩阵乘法,经典的 wq、wk、wv):
Query (Q):"我在寻找什么"——比如 token "on" 寻找一个要附加到的主语
Key (K):"我作为搜索键提供什么"——比如 "cat" 把自己宣传为一个动物主语
Value (V):"如果我被选中会传递什么内容"
对于每个 token:取它的 Q 与所有前面 token 的 K 的点积(→ 亲和力得分),按 √(head 维度) 重新缩放(这里 √64 = 8)——没有它的话,大向量会使得分数增长,softmax 饱和,梯度消失——然后对得分做 softmax(→ 权重求和为 1),token 的新表示是 V 的加权平均。结果:每个 token "吸收" 来自相关 token 的信息,权重是学到的,内容相关的。
类比:哈希表上的查找,但是模糊的、可微分的——与其精确匹配 key,你得到与所有 key 的相似度得分,并按比例从它们全部取值。
在语言建模中,位置 t 的 token 只能看到位置 ≤ t——否则在训练期间它会"偷看"答案。因果掩码会把未来方向的注意力清零。在 PyTorch 代码中是 is_causal=True 标志。
与其用一个大向量做一个注意力,你用大向量的小份量并行做 N 个注意力("head"),然后拼接。每个 head 学会寻找不同的东西:一个追踪句法依赖,另一个追踪共指,等等。在我们的参考模型中:10 个 64 维的 head。
每一层的第二个组件:两个(或三个,用 SwiGLU)矩阵乘法,中间有非线性,独立地应用于每个 token。如果注意力是 token 互相交谈的地方,FFN 就是每个 token 独立处理自己收集内容的地方。这也是模型"事实知识"的大部分被认为所在的地方——这正是 MoE 倍增成专家的部分(我们稍后会讲到)。
一个 Transformer 块 = 注意力 + FFN,每个前面都有归一化。模型是相同块的堆栈(参考模型中 20 个)。关键细节:残差连接——每个组件的输出加到输入上,不是替换它(x = x + attn(...))。想象一条"传送带"(残差流)在层之间传送 token 的表示,其中每个块读取它并增加自己的贡献。没有这个,梯度无法通过 20 层反向流动(梯度消失),深度网络就不会训练。
在每个组件之前,你把 token 的向量重新缩放到标准的"幅度"。原因:数值稳定性——没有它,值的幅度会层层漂移直到它们爆炸或消失。类比:无线电链中的 AGC(自动增益控制),或阶段之间的信号均衡。RMSNorm 是今天使用的最小版本(只缩放,不居中)。"Pre-norm" = 你在每个组件的输入处归一化。
问题:注意力本身不知道 token 在哪里——这是一个集合上的运算,"the cat bites the dog" 和 "the dog bites the cat" 会得到相同的得分。你需要注入位置。现代方法是 RoPE:按 token 位置成比例的角度旋转 Q 和 K 向量(维度对 = 旋转平面,每对不同的频率)。优雅的结果:Q 和 K 之间的点积最终取决于 token 之间的相对距离,而不是绝对位置——正好是你对语言想要的。诚实的类比:相位编码——位置信息存在于信号的相位中,相位差给你距离。
在栈的末尾,最后一层(语言建模头,lm_head)把 token 的 640 维向量投影到 32,768:每个词汇 token 一个得分(logit)。Softmax → 概率 → 下一个 token 被绘制。温度(temperature)在 softmax 前除 logit:<1 使分布更集中(保守),>1 更平坦(有创意)。Top-p / nucleus 采样切割尾部:它只从累计到达概率 p 的最可能 token 中采样。
在生成期间,每个新 token,注意力计算重用所有前面 token 的 K 和 V——它们不会改变。每次都重新计算它们会很疯狂:你把它们保留在内存中。这就是 KV 缓存,它的大小是推理瓶颈(它随着 context × 层 × KV head × head_dim × 2 增长,用于 K 和 V)。当你读到 GQA、滑动窗口和 MLA 存在"来压缩 KV 缓存"时,现在你知道它是什么了:生成的工作内存。完美的类比:它是记忆化,纯粹缓存不可变的中间结果。
模型一次可以在视图中保留的最大 token 数:prompt 加上它已经生成的所有内容。它有两个不同的限制。一个学到的——RoPE 频率在训练中看到:超过那个长度模型不再能放置位置,质量会崩溃。一个物理的——KV 缓存:上下文翻倍,内存翻倍。在训练后扩展它是可能的(RoPE 缩放、YaRN:你重新缩放频率来"拉伸"窗口超过训练长度),但在质量上它从不完全免费。当你读到"128k context"时,就是这个——并且在它后面几乎总是有一个权衡。
在经典多 head 中每个 head 有自己的 K 和 V → 巨大的缓存。GQA:许多查询 head 共享少数 K/V head(比如 10 个查询 head,2 个 KV head)。缓存减少 5 倍,质量几乎相同。实际上是缓存去重。
大多数层把注意力限制到最后 512 个 token(一个滑动窗口)而不是整个 context;只有某些层看到一切(参考模型中的四分之一)。远处的信息仍然传播:层组合(第 2 层的窗口看到在第 1 层已经吸收了 context 的 token)。更少的计算,更少的缓存。
驯服 KV 缓存的第三种方法,在 GQA 和滑动窗口之后。MLA 不是为每个 head 缓存 K 和 V,而是把它们压缩到单个低秩潜在向量中,只缓存那个;需要时用乘法动态重构每个 head 的 K 和 V。缓存比 GQA 小得多,质量相当,代价是有点更多的计算。这是 DeepSeek-V2/V3 的选择。概念家族与 GQA 和滑动窗口相同:三种全部权衡计算或质量的一点点,来把更多 context 放进内存。
记住:FFN 是每个块的"单独处理"组件,持有大量参数。MoE 的想法是:每个块不再用一个 FFN,而是放 32 个专家和一个路由器——一个小线性层,为每个 token 选择 4 个最适合的专家。只有这 4 个计算。结果是:模型的容量与 32 个 FFN 相当(并非完全相同)——容量由总参数数决定——但计算成本只等于 4 个(计算 = 活跃参数)。专业化在训练过程中自然浮现:没人手动分配主题给专家。
类比:内容感知负载均衡器前跟着一个专业工人池——妙处在于路由和专业化在训练中共同演化。
围绕它的术语有:
细粒度:32 个小专家优于 8 个大专家——更多组合方案,更细致的专业化。
共享专家:一个始终活跃的专家用于通用知识,其他专家无需重复。
路由崩溃:病症——路由器收敛到只向 2-3 个专家发送数据(它们因此改进,获得更多流量:正反馈)。其他专家保持未训练状态:死参数。
无辅助损失平衡:由 DeepSeek 推广的解决方案。为每个专家的选择分数添加偏差:过载专家 → 偏差降低 → 被选中频率降低。它本质上是一个负载积分控制器——运行在训练之外的控制回路。如果你熟悉 PID 控制,会比普通 ML 工程师更好理解。
16 位浮点格式代替 32 位:内存减半,张量核心吞吐量翻倍。bf16 保持与 fp32 相同范围(牺牲精度),因此无需担心溢出。"混合"是因为精细操作(规范化、损失求和)仍在 fp32 进行。
torch.compile:一个 JIT,将操作融合为优化的 GPU 内核。FlashAttention:一个注意力实现,不会显式构建 T×T 分数矩阵(在 SRAM 中按块计算)——数学等价,内存从 O(T²) 降至 O(T)。MFU(模型 FLOPs 利用率):实际使用 GPU 理论 FLOPs 的比例——流水线效率指标。
预训练:对数十亿网页 token 的纯语言建模。产生知识渊博的"补全器"但非助手。
SFT(监督微调):在格式良好的对话示例上进行微调。教会问-答格式。
DPO/RLHF:基于偏好的优化——好/坏响应对。教授风格和对齐。
RLVR/GRPO:在可验证答案任务(数学、代码)上的强化学习。这是推理模型诞生之处。与 SFT 的关键区别:模型从自身尝试的奖励/惩罚中学习,而非从给定示例。
微调所有参数(我们的模型 180M,实际模型 70 亿)意味着在 VRAM 中同时保存权重、梯度和优化器状态——对消费级硬件来说不可行。LoRA 规避此问题:冻结基础权重,只在某些层旁添加小低秩矩阵(A·B,几千个参数)。大部分权重保持不变,你只修改 1% 却获得大部分效果。QLoRA 巧妙改进:基础权重量化到 4 位(冻结),在其上添加 LoRA 适配器——可在单个消费级 GPU 上微调 7B 模型。PEFT(参数高效微调)是包含它们的技术族。
源自 RLVR 的线索。想法是:让模型在回答前生成长推理链(思维链),通过 RLVR 训练生成高质量推理。结果是范式转变:计算花在推理而非仅在训练——更多"思考" token = 难问题更好答案。这是测试时计算:相同架构,但会"停下来思考"。推理模型(o1 / R1 风格)就是这样诞生。注意如何与其他因素相连:推理 token 填满上下文窗口,扩大 KV 缓存。对 LLM 来说,思考的代价是内存和延迟。
计算、参数和 token 的经验关系:对给定计算预算,有最优比例(约 20 token 每参数,"Chinchilla")。"过度训练" = 故意远超——小模型长期训练推理成本更低。这是资本支出与运营支出的权衡,2026 年推理运营成本占主导。
到此为止:模型如何学习。现在:如何运行和部署它。这是几乎所有术语表都忽视的部分,也是你在自己硬件上尝试运行模型时亲身面对的内容。
之前的 bf16 用于训练。推理时可更低:权重量化到 8 位或 4 位(有时更少)。7B 模型用 fp16 需约 14 GB 显存;4 位则需约 4 GB——这通常是"能运行"和"无法运行"的分界线。你会遇到的方法:GPTQ 和 AWQ(仅量化权重,在语料库上校准以最小化质量损失),GGUF(llama.cpp 的格式,具有混合精度 k-量化)。权衡总是一样的:更少位数、更少显存、质量略微下降——但低于 4 位时,那一丝问题就变成了绳子般粗的问题。
推理有两个特征相反的阶段。预填充阶段一次处理整个提示,并行执行:计算受限(大量矩阵乘法;第一个 token 生成)。解码逐个生成 token,每次都要重新读取整个 KV 缓存:受内存带宽限制——等的不是计算完成,而是数据到达。这就是为什么 FLOPs 相同的两个模型生成速度可能差异巨大,以及为什么推理优化的 90% 关乎优化内存访问。这一层的其他概念都源于此。
使现代推理服务(vLLM)高效的两个想法——对系统管理员来说这是自己的领地。PagedAttention:不是把每个请求的 KV 缓存分配为连续块(这会导致碎片化和浪费,就像朴素的内存分配器),而是将其分解为带转换表的非连续页面。这就是虚拟内存在 KV 缓存上的应用,仅此而已。连续批处理:不是等批中所有请求完成再接受新请求