
“AI工程”这个说法这两年听得多了但真正称得上“从零开始”把一套东西跑起来的其实没多少人。不是大家不会调 API而是很多人一上来就抱着现成的框架和开源模型出了问题连排查的抓手都没有只能到处抄帖子。我今天想聊的就是我自己从零搭一套 AI 工程能力的过程——不依赖任何封装好的全家桶从数据准备、模型选型、训练调优到部署上线一条链路自己啃下来的完整经验。这适合那些真想搞懂 AI 工程到底是怎么回事的人也适合团队里准备从零搭基建的技术负责人。看完之后你会发现这条路不难走但每一步都有它非走不可的理由。1. 整体思路拆解为什么我坚持要从零开始1.1 “从零”的真正含义很多人一听到 ai-engineering-from-scratch第一反应就是“不用 LangChain 这类框架从底层自己写代码”。这个理解对了一半但不全面。我说的“从零”指的其实是两层意思。第一层是不依赖现成的端到端平台比如那种拖拽式建模工具、一键部署的云套餐这些东西确实快但会把所有实现细节都变成黑盒。第二层是即使用了开源库也要知道它内部到底做了什么。举个例子微调一个模型的时候很多人直接用 HuggingFace 的 Trainer 跑完就完了但你问他学习率是怎么调的、数据是怎么组装的、梯度累积到底起了什么作用他答不上来。这种情况遇到标准场景没问题一旦业务数据特殊、效果不达标就不知道从哪里下手只能盲目试参数。我在这次实践中给自己立了几条规矩所有关键环节必须能解释清楚原理所有配置必须能手动覆盖默认值所有数据变换必须能脱离框架独立实现。听起来很繁琐但这种“笨办法”带来的回报是整个系统出了任何问题我都能快速定位到具体环节而不是对着日志发呆。1.2 为什么在这个时间点选择走这条路说实话如果放在两年前我可能也会推荐直接用封装好的方案因为那时候底层的工具链还不够稳定自己搞一套的维护成本非常高。但现在的生态已经成熟到了一个新阶段基础模型的选择足够多各个开源库的接口趋于统一硬件资源也比以前容易获取。在这种情况下掌握底层能力反而成了性价比更高的选择。打个比方用现成框架相当于开自动挡的车日常代步没问题但从零搭一套工程相当于把发动机、变速箱、传动系统的原理都过了一遍这时候你再去开自动挡感受完全不一样——你知道它换挡的逻辑是什么也知道什么情况下会出问题。对于要把 AI 能力真正落地到业务里的团队来说后者这种掌控感太重要了。我有一次和同行交流他提到他们的系统在高峰期经常出现推理延迟抖动但用的框架是黑盒查了几天都没找到原因。后来我帮他做了个链路剖析发现是数据预处理阶段有个不必要的同步等待。这个问题的根因其实不难找但如果你对整条链路没有底层的认知根本连从哪里查都不知道。2. 工程基础层搭建数据、算力与模型的准备2.1 数据链路的工程化设计数据是 AI 工程的起点也是我在这次实践中花时间最多的部分。很多项目死在第一步不是因为模型不行而是数据链路一塌糊涂训练集和验证集有时间重叠、标签分布严重倾斜、数据清洗逻辑里藏着隐性的 bug。我设计的数据链路分为四个阶段采集、清洗、标注、版本管理。采集阶段要解决的是数据来源的合法性、真实性和覆盖度问题。我做的是一个电商场景的文本分类项目原始数据来自用户评价、客服对话和商品描述三个渠道每个渠道的文本风格差异非常大如果不做分层采样训练出来的模型会偏向样本量最大的那个渠道。清洗阶段最关键的是去重和过滤。文本去重不能只看精确匹配因为很多相似文本只是个别字不一样但语义完全相同。我用的方法是 MinHash 加 LSH 做近似去重把相似度超过阈值的样本合并。这个环节的效果非常显著去重之后数据量减少了 18%但模型效果反而提升了因为模型不再反复学习重复的模式。标注环节我强烈建议不要全量外包。最合理的做法是先用规则或者小模型做预标注然后让人工只对置信度低的部分进行修正。这样既控制了成本又保证了质量。我当时按这个策略标注了 4 万条数据但人工真正介入的只有 9000 条左右。数据版本管理这一点我要特别强调。训练数据和代码一样需要版本管理否则你复现不出历史实验结果。我的做法是每次数据集变更都要记录变更内容、变更原因、变更前后的统计指标并且用 DVC 配合对象存储保存每一版数据的快照。宁可多花点存储钱也不要事后发现“之前那个效果好的模型用的到底是哪版数据”这种尴尬。2.2 算力规划与训练环境算力规划这件事很多人的想法是“越多越好”但实际工程里更重要的是匹配。我个人的经验是先做一个估算不要盲目申请资源。以微调一个 7B 参数的模型为例如果使用 LoRA 这种参数高效微调方法训练时的显存需求大致可以按下面的方式估算模型权重用 FP16 存储占用约 14GB梯度、优化器状态、LoRA 适配层加起来大概还要 10GB 左右再加上 Activations 和中间变量总占用量大约在 35GB 到 40GB 之间。选型的时候就清楚了——单张 40GB 以上的卡就能跑通而不需要一上来就上一整台 8 卡的机器。训练环境我推荐直接用容器方案把 PyTorch、CUDA、依赖库都锁版本打成一个镜像。这里有一个特别容易踩的坑CUDA 版本和 PyTorch 版本不匹配会导致莫名奇妙的算子编译错误。我的经验是先在本地用 CPU 模式跑通整个数据加载和模型初始化的流程确认代码没有逻辑错误再切到 GPU 环境去跑真正的训练。这样可以把“代码问题”和“环境问题”分开排查否则两者混在一起的时候排查效率极低。2.3 模型选型的底层逻辑模型选型这件事直接决定后面的训练成本和推理效率。我见过太多人不管什么场景都往大了选好像模型参数越多效果就一定越好。实际情况是模型能力和参数量的关系并不是线性的参数翻倍带来的效果提升往往远小于翻倍而训练和推理成本却是实打实地涨。我的选型流程是这样的先用一个小规模的模型跑通全流程验证数据质量和训练代码的正确性然后在小规模实验的基础上根据显存占用、训练速度和效果指标这三个维度估算大规模模型的资源需求。之后再决定是使用 7B 还是 13B 的模型。此外还有一个必须考虑的问题是任务类型。如果是做开放式的文本生成需要模型拥有更广的知识面模型规模就得大一些或者搭配 RAG 来补充领域知识。如果是做分类、抽取这类判别式任务中小规模模型加精细的微调往往更合适——这类任务对模型的推理能力要求不太高但对领域数据的拟合要求更细。我见过一个团队用 70B 的模型做商品评论的情感极性分类准确率确实比 7B 模型高了一点但推理成本高了十倍以上而且延迟从 200 毫秒涨到了 2 秒业务方根本接受不了。后来换了 7B 模型加领域数据微调效果几乎持平成本却降了一个数量级。这就是典型的不做选型分析导致的资源错配。3. 核心环节实现训练、评估与调优的关键代码和参数细节3.1 微调方案选择全参还是 PEFT到了训练阶段第一个要决策的问题是微调方式。全参微调的效果上限确实更高但它对显存的需求不是一般的团队能承受的。以 7B 模型为例全参微调需要大约 140GB 的显存而且训练速度慢、对优化器参数敏感调试成本很高。我的选择是 LoRA 这类 PEFT 方法。它的核心思路是冻结原模型的所有参数只为需要适配的层添加低秩的分解矩阵。这样做能把需要训练的参数从几十亿降到几百万显存占用减少到原来的四分之一左右训练速度也能大幅提升。对于大多数业务场景LoRA 的效果和全参微调几乎无差别因为预训练模型的知识已经足够丰富我们要做的只是让模型适配特定的数据分布和输出格式。实际操作中我在配置 LoRA 时关注三个关键参数秩、缩放系数和作用于哪些模块。秩决定了低秩矩阵的表达能力太小了学不到领域特征太大了又失去了 PEFT 的意义。我在文本分类任务上用 8 到 16 之间的值比较合适生成类任务则适当调大。缩放系数的默认值经验是秩的倍数关系我通常先按 2 倍来设再根据训练时的损失变化做调整。一个容易忽略的问题是 LoRA 作用的模块选择。如果只作用于注意力层的 Q 和 V 矩阵训练速度快但效果可能一般如果把 K、Q、V、O 全部加上效果更好但训练时间变长。我的建议是先只改 Q、V 跑一版看效果如果欠拟合再逐步扩展。3.2 数据组织与训练策略的细节训练数据处理有几个细节直接影响到效果。第一个是采样策略。我的项目里三个渠道的数据分布是 5:3:2如果直接混在一起训练模型在客服对话这种样本量小的渠道上表现会比较差。处理方法是把三个渠道的数据按一定比例混合确保每个 batch 里都有各渠道的样本这样模型在每个更新步骤里都能接触到不同风格的文本。第二个细节是文本长度的处理。直接截断会丢信息盲目加长又会拖慢训练速度。我的做法是先统计语料长度的分布取 P90 的位置作为最大长度让绝大多数样本都能完整通过模型。如果发现有少量超长样本包含关键信息再单独设计分段池化的策略。第三个细节是训练过程中的动态调整。我采用的是带预热的学习率调度前几百步用线性预热让模型慢慢从预训练状态过渡到微调状态避免一开始学习率太大把已经学好的参数冲坏。训练过程中我还打开了梯度累积开关——因为我的 batch size 受显存限制只有 16通过累积 4 个 step 的梯度等效的 batch size 变成了 64。这个技巧在显存和效果之间找到了一个比较好的平衡点。3.3 评估体系的建立评估这件事的重要性我无论如何强调都不为过。如果评估指标和实际业务目标脱节那你训练出来的模型很可能指标很好看但上线之后业务效果一塌糊涂。我在项目里用的是三层评估体系。第一层是自动指标分类任务看 F1、精确率和召回率生成任务看 ROUGE 和 BLEU。这些指标适合在训练过程中做快速筛选判断当前这一步迭代是否有效。第二层是规则校验针对业务里明确不允许出现的错误类型设计校验规则比如电商场景里绝对不能把好评识别成差评这类错误即使 F1 再高也不能接受。第三层是人工评测抽一批有代表性的样本让人工打分。这个环节最费时间但也是唯一能发现模型“语义上其实没理解对”这个问题的环节。三层评估结果会汇总到一张评分表里综合判断当前版本是否达到上线标准。我坚持要对“模型的效果”和“效果是否稳定”同时评估。只看一个测试集的指标是不靠谱的我会在训练过程中定期对验证集做多次采样观察指标波动范围。波动大的模型即使均分高上线之后也会有各种意想不到的糟糕案例。3.4 微调过程中的典型训练记录我在 7B 模型上用 LoRA 微调记录了一个比较典型的训练过程。初始学习率 2e-4预热的步数设为总步数的 10%LoRA 的秩设为 16作用在注意力层的全部四个矩阵上。训练集 3.5 万条batch size 用梯度累积等效到 64总共训练 3 个 epoch。第一个 epoch 下来验证集的损失下降非常明显从 1.8 降到 0.9 左右。第二个 epoch 开始下降变缓到第三个 epoch 时验证损失几乎不再变化但训练损失还在缓慢下降——这说明模型开始过拟合了。这时候就不再继续增加 epoch而是回退到第二个 epoch 结束时的检查点作为最终模型。我在实际中反复确认了一个规律不要等到训练完全收敛要在验证集效果开始变平甚至变差之前停下来这个点才是真正的最佳模型。4. 部署上线与持续迭代把模型变成真正可用的服务4.1 推理服务化性能优化的关键模型微调好之后下一步是推理服务化。这个环节最大的挑战不是把模型跑起来而是把延迟和吞吐量优化到业务可接受的水平。我采用的方案是先把模型从 PyTorch 的动态图模式导出为静态图格式的推理引擎版本这样做的好处是推理框架可以对计算图做算符融合减少算子调度开销。实测下来单次推理的延迟在 GPU 上降低了约 30%。另外一个重要的优化手段是动态批处理。单请求推理的 GPU 利用率很低大量时间花在数据搬运上。我给服务增加了动态批处理队列——当请求到达时如果 GPU 当前没有在执行任务等一个极短的时间窗口比如 20 毫秒把这段时间内积累的多个请求拼成一个 batch 一起推理。这个优化直接把吞吐量提升了两倍以上而单个请求的延迟增加几乎可以忽略。还有一个细节是数值精度。FP16 推理精度在大多数任务上已经足够了但偶尔会出现严重的精度损失。我的做法是搭上线前先在完整测试集上对比 FP16 和 FP32 的指标差异如果指标下降超过预设阈值就换回 FP32 或者用混合精度的方式处理。4.2 模型监控与线上效果保障模型部署上线之后真正的工作才刚刚开始。上线前很多问题没有暴露是因为测试集和真实数据分布不一致。我遇到过最典型的情况是测试集上 F1 是 0.92上线后真实数据里的表现却只有 0.8 左右原因是有一种新型的文本表达方式在测试集里完全没出现过。所以模型监控不是只盯系统指标更要盯业务指标。除了常规的请求延迟、GPU 利用率和错误率我还监控线上模型的预测结果分布比如分类标签的分布比例、预测置信度的均值和高低差。如果发现分布和训练时差别太大需要及时触发告警并把问题反馈给数据团队补充新的训练样本。这里有个比较容易被忽视的点预测置信度的校准。模型的输出概率并不一定等于真实概率。我在服务里加了一个温度缩放校准层在验证集上学习一个温度系数把模型的原始 logits 除以这个系数后再做 softmax。这样输出的置信度才比较接近真实概率下游业务在做阈值判断时才有意义。4.3 持续迭代的闭环机制AI 工程的迭代和平时的软件开发不太一样最大的区别在于数据和模型都需要周期性地更新。我搭了一套每周迭代一次的闭环机制线上收集的疑似错误样本每周进行一次人工复核筛选出真正的错误样本加入训练集重新训练模型评估通过后再上线。这套流程跑起来之后模型的指标每个周期都能看到小幅提升。我还做了一个有必要的处理保留每一版模型在固定测试集上的评测结果。这样做的好处是任何一次更新出了效果回退都能立刻对比出是哪次变更导致的。也是因为保留了历史版本线上模型出了问题才能做到秒级回滚。模型版本管理这件事情和代码的 Git 管理一样应该是 AI 工程的基础设施而不是出了问题才想起来的事。5. 全流程实战回顾从零到一搭建一个文本分类系统5.1 需求分析与整体选型为了把上面的思路串成一个完整的例子我就拿我做的电商用户评论分类项目展开说。需求是把用户评论自动分为“质量相关”“物流相关”“服务相关”和“其他”四个类别语料大概是拼多多、淘宝、京东这类平台上用户写的真实评价。这类任务看起来简单其实文本口语化严重、错别字多加上电商平台的评价表述千奇百怪对实际效果影响极大。我在整体选型阶段先拿一个 500M 左右的小模型跑了一遍全流程确认数据链路和训练脚本都没有问题后才切换到 7B 模型上正式训练。这个分两步走的策略帮我省了不少算力也避免了在大模型上反复调试代码的尴尬。5.2 数据处理与模型训练的完整落地数据处理流程完全按第四节讲的链路走。原始数据约 20 万条清洗去重之后剩 16 万条左右再经过预标注和人工校准最终得到训练集 3.5 万条、验证集 5000 条、测试集 5000 条。每条数据通过一个标准的 prompt 模板组织成输入模板里明确告诉模型任务是什么、输出格式是什么——这一点非常重要模型对格式的遵循能力往往决定了下游解析的稳定性。训练脚本我做了两个版本一个用于调试一个用于正式训练。调试版跑一个极小的步数只验证代码不报错正式版才打开完整的评估和日志功能。正式训练用 LoRA 微调 7B 模型总共训练 3 个 epoch单卡 40GB 显存每个 epoch 大约 50 分钟。最终模型在测试集上的 F1 达到了 0.915相比未微调的基础模型提升了 8.7 个百分点。把模型封装成服务时我用的是动态批处理加静态图导出单条评论的 P99 延迟控制在 150 毫秒左右单卡 QPS 可以达到 80 以上这个性能对日均百万级的业务量是完全够用的。上线后我持续监控了两周模型的预测分布稳定没有出现明显的效果衰减。5.3 项目复盘三个最有价值的经验第一个经验是模型微调这种环节的收益是有限的更大的收益来自对数据的理解。我做这个项目最大的效果提升来自清洗策略的调整而不是调参。数据去重之后效果提升了两个点这意味着模型之前至少有接近两个点在“背书”而不是在学习。第二个经验是评估指标和业务目标一定要对齐。只看 F1 会误导你业务里有些分类错误的代价远高于其他错误必须把这些信息通过加权或者规则过滤的方式反馈到评估里。第三个经验是基础设施宁可多花时间也不要偷懒。数据版本管理、实验结果记录、模型版本管理这些前期的工作可能会让项目启动慢几天但在整个生命周期里节省的时间是几倍甚至十几倍。6. 常见问题与排查技巧实录6.1 训练阶段的典型问题训练过程中最容易遇到两类问题。一类是 Loss 不下降通常由学习率设置不当、数据有错误或者是数据处理流程有 bug 导致。我的排查思路是先拿一个小数据集跑训练确认代码没问题再检查数据。如果数据也没问题就检查梯度在训练脚本里插入几个 hook打印每一层梯度的均值和方差。如果梯度接近零说明模型进入了饱和区或者学习率太小如果梯度有异常大的值多半是数据里出现了异常样本。另一类是训练速度慢。除了硬件性能原因很多时候是数据加载的效率问题。我遇到过因为读取数据时用了不必要的同步操作导致 GPU 大量时间在等待数据的情况解决方法是把数据加载改成异步模式并且把数据预先做成内存映射格式而不是在训练时反复做磁盘 IO。仅仅这两个改动训练速度就提升了近三倍。6.2 部署与运行时的典型问题部署阶段让我印象深刻的是模型推理结果出现了大量诡异的输出——模型会产生一些不存在的分类标签。后来排查发现是 prompt 模板里用了特殊分隔符Tokenization 在线上和训练时不一致导致。说实话训练和推理时的预处理逻辑不一致这个问题是 AAA 级的坑。我现在会特意写一版测试用例把训练时的预处理和推理时的预处理输出做对比保证它们完全一致。还有一个常见问题是显存泄漏。推理服务跑时间长了显存占用会缓慢增长最终导致 OOM。排查工具推荐用 PyTorch 内存快照工具把快照 dump 出来看哪些 tensor 没被释放。大多数情况下是模型推理的循环里有变量引用恶性累积修正后通常就恢复正常。我建议把“长时间压力测试”作为上线的前置条件至少跑 72 小时看内存是否平稳。6.3 一个非常有用的排查思路最后分享一个通用的排查方法论。当我面对一个“模型效果差”的问题时我不会直接去调模型参数而是按照下面的顺序一层层排查先确认数据是否有问题再确认训练过程是否正常再确认评估方式和业务目标是否对齐最后才看模型结构和参数设置。这个顺序看起来每一步都没什么技术含量但实际操作中能帮你避开大量无效调参。多数“效果差”的问题最后都能往前追溯发现是数据问题或者评估问题而不是模型本身的问题。先能干这个排查逻辑整个项目的推进就顺了很多。7. 写在最后做完这个项目我最大的体会从零搭建完这一套 AI 工程能力之后我回到最初那个问题上——为什么值得花这么多时间从零开始我的答案是从零开始意味着你积累的不是某个框架的用法而是判断力。你知道什么环节在什么条件下会有问题你知道性能瓶颈大概率出现在哪里你不再需要依赖某个平台或者某个工具链来替你兜底。我自己的实际操作体会是不要试图一步到位。先跑通一条最小的数据链路用最小的模型、最小的数据量把代码跑通把评估指标跑起来然后逐步把规模扩大。每扩大一步记录一次结果保证每步都是可复现、可解释的。这个过程不需要你懂特别高深的数学但需要你有足够的耐心接受手上的问题是一个系统性的工程问题而不是某一个魔法参数能解锁的。最后再分享一个小技巧把所有运行过的实验都记录下来哪怕是很失败的实验也要记录。我自己的实验记录里保存了大量“这个参数不行”“这个数据处理导致效果变差”的笔记。项目做久了以后再回头看这些记录会发现它们在指导新一轮迭代时比任何文档都有用。AI 工程这条路没有捷径但每一步认真走下来回头看的风景会让你觉得一切值得。