☰
从零手搓AI工程:架构设计、参数计算与故障排查实战
2026/9/30 3:50:03 网站建设 项目流程

1. 从零手搓AI工程:为什么我不建议你直接调包

第一次看到ai-engineering-from-scratch这个项目名的时候,我脑子里蹦出来的画面是:一个人坐在黑漆漆的终端前,拒绝所有现成的框架,从矩阵乘法开始一行一行地把一个能跑起来的AI系统敲出来。这个直觉基本是对的,但又不完全对。这个标题背后真正指向的,是一整套“把AI从论文里的公式变成能上线、能扛住真实流量、能被别人接手维护的工程系统”的完整能力链条,而不是单纯地“不用框架”。

我在这个方向上折腾了差不多三年,带过几个从零起步的小团队,也接手过别人半途而废的“自研AI平台”。踩过的坑足够写一本小册子。所以这篇东西不打算给你灌鸡汤,也不打算复述那些“AI工程师需要掌握数学、编程、业务”之类的废话。我想做的是,把ai-engineering-from-scratch这个命题拆开,告诉你一个真正从零构建AI工程体系的人,在每一个关键节点上到底该做什么选择、为什么这么选、以及选错了会付出什么代价。

先说清楚这个内容适合谁。如果你是完全零基础、连Python都没写过几行的人,这篇会让你有点吃力,但硬啃下来能帮你建立正确的方向感,少走两年弯路。如果你已经会用几个主流框架、能跑通demo但一上生产就各种崩的人,这篇基本就是给你写的,因为从“能跑”到“能扛”之间的那段路,才是AI工程真正的分水岭。如果你已经是资深从业者,可以重点看我在参数计算、故障排查和工程取舍上的具体做法,那些是我用真金白银的线上事故换来的。

核心关键词ai-engineering-from-scratch我理解成三层含义:第一层是从零搭建,指基础设施、数据管道、训练流程、推理服务这些环节你都得自己趟一遍;第二层是工程化,指你做的不是实验室玩具,而是要面对并发、延迟、成本、可维护性这些现实约束;第三层是从原理出发,指你得知道每个组件为什么存在,而不是把它当成黑盒。这三层缺一层,你做的都只能叫“AI实验”,不能叫“AI工程”。

下面我会按照一个真实的从零构建路径来展开:先讲整体架构怎么设计、为什么这么设计,再拆核心模块的实现细节和参数计算,然后是完整的实操流程和现场记录,最后是我这些年攒下来的故障排查表和避坑经验。每一部分我都会尽量给出可以直接抄作业的方案,同时把背后的“为什么”讲透。

2. 整体架构设计与技术选型思路

2.1 从需求倒推架构:先想清楚你要扛什么

很多人一上来就问“我该用什么框架”“我该买什么显卡”,这是典型的顺序错误。正确的第一步是搞清楚你的系统要面对什么样的负载。我见过太多团队花两个月搭了一套漂亮的训练流水线,结果上线发现推理请求的QPS只有个位数,整套分布式训练的东西全成了摆设。

从零构建AI工程,我习惯先把需求拆成四个维度来量化。第一个维度是数据规模:你的训练数据是GB级、TB级还是PB级?这直接决定了你需不需要分布式存储和分布式训练。第二个维度是模型规模:参数量是百万级、十亿级还是更大?这决定了单卡能不能放下、要不要做模型并行。第三个维度是推理负载:峰值QPS是多少、P99延迟要求是多少毫秒?这决定了你的服务架构是单机还是集群、要不要做批处理。第四个维度是迭代频率:模型多久更新一次?是每天一次还是每周一次?这决定了你的CI/CD和模型版本管理要做多重。

