我可以先告诉你们一个真实的感受:我见过太多朋友从“跑通一个Jupyter notebook里的模型”跳到“维护一个线上AI服务”时,被各种工程问题打得措手不及。模型精度明明还行,可数据一多就内存溢出;本地预测好好的,容器里一跑就报依赖冲突;接口响应慢到被上游疯狂投诉,一查才发现连批量推理的并发控制都没做。这些问题本质上都指向同一个方向——AI工程化,而不是单纯会“炼丹”。
“ai-engineering-from-scratch”这个题目,我理解的核心根本不是“从零写一遍Transformer”,而是从零建立起一套能把模型稳定、高效、可维护地跑起来的工程能力。它解决的是“算法代码和软件系统之间的那一大段距离”。我下面写的内容,就是基于我自己做过的多个AI服务落地项目,把从技术选型、环境搭到上线监控、迭代复盘的过程拆开来讲,希望能给准备入行或者正在转型的朋友一条清晰的参考路径。
1. 先想清楚:AI工程到底在解决什么问题
1.1 别把AI工程当算法岗
很多人一听到AI工程,第一反应是“那得会最新的模型结构、懂最深的数学推导”。真实项目里完全不是这么回事。算法岗的终点一般是出指标、出实验报告,而AI工程的终点是让模型在真实环境里持续稳定地创造价值。这个区别特别像“做饭”和“开餐厅”的区别:做饭只需要把菜烧好,但开餐厅要考虑备菜供应链、后厨动线、出餐高峰期并发、食材损耗、食品安全检查,甚至厨师突然请假怎么顶班——这些才是工程。
放到具体场景里,AI工程通常要承担这些职责:
- 把训练好的模型封装成可复用的服务,比如HTTP接口、消息队列消费者、批处理任务。
- 设计数据流向,包括离线训练数据、在线推理特征的获取与同步。
- 解决资源问题,一台GPU怎么同时服务多个模型?没有GPU的机器怎么用CPU做推理优化?
- 建立监控体系,准确率下降、延迟超标、内存泄漏、请求异常,这些都不能靠肉眼盯。
- 参与模型迭代流程,从数据标注规范到训练验证、灰度发布、回滚机制,形成闭环。
如果你脑子里只盯着模型精度,却对上述任意一个环节没概念,那项目一上线就会被迫补课。我个人的经验是,先补工程能力,再回去优化算法,效率会高很多。因为算法实验里的很多坑,根源都在数据处理和部署环境上,工程基础扎实了,实验跑得又快又干净。
1.2 从零起步需要建立的三层能力
我把AI工程从零到一的能力分成了三层,你可以对照看看自己卡在哪一层。
第一层是基础设施能力。包括Linux基本操作、Docker容器化、Python环境管理、GPU驱动与CUDA版本对应关系、常用中间件(如Redis、MySQL、消息队列)的搭建与使用。这一层不性感,但没它寸步难行。我面试过不少人,模型推理代码写得漂亮,一问他怎么给测试环境装个带CUDA的PyTorch镜像,直接卡壳。这不行,工程里一半的时间都在跟环境打交道。
第二层是模型服务化能力。包括把PyTorch/TensorFlow模型导出成可部署的格式(TorchScript、ONNX、TensorRT等),用FastAPI或Flask包一层HTTP服务,写清输入输出校验、异常处理、推理超时;理解同步接口和异步任务的区别,知道批处理怎么实现,了解用消息队列解耦生产和消费。很多项目初期用Flask裸跑也能撑住,但一旦流量涨起来,就得回到这一层去补课。
第三层是MLOps能力。包括数据版本管理、模型版本管理、实验追踪、CI/CD流水线、监控告警、模型漂移检测等。这一层是从“能跑”到“跑得稳”的分水岭。很多小型团队会忽略它,但我建议哪怕是个人项目,也至少用上实验追踪和简单的监控,因为这些习惯会直接影响你在更大平台上的工作方式。
2. 从零开始的技术栈选型与学习路径
2.1 入门环境配置的关键选择
“从零开始”最常见的卡点不是算法,而是装环境。我建议先做一套固定的工具箱,不要频繁换花样。硬件上如果你有一张NVIDIA显卡,哪怕是GTX 1650这种老卡,都足够跑很多入门模型;没有的话,用云GPU实例(按小时计费)或者Google Colab也能应付。重点是不要被“必须有一张顶级卡”这种想法拦住,工程学习阶段CPU跑小模型完全够用。
软件环境我现在的推荐组合是这样的:
- 系统:Ubuntu 20.04或22.04 LTS,别用Windows作为主力开发机,很多坑都是环境差异带来的。
- Python管理:用Miniconda,创建独立的虚拟环境,Python版本固定3.9或3.10。
- 深度学习框架:PyTorch为主,生态好、调试直观;TensorFlow适合已有老系统的人,新手别两头抓。
- 容器化:Docker必学,然后是docker compose来编排依赖服务。
- 服务框架:FastAPI,自带OpenAPI文档,异步支持好,做AI服务的HTTP层非常顺手。
- 推理加速:先学会onnxruntime和TorchScript,TensorRT可以等有具体业务需求后再深入。
- 监控:基础用Prometheus + Grafana,业务指标可以先用日志和自定义metric兜底。
这里有个容易踩的坑:CUDA版本和PyTorch不匹配。我见过太多人装了最新的CUDA 12.x,结果PyTorch官方只支持到11.8,整个环境跑不起来。我的建议是安装PyTorch时不要手动去装CUDA toolkit,直接用pip安装带对应cuda版本的PyTorch包就行,它会自动带好runtime。比如pip install torch==2.1.0+cu118,然后nvidia-smi显示的是驱动版本,跟你的运行环境不一定冲突,不要只看表面。
2.2 学习路径怎么排才不会劝退
我不建议上来就啃《深度学习》大部头,更不建议直接看各种论文复现库,而是建议按这个顺序来:
- 先用现成框架跑通一个分类任务。比如HuggingFace上的BERT分类模型,加载预训练权重预测一条文本的情感。这一步的目的是建立“模型是怎么被调用”的整体感知,哪怕你不懂里面每个参数也没关系。2. 手动实现一个简单的线性回归或逻辑回归,用PyTorch写训练循环,理解前向传播、反向传播、优化器更新的基本流程。3. 学Docker和FastAPI,把上面那个模型包成一个容器服务,用curl验证接口。4. 做一个完整的小项目,比如客服工单自动分类或者评论审核辅助,从数据准备到训练、部署、测试全走一遍。5. 再回头看模型结构和经典论文,你会发现理解速度快很多,因为你知道哪些细节在工程上真正重要。
这个顺序可能颠覆很多人的认知,但它恰好符合“AI工程”的定位:先跑通,再理解。我见过很多血泪案例,一个人埋头学了三周反向传播推导,结果连模型都还没跑起来,就失去了耐心。工程领域,行动是最好的过滤器。
3. 第一个真实项目:做一个文本分类服务
3.1 从数据集构建到训练阶段的问题
我用一个“工单自动分类”的项目来当例子,因为每个公司几乎都有客服工单,业务理解成本低,同时它又具备AI服务的所有典型要素:文本输入、多分类输出、需要对接业务系统。
第一步是数据建设。你不能拿个公开数据集就完事,真实场景的数据都在Excel、数据库、聊天记录里。数据质量直接影响工程上限。我当时拿到一批历史工单,大概几万条,字段很乱,有长有短,还有大量重复和无关内容。需要先做清洗:去重、去HTML标签、归一化标点、过滤垃圾文本。然后要确定分类体系,不是越细越好,而是要和业务方达成共识。当时我们定了六个大类,比如“账号问题”“支付问题”“技术故障”“产品咨询”等。每条样本可能需要多人标注取交集,保证一致性。
训练阶段我用的是HuggingFace的bert-base-chinese做微调。代码框架很成熟,但有个隐藏的坑:类别不均衡。支付问题的样本可能有几千条,技术故障只有几十条,如果不处理,模型会放弃学习弱势类别。我用了三种方法组合:重采样、类别权重、以及阈值调整。在工程上我建议一定要保存每个类别的precision/recall/f1,而不是只看整体准确率,因为整体准确率会被大类掩盖。
成本方面,微调一个base模型,用一张T4大概半小时就够了,算力根本不是瓶颈。真正的瓶颈在数据整理和验证。我当时用sklearn的train_test_split做分层划分,再用datasets库做tokenization。这里要注意tokenizer的max_length,工单平均长度在100字左右,我设到128,过长直接截断;有些工单很长,后来发现用截断丢弃了关键信息,就改为分段取前512再平均池化,这个简单改动让分类效果提升了三个百分点。
3.2 把模型封装成可靠的API服务
训练完模型只是开始,接下来要把它变成一个能对外服务的API。我选择FastAPI加Uvicorn,主要因为它的Pydantic模型可以做请求校验,还能自动生成接口文档,团队联调时省很多沟通成本。
基础架构是:加载模型到内存(通常放在全局变量),每个请求进来后先做预处理(转小写、清洗、tokenize),再调用模型预测,最后返回JSON。听起来简单,实际要考虑几个问题。
第一是并发控制。PyTorch模型在CPU或GPU上推理时,如果多个线程同时跑,会有锁竞争和显存冲突。最简单的方式是用threading.Lock保证同一时间只有一个推理在跑,如果QPS高,就用multiprocessing或者多副本部署。第二是批量推理。个别请求可能只包含一两条文本,但模型对batch=1的推理很浪费。可以用一个队列收集请求,积攒一定数量或时间间隔后合并成batch一起推理,吞吐量能提升不少。实现并不复杂,就是生产者-消费者模式。第三是超时和熔断。每个请求必须设置超时,比如2秒还没推理完就返回错误,不能无限等。调用方的重试策略也要有最大次数和退避,不然后端一抖动,前端就会雪崩。
我当时提供了一个/predict接口,输入{"text": "我登录不了账号,总是重置密码失败"},输出{"label": "账号问题", "confidence": 0.97}。同时在内部做了两层:第一层是规则召回,比如文本命中某些关键词就直接返回,不送模型;第二层才是模型分类。这个小设计在高流量时能省一半算力。
3.3 部署上线的三种可选方案
部署方案我分三种,复杂度从低到高:
方案一:单机Docker部署。把模型、代码、依赖一起打镜像,启动后用Nginx反向代理对外提供HTTP,适合内部工具或小流量场景。我当时就用这种方式部署了一个内部工单分类服务,几十个人用,很稳。这个方案要特别注意镜像大小,PyTorch的CPU镜像大概2G,可以用pytorch/pytorch:2.1.0-cpu这个官方基镜像,然后在Dockerfile里只装必要的依赖。
方案二:多副本 + 负载均衡。把同一个服务镜像跑多个容器,前面加一层负载均衡(Nginx灵活配置,或者Kubernetes的Service)。不存在状态、模型只读,JSON不可变,所以这是天然无状态服务,可以随便扩缩容。这个模式下一个重要改动是优雅启动和停止,启动时先加载好模型再监听端口,停止时处理完正在跑的请求再退出。信号量配合async,代码里十来行就能搞定。
方案三:Kubernetes + 弹性伸缩。用Deployment管理副本数,配HPA按CPU或自定义metric来扩缩。注意GPU节点调度要配好nvidia.com/gpu资源,避免CPU任务浪费GPU节点。这个方案需要团队有K8s基础,对个人项目来说可以先跳过,但了解概念很重要。
我个人建议第一个项目用方案一,跑通后直接演进到方案二,再按团队实际需求上K8s。不要一上来就搭一套微服务和容器编排,复杂度会让你根本分不清是模型出了问题还是基础设施出了问题。
4. 工程化核心:性能优化、监控与迭代
4.1 推理速度的常用加速手段
当你的服务开始服务真实流量,第一个挑战就是延迟和吞吐。我按见效顺序说说常用的手段。
第一优先级是输入长度控制。文本分类这种场景,很多请求文本很长,而模型对长文本的计算复杂度是O(n^2)。把输入截断到合适长度,延迟能降一半。我当时用了动态截断策略,保留开头和结尾各128个token,中间用分隔符拼接,效果不错,对于那些关键信息在末尾的工单特别有用。
第二优先级是模型量化。用torch.quantization做动态量化,把全精度权重转为int8,CPU上推理速度能提2到3倍,精度损失通常很小。如果你的推理跑在GPU上,可以试一下TensorRT,但对大部分文本小模型来说,int8动态量化已经够用。
第三优先级是推理引擎替换。把PyTorch模型导出成ONNX格式,再用onnxruntime加载。onnxruntime做了大量算子融合优化,在CPU上比PyTorch的eager模式快很多。导出过程中有一些坑,比如某些动态控制流算子不支持,需要固定输入尺寸或改代码,但分类模型一般比较顺利。我当时导出的BERT ONNX模型,在8核CPU机器上单条推理从约80ms降到约25ms,这个提升非常直观。
第四优先级是分布式缓存。如果服务是文本分类,重复请求比例可能很高。缓存可以放在Redis或本地内存,key用文本的hash,value用预测结果,TTL设成几小时。注意不要给长文本直接做hash key,会占太多内存,可以先做归一化后再hash。
优化是系统工程,别一上来就上TensorRT,先用各种廉价招数夹出最大收益,复杂方案留给真正的瓶颈。
4.2 线上监控怎么设计才有价值
很多AI服务的监控做得很唬人,一堆Grafana大盘,CPU、内存、GPU利用率全都有,但一问业务方“模型效果现在怎么样”,没人答得上来。我建议监控体系分三层:
第一层:服务健康度。包括QPS、错误率、P99延迟、依赖组件(数据库、Redis)是否正常。这一层主要告诉运维“服务是否还活着”。
第二层:模型预测结果分布。记录每次预测的label分布、置信度平均值、最高置信度、输入文本长度的分位数。这些指标能反映模型行为是否发生漂移。比如之前90%的工单被分到“账号问题”,突然有一天变化成40%,哪怕准确率还没崩,也可能意味着线上数据分布变了。
第三层:业务效果。这是最难的一层,通常要依赖下游反馈。比如工单分类服务,需要追踪“自动分配准确率”,即在回复工单时是否被客服纠正。短期没反馈,可以抽检或做规则对比。我做法是每天随机抽取一定数量线上预测入库,人工抽检,统计准确率趋势并记录在dashboard。
日志也很重要,每个请求的输入、预测结果、耗时都应该结构化打印。注意不要打印超长文本,否则日志系统会被撑爆,可以先截断或者计算hash保存。
告警规则要克制,不要每一分钟都pager。我建议只有这些情况才告警:错误率连续5分钟超过5%、P99延迟超过预留阈值两倍、QPS跌到接近0(服务可能挂了)、模型输出分布出现明显突变。其他情况写日报里就行,不然大家会对告警麻木。
4.3 模型迭代不能靠“重训就完事”
模型上线后必然面临效果下降,因为真实数据不断在变化。迭代这件事,工程上有几个关键细节让很多人翻车。
首先,数据和模型都要有版本。训练数据打包时带上数据集的commit hash或日期,模型文件命名带上版本号和评估指标。我见过最乱的团队,因为找不到“效果最好的那个模型用的数据是哪一版”,只能重新标注,白白浪费两周。建议用DVC或者简单地在训练脚本里记录每一轮的参数和指标到CSV,配合MLflow做实验追踪,个人项目至少也要养成命名规律。
其次,要有灰度发布和回滚机制。新模型不要直接全量切换,先用5%流量试运行,对比新旧模型的预测分歧和线上真实反馈,确认没问题再逐步放量。放量可以用服务配置中心动态控制,也可以用部署两个版本加基于权重的负载均衡。回滚要快,如果新模型出了严重问题,一条命令切回旧版。我的做法是保留最近三个版本的模型文件,并且把接口层设计成能通过环境变量指定模型路径,这样回滚就是改一个变量再重启服务。
再次,数据再训练要自动化。最好的方式是定期收集线上预测分化的样本,加上人工标注,累积到一定数量后自动触发训练任务,然后把新模型打包镜像推到测试环境,跑评估脚本,如果指标达标则进入灰度发布。这套Pipeline可以用Airflow或者简单的GitHub Actions加定时任务实现,关键是“自动触发”而不是人肉提醒。
5. 常见问题与排查技巧实录
5.1 环境相关的“玄学”问题,其实都有原因
我在带新手做AI项目过程中,遇到的环境问题十有八九都是下面这几类,这里直接给出排查思路:
“镜像启动后CUDA不可用”:大概率是基础镜像和依赖版本不匹配,或者是容器没有暴露GPU。Docker启动加--gpus all,并确认宿主机有NVIDIA Container Toolkit。在PyTorch容器里跑torch.cuda.is_available()检查,输出False就逐层排查:宿主机驱动是否支持容器、镜像内CUDA版本是否偏高。
“Docker构建时pip下载慢或超时”:配国内pip镜像源,在Dockerfile里设置PIP_INDEX_URL,或者用分层缓存,把requirements.txt放在代码之前复制。我还习惯把常用的模型预下载和代码分开,做volume挂载,这样改代码重启镜像不用重新下载模型。
“端口占用导致服务起不来”:用netstat -tlnp找占用进程,常见是别上一次崩溃的进程没清干净,或者是多个容器端口映射冲突。最好每个服务固定端口范围,并在启动脚本里做端口占用检查。
“模型文件太大导致镜像构建失败”:建议不要直接把权重放镜像里,而是在容器启动时从对象存储拉取,或挂载数据卷。镜像里只有代码和依赖,大小会小很多,迭代也更灵活。
5.2 推理阶段的经典Bug与定位方法
有个非常典型的Bug是“第一次请求特别慢,之后变快”。这是模型加载和预热没做好。解决方法是服务启动时做一次空预测或真实验证预测来触发CUDA kernel和ONNX session初始化,再对外提供服务。如果你不做预加载,用户的第一笔请求可能就超时了。
还有一个常见问题是“GPU显存持续上升”。这通常不是内存泄漏,而是PyTorch的缓存机制在作怪。PyTorch为了快速分配,会缓存已释放的显存,不立刻还给系统。不一定有问题,但你可以做一个周期性显存统计来观察是否持续无界增长。可以在每推理1000次后打印torch.cuda.memory_summary(),看缓存和实际占用。如果真有问题,多半是某些tensor被留在了计算图里,检查一下有没有detach()。
另一个高频问题是“CPU版本模型和GPU版本模型预测结果不一致”。理论上浮点误差很小,但如果使用不同版本PyTorch或者量化参数不一致,结果差异可能不是模型问题。我遇到过用GPU训练后导出ONNX,在CPU上跑,发现token_type_ids处理不一致导致预测偏移。定位方法很简单:固定同样输入,逐层检查tokenizer输出,再检查模型的logits,基本能找到差异点。
5.3 排查工具与日志规范的推荐
你可以把这当作一个“AI服务体检清单”:
- 服务层:FastAPI内置的/logs接口、uvicorn的access log、Python logging按天轮转。
- 请求层:用
structlog输出JSON格式日志,包含request_id、耗时、label、confidence、输入长度。务必带request_id,这样才能在日志里串起一个请求的完整生命周期。 - 资源层:
nvidia-smi定时采样、psutil做进程CPU/内存监控、docker stats看容器资源占用。 - 链路层:如果公司有Jaeger或SkyWalking,可以接入跟踪;没有的话,日志里把上下游调用时间戳记好就能排查很多问题。
排查问题时,我的经验顺序永远是:先看日志有没有报错,再看资源有没有打满,再看输入数据是否符合预期,最后才怀疑模型本身。大多数线上事故都是前三者导致的。
6. 从零到一的项目实战复盘
6.1 一个迷你AI工程项目的成本与时间预估
很多朋友关心“从零开始做一个AI服务需要多久”。不说大厂复杂场景,就说我们上面那个工单分类服务:如果一个人有一定Python基础,每天能投入3小时左右,我给一个参考周期:
- 第一周:搭环境、选数据集、跑通BERT微调示例。
- 第二周:清洗真实工单、定分类体系、做标注规范。
- 第三周:微调模型、评估调优、导出ONNX。
- 第四周:写FastAPI服务、容器化部署、加日志监控。
- 第五周:线上试运行、处理反馈、迭代。
总成本几乎是零(自己电脑CPU跑得动,或者租个低价GPU实例),但收获却是整套AI工程思维。关键不是硬啃算法,而是让项目持续转起来,你会在迭代过程中逼自己学会那些文档里没写的问题。
6.2 这个项目做完后,你就能解锁什么技能
完成这样一个小项目,你就等于拥有了以下这些“可迁移能力”:
- 能独立把一个模型文件变成一个HTTP服务,这是AI工程师的核心动手能力。
- 知道怎么组织代码,把数据处理、模型加载、推理逻辑、API层分开,而不是全揉在一个notebook里。
- 懂得监控和日志里的维度,在出问题时能快速定位到是数据变化、部署异常还是模型退化。
- 有了“发布”和“回滚”的肌肉记忆,不再一股脑往上冲,而是有节奏地把模型变更引入生产环境。
- 理解模型指标和业务指标之间的差异,能从用户反馈中反推数据质量和模型效果问题。
更重要的是,这个项目可以作为你作品集或者简历里的实打实的亮点。面试官问起的时候,你能讲清楚每一个决策和踩过的坑,这比背八股文有说服力得多。
6.3 接下来怎么继续深入
做完了文本分类服务,你可以在这个小项目基础上逐步扩展复杂工程能力。推荐的进阶方向有:
- 接入消息队列:把同步API改成异步任务,用Redis或RabbitMQ解耦,让高耗时推理任务不影响主业务响应。
- 做多模型路由:按业务场景或输入特征路由到不同模型,再统一做结果融合,这涉及模型管理和版本感知。
- 引入特征平台:把用户的统计特征、实时特征存起来,与模型预测结合,提升效果,你就开始接触推荐系统的工程化。
- 加A/B测试框架:做更加严谨的线上效果对比,而不是简单看累计指标。
我一直觉得AI工程不是一个岗位名称,而是一套解决问题的方法论。从完成第一个端到端项目开始,你就会逐渐摆脱对“教程依赖”的焦虑,遇到新场景就能拆解成数据、模型、服务、监控这几个固定环节。这个“从零开始”的过程,走通一次之后,就不再是零。