ARTICLE DETAIL

资讯详情

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

从零开始AI工程化:手写模型训练到推理部署的全流程指南

从零开始AI工程化:手写模型训练到推理部署的全流程指南 从零开始做 AI 工程化到底在做什么我的第一反应是这是一条看起来有手就行、实际上一脚一个坑的路。网上到处都在聊大模型、智能体、RAG但真正动手的时候你会发现最缺的不是懂概念而是能把概念变成能跑的东西的本事。ai-engineering-from-scratch这个标题我愿称之为 AI 领域最诚实的一种学习方式不依赖现成框架的黑盒封装不靠调别人的库来骗自己从头把数据、模型、训练、推理、部署每一环都亲手推一遍。这个过程极其折磨但走完之后你对整个 AI 系统的理解深度和只会调用接口的人完全不在一个维度。这篇文章不是什么理论大杂烩而是我从零搭建 AI 工程能力时真实走过的路。我会先讲清楚从零到底指什么、该怎么拆解目标然后给你一套可以照着落地的技术栈和学习路径再拆开几个核心环节比如数据准备、词表构建、训练循环、推理部署讲清每一步的原理和实操要点最后把踩过的坑、排查问题的方法全部整理出来。无论你是有基础的开发者想补工程短板还是刚入门想找一条扎实的路线这篇文章都能让你少走至少两个月的弯路。1. 整体设计与思路拆解1.1 什么叫从零先别急着自我感动很多人一提from scratch就以为是连 Python 都要从语法学起这是误解。AI 工程里的从零指的是不依赖现成的 AI 框架和模型封装从数据整理、模型结构定义、训练逻辑编写、到推理服务部署每一层都自己亲手实现一遍。你可以用 PyTorch 这种基础深度学习库但不要去调transformers里写好的BertModel或者AutoModelForCausalLM你可以参考论文公式但不要复制别人的train.py就跑。举个例子当你决定从零构建大语言模型时你至少得亲手做这几件事自己写分词器的训练逻辑而不是调用现成的tokenizers库一键完成自己实现多头注意力机制和前馈网络而不是nn.Transformer直接堆自己写训练循环、梯度累积、学习率调度自己处理数据集的打包、采样、填充逻辑自己写推理时的自回归生成函数包括 KV 缓存这类细节。我见过太多人学过 AI了但让他解释一下attention_mask在计算过程中到底是怎么被广播到注意力分数上的直接就卡住。这说明他只是在用不是懂。从零做一遍天然就会逼你把这些问题搞清楚。1.2 为什么非要走这条笨路直接告诉你我的判断如果目标是尽快做出一个能用的产品那别从零造轮子该用现成框架就用现成框架。但如果目标是建立可迁移的 AI 工程能力那从零走一遍几乎是必经之路原因有三。第一框架封装了太多底层逻辑掩盖了问题的真正来源。模型不收敛、显存溢出、推理速度慢你以为是代码写错了实际上可能是对注意力机制的计算流程理解不透。你如果亲手实现过反向传播中的梯度流动排错的速度会快上一个数量级。第二AI 工程最值钱的不是调参而是做决策。用多大的词表、序列长度取多少、用哪种位置编码、要不要做梯度累积、batch size 和显存之间怎么平衡、推理时用贪心还是采样。每个决策都依赖对底层机制的理解。从零做过一遍之后你会形成自己的决策框架而不是永远在等别人告诉你超参数是多少。第三大模型领域迭代快抽象层不稳定。你今天学会的框架封装明年可能就变了但注意力机制、训练原理、分布式策略这些东西五年前和五年后不会有本质变化它们才是 AI 工程的稳定内核。1.3 从零开始的技术栈选型与工具准备我用到的技术栈比较主流你也可以根据自己的情况替换但思路是一样的。层级选型说明编程语言Python 3.10AI 工程事实标准生态最全数值计算库PyTorch 2.x动态图更适合教学查错便于自定义训练逻辑数据处理NumPy / DatasetsDatasets 处理大数据集更省内存可视化TensorBoard / wandb监控训练曲线必备环境管理conda / venv隔离依赖避免环境冲突硬件单张 RTX 4090 起步24G 显存足够训练小型模型也适合复现实验这套组合的好处是每一层都有替代方案但都不影响你理解核心逻辑。PyTorch 的nn.Module接口足够底层可以让你自己注册参数、写前向传播同时它的autograd机制又帮你省去了手动写反向传播的繁琐能让你把精力集中在模型架构和训练策略上。2. 从零构建语言模型核心细节与实操要点2.1 数据工程Token 化之前的那些事所有人都盯着模型结构但真正决定你模型上限的往往是数据。我做过几次实验同样的代码架构换了数据清洗策略下游任务效果差距可以有两位数百分比。从零构建数据管线的时候有几个细节值得花大力气编码一致性。从互联网上爬来的数据会有各种编码问题。常见的坑包括繁体/简体并存、全角半角混用、HTML 标签残留、不可见字符和零宽字符。这些数据直接丢进 tokenizer 里训练词表会被垃圾字符污染模型生成质量也明显变差。我建议在清洗阶段至少做一次unicode标准化统一转成 NFC 形式把全角字符转半角再用正则把乱码和 HTML 标签清理掉。重复数据的去重。LLM 训练时重复数据会严重拖慢收敛速度、加剧过拟合甚至导致模型背答案而非学规律。简单方案是用哈希去重更推荐的做法是按内容相似度做去重。比如先把文本转成 n-gram 集合再按 Jaccard 相似度筛选。这个环节挺吃算力但效果立竿见影。质量过滤的策略。不是所有文本都值得训练。广告、SEO 垃圾站、无意义问答、日志信息这些噪音对模型有害无益。我实践下来比较好用的过滤器有三个维度长度过滤太短的句子信息量不够直接扔掉、语言标识过滤用语言检测模型筛掉目标语言之外的文本、符号密度过滤标点、特殊字符占比过高的文本多半是乱码。数据工程的产出是一个干净、去重、格式统一的文本语料库。看起来简单做起来却最费时间。但这一步省下的时间会在训练阶段十倍百倍地还回来。2.2 从零训练 BPE 词表为什么拿现成的会翻车很多初学者图省事直接拿现成的 BPE 词表比如 GPT-2 的词汇表来用。问题是词表跟领域强相关。你让模型去处理法律文书却拿着从英文维基百科训练出来的词表效果一定差。原因很简单——法律术语会被拆成零碎的 subword序列长度暴增模型根本捕捉不到词语级别的语义。自己训练一个 BPE 词表其实并不复杂。核心步骤如下数据足够大至少几 GB 的文本Stack Overflow、GitHub 代码、百科语料都行初始化词表包含所有单个字符不断统计相邻字符对的共现频率把最高频的 pair 合并成新 token重复直到词表大小达到目标常见取值 32K、50K、100K对 OOV词表外单词的情况因为 BPE 基于 subword理论上不会有真正无法表示的 token只是表示粒度粗细的问题。词表大小直接决定模型的 Embedding 层参数量。50K 词表配上 768 维 hidden size光 embedding 就是 50K × 768 ≈ 3800 万参数这还不算输出层。小模型用大词表会导致参数量浪费在 embedding 上模型容量不够学习复杂语义。这是初学者常犯的错误我一开始也没意识到。我的个人建议起步阶段 8K~32K 词表足够用。词表太小的坏处是序列过长、训练变慢词表太大的坏处是参数浪费、小模型学不动。32K 是一个兼顾训练速度和表达能力的经验值。2.3 注意力机制实现中的魔鬼细节很多人以为实现注意力机制就是套用公式softmax(QK^T / sqrt(d)) V。真要自己从零写有几个细节会卡住你很久我一一说清楚。第一Key 和 Query 的维度匹配问题。多头注意力中head_dim hidden_dim / num_heads这个必须整除。如果你设置hidden768、num_heads12每一步的head_dim64这是标准配置。但如果你把num_heads设成 16 会怎样768/1648也能跑但效果往往不如 64 的整数倍好。因为head_dim太小每个头的表达能力受限太大则头之间的差异化不足。常见经验值在 32~128 之间。第二注意力掩码的广播机制。padding mask 的形状是[batch, 1, 1, seq_len]因果 mask 的形状是[1, 1, seq_len, seq_len]。它们在计算注意力分数时需要广播到[batch, num_heads, seq_len, seq_len]才能生效。我见过很多人在这里写错padding 的部分没有被 mask 掉导致模型在训练时看到了 padding token 的信息。调试方法很简单打印 attention weights 的热力图如果 padding 位置有高权重说明 mask 没生效。第三Scale 因子的意义。QK^T的结果随着维度增大而增大如果不做缩放softmax 会进入饱和区梯度极小、难以训练。1/sqrt(d_k)这个缩放因子本质上是让 QK^T 的方差保持在 1 附近。这不是拍脑袋来的是数学推导过的结果。第四KV Cache 的实现。推理阶段每生成一个新 token其实只需要计算新 token 与已有 token 的注意力已有的 Key 和 Value 不需要重新计算。把它们缓存下来能省掉约 50% 的重复计算。这个优化不做的话推理速度会慢到怀疑人生。这几个细节从零做一遍才能有切身体会靠看博客是记不住的。2.4 训练一个 BabyGPT从数据打包到 loss 不降我从零构建的第一个语言模型是参数量约 5000 万的小型 GPT。这个体量在单张 4090 上跑得飞快几分钟迭代一轮非常适合验证训练流程是否通顺。给它起了个外号叫BabyGPT。训练管线我拆成了这几个环节数据打包。把清洗后的文本切成等长序列比如 256 个 token。做法是先把所有文本拼成一个超长 token 流然后按seq_len切片。相邻 token 之间保留上下文关联比随机采样效果稳定。我试过随机打乱句子再拼接训练 loss 永远下不去——因为前后文根本不通顺模型学不到任何语言规律。训练循环。训练步骤并不神秘就是重复四件事forward算 loss →backward算梯度 →optimizer.step()更新参数 →lr_scheduler.step()调整学习率。但有几个坑必须注意一是有batch维度的计算要避免维度错误二是gradient_accumulation的实现逻辑三是显存溢出时的处理方案。超参数的合理性。我最初训练不收敛以为代码哪里有 bug排查半天发现是学习率太大了。3e-4在小模型上跑得不错但在我的模型规模和 batch size 下就是发散。后来把学习率降到1e-4loss 曲线肉眼可见地稳定下降。训练监控。只盯着 loss 会掩盖问题。我后来习惯用 TensorBoard 同时看四组曲线训练 loss、验证 loss、梯度范数、学习率。梯度范数爆表比如超过 10说明有不稳定因素要么调低学习率要么查一下数据里是不是有异常样本。下面是 BabyGPT 在 8GB 左右数据上训练 20 个 epoch 的超参配置可以作为起步参考参数数值备注vocab_size8192BPE 词表hidden_size384不能太小否则学不到东西num_layers6层数少一点调试方便num_heads6head_dim 64seq_len256显存不够就减小batch_size32具体看显存余量learning_rate2e-4配 cosine 衰减warmup_steps500前 500 步线性升到峰值优化器AdamWbeta(0.9, 0.95)这套配置在 24G 显存上能跑到大约每秒 8~12 个 step取决于数据读取效率。loss 从初始的ln(8192) ≈ 9.01逐步降到 4.5 左右生成出来的文本已经有一点语义连贯性了。3. 推理与部署从模型到可用服务3.1 自回归生成贪心、采样与温度训练完成之后最兴奋的时刻就是把模型跑起来看它输出。自回归生成的实现看似简单——输入一个 prompt预测下一个 token把它拼回去再预测下一个——但中间的细节能直接决定生成质量。最朴素的是贪心搜索每一步只选概率最高的 token。贪心的问题在于它容易陷入重复。写过几次之后生成文本会进入复读机模式。原因是高概率的 token 往往集中在重复的短语上。所以实际部署中更常用的是采样策略。从概率分布中随机采样但用temperature参数调节分布的尖锐程度。temperature 小于 1分布更尖锐输出更保守大于 1分布更平滑输出更大胆但胡言乱语的风险也更高。常见做法是 temperature 取0.7~0.9配合top_p0.9的核采样效果比较均衡。常见的温度参数示例代码生成任务temperature0.2~0.3要的是确定性开放域对话temperature0.8~0.9要的是多样性摘要任务temperature0.5 左右既要忠于原文又要有一定表达空间。还有一个经常被忽略的细节EOS 停止符的处理。生成模型需要学会在合适的地方输出|endoftext|否则你会得到一篇活着一直输出到 max_length 截断的文本。训练过程中就要让模型见过足够多的 EOS 样本推理时也需要在检测到 EOS 后立即终止循环。3.2 工程化部署的关键量化、批处理与缓存模型能跑起来和能上线服务之间隔着好大一段距离。真正做过部署的人都会对这三件事深有体会。第一量化。FP16 推理是常规操作但显存实在不够的时候可以考虑 INT8 量化。INT8 把模型体积压缩到原来的 1/4 左右显存占用大减推理速度反而因内存带宽瓶颈的缓解而变快。精度损失在小模型上明显但在 7B 以上模型上通常可以接受。我自己的经验是如果只是内部演示INT8 完全够用如果要做产品级输出至少要跑一遍基准测试看准确率掉多少。第二批处理。单条请求逐个推理GPU 利用率低到离谱。正确做法是动态批处理把等待队列里的请求攒起来凑够一个 batch 再一起推理。因为 self-attention 的并行性batch 大小为 8 时的单条延迟往往只比 batch 为 1 时多 20%~30%但吞吐量提升接近 8 倍。这就是典型的数学上划算的优化。第三KV Cache 与显存规划。KV Cache 的显存消耗是隐性的很多人上线当天才被它打懵。以 7B 模型为例num_layers32、num_heads32、head_dim128、序列长度 2048单条请求的 KV Cache 占用约为 32 × 2 × 2048 × 4096 × 2FP16 字节 约 2GB。你以为 24G 显存装得下 7B 模型约 14GB实际上加上 KV Cache 和中间激活值能服务的并发数远比想象中少。部署的完整链路应该是模型导出 → 量化/剪枝 → 服务封装 → 请求调度 → 监控与灰度发布。每一步都有大量细节但从零走通一次你对AI 工程这四个字的理解会完全不一样。3.3 评测闭环不准就没有优化方向部署不是终点。我见过太多团队模型上线之后评测指标停留在看起来还行的水平。没有量化评测就没有办法判断后续的迭代是否真的有效。从零构建评测闭环我的建议是分三层底层是指标层Perplexity 衡量语言模型基础能力BLEU 和 ROUGE 衡量生成质量准确率和 F1 用于分类任务。每个指标都有各自的局限性不能只看一个数字就下结论。中间是任务层针对你的场景梳理出 10~20 个典型测试用例。比如写代码、写文案、做摘要、解答常识问题。这些用例要覆盖正常情况、边界情况和刁钻情况。顶层是对比层把你从零训练的模型和社区开源的同级模型放在同一批测试集上跑量化差距。对比不是为了打击自己而是为了明确优化方向——如果差距主要来自数据规模那就扩容数据如果差距主要来自架构那就调结构。没有评测闭环的从零构建只是从零训练有了评测闭环才配叫从零工程化。4. 推理模型方向与进阶路径4.1 从语言模型到推理模型到底多了什么从零构建推理模型是最近很热的方向。推理模型和普通语言模型的区别从工程角度看核心在于两件事。第一是中间推理路径。普通语言模型直接给出答案推理模型会先产出一段显式的思考过程再给出结论。训练这类模型的关键在于构建思考过程 最终答案的成对数据。数据质量比数据数量更重要。我试过用现有大模型自动生成推理路径来训练小模型效果完全取决于教师模型在目标领域的推理可靠性教师模型不行出来的推理链路就是一本正经的胡说八道。第二是搜索与回溯机制。更强的推理模型会在生成过程中动态探索多条路径评估哪条路径更合理。这种机制目前大多依赖大规模强化学习和大量试错。从零实现一个能真正思考的推理模型门槛比语言模型高不少。4.2 复现论文实验的工程素养如果你也打算从零走一遍我建议你拿一篇论文作为锚点比如 GPT-2 或者 LLaMA 的架构解析。不要只看结论重点看清楚了这些工程细节训练数据规模与来源分词器类型、词表大小、训练语料模型初始化方式优化器配置、学习率调度、warmup 策略训练稳定性和损失曲线形态评测指标和数据集。我自己的习惯是拿一张纸把论文里的关键数字全部抄下来对比自己实验的数字逐项找差距。这种数字对比法比读十篇综述都有用。4.3 长期学习路线规划建议从零开始做 AI 工程最怕的是东学一块、西学一块最后啥都不精。我建议你按顺序来每一阶段都有明确的产出作为验收标准阶段目标验收标准第一阶段掌握 Python、PyTorch、数据处理能实现一个 MLP 并完成分类任务第二阶段数据工程能训练一个 8K 词表的 BPE第三阶段模型架构能实现完整的 GPT-2 架构第四阶段训练策略训练一个 loss 合理下降的 50M 模型第五阶段推理部署模型能封装成 HTTP 服务并被调用第六阶段评测闭环能在固定测试集上对比不同版本模型第七阶段进阶方向尝试推理模型、多模态、强化学习这个路线不需要一年甚至不需要半年。但每走一步都要确保前一步是扎实的。5. 常见问题与排查技巧实录5.1 训练不收敛先查这三个地方如果你发现 loss 一直在高位震荡甚至发散别急着调参先按这个顺序排查。第一数据对不对。把训练数据随机抽几条打印出来人工读一遍。如果 text 拼接处明显语义断裂、padding 位置混入有效 token、或者 EOS 标签错误那训练不收敛就太正常了。数据问题占到我踩过的坑的六成以上。第二代码的数值稳定性。检查 softmax 的输入是否包含了NaN或过大值检查 loss 计算时是否出现了log(0)的情况检查 embedding 层的梯度是否为NaN。一种快速定位方法在每次backward()之后打印所有参数的梯度范数范数为 0 或为 NaN 的地方往往就是问题源头。第三超参数是否在合理区间。学习率太大是最常见原因。我的判断标准是如果 loss 在前 100 步直接冲到无穷大把学习率除以 10 再试如果在 1000 步内 loss 几乎不动把学习率乘以 3~5 再试。我在多数情况下用的排查顺序是数据 → 数值 → 超参。从经验看这个顺序排查效率最高因为数据问题定位成本最低、超参问题最费算力放到最后试探性调整。5.2 显存溢出、OOM 分析与显存规划显存溢出OOM是从零训练最常遇到的报错。原因可能是模型太大显存装不下也可能是序列太长、batch 太大。按照 GPU 显存占用的组成来逐个拆解模型参数总参数 × 4 字节FP32或 × 2 字节FP16优化器状态AdamW 需要额外的 8~12 字节每参数激活值每一层的中间激活显存大头。处理方案按优先级排列首先减batch_size一次减半简单有效其次用梯度累积模拟大 batch多次forward/backward后再执行一次optimizer.step()尝试开启torch.utils.checkpoint做激活检查点用时间换显存最后考虑模型层面减小seq_len或模型层数。还有一种情况是显存碎片化。训练时间长了之后显存被切成碎片单次申请大块显存失败但总量还够。这种情况下减小 batch 往往没用考虑重启进程或使用官方推荐的内存分配策略。5.3 推理太慢的破局思路推理慢分几种情况对应的解法完全不同我对它们的推荐顺序如下症状根因解法单条生成延迟高没有开启 KV Cache实现 KV Cache 后再测速吞吐量上不去没有做批处理实现动态 batch 调度延迟波动大请求长短差异大按长度分配批处理策略显存不够导致换页模型太大或 KV Cache 超限量化、减小最大生成长度5.4 绕过效果不理想的几个自我诊断问题训练完的模型能力不行你容易陷入自我怀疑不知道怎么改进。我习惯问自己几个标准问题来定方向是训练数据太少还是数据质量太差如果是后者优先清洗数据是模型容量不足还是训练不充分看验证 loss 是否还在下降如果下降继续训就行是评测指标不合理还是模型真不行换几个评测集交叉验证有时候是评测选得有问题是生成策略的问题还是模型本身的问题同样的模型换采样参数效果差别极大。我觉得这也是新手阶段最容易忽略的事你反复调代码结果问题根本不在代码里。先把问题定位准确再决定工具怎么用能在很大程度上避免无意义的折腾。6. 写在最后的一些体会从零开始做 AI 工程我最大的感受是这个领域的信息量太多、噪音太大真正的护城河不是知道很多概念而是亲手修好过很多 Bug。每修好一个 Bug你对系统的理解就加深一层。这种理解没法靠任何课程灌输只能在动手踩坑中一点点积累。如果你现在正准备走这条路我的建议就三条第一别贪大先从 50M 参数的小模型开始跑通完整链路比训练一个大模型有价值得多第二留着基线每次改动都保留一个可运行的版本否则你会陷入改完不知道怎么改回去的困境第三把实验过程完整记录下来包括数据规模、超参数、loss 曲线、踩过的坑。这份记录到后期就是你最宝贵的参考比任何开源项目都更适合指导你下一步的优化方向。我还想纠正一个心态误区从零做 AI 工程并不意味着什么都从零开始实现。基础库、工具链、成熟算子库该用就用那些是我们在巨人肩膀上。真正要从零的是你对整个系统的掌控力——你知道每一步在做什么为什么这么做出了问题知道你改的是什么位置。能拿 GPT-2 架构训练出一个真正能用的模型这项工程能力值得你花几个月去打磨。它会让你在后续任何一个 AI 项目里都拥有比只会调 API的人更深一层的判断力。这条路确实辛苦但回过头看每一步都算数。
返回列表