举个我实际遇到的例子。有个做内容审核的场景,数据量不大(几百GB文本),模型是几亿参数的分类模型,但推理峰值QPS能到两千,P99延迟要求200毫秒以内。这种情况下,训练侧其实一台带几张卡的机器就够了,真正的难点全在推理侧的高并发和低延迟。如果一开始把精力全砸在分布式训练上,就是典型的用力用错地方。反过来,另一个做推荐召回的场景,数据是TB级、模型参数几十亿、但推理QPS只有几十,那训练效率和模型容量才是核心矛盾。

所以我的建议是,动手写第一行代码之前,先拿一张纸把这四个维度的数字写下来,然后据此画一张最简架构图。这张图不用漂亮,但要能回答:数据从哪来、经过哪些处理、模型在哪训练、训练完的产物存哪、推理服务怎么部署、请求怎么进来。这张图就是你后面所有技术选型的依据。

2.2 技术栈选型:每个选择背后的取舍逻辑

架构图有了,接下来是选型。这里我要强调一个原则:从零构建不等于从零造轮子。ai-engineering-from-scratch的核心是让你理解每个环节,而不是让你用汇编去写矩阵乘法。合理的做法是,在你能掌控的范围内选择成熟组件,但必须清楚每个组件在干什么。

计算框架层面,PyTorch 目前是绝大多数场景的默认选择,生态最全、调试最方便、社区最活跃。TensorFlow 在部分生产部署场景仍有优势,尤其是需要用到特定推理优化工具链的时候。JAX 适合做研究和对函数式编程有偏好的团队,但工程生态相对薄一些。我的经验是,除非有非常明确的理由,否则训练侧选 PyTorch,能省掉大量踩坑时间。

推理框架层面,这是从零构建时最容易被低估的环节。训练用的框架直接拿来做推理,性能往往差一大截。常见的做法是训练用 PyTorch,推理时把模型导出成中间格式,再用专门的推理引擎加载。这里涉及一个关键决策:你是要追求极致性能,还是要追求部署简单?追求性能就上专门的推理引擎,追求简单就直接用原框架的服务化方案。我个人的分界线是:如果P99延迟要求在100毫秒以内且QPS超过500,就值得上专门的推理引擎;否则原框架的服务化方案足够用,维护成本低得多。

数据层这块,小规模用文件系统加内存缓存就能撑很久,别急着上分布式存储。我见过数据才几十GB就搭一套分布式文件系统的,纯属给自己找麻烦。真正需要分布式存储的临界点,大概是单机磁盘放不下、或者多机训练需要共享数据的时候。数据处理管道同理,数据量小的时候用脚本跑批就行,数据量大且需要流式处理时再考虑专门的数据管道工具。

服务层是最容易被忽视但最影响线上稳定性的部分。从零构建时,很多人会把模型推理逻辑直接塞进一个Web框架的路由里,这在demo阶段没问题,但一上生产就会暴露各种问题:请求排队、内存泄漏、模型加载阻塞、优雅退出等等。我的做法是把推理服务单独抽出来,用一个专门的进程管理模型的生命周期,Web层只负责请求转发和结果返回。这样模型加载、卸载、更新都不会影响Web层的稳定性。

2.3 环境与依赖管理:别让环境问题吃掉你一半时间

从零构建AI工程,环境问题能吃掉你至少三分之一的时间,这是我用血泪换来的教训。核心原则是:训练环境和推理环境必须分离,且都要能一键复现。

训练环境的特点是依赖多、更新快、对GPU驱动版本敏感。推理环境的特点是依赖少、要稳定、对启动速度敏感。把这两个环境混在一起,结果就是你想升级一个训练用的库,结果把线上推理服务搞崩了。我的做法是训练环境用容器镜像管理,每次实验锁定一个镜像版本;推理环境用更精简的镜像,只装推理必需的依赖,且镜像构建后不再轻易改动。

依赖锁定这块,Python生态里pip的requirements.txt只能锁顶层依赖,底层依赖的版本漂移照样会让你的环境在某次重新安装后崩掉。更可靠的做法是用能锁定完整依赖树的工具,把每个包的精确版本和哈希都固定下来。这样即使上游包更新了,你的环境也不会变。这个习惯在从零构建时尤其重要,因为你会频繁地重建环境,没有锁定的话每次重建都可能引入新的不确定性。

