1. 从零手搓AI工程为什么我不建议你直接调包很多人一听到“AI工程”这四个字第一反应就是打开某个云平台调一个现成的大模型接口写几行胶水代码然后对外宣称自己“搞定了AI”。我见过太多这样的项目上线三天接口一限流就崩成本一算吓一跳出了问题连日志都看不懂。这种“调包式AI工程”在演示阶段确实能唬住人但一旦进入真实业务场景几乎必然翻车。ai-engineering-from-scratch这个标题核心价值不在于“AI”而在于“from scratch”——从零开始。它要解决的不是“怎么调用一个模型”而是“当所有现成工具都不好用、或者你根本不想被平台绑死时你该怎么自己搭一套能跑、能调、能扩展的AI工程体系”。这适合两类人一是想真正理解AI系统底层运转逻辑的开发者二是在实际业务中被现成方案坑过、决定自己掌控全链路的工程师。我自己的经历就很典型。早期做文本分类项目直接用了某平台的托管服务前两周效果很好第三周业务量翻倍账单直接翻了五倍而且响应延迟从200毫秒涨到2秒。更致命的是我想调整一下分词逻辑发现平台根本不开放这个层级的控制。那一刻我意识到把AI工程完全建立在别人的黑盒上等于把命脉交了出去。于是我开始从零搭建自己的推理管线从数据清洗、特征工程、模型加载、批处理调度到结果缓存全部自己写。这个过程踩了无数坑但也让我真正搞明白了AI工程到底是怎么回事。这篇文章不会教你“三行代码调用大模型”那种内容网上已经泛滥了。我要分享的是当你决定从零构建AI工程能力时需要想清楚哪些问题、避开哪些陷阱、掌握哪些核心模块。全文会围绕数据管线、模型推理、服务化、性能调优和成本控制这几个硬核主题展开每个部分都会给出可复现的操作思路和我在实际项目中总结的经验参数。2. 数据管线AI工程里最脏最累但最不能省的活2.1 为什么数据清洗比模型选择更重要很多人把80%的精力花在选模型上却只给数据清洗留20%的时间。我的经验恰恰相反在一个中等复杂度的AI工程里数据管线的质量直接决定了系统上限而模型选择只影响你离这个上限有多近。我做过一个情感分析项目用同一个模型只是把数据清洗流程从“简单去重”升级到“多级过滤标准化”准确率从78%提升到91%。模型没换参数没调纯粹是数据干净了。从零构建数据管线你需要自己实现至少四个环节采集、清洗、标注和版本管理。采集环节要处理的是数据源异构问题——数据库、日志文件、API返回的JSON、甚至手工录入的表格格式五花八门。我的做法是统一转成中间格式通常是JSON Lines每行一条记录附带来源标记和时间戳。这个中间格式看起来简单但它让后续所有处理步骤都变得可插拔。清洗环节是最考验耐心的。我通常分三步走第一步是规则过滤比如去除长度过短或过长的文本、过滤掉包含特定乱码字符的记录第二步是统计过滤计算词频分布把出现频率异常高但无意义的词比如某些HTML残留标签加入黑名单第三步是语义去重用简单的TF-IDF向量计算相似度把相似度超过0.95的记录合并。这三步下来数据量通常会减少30%到50%但剩下的数据质量会有一个质的飞跃。2.2 标注环节的坑别让标注员决定你的模型上限如果你做的是监督学习标注环节就是第二个大坑。我见过太多项目死在标注一致性上同一个样本标注员A标成正面标注员B标成负面模型学到最后直接精神分裂。从零构建标注体系我的建议是必须做三件事制定标注手册、计算标注者间一致性、建立仲裁机制。标注手册要细到令人发指的程度。比如做意图分类不能只写“判断用户想干什么”而要写“如果用户说‘帮我查一下明天天气’标为‘查询天气’如果用户说‘明天天气怎么样’同样标为‘查询天气’如果用户说‘明天天气不错’标为‘闲聊’”。每个类别至少给10个正例和5个反例。手册越细标注一致性越高。计算标注者间一致性我常用Cohens Kappa系数。具体操作是让两个标注员独立标注同一批100条数据然后计算Kappa值。如果Kappa低于0.7说明标注标准有问题需要重新培训0.7到0.85之间可以接受高于0.85说明标注质量很好。这个指标比单纯看准确率靠谱得多因为它排除了随机一致的可能性。仲裁机制是最后一道防线。当两个标注员意见不一致时由第三个人通常是项目负责人做最终裁决并且把裁决理由记录到手册里。这样手册会越来越完善标注质量也会逐步提升。我自己的项目里标注手册从第一版的3页纸经过三个月迭代变成了27页但标注一致性从0.62提升到了0.89模型效果直接上了一个台阶。2.3 数据版本管理别让“上次那个数据集”成为千古谜案数据版本管理是AI工程里最容易被忽视的环节。我敢打赌每个从零做AI工程的人都经历过这种场景三个月后想复现一个实验结果发现“上次那个数据集”已经找不到了或者找到了但不知道是哪一版。这种痛苦一次就够所以从第一天起就要建立数据版本管理习惯。我的做法很简单每次数据管线跑完生成一个版本号比如v20240115_001然后把三样东西打包存档原始数据快照、清洗后的数据、清洗脚本和参数配置。存档位置可以是本地磁盘、对象存储或者Git LFS关键是版本号要能追溯到具体的处理逻辑。我还会在版本号里嵌入一个短哈希用来校验数据完整性。更进一步我会维护一个数据版本表记录每个版本的关键指标样本总数、类别分布、平均长度、标注一致性等。这样当模型效果波动时我可以快速对比不同版本的数据差异定位问题。这个习惯看起来麻烦但它救过我至少三次——有一次模型突然对某类样本表现极差我对比数据版本表发现新版本里这类样本的占比从15%降到了3%模型只是没见过足够多的例子而已。3. 模型推理从加载到批处理每一步都有讲究3.1 模型加载为什么你的服务启动要三分钟从零构建推理服务第一个要解决的问题就是模型加载。我见过太多服务启动要等两三分钟原因无非两个模型文件太大或者加载方式太笨。模型文件大是客观事实但加载方式可以优化。我的经验是如果模型超过500MB就不要在服务启动时同步加载而是用懒加载加预热的方式。具体做法是服务启动时只加载一个轻量级的占位模型比如一个空壳或者小模型然后后台异步加载真正的模型。同时在服务启动后立即用几条典型请求做预热推理让模型权重真正进入内存并触发底层计算图的初始化。这样服务可以在10秒内对外可用虽然前几条请求会慢一点但用户体验比等三分钟好得多。另一个坑是模型格式。从零做AI工程你可能会遇到各种格式PyTorch的.pt、TensorFlow的.pb、ONNX的.onnx还有各种量化后的格式。我的建议是统一转成ONNX。ONNX的好处是跨框架、跨平台而且推理时可以用ONNX Runtime性能通常比原生框架好20%到30%。转换过程可能会遇到算子不支持的问题但大部分常见模型都能顺利转换。如果遇到不支持的算子可以尝试用ONNX的扩展算子或者把那一小段逻辑用Python重写。3.2 批处理吞吐量和延迟的平衡艺术批处理是推理服务的核心优化手段但很多人用不好。我见过两种极端一种是一条一条推理GPU利用率不到10%另一种是攒一个巨大的批次结果延迟高到用户无法接受。正确的做法是动态批处理设置一个最大批次大小和一个最大等待时间哪个先到就触发推理。举个例子假设你的服务每秒收到50个请求单个请求推理耗时20毫秒。如果一条一条处理每秒最多处理50个刚好够用但GPU利用率很低。如果设置最大批次为16最大等待时间为50毫秒那么系统会攒够16个请求或者等50毫秒就触发一次推理。16个请求一起推理可能只需要80毫秒平均每个请求5毫秒吞吐量提升到每秒200个延迟也在可接受范围内。这里的关键参数是最大等待时间。设得太短批次攒不起来吞吐量上不去设得太长延迟太高用户体验差。我的经验值是如果业务对延迟敏感比如实时对话最大等待时间设为20到30毫秒如果对吞吐量更敏感比如离线批量处理可以设到100到200毫秒。这个参数需要根据实际业务压测来调没有万能值。还有一个细节批处理里的请求长度可能差异很大。如果直接把一个长度10的请求和一个长度1000的请求放在同一批padding会浪费大量计算资源。我的做法是分桶批处理按请求长度分成几个桶比如短、中、长每个桶内部做批处理。这样padding浪费少整体效率更高。实现上可以用优先队列每个桶一个队列调度器轮流从各队列取请求。3.3 结果缓存别让同样的请求算两遍缓存是提升推理服务性能的另一个利器但AI工程的缓存和普通Web缓存不太一样。普通Web缓存通常按URL缓存整个响应但AI推理的输入是文本或向量直接按原文缓存命中率可能不高。我的做法是分层缓存第一层是精确缓存按输入文本的哈希值缓存结果第二层是语义缓存按输入向量的相似度缓存结果。精确缓存很简单用Redis或者内存字典都行键是输入文本的MD5值是推理结果。命中率取决于业务场景如果是客服问答这种重复率高的场景命中率能到30%以上。语义缓存稍微复杂一点先把输入文本转成向量可以用一个轻量级的编码器然后在向量数据库里查找相似度超过阈值的记录。如果找到直接返回缓存结果如果没找到走推理流程然后把新结果存入向量数据库。语义缓存的阈值设置很关键。设得太高比如0.98命中率低缓存形同虚设设得太低比如0.85可能返回不相关的结果影响业务质量。我的经验是对于分类任务阈值可以设到0.92左右对于生成任务阈值要更高0.96以上比较安全。另外语义缓存要设置过期时间因为业务数据分布可能会漂移太老的缓存结果可能不再适用。4. 服务化把你的推理能力包装成别人能用的东西4.1 API设计别让调用方猜你的心思从零构建AI工程最终一定要对外提供服务而API设计就是你和调用方之间的契约。我见过太多糟糕的AI API参数命名随意、错误码混乱、文档缺失。好的API设计应该让调用方一眼看懂怎么用不用猜。我的API设计原则有三条第一输入输出都用JSON字段名用下划线命名法保持一致性第二每个请求必须包含一个request_id方便追踪和排查问题第三错误响应要包含明确的错误码和人类可读的错误信息。比如{ request_id: req_20240115_abc123, error_code: INVALID_INPUT, error_message: 输入文本长度超过最大限制当前长度5120最大限制4096 }这样的错误响应让调用方一目了然不用去翻文档或者猜。另外我强烈建议给API加一个健康检查接口返回服务状态、模型版本、平均延迟等关键指标。这样运维人员可以快速判断服务是否正常而不是等到用户投诉才发现问题。4.2 限流与降级保护自己也保护调用方AI推理服务通常计算密集如果不限流一个恶意调用或者一个bug就能把服务打挂。从零构建服务化能力限流是必须的。我的做法是令牌桶算法加优先级队列每个调用方分配一个令牌桶按配置的速率发放令牌请求到达时先检查令牌没有令牌就进入等待队列或者直接拒绝。限流之外降级策略也很重要。当服务压力过大时可以临时降级比如把大模型换成小模型、把精确推理换成缓存结果、或者直接返回一个默认结果。降级策略要提前配置好并且能够动态开关。我通常会在服务里内置一个降级开关通过配置中心控制。当监控系统发现延迟超过阈值或者错误率上升时自动触发降级等压力过去后再恢复。这里有个经验降级策略一定要在平时就测试。我见过一个项目降级代码写了但从来没跑过真到需要降级的时候发现代码有bug反而造成了更大的故障。所以我的建议是每个月至少做一次降级演练确保降级路径是通的。4.3 监控与日志出问题时你能看到什么AI服务的监控和普通Web服务不太一样除了CPU、内存、QPS这些常规指标还要监控模型特有的指标推理延迟分布、批次大小分布、缓存命中率、输入长度分布等。这些指标能帮你快速定位问题。比如如果发现推理延迟突然上升但QPS没变可能是输入长度变长了导致单次推理耗时增加。如果发现缓存命中率下降可能是业务数据分布变了需要调整缓存策略。如果发现批次大小一直很小可能是最大等待时间设得太短需要调大。日志方面我建议记录每个请求的完整信息请求ID、输入摘要不要记录完整输入避免隐私问题、输出摘要、推理耗时、是否命中缓存、使用的模型版本。这些日志在排查问题时非常有用。我自己的项目里日志会写入Elasticsearch然后用Kibana做可视化。这样当用户投诉“刚才那个请求结果不对”时我可以根据请求ID快速找到对应的日志复现问题。5. 性能调优从能用 to 好用差的是这些细节5.1 量化用一点点精度换大幅性能提升模型量化是推理性能优化的第一把刀。简单说就是把模型权重从32位浮点数降到16位甚至8位整数。这样做的好处是模型文件变小、内存占用降低、推理速度提升。代价是精度可能略有下降但通常下降幅度很小。我的经验是对于大多数分类和抽取任务16位量化几乎无损推理速度能提升30%到50%。8位量化精度损失稍大但速度提升更明显能到2倍左右。具体选哪种要看业务对精度的容忍度。我的做法是先做16位量化如果精度达标就上线如果还不够快再尝试8位量化同时用一小批标注数据做校准确保精度下降在可接受范围内。量化的实现方式有两种训练后量化和量化感知训练。训练后量化简单直接对已有模型做转换适合快速验证。量化感知训练需要在训练时模拟量化误差精度保持更好但需要重新训练。从零做AI工程我建议先用训练后量化快速验证效果如果精度不达标再考虑量化感知训练。5.2 算子融合与图优化让计算图跑得更顺现代推理框架比如ONNX Runtime、TensorRT都支持算子融合和图优化但很多人不知道这些优化需要手动开启。算子融合是把多个小算子合并成一个大算子减少内存访问和内核启动开销。图优化是重新排列计算顺序消除冗余计算。以ONNX Runtime为例你可以在创建推理会话时设置优化级别import onnxruntime as ort options ort.SessionOptions() options.graph_optimization_level ort.GraphOptimizationLevel.ORT_ENABLE_ALL options.intra_op_num_threads 4 options.inter_op_num_threads 2 session ort.InferenceSession(model.onnx, options)ORT_ENABLE_ALL会开启所有图优化包括常量折叠、算子融合、死代码消除等。实测下来这个设置能让推理速度提升15%到25%。另外intra_op_num_threads和inter_op_num_threads控制线程数需要根据CPU核心数调整。我的经验是intra_op_num_threads设为物理核心数的一半inter_op_num_threads设为2到4这样能充分利用CPU又不会过度竞争。5.3 内存管理别让OOM成为你的日常AI推理服务的内存管理是个精细活。模型权重、中间激活值、输入输出缓冲区都要占内存如果不加控制很容易OOM。我的做法是第一限制最大批次大小根据可用内存反推第二及时释放中间变量避免Python的引用计数导致内存泄漏第三用内存池管理频繁分配释放的缓冲区。限制最大批次大小有个简单公式最大批次 可用显存 / (单样本激活值 模型权重)。单样本激活值可以通过跑一个样本然后看显存增量来估算。比如模型权重占2GB跑一个样本显存增加50MB可用显存8GB那么最大批次大约是(8-2)/0.05120。但实际要留一些余量设成100比较安全。Python的内存管理有个坑循环里创建的临时变量如果不手动释放可能会一直占着内存。我的习惯是在循环末尾显式调用del删除大变量然后偶尔调用gc.collect()。虽然Python有垃圾回收但显式释放更可控。另外如果用的是PyTorch记得用torch.cuda.empty_cache()清理GPU缓存但这个操作比较耗时不要频繁调用。6. 成本控制从零做AI工程省钱就是赚钱6.1 算力选型GPU不是唯一答案很多人一提到AI推理就想到GPU但实际上很多场景CPU就够了。我的判断标准是如果模型小于100MB且QPS低于50CPU完全能扛住。用CPU的好处是成本低、部署简单、弹性好。云服务商的CPU实例比GPU实例便宜一个数量级而且不需要担心GPU驱动和CUDA版本问题。如果确实需要GPU也要选对型号。推理场景下GPU的显存带宽比计算能力更重要因为推理通常是内存密集型而不是计算密集型。所以选GPU时优先看显存大小和带宽而不是CUDA核心数。比如某些专业推理卡计算能力不如顶级训练卡但显存大、带宽高推理性价比反而更高。还有一个策略是混合部署把轻量级请求路由到CPU重量级请求路由到GPU。这样既能保证整体吞吐量又能控制成本。实现上可以用一个简单的分类器判断请求复杂度或者按调用方等级路由。我自己的项目里大约70%的请求走CPU30%走GPU整体成本比全GPU部署降低了60%。6.2 自动扩缩容让资源跟着业务走自动扩缩容是控制成本的另一个关键。业务量有高峰有低谷如果一直按高峰配置资源低谷时就是浪费。我的做法是基于QPS和延迟两个指标做扩缩容当QPS超过阈值或者平均延迟超过阈值时增加实例当QPS低于阈值且延迟正常时减少实例。扩缩容的粒度要细最好能按分钟级别调整。云服务商通常提供自动扩缩容组可以配置最小实例数、最大实例数和扩缩容规则。我的经验是最小实例数设为峰值需求的20%最大实例数设为峰值需求的150%扩缩容冷却时间设为3到5分钟。这样既能快速响应流量变化又不会频繁抖动。还有一个省钱技巧是使用抢占式实例。抢占式实例价格通常是按需实例的30%到50%但可能被随时回收。适合用在无状态、可重试的推理任务上。我的做法是把推理服务设计成无状态的请求可以重试然后用抢占式实例跑大部分流量用按需实例做兜底。这样整体成本能再降30%左右。6.3 模型压缩小模型也能干大事模型压缩是降低推理成本的终极手段。除了前面说的量化还有剪枝和知识蒸馏。剪枝是去掉模型中不重要的权重或神经元让模型变小。知识蒸馏是用一个大模型教一个小模型让小模型达到接近大模型的效果。剪枝的实现相对简单训练一个模型然后根据权重绝对值大小去掉最小的那部分权重再微调一下。剪枝率通常能到30%到50%而不明显损失精度。知识蒸馏稍微复杂需要设计损失函数让学生模型的输出分布逼近教师模型。但效果通常更好小模型能达到大模型95%以上的效果而推理成本只有大模型的十分之一。我的建议是如果业务对延迟和成本敏感优先考虑知识蒸馏。先训练一个大模型作为教师然后用教师模型生成软标签再用软标签训练一个小模型。这个过程可能需要反复迭代但一旦成功收益是长期的。我自己的项目里通过知识蒸馏把模型从1.2GB压缩到80MB推理速度提升8倍成本降低90%而准确率只下降了1.5个百分点。7. 我踩过的那些坑和总结出的几条铁律从零做AI工程这些年踩过的坑比写过的代码还多。这里分享几条用真金白银换来的经验希望能帮你少走弯路。第一条铁律永远不要相信“默认配置”。无论是推理框架、数据库还是消息队列默认配置都是为通用场景设计的不是为你的场景设计的。我见过太多项目因为用了默认的线程数、默认的批次大小、默认的超时时间导致性能差或者不稳定。每次上线前花半天时间把关键配置过一遍根据实际压测结果调整这个投入产出比极高。第二条铁律监控要走在问题前面。不要等用户投诉了才去看日志。我习惯在服务上线前就把关键指标监控配好推理延迟的P50、P95、P99错误率缓存命中率批次大小分布。然后设置告警阈值比如P99延迟超过500毫秒就告警。这样问题刚冒头就能发现而不是等它变成故障。第三条铁律降级方案要能一键切换。AI服务的不确定性比普通服务高模型可能因为数据漂移而效果下降硬件可能因为负载过高而变慢。所以降级方案必须提前准备好并且能够快速切换。我的做法是把降级开关放在配置中心运维人员不需要重启服务就能切换。而且降级方案要定期演练确保真的能用。第四条铁律数据质量决定一切。模型可以换框架可以换但数据质量不行整个系统就是空中楼阁。我宁愿花一周时间清洗数据也不愿意花一周时间调模型参数。因为数据干净了模型效果自然好数据脏再怎么调参都是白费力气。最后分享一个我常用的检查清单每次上线AI服务前都会过一遍模型文件是否已量化、批处理参数是否已压测调优、缓存是否已启用、限流是否已配置、降级开关是否已测试、监控告警是否已生效、日志是否包含请求ID和推理耗时。这个清单看起来简单但每次都能帮我发现一两个遗漏项。AI工程没有银弹把每个细节做到位系统自然就稳了。