1. 从零搭建AI工程能力:为什么我劝你别一上来就调包
这两年AI应用开发的门槛肉眼可见地降低了,随便拉个框架、调个API就能跑出一个能对话的Demo。但我带过不少新人,也面试过不少号称“做过AI项目”的候选人,发现一个很普遍的问题:模型能跑起来,但一问到数据怎么清洗、推理延迟怎么优化、线上服务怎么部署、显存不够怎么降级,基本就卡壳了。这就是典型的“会调包,但不懂工程”。
ai-engineering-from-scratch这个方向,说白了就是解决这个断层——它不教你从零推导反向传播,也不要求你手写CUDA算子,而是聚焦在“一个AI能力从想法到线上稳定跑起来”这条链路上,把每个环节该做什么、为什么这么做、踩过哪些坑讲清楚。适合谁看?如果你已经会用Python,能看懂基本的模型调用代码,但一到工程化落地就心里没底,那这篇内容就是给你写的。我会按实际项目推进的顺序,把数据、模型、服务、监控这几块拆开讲,每个环节都给出可复现的操作和参数选择的理由。
先明确一个前提:AI工程不是算法研究。算法研究追求的是SOTA指标,AI工程追求的是在给定成本、延迟、稳定性约束下,把模型能力稳定地交付出去。这两者的目标函数完全不同,所以很多在论文里好用的方法,到了工程环境里反而会成为负担。理解这一点,后面的所有取舍就都有依据了。
2. 整体设计思路:把AI能力当成一条流水线来拆
2.1 为什么先定约束再选模型
很多人做AI项目的顺序是反的:先找一个最强的模型,然后想办法把它塞进业务里。这个顺序在工程上会带来大量返工。正确的做法是先明确约束条件,再倒推模型选型。约束条件主要看四个维度:延迟要求、吞吐量、成本预算、精度底线。
举个例子,如果你做的是实时对话场景,端到端延迟通常要控制在1秒以内,那模型参数量就不能太大,或者必须做量化加速;如果你做的是离线批量处理,比如每天跑一次文档摘要,那延迟可以放宽到分钟级,这时候就可以用更大的模型换更好的效果。成本预算决定了你能不能用商用API,还是必须自己部署开源模型。精度底线则决定了你能不能接受量化带来的微小掉点。
我一般会先画一张约束表,把四个维度的硬性要求写下来,然后拿这张表去筛模型。这样筛出来的候选通常只有两三个,再逐个做小规模验证,效率比盲目试要高得多。
2.2 分层架构:把变化的部分隔离出来
AI工程和传统后端工程最大的区别在于,模型这一层是高度易变的。今天用这个模型,明天可能就换了;今天用这个版本,明天可能就升级了。如果模型逻辑和业务逻辑耦合在一起,每次换模型都是一次大手术。
所以我习惯把整个系统分成三层:接入层、编排层、模型层。接入层负责协议转换、鉴权、限流,这部分基本不变;编排层负责prompt组装、上下文管理、结果后处理,这部分会随业务调整;模型层负责实际的推理调用,这部分会随模型迭代变化。三层之间通过明确定义的接口通信,换模型的时候只动模型层,业务代码不用改。
这个分层看起来简单,但实际做的时候很多人会偷懒,把prompt直接写在业务代码里,把模型调用和数据库操作混在一起。等到要换模型或者做A/B测试的时候,就会发现改动面大得吓人。所以这一步的纪律性很重要,宁可前期多写一点接口定义,也不要后期被耦合拖死。
2.3 从Demo到生产的四个断层
我观察下来,AI项目从Demo到生产,通常会经历四个断层。第一个是数据断层:Demo用的是干净的小样本,生产环境的数据脏得多、分布也更多样。第二个是性能断层:Demo单条请求跑得通,生产环境并发一上来就崩。第三个是稳定性断层:Demo不考虑超时、重试、降级,生产环境这些都必须有。第四个是观测断层:Demo跑完就完了,生产环境需要知道每次调用的耗时、成功率、输出质量。
这四个断层里,数据断层最容易被低估。很多人以为模型选好了就万事大吉,实际上数据清洗和预处理的工作量往往占到整个项目的一半以上。性能断层和稳定性断层是工程基本功,观测断层则决定了你能不能持续优化。后面我会逐个展开讲怎么跨过这些断层。
3. 数据环节:脏数据才是常态
3.1 数据清洗的优先级排序
拿到一批原始数据,不要急着全部清洗一遍。先做采样,人工看一百条左右,把问题归类。常见的问题类型有:格式不一致、字段缺失、重复、噪声、标注错误。归类之后,按影响面和修复成本排优先级。
格式不一致通常影响面最大,因为会导致后续所有处理都出错,所以优先修。字段缺失要看缺失比例,如果某个字段缺失超过30%,可能要考虑放弃这个字段而不是强行填充。重复数据如果不处理,会导致训练集和测试集泄漏,评估结果虚高,这个必须查。噪声和标注错误修复成本高,如果比例不大,可以先标记出来,后续再处理。
我一般会写一个数据质量报告脚本,自动统计每个字段的缺失率、唯一值数量、长度分布,然后人工看报告决定清洗策略。这个脚本不复杂,但能省下大量来回沟通的时间。
3.2 文本数据的预处理实操
文本类AI项目里,预处理的质量直接决定模型效果的上限。我通常按这几步走:先做编码统一,全部转成UTF-8;然后做不可见字符清理,把零宽空格、软连字符这类东西去掉;接着做长度过滤,太短的和太长的都单独拿出来看;最后做去重,用SimHash或者MinHash做近似去重。
这里有个细节很多人会忽略:中文文本里的全角半角混用。如果不统一,同一个词会被当成两个不同的token,影响模型理解。我一般用unicodedata.normalize('NFKC', text)做标准化,能把大部分全角字符转成半角,同时保留中文汉字不变。
还有一个坑是HTML标签残留。如果数据是从网页抓的,经常会有<br>、 这类东西混在里面。用正则批量清理的时候要小心,不要误伤正常的尖括号内容。我的做法是先统计标签类型,确认没有正常内容被误伤,再批量替换。
3.3 数据版本管理:别再用文件名区分了
我见过太多项目用data_final_v2_真的最终版.csv这种方式管理数据版本,结果就是没人知道哪个文件对应哪次实验。数据版本管理应该和代码版本管理一样严肃。
轻量级的做法是用DVC或者Git LFS,把数据文件的哈希值和代码commit关联起来。每次实验记录清楚用了哪个数据版本,这样结果可复现。如果团队规模小,至少也要维护一个数据清单文件,记录每个数据文件的来源、处理脚本、生成时间、样本数量。
还有一个实践是给数据打标签。比如标记这批数据是“训练用”还是“评估用”,是“已清洗”还是“原始”。这些元信息看起来琐碎,但等到要追溯问题的时候,能救命。
4. 模型环节:选型、量化与推理优化
4.1 模型选型的决策框架
模型选型不是选最强的,而是选最合适的。我一般从四个维度打分:效果、速度、成本、可控性。效果看公开榜单和实际业务样本上的表现;速度看首token延迟和生成速度;成本看显存占用和单位请求费用;可控性看是否支持微调、是否开源、社区活跃度。
对于大多数业务场景,我建议先用商用API快速验证需求,确认有价值之后再考虑自部署。自部署的触发条件通常是三个:数据不能出内网、调用量大到API成本不划算、需要对模型做深度定制。这三个条件满足任意一个,才值得投入自部署的工程成本。
选开源模型的时候,不要只看参数量。7B的模型如果量化做得好,效果可能接近未量化的13B,但速度快一倍。所以选型的时候要把量化方案一起考虑进去,而不是先选模型再想怎么量化。
4.2 量化:用精度换速度的账怎么算
量化的本质是把模型权重从高精度浮点数转成低精度表示,减少显存占用和计算量。常见的精度有FP16、INT8、INT4。FP16相比FP32能省一半显存,效果几乎无损,基本是默认选项。INT8能再省一半,效果通常掉1到2个点。INT4省得更多,但效果掉得也更多,适合对精度要求不高的场景。
量化不是免费的午餐,掉点多少取决于模型和任务。我的做法是准备一个评估集,分别跑FP16、INT8、INT4三个版本,记录效果和速度,然后根据业务能接受的精度底线来选。如果INT8掉点在可接受范围内,那就用INT8,省下来的显存可以部署更大的模型或者提高并发。
这里有个实操细节:量化后的模型要做校准。校准数据的分布要尽量接近真实推理数据,否则量化误差会偏大。我一般从真实请求里采样几百条做校准,效果比用随机数据好很多。
4.3 推理加速的常用手段
推理加速的手段很多,但不要一次全上,要逐个加、逐个测。常用的有:KV Cache复用、连续批处理、投机解码、算子融合。KV Cache复用对多轮对话场景效果明显,能避免重复计算历史token。连续批处理能提高GPU利用率,适合高并发场景。投机解码用小模型猜、大模型验,能在不损失效果的前提下提速。算子融合是框架层面的优化,通常框架已经默认开启。
我一般先用profiler定位瓶颈,看时间花在哪儿。如果是显存带宽瓶颈,优先考虑量化;如果是计算瓶颈,优先考虑算子优化;如果是调度瓶颈,优先考虑批处理。不要凭感觉优化,一定要有数据支撑。
5. 服务化:让模型稳定跑在线上
5.1 服务框架选型对比
模型服务化框架主要有几类:通用Web框架自己封装、专用推理服务框架、云厂商托管服务。通用框架灵活但什么都得自己写;专用框架开箱即用但定制性差;托管服务省心但成本和数据合规要权衡。
我一般推荐用专用推理服务框架起步,比如支持动态批处理和模型热更新的方案。这类框架把并发、批处理、显存管理这些脏活累活都处理好了,你只需要关注业务逻辑。如果业务有特殊需求,再考虑自己封装。
选框架的时候重点看几个能力:是否支持动态批处理、是否支持多模型共存、是否支持灰度发布、是否有完善的监控指标。这几个能力决定了你后续运维的轻松程度。
5.2 接口设计:同步还是异步
AI推理的接口设计,第一个要决定的是同步还是异步。同步接口简单,客户端发请求后等着结果返回。但如果推理耗时较长,同步接口会占用连接资源,并发一高就容易超时。异步接口是客户端发请求后拿到一个任务ID,然后轮询或者通过回调拿结果。
我的经验是:如果推理耗时在3秒以内,用同步接口;超过3秒,用异步接口。对话类场景通常用流式返回,既能降低首token延迟的感知,又能保持连接活跃。流式返回要注意处理客户端断开的情况,及时释放推理资源。
接口的输入输出格式要明确定义,最好用schema约束。输入要限制最大长度,防止超长请求打爆显存。输出要定义清楚字段含义,方便下游消费。
5.3 超时、重试与降级策略
线上服务必须考虑失败的情况。超时设置要分层次:客户端超时、网关超时、推理超时。推理超时应该是最短的,因为它是最后一道防线。重试要谨慎,因为AI推理通常不是幂等的,重试可能导致重复计费或者重复生成。如果必须重试,要确保请求ID一致,服务端做去重。
降级策略是保命的。当模型服务不可用时,可以降级到规则引擎、缓存结果、或者返回兜底话术。降级要提前设计好触发条件,比如连续失败次数超过阈值、平均延迟超过阈值。降级后要有恢复机制,不能一直降级下去。
我一般会做一个降级开关,可以手动触发也可以自动触发。自动触发基于监控指标,手动触发用于紧急情况。降级期间要记录日志,方便事后分析。
6. 监控与迭代:上线只是开始
6.1 必须监控的核心指标
AI服务的监控和传统服务有重叠也有差异。重叠的是延迟、成功率、QPS这些通用指标。差异的是AI特有的指标,比如输出长度分布、token消耗量、显存占用、批处理效率。
延迟要分首token延迟和总延迟,这两个指标反映的问题不同。首token延迟高通常是prefill阶段慢,总延迟高可能是decode阶段慢或者输出太长。成功率要区分是网络失败还是推理失败,推理失败还要看是超时还是报错。token消耗量直接关联成本,要按业务维度拆分,知道哪个功能最费token。
输出长度分布是个容易被忽略但很有用的指标。如果输出长度突然变长,可能是prompt出了问题,或者模型行为发生了变化。显存占用要设置告警阈值,接近上限时提前扩容或者降级。
6.2 输出质量的评估方法
输出质量评估是AI工程里最难的部分,因为质量本身是主观的。我的做法是分两层:自动评估和人工评估。自动评估用规则或者小模型做打分,覆盖大部分请求;人工评估抽样做,校准自动评估的准确性。
自动评估的规则可以包括:输出是否为空、是否包含敏感词、是否超出长度限制、是否包含特定格式。这些规则能拦住大部分明显问题。更细的质量评估可以用一个小的评估模型来做,比如判断输出是否相关、是否连贯。
人工评估要设计好评测标准,最好用打分表而不是主观描述。评测人员要经过培训,保证一致性。评测结果要定期分析,找出系统性问题。
6.3 持续迭代的闭环怎么建
AI工程的迭代闭环是:收集线上数据、分析问题、改进方案、验证效果、发布上线。这个闭环转得越快,系统进化得越快。
收集线上数据要注意隐私和合规,敏感信息要脱敏。分析问题要分类,是数据问题、模型问题还是工程问题。改进方案要小步快跑,一次只改一个变量,方便归因。验证效果要用同一套评估集,保证可比性。发布上线要灰度,先小流量验证再全量。
我一般会维护一个实验记录表,记录每次迭代的假设、改动、结果。这个表积累下来,就是团队最宝贵的知识资产。
7. 常见问题与排查技巧实录
7.1 显存溢出排查清单
显存溢出是自部署模型最常见的问题。排查顺序一般是:先看输入长度,超长输入是头号嫌疑;再看批处理大小,批太大也会爆;然后看是否有内存泄漏,长时间运行后显存持续增长;最后看模型本身,有些模型加载后就有固定占用。
解决手段对应着来:限制输入长度、减小批大小、定期重启服务、换更小的模型或者量化。我一般会设置一个显存水位告警,到80%就提醒,到90%就自动降级。
7.2 输出不稳定的归因方法
输出不稳定表现为同样输入有时好有时坏。归因要看几个方面:采样参数是否固定,temperature和top_p如果没固定,输出本来就会变;模型版本是否一致,不同版本行为可能不同;输入是否有细微差异,比如空格或者标点;服务端是否有并发干扰,批处理可能影响结果。
排查的时候先固定所有随机种子和采样参数,如果还稳定不了,再查模型版本和输入。我遇到过因为输入里混了不可见字符导致输出完全不同的情况,所以输入清洗一定要彻底。
7.3 延迟毛刺的定位思路
延迟毛刺是指大部分请求很快,但偶尔有请求特别慢。定位思路是先看毛刺是否规律,规律的话可能是定时任务干扰;再看是否和特定输入相关,可能是长输入触发;然后看是否和并发相关,可能是批处理等待;最后看是否和资源相关,可能是GC或者显存交换。
我一般会记录每个请求的详细耗时分解,包括排队时间、prefill时间、decode时间。这样毛刺出现时能快速定位到具体阶段。如果是排队时间长,说明并发能力不足;如果是prefill时间长,说明输入太长或者模型太大;如果是decode时间长,说明输出太长或者采样参数设置不当。
7.4 常见问题速查表
| 问题现象 | 可能原因 | 排查手段 | 解决方向 |
|---|---|---|---|
| 显存溢出 | 输入过长、批过大、泄漏 | 看输入长度分布、批大小、显存趋势 | 限长、减批、重启、量化 |
| 输出不稳定 | 采样参数、模型版本、输入噪声 | 固定参数、对比版本、清洗输入 | 固定种子、锁定版本、加强清洗 |
| 延迟毛刺 | 定时任务、长输入、批等待、GC | 耗时分解、关联分析 | 错峰、限长、调批、优化内存 |
| 成功率下降 | 网络、超时、模型报错 | 看错误分类、超时分布 | 重试、扩容、降级 |
| 成本超预期 | token消耗大、并发高 | 按业务拆分token消耗 | 优化prompt、缓存、限流 |
这张表我一般贴在工位上,出问题的时候先对照一遍,能解决大部分常见故障。剩下的疑难杂症再深入排查。
8. 一些踩坑之后的个人体会
做AI工程这几年,我最大的体会是:不要迷信任何单一方案。模型在迭代,框架在迭代,最佳实践也在迭代。今天好用的方法,半年后可能就过时了。所以比起记住具体方案,更重要的是理解每个方案背后的约束和取舍。知道为什么用这个方案,比知道怎么用这个方案更重要。
另一个体会是:可观测性怎么强调都不为过。我见过太多项目上线后两眼一抹黑,出了问题只能靠猜。花在监控和日志上的时间,最终都会以更快的排查速度回报回来。宁可上线慢一点,也要把观测做扎实。
最后一个建议是:保持动手。AI工程是个实践性极强的领域,看再多文章不如自己跑一遍。从数据清洗到服务部署,每个环节都亲手做一次,踩过的坑才会变成自己的经验。我到现在还保持着每周跑一个小实验的习惯,不一定有明确目的,就是保持手感。这个习惯帮我避开了很多“看起来没问题”的陷阱。