GPU环境还有一层额外的坑:驱动版本、CUDA版本、框架版本三者之间有严格的兼容矩阵。我踩过最惨的一次是,训练机器上驱动版本比推理机器新了一个大版本,结果同一个模型在两边加载出来的结果有微小差异,排查了整整两天才发现是底层算子实现不同导致的。从那以后,我要求所有涉及GPU的环境,驱动和CUDA版本必须统一,且记录在案。

3. 核心模块实现与关键参数计算

3.1 数据管道:从原始数据到可训练样本

数据管道是AI工程里最不起眼但最影响最终效果的部分。我见过太多团队模型结构调了几个月,最后发现是数据管道里有bug导致标签错位。从零构建时,数据管道要解决四个问题:读取、清洗、转换、喂给训练。

读取环节的核心是IO效率。如果数据存在本地磁盘,顺序读取通常比随机读取快一个数量级,所以数据文件的组织方式很关键。我的做法是把训练数据打成若干个较大的分片文件,每个分片内部按顺序存储样本,训练时顺序读取分片再在内存里打乱。这样既保证了IO效率,又保证了训练的随机性。分片大小有个经验值:单个分片控制在几百MB到1GB之间,太小会导致分片数量过多、管理开销大,太大会导致内存放不下、无法有效打乱。

清洗环节要处理的是缺失值、异常值、重复样本和标签噪声。这里有个容易被忽视的点:清洗规则必须版本化。你今天用一套规则清洗了数据,明天改了规则重新清洗,如果没记录版本,两次训练的结果就没法对比。我的做法是把清洗逻辑写成可配置的代码,每次清洗产出一个带版本号的数据集,训练时记录用的是哪个版本。这样出了问题能快速定位是哪次清洗引入的。

转换环节是把原始数据变成模型能吃的张量。这里的关键是特征工程和归一化。数值特征要做归一化或标准化,具体用哪种取决于数据分布:近似正态分布用标准化,分布未知或有明显边界用归一化。类别特征要做编码,低基数用独热编码,高基数用嵌入或哈希。文本和图像各有专门的预处理流程,但原则是一样的:预处理逻辑必须和训练时完全一致,且要能被推理服务复用。我见过最典型的bug就是训练时用了某种归一化,推理时忘了做,导致线上效果暴跌。

喂给训练环节的核心是批处理和数据加载并行。批大小(batch size)的选择是个权衡:太小会导致训练不稳定、GPU利用率低;太大会导致内存溢出、收敛变慢。我的经验公式是,在GPU内存允许的前提下,从较小的批大小开始(比如32或64),逐步翻倍直到内存快满,然后取那个值的一半作为最终批大小,留出余量。同时,数据加载要用多进程并行,进程数一般设为CPU核心数的四分之三左右,留一些给其他任务。

3.2 模型训练:从单卡到分布式的关键决策

训练环节的第一个决策是单卡还是多卡。判断标准很简单:单卡能放下模型且训练时间可接受,就用单卡。多卡训练会引入通信开销和并行策略的复杂性,不是免费的。我见过模型明明单卡几小时就能训完,非要上多卡,结果调试并行策略花了两天,得不偿失。

单卡放不下的情况,就要考虑并行策略。常见的有数据并行、模型并行和流水线并行。数据并行是最简单的,每张卡放一份完整模型,数据切分到各卡,梯度做同步。它的前提是单卡能放下完整模型。模型并行是把模型切分到多张卡上,适合单卡放不下但层间依赖不复杂的情况。流水线并行是把模型按层切成多个阶段,不同阶段放在不同卡上,适合层数很多的大模型。实际中往往是几种策略混合使用,但混合策略的调试难度是指数级上升的,所以能简单就简单。

