ARTICLE DETAIL

资讯详情

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

从零开始AI工程:手搓LLM与推理模型的实战路径

从零开始AI工程:手搓LLM与推理模型的实战路径 先聊一个很现实的问题每天都有大量的人想“从零开始”进入AI工程但他们打开任何一本大模型教程后做的事高度一致——把注意力放在“看”而不是“做”上。看架构图、看损失曲线、看论文推荐看完了自己写第一句代码都憋不出来。我自己带过不少工程师也做过不止一个端到端的AI项目最深的感触是ai-engineering-from-scratch这个目标本身没有错错的是大多数人对“from scratch”的理解过于模糊。有人要的是从零预训练一个LLM有人要的只是不依赖闭源API自己搭一个推理服务还有人想的其实是“把大模型训练和部署的全链路彻底搞懂”——这三者的工程路径完全不同。前阵子有两样东西热度很高一个是《Build a Large Language Model from Scratch》这本书另一个是build a reasoning model from scratch这个检索词。把它们放在一起看特别有意思前者教人从数学和代码层面手搓一个GPT后者更偏向在已有底座模型上做一个会“思考”的推理模型。这其实是两个层面的事很多新手混为一谈结果就是既读不懂书也搭不好工程。这篇文章我打算用我实际趟过的路子把“从零开始AI工程”拆成可执行的最小闭环先讲清楚目标定义再逐层拆解数据、模型、训练、评测、部署五个门槛然后给一个单卡能跑完的实操路径最后聊聊“手搓LLM”和“手搓推理模型”到底分别该怎么做以及那些踩过千百遍的坑怎么排查。内容会偏向一线实战适合正在准备转行AI工程、或者已经入行但始终觉得自己差一块拼图的工程师。读完你未必能立刻造出一个ChatGPT但至少应该能够自己握着一套能跑通、能解释、能修改的训练与推理链路。1. 先把“从零开始”这四个字拆清楚1.1 你想要的到底是哪一种“从零开始”“从零开始”在AI工程语境下至少有三种完全不同的含义。第一种是论文级复现从张量操作开始自己写多头注意力、自己实现反向传播目标是把一个极小模型训练到能在某个玩具数据集上有合理表现。第二种是工程级自建不依赖闭源API自己维护数据、自己训练或微调一个开源模型、自己部署成可调用的服务这是目前绝大多数企业落地时的真实诉求。第三种是研究级探索想在算法层面做出新东西比如从头设计一个新的模块或者用新的强化学习策略训练推理模型。这三种路线对工具、算力、数学基础的要求完全不一样。我见过有人买了一台双卡服务器花了两周时间想复现GPT-2连代码都没跑通因为他的目标其实只是“想做一个本地聊天机器人”——那是第二种完全不用从矩阵乘法开始。反过来也有人用现成的LoRA脚本微调了个模型回来跟我说“从零开始好简单”结果让他改一个采样参数都找不到在哪——那是把“调用别人的轮子”当成了“自己造轮子”。所以在动手之前先诚实地回答一个问题你说的“从零”是从哪个零开始。如果是产品型目标你的重点应该放在数据清洗、训练配置、评测闭环和部署稳定性上如果是学习型目标你才需要老老实实去啃Transformer细节。这篇文章以工程型目标为主线同时兼顾学习型目标因为对大多数人来说真正的成长发生在“动手做”和“被问题卡住”之间而不是发生在读书笔记里。1.2 为什么“自己动手”依然值得有人觉得现在是API时代自己训练模型是不划算的。这话对了一半。对绝大多数通用任务调用成熟API的确更便宜、更快但AI工程这个岗位存在的价值恰恰是处理“API不太好使”的场景比如领域数据敏感、需要极低延迟、要私有化部署、要控制每一个生成样本的行为。这些场景下你不可能永远靠别人给的pipeline必须理解从训练到服务的完整链路。另一个容易被忽视的理由是排障能力只能在第一线练出来。用现成框架做微调时一个参数不收敛、显存OOM、生成全是重复词这些问题的根源可能藏在数据采样、学习率调度、tokenizer的padding方式甚至多卡通信默认配置里。如果不懂底层就只能靠猜如果你亲手搭建过一次哪怕很小的训练框架绝大多数问题你一看log就能定位到具体环节。这种能力在面试中也很值钱——能聊明白“你的loss为什么下降得慢”的人永远比只会贴代码的人有优势。1.3 用“最小闭环”定义目标我在带新人时几乎不让他们第一步就去碰大模型。统一的起点是在单卡上用几百万token的数据训练一个参数不超过5000万的小型语言模型然后写一个字符级或子词级的生成接口让它能连续生成文本。就这么一件事在没做过的人眼里毫无吸引力但它能逼着你走完数据加载、batch组装、前向、反向、梯度裁剪、checkpoint、推理采样、指标计算的全过程。这个闭环的价值超过你顺手跑通10个开源项目的价值因为那些项目你只做了一件事——按下运行按钮。接下来我按照一个完整的AI工程项目应该跨越的五个门槛展开这五层逻辑放之任何规模的项目都成立只是规模不同面对的复杂度不同。2. 一整套AI工程需要跨过的五道门槛2.1 数据和数据管线没有数据一切免谈很多人以为“从零开始AI工程”的第一步是写模型但实际上第一步必须是搞定数据。我见过最典型的翻车现场花了一个月研究Transformer最后用爬虫随手抓了一堆网页文本喂进去训练出来的模型除了重复和乱码什么都没有。数据质量决定了模型能力的上限模型架构只是在逼近这个上限这个常识在实操中经常被忽略。一个合格的训练数据管线至少要包含五个环节采集、清洗、去重、分词、格式组织。清洗要处理HTML残留、乱码、敏感信息、过长或过短的样本去重要用MinHash这类技术把近乎重复的文本去掉否则模型会在重复模式上过拟合分词要决定用字符级、BPE还是别人训练好的tokenizer这直接影响语料库的规模估算。组织格式上通常每个样本会拼接成固定长度的序列并加入特殊的起始和结束标记。举个例子如果你用TinyShakespeare数据集原始文本大约1MB在用GPT-2的BPE分词后大约是30万到40万个token。这么小的量只够训练一个玩具模型。但它的好处是干净、小、迭代快。等你在这个规模上把管线跑顺了要换成TB级数据集只是换源和增加并行度的问题思路完全一致。数据这一步最容易被低估实际上它最花时间也最值得花时间。2.2 模型架构和Token化不是背公式的事到了模型层我的建议是一句话不要直接调封装好的Transformer模型来糊弄自己。你至少要亲自动手实现一个极简版才能在与显存和性能搏斗的时候知道哪里能省内存、哪里能加速。具体拆开看一个可用的语言模型由四个核心组件组成tokenizer、token嵌入层、堆叠的Transformer块、输出映射层。Tokenizer负责把文本变成整数id嵌入层把这些id映射成高维向量Transformer块负责在这些向量之间做信息混合主要是多头注意力和前馈网络输出映射层把最后一层表示映射成词典大小上的概率分布。很多人抓不住重点在注意力机制里消耗了太多精力反而忽略了一个事实工程调试中最常见的坑根本不在于注意力数学细节而在于维度匹配。嵌入维度、头数、头维度、中间层维度、层数、序列长度这六个数字相互约束任何一个不匹配都会让模型在第一次前向时报错。我建议你动手实现一个简化模型时先定下一组固定数值比如vocab_size5120, hidden_size256, num_layers4, num_heads8, max_seq_len128前向通过后再逐步加大。2.3 训练循环loss不下降的那一刻才是开始训练层是整个工程里最像“手艺活”的部分。核心组件并不复杂优化器、学习率调度器、损失函数、梯度裁剪、混合精度、checkpoint。但要不要用warmup、学习率峰值设多高、batch size太大导致显存不足怎么处理这些问题没有任何标准答案只能靠对原理的理解加足够的调试经验。先说损失函数。自回归语言模型用的是交叉熵目标就是预测下一个token的概率。一个关键细节是计算loss时要忽略padding位置否则样本长短不一padding会让loss被稀释。接着是优化器选择AdamW是默认选择它对学习率的敏感度低于SGD不容易一开始就发散。学习率调度上推荐先跑一小段warmup再进入余弦退火。这个模式在绝大多数规模下都有效。真正磨人的环节是梯度异常。loss变成NaN、梯度爆炸导致参数变为Inf、loss不降反而上升这些现象几乎每个人都会遇到。我一般排查顺序是先看数据里有没有空样本或全padding样本再看学习率是不是太大然后用梯度裁剪把全局梯度范数限制在1.0附近最后检查混合精度fp16下的loss缩放有没有出问题。把这个排查顺序记牢能省掉一半无谓的时间。2.4 评测拿什么证明它“能用了”技术圈有个很不好的习惯训练完了就靠人眼读几句生成文本觉得“还行”就算结束。这在工程上是不够的。你必须有量化的评测指标才能判断一次修改到底变好了还是变差了。对语言模型来说最基本的指标是困惑度。困惑度低意味着模型对下一个token的预测概率更高但它并不能直接反映生成质量所以还需要任务级指标。如果做文本生成可以算BLEU或ROUGE如果做问答可以算准确率或F1如果做推理模型需要更细的行为指标。这还没完最关键的是要建立属于你自己场景的评测集。它不需要很大但必须覆盖正常输入、边界输入和典型脏输入三类情况。我在自己的项目里常用一个非常土但有效的方法固定20个种子提示词每次训练完就批量生成一遍人工快速扫一遍输出再配合困惑度和任务指标做决策。这个“哨兵集”能第一时间暴露退化比单看曲线图靠谱得多。评测集这件事没有标准答案真正的标准是“你能对别人说清楚你的模型变好了多少、在哪方面变好了”。2.5 部署模型能跑和能服务是两回事训练结束只代表模型文件存在磁盘上了。要把模型变成产品你还得跨过部署这道门槛这也是“AI工程”中的工程二字的重量所在。一个模型单独推理一次可能只要几十毫秒但面对每秒几十个并发请求时显存怎么够、延迟怎么保证、请求怎么排队、生成超时怎么办全都要设计。最简单的起步方案是把模型导出成推理后端的格式用支持动态batch的推理服务挂载一个HTTP接口。动态batch能大幅度提升GPU利用率因为模型在等某个慢请求时可以把其他排队请求拼到一个batch里算。GPU显存不够的时候看量化从fp16降到int8或者int4是常用手段代价是精度略有损失。如果想进一步降低延迟可以调整采样参数比如降低max_new_tokens、使用KV cache的复用等。部署层从0到1并不难难的是你愿不愿意用一个真实模型经历一次线上流量冲击。当“自适应批处理”“超时熔断”这些概念不在你的服务里真实出现一次时你对它们的理解永远是纸面的。3. 从零实操用单卡走通一个端到端小项目3.1 选型微型GPT加单文件语料理论聊完这里给一条我已经验证过多次的最小路径。整个项目只需要一块8GB以上显存的GPUCPU单机也能跑只是慢一点。选型建议如下模型选择一个参数在4000万左右的微型GPT。层数8层嵌入维度256注意力头8个序列长度128词典大小用GPT-2的BPE词汇表大约5万个token。这个规模在8GB显存下可以训练在消费级显卡上也不至于完全不可行。数据选择TinyShakespeare单文本文件大约1MB。或者任何一卷干净的公版小说一次性读入并分词。之所以选单文件数据是为了省掉构建数据管道的时间让你把注意力放在训练本身。但请注意这不意味着数据管线不重要而是这个阶段我们要把它简化到最小可见版本。计算量做个粗略估算4000万参数训练步数假设10000步batch size为32序列长度128每步处理4096个token总计处理约4000万token。这个量级在单张A100上可能十几分钟到半小时跑完在消费级显卡上可能需要几小时。这个规模足够让你完整观察训练动态但又不至于烧掉太多钱。3.2 最小训练脚本怎么组织直接上核心代码。我用PyTorch写一个极简的训练循环伪代码省略一些与模型定义无关的部分但保留训练流程的关键结构。import torch import torch.nn as nn def train_one_epoch(model, dataloader, optimizer, scheduler, grad_clip1.0): model.train() total_loss 0 for step, batch in enumerate(dataloader): input_ids, target_ids batch[input_ids], batch[target_ids] optimizer.zero_grad() logits model(input_ids).logits # shape: [batch, seq_len, vocab_size] loss nn.functional.cross_entropy( logits.view(-1, logits.size(-1)), target_ids.view(-1), ignore_index0, # 这里假设pad_token_id0 ) loss.backward() # 梯度裁剪防止梯度爆炸导致NaN torch.nn.utils.clip_grad_norm_(model.parameters(), grad_clip) optimizer.step() scheduler.step() total_loss loss.item() if step % 100 0: avg_loss total_loss / (step 1) print(fstep {step}: loss {avg_loss:.4f}, lr {scheduler.get_last_lr()[0]:.2e})这个脚本的核心逻辑就三件事切batch、算loss、反向更新。很多人会纠结要不要用现成的Trainer我的建议是学习阶段一定要自己写。只有自己写过这个循环你才理解HuggingFace Trainer里那些参数背后到底在做什么以及为何有时候改了参数却没效果。3.3 训练时要盯着的几个关键数训练过程中不要只看loss。我习惯同时盯着四个数字训练loss、梯度范数、学习率、每秒处理的token数。它们各有意义。训练loss不用说它告诉你模型在训练集上的拟合程度。梯度范数则是健康指示灯如果一直大于5或者持续飙升说明训练不稳定。学习率要按计划走如果使用的是warmup加余弦退火前几百步是预热loss慢一点是正常的。每秒token数告诉你训练效率也能帮你估算总时长。损失曲线有几个常见形态这里列一个速查表表格曲线表现可能原因建议动作loss持续下降但速度很慢学习率过低或模型太小提高学习率或先小规模验证loss先下降后反弹学习率过高或数据噪声大降低学习率检查数据清洗loss出现NaN或剧烈震荡梯度爆炸或fp16溢出开启梯度裁剪调整混合精度loss下降正常但生成乱码生成采样参数错误检查temperature和top_k看看是否贪心解码loss正常但评测指标没提升评测集和训练集分布不一致检查评测集构造防止信息泄漏3.4 跑起来之后怎么验证它真的会“说话”训练完成后你要写一个推理函数。最朴素的生成方式是贪心解码每次取概率最大的token作为下一个token拼到输入后面再重复。贪心解码容易产生重复所以实际会用一个稍微复杂一点的采样策略比如top-k加temperature。下面是一个参考生成接口的例子def generate(model, tokenizer, prompt, max_new_tokens200, temperature0.8, top_k40): model.eval() input_ids tokenizer.encode(prompt, return_tensorspt) with torch.no_grad(): for _ in range(max_new_tokens): logits model(input_ids).logits[:, -1, :] / temperature filtered_logits top_k_filter(logits, top_k) probs torch.softmax(filtered_logits, dim-1) next_id torch.multinomial(probs, num_samples1) input_ids torch.cat([input_ids, next_id], dim-1) if next_id.item() tokenizer.eos_token_id: break return tokenizer.decode(input_ids[0])在微型模型上生成文本效果一定会让你觉得“这也太笨了”但这是正常的。关键不是你得到了多好的文章而是你亲手走完了从数据到模型的闭环。想验证模型确实学到了东西可以做个控制实验拿一个没训练过的模型和训练好的模型分别给同一句话看后者损失是否显著更低。这个对比能让你更直观地理解训练带来的变化。4. 从零构建LLM与从零构建推理模型其实是两件事4.1 真正“从零预训练”一个语言模型现实吗回到开头提到的《Build a Large Language Model from Scratch》这本书。它确实是目前把语言模型内部讲得最透彻的实践书之一但很容易给读者造成一个错觉读完就能自己造大模型。现实是书的完整代码也就训练一个小的GPT模型距离真正的大模型有一条巨大的鸿沟——数据规模和工程复杂度。如果真的想走预训练路线我的建议是分三步走。第一步用书中的代码自己从零实现一个小模型在TinyShakespeare上跑通第二步拆解NanoGPT这种更工程化的开源库看别人如何组织数据加载、checkpoint和分布式逻辑第三步如果有算力用开源预训练框架比如LitGPT对已有的公开数据集做继续预训练或复现而不是真的从互联网原始数据开始处理——那是公司级别的工程不是你个人几周能搞定的。还有个计算预算的常识训练一个大模型的成本大致正比于模型参数量乘以训练token数。即使是一个10亿参数模型训练1000亿token也需要约等于几千张高端GPU卡连续跑数天到数周的算力。个人或小团队做预训练要么把模型缩小到1亿以下参数要么使用大量数据继续训练已有的较小模型。否则不是工程问题是财务问题。4.2 从零构建“推理模型”的可行路径再看另一个热门短语build a reasoning model from scratch。很多人把它理解成“从零训练出一个会推理的模型”但实际上现在的推理模型并不是重新发明一种新架构而是在一个强底座模型之上利用强化学习训练出长思维链的产物。所谓推理能力很多情况下是通过“让模型生成更多推理步骤、再根据结果的正确性给奖励”这种方式激发出来的。工程上比较清晰的路径是这样先有一个基础模型最好是已具备较强通用能力的开源模型。第一步做轻量监督微调准备一批包含长思考过程的示范数据让模型学会在输出答案之前写出推理过程。第二步做强化学习生成多个候选答案用规则或结果判断对错给正确的样本高奖励、错误的样本低奖励并用GRPO这类策略去更新模型。第三步做行为约束通过系统提示词或解码参数控制模型展示思考过程的长度和格式避免它把推理过程全部吞掉。这听起来不算高不可攀但实际操作时最费时间的是搭建“可验证奖励”的评测闭环。比如数学题可以用符号规则判断答案是否一致代码题可以跑测试用例判断能不能通过。泛化到开放域问答这个验证器怎么设计就成了最大的工程难点它甚至比训练本身更难。4.3 这些学习资料应该怎么读《Build a Large Language Model from Scratch》和类似资源我建议按“三遍法”来读。第一遍通读代码不要在细节上停留太久目标是知道每个文件在干什么。第二遍合上书凭记忆自己重写训练循环和模型结构卡住的地方就是你的知识盲区。第三遍再拿书对照只修正关键差异不用逐行一样。这个方法对比直接抄书有个巨大的好处你会把“读懂了”变成“会写了”只有后者才是工程师的真实技能。做推理模型方向也类似。先别拿着别人的强化学习框架直接跑先用一个小模型和一个小数据集自己写一遍策略梯度更新。要理解一个可选动作怎么会影响概率分布才能理解在大模型上为什么要把KL散度控制得那么严格。5. 常见问题与排查技巧实录我把实战中遇到频率最高的问题整理成一个速查表再展开讲几个我踩得最深的坑。这些问题不会只出现在新手身上很多有经验的工程师同样可能翻车只是翻车的姿势更深一点。表格问题现象可能原因排查思路与修复loss不下降学习率太低数据没有shuffle模型参数初始化问题提高学习率检查dataloader的shuffle用一个小batch先过拟合一轮loss下降很快但生成乱码tokenizer与模型训练数据不一致检查prompt是否经过和训练时相同的预处理流程生成内容一直重复采样参数不合适训练不充分模型容量太小提高temperature、加repetition_penalty延长训练或增大模型单卡训练OOMbatch_size太大序列太长模型太大减小batch size开梯度累积使用混合精度或梯度检查点多卡训练loss不一致数据并行时batch分布不同随机种子不同步固定随机种子每张卡分到的数据要均衡且shuffle一致微调之后通用能力下降新数据集太小或学习率太大灾难性遗忘降低学习率混合旧数据用LoRA限制更新范围第一个我要单独说的是“loss下降正常但评测效果差”。这个问题最阴险。它通常意味着评测集跟训练集分布不一致或者评测提示词的格式没有对齐训练格式。比如训练时每条样本开头都有|user|标记评测时忘了加模型面对的输入分布完全不同效果自然会垮掉。所以排查这一问题时先把评测输入打印出来和训练样本做肉眼对比往往一眼就能发现问题。第二个是显存OOM。很多人一上来就加batch size其实很多情况下模型本身可以做得更省。开混合精度是最便宜的省显存方式梯度累积可以在不减少总batch的前提下降低单步显存压力如果序列长度很长可以用梯度检查点它拿时间换显存适合模型大但batch小的场景。我的习惯是先算一笔账模型参数乘以2是fp16梯度的存储乘以4到8是AdamW状态再加上激活值估算差不多就能知道极限在哪。第三个是微调灾难性遗忘。这个问题在“构建推理模型”方向上特别容易出现。你拿一份推理链数据继续训练模型越训越会做这类题但通用的指令跟随能力明显下降。解决思路是混合通用数据把推理数据、指令数据、普通文本按比例混合比如7比3比1并在训练中持续监控一组“哨兵任务”的指标一旦通用指标下滑就降低推理数据的比例或学习率。还有一个坑跟数据泄漏有关。很多人做评测集时直接从训练语料里抽了一部分文档结果模型其实“背过”答案评测指标虚高。你换一批没见过的题效果立刻跳水。解决方法是坚持用时间上靠后、来源完全独立的数据当评测集并且定期人工核对生成结果不要只看自动指标。最后分享一条实战里非常有用的小技巧永远保留下模型初始的checkpoint。所有训练脚本、数据清洗脚本、tokenizer配置全部按时间版本命名。这个习惯听起来老土但当你某天训练出了奇怪模型、需要回溯是哪个数据把行为带偏了的时候版本化是你唯一的朋友。我个人在实际操作中的体会是AI工程能力的增长不是一个平滑曲线而是台阶式上升。每个台阶的起点都是“你知道但还没亲手做的东西”而踩上台阶的关键动作无一例外是动手写一个最小的可运行版本。这个过程里你会犯很多愚蠢的错误但那些错误才是真正的教材。现在再回看标题里的“from scratch”我更愿意把它理解成一种心态不满足于让别人封装好一切愿意钻进黑盒里面看一眼。从这个标准来说哪怕你用现成框架做训练只要你清楚每一步在算什么、每个参数在影响什么你已经是在做真正的AI工程了。最后给正在上路的朋友一个非常具体的建议这个月就定一个“最小闭环”目标——用一块显卡、一份干净语料、一个微型模型从数据读到最终生成跑通一遍。完成之后你再看那些大模型论文、推理模型教程、分布式训练源码会发现它们都变得远比以前好懂。不是因为它们变简单了而是你已经站在入口之内了。
返回列表