从零训练LLM:一条可复现的全流程工程路线
看到各个技术社区里“from scratch”系列的项目一个接一个火起来从大语言模型到推理模型从训练脚本到推理引擎大家都在往“从零构建”这个方向上挤。我最初以为这就是一群极客在自娱自乐直到自己花四个月把一个AI engineering的完整链路——从数据处理、分词器训练、预训练、指令微调到强化学习微调——全部跑通之后才真正明白这件事对普通工程师的价值在哪里。这篇文章不会教你复制GPT-4也不会带你从头推导所有数学公式而是把一个AI工程师从零开始训练自己的语言模型、甚至尝试让模型具备基础推理能力的过程按真实工作流拆开讲。内容包括怎么定义“从零”的边界、一条最小可行技术路线、让模型从“能说话”走向“会推理”的强化学习实验、只有一张消费级显卡时怎么分配算力以及我踩过的那些让项目差点中断的坑。适合谁看呢第一类已经会用Transformers库做推理但对训练全链路感到陌生的人第二类打算做一个“从零复现LLM”项目却不知道从哪下手的人第三类想搞清楚“RL微调”到底是不是玄学、以及它和普通SFT有什么区别的人。看完之后你应该能对“从零训练一个模型”这件事建立起完整的操作直觉。1. 从零复现LLM这件事到底图什么1.1 黑盒之外的调试能力如果你只是调用现成大模型的API或者用开源权重做推理那你面对的其实是一个黑盒。输入一段文本得到一个回答至于这个回答为什么出现、哪个环节出了偏差、换一组数据会不会更好你没有任何控制权和观察窗口。我刚开始做AI应用时也是一样的思路反正有现成模型效果不行就换提示词提示词不行就换模型再不行就上RAG。这套流程应付Demo足够了直到有一天我需要一个垂直领域的小模型来降低推理成本才发现自己完全不知道该从哪里下手——数据要什么样的、训练多少步、模型多大合适、为什么微调之后反而变笨了。这些问题不会调用API的人是答不上来的。从零构建一个模型最大的收获在于你被迫理解从数据到权重的每一个决策点。比如同样一句话在分词器里被切成几个token、不同切法对训练效率的影响、学习率过大时loss曲线会如何震荡、数据重复率高了模型会如何退化。这些知识在论文里读一百遍都不如自己亲眼看到一次损失曲线来得深刻。有了这种调试能力之后你再回去用大模型API会忽然发现很多之前觉得“玄学”的问题其实都有清晰的解释。1.2 “从零”的边界由你定义很多人一听“from scratch”就吓退了以为要从写矩阵乘法开始。其实没这个必要。这里要明确一下“从零”的合理边界否则你会在无意义的事情上消耗大量精力。我给自己的定义是不使用任何开源预训练权重从完全随机的参数出发亲手完成数据清洗、分词器训练、模型架构搭建、预训练、指令微调以及可选的强化学习微调。在这个过程中允许使用PyTorch、HuggingFace的tokenizers库这类通用工具——它们只是“语言和工具”不是“模型”。这就像是自己搭一栋房子你可以用标准砖块和水泥但不会直接买一栋精装房来改装。这个定义有两个实际好处。第一它保证了你能真正理解每个环节的原理随机初始化的权重经过训练变成有语义理解能力的模型这个过程里每一步都看得见摸得着。第二它控制了项目的复杂度和时间成本让你在几周内而不是几年内看到结果。也有人选择连反向传播都自己手写我认为除了教学目的这在工程上没有太大必要——就像你不会每次做饭都自己种小麦一样。还有一个现实收益容易被忽略成本控制能力。很多业务场景根本不需要几十亿甚至上千亿参数的大模型一个几千万参数的小模型做好数据清洗和微调效果往往出乎意料地好。但如果你只会调用外部API就永远没有机会把这样一个“小而美”的模型优化到可用状态。从零构建的真正价值是让你获得对整套技术栈的自由度——想用多大模型、怎么训练、怎么评估都由你来决定而不是被供应商牵着走。2. 一条可以跑通全流程的最小路线数据、分词、架构、训练2.1 数据准备先别急着上“全网语料”绝大多数从零项目死在数据准备阶段死法高度一致想搞一个“够大、够全、够干净”的语料库于是开始爬网页、筛去重、做格式清洗一搞就是两个月模型一行代码没跑。我的建议很直接第一阶段的目标不是“大”而是“干净且能跑通流程”。我用的是TinyStories数据集——一个专门为小模型设计的英文故事集每条文本都简短、多样、拼写规范。配合一个开源中文语料两条数据合起来也就几百万条几分钟就能完成预处理。后来我又加了一批自己用规则生成的合成数据比如数学运算题用来做后续的推理能力实验。这里有一个重要认知数据质量对最终效果的影响远远大于模型尺寸和训练步数。一条拼写混乱、内容重复的文本会在训练中反复干扰模型的学习而一批结构清晰、领域聚焦的语料即使量不大也能让小模型在特定任务上表现惊艳。所以第一阶段宁可手动挑数据也别盲目追求规模。数据清洗的具体操作我通常按这个顺序来先按长度过滤太短的没有学习价值太长的会拖慢批次再做规则去重最后人工抽检100条看格式和内容。至于更复杂的去重算法、敏感信息过滤等流程跑通之后再逐步加上——不要一步到位因为你怎么知道自己在清洗过程中有没有把有用的信息也删掉呢2.2 分词器最容易跳过却决定上限的环节很多人对分词器有个误解反正有现成的BERT或GPT-2分词器直接用不就行了如果你只是做推理确实可以但如果你要从预训练开始这个做法会带来一个隐蔽的问题——分词器和你的语料分布不匹配导致文本被切得又碎又低效训练时每个句子被拉长学习效率大打折扣。分词器做的事情本质上是在“字符”和“词”之间找到一组最优的子词单元。以最常见的BPEByte Pair Encoding算法为例它从单个字符开始反复统计相邻单元的出现频率把最高频的组合合并成一个新单元直到达到你预设的词表大小。这样做的好处是常见词作为一个整体被切出来生僻词退化为多个子词既控制了词表大小又不会出现太多“未登录词”。训练自己的分词器其实很容易几行代码就能搞定from tokenizers import ByteLevelBPETokenizer tokenizer ByteLevelBPETokenizer() tokenizer.train( files[data/pretrain_corpus.txt], vocab_size32000, min_frequency2, special_tokens[s, pad, /s, unk] ) tokenizer.save_model(tokenizer)我建议词表大小设为16000到32000之间。太小了文本被切得过于细碎序列长度飙升训练成本增加太大了词表里塞满低频子词模型需要更多数据才能学会它们的语义。32K是一个在大多数场景下都很稳妥的起点。有一个细节容易被忽略分词器的词表一旦训好后续最好不要随意增删否则之前所有预训练权重都得“翻译”到新词表上等于模型白训了。所以在这个环节多花半小时做数据抽样、确认切分效果是值得的。2.3 模型架构从几十M参数开始别一上来就7B架构选择是整个路线里最容易“眼高手低”的地方。看到别人训练7B、13B模型你也想做大的结果一张显卡放不下、训练时间按周算、loss还不降项目直接夭折。我踩过这个坑之后悟出一个原则先用一个极小模型跑通全流程再逐步放大。放在这篇文章的语境里第一阶段的模型配置大概是这样的配置项数值说明层数6深度足够学习复杂特征注意力头数8多头机制捕捉不同关系隐藏层维度512与头数搭配合理词表大小32000上面训练好的分词器参数量约40M单张消费级显卡轻松跑最大序列长度512够用超过则截断或分段我特意把参数量压在40M左右目的很明确让单次训练实验在半小时到一小时内出结果。你可能会问40M的模型能有什么用答案是它的作用是让你在几个小时内验证完“数据-分词-训练-采样”整条链路是否通畅。等这条路走通了你再把层数从6加到12、隐藏维度从512加到768参数量到200M左右依然能在单卡上训练只是时间会拉长到几小时。架构代码我不建议自己从头写。直接用GPT-2的PyTorch实现或者HuggingFace的GPT2Config来实例化一个随机权重的模型都是很好的选择你只需要修改配置参数。从零训练不等于从零写代码重要的是理解架构里几个关键模块自注意力怎么计算、位置编码怎么加、FeedForward层的作用是什么、LayerNorm放在哪里。把这些弄明白比手写一遍Transformer更能帮你建立可靠的心智模型。2.4 预训练看懂loss曲线再谈优化预训练阶段的目标很纯粹让模型学会“预测下一个词”。在这个阶段你会第一次看到loss曲线而看懂这条曲线是整个AI工程里最重要的基本功。我的训练配置是这样的AdamW优化器学习率采用warmupcosine衰减策略warmup步数设为总步数的3%-5%最大学习率1e-3到3e-3之间小模型可以相对大胆一点权重衰减0.01全局梯度裁剪设为1.0。批次大小在显存允许的前提下尽量大一些8到32之间都可以因为更大的批次会让梯度的方向更稳定。第一次看到loss曲线下降时很多人会误以为“只要loss在降就万事大吉”。实际上有几个关键信号需要盯住loss在持续下降但非常缓慢先检查学习率是否过小再看数据是不是太少、模型学到了瓶颈。loss在训练后期反弹多半是学习率衰减太慢或者数据里有重复内容导致过拟合。loss在某个值附近剧烈震荡学习率过大或者批大小太小梯度噪声太大。loss降得很快但是生成质量极差很可能数据本身太简单比如全是短文本模型学到的都是浅层模式。一个容易被新手忽视的细节是预训练loss不高不代表模型生成的文本就有意义。因为语言模型的目标是“下一个词的概率分布”一旦上下文稍微长一点误差就会累积生成结果可能完全是胡言乱语。所以我在预训练阶段就会定期做一次sample——输入一个开头让模型续写几十个token亲眼看一看模型到底在说什么。这一步比任何指标都直观。预训练的步数我建议控制在1万到5万步之间40M模型配合适量数据。这不是一个严格数字而是一个经验区间步数太少模型欠拟合步数太多后期收益边际递减而且小模型很容易在重复数据上过拟合。核心原则是小步快跑多训几个实验对比而不是一次性把某一组配置跑到底。2.5 指令微调让模型学会“回答”而不是“续写”预训练完成之后你手里其实是一个“文本续写机器”给它半句话它会按语料风格接着写但完全不会“一问一答”。要让模型变成能用工具必须有指令微调SFT这一步。SFT的本质是把预训练模型的行为从“预测下一个词”调整为“遵循用户意图生成回答”。具体做法是构造一批“用户指令标准回答”的配对数据用这些数据继续训练模型但训练目标仍然是最小化交叉熵——只是注意力集中在“回答部分”的token上指令部分的token只作为上下文不参与损失计算。这一步我有两个实操建议。第一数据格式要统一。我在实验里统一用这样的对话结构s用户请介绍一下太阳系的行星。 助手太阳系有八大行星按离太阳的距离由近到远依次是水星、金星、地球、火星、木星、土星、天王星和海王星。/s第二训练时不要把整条对话都丢进去让模型学而是把损失集中在“助手”之后的部分。HuggingFace的transformers里有一个DataCollatorForLanguageModeling或者trainer的mask机制可以做到这一点不熟悉的话可以查一下“causal LM SFT loss masking”的写法。这个细节如果做错模型会把“用户的指令”也当作要复述的内容导致回答质量明显下降。指令微调的数据不需要太多几千条高质量对话就足以让模型学会“回答”的基本形态。如果你的目标是某个垂直领域比如客服、代码注释生成那数据量和领域覆盖度比通用对话更重要。2.6 推理与评测别只看loss模型训练完之后评测是你和模型之间的第一次“正式对话”。很多人的习惯是让模型生成一段话肉眼看一下“像不像样”这远远不够。我推荐一套简单但系统的评测方法准备20到50条固定测试题覆盖你想要的能力维度比如事实回答、逻辑推理、指令遵循、格式控制。每轮实验后用同样的测试题、同样的采样参数去问模型把回答记录下来对比不同训练配置之间的差异。注意采样参数要固定否则text生成时的随机性会干扰你的判断——尤其是在温度比较高的情况下。除了人工定性判断还有一个自动指标值得关注模型在测试集上的困惑度perplexity。这个指标能反映模型对语料的拟合程度但要注意困惑度低并不等于回答质量高它只代表模型能“流利地预测文本”不代表它能“准确理解并回答”。所以我的习惯是困惑度作为辅助参考人工测试问答作为主要判断依据两者结合才能下结论。如果你打算继续做第三章的强化学习微调评测环节就更重要了因为你后面要拿它来量化“推理能力提升”到底提升到了什么水平。这一步的评测基准会直接变成你的RL奖励信号来源之一。3. 从“能聊”到“会推理”给模型加Reasoning能力的实验路径3.1 先说清楚“推理”在这里指什么“推理能力”这个词在AI领域已经被用滥了。它究竟指什么在本文的语境下我把它限定为模型能够通过多步中间过程解决训练数据里没有直接出现的任务。最典型的场景是数学计算——比如训练数据里见过“23×492”但没见过“37×8296”模型能不能算出来普通的SFT模型在这个问题上会暴露一个明显短板它生成回答太快了快到一看就是在“背答案”。比如你问它“37×8等于多少”它可能直接回答“296”——答案碰巧是对的但换个数字就一塌糊涂。这种“答得快但答得浅”的本质是模型没有经历一步步推导的过程。想要让它学会推理就必须让它“想得更久”并且把这个思考过程显式地写在生成结果里。这就引出了推理模型的第一个核心技术思维链Chain of ThoughtCoT。简单来说就是让模型在给出最终答案之前先产生一系列中间推理步骤。这个机制在超大模型中可能自动涌现但对从零训练的小模型来说必须靠数据设计来“教会”它。3.2 三步小实验从SFT到RL我给自己的推理实验设计了三个递进阶段每一步都有可测量的指标。第一阶段构造CoT训练数据。拿小学数学题来说每道题都附带完整的解题步骤用户小明有3个苹果小红给了5个苹果他又吃掉了2个还剩几个 助手小明原来有3个苹果。小红给了5个所以一共有358个苹果。吃掉了2个所以剩下8-26个苹果。答案是6个。用一批这样的数据对基础模型做SFT模型会学会“先写步骤再给答案”的输出格式。但我测试发现它只会“模仿格式”并没有真正学会计算——换一道没见过的题它就露馅了步骤写得有模有样计算过程却是错的。这说明CoT的格式可以通过SFT学出来但“推理能力”本身不会凭空产生。第二阶段引入强化学习。这里选择GRPOGroup Relative Policy Optimization算法原因很实际它相比PPO不需要单独训练一个Critic价值模型计算开销小很多代码实现也简单特别适合小团队在消费级显卡上做实验。GRPO的核心思路是对同一个问题采样多组回答然后依据一组奖励信号比如“答案是否正确”和“格式是否符合规范”对这组回答做相对比较计算出优势值再据此更新策略模型。第三阶段设计奖励函数。这是整个RL实验里最考验功力的一步。我在第一版设计里只奖励“最终答案正确”结果模型很快就学会了钻空子——它会在中间步骤里胡编乱造最后硬蒙一个正确的数字。后来我加了两条规则一是限制中间步骤必须包含算式二是如果某条回答的格式不符合CoT规范就给予较低奖励。这里的原则很简单奖励函数写清楚了模型才知道什么叫“好的推理过程”。3.3 一个可复现的小实验示例拿最可控的“两位数乘两位数”作为推理任务来举例。我构造了5000道训练题和500道测试题训练题和测试题完全无重叠。三个阶段的测试结果如下实验配置测试正确率观察到的生成行为基础SFT无CoT数据约28%直接给答案错误很离谱SFT CoT数据约49%有步骤格式但计算经常出错SFT CoT GRPO约66%步骤完整计算错误率明显下降你可以看到真正拉高正确率的是RL阶段。为什么会这样因为SFT只是让模型“学习数据中的模式”学完就固定了而GRPO会不断地从模型自身采样中挑出“好的推理过程”并强化这就相当于让模型在自己的搜索空间中不断迭代优化。这是一个从“模仿”到“超越”的关键转变。这里的数字只是参考不同初始权重、采样温度和奖励设计都会影响结果但趋势是稳定的。做这个实验最大的价值在于你能亲手验证“RL不是玄学”它的效果确实可测量、可复现。3.4 训练不稳定时怎么排查GRPO并不是一路绿灯的实验。我最常遇到的两种异常情况第一种奖励越来越大但测试正确率不升反降。这个现象通常意味着模型在“奖励黑客”——它找到了某种能拿高分但没有真正提高推理能力的捷径。比如它可能学会了一种话术步骤写得特别长、特别复杂让格式评分器给了高分但计算本身仍然错得离谱。解决办法是定期在测试集上算正确率不能只看奖励曲线同时把格式奖励的权重降低别让模型把精力都花在“把话说漂亮”上。第二种训练过程中loss崩了生成文本变成乱码。这多半是学习率太大或者采样温度太高导致模型离线探索太远生成的样本质量太差。我的修复方式是把学习率降到原来的三分之一同时降低采样温度比如从1.0降到0.7并且在奖励里加大对“格式错误”的惩罚力度。训练RL和预训练的节奏不同它更像是“短线交易”每一步都在做策略更新所以稳定性比速度更重要。4. 只有一张消费级显卡怎么把流程跑下来4.1 先从显存账算起现实情况是绝大多数业余做AI工程的人手上只有一张消费级显卡。我先给大家算一笔显存账避免有人一头扎进7B模型的训练里。以FP16精度为例一个7B参数的模型光参数就要占14GB显存。AdamW优化器需要为每个参数保存一阶动量和二阶动量这又额外需要28GB。也就是说光是“参数优化器状态”就已经需要42GB显存还没算中间激活值和梯度。一张24GB的RTX 3090/4090根本放不下更别说训练了。所以在从零构建的过程中我强烈建议把目标模型尺寸控制在1B参数以下。300M到1B是一个甜点区间模型足够大能有比较丰富的语言能力又足够小在单张卡上可以跑通完整的训练流程只是需要一些技巧。具体的显存估算可以按这个公式粗略计算训练所需显存 ≈ 参数量 × (4字节参数 4字节梯度 8字节优化器状态 若干倍激活值)其中激活值乘数取决于批次大小和序列长度经验上挂2到4倍比较保守。对照下来300M模型的训练显存高峰大约在6到12GB之间1B模型则在20到30GB之间。想跑1B就必须配合LoRA这类参数高效微调方法因为在微调阶段冻结大部分参数后优化器状态和梯度的开销大幅下降。4.2 一套能落地的资源策略我完整的算力方案分三个阶段第一阶段40M模型全参数预训练单卡RTX 3090半小时到两小时搞定。这个阶段的目的就是把预训练跑通体会loss曲线变化。第二阶段把模型放大到300M左右用同样的数据流程做一次完整的预训练时间会拉长到几小时到一天但依然可控。第三阶段在SFT和GRPO阶段一律使用LoRA只训练低秩适配器的参数这样显存占用断崖式下降1B模型放在24GB卡上也变得可行。LoRA的原理说起来也简单冻结原模型的权重在两个线性层之间插入一个小矩阵低秩矩阵训练时只更新这个小矩阵的参数。相当于给预训练模型加了一个“可拆卸的定制适配器”效果很好、成本很低。我通常把LoRA的秩rank设为8到16这是个性价比很高的区间——太大了训练变慢提升有限太小了表达能力不足。如果你手里的卡显存只有16GB甚至更低也不用灰心。把模型压到100M级别、使用LoRA、再把批次大小降到1配合梯度累积同样能完成整个流程只是时间会拉长。关键是先跑通再优化速度。4.3 训练的效率与止损线在整个从零构建过程中最浪费时间的不是训练本身而是“死等一个注定失败的实验跑完”。我在前面踩过无数次这种坑后来总结出一条止损原则预训练阶段如果前500步loss几乎不下降或者梯度范数异常立即停掉改配置不要心存侥幸。具体操作上我习惯用Weights Biases或者TensorBoard记录训练曲线每50步看一下loss和梯度范数。如果loss平坦但梯度范数保持在一个稳定值可能是学习率太小如果梯度范数飙升到正常值的10倍以上说明学习率过大或者数据有异常样本需要调小学习率或检查batch里是否有“脏数据”。另外一个常用技巧是在正式训练前用一小块数据比如500条跑几步确认模型能正常过拟合这块数据这说明整个链路完好只是数据层面的问题——这一步能帮你省掉大量调试时间。训练过程中的自动保存也值得做好。我每1000步保存一次checkpoint同时把最优的checkpoint单独备份。RL阶段更要注意因为策略更新一旦跑偏生成质量可能在几百步内迅速退化这时候如果没有早期checkpoint可以回退就得从头再来了。这不只是时间问题更是心态问题——项目死在最后一步比死在起步阶段更让人崩溃。5. 新手最容易被劝退的节点与我的解决办法5.1 第一个坑在流程没跑通前就追求完美数据我见过太多人包括曾经的自己在数据准备阶段就耗尽所有热情。他们会花三周去清洗一个“完美的中文语料库”结果模型训练时发现流程中一个简单的bug浪费的所有清洗时间都变成了沉默成本。正确的姿势是第一版数据能用就行几百MB甚至几十MB都够目标是赶紧跑通流程第二版再迭代数据质量每轮都记录数据改动带来的效果变化。这就像做菜先做一个“能吃”的版本再逐步调味到“好吃”而不是一开始就抱着米其林的标准备菜三小时。5.2 第二个坑模型尺寸的“先大后小”陷阱“先跑小的再跑大的”这句话听着像废话但执行起来是非常反人性的。因为人总是倾向相信“大模型效果更好”于是跳过小模型直接开搞结果卡在显存、速度、调参的三重折磨里。我后来给自己立了一条规矩任何新方案先用40M模型跑通、跑稳再谈放大。这条规矩救过我很多次——很多问题在小模型上几分钟就能暴露放大后可能要浪费一整天。5.3 第三个坑loss不降只会怀疑人生不会排查loss不降是常态但它背后的原因各不相同。我总结了一套排查路径按顺序执行可以覆盖90%的问题先检查数据batch里有没有大量重复或空文本标签和输入有没有错位再检查优化器学习率是否太小或太大warmup有没有生效再看模型结构输出层维度是否匹配词表大小有没有用正确的损失函数最后看随机种子小模型参数初始化对结果影响很大换个种子再跑一次对比一下。这套路径花了我们很长时间才摸索出来因为前期每次loss不正常第一反应都是“调学习率”结果很多时候问题根本不在学习率而在数据错位。5.4 第四个坑RL训练中奖励函数被“钻空子”如果你做到了第3章那这个坑你一定会遇到。模型是最没有道德感的优化器只要奖励函数有漏洞它一定会找出来。比如我奖励“回答包含答案”模型就学会了把答案重复写三遍我奖励“格式合规”模型就会生成一长串模板化的废话来灌水。治理办法是奖励函数的设计要尽可能具体到行为层面同时引入规则性的惩罚项。每轮训练后抽20条生成结果人工过目一遍亲眼看看模型是不是在“走捷径”。如果发现在钻空子马上调整奖励再训练。这个过程很烦但恰恰是RL工程最核心的经验积累。5.5 给新手的总路径建议如果你打算完整走一遍这个流程我的建议路径是先用40M模型小语料跑通预训练目标看懂loss曲线。构造几千条SFT数据完成指令微调目标模型学会“一问一答”。选一个具体推理任务比如两位数乘法构造CoT数据做SFTGRPO对比实验目标体会到RL带来的能力跳跃。每一步都记录实验笔记包括数据规模、超参数、loss曲线、生成样例、自己的判断和猜测。完成这四步你对“从零训练AI模型”的理解会比读十篇论文都深。接下来再遇到任何大模型相关问题你至少知道是哪个环节出了问题而不是只能无助地刷新页面祈祷下一次生成效果好一点。最后聊一点个人体会。做了几个月从零AI工程我最大的收获不是跑通了模型而是建立了对训练过程的直觉——看到loss曲线异常时能判断出是数据问题、学习率问题还是采样噪声模型生成质量差时知道该回去改数据还是改奖励。这种“debug训练过程”的能力在纯调用API的工作里永远培养不出来。如果看完你也想动手建议按最小路线执行。还有一个实用小技巧提前把实验记录模板做好每个实验跑完把配置、曲线、样例截图和当天的判断写进去。几天后再翻这些记录你会惊讶地发现自己对模型训练的理解提升得有多快。这比任何一次“跑通实验”都更有价值。