
熟悉AI工程的都知道一个尴尬现象调HuggingFace的API、改超参、跑通在线demo大家都熟练得不行可一旦要求不套现成模型从数据和代码一步步构建一个完整的AI系统很多人就卡住了。这正是ai-engineering-from-scratch这个主题戳中我的地方——它意味着把系统当作亲手搭起来的机器而不是一个只会调参的黑盒。结合最近社区里build a reasoning model from scratchbuild a large language model from scratch这些话题的热度我想把这条路线完整拆解一遍从数据管线的搭建、模型架构的实现、训练稳定性的调试到推理服务的部署和评估闭环的建立把我实际踩过的坑、验证过的做法都讲透。这篇文章适合两类人一是想真正理解大模型工程细节的开发者二是在公司里被问能不能不上开源模型、自己搞一套的工程师。1. 为什么把不做黑盒当作第一原则1.1 阵营战与游击战AI engineering from scratch到底在做什么先说清楚一个误解。from scratch不是让你从晶体管制CPU、从CUDA重写矩阵乘法那是半导体和编译器工程师的活儿。AI工程意义上的from scratch是指你在数据、模型代码、训练过程、推理服务这四个层面都拥有自主搭建和控制的能力而不是靠着别人封装好的组件拼个demo出来。我见过两种典型角色。一种是调包侠熟练运用transformers、sentencepiece、peft这些库让他换个模型架构、改个注意力实现就无从下手另一种是理论家论文读了不少能画出完整的Transformer结构图但一跑训练就发现显存爆炸、loss乱跳、分布式同步出错。真正能落地的是第三种清楚每一行代码在算什么、每一份数据会被模型以什么方式消费、训练和推理之间的数值差异从哪来。这就是from scratch的阵营战思维——你像一个指挥官对每个阵地都有数而调包更像是游击战打一枪换一个地方爽快但不持久。我在实践里最深的感受是from scratch本质上是一种排障能力和掌控感的训练。当你完全依赖现成框架时一旦出现诡异问题你能做的只有上网搜issue但当你亲手搭建过每一层你至少知道问题可能出在哪五个位置排查范围会缩小一个数量级。1.2 什么时候该自己搭什么时候别折腾但我也得劝一句不是所有场景都值得from scratch。这个判断做错了比技术不行更坑。我自己的判断标准是三条产品处于快速验证期还没搞清用户要什么直接用成熟开源模型或API别自己搭。核心竞争点不在模型本身比如就是个展示页也不该自己搭成本完全划不来。但如果你想做的东西恰好需要对模型结构或训练过程的细粒度控制比如定制一个推理模型、做一个垂直领域专用小模型、或者要把推理延迟压到极低那from scratch就是绕不开的路。举一个例子我去年做了个数学题解答引擎的尝试一开始想微调一个开源模型很快发现用户要的是给出正确的分步推理过程而通用模型的推理过程经常在第二步就开始胡扯。这个时候你需要的不是换更好的base模型而是自己控制训练数据里的推理标注粒度、设计专门的过程奖励信号。只有自己搭起来才能碰这些变量。1.3 一张全局地图先给系统画像开始动手之前先把全局画出来。一个完整的AI工程系统大致包含五个模块它们是循环关系而不是线性的数据工程采集、清洗、去重、配比、tokenize。模型架构定义网络结构、初始化、前向/反向。训练系统优化器、学习率调度、混合精度、分布式并行、日志与恢复。推理服务模型加载、量化、批处理调度、解码策略。评估闭环离线benchmark、在线指标、bad case反哺数据。很多人只关注中间三个觉得数据拿别人的就行、评估最后跑一跑就行。实际上我做过几个项目后越来越确定数据评估这两个环才是真正决定上限的地方训练和推理只是把数据里藏的规律稳定地提取出来。这张地图你心里有数了后面每一步都不是孤立的——你处理数据的时候会想着tokenizer压缩率你做推理服务的时候会想着评估样本能不能回流到训练集。这就是典型的工程闭环思维不写出来很容易迷失。2. 数据、架构、训练从零搭模型的三大地基2.1 数据管线tokenizer起步质量决定上限说个反直觉的事很多从零开始的AI项目第一步不是选模型架构而是练一个自己的tokenizer。为什么因为开源模型的tokenizer是照着它的训练语料练出来的。如果你要做的是中文社区问答、垂直领域文档、代码注释混合体直接套用通用tokenizer要么未登录词多要么同样一段文本被切得很碎压缩率惨不忍睹最后训练效率、生成质量都会受影响。我通常用BPEByte Pair Encoding路线练tokenizer配合一个足够大的语料样本。实操时核心指标就两个压缩率和覆盖度。压缩率是原始字节数除以token数一般3.5以上算合格覆盖度是指语料里有多少内容能在词表里找到对应token这个数偏低说明分词粒度选错了。我常用的检查方式是拿一小段真实业务文本打印出tokenize结果肉眼看碎片化程度——这个动作虽然土但比任何指标都好使。练完tokenizer就是数据清洗。这个环节拼的不是算法是耐心。我的清洗流程大致是抽正文把HTML标签、导航、页脚、重复模板剥掉只留正文。这个用trafilatura或自研抽取器都行关键是先抽样100条人工看确认抽取质量再全量跑。语言过滤用fastText的语言分类器把目标语言之外的内容丢掉。别小看这一步网上爬来的数据里其他语言的比例比你想象的高。去重包括精确去重和模糊去重。精确去重直接哈希模糊去重一般走MinHash LSH可以拿datasketch库跑。重复语料会让模型训练时对高频片段产生过拟合表现为生成时不断循环同样的话。质量过滤按长度、标点密度、重复率打分。我常用一个简单规则有效中文字符占比低于40%的句子直接扔n-gram重复率过高的段落也扔。最后是配比。这是我从多次训练里总结出的铁律模型最终擅长什么主要看训练数据的配比而不是你事后想让它擅长什么。比如你做通用对话助手百科、社区问答、代码、新闻的比例大概可以按5:3:1:1但你若想要推理能力强数学和逻辑类数据得显著加码至少占到总量的15%到20%。我最早做过一次实验数学数据占比不到5%结果模型在简单应用题上连基本的数量关系都理不清后来重训才明白是配比的事。2.2 架构落地从Llama的骨架里理解每一层架构这块我直接说结论**新项目从零写模型优先选Llama风格的结构不要自己去发明新attention。**原因很朴素这个结构经过了社区大量验证稳定、省显存、外推效果好你想在此基础上做定制也最容易找到参考。拆开看Llama风格模型的骨架大概是这样的token embedding把token映射为隐藏向量。它是嵌入矩阵的第一层也是反向传播时梯度最大的地方之一。RMSNorm代替LayerNorm计算量小且训练稳定。它只对激活做归一化不学偏置实现起来几行代码。RoPE旋转位置编码把位置信息以旋转矩阵的形式注入query和key好处是外推长度比绝对位置编码好训练2048长度的模型推理时能延展到更长的上下文。GQA分组查询注意力把key/value头分组共享减少KV cache占用。7B左右的模型用GQA能省接近一半的推理显存。SwiGLU前馈网络比传统的ReLU FFN多两个门控矩阵表达能力更强代价是参数多了一些。因果注意力掩码保证每个位置只能看到它之前的内容这是decoder-only结构的基本约束。从工程实现角度我建议你不要直接读Llama的官方实现然后照抄而是自己按张量形状一步步推。比如注意力这块你只要盯着三个形状Q/K/V都是[batch, seq_len, num_heads, head_dim]经过RoPE旋转后做矩阵乘法得到[batch, num_heads, seq_len, seq_len]的注意力分数再mask掉未来位置、softmax、加权求和最后把多头拼回去映射成输出。想清楚这几个形状整个模型就通了。我的一个习惯做法是先把模型写成能跑通前向但不保证高效的版本在单卡小batch上验证形状和梯度确认没问题之后再上分布式和混合精度优化。先跑对再跑快这个顺序在from scratch阶段能省掉大量排查时间。2.3 训练稳定性loss曲线不是说降就降的模型写完了数据准备好了接下来是训练。很多人第一个脚本就上来开训结果训到几千步loss突然暴涨或者直接NaN。这种问题在from scratch项目里几乎是必过的关卡。我的训练配置基准如下你可以直接照抄起步优化器用AdamWbeta10.9beta20.95weight_decay0.1。学习率应用warmup加cosine衰减。warmup步数占总步数的1%到3%峰值学习率从1e-4到3e-4之间调。峰值学习率是训练里最敏感的超参大0.5倍可能就崩。混合精度用bfloat16省显存且相比fp16更不容易溢出——训练loss spike的一大来源就是fp16的精度溢出。梯度裁剪设1.0这是防loss spike的保险丝。全局batch size从64到512之间视数据量和显存而定。关于BF16我想多说一句。BF16的尾数位少但它和FP32有相同的指数范围所以在梯度传播时不太容易出现Inf/NaN稳定性显著好于FP16。如果你的显卡支持A100/H100以及RTX 40系以上都支持无脑用BF16就行。训练时我盯的指标就几个loss、gradient norm、learning rate、throughput。其中gradient norm是预警器——它一旦突然飙到几十上百说明某一步的数据或参数出了问题这时候赶紧降学习率或者检查输入数据还没到NaN就已经能拦住大半事故。当模型规模上到7B以上单卡肯定装不下。这时候再上FSDP或ZeRO原则是先让单卡小batch跑通再逐步加并行维度。不要一开始就上64卡分布式那样出了问题你根本分不清是数据问题、模型问题还是通信问题。从1卡到2卡到4卡每步都确认loss曲线形态一致再规模化。3. 让模型学会推理从SFT到逐步监督的增量路线3.1 推理能力不是涌现是组织化最近build a reasoning model from scratch的话题很火但我要先泼一盆冷水推理能力不是你把模型训大就自动长出来的它依赖于训练信号里有明确的推理示例和反馈。为什么预训练阶段模型学的是下一个词最可能是什么它掌握的是海量的统计相关性。碰到一道题小明有3个苹果小红有5个谁多模型可能见过类似的句子直接输出小红多但碰到需要三步以上才能解决的问题它需要在内部组织多个信息片段这种组织能力不是靠记忆能覆盖的。打个比方一个学生把整个题库都背下来了但你没有让他做过一次写出解题步骤的训练考试碰到稍微变形的题就不会。推理模型也是这样它需要被训练成愿意且能够输出中间推导步骤的结构化思维方式。3.2 从CoT数据到过程监督推理模型的四步路线我在实践里把构建推理模型的路线拆成四步每一步都有明确产出第一步人工构造小批量CoT标注数据。找几百道有标准答案的题数学、逻辑、代码人工写详细的逐步推理过程。这一步量少没关系但质量必须是钻石级因为后续的自动扩展数据全是基于这批种子生成的。种子数据的结构决定了模型学到的推理形态比如我要求推理过程里必须有已知条件和每步依据模型的输出就会跟着有模有样。第二步用种子数据做SFT。在base模型上做指令微调让模型学会输出思考过程 最终答案的格式。这个阶段不用太多数据几百到几千条就够了目的是让模型开口说步骤。第三步拒绝采样扩充数据。让SFT后的模型对同一道题采样多条推理路径比如8条到16条只保留答案正确且推理过程没有明显跳步的样本再把它们当训练数据继续微调。这一步的本质是让模型自己当数据生成器你做质检员。我实测下来采样数量翻倍带来的收益会递减8条是个性价比不错的点。第四步如果前三步做完推理正确率还达不到目标就走过程奖励模型路线。给每一步推理打分比如用GPT-4逐点评判或训练一个PRM模型预测步骤正确性然后用这些逐步反馈继续强化。这就是所谓的过程监督——模型不仅知道最终答案错了还知道第二步的推理依据就错了。这一步是效果上限最高的也是工程量最大的适合追求极致推理质量的场景。3.3 别把benchmark当信仰但要会用推理能力到底怎么量社区里最常用的是GSM8K小学数学应用题、MATH竞赛数学、BBHBig-Bench Hard这些。我建议你从项目第一天就把评估脚本写出来而不是训完再补。具体做法是固定一个评估集每次模型迭代后在完全相同的条件下跑一遍记录准确率。这里要小心两件事一是采样温度要固定推理评估一般用温度0或一个很低的温度否则结果有随机性二是提示词要统一换了提示词框架分数波动可以高达十几个点你的对比就失真了。我自己的习惯是搞一个双轨制benchmark分数作为门禁bad case分析作为方向盘。门禁告诉你这次迭代有没有变差方向盘告诉你下一步往哪个方向收集数据、调整推理引导方式。只盯benchmark会让模型过拟合评测集只看bad case又缺少全局判断两者结合才稳。4. 推理部署与评估工程闭环里最容易被忽视的部分4.1 训练好模型只是开始从checkpoint到服务很多人在训练阶段投入大量精力模型效果不错结果到了部署阶段发现处处透风。训练好的模型要变成可用服务至少要过三道关格式与精度、解码参数、批处理调度。第一关是精度。训练时用的BF16在推理时通常可以直接用显存紧张再考虑量化。量化的选择我放在表格里说明方案显存占用质量损失适用场景BF16/FP162字节/参数几乎无一般情况首选效果最稳INT8W8A81字节/参数较小显存不足时的折中INT4W4A160.5~0.6字节/参数明显需校准追求极致吞吐、对质量不敏感注意量化不是光压数值就行。做INT8或INT4量化时需要用一小批有代表性的数据做校准让量化缩放系数贴合真实激活分布。我见过有人图省事用随机数据校准结果推理质量掉得莫名其妙查半天才发现是校准集不对。第二关是解码参数。训练时的teacher forcing和推理时的自回归生成有天然差异所以部署时要盯住温度、top-p、max_new_tokens这些参数。推理模型尤其要注意max_new_tokens设太小思考过程没写完就被截断这是最常见的线上质量事故来源。4.2 吞吐与延迟的账批处理调度是关键模型本身写得好不代表服务性价比高。推理服务最核心的工程点在于怎么把GPU喂饱。早年大家做推理服务都是一次处理一个请求简单但GPU利用率极低。现代的推理引擎几乎都实现了continuous batching连续批处理一个请求里已经生成完了的序列立刻移出新的请求随时插入而不是等整批都生成完再统一换新。这个机制能把吞吐量拉高好几倍。我做过一个7B模型的线上服务对比同样一台A100用naive的逐请求推理每秒只能处理几个请求换成vLLM这类支持continuous batching的引擎后吞吐翻了五六倍。中间的关键参数是max_num_seqs最大并发序列数我建议从8开始调观察延迟和吞吐的拐点再决定不要一次性拉满。另外如果做的是高并发场景很多框架会用投机解码speculative decoding来加速用小模型草拟多个候选token大模型一次验证。这个技术的吞吐收益很可观但实现复杂度高我建议等项目稳定运行后再考虑。4.3 离线指标够用但线上体验是另一门账最后回到那个循环评估和部署不是终点而是新一轮数据的起点。离线benchmark能告诉你模型在这个测试集上准确率多少但它回答不了用户在真实场景里满不满意。比如我问你一个回答准确但啰嗦800字的模型和一个回答简洁但偶尔小错的模型哪个线上留存高答案是后者因为用户等不及也看不完。所以线上的关键指标要单独接首token延迟、总生成时间、用户反馈率、对话轮均长度。我的做法是对线上日志每周做一次bad case采样把那些用户明显不满意的回答收集起来比如用户连续追问同一句话、或者直接点了不喜欢回到数据配比和推理参数上找原因。很多时候一个线上问题最终解决方案不是微调模型而是调整解码温度、换一个提示词模板甚至只是把max_new_tokens改大一点。这说明评估闭环不只是评估模型也是在评估整套系统。真正有效的AI工程闭环是这样的离线评测发现问题训练数据修正问题线上监控验证问题是否真的被解决然后带着新数据回到下一步。这个循环一旦转起来你才算是把from scratch的整套系统盘活了。5. 显存、日志与可复现性from scratch路上的隐形杀手5.1 先算显存账别等OOM再调batch从零开始的人最容易犯的错是训练脚本一跑就OOM然后低头猛调batch size。实际上显存是可以用公式先估算的。训练时的显存大头主要三块模型参数、优化器状态、激活值。以7B模型为例BF16训练时参数本身14GB。AdamW优化器要额外保存FP32的动量与方差这部分又将近14GB乘2动量和方差各一份加上FP32的参数副本又是14GB总共是参数量的若干倍。激活值在长序列、大batch时更是无底洞。粗算一下7B模型用AdamW做BF16训练实际显存需求基本在60GB到80GB这个区间所以单张A10080GB是起步配置409024GB只能训很小的模型或用Lora这类参数高效微调手段。推理阶段显存就好算多了模型权重加KV cache。KV cache是怎么涨的每个token、每层、每个注意力头都要保存一份key和value长度翻倍它就翻倍并发请求数翻倍它也翻倍。这就是为什么长上下文场景下KV cache经常比模型权重本身还占显存也是GQA这类分组注意力为什么受欢迎——它直接把KV cache的头数槽位砍掉一大截。我的建议是训练和推理的显存脚本在项目第一天就写好后面每次换参数先跑脚本算账再决定卡数和batch而不是靠感觉试错。5.2 日志、checkpoint与恢复别让两天训练白跑训练稳定性不只是loss那一张图。我吃过最大的亏是训练跑了两天机器断电checkpoint只有最新的加载回来发现loss直接从3跳到50整个实验报废。从那以后我的checkpoint策略变成每500步存一个可恢复的checkpoint包括模型权重、优化器状态、学习率调度器状态、随机数状态、当前步数。不覆盖历史至少保留最近5个版本。训练日志里除了loss必须记录gradient norm、learning rate、tokens seen、throughput。这些字段在排查loss波动时缺一不可。恢复机制也有讲究。从checkpoint恢复时如果没恢复随机数状态数据加载的shuffle顺序会变化虽然不致命但会导致loss曲线和之前不完全衔接影响你对训练健康度的判断。所以把random state和dataloader状态一起存了恢复才能做到无缝。5.3 一页纸实验记录法让一切可复现from scratch项目最隐蔽的风险是实验失控。改动了一个超参调了一批数据换了个tokenizer版本——三个改动叠在一起效果变了你根本不知道是哪个的功劳。到后面想复现自己之前的效果翻遍代码仓库也找不到当时的配置。我用的方法很简单一张Markdown表格每个实验一行必填字段实验编号与目的模型配置层数、头数、维度、词表大小数据版本与配比记录数据快照的哈希或版本号关键超参峰值学习率、warmup步数、batch size、序列长度优化器与精度AdamW、BF16等训练结果最终loss、benchmark分数、bad case链接结论这个实验教会了你什么这个方法不复杂但它是from scratch工程的记账本。等到你踩坑越来越多会发现绝大多数的诡异问题都能在实验记录里找到对应的变量变更。能复现才能改进能改进你搭建的这套系统才算真正归你所有。我自己的体会是大模型工程从零走一遍最大的收获往往不在最终那个模型上而在于你对自己系统每一根线缆都有了手感——知道数据在哪一环被污染、结构在哪一处该调整、训练在什么情况下要踩刹车。这种掌控感才是from scratch这个词真正值钱的地方。