学习率是训练里最需要仔细调的参数。我的经验是,先用一个较小的学习率跑几百步,观察损失下降情况,如果下降太慢就逐步放大,直到损失开始震荡或发散,然后取那个临界值的三分之一到二分之一作为最终学习率。配合学习率预热(warmup)能显著提升大模型训练的稳定性,预热步数一般设为总步数的百分之一到百分之五。学习率衰减策略里,余弦衰减在大多数场景下表现稳定,阶梯衰减在需要精确控制训练阶段时更好用。

训练过程中的监控比很多人想的更重要。至少要监控四个指标:训练损失、验证损失、学习率、梯度范数。训练损失和验证损失的差距能告诉你有没有过拟合;学习率曲线能告诉你调度策略有没有生效;梯度范数能提前预警梯度爆炸或消失。我习惯在训练脚本里加一个简单的异常检测,当损失出现NaN或梯度范数超过阈值时自动暂停并保存现场,这样能省掉大量事后排查的时间。

3.3 推理服务:把模型变成能扛流量的API

推理服务是从零构建AI工程里最考验工程能力的部分。核心要解决五个问题:模型加载、请求处理、批处理、资源管理、版本更新。

模型加载的关键是启动速度和内存占用。大模型加载可能要几十秒甚至几分钟,这期间服务是不可用的。我的做法是服务启动时先加载模型再对外暴露端口,同时加一个健康检查接口,只有模型加载完成后健康检查才返回正常。这样负载均衡器不会把请求打到还没准备好的实例上。内存占用方面,要清楚模型本身占多少、推理时的中间激活占多少、批处理缓冲占多少,三者加起来不能超过实例内存,否则会在高负载时被系统杀掉。

请求处理的核心是并发模型。同步阻塞的处理方式在低QPS下没问题,但QPS一高就会因为等待而堆积。我的做法是用异步处理,请求进来后先入队,由专门的推理工作进程从队列取任务执行。这样Web层不会被推理阻塞,能持续接收新请求。队列要有上限,超过上限直接拒绝并返回明确错误,这比无限堆积最后雪崩要好得多。

批处理是提升推理吞吐最有效的手段。原理是把多个请求合并成一个批次一起推理,充分利用GPU的并行能力。批处理的关键参数是最大批大小和最大等待时间。最大批大小受GPU内存限制,最大等待时间决定了延迟上限。我的经验值是,最大等待时间设在P99延迟要求的十分之一左右,比如要求200毫秒,就设20毫秒。这样大部分请求能在等待时间内凑够一批,少数请求即使没凑够也会在超时后单独执行,不会无限等待。

资源管理这块,要特别注意显存碎片问题。长时间运行的服务,如果频繁地分配和释放显存,会产生碎片,最终导致明明有足够显存却分配失败。解决办法是预分配显存池,服务启动时就把需要的显存一次性申请好,运行过程中从池子里分配,不直接向系统申请。这个技巧能显著提升长时间运行服务的稳定性。

版本更新是最容易被忽视的环节。模型更新时,不能直接替换正在使用的模型文件,否则正在处理的请求会出错。正确的做法是新旧模型共存,新请求走新模型,旧请求继续用旧模型处理完,等旧模型的请求全部结束后再释放。这个过程叫优雅更新。实现上可以用引用计数,每个模型维护一个正在处理的请求数,归零后才允许卸载。

4. 完整实操流程与现场记录

4.1 从空目录到可运行服务:一次真实的搭建过程

我拿最近一次从零搭建的经历来还原整个过程。场景是一个文本分类服务,数据约200GB,模型参数量约3亿,推理峰值QPS约800,P99延迟要求150毫秒。

第一步是环境准备。我建了两个容器镜像,训练镜像基于官方深度学习镜像,装了数据处理和训练相关的库;推理镜像基于精简的基础镜像,只装了推理引擎和Web框架。两个镜像的GPU驱动和CUDA版本严格一致。镜像构建脚本和依赖锁定文件都进了版本控制。

