ARTICLE DETAIL

资讯详情

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

从零构建AI工程能力:数据、推理、服务与运维的完整链路

从零构建AI工程能力:数据、推理、服务与运维的完整链路 1. 从零搭建AI工程能力为什么大多数人卡在“会调包”这一步“ai-engineering-from-scratch”这个标题第一次看到的时候我愣了一下。不是因为它有多花哨恰恰相反它朴素得有点不像现在这个时代的项目名。现在满屏都是“大模型实战”“Agent开发”“RAG从入门到精通”突然冒出来一个“从零开始的AI工程”反而让人想点进去看看它到底想干什么。我做了十多年一线开发最近五六年一直在跟AI相关的工程落地打交道。带过不少新人也面试过不少号称“熟悉AI开发”的候选人。一个很普遍的现象是很多人能跟你聊Transformer的注意力机制能背出LoRA的原理甚至能跟你讨论MoE的负载均衡策略但你让他从零搭一个能跑起来的推理服务他会在环境依赖、显存管理、批处理调度这些“脏活累活”上卡住。这就是“会调包”和“会工程”之间的鸿沟。这个项目标题里的“from scratch”我理解它想强调的是从底层构建认知而不是从底层造轮子。这两者有本质区别。从底层造轮子意味着你要手写矩阵乘法、手写反向传播那是研究者的路径。从底层构建认知意味着你要理解一个AI系统从数据到模型到服务到监控的完整链路知道每一层在干什么、为什么这么设计、出问题该往哪个方向排查。这是工程师的路径。这篇文章我想聊的就是后者。我会围绕“从零构建AI工程能力”这个核心把我在实际项目中踩过的坑、总结的方法、以及那些文档里不会写的经验尽可能完整地摊开来讲。不管你是刚转行想做AI工程的新人还是已经在大模型应用层摸爬滚打了一段时间想补全底层认知的老手应该都能从中找到一些对你有用的东西。提示这篇文章不涉及任何具体平台的部署教程也不推荐任何特定工具链。我聊的是方法论和工程思维工具会过时思维不会。2. 拆解“AI工程”这四个字它到底包含哪些能力模块很多人对AI工程的理解是“会训练模型”或者“会调API”。这两个理解都太窄了。我在实际项目中带团队的时候会把AI工程能力拆成五个层次从下往上依次是数据工程、模型工程、推理工程、服务工程、运维工程。每一层都有它独特的挑战而且越往上跟传统软件工程的交集越多。2.1 数据工程AI系统的地基也是最容易被低估的部分数据工程在AI项目里的地位怎么强调都不过分。我见过太多项目模型选得很先进架构设计得很漂亮最后效果上不去一查发现是数据管道出了问题。数据工程的核心任务包括数据采集、数据清洗、数据标注、数据版本管理、数据质量监控。这里面最容易被低估的是数据版本管理。传统软件工程有Git来管理代码版本但数据版本呢很多人就是用文件夹加日期来区分data_20240101、data_20240115时间一长自己都记不清哪个版本对应哪次实验。正确的做法是引入数据版本管理工具或者至少建立一套命名规范和元数据记录机制。每次实验用的数据版本、预处理参数、采样策略都要可追溯。否则你复现不了自己的实验结果更别提让别人复现了。另一个坑是数据质量监控。训练数据里混入了脏数据、重复数据、标注错误的数据模型训练的时候不会报错但效果会悄悄变差。我建议在数据管道里加一道“数据体检”环节统计一下类别分布、文本长度分布、重复率、异常字符比例这些指标。这些指标不需要多复杂但能帮你提前发现很多问题。2.2 模型工程训练只是冰山一角模型工程涵盖的东西比“训练”两个字宽泛得多。它包括模型选型、训练策略设计、超参数调优、模型评估、模型压缩、模型版本管理。模型选型这一步很多人上来就问“哪个模型效果最好”。这个问题本身就有问题。效果最好的模型往往参数量最大、推理成本最高。你需要根据实际场景在效果、成本、延迟之间做权衡。我的一般原则是先用小模型跑通全流程再根据效果瓶颈决定是否升级模型。一上来就上最大的模型调试周期长、成本高而且你很难判断效果不好是模型的问题还是数据的问题。模型评估也是重灾区。很多人只看一个准确率或者F1值就完事了。但在实际业务中你需要关注的是分层评估不同类别上的表现、不同数据分布上的表现、边界样本上的表现。一个模型整体准确率90%但在某个重要类别上只有60%这个模型能用吗取决于业务场景。但如果你不做分层评估你根本发现不了这个问题。2.3 推理工程从模型文件到可用服务的关键一跃推理工程是我认为最被忽视的环节。很多从研究转工程的人训练完模型导出个文件就不知道下一步该干什么了。推理工程要解决的问题包括推理框架选型、显存管理、批处理策略、量化与加速、并发控制。显存管理是第一个拦路虎。训练的时候你可以用大显存卡推理的时候往往要在有限的显存里塞下模型和缓存。这时候你就需要理解KV Cache的显存占用怎么算、PagedAttention为什么能提升显存利用率、量化到什么程度会明显影响效果。这些不是调包能解决的需要你对推理过程有基本的认知。批处理策略是另一个关键点。在线推理场景下请求是陆续到达的如果来一个请求就做一次前向传播GPU利用率会非常低。你需要做动态批处理把短时间内到达的请求攒在一起送进模型。但批处理又会增加单个请求的延迟所以需要在吞吐量和延迟之间找平衡。这个平衡点怎么找只能通过压测来定。2.4 服务工程让AI能力被其他系统调用服务工程就是把推理能力包装成API让上层业务系统能够调用。这部分跟传统后端开发很像但有几个AI特有的坑。第一个坑是超时设置。AI推理的延迟波动很大同样长度的输入延迟可能差好几倍。如果你按传统API的超时设置来配要么频繁超时要么超时设得太长导致请求堆积。我的经验是设置一个合理的默认超时同时提供异步接口给长文本场景。第二个坑是错误处理。AI服务可能因为各种原因失败显存不足、输入格式错误、模型内部异常。你需要区分哪些错误可以重试、哪些错误应该直接返回给用户、哪些错误需要触发告警。不要把所有异常都吞掉返回一个500那样排查问题的时候你会很痛苦。2.5 运维工程上线只是开始运维工程包括监控、告警、日志、容量规划、灰度发布、回滚机制。AI系统的运维比传统系统更复杂因为你需要监控的指标更多除了CPU、内存、网络这些常规指标还要监控推理延迟、吞吐量、显存使用率、请求队列长度、模型输出质量。模型输出质量监控是最难做的。传统系统可以用错误率来衡量健康度但AI系统输出的是自然语言或者概率分布你很难用一个简单的指标来判断“输出是否正常”。我的做法是建立一套离线评估集定期用线上流量采样跑评估同时监控输出分布的统计特征。如果输出长度分布、词汇分布突然发生偏移可能意味着输入数据分布变了或者模型出了问题。3. 从零构建的第一个坎环境与依赖管理聊完能力模块我们回到“from scratch”这个关键词。从零开始构建AI工程能力第一个要过的坎不是算法不是模型而是环境和依赖管理。这个坎看起来很低但绊倒的人最多。3.1 为什么AI项目的依赖管理比普通项目难普通Web项目的依赖管理相对简单一个requirements.txt或者package.json版本冲突的概率不高。但AI项目的依赖管理是地狱级别的原因有几个。第一依赖链条长且深。你装一个深度学习框架它会带出一堆底层库CUDA、cuDNN、NCCL、各种数学库。这些库之间有严格的版本对应关系错一个版本就可能跑不起来。第二硬件相关性强。同样的代码在A卡上能跑在B卡上可能就报错。CUDA版本、驱动版本、框架版本三者必须匹配。我见过太多人在这上面浪费一整天。第三Python生态的依赖冲突。AI领域的库更新极快很多库对同一个底层依赖的版本要求不一样。你装了一个库可能把另一个库的依赖降级了然后另一个库就跑不了了。3.2 我的环境管理实践经过多次踩坑我现在的做法是每个项目一个独立的环境用容器做隔离用锁文件固定版本。具体来说我不会直接在宿主机上装深度学习框架。我会写一个Dockerfile从基础镜像开始明确指定CUDA版本、Python版本、框架版本。所有依赖都写在里面构建成镜像。这样至少保证了环境的一致性在我机器上能跑在服务器上也能跑。对于Python依赖我会用pip-compile或者poetry lock生成锁文件把所有依赖的精确版本固定下来。不要用pip install -r requirements.txt这种松散的方式因为requirements.txt里写的可能是torch1.12今天装是1.12明天装可能就是2.0了行为可能完全不一样。注意不要迷信“最新版本”。AI领域的库最新版本往往意味着最多的bug。我一般会选一个发布至少三个月、社区反馈稳定的版本。3.3 一个真实的踩坑案例有一次我在一个新环境里部署一个推理服务代码在本地跑得好好的到了服务器上就报CUDA out of memory。我检查了显存模型大小一样输入长度一样为什么本地能跑服务器不能跑排查了半天发现是CUDA版本不一样。本地是CUDA 11.7服务器是CUDA 11.8。不同CUDA版本下PyTorch的显存分配策略有细微差别导致同样的模型在11.8下多占了几百MB显存刚好越过了边界。这个问题的教训是环境一致性不是“差不多就行”而是必须精确一致。CUDA版本、驱动版本、框架版本、甚至Python小版本都要对齐。后来我所有项目都强制用容器就是为了避免这类问题。4. 数据管道的搭建从原始数据到训练样本环境搞定之后下一步就是数据。数据管道是AI工程里最“脏”的部分但也是最重要的一部分。我见过太多项目模型换了一茬又一茬效果提升不明显最后发现瓶颈在数据上。4.1 数据采集与清洗的工程化思路数据采集的第一步是明确数据来源和数据量级。不同来源的数据清洗策略完全不一样。比如从网页抓取的数据需要去HTML标签、去广告、去导航栏从业务系统导出的数据需要处理缺失值、异常值、格式不一致的问题。我的建议是把数据清洗做成可配置的管道而不是写死的脚本。什么意思就是把清洗步骤拆成一个个独立的处理单元每个单元有明确的输入输出和参数。比如“去重”是一个单元“过滤短文本”是一个单元“替换敏感词”是一个单元。然后用一个配置文件把这些单元串起来。这样当数据源变化时你只需要调整配置不需要重写代码。这样做还有一个好处可复现。你每次跑数据管道用的配置是固定的产出的数据版本就是确定的。不会出现“上次跑的时候我手动改了个参数忘了记下来”这种情况。4.2 数据标注的质量控制如果你的项目需要标注数据那标注质量控制就是重中之重。我见过太多项目标注数据里错误率高达10%以上模型训练出来效果自然好不了。控制标注质量我总结了几条经验。第一标注规范要细到可执行。不要写“标注文本的情感倾向”这种模糊的规范要写“如果文本包含明确的正向情感词且没有否定词标为正向如果包含明确负向情感词标为负向如果情感不明确或中性标为中性”。规范越细标注一致性越高。第二做交叉验证。同一批数据让不同标注员标计算一致性指标。如果一致性低于某个阈值说明规范有问题或者标注员理解有偏差需要重新培训。第三抽样复核。标注完成后随机抽一批数据人工复核统计错误率。如果错误率超过可接受范围整批数据都要重新处理。4.3 数据版本管理的落地方法数据版本管理我推荐用**DVCData Version Control**或者类似的工具。它的核心思想是数据文件本身不放进Git而是把数据的哈希值和元信息放进Git。这样你切换Git分支的时候可以同时切换对应的数据版本。如果不想引入额外工具至少要做到每次数据变更都记录变更日志包括变更时间、变更内容、变更原因、影响范围。这个日志可以是一个Markdown文件放在项目根目录下。别小看这个习惯当你三个月后想复现某个实验结果时它会救你的命。5. 模型训练与评估那些文档不会告诉你的细节数据准备好了接下来就是模型训练。这部分网上的教程最多但我想聊的是那些教程里不会写的细节。5.1 训练前的检查清单在按下训练按钮之前我通常会过一遍检查清单。这个清单帮我避免了很多低级错误。检查项常见问题检查方法数据加载数据路径错误、格式不匹配先跑一个batch打印输入输出标签对齐标签和输入错位随机抽几条样本人工核对损失函数损失函数与任务不匹配检查损失函数输入输出维度学习率学习率过大导致发散先用小学习率跑几百步看loss曲线显存占用batch size过大导致OOM逐步增大batch size找到上限随机种子未固定种子导致不可复现固定所有随机源这个清单看起来简单但每一条我都见过有人踩坑。特别是标签对齐数据预处理的时候一个shuffle操作没对应好标签和输入就错位了模型训练出来效果奇差你还找不到原因。5.2 训练过程中的监控指标训练过程中不要只盯着loss。我建议监控以下几类指标第一类优化指标。包括训练loss、验证loss、学习率、梯度范数。梯度范数特别重要如果梯度范数突然变得很大说明可能遇到了梯度爆炸需要检查是否需要梯度裁剪。第二类性能指标。包括吞吐量每秒处理的样本数、显存使用率、GPU利用率。如果GPU利用率很低说明数据加载是瓶颈需要优化数据管道。第三类健康指标。包括参数范数、激活值分布、权重更新比例。这些指标能帮你发现模型是否在正常学习。比如权重更新比例如果接近0说明模型已经饱和了再训练下去意义不大。5.3 模型评估的陷阱模型评估最容易踩的坑是数据泄露。什么是数据泄露就是训练数据里包含了测试数据的信息。比如你做文本分类预处理的时候用了全量数据来构建词汇表然后划分训练集和测试集。这样测试集的词汇信息就泄露到了训练过程中评估结果会虚高。正确的做法是先划分数据集再分别做预处理。训练集的词汇表只能从训练集构建测试集里出现的未登录词按未登录词处理。这个原则说起来简单但实际操作中很容易违反。另一个陷阱是评估指标选择不当。不同任务适合的指标不一样。分类任务看准确率、精确率、召回率、F1生成任务看BLEU、ROUGE、BERTScore排序任务看NDCG、MAP。而且同一个任务在不同业务场景下关注的指标也不一样。比如一个内容审核模型你更关心的是召回率不能放过违规内容而不是准确率。6. 推理服务的性能调优从能用 to 好用模型训练完导出成文件这只是半成品。要让它变成一个能对外服务的系统还需要做很多工程优化。6.1 推理框架的选择逻辑推理框架的选择核心看三个维度硬件平台、模型类型、性能要求。如果是NVIDIA GPUTensorRT通常是性能最好的选择但它对模型结构的支持有限不是所有模型都能顺利转换。ONNX Runtime通用性更好支持多种硬件后端性能也不错。如果是CPU推理OpenVINO在Intel平台上表现很好。我的建议是先用ONNX Runtime跑通如果性能不达标再考虑TensorRT。ONNX Runtime的调试体验比TensorRT好很多遇到不支持的算子也更容易定位。6.2 批处理与缓存的工程实现批处理是提升推理吞吐量最有效的手段。但批处理的实现方式有很多种效果差异很大。最简单的做法是静态批处理凑够N个请求就送进模型。但这样会导致延迟不可控如果请求来得慢第一个请求可能要等很久。更好的做法是动态批处理设置一个最大等待时间在等待时间内尽可能多地攒请求。比如设置最大等待10ms那么即使只来了一个请求10ms后也会送进模型。这样延迟有上限吞吐量也能提升。缓存是另一个优化手段。如果你的服务中有大量重复或相似的请求可以在服务层加缓存。比如相同的问题直接返回缓存结果相似的问题可以复用部分计算结果。但缓存要注意失效策略模型更新后缓存要清空。6.3 量化与蒸馏的取舍量化和蒸馏是模型压缩的两种主要手段。量化是把模型参数从高精度如FP32转成低精度如INT8蒸馏是用大模型教小模型。量化的好处是推理速度快、显存占用少坏处是可能影响效果。我的经验是INT8量化对大多数任务的效果影响在可接受范围内但需要做校准。校准就是用一批代表性数据跑一遍统计激活值的分布确定量化的缩放因子。校准数据的选择很重要要覆盖实际场景中的数据分布。蒸馏的好处是可以得到一个结构更简单、推理更快的小模型坏处是需要重新训练成本较高。蒸馏适合的场景是你有充足的计算资源做训练但推理资源有限。7. 上线之后监控、告警与持续迭代服务上线不是终点而是起点。上线之后你会遇到各种在开发环境里遇不到的问题。7.1 线上监控体系的搭建线上监控我一般分三层基础设施层、服务层、业务层。基础设施层监控CPU、内存、GPU、网络、磁盘。这些用常规的监控工具就能搞定。服务层监控请求量、延迟分布、错误率、队列长度。这里要特别注意延迟分布不要只看平均延迟。平均延迟100ms但P99延迟可能到了2秒用户体验完全不一样。业务层监控模型输出质量。这个最难做但最重要。我的做法是定期采样线上请求用离线评估集跑一遍看效果指标有没有下降。同时监控输出的一些统计特征比如输出长度分布、词汇丰富度、重复率。如果这些特征发生明显偏移可能意味着输入分布变了或者模型退化了。7.2 告警策略的设计告警设计的原则是宁可漏报不可误报。误报多了大家就会对告警麻木真正出问题的时候反而没人关注。我一般会设置两级告警警告级和严重级。警告级只发通知不打电话严重级才触发电话告警。警告级的阈值设得宽松一些严重级的阈值设得严格一些。告警的指标也要有选择性。不是所有指标异常都需要告警。我一般只对以下情况设告警服务不可用、错误率超过阈值、延迟超过阈值、显存使用率超过阈值。其他指标异常可以通过看板观察不需要主动告警。7.3 模型迭代的工程化流程模型迭代不是“训练一个新模型然后替换上去”这么简单。你需要一套工程化的流程来保证迭代的安全和效率。我的做法是建立模型注册中心所有模型版本都注册在案记录训练数据版本、超参数、评估指标。上线新模型时先做灰度发布把一小部分流量切到新模型观察一段时间。如果指标正常再逐步扩大流量比例。如果指标异常立即回滚。灰度发布的关键是流量切分策略。可以按用户ID哈希切分保证同一个用户始终看到同一个模型也可以按请求随机切分适合无状态服务。具体用哪种取决于业务场景。8. 我在这条路上踩过的几个印象深刻的坑聊了这么多方法论最后分享几个我实际踩过的坑。这些坑在文档里找不到但每一个都让我付出了真金白银的代价。第一个坑是显存碎片化。有一次我部署一个服务模型加载后显存占用正常但跑了一段时间后开始报OOM。排查发现是显存碎片化频繁地分配和释放不同大小的显存块导致虽然总空闲显存够但没有连续的大块可用。解决办法是预分配显存池或者用支持显存池化的推理框架。第二个坑是批处理导致的延迟毛刺。我设置了一个动态批处理策略最大等待时间50ms。大部分请求延迟都在100ms以内但偶尔会出现几秒的延迟。排查发现是某个请求的输入特别长导致整个batch的处理时间被拉长。解决办法是按输入长度分桶长度相近的请求放在同一个batch里处理。第三个坑是模型更新后的缓存污染。我加了一层结果缓存来提升性能但模型更新后忘了清缓存导致用户拿到的是旧模型的结果。解决办法是把模型版本号作为缓存key的一部分模型更新后缓存自然失效。这些坑的共同特点是它们都不是算法问题而是工程问题。这也是为什么我觉得“AI工程”这个方向值得单独拿出来讲。算法决定了一个AI系统的上限但工程决定了它能不能稳定地达到这个上限。如果你正在从零构建自己的AI工程能力我的建议是不要一上来就追求最先进的模型先把数据管道、推理服务、监控体系这些基础设施搭好。基础设施搭好了换模型就是改一个配置的事基础设施没搭好每换一次模型都是一次重来。这个道理我用了好几年才真正明白。
返回列表