ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从零构建LLM的工程实战:数据、训练与推理全解析

从零构建LLM的工程实战:数据、训练与推理全解析 最近《Build a Large Language Model (From Scratch)》这本书在国内开发者圈子里火得不行连带着ai-engineering-from-scratch这个话题也跟着热起来。不少朋友拿着书来问我读完这本是不是就能从零搓一个GPT出来我的回答一直是能但你要做的不是复现代码而是建立一套完整的工程直觉。真正从零开始干过一遍的人都会明白训练一个模型只是起点数据、算力、评估、部署、迭代每一步都会教你怎么做人。这篇文章我就把这一路踩过的坑和摸出来的方法摊开聊给想往底层走的朋友一条能落地的路径。适合谁看如果你正在用现成的API做应用却总感觉隔着一层纱或者你刚读完一两本LLM的书想知道下一步该怎么从看书过渡到真训一个模型——那这篇就是写给你的。1. 为什么从零开始不是造轮子而是AI工程师的地基1.1 会调API不等于会做AI工程先说个扎心的事实现在市面上大量挂着AI工程师头衔的人实际工作就是把Prompt调来调去再包一层FastAPI。这没什么不好应用层本来也有价值但它不叫AI工程叫AI应用开发。真正的AI工程是你得能回答这几个问题数据里的噪声怎么影响loss曲线为什么同一个模型换一组数据训练就崩显存不够时除了减小batch还能怎么办评估指标显示变好了实际业务为什么反而变差了这些问题的答案全藏在from scratch的过程里。就好比你会开车不代表你会修车但你要是自己把一辆车从零件组装起来过再遇到异响和抖动至少知道该检查悬挂还是发动机而不是直接打救援电话。我说句实在话我用过不少现成的模型服务也做过微调但真正让我对AI工程有体感的转折点是我自己从头训了一个1亿参数的小模型。那个模型放到今天连玩具都算不上但它让我把前向传播、反向传播、学习率调度、数据配比这些抽象概念全部串了起来。从那之后再看任何论文和框架感觉完全不一样。1.2 从零开始的三个层次复现、改进、创新很多人对from scratch的理解过于单一觉得就是把论文里的公式变成代码。我的经验是它至少有三个层次对应三种不同的能力复现层照着开源代码把一个小规模的GPT训练出来理解每一行代码在干什么能改超参数并观察效果。这一层解决的是我会用工具的问题。改进层在复现的基础上针对某个环节做优化。比如改数据采样策略、换位置编码、加新的正则化手段并且能设计实验证明你的改动确实有效。这一层解决的是我能做判断的问题。创新层当你的改进积累到一定程度你开始能提出自己的想法设计新的架构或训练方法。这一层解决的是我能做研究的问题。绝大多数人卡在第一个层次和第二个层次的交界处。原因也很简单复现看起来容易但真跑起来全是坑。数据清洗的细节、分布式训练的通信开销、评估指标的波动每一个都能让你怀疑人生。但也正因为有这些坑才逼着你去理解原理。我强烈建议每个想入行的人至少完整走一遍第一层次再尝试触碰第二层次。2. 从零构建LLM必须掌握的五个知识主干2.1 数据工程模型吃进去什么决定它吐出什么很多人一想到从零训练第一反应是我要用什么架构但真正做过项目的人会告诉你数据才是最耗时间、最影响结果的部分。我自己的统计是一个训练项目里数据清洗和预处理往往占掉一半以上的时间。先说数据集配比。你不可能只拿一种数据源训练出好模型市面上成熟的开源模型基本都遵循类似的原则通用网页文本提供语言能力代码数据强化逻辑性数学和科学数据训练推理高质量问答数据做对齐。这个配比直接决定模型的性格。我见过有人用90%的代码数据训出来的模型写代码很溜但通用对话一塌糊涂因为它的世界知识太少了。然后是清洗环节。别小看这一步我踩过一个很蠢的坑数据集里混了大量重复文本导致模型训练到后期出现严重的复读现象。后来用MinHash去重跑了一遍问题立刻缓解。另外HTML标签、无意义字符、敏感信息都需要处理这些细节虽然不性感但每一个都会在最终效果上体现出来。一个可参考的处理流程是编码检测与统一、HTML标签剥离、重复内容去重、基于规则的毒性过滤、按长度和质量分数过滤。最后是数据配比里的epoch控制。大型数据集通常每个epoch只过一遍甚至数据集太大一个epoch都过不完但小数据集如果反复过多个epoch就很容易过拟合。我常用的做法是把高质量数据重复2-3次低质量数据只过0.5次甚至直接丢弃让模型在有限的算力预算内见到更多有效信息。2.2 架构与分词理解tokenizer和transformer的底层交互架构这一块推荐从GPT-2的小规模复现入手因为它足够简单、经典而且你能找到海量参考实现。但很多人把注意力全放在多头注意力和前馈网络上忽略了tokenizer的作用这是一个大误区。Tokenizer是你的模型认知世界的字母表它的词表大小直接影响两个东西序列长度和嵌入维度。词表越大每个token承载的信息越多但在相同上下文长度下能看到的文本越少词表越小句子被切得越碎序列变长计算量也随之增加。以GPT-2为例它的词表是50257上下文是1024如果你要复现一个类似规模的模型这两组数字之间需要做好平衡。除了词表大小还需要理解tokenizer对推理能力的间接影响。一个常见的现象是数字相关的任务效果差往往是因为tokenizer把连续数字切成了不规则的片段。很多模型在数学题上表现糟糕根因其实在分词阶段就埋下了。做reasoning model的人对此感触更深因为推理任务高度依赖符号的正确切分和理解。我还建议你动手训练一个自己的BPE tokenizer哪怕只是在一个几MB的小语料上。这个过程的收获很大你能直观体会到词表扩容、合并次数、罕见token处理对序列长度的影响比看十篇讲tokenizer的博客都管用。2.3 训练算法与评估闭环不仅仅是梯度下降把数据准备好、架构搭起来接下来就是训练。但训练不是简单地写个循环跑loss而是需要一整套调度策略。先说学习率。完全不设warmup的学习率在训练初期很容易让loss炸掉因为随机初始化的模型参数对应的梯度方向几乎是噪声以较大学习率更新会把参数推到一个很差的区域。我惯用的方案是前1%-2%的steps做线性warmup从0上升到峰值后面用余弦退火逐步降到接近0。峰值学习率的选择和batch size强相关一般按经验公式lr_peak base_lr * sqrt(batch_size / reference_batch_size)这个base_lr通常取1e-4到3e-4之间。再说评估闭环。训练过程中光看训练loss是不够的你还需要一个和训练数据分布不一样的验证集。每训练几千步就做一次验证记录loss和下游任务的表现。这里面有一个非常容易踩的坑验证集如果是从训练集里随机切出来的你可能会高估模型泛化能力因为数据之间可能存在重复或近重复。正确的做法是把去重后的数据按固定间隔比如按文档ID哈希划分验证集确保见过的文本不会出现在验证集里。3. 从读书到能跑用开源方案把理论落成工程3.1 选型思路GPU、框架、数据规模怎么配书里的代码跑起来和真正按工程标准训练是完全不同的两套玩法。你需要先想清楚自己手里有多少算力以及期望达到什么效果。我见过不少人的第一反应是我要租一堆A100集群但我不建议一上来就把规模拉满。原因很简单算力越强出错后犯错的代价越大。我的建议路线是分三步走第一步单张消费级显卡比如RTX 3090/409024GB显存训练参数在0.5亿-1亿左右的模型把流程彻底跑通。第二步租用1-2张A100/H100训练5亿-20亿参数的模型体验一下多卡数据并行和分布式通信的开销。第三步如果真的有需求和预算再做更大规模的尝试。框架选型上我不太推荐新人一上来就啃PyTorch最底层的实现。直接用transformers库虽然方便但它们封装太深很多东西会被自动处理你反而不理解。比较折中的做法是用nanoGPT或者llm.c做底子这是Andrej Karpathy的开源项目代码量小、结构清晰能很好地看到从数据加载到模型训练的全过程。跑通之后再逐步迁移到自己组织的工程代码。数据规模可以参考一个比例假设你打算训练1亿参数的模型对应的训练token量大概在1B-2B这个区间比较合理也就是10到20倍参数量的数据。这个比例虽然不能和顶级大模型动辄几百倍数据量相比但对于一个学习项目来说已经足够看到记忆和泛化的分化。3.2 用nanoGPT跑通最小复现一次完整的实战记录以nanoGPT为例整个流程大致是准备数据、训练tokenizer、预处理为二进制格式、修改配置文件、启动训练、监控loss和采样输出。具体说几个容易卡住的细节第一数据格式。nanoGPT默认直接用train.bin和val.bin预处理脚本会把文本编码成token id序列并保存为二进制。新手最容易忽略的是这个二进制文件的读取是内存映射memory-mapped的意味着你的数据集大小可以超过物理内存很多倍只要磁盘空间和缓存能兜底就行。这个设计在大语料上非常重要别因为下载数据太大而不敢跑。第二配置文件。config/train_gpt2.py里有gradient_accumulation_steps这个参数它解决的是显存放不下大batch的问题。假设你想要的等效batch size是512而单次能塞下的样本是16那gradient_accumulation_steps就设为32。原理是把多个小batch的梯度累加起来再更新一次参数效果上接近大batch训练。但有个细节梯度累积模拟的大batch和真正用大batch训练是有细微差别的因为BatchNorm如果用到的话的统计量不同不过Transformer里基本没有这个问题。第三断点续训。训练时间长断电和宕机是家常便饭。从第一天就应该设计checkpoint机制定期保存模型权重、优化器状态、学习率调度器的位置step数以及RNG状态。只保存权重不保存优化器状态会导致重启后训练波动很大。跑通之后我建议你做一个抽样对比实验固定训练数据不变分别用不同随机种子训练两个模型观察它们的输出差异。你会发现即使数据完全一样模型生成的内容也可能大相径庭。这个现象会帮你建立对模型即压缩的直觉——训练过程的随机性本身就是一种隐式正则化。3.3 从玩具到可用必要的工程化改造nanoGPT跑通之后距离一个可用的工程还有很长的路要走。我自己做过一轮改造值得分享的有这么几个点自定义数据集格式nanoGPT默认不吃JSONL需要自己写转换脚本。建议从一开始就把数据流水线做成配置文件驱动而不是硬编码路径。多机多卡支持如果只是单机多卡用PyTorch自带的DistributedDataParallel就够。但一旦跨节点通信开销会变得很突出这时候需要考虑梯度压缩和异步并行策略。日志与可视化别再用print看loss了。把指标写到wandb或者TensorBoard里尤其是学习率曲线和梯度范数。梯度范数异常往往是训练崩溃的前兆。这些改造看似琐碎但正是它们把复现代码变成工程能力的分水岭。4. 构建一个善于推理的模型和训练LLM有什么本质区别4.1 推理模型的概念拆解从零构建reasoning model到底在构建什么热搜词里有一条是build a reasoning model from scratch很多人望文生义觉得这就是训练一个会思考的模型。实际上从工程视角看推理模型的核心差异不在架构而在训练范式和数据组织方式。从架构上说reasoning model和普通LLM一样是decoder-only的Transformer没有本质区别。真正的差异在于推理模型需要模型在输出最终答案之前生成一段思考过程通常叫CoT思维链。这段思考过程不是人类写的提示词而是模型自己生成出来的中间推理步骤。为了让模型学会生成有效的思考过程我们需要在训练数据里专门构造这类样本并通过特定的训练方式强化这种能力。这里的关键问题是**如何让模型知道要先生成思考过程再给出答案**最常见的做法有两种第一种是在数据里显式标注推理过程用监督微调SFT让模型学习。比如一个数学题的样本输入是题目输出先是Let me think step by step...然后是一系列中间推导最后是答案。这种方式优点是可解释、可控缺点是依赖人工标注或从大型模型蒸馏且模型可能只是背下来推理模板并没有真正的推理能力。第二种是让模型自己生成推理路径再用某种策略优化。比如RLVRReinforcement Learning with Verifiable Rewards让模型自由尝试多种思路只要能给出正确答案就给予正向奖励探索过程中自然而然强化了有效的推理模式。这种方式更贴近OpenAI的o1系列和DeepSeek-R1的训练思路也是目前做推理模型的主流方向。4.2 一套可落地的小推理模型训练路线如果你只想从零跑通一个推理模型不用冲到几百亿参数我给你一条尽可能精简的路径第一步准备推理密集数据集。以数学题和逻辑题为主比如GSM8K的英文原版配合代码生成类任务。重点不是题量多大而是每一道题的答案校验必须是确定性的。这直接决定了后面RLVR能否自动化运作。第二步先用一个基础模型做SFT。基础模型可以是Llama-3.2-1B或Qwen-2.5-1.5B这类小模型。SFT阶段的目标是让模型学会遇到题就产出CoT并给出答案的格式。这个阶段的loss不需要太低重点是格式正确率。第三步接入RLVR。这里需要写一个环境模块模型每输出一个answer就程序化验证它是否和标准答案一致或者用Python执行一段生成的代码来验证。奖励信号就是0或1。训练算法可以用PPO或者更轻量的GRPO。GRPO在7B以下模型上效果稳定实现起来也相对简单不需要一个庞大的critic模型。第四步多轮迭代。训练中要定期采样模型的输出观察CoT质量。一个常见现象是模型会走捷径输出一些看似推理、实则胡扯的内容碰巧猜对了答案。这就说明奖励设计不够严格需要给不诚实的推理设置惩罚或者改用更严格的格式校验。4.3 推理能力评测为什么只看准确率远远不够推理模型上线前评估环节比训练还要让人头疼。准确率是最直观的指标但它掩盖的问题非常多。第一个问题是过程正确性。模型可能最终答案对了但推理过程中有严重错误比如逻辑跳步、捏造计算过程。如果我们只按答案评分模型的推理链质量会越来越差。所以我的习惯是设置推理链抽查机制定期人工抽看几十条CoT把明显不合理的样本标记出来作为bad case回灌到训练集里做负样本。第二个问题是分布外泛化。很多模型在训练数据分布内表现优异换个题型就立刻崩盘。建议在评估集里专门混入和训练集不同风格、不同难度的题目观察准确率掉多少。这个掉幅基本反映了模型的真推理能力而不是记忆能力。第三个问题是推理成本。CoT会显著增加输出token数量。同一个问题普通模型生成80个token推理模型可能要生成500个token。这会导致推理延迟和成本飙升。在实际应用中要对复杂任务使用推理模型、简单任务使用普通模型做分层路由不然成本上吃不消。5. 从零训练时最容易被忽视的工程细节与踩坑实录5.1 loss尖峰与训练崩溃一个完整的排查链路训练过程中的loss曲线不会一直是平滑下降的。我遇到过一个非常典型的loss spike尖峰问题排查链路相当有价值写出来供参考。现象训练到第8000步左右loss突然从2.3跳到15然后怎么都回不来。排查第一步先确认是不是数据问题。我用采样脚本检查了当前正在训练的数据区间结果数据正常没有异常脏数据。这一步把数据嫌疑排除。排查第二步看梯度范数。如果在loss spike之前梯度范数出现了指数级增长说明模型参数已经进入了一个悬崖区域。我复现后发现spike前100步梯度范数确实在稳步上升。于是怀疑是学习率在峰值附近过大加上batch内出现了一些极端样本。排查第三步上网查类似问题发现高学习率配上大批次的hard negative容易出现这种梯度爆炸。解决方案是给梯度裁剪加一个较低的最大值比如max_grad_norm1.0同时把warmup步数加倍让学习率上升得更平滑。排查第四步为了保险我把optimizer的状态从spike前的checkpoint恢复重新用调整后的配置继续跑。验证发现loss不再跳动。这个案例非常有代表性因为三个环节数据、学习率、裁剪中任何一个都可能引爆问题没有系统的排查顺序你只能猜。5.2 显存优化的优先级从降低精度到重新设计batch24GB显存想训练一个稍微像样的模型总能在某个时刻爆显存。我的显存优化清单按性价比排序是梯度累积先用这个改动最小效果立竿见影。它不减少算力需求但能把显存峰值压下来。混合精度训练torch.cuda.amp几乎是无痛迁移显存能省一半速度还可能更快。代价是偶尔数值不稳需要配合loss_scaling缓解。激活函数重计算gradient checkpointing前向传播时丢弃中间激活反向传播时重新计算用时间换显存。这个操作能省1/3左右的显存但训练速度会下降20%-30%。模型并行/分片这是最后的武器。模型并行需要把Transformer层切到不同GPU上通信开销很大小模型不建议做除非你确实需要突破单卡极限。还有一个反直觉的点很多人以为减小seq_len能省大量显存但它对训练效果的影响非常大。序列长度直接决定模型能学到多远距离的依赖太短的话训练出来的模型在处理长文档时几乎不可用。我倾向于在数据预处理阶段按序列长度分桶bucketing把长短不一的样本分组最大化单卡能容纳的有效token数。5.3 数据泄漏与灾难性遗忘两个容易毁掉模型的隐形杀手先说数据泄漏。预训练数据直接从网上爬很容易混入评测集的内容。比如你想在HellaSwag上评估模型但预训练数据里恰好包含了HellaSwag的样本那评估分数再高也没有意义。正确的做法是开一个评测集文本指纹列表在你的语料库构建时主动去重一旦发现有评测集文本的连续子串直接丢掉。这个流程很琐碎但不做的话你后面所有实验结论都可能是错的。再说灾难性遗忘这在做SFT或者RLVR时最容易出现。模型在推理数据上训练了一阵子通用对话能力大幅下降。这是因为新数据的梯度方向覆盖了旧知识。缓解手段有这么几个把通用语料按一定比例混入训练batch比如10%的通用数据90%的推理数据降低微调部分的学习率以及使用LoRA之类的参数高效微调只更新部分参数尽量保住预训练学到的知识。我在做推理模型SFT时就吃过这个亏——模型数学变好了但连写一封邮件这种基础任务都不会了后来混入通用语料才算救回来。6. 最后聊聊我的真实体会从零开始到底值不值经常有人问我花这么多时间和算力去从零训练一个模型意义在哪反正又不比开源模型强。我理解这种想法但我个人的体会是这个过程最大的产出不是模型本身而是你脑子里的调试直觉。现在我拿到一个新的开源模型不会像以前那样直接拿上去跑而是会先看它的tokenizer词表、数据配比、训练超参数大致猜到它擅长什么、短板在哪。遇到bad case的时候我也能更快判断问题出在训练数据、模型结构还是解码策略。这些判断能力全部是在from scratch的过程中一点点积累出来的。如果你也想走这条路我最后给几条建议第一从小模型开始1亿参数已经足够学到80%的工程知识第二坚持写实验笔记把每个超参数改动和对应效果都记录下来你会形成自己的手感第三不要盲目追求复现大模型先把小模型做扎实再一步步放大。这条路没有捷径但也没有想象中那么遥不可及。祝你能在训练日志里看到loss曲线缓缓下降的那一刻那是这个领域最独特的成就感。
返回列表