第二步是数据管道搭建。原始数据是分散的文本文件,我先写了一个脚本把它们合并成约500个分片文件,每个约400MB。然后写清洗脚本,处理掉空文本、超长文本和重复样本,清洗后的数据集打上版本号。接着写转换脚本,把文本转成token序列并做截断和填充,输出成训练可直接读取的格式。这一步花了大概两天,其中一天半在调IO效率。

第三步是训练脚本。我先用单卡跑通了整个流程,确认损失能正常下降。然后逐步放大批大小,从32开始翻倍,到512时显存快满了,最终定在256。学习率用前面说的方法调到了3e-4,配合5%的预热和余弦衰减。训练跑了约18小时,验证集准确率收敛到预期水平。训练过程中我盯着梯度范数,中间有一次飙升到阈值附近,自动暂停后检查发现是某个批次的异常样本导致的,清洗规则里补了一条过滤后重新训练就正常了。

第四步是推理服务。我把训练好的模型导出成推理引擎能加载的格式,写了一个异步推理服务。批处理参数设成最大批大小64、最大等待时间15毫秒。服务启动时预分配显存,加载模型后开始健康检查。我用压测工具模拟了800 QPS的负载,P99延迟稳定在120毫秒左右,留了足够余量。压测中发现一个问题是长时间运行后显存缓慢增长,排查发现是某个中间结果的缓存没有清理,修掉后连续跑了24小时显存稳定。

第五步是上线和监控。服务部署了三个实例,前面挂负载均衡。监控覆盖了QPS、延迟分布、错误率、显存占用、GPU利用率这几个核心指标。上线第一周每天看一次监控,确认没有异常后改成每周看一次。模型更新走的是优雅更新流程,新模型先在一个实例上灰度,确认没问题再全量。

4.2 关键参数的计算过程实录

批大小的计算我是这么做的。模型参数量3亿,按混合精度算,模型本身占约600MB。中间激活和优化器状态加起来约是模型本身的3到4倍,算2.4GB。加上数据加载的缓冲和框架开销,单卡总占用约4GB。我用的卡是24GB显存,留出20%余量后可用约19GB。理论上批大小可以到很大,但实际受限于训练稳定性和收敛速度,最终定在256。这个值是通过实验确定的:128时训练略慢,512时显存占用到18GB且收敛开始不稳定,256是平衡点。

学习率的计算我用的是线性缩放规则的一个变体。先在小批大小(32)下找到合适的学习率是1e-4,然后按批大小比例放大,但加一个平方根阻尼,最终是1e-4乘以(256/32)的平方根,约2.8e-4,取整到3e-4。这个规则不是绝对的,但作为起点很实用,能省掉大量试错。

推理服务的批处理参数计算是这样的。P99延迟要求150毫秒,模型单次推理耗时约30毫秒(批大小64时)。留给批处理等待的时间不能超过总延迟减去推理耗时再留余量,即150减30再留20%余量,约96毫秒。但等待时间太长会导致低负载时延迟无谓增加,所以取一个更保守的值15毫秒。这样高负载时大部分请求能在15毫秒内凑够一批,低负载时最多等15毫秒就执行,延迟可控。

显存预分配的大小计算:模型600MB,最大批大小64时的中间激活约1.2GB,推理引擎的运行时开销约500MB,加起来约2.3GB,预分配时留50%余量,申请3.5GB。这样既不会浪费太多显存,又能应对峰值。

4.3 实操中的注意事项与细节技巧

训练脚本里我强制加了一个随机种子固定的逻辑,包括Python、框架和CUDA的种子。这不是为了完全复现(GPU上的完全复现很难),而是为了在排查问题时能有一个稳定的基线。如果两次相同种子的训练结果差异很大,说明代码里有不确定性的地方,需要排查。

数据加载的多进程数我设成CPU核心数减二,而不是核心数减一。留两个核心给主进程和系统,能避免数据加载把CPU占满导致其他任务饿死。这个细节在训练机器同时跑其他任务时特别重要。

推理服务的健康检查我分了两级:浅健康检查只返回服务进程是否存活,深健康检查会实际跑一次小推理确认模型可用。负载均衡用浅检查做路由,监控系统用深检查做告警。这样既能快速摘除挂掉的实例,又不会因为深检查太频繁而增加负担。

模型文件我做了完整性校验。每次导出模型时计算一个哈希值,加载时校验。这能防止模型文件在传输或存储过程中损坏导致的诡异问题。我遇到过两次模型加载后结果异常,最后发现是文件损坏,有了校验后这类问题一眼就能定位。

日志方面,我在推理服务里记录了每个请求的输入摘要、输出摘要、耗时和批次信息。摘要不是完整内容(那会占太多空间),而是内容的哈希和长度。这样出问题时能快速定位是哪个请求、哪个批次,又不会泄露敏感内容。日志按天切割,保留最近30天。

5. 常见故障排查与避坑经验实录

5.1 训练阶段典型问题速查

训练阶段的问题往往表现为损失不下降、损失震荡、损失变NaN、验证集效果差这几类。我把这些年遇到的典型问题和排查思路整理成了一张表。

现象可能原因排查方法解决思路
损失完全不降学习率过小、数据标签错位、模型初始化异常先用极小数据集过拟合测试过拟合测试能过说明是学习率问题,不能过说明是数据或模型问题
损失震荡剧烈学习率过大、批大小过小、数据噪声大逐步降低学习率观察降学习率到震荡消失,再考虑增大批大小
损失变NaN梯度爆炸、除零、数值溢出检查梯度范数和输入数据范围加梯度裁剪、检查归一化、用更稳定的数值实现
验证集效果远差于训练集过拟合、数据分布不一致对比训练和验证数据的统计特征加正则化、检查数据划分是否随机、补充验证集数据
训练速度突然变慢数据加载瓶颈、显存碎片、其他进程抢占看GPU利用率和数据加载耗时优化数据管道、重启进程清理碎片、隔离资源

过拟合测试是我最常用的诊断手段。拿几十条数据,关掉所有正则化,让模型去拟合,正常情况下应该能到接近100%的准确率。如果连这个都做不到,说明代码里有bug,不用往下调参了。这个测试能快速区分“模型能力问题”和“代码问题”,省掉大量无效调参。

梯度裁剪是个几乎必加的操作。我一般设一个阈值,比如1.0或5.0,当梯度范数超过阈值时按比例缩放。这能防止偶发的梯度爆炸把模型带偏。阈值的选择要看具体任务,分类任务一般1.0够用,生成任务可能要放宽到5.0。

5.2 推理阶段典型问题速查

推理阶段的问题更偏向工程,表现为延迟高、吞吐低、内存泄漏、结果不一致这几类。

现象可能原因排查方法解决思路
P99延迟远高于P50批处理等待、GC停顿、偶发大请求看延迟分布和批次大小分布调整批处理参数、优化内存管理、限制单请求大小
吞吐上不去GPU利用率低、批大小太小、CPU瓶颈看GPU利用率和各环节耗时增大批大小、优化预处理、减少CPU-GPU拷贝
内存缓慢增长缓存未清理、引用未释放、日志缓冲长时间运行观察内存曲线定位泄漏点、加定期清理、限制缓存大小
结果与训练时不一致预处理不一致、数值精度差异、版本不匹配用相同输入对比训练和推理输出统一预处理逻辑、统一精度、锁定版本
服务启动慢模型加载慢、依赖初始化慢分阶段计时模型格式优化、延迟加载非必需依赖

