
先把话放前面这两年AI热度高但大多数人的“AI工程”其实是套壳工程——调API、拉模型、跑微调脚本出了问题只能靠猜。真正能hold住全链路、从零把一条AI应用线搭起来的人少得可怜。这也是“ai-engineering-from-scratch”这个项目存在的意义不依赖任何黑盒把数据、模型、训练、评估、部署一整套环节亲手做一遍。这篇文章我会完整拆解这个项目的设计思路、核心环节的实操细节、常见坑和我的排查经验全文偏工程实践适合想接触AI基建、不想只做调包侠的开发者参考。1. 项目的定位与顶层设计拆解1.1 为什么非得“from scratch”不可我见过不少团队产品上线速度确实快模型一接、prompt一调demo马上能跑。但一旦涉及效果优化、推理成本控制、数据回流就卡住了——因为底层的东西全是黑的。模型内部怎么处理token为什么同样的数据换一个随机种子效果差一大截显存为什么突然爆了这些问题靠调API永远找不到答案。“from scratch”不是让你真的去重写Transformer论文里的每一个公式而是指整个工程链路里每一个关键环节你都得有亲手搭建的能力。我做的这个项目里包括从清洗原始语料开始自己训练一个轻量级Tokenizer用PyTorch从零实现一个可训练的Transformer解码器自己写训练循环、学习率调度、混合精度逻辑构建评估集并跑人工评估最后把模型部署成HTTP服务压测并优化推理延迟。这一轮走完AI工程在你的认知里就不再是“玄学”而是可拆解、可控制、可优化的系统工程。1.2 这套项目要解决的核心问题把项目拆到底它本质在回答三个问题。第一个问题是“数据怎么做”。大多数入门项目直接用开源数据集但真实业务里数据永远是脏的、偏的、不够的。我在项目里从Reddit、维基百科、代码托管平台抓取原始文本后按去重、过滤、采样三步处理并且花了大量时间调tokenizer的词表大小。这个环节枯燥但直接影响后面的模型效果。第二个问题是“模型怎么训”。从零搭建Transformer你才会真正理解参数量的组成、激活内存的消耗、不同并行策略的差异。我用了一个约1.5亿参数的小模型做实验既能在单卡上跑起来又能展示梯度累积、学习率预热、权重衰减这些训练技巧的实际作用。第三个问题是“系统怎么部署”。模型训完不是终点把它封装成服务、支持并发推理、控制延迟是工程落地的关键。这个项目里我实现了一套基于FastAPI的推理服务带流式输出、批处理调度和基础监控。虽然规模不大但完整的部署流程和线上排查方法都跑了不止一遍。1.3 整体技术栈选型的心路历程技术栈选型上我做过好几轮权衡最终敲定的组合是PyTorch为主HuggingFace生态的Datasets做数据管理、Tokenizer做基准参考、Accelerate做分布式训练简化推理服务用FastAPI Uvicorn容器化用Docker Compose搞定。为什么不直接用现成的训练框架Lightning和HuggingFace Trainer确实快但它们的封装把很多东西藏了起来。你很难感受到梯度累积到底是怎么回事也不清楚数据加载器num_workers设多少能跑满GPU。我倾向于在项目里先用原生PyTorch写一遍等真正理解了底层机制再考虑用框架提升效率。这个顺序倒了学到的全是表面功夫。2. 核心技术点的从零实现思路2.1 数据工程——AI工程的地基做了一整轮项目下来我的感受是模型架构反而不用太过纠结数据工程才是决定上限的环节。这个项目里我首先写了一个数据清洗管线。原始语料经过以下几个步骤HTML标签剥离、文本去重用MinHash对相似文本做近似去重、语言过滤只保留中文和英文、长度过滤丢弃少于20字符的碎片文本。这一步听起来简单但每一项背后都有讲究。比如MinHash的band和row参数怎么设直接决定去重精度和计算开销。我实测下来对于几十GB级别的语料band20、row5这组参数能在准确率和速度之间取得不错的平衡。然后是Tokenizer训练。推荐直接基于HuggingFace的tokenizers库训练一个BPE模型但词表大小一定要自己实验。词表太小句子被切得稀碎序列长度膨胀训练效率变低词表太大嵌入层参数量暴增小模型撑不住。我试过8000、16000、32000三档最后根据序列长度分布和模型参数量选了16000。这个值对轻量级模型来说比较划算。注意不要迷信开源数据集的“干净”真实抓下来的数据里重复率经常超过30%。如果没有去重模型会严重过拟合到高频片段上表现为生成内容不断复读。2.2 模型结构——从零搭建解码器模型结构上我实现了一个标准的GPT风格解码器。核心模块包括Token Embedding 位置编码、多层TransformerDecoderBlock带因果自注意力、LayerNorm和最终输出头。自注意力实现时最容易出错的是因果掩码。PyTorch里用torch.triu生成上三角矩阵把未来位置遮住但必须注意diagonal1这个参数因为我们要保留当前位置自身的注意力所以对角线元素需要设置为0。# 因果注意力掩码shape: (seq_len, seq_len) mask torch.triu(torch.ones(seq_len, seq_len, dtypetorch.bool), diagonal1) # mask[b, a] True 表示位置b不能看到位置a从零写Transformer还有一个容易被忽略的点初始化方式。如果没有对输出层的权重做特殊缩放训练初期loss下降会非常慢。我参照GPT-2的做法把残差层输出按2 * num_layers做缩放并把输出头的权重置零偏置。这个小改动实测能让loss前期下降速度提升20%以上。2.3 训练循环——加速、稳定、收敛缺一不可训练循环是用原生PyTorch写的关键点有三个。第一个是动态填充和批处理。文本长度差异很大直接按最大长度padding会浪费算力。我用的是分桶策略先把样本按长度分成若干桶每个batch只从同一个桶里取数据。配合num_workers4的多进程数据加载单卡训练吞吐量能提升三成。第二个是混合精度。A100、V100这些卡都有Tensor CoreFP16计算能比FP32快不少。但直接用torch.cuda.amp会偶尔遇到loss溢出变成NaN的问题。我的做法是开启GradScaler并把scale_window调整到500步以内。如果连续多次出现inf就适当降低学习率。这一套下来训练速度提升了一倍稳定性也够了。第三个是优化器与学习率调度。我用的是AdamW权重衰减设0.1学习率采用warmup cosine退火。warmup步数约占总步数的5%——对于1.5亿参数的小模型3000步的warmup就够。单独提一下不要小看权重衰减它对于抑制过拟合、提升泛化能力的作用比数据增强还明显。2.4 评估与对齐——不止看loss很多人训练完模型只看loss曲线觉得降得不错就完事了。实际上loss下降只能说明模型在“记住”训练集不代表生成质量好。我在项目里搭了一套双通道评估流程。一是自动评估维护一个多样本的提示词集合覆盖问答、摘要、代码生成、故事续写等任务批量生成后用BLEU、ROUGE、perplexity多个维度打分。二是人工评估每轮迭代随机抽200条生成结果我亲自逐条打分。我渐渐意识到自动指标容易受长度和重复惩罚影响结果跟人工判断差距其实不小所以人工抽检不可省。对齐这块我用的是RLHF的简化版——基于GPT-2训练一个奖励模型再用PPO算法微调策略模型。完整复现RLHF的工程成本非常高所以我建议只做两轮迭代重点理解“偏好数据构造→奖励模型训练→策略优化”的闭环逻辑。真要做产品级对齐再上DPO这类更稳定的方案。3. 实操过程与核心环节实现3.1 环境准备与基础依赖开发机是一张24GB显存的RTX 3090CPU 16核内存64GB。这个配置做1.5亿参数模型的训练和推理完全够用。基础依赖版本我用的是PyTorch 2.1.0、transformers 4.36.0、tokenizers 0.15.0、datasets 2.16.0、accelerate 0.26.0、fastapi 0.109.0。这里有个经验不要盲目追求最新版本稳定是关键组合出问题的时候找个“大家都验证过的版本组合”能省半天时间。安装完依赖后我第一件事不是写代码而是用一个小脚本快速验证CUDA可用性和显存信息import torch print(torch.__version__) print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0)) print(torch.cuda.get_device_properties(0).total_memory / 1024**3, GB)输出正常之后才继续往下走。别小看这一步环境有问题会浪费你大量的调试时间。3.2 构建训练数据管线数据我用了三条线维基百科约2.5GB、Reddit问答约1.2GB、GitHub开源代码约3GB。总计约6.7GB的原始文本最终的清洗和处理脚本跑了一个晚上。清洗之后数据规模缩到了4.1GB左右。8%以上的重复内容被去掉了这比预想高不少。接着把文本按段落切分成样本每段平均长度约900个字符。对中文语料我额外做了繁体转简体和全角转半角处理这些在开源数据集里经常被忽略但对中文模型效果影响很大。数据集构建完成后的格式化处理也值得说。每个样本结构是{text: 完整文本段落不截断训练时动态切块}训练时通过Dataset.map函数做分词和分块块大小设512 token相邻块之间加一个分隔符避免粘连。这样处理序列利用率比直接截断提高了不少。3.3 模型训练全流程模型配置直接上参数表说明参数值说明层数12TransformerDecoder层数隐藏维度768与GPT-2 small一致注意力头数12标准多头注意力词表大小16000自定义BPE词表最大序列长度512训练时上下文长度参数量1.5亿轻量级模型训练超参数batch size按token计设为64即每步处理32768个token优化器用AdamWβ(0.9, 0.95)weight_decay0.1学习率峰值设为3e-4warmup 3000步之后按cosine退火到3e-5。梯度累积设置成4步因为单卡一次装不下太大的batch累积4次等价于有效batch size翻4倍。总训练步数设定为5万步。在3090上每步大约0.8秒全天候跑需要11小时左右。实测跑到3万步左右loss降幅开始变小到4万步时验证集perplexity基本稳定在18.5左右。训练过程中的关键节点我整理了张表训练步数训练loss验证perplexity备注10006.45180.3还在warmup100003.9238.7生成结果开始连贯300003.1519.8基本可读仍有重复500003.0218.5最终模型效果可用注意一个现象loss看起来还在降但perplexity已经不怎么动了。这说明模型开始进入过拟合区间再训练下去收益有限。这也是为什么评估环节不能只看loss。3.4 推理服务与性能优化模型训完之后我用FastAPI封装了一个推理服务。虽然commit不多但接口设计、批处理、生产部署都跑了一遍。接口设计上我支持了三个参数prompt输入文本、max_new_tokens生成上限、temperature采样温度。流式输出用StreamingResponse实现前端可以像ChatGPT那样一个字一个字往外蹦。服务性能实测数据如下配置平均延迟吞吐量FP32batch198ms/token约10 token/sFP16batch155ms/token约18 token/sFP16batch832ms/token约31 token/s对于1.5亿参数模型3090上单卡跑到30 token/s左右已经能支撑内部工具使用。如果要做线上高并发就得考虑量化到INT8甚至INT4或者上vLLM这类推理框架。但作为“from scratch”项目先搞清楚一条链路能跑通再横向对比优化方案这个学习路径我认为合理。3.5 RLHF简化对齐实践最后一块是对齐训练。我先构造了约8万条“指令-回答”对按质量高低排序后随机选两条让同一个模型生成候选回答标注者投票选更好的那个。这个偏好数据集用来训练奖励模型奖励模型结构跟主干模型一致只是把输出头换成标量回归头。奖励模型训完之后用PPO策略优化主干模型超参数取了KL系数0.1、学习率1.5e-6。这个阶段跑起来比预训练费劲多了——大批量推理逻辑、优势计算、价值模型都要配齐。建议第一次做的朋友别贪多跑两个epoch质量有提升即可。最终对齐后的模型做人工评测指令遵循能力明显改善编造内容的概率低了很多。当然这只是简化版RLHF和ChatGPT那种万人反馈训练出来的模型没法比但闭环打通了。4. 常见问题与排查技巧实录4.1 loss震荡不收敛训练到几千步后loss偶尔会反弹甚至冒出一个尖峰。我排查后确认了几个原因一是混合精度下loss溢出导致梯度变成NaN二是batch太小学习率相对过大三是数据管道偶尔返回了空样本。排查办法是用torch.isnan()检查梯度和loss打印异常时刻的样本内容。做过一轮修正之后训练稳定多了。这里有个经验训练脚本里一定要加异常检测逻辑不然后期出事根本不知道是什么时候埋的雷。if torch.isnan(loss): print(fNaN loss at step {step}, inputs: {input_ids[0][:20]}) break4.2 生成结果全是复读机模型loss正常但生成时反复输出同一句话。原因是采样时temperature设太低模型每次都挑概率最高的路径陷入重复循环。解决办法有两个一是在解码时加repetition_penalty二是使用top-p采样。我实测下来repetition_penalty1.2配合top_p0.9效果比较理想。如果复读情况还非常严重那就要怀疑训练数据的去重不够干净。模型记死了训练集里反复出现的片段这种情况靠解码参数救不回来。4.3 推理服务OOM崩溃第一次压测时并发度一上去GPU显存直接爆掉。排查发现每个请求进来都创建了一份完整的模型副本几十个并发自然扛不住。解决方式是模型常驻内存全局只加载一份推理请求做排队batch推理用动态批处理continuous batching。做完这三件事后并发从4路提升到了16路显存占用依然稳定。4.4 评估指标虚高但效果差自动评估指标BLEU、ROUGE都很高人工看生成结果却差强人意。后来发现问题出在“评估数据太规整”——评估集的样本都是模板生成的模型背住了模板套路。改用真实用户输入做评估后指标大幅回落。这提醒我一件事评估集的组织和覆盖面决定了技术走向判断的准确性。4.5 问题排查速查表现象可能原因排查动作训练loss不降学习率过大/过小打印梯度范数调整学习率训练突然NaN混合精度溢出启用GradScaler降低学习率生成复读解码参数不合理调高repetition_penalty生成复读训练数据去重不彻底加强去重重新训练推理显存爆每请求加载模型模型全局常驻指标高效果差评估集过于模板化混入真实用户输入中文生成质量差未做繁简/全角处理数据清洗管线里加上5. 个人经验与后续扩展建议5.1 从零做一遍改变了我的什么认知说实话这个项目做完我最大的变化不是“会写Transformer代码了”而是对AI工程有了一个全局手感。以前看技术方案总爱纠结“用哪个框架”“调哪个参数”做完之后我第一个反应是看数据和评估——这个坑位对了其他都是次要的。另外我很建议大家把整个项目写一个完整的技术报告包括每一次失败的实验记录。回头翻看时那些失败记录比成功路径更有价值。因为别人的成功方案未必适合你的场景但失败原因往往是共通的。5.2 几个人想要避开的弯路第一不要一开始就追求大模型。1.5亿参数的模型能快速走通全流程10亿参数的模型试错成本会高很多。先跑通再放大。第二不要跳过评估环节。哪怕用最简单的人工抽检也要做。模型训练到后期loss的参考价值非常有限评估结果是决定继续训练还是停下的唯一标准。第三不要轻视数据工时。整个项目里数据清洗和评估占了我大概60%的时间。工程化AI项目的时间和资源分配要想清楚。5.3 后续还能怎么扩展如果要把这个项目往后推一步有三条线都可以走一是把模型规模扩大到10亿级别同时引入张量并行和流水线并行走一遍完整的分步式训练流程二是把推理服务升级成vLLM框架跑一跑PagedAttention和Continuous Batching对比跟原生实现的差异三是在对齐阶段换DPO方案做一轮成本与效果的详细对比。这些扩展方向本质上是互相补充的两个目标更懂底层、更会做选择。我个人后续打算先做第二条因为推理优化对线上收益最直接写出来对同行也最有参考价值。