ARTICLE DETAIL

资讯详情

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

从零构建大语言模型到推理模型:AI工程师实战路线图

从零构建大语言模型到推理模型:AI工程师实战路线图 如果你刷到这篇博客大概率和我一样不太甘心只当一个“调 API 的工程师”。过去两年我陆续用业余时间把ai-engineering-from-scratch这条路走了一遍——不是从零实现一个 PyTorch而是从原始数据、tokenizer、Transformer 训练一直到能跑通一个小规模的推理模型reasoning model。网上关于“build a large language model from scratch”的热度一直很高但真正动手的人少能坚持到把损失函数压下去并看到模型说出连贯句子的人更少。这篇文章就是把我踩过的坑、验证过有效的路线和为什么这样做一次说清楚。它适合两类读者一是对 LLM 原理有基本概念、想摆脱黑盒思维亲手训练一个模型的工程师二是想低成本复现“从零构建推理模型”这条技术路线的研究者。我不会只给结论会把你带进每一个关键决策的现场。1. 为什么“从零开始”不是炫技而是 AI 工程的基本功1.1 API 时代的能力错觉先泼一盆冷水。用 GPT-4 或者 Claude 写出很漂亮的 demo和真正具备 AI 工程能力是两回事。API 调用就像开自动挡汽车你能到达目的地但对发动机、变速箱的配合逻辑完全没有感知。一旦遇到线上问题——为什么同样提示词今天输出质量骤降、为什么长上下文下模型忽然“失忆”、为什么量化后某些数学题开始乱答——你就只能靠玄学调参。我亲眼见过一个团队产品全都架在闭源 API 上某天上游模型升级整个业务的输出风格变了他们连降级方案都没有因为没人知道模型内部发生了什么。从零训练过模型的人至少会懂得输出的变化可能来自采样温度、系统提示词、上下文长度、甚至 tokenizer 对特殊字符的处理。这个“懂得”就是基本功。1.2 从零复现的价值调试、评估与性能优化自己从零训练一个小模型第一个收获是你会真正理解损失曲线的语言。什么时候该涨却跌了什么时候明明在降但生成效果变差这些都是 API 调用永远教不会你的。第二个收获是评估能力。网上大多数人对模型效果的评价停留在“能不能对”但 AI 工程需要你定义什么是“对”数学题要看最终答案代码题要看能否编译运行开放问答要看内容安全性。这些评估体系只有在你亲手造模型、亲手标注数据时才能建立起来。第三个收获是成本直觉。API 按 token 计费你很难感知一个回答背后是多少算力。自己训练时你会清楚 1B 参数模型跑一次 forward 需要多少显存、微调一个 epoch 要多少卡时。这种感知在做技术选型和成本控制时极其重要。1.3 我走的完整链路从数据到推理模型我选择的技术路线是这样一条链路数据工程爬取、清洗、去重、配比Tokenizer从零训练一个 BPE 词表预训练训练一个 0.1B1B 的 base 模型指令微调构造 SFT 数据让模型学会对话推理能力用可验证奖励训练一个 reasoning model部署量化、推理加速、服务化每一步都有大量细节。我先把话说清楚所谓“from scratch”并不是连 PyTorch 的算子都要自己写而是不依赖任何闭源大模型为你生成数据和技术细节。计算框架该用就用但模型架构、训练脚本、评估方法必须是你自己可控的。这一点想明白后面才不会走偏。2. 第一块基石数据工程与 tokenizer决定上限的往往不是网络结构2.1 数据清洗去重比你想的重要得多很多人以为模型效果差是网络结构不够好我实际跑下来发现数据才是第一生产力。我在一次 0.3B 模型的预训练中前期用未清洗的网页数据loss 一直卡在 2.1 下不去后来做了去重和过滤同样算力下 loss 在 1.8 附近稳定。差距非常大。清洗管线我按这个顺序做编码检测与乱码过滤用ftfy修复常见 mojibake过滤控制字符。语言识别用fasttext的 langid 模型区分中英文按需配比。MinHash 去重对段落做 MinHash LSH重复内容直接剔除。公共爬虫语料里面重复率常常超过 30%这个步骤不能省。质量过滤用gzip压缩比作为启发式信号压缩比异常低的文本通常是列表页、导航栏这类低质量内容。PII 与安全过滤把邮箱、手机号、身份证号模式匹配后替换成占位符。这里有一个重要经验不要在主流程里直接改原始数据。每次清洗都生成一个新版本数据集并记录清洗前后的 token 数量变化。否则你根本不知道哪个环节提升了模型效果后续想做消融实验都无从下手。2.2 从零训练 BPE tokenizer 的实操要点我使用 HuggingFacetokenizers库训练 byte-level BPE核心配置如下from tokenizers import Tokenizer, models, trainers tokenizer Tokenizer(models.BPE()) trainer trainers.BpeTrainer( vocab_size32000, min_frequency2, special_tokens[pad, unk, s, /s], initial_alphabetpre_tokenizer.get_alphabet(), ) tokenizer.pre_tokenizer pre_tokenizers.ByteLevel() tokenizer.train(files, trainer)我在这个环节踩过三个坑逐一说明。第一个坑训练语料太少。BPE 词表需要足够的语料统计频率我只扔了 500MB 中文语料进去结果很多常用词被切得稀碎序列长度暴涨训练速度明显变慢。后来我把语料扩到 2GB情况才好转。经验是词表 16K32K 需要至少 12GB 清洗后的文本。第二个坑忽略了 normalizer。中文还好英文的 Unicode 全角字符、大小写归一化如果没做同一个词会出现多种形态词表被无效占位。务必在 BPE 之前加normalizers.Sequence。第三个坑特殊 token 的位置。unk不是越多越好base 模型如果用一堆特殊 token预训练时语料里几乎见不到它们微调阶段才开始接触模型容易表现怪异。我的做法是只在必要场景使用s、/s这类结构 token。2.3 数据配比的经验值多语言多领域数据怎么配比我推荐一个起点再基于验证集 loss 做调整数据域配比说明通用网页60%多样性来源覆盖常识书籍/长文15%长依赖能力降低困惑度代码15%结构化逻辑近年重要性上升数学/科学5%推理能力的基础对话/问答5%指令遵循的种子注意这里说的是预训练阶段的配比。如果你直接复用社区微调数据可能因为配比失衡导致模型只会聊天、不会做正经推理。我用一个简单办法验证预训练结束后分别跑一个语言建模测试集和一个小规模下游任务集看哪个方向损失偏高再回头调数据。3. 手写 Transformer 时最容易翻车的三个地方3.1 位置编码到底加在哪里、用哪种Transformer 本身没有顺序概念位置编码是必须的。现在主流已经不是绝对正弦编码而是旋转位置编码RoPE。我第一次实现时天真地以为“加在 embedding 上就行”结果训练不稳定。原因是 RoPE 是乘性的它的正确做法是在 attention 的 q 和 k 计算之后、点积之前对向量做旋转变换。用 PyTorch 实现 RoPE 的核心逻辑如下def apply_rope(x, cos, sin): # x: [batch, seq, heads, head_dim] d x.shape[-1] x1 x[..., : d // 2] x2 x[..., d // 2 :] return torch.cat([x1 * cos - x2 * sin, x1 * sin x2 * cos], dim-1)RoPE 的优势是相对位置信息天然蕴含在内外推性也比绝对编码好。我的建议是新项目直接用 RoPE别在绝对编码上浪费时间。实现时注意cos和sin的维度是[seq, head_dim]要分别对应 q 和 k 的序列长度。3.2 因果掩码的细节陷阱训练 decoder-only 模型时因果掩码causal mask是核心。问题出在很多人把 mask 加错了位置。它必须加在 softmax 之前的 attention score 上把未来位置的分数置为-inf而不是在输出层做截断。我第一次写的时候把 mask 用在了 attention 输出之后模型训练 loss 确实下降了但生成阶段暴露了问题——因为生成时是逐步解码根本没有“未来 token”可以看训练和推理行为不一致生成文本东一句西一句。排查半天才发现是掩码位置错误。正确代码如下scores torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(d_k) scores scores.masked_fill(mask 0, float(-inf)) attn torch.softmax(scores, dim-1)注意mask 0的位置会把整个未来行变成-infsoftmax 之后对应概率为 0这样就保证第 i 个 token 只看得到前 i 个 token含自身。3.3 数值稳定性LayerNorm、fp16 与梯度裁剪训练 Transformer 的经典难题是数值不稳定。我遇到过几次 loss 突然变成 NaN几乎都是这几个原因fp16 下 softmax 上溢出。解决实现时用max减去最大值再做 expPyTorch 内部已做但如果你手写 attention 要自己注意。学习率过大导致梯度爆炸。解决max_grad_norm1.0的梯度裁剪必不可少。初始化尺度不当。GPT-2 风格的初始化会对残差层做1/sqrt(2 * num_layers)比例的缩小这个细节很容易漏。还有一个容易被忽略的点pre-norm 和 post-norm 的选择。我的对比经验如下结构稳定性最终效果应用场景Post-LN训练后期易波动收敛效果理论更优经典 TransformerPre-LN训练稳定可加大学习率略低于 Post-LN但更稳现代 LLM 主流绝大多数开源大模型用的都是 Pre-LN如 LLaMA 系列因为它的梯度更容易流动适合超大批次训练。我建议你也直接用 Pre-LN省掉跟 NaN 搏斗的时间。4. 从语言模型到推理模型reasoning model 的从零构建路线4.1 先纠正一个误解不是所有推理能力都靠 RL“build a reasoning model from scratch”最近很热很多人以为推理模型就是把 RL 跑一遍。实际上推理能力有三个来源预训练时塞进去的代码和数学数据、SFT 阶段用的长思维链数据、以及 RL 阶段的可验证奖励。三者缺一不可。我在没有充足计算资源时仅靠前两步就能让一个 1B 模型在小规模数学题上显著提升RL 是锦上添花但绝非唯一答案。4.2 可验证奖励最朴素的 RL 训练流程如果你真的要训练一个能“想清楚再回答”的模型核心思路是这样的构造带推理过程的数据每道数学题包含逐步推导和最终答案。SFT 让模型先学会模仿这种长回答格式。RL 阶段用规则打分器做奖励最终答案正确得 1错误得 0。不训练额外奖励模型避免奖励黑客问题。我用过一个轻量实现参考了 GRPO 的思路流程如下对每个 prompt 采样 8 个 response。计算每组 response 的奖励均值作为 baseline。对每个 response 计算优势值(r_i - mean_r)。用 PPO 风格的 clipped loss 更新策略模型。这个流程的代码量不大但训练极其不稳定。我的经验是先冻结参考模型KL 惩罚系数从 0.01 开始调采样温度保持 0.71.0batch 不要太小至少 512 条 prompt。另外一个关键推理模型的 RL 数据只能选有确定答案的领域比如数学、代码。开放问答根本没有可验证奖励强行 RL 只会让模型学会钻空子——比如回答“答案是 A 吗肯定是 A”来骗分。4.3 算力受限时的替代方案没有几十张 A100能不能做推理模型能但要降低预期。我试过三条路蒸馏用一个大模型生成推理轨迹然后用这些轨迹做 SFT。这是性价比最高的路线。自一致性在推理时多次采样投票选择出现频率最高的答案。不训练也能涨点。专门化小模型把模型限制在数学或代码单领域把模型规模压到 0.5B再配一个领域 tokenizer效果远好于一个啥都想干的通用小模型。如果你想快速看到效果我的建议排序是蒸馏 SFT 自一致性 轻量 GRPO。先让流程跑通再逐步加大 RL 投入。5. 从 0.1B 到 1B训练过程踩过的坑和评估体系5.1 训练不收敛的排查顺序训练不收敛时第一反应别是调网络架构。我的排查顺序固定是看 loss 是否下降。如果完全不动先跑一个极小规模单 batch过拟合测试确认模型能记住数据。查数据问题字段有没有对齐、tokenizer 有没有把 prompt 切废。查学习率通常 1e-4 附近起步warmup 占总步数 1%2%。warmup 没做前期很容易震荡。查 loss 曲线细节如果是先降后涨大概率是学习率过大或数据重复过拟合。查梯度范数每次打印grad_norm如果超过 10优先调梯度裁剪而不是降低学习率。我记得有一次模型到第 800 步突然 loss 飙升到 12查半天发现是清洗脚本把某个语料库的标签符号全部删成了空行模型被迫预测大量空行。这类问题不看数据根本定位不到。5.2 困惑度之外你需要一套自定义评估任务很多人习惯只看 perplexity但它和真实产品体验的相关性没那么高。我在训练中同时维护三个评估维度维度指标说明语言质量PPL、复现率关注句式连贯性指令遵循自建 200 条指令集每条按规则打分推理能力GSM8K 数学子集、代码编译客观可验证这套评估每周跑一次对比不同 checkpoint。它能帮你发现 PPL 很低但模型“变傻”的情况——比如只学会了高频套话遇到具体问题就失忆。5.3 我从 0.5B 模型上线得到的部署经验模型训完不等于结束。我用vLLM做推理服务时遇到第一个问题是显存不够0.5B 的模型fp16 权重约 1GB但 kv cache 在长上下文下会撑爆。解决方法是限制最大长度并把gpu_memory_utilization调低预留缓存。第二个问题是量化。我对模型做了 AWQ 4bit 量化精度损失在可接受范围显存降到 0.4GB。如果你的应用对延迟敏感量化和 PagedAttention 几乎必备。第三个经验很多人不知道生成参数比模型权重更影响体感。同一个模型temperature0.3和temperature1.0差别巨大。做产品时先固定生成参数再优化模型否则你永远不知道问题出在模型还是采样策略。6. 想从零开始的人这是我给你的一条可执行路线图6.1 一份 12 周路线图我根据自己的经验整理了一份面向全职工程师、每天两小时的 12 周路线阶段内容产出第 1-2 周准备数据清洗管线 BPE tokenizer清洗后 1GB 数据集第 3-4 周实现 Transformer 训练脚本跑通单卡小模型loss 收敛的 0.05B 模型第 5-6 周扩大数据到 3GB训练 0.1B 模型能生成连贯文本的 base 模型第 7-8 周构造 SFT 数据做指令微调能够对话的模型第 9-10 周构建推理评估集跑通 GRPO 训练数学准确率有明显提升第 11-12 周量化 vLLM 部署 小产品 demo可用的低成本服务这个路线最大的好处是每两周都有看得见的产出不容易中途放弃。6.2 算力问题没有 A100 怎么活我默认你只有一两张 3090/409024GB 显存。完全可行。关键技巧用梯度累积模拟大 batchmicro_batch_size4grad_accum_steps8等效 32 batch size。启用torch.compile和 bf16 混合精度24GB 显存可以训 0.7B 模型。序列长度先从 512 开始稳定后再逐步加长。这比一开始就上 2048 更容易成功。盘活现有资源我在 AMD CPU 的机器上用纯 CPU 训练过一个小模型做验证速度慢但能排查代码逻辑节约 GPU 时间。6.3 一个容易被忽略的团队协作问题最后说一个非技术但很重要的事从零训练模型的项目一定要保存完整的训练日志。我见过太多人跑完一轮训练说“效果还行”但问他要训练超参、数据版本、评估结果什么都拿不出来。没有日志你无法复现自己的成果更无法证明 pipeline 的可靠性。我在每一轮训练前都会建一个实验目录包含数据版本哈希、tokenizer 配置、训练超参、评估指标、wandb 曲线地址。写实验卡的习惯救了我无数次。6.4 我最后想补充的做ai-engineering-from-scratch这件事我的真实体会是它并不需要你是天才但需要你有耐心。训练一次模型最短几个小时迭代一次可能要一周。你会在凌晨三点盯着 loss 曲线等它下降会为 tokenizer 的一个超参数折磨一整天。但当你亲手训练的小模型第一次流畅地回答出问题、第一次通过数学推理得到正确答案时那种“底层原理尽在掌控”的感受是调用任何 API 都无法替代的。如果你准备动手我的建议是先别想太大的目标。从 0.05B 的玩具模型开始走通一条微小的闭环再逐步放大。这条路上每一步的坑都会变成你判断大模型产品问题时的直觉。祝训练顺利。
返回列表