结果不一致这个问题我单独说一下,因为它最隐蔽。有一次线上模型效果比离线评估差了一大截,排查了一周才发现是推理时的文本截断长度和训练时不一样。训练时截断到512,推理时默认截断到128,导致长文本的信息大量丢失。从那以后,我把所有预处理参数都写进模型文件一起导出,推理时从模型文件读取,杜绝了这类不一致。

内存泄漏的排查有个实用技巧:在服务里加一个接口,能手动触发内存快照并输出当前的对象统计。这样不用等内存涨到出问题,随时可以看内存里有什么。我一般在上线前就用这个接口跑一轮压测,确认内存稳定后才上线。

5.3 那些文档里不会写的避坑经验

第一条经验:永远不要在周五下午上线模型更新。这不是玩笑。模型更新引入的问题往往不是立刻暴露的,可能要跑几个小时甚至一天才显现。周五下午上线,出问题时你已经没精力处理了,周末还得加班。我的规矩是周二或周三上线,留出足够的时间观察和处理。

第二条经验:保留至少两个历史版本的模型和对应的预处理逻辑。回滚的时候如果只回滚模型不回滚预处理,照样会出问题。我把模型和预处理打包成一个版本单元,回滚时整体回滚。这个习惯救过我至少三次。

第三条经验:压测要用真实数据分布,不要用随机数据。随机数据往往触发不了真实数据里的边界情况。我见过用随机数据压测一切正常,上线后遇到真实的长文本请求直接超时。压测数据要从真实数据里采样,且要覆盖各种长度和类型的请求。

第四条经验:监控要覆盖“没有请求”的情况。有些故障表现为服务还在但请求进不来,比如负载均衡配置错误、健康检查误判。如果只监控请求相关的指标,这种情况发现不了。我加了一个独立的探针,定期从外部发一个测试请求,确认端到端链路是通的。

第五条经验:训练和推理的代码要尽量复用。预处理、后处理这些逻辑如果两边各写一份,迟早会不一致。我的做法是把这些逻辑抽成独立的模块,训练和推理都引用同一个模块。这样改一处两边都生效,不会出现不一致。

6. 从能跑到能扛:工程化能力的进阶路径

6.1 可观测性建设:让系统自己告诉你哪里有问题

从零构建的AI系统,能不能扛住生产,很大程度上取决于可观测性做得好不好。我见过太多系统出问题时只能靠猜,因为没有足够的观测数据。可观测性建设分三个层次:指标、日志、追踪。

指标是最基础的,要覆盖系统层面和业务层面。系统层面包括CPU、内存、GPU利用率、显存占用、网络IO这些。业务层面包括QPS、延迟分布、错误率、批大小分布、模型加载次数这些。指标的关键是要有基线,知道正常情况下的值是多少,才能判断异常。我一般在上线前跑一轮标准压测,把各项指标的正常范围记录下来,作为后续告警的阈值依据。

日志要结构化,不要打一堆自由文本。结构化的日志能被自动解析和聚合,排查问题时能快速筛选。我要求日志里至少包含时间戳、请求ID、阶段标识、耗时、结果状态这几个字段。请求ID贯穿整个处理链路,这样能追踪一个请求从进来到返回经过了哪些环节、每个环节花了多久。

追踪是最高级的,能还原一个请求的完整调用链。在微服务架构里尤其重要,因为一个请求可能经过多个服务。实现上可以用开源的追踪工具,在每个服务里埋点,把调用关系串起来。追踪的开销比指标和日志大,所以一般只对部分请求采样,比如百分之一。

6.2 持续迭代:模型更新和系统演进的节奏把控

AI系统和传统软件最大的区别是,它的核心(模型)会随着数据变化而需要持续更新。这个更新节奏的把控是个技术活。更新太频繁,系统不稳定,运维压力大;更新太慢,模型效果跟不上数据变化,业务指标下滑。

我的做法是建立一个模型评估流水线,每次有新数据或新模型时自动跑评估,产出效果指标。然后设一个阈值,只有当新模型的效果超过线上模型一定幅度时才触发更新。这个幅度不能太小,否则会因为评估噪声导致频繁更新;也不能太大,否则会错过真正的提升。我的经验值是,分类任务设1到2个百分点,生成任务看具体指标,一般设3到5个百分点。

更新流程要自动化,但要有卡点。自动化是指从训练完成到评估到部署这一整套流程能自动跑通。卡点是指在关键环节要有人确认,比如评估结果确认、灰度发布确认。全自动无人值守的更新在现阶段风险太大,我还没见过哪个团队能完全放心地这么做。

系统演进方面,要定期回顾架构是否还匹配当前负载。业务增长会带来负载变化,原来够用的架构可能就不够用了。我一般每季度做一次容量评估,看当前资源利用率、延迟趋势、成本趋势,据此决定要不要扩容或调整架构。这个回顾不用很复杂,一张表列出关键指标和趋势就够了。

6.3 团队协作:从个人项目到多人维护的过渡

从零构建的AI工程,一开始往往是一个人搞定的。但随着系统变大,必然要多人维护。这个过渡如果没处理好,会引入大量混乱。我的经验是,在系统还只有你一个人维护的时候,就要按多人协作的标准来组织代码和文档。

代码组织上,按功能模块划分目录,每个模块有清晰的接口和职责。不要把所有代码堆在一个大文件里,哪怕现在只有你一个人看。我见过太多“个人项目”在第二个人加入时变成灾难,因为代码没有任何结构,新人根本无从下手。

文档方面,至少要有一份架构文档、一份部署文档、一份故障排查手册。架构文档讲系统由哪些部分组成、各部分怎么交互。部署文档讲怎么从零把系统跑起来。故障排查手册讲常见问题和处理步骤。这三份文档不用写得很漂亮,但要能让人照着操作。我一般要求新人能只看文档就把系统跑起来,做不到就说明文档不合格。

代码评审在多人协作时是必须的。AI工程的代码评审除了看逻辑正确性,还要特别关注数据处理和模型相关的部分,因为这些地方的bug最隐蔽、影响最大。我要求涉及数据管道和模型导出的改动必须至少两个人看过,且要有对应的测试。

版本管理上,模型、数据、代码三者要能对应起来。每次上线要记录用的是哪个代码版本、哪个模型版本、哪个数据版本。这样出问题时能精确回滚到之前的组合。我见过只回滚代码不回滚模型的,结果新旧不匹配出了更严重的问题。

7. 我个人的一些实操体会

折腾了这么多年从零构建AI系统,最大的体会是:工程能力比算法能力更稀缺,也更值钱。算法层面的东西,论文和开源代码到处都是,学起来快。但工程层面的东西,每个系统都不一样,没有通用答案,只能靠一个个项目积累。一个能把模型训出来的人很多,一个能把模型稳定服务好的人很少。

第二个体会是:简单方案往往比复杂方案更可靠。我早期特别喜欢上各种先进工具,觉得越复杂越显得专业。后来发现,每引入一个组件就多一个故障点,多一份维护成本。现在我的原则是,能用简单方案解决就不用复杂方案,实在需要复杂方案时也要确保每个组件都有明确的不可替代的理由。

第三个体会是:监控和测试的投入永远不亏。我见过太多团队把时间全花在模型调优上,监控和测试能省就省,结果上线后天天救火,反而没时间做优化。我的做法是,监控和测试的投入至少占总投入的三分之一。这部分投入不会直接提升模型效果,但能保证系统稳定运行,让你有时间去做真正的优化。

最后分享一个小技巧:给系统加一个“降级开关”。当模型服务出问题时,能一键切换到备用方案,比如返回默认结果或走规则逻辑。这个开关在紧急情况下能救命,避免因为模型服务故障导致整个业务不可用。降级方案不用很精确,能兜底就行。我负责的系统都配了这个开关,用过两次,每次都避免了重大事故。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询