资讯详情

资讯详情

从零构建大语言模型与推理模型:手写GPT到强化学习全攻略

这两年聊到 AI 工程几乎人人都绕不开一个词from scratch。翻译过来是从零开始但具体从哪个“零”出发大家脑子里装的其实完全不一样。有人理解的从零是抱着《Build a Large Language Model (From Scratch)》从头读到尾把分词、多头注意力、预训练这些模块一个个敲出来有人理解的从零是连矩阵乘法和反向传播都要用纯 Python 手推一遍还有人理解的从零是不依赖任何现成的模型框架从数据构造、训练脚本、评测、部署一路搭起一套自己的 AI 系统。这几年“build a reasoning model from scratch”之类的讨论反复出现说明大家已经不满足于只会跑通别人的代码而是想亲手摸清一套模型到底是怎么训练出来的。这篇文章适合三类人刚入门、想搞清楚 AI 工程到底要学什么的实践者已经能熟练调用模型 API、但希望深入内部机制的开发者以及正打算从零构建语言模型或推理模型、却不确定第一步该落在哪的学习者。我会按自己实际验证过的顺序把要补的基础、要写的代码、要避开的坑一次讲清楚。1. 先定义坐标你的“从零”到底是指哪一个零1.1 三种常见的起点第一种是完全不引入深度学习框架只用 Python 和 NumPy 手写神经网络。这条路最硬核但收益也最直接矩阵乘法、梯度更新、反向传播在你看不见的地方究竟发生了什么都会被迫摊在阳光下。坏处也很明显时间成本极高而且在纯 NumPy 环境里做大规模分布式训练并不现实。适合想彻底吃透数学和训练机制的人不适合作为唯一路线。第二种是使用 PyTorch 这类框架但不调用现成的 Transformer 封装而是自己手写多头注意力、层归一化、训练循环。这条路对应《Build a Large Language Model (From Scratch)》这类教材的核心做法。它的巧妙之处在于框架帮你解决了很多工程性问题比如自动求导、GPU 调度、张量运算加速但模型内部的结构仍然由你亲手搭建。我认为这是绝大多数学习者真正应该走的路。第三种是系统级的从零开始。不关心模型内部每一个张量的流动而是把重心放在数据管道、训练配置、评测指标、部署服务、持续迭代这套完整链路。适合目标是成为全栈 AI 工程的人但如果完全不走第二种路很容易变成“会调库但不懂模型”的工具人。路线典型做法适合谁主要风险底层实现纯 NumPy 手写网络与梯度想彻底吃透训练机制的人耗时巨大容易耗尽耐心模块构建PyTorch 手写 Transformer 组件想真正理解大模型的绝大多数学习者容易变成“抄代码”缺乏验证系统构建自建数据-训练-评测-部署全链路想做完整 AI 产品的人模型内部理解偏弱我不建议你在三条路里只选一条。更务实的组合是以第二种为主干用第一种做辅助练习用第三种收尾。很多人问为什么不一上来就读大模型论文我的回答是论文回答的是“为什么这样做”代码回答的是“怎么把这套东西从文件堆里真正跑起来”。没有代码兜底论文读完很容易就忘。1.2 语言模型线和推理模型线的关系最近有两个高频关键词值得注意一个是“build a large language model from scratch”对应语言模型线另一个是“build a reasoning model from scratch”对应推理模型线。这两条线不是并列关系而是明显的递进关系。语言模型线要解决的核心问题是让模型学会“预测下一个 token”。训练目标单一、评价指标清晰、入门门槛低几百 MB 文本数据就能感受到损失在下降。推理模型线则要复杂得多通常需要在预训练底座之上再叠加思维链数据监督微调和强化学习。它要解决的不只是“输出连贯”而是“输出正确、可验证、能自我纠错”的高质量内容。所以我的建议很明确先从语言模型线走通。先做一个最小规模的 GPT让它在小数据上真实地学会续写文字然后再考虑推理模型。如果在 1B 参数以下的模型上硬做推理强化学习大概率会得到反复震荡甚至完全训崩的结果。这不是能力问题是路线顺序问题。顺带说一句资料获取方式。很多朋友喜欢去网盘找电子版教材我看到这类搜索量一直很大。作为过来人我的建议是直接买正版或者打开官方配套的代码仓库。正版书一般带着完整代码、练习和更新说明在线网盘资源往往版本残缺、代码不匹配最后省下的钱都会被额外的时间成本吃掉。2. 地基三件套数学直觉、Python 能力、系统化习惯2.1 数学按模型运行的路径去学而不是按教材目录去学一提到从零开始很多人的第一反应是先啃完线性代数、概率论、微积分三本书再说。这个想法很容易把学习周期拉长到令人绝望的程度。真正常用的数学并没有那么多关键在于知道每个概念在模型里到底站在什么位置。先说线性代数。你不需要会证明一堆定理但必须对矩阵形状和矩阵乘法有直觉。Transformer 里的输入是一批 token 嵌入形状通常是(batch_size, seq_len, d_model)每一层计算无非就是让这个三维张量在d_model和seq_len两个方向上变换。注意力分数是q和k的矩阵乘前馈网络是两次线性映射位置编码本质上是往输入里注入一组可学习或固定的模式。再说概率和信息论。Softmax 把一组实数变成概率分布交叉熵衡量“预测分布”与“真实分布”的差距KL 散度则让你理解为什么蒸馏和强化学习里会反复出现“分布对齐”的概念。这些内容不是孤立的数学题而是模型损失函数的一部分。最后是微积分。大多数时候你不需要手动算导数因为 PyTorch 的backward()会替你完成。但“梯度是损失函数上升最快的方向”这一直觉必须有。反向传播依赖链式法则理解它的最好方式不是背公式而是亲手实现一次两层的反向传播。这里有个很实际的经验按“模型代码运行路径”组织数学学习比按教材线性推进高效得多。遇到一个不懂的数学符号先问它出现在代码的哪一行再去查定义记忆会牢固得多。2.2 Python给自己做一次能力自检在打开任何模型代码之前先给自己做个能力自检省得到时候一边查语法一边怀疑人生。以下清单并不要求你全精通但每项都应该达到“看到代码能快速理解需要时能自己写出来”的程度。列表推导式、字典、集合这些基础数据结构能不能顺手就用能否读写 JSON、CSV 文件并完成简单的数据清洗能否用 NumPy 写出矩阵乘法、转置、softmax是否理解 Python 的__init__.py、import路径和模块组织能否用print加张量shape输出快速定位维度不一致的问题是否用过torch.utils.data.Dataset和DataLoader理解批处理逻辑这里最容易被忽略的是“调试能力”。我见过太多初学者卡在两三行的报错上动弹不得。其实大模型训练中的绝大多数 bug靠三招就能解决打印张量形状、打印前几个数值的变化、把训练步数调小看损失是否下降。如果你能以pdb或简单断点的方式进入代码内部一步步看数据流很多问题会在十分钟内现出原形。2.3 系统化习惯先把随机种子和版本锁死从零开始和写脚本最大的区别是对“可复现性”的要求。你不仅要写出一段能跑的代码还要保证同样的代码在不同时间、不同机器上尽量跑出接近的结果。这件事的工程基础很简单固定随机种子、固定依赖版本、记录实验配置。固定随机种子的意义在于权重初始化和数据打乱都依赖随机数生成器不固定的话两次训练的起点和数据顺序都不一样最终模型效果自然难以比较。固定依赖版本同样重要PyTorch 版本、CUDA 版本、Python 小版本都会影响浮点计算细节严重的会让训练完全无法复现。我早期吃过一个亏一口气训练了十几小时的模型中间打断几次后重启训练发现结果和第一轮对不上。排查到最后发现是不同的 Python 环境混用了不同的 CUDA 版本。从那以后我给自己的实验目录固定一个environment.yml每次开新项目先复制一套确定版本的环境。这个习惯虽然不起眼但能省掉无数无效的对比实验。3. 动手第一关手写反向传播搭一个最小 GPT3.1 用两层网络手动计算梯度建立“训练能收敛”的最朴素直觉我几乎会向每个从零开始的初学者推荐同一个练习用 NumPy 手写一个两层全连接网络完成一次完整的梯度下降训练。代码不长但一轮跑下来你对“前向传播、损失函数、反向传播、参数更新”这四段流程的认知会从模糊的名词变成清晰的画面。下面是一个简化但完整的手写版本输入是一个 batch 的样本标签是类别编号训练目标是让交叉熵损失下降。import numpy as np def forward(x, params): z1 x params[W1] params[b1] a1 np.maximum(0, z1) # ReLU logits a1 params[W2] params[b2] return logits, a1 def softmax_cross_entropy_step(x, y, params): logits, a1 forward(x, params) exp_logits np.exp(logits - logits.max(axis1, keepdimsTrue)) probs exp_logits / exp_logits.sum(axis1, keepdimsTrue) loss -np.log(probs[np.arange(len(y)), y]).mean() dlogits probs.copy() dlogits[np.arange(len(y)), y] - 1 dlogits / len(y) grad {} grad[W2] a1.T dlogits grad[b2] dlogits.sum(axis0) da1 dlogits params[W2].T da1[a1 0] 0 # ReLU 反向 grad[W1] x.T da1 grad[b1] da1.sum(axis0) return loss, grad这段代码里最值得反复看的是da1[a1 0] 0那一行它就是 ReLU 的链式法则在代码里的形态。跑完一轮训练看到损失真的在下降你对“梯度更新让模型逐步逼近数据规律”这句话才算有了真实感受。提示不要用肉眼判断梯度对不对。把这段代码算出的梯度和数值梯度做一次对比误差在 1e-4 以内基本可以确认实现正确。这个步骤叫梯度检查是手写网络时最容易被跳过、但最值得做的验证。3.2 从全连接网络跳到因果自注意力手写反向传播建立的是“训练闭环”的直觉而大模型的核心难点则集中在注意力机制上。Transformer 里最容易被理解错的地方是它的自注意力并不是让整个序列自由交互。GPT 这类自回归模型的注意力必须带上因果掩码意思是每个位置只能看到自己和自己之前的 token不能偷看未来。下面的 PyTorch 代码实现了完整的因果自注意力它是从一个最小 GPT 拆出来的核心模块。import torch import torch.nn as nn class CausalSelfAttention(nn.Module): def __init__(self, d_model, n_heads, block_size): super().__init__() assert d_model % n_heads 0 self.n_heads n_heads self.d_head d_model // n_heads self.qkv nn.Linear(d_model, 3 * d_model) self.out nn.Linear(d_model, d_model) mask torch.tril(torch.ones(block_size, block_size)) self.register_buffer(mask, mask) def forward(self, x): B, T, C x.shape q, k, v self.qkv(x).chunk(3, dim2) q q.view(B, T, self.n_heads, self.d_head).transpose(1, 2) k k.view(B, T, self.n_heads, self.d_head).transpose(1, 2) v v.view(B, T, self.n_heads, self.d_head).transpose(1, 2) att q k.transpose(-2, -1) * (self.d_head ** -0.5) att att.masked_fill(self.mask[:T, :T] 0, float(-inf)) att torch.softmax(att, dim-1) y att v y y.transpose(1, 2).contiguous().view(B, T, C) return self.out(y)这里有两个细节值得停下来琢磨。第一qkv一次投影到三倍维度再拆成q、k、v这样写不是花活而是在工程上减少矩阵数量、提高计算效率。第二注意力分数除以sqrt(d_head)是缩放点积目的是防止维度增大后点积数值过大、Softmax 落入梯度饱和区。在真实的最小 GPT 里你还需要补上 token embedding、位置 embedding、LayerNorm、逐位置前馈网络、最终输出层再把多个这样的注意力模块串起来。整个模型控制在 100 多行代码以内完全可行。按固定顺序串起来注意力 - 残差连接 - 层归一化 - 前馈网络 - 残差连接。3.3 判断训练闭环是否打通的两个经验法则写完代码后先别急着上大数据。我有两个特别管用的经验法则基本上零基础跑通也能用。第一个法则先用一小块训练数据做“过拟合测试”。从真正的训练集里切出 100 到 500 个样本训练几十步看损失能不能降到接近零。如果损失几乎归零说明前向传播、反向传播、参数更新整条链路都是通的。如果损失卡在某个高位不下来先把注意力掩码和 embedding 的维度对齐查一遍。第二个法则用几个让你自己眼前一亮的短句去生成。训练一个小规模的 GPT 之后输入几个不同的半句话去看输出是否语义连贯。Loss 下降代表模型学到了概率模式但生成结果才是它真实理解能力的体现。把“过拟合测试”和“生成样例测试”两个关卡都过了你就有资格进入下一步了。4. 沿着“大语言模型 from scratch”的路线做全链路实践4.1 一本典型教材的路线拆解现在市面上最热门的“从零构建大语言模型”路线其实可以浓缩成一条很清楚的链路。不同的书叫法略有差异但核心阶段大致相同。以《Build a Large Language Model (From Scratch)》中译名《大语言模型从零构建》为例它的章节安排几乎就是一条完整的工程曲线。章节方向关键实践我标注的重难点LLM 基础理解语言模型的输入与输出输出单位是 token不是单词文本数据处理实现文本切分和 BPE 分词编码边界决定了模型的词汇视野注意力机制手写多头注意力因果掩码的位置最容易错GPT 架构组装 Transformer Block维度一致性需要反复验证预训练跑通训练循环判断收敛别和过拟合混淆分类微调在分类任务上调整模型冻结策略与学习率配合指令微调构建指令-响应对数据格式决定了模型服从度加载与使用保存权重加载权重state_dict 的 key 匹配这本书的好处是它刻意避开了“直接调用nn.Transformer然后完事”的做法要求你把每个模块自己写出来。这正对应 1.1 里说的“模块构建”路线。读完它你会对 BPE 分词如何处理未知词、位置编码如何给序列注入顺序感、预训练和微调在计算图上的差别都有亲身代码经验。4.2 不要满足于“跑通”要学会“重写”跟着教材敲代码跑通是最容易让人误以为自己学会的阶段。衡量是否真正掌握我有一个很变态但很有效的标准把书合上从头再写一份。不是复制而是凭记忆和逻辑重新组织代码。写不出来的地方就是还没完全理解的地方。我在带人入门时见过太多“手打一遍代码”的情况。手打确实比复制粘贴强一些但它仍然是逐行翻译别人的思路。真正的理解来自把问题反过来问自己如果我要从纯文本数据出发自己设计一个预测下一词的模型第一步应该处理什么当你能从头独立搭建一个最小 GPT 并训练到损失下降时才真正谈得上 from scratch。4.3 我更推荐的实践顺序先有模型后补原理这里分享一个我自定义的顺序比按教材章节从头读到尾更适合实践者。我的建议是先跳着做 2 到 5 章也就是完成数据分词、注意力机制、GPT 架构、预训练这一条最小链路先把模型在显存里跑起来。然后再回头补第 1 章和基础理论。原因很简单前几章如果逐字精读你会在分词细节和注意力实现上消耗两周却看不到一个完整的训练效果。先跑起模型损失曲线会给你即时反馈这种反馈比任何抽象解释都有力。等模型真正能生成句子了再回去看那些当初让你困惑的概念往往一点就通。5. 升级打怪从“会生成”的 LLM到“会推理”的 Reasoning Model5.1 推理模型的本质把思考时间变成一条可训练的轨迹普通语言模型接到一个问题后会直接生成一个接一个的 token生成完了就结束。推理模型的差别在于它能在最终答案之前先生成一段很长的“思考草稿”包括尝试、验证、修正、回退。这个“思考草稿”在 OpenAI 的 o 系列和开源社区复现的 R1 类方案中都存在本质上是一条被记录下来的思维链轨迹。训练推理模型的时候我们做的其实是两件事。第一让模型在大规模预训练中已经具备足够的知识和语言能力第二通过思维链数据和强化学习让模型在推理时学会“把时间花在思考上”。模型不是被硬塞进正确答案而是被调教出“自己找到正确路径”的行为模式。这也是为什么“build a reasoning model from scratch”比“build a large language model from scratch”更难。前者是在一个已经能说话的基础上训练“怎么想”后者是训练“怎么说”。很多初学者搞不清这个顺序直接拿小模型硬做推理强化学习往往训练几轮后 loss 不降反升。5.2 思维链数据的三条来源理性数据从哪里来是推理模型训练里比模型结构更关键的问题。没有足够的优质思维链数据后面一切强化学习都是空中楼阁。第一条来源是人工标注。找专业的人把数学题、逻辑题、编程题的完整思考过程一步步写下来。质量最高但成本也最高只适合小规模验证。第二条来源是蒸馏。用一个已经具备推理能力的大模型生成推理步骤再用规则过滤掉结果错误、过程混乱的样本。工程成本低但需要注意大模型的错误也可能被蒸馏进小模型过滤逻辑要足够严格。第三条来源是程序自动生成。对数学题、代码题、逻辑谜题这类答案可验证的任务可以用程序生成海量题目自动判断模型输出是否正确只把正确且过程完整的样本留作训练语料。这条路线在工程上最划算也是目前推理模型开源复现的主流做法。我建议从第三条开始练手。原因很简单验证不靠人看而是靠跑代码。写一个简单的“生成题目 - 模型作答 - 脚本判分 - 筛选保留”的数据生产管线你就能拥有一份源源不断的推理数据。5.3 最小可行的强化学习路径拿到思维链数据之后接下来是强化学习微调。这一块听起来复杂但最小可行的路径比想象中要窄得多。第一步准备底座模型。最好是已经完成预训练并且能稳定完成普通问答的模型参数规模在 1B 到 7B 之间比较合适。没有底座就上强化学习结果往往是一场灾难。第二步定义奖励函数。从零开始做时建议不要引入需要额外训练一个模型的奖励模型直接用规则判定就可以了。数学题看最终答案是否一致代码题看测试用例是否全过格式题目看是否包含规定的标签结构。规则可验证的奖励是工程中最省心也最稳定的方案。第三步选择策略优化算法。社区里最常被提到的两个名字是 PPO 和 GRPO。PPO 需要额外训练一个 critic 模型来估计价值函数实现复杂、算力开销大。GRPO 的改进在于直接用一组采样结果的相对表现来计算优势值不需要 critic 模型训练更稳定也更适合预算有限的团队。第四步循环迭代。从数据池里采样一批问题让当前策略模型生成答案用规则奖励打分计算优势值更新策略权重再进入下一轮。这个过程跑起来并不神秘难的其实是基础设施。一次采样要 roll out 一整段长思维链显存占用和计算量比普通微调大得多数据管线的每一步都可能爆出意外。5.4 给算力预算有限的人一个预期管理对个人开发者来说复现真正工业级推理模型的完整训练链路预算和心理预期都必须调低。7B 参数模型做强化学习即便用 8 卡 A100一轮实验也要按天计费。更稳的路线是在 1B 到 3B 参数区间证明自己的训练流程能跑通观察“推理能力是否在某个 step 出现明显跳变”然后再把同一套流程平移到更大规模。从零构建推理模型最值得收获的成果并不是那个最终权重文件而是亲手走完“生成思维链数据 - 监督微调 - 规则奖励 - 策略优化”这条完整链路后头脑里建立起的工程直觉。有了这个直觉你未来再读任何推理模型论文都能自动脑补出它在工程上大概长什么样。6. 工程化闭环评估、部署、成本与我的踩坑记录6.1 模型评估别只用 Loss 说话训练过程中每天盯着 loss 曲线是最容易产生错觉的事。Loss 下降当然说明模型在学习但它不告诉你它在学习什么有时甚至是在学习训练集里的噪声。一个科学的评估体系应该分层设计。场景主要指标必要的辅助手段语言建模预训练loss、困惑度 PPL手动生成样例观察语义连贯性分类微调accuracy、F1错误样本的逐条分析推理模型可验证正确率思维链抽样阅读看推过程是否合理我见过最典型的“Lost 骗局”是有人在微调时发现训练 loss 下降很快但评估指标原地不动最后发现是数据格式错误导致模型在背诵标签而不是理解任务。评估必须同时依赖定量指标和样本检查两者缺一不可。6.2 训练成本怎么算在哪里能压缩算力成本是很多从零开始的朋友不敢推进的真实阻力。我的建议很务实先用消费级 GPU 把流程跑通再决定要不要租云 GPU 练大模型。成本估算要按小时计算而不是按“一次训练多久”的模糊感觉。资源单小时参考费用适合做的工作本地消费级 GPU主要是电费400M 到 1B 参数的小模型实验租用 T4约 1 美元级别数据预处理和小规模微调租用 A100/H100数美元级别7B 级微调和强化学习实验成本压缩的关键不在省硬件而在省无效迭代。固定随机种子、保存每轮 checkpoint、离线缓存数据集都是为了不让一次失败的参数组合浪费整晚算力。先在小数据集上把超参数试出大致范围再用全量数据跑能省下至少三分之一的开销。6.3 我这几年踩过的三个坑第一个坑是跳步。早期我急着复现推理模型上来就准备强化学习结果底座模型连普通问答都做不好训练直接崩掉。这个教训让我彻底明白了推理能力必须建立在生成能力之上。后来我把预算和时间重新分配先花两个星期做数据用小模型把生成质量提到稳定水平再碰强化学习效果立刻就不一样了。第二个坑是不锁环境。某次模型训练到一半中断重启后性能曲线完全对不上查了整整一天才发现是 CUDA 版本不一致导致的。现在我的每个项目文件夹里都固定放一份环境配置文件换机器第一件事是把环境对齐而不是直接跑代码。第三个坑是忽视数据质量。我一度以为加大训练数据量就能提升效果结果发现模型生成了一堆语法正确但内容空洞的废话。后来翻了训练集才意识到里面有大量重复文本模型学到的只是高频模式。花三天清洗数据后同样算力的情况下效果立竿见影地变好。说了这么多如果让我重新按 from scratch 走一遍我不会再从任何宏大目标开始而会先用 50MB 文本训练一个不到 1 亿参数的小 GPT蹲在终端前盯着 loss 曲线一点一点往下掉。我会先接上真实数据让模型生成句子再接上思维链数据让模型学会一步步推理最后才谈强化学习和工程化部署。这个顺序让我一直受用。你不需要一步到位只需要从第一步开始。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →