资讯动态

从零手写大语言模型:注意力机制、训练到微调对齐的完整实践

发布时间:2026/10/18 19:50:41 来源:尧图企业网站定制
去年我翻过大语言模型相关的不少教程印象最深的是两种极端一种上来就是整页的矩阵求导推导过程堪比数学论文普通人看到第二行就开始犯困另一种则是“三行代码调接口”的速成教学跟着点完鼠标除了会照猫画虎底层的原理完全没进脑子。后来有人把一本开源电子书《Happy LLM从零开始的大语言模型原理与实践教程》丢给我说“这本书认真讲原理上手又能跑起来”。刚看到“从零开始”四个字我其实是有点警惕的毕竟这个词被用得太滥了。但真正翻开前两章之后我的判断变了这书的作者是真的在教人“从零开始”把大语言模型从文本切词到梯度回传、从注意力计算到采样生成的整条链路拆开揉碎带着你一步一步完成。这篇文章我想聊聊它的写法、它打动我的几个章节以及我在照着书中内容复现时踩到过的真实问题。不管你是刚接触LLM的初学者还是已经调过不少模型但总是“知其然不知其所以然”的工程师这本书的阅读路线和避坑思路都值得参考。1. 从零开始到底从哪开始这本书搭建的知识脚手架1.1 大多数教程断在哪一层市面上讲大模型原理的内容断层现象非常明显。学术风格的教程会直接把Transformer结构图扔出来然后开始推导自注意力公式默认读者已经具备完整的深度学习和NLP基础偏工程风格的教程则喜欢拿现成的模型库做演示加载模型、准备数据、跑训练所有的细节都被封装成了接口。这两类内容有一个共同的问题中间缺了“机制可视化”的那一层。很多人知道有Q、K、V三个矩阵也知道要去算点积但当你问他“为什么Q乘K之后要除以根号d_k”的时候大概率只能回答“这是公式里规定的”。这本书好就好在它会停下来问这个“为什么”并且用足够直白的方式把它讲清楚。它的默认读者设置也很明确——会写Python了解基本的张量操作懂得反向传播的大致概念但对Transformer和大语言模型没有系统认知。作者不会默认你已经学过词嵌入也不会默认你懂因果掩码而是从“一段文本如何变成矩阵”“一个词如何用向量表示”这种最基础的问题开始铺路。1.2 一张全流程地图贯穿始终这本书在开篇就给出了一张非常关键的“信息流动地图”原始文本 → 分词 → token ID → Embedding向量 → 多层Transformer计算 → 输出logits → Softmax得到概率分布 → 采样生成下一个token。这张图本身没什么稀奇但妙的是书的后续章节完全是按照这张地图来组织的。每一章只负责讲清楚图上的某一个环节并且配上可运行的代码。由于所有代码都围绕同一个玩具级模型展开你会清楚地看到“这一章在模型代码里对应哪一块”而不是学完一堆孤立的知识点却拼不成一个整体。以注意力机制为例网上很多教程会单独抽出一段自注意力的代码给你看但不会告诉你它在整个模型里被调用了多少次和残差连接、层归一化之间是什么关系。这本书则会把一个完整的TransformerBlock拆给大家看注意力只是其中的一个子模块并且告诉你它前面的归一化是干什么的、后面的前馈网络又承担了什么角色。从阅读体验来说这种“先有地图再逐个点亮”的写法比直接钻进公式里舒服得多。它适合的读者不光是新人——像我这种平时主要做应用层开发、对底层细节已经有些生疏的工程师也能靠这张地图快速把知识网格补充完整。2. 手写注意力机制从QKV到Mask这本书的拆解方式2.1 用“资料检索”类比理解Q、K、V自注意力机制里最容易让人困惑的就是Q、K、V这三个字母到底代表什么。网上的标准说法是“Query、Key、Value”中文一般翻译成查询、键、值。但如果你只是记住了这三个单词依然很难直观感觉到它们在做什么。这本书的类比我很喜欢想象你在一个图书馆里做文献检索。你手上有一张纸条上面写着你关心的话题这就是Query图书馆里每本书的侧边都贴着索引标签这就是Key而书架上真正的书本内容才是Value。注意力机制的流程本质上就是拿Query去和每个Key做匹配度打分分数越高说明这本书越相关最后按相关度加权地把对应的Value取出来。这个类比虽然简单但能解释很多后续细节。比如为什么Q和K的维度必须一致而V可以不同因为Query和Key要直接做点积运算维度必须匹配Value代表被提取的内容它的维度可以独立设计。再比如多头注意力为什么有用因为单一的Query往往只能关注一种类型的关系而不同的注意力头可以各自关注不同的关系维度——比如某一个头在关注语法依赖另一个头在关注指代关系还有一个头在关注词与词之间的共现模式。2.2 除以根号维度为何是必要操作注意力分数计算公式中Q和K做点积之后会除以根号下d_k这个操作看起来像是一个无伤大雅的缩放实际上却是保证训练稳定性的关键。原因说来也简单当两个向量的维度d_k变大时点积结果的方差会随之变大。比如d_k64时点积结果的方差大约是64这意味着很多分数会落在绝对值很大的区域。如果直接把这些分数送进SoftmaxSoftmax的输入在远离原点的区域梯度会变得非常小反向传播时梯度就容易消失。除以根号d_k之后方差被拉回到1左右的量级Softmax就能工作在一个梯度比较健康的状态。为了验证这一点我自己也做过一个对比实验在d_k128的情况下去掉缩放项后训练明显变得不稳定loss曲线出现剧烈震荡加上缩放之后收敛立刻恢复正常。书里虽然只用一小段话解释了这个原因但如果你自己跑一遍代码体会会深得多。更难得的是书里附了一份极简的注意力实现代码核心计算全部显式写成张量操作方便观察每一步的中间形状变化import torch import torch.nn.functional as F def scaled_dot_product_attention(Q, K, V, maskNone): d_k Q.size(-1) # Q、K点积得到注意力分数 scores torch.matmul(Q, K.transpose(-2, -1)) / (d_k ** 0.5) if mask is not None: scores scores.masked_fill(mask 0, float(-inf)) weights F.softmax(scores, dim-1) return torch.matmul(weights, V)这段代码虽然只有不到十行但它把注意力机制的全部核心逻辑都包含进去了矩阵乘法、缩放、掩码、Softmax归一化、加权求和。跟着敲一遍再对比着看公式基本很难再忘记。2.3 训练与推理中的掩码逻辑并不相同注意力机制里最容易让初学者栽跟头的是掩码的处理。书里很清楚地划分了两个场景训练阶段的因果掩码和推理阶段的缓存优化。在训练阶段我们教模型去预测下一个token所以每个位置最多只能看到当前位置之前的token不能被未来信息“剧透”。实现方式也很直接——构造一个下三角矩阵让未来位置全部变成负无穷经过Softmax之后这些位置的权重自然变成0。到了推理阶段情况其实更复杂一些。如果一次只生成一个token那么对于已经生成出的历史token它们的Key和Value在每一层中已经算过一遍了完全没必要重新计算。于是就有了缓存机制把之前算过的K、V矩阵存下来下一次只需要用新的Query去和缓存中的K、V做注意力计算。书里把这两个阶段的代码放在一起对比看完你会很清楚为什么训练时用掩码、推理时却可以不用完整掩码而只用单行的Query去做增量计算。3. 搭一个迷你语言模型数据、训练曲线与生成效果3.1 小规模语料也能训练出有意义的生成效果很多教程喜欢一上来就给一个几百GB的数据集作为练习这其实很劝退。这本书反其道而行——它建议你直接下载几MB级别的公开文本语料比如经典文献或者优质文章的纯文本片段用来做一个几十MB就能跑动的迷你模型。坦白说刚开始我是怀疑的总觉得数据量这么小模型大概率只会背诵语料。但实际上训练目标让模型学会的不是背诵而是token之间的统计关系。即使语料规模不大模型也能学到不少词法结构和基础语法规律生成出来的句子至少在局部是通顺的。对于理解大语言模型的训练机制来说这种“小模型训练闭环”已经足够。处理数据时书中反复强调了一个容易漏掉的问题训练序列不是每句话之间独立不相关的大语言模型用的是“继续预测下一个token”这种方式所以语料天然是连续的长文本流需要按固定长度的窗口去切块。切块时相邻的两个训练样本之间应该部分重叠或者直接把文本切成连续的不重叠块而不是把每个句子单独拖出来当一个独立样本。否则模型就学不到跨句子的长期依赖生成效果也会明显打折。3.2 模型结构和训练超参数里的门道这本书里搭的迷你模型是一个标准的Decoder-only结构层数不多注意力头数也不多参数量控制在千万级别左右。虽然规模比工业级模型小了好几个数量级但它在结构上保留了大模型的所有关键要素Embedding层、多头注意力、前馈网络、残差连接、层归一化以及最后的输出映射层。训练超参数的选择也值得好好看。它用的优化器是整体训练领域比较常见的自适应矩估计优化器配合学习率warmup和cosine decay策略。warmup阶段学习率从很小的值慢慢升到峰值这样可以让模型在前期避免因为过大的更新步长导致震荡后面学习率逐渐衰减则让模型在后期能够在一个比较精细的范围内继续调优。实际训练时loss曲线会呈现一个很有规律的变化过程。起步阶段loss下降非常快大概是因为模型在快速学会“高频词优先出现”这类最基本的统计规律随后下降速度变慢模型开始捕捉更长距离的搭配关系再往后是一个漫长的平台期。如果你的loss在某个区间横着不动不代表模型学不动了有可能只是学习率已经衰减到很小更新幅度非常微弱。3.3 从生成效果看“智能”是如何浮现的训练完成后打开生成开关第一次看到模型输出时给人的感觉是“既惊喜又可笑”。初始阶段模型只能输出一些毫无意义的字符组合经过几百步训练后它开始产生合理的词频排序再往后可以生成语法基本正确但内容毫无逻辑的句子最后如果语料质量高且训练充分它甚至能偶尔输出一两句看起来“像模像样”的话。亲眼见证这个过程你会非常直观地理解“大语言模型的智能是从统计规律里涌现出来的”这句话到底是什么意思——它并不是某个神秘算法突然产生了意识而是一层又一层的条件概率叠加最终在文本生成这个维度上表现出了令人惊讶的连贯性。很多人对“涌现”这个概念充满玄学想象看完这本书的迷你模型训练过程至少能祛魅一半。4. 微调与对齐不是玄学书中给出的可落地工具箱4.1 指令微调的数据是怎么组织起来的预训练只是让模型学会了“像人一样说话”但并没有让它学会“听懂人的指令”。指令微调要解决的问题是让模型根据用户输入的任务描述去生成符合要求的回答。这本书在讲指令微调时没有停留在“准备一些问答对”这种抽象描述而是直接展示了数据模板的构造方式。每一个训练样本都要组织成一段完整的对话文本包括系统提示词、用户指令、模型回答然后把整段文本按token切分好去训练模型预测下一个token。关键是训练时的损失计算范围。指令微调不能把整段文本的每个位置都拿来算loss因为很多高权重token来自用户的提问和系统提示词如果让模型也去预测这些部分它会把注意力分散掉。正确的做法是只对模型回答部分的token位置计算损失让模型知道“只有生成回答这件事才是重要的”。这个细节虽然看起来不起眼却是指令微调效果差异的重要来源之一。4.2 奖励模型与策略优化的迷你实现在对齐阶段书里讲到了偏好对齐的整个流程但它的讲述方式非常务实——会先给你一个最小可跑的简化版本而不是直接把庞大的工程化实现甩到脸上。偏好对齐的思路可以概括成三句话先让模型产生多种回答再训练一个奖励模型为这些回答的好坏打分最后用强化学习优化语言模型的策略让高分回答的出现概率越来越大。这里面的奖励模型本质上是一个在“好回答/坏回答”偏好数据上训练出来的评分器输出的是一个标量分数。书里还花了一定的篇幅介绍了一个更轻量的替代方案直接从偏好数据中构造训练目标省略奖励模型和强化学习这两个复杂的训练环节。这个方案叫直接偏好优化它的思想是让模型在训练时直接“靠近被偏好的回答、远离不被偏好的回答”用简单对比的方式就能完成对齐。对于想快速体验对齐效果的读者来说这个方案友好得多。在讲强化学习流程时作者特意提醒了一个容易被新手忽略的点奖励模型给出来的分数分布可能和语言模型原有的输出分布不匹配需要经过归一化或者基线处理否则强化学习阶段会非常不稳定甚至出现模型输出质量不升反降的情况。4.3 评测模型时该看哪些指标训练完之后怎么评价一个微调后的模型到底变好了没有这个问题书里同样给出了务实的答案。困惑度是一个老牌指标它计算的是模型对真实文本的预测概率数值越低说明模型拟合越好。但它对生成质量评估是粗粒度的两个困惑度几乎一样的模型生成出来的内容可能有明显差异。所以书里还推荐了人工AB对比的做法让两个模型针对同一批测试问题分别生成回答由人工标注者在不看模型名的情况下评判哪个回答更符合用户需求。这种评测方式成本高但结果最可靠。在一些工业项目里线下指标的涨跌常常和线上效果不完全一致最终还是得靠这种人工对比的方式来做最终判断。5. 复现这本书时的三个坑精度、显存与采样参数5.1 fp16训练导致loss不下降的隐形杀手我在照着书里代码实验的时候遇到的第一个麻烦是数据类型问题。为了省显存我把模型参数切到了half精度结果训练loss一直卡在一个数值上不往下走再仔细观察发现参数更新之后数值精度已经顶不住梯度计算的要求了。原因并不复杂——fp16的动态范围小很多小数值在计算过程中会被直接截断成0梯度信息丢失严重。网上的博客经常鼓吹“用fp16训练又快又省显存”但实际落地时必须配合混合精度策略也就是在计算梯度时用更高精度的格式做累加更新参数时再转换回半精度。自己动手试验之后我的建议是如果你只是跑书里这种千万参数级别的迷你模型根本没必要用半精度。老老实实用默认的float32训练速度完全在可接受范围内却可以省掉一大类莫名其妙的调试时间。5.2 显存不够时的正确解法是梯度累积跑实验的时候还会遇到一个现实问题想用更大的批量来平滑梯度但显存根本塞不下。不少人第一反应是直接减少批量大小结果batch变小之后梯度噪声变大训练曲线变得毛糙收敛质量也下降。书里推荐的显存优化方案是梯度累积。做法很简单——把一个大batch拆成几个小batch分批前向计算得到梯度把梯度累加起来攒够一个大batch的梯度之后再统一做一次参数更新。这样做既能保住大batch带来的训练稳定性又不会突破显存上限。如果梯度过大导致loss飞掉还有一招是梯度裁剪。设定一个梯度范数的上限超过上限就等比例缩放。这个操作看着粗暴实际很管用尤其对于Transformer这类深层模型几乎属于标配操作。5.3 采样参数不是随便调的最后一个坑是生成阶段的采样参数。很多人第一次写生成函数时直接在模型输出的概率分布上做随机抽样结果模型生成出来的内容要么胡言乱语要么始终在几个高频词之间打转。这里要解释一下temperature这个参数。temperature1时采样完全按照原始概率分布来temperature越低分布越尖锐模型倾向于选择概率最高的tokentemperature接近0的时候基本就变成贪心搜索每一次都选概率最高的那个token输出稳定但容易重复。另一个常用参数是top-p采样它控制的是只从累计概率达到某一阈值的候选token里采样。比如top-p0.9表示从概率累计到90%的那些token里随机挑一个。temperature负责调节分布的锐度top-p负责截断候选范围两者配合使用才能在“多样性”和“稳定性”之间找到平衡。我刚开始复现的时候总是把temperature调得很高想增加生成内容的丰富度结果模型大量输出无意义的词后来才悟到应该是基于top-p先圈定一个合理的候选范围再在这个范围内用temperature做微调。读这本书还有一个让我印象很深的地方就是它每章末尾的练习设计。每一个练习都在前一章的代码基础上修改而不是重新做一个新项目。你不需要GPU集群只需要一台普通电脑就能完成绝大部分的实验。照着我个人的经验读这本书最有效率的方式是先不要跳着看把前四章的顺序吃透理解注意力机制的原理和迷你模型的训练闭环等自己亲手把玩具模型跑通一遍之后再去翻后面偏工程的章节收获会非常大。大语言模型的知识体系看起来庞大但核心的地基无非就是注意力机制、训练目标和解码策略这几块。这本书恰恰就是围绕这几块地基认认真真铺了一条让人能走通的路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