1. 从零搭建AI工程能力,为什么大多数人卡在第一步就放弃了
这两年“AI工程”这个词被说得太多了,多到有点变味。打开任何一个技术社区,满屏都是“大模型应用落地”“RAG实战”“Agent开发”,看起来好像不跟着学就要被淘汰。但真正动手的人会发现一个很尴尬的现实:教程看了一堆,环境装了三遍,代码跑起来报了一屏红字,然后就不知道下一步该干嘛了。
我自己带过几个刚入行的朋友,也帮不少转岗的同事梳理过学习路径,发现一个特别普遍的规律——大部分人不是学不会,而是被“从零开始”这四个字吓住了。他们以为“from scratch”意味着要从线性代数、概率论、反向传播一路啃到Transformer,等把这些全搞明白再动手。结果就是永远停在第一章,永远在“准备阶段”。
其实“ai-engineering-from-scratch”这个方向真正要解决的问题,不是让你从数学公式推导开始造轮子,而是帮你建立一套从环境到部署的完整工程链路认知。你需要知道一个AI应用从想法到跑起来,中间到底经过哪些环节,每个环节用什么工具,遇到问题去哪里找答案。这套认知比你会不会手写注意力机制重要得多。
这篇文章适合三类人:第一类是有一定编程基础但没接触过AI工程的后端或前端开发者;第二类是在校学生,课程里学了不少理论但没做过完整项目;第三类是从数据分析、产品岗想往AI方向转的朋友。我会按照一个真实项目的推进顺序,把每个阶段的核心任务、工具选型逻辑、容易踩的坑都讲清楚。你不需要先成为算法专家,但你需要知道工程上怎么把东西串起来。
2. 动手之前先把“工程链路”想明白,别急着装CUDA
2.1 一个AI应用到底由哪几层组成
很多人一上来就装驱动、配环境,折腾两天连第一步都没跑通。问题出在他们脑子里没有一张完整的“地图”。我习惯把AI工程分成五层来看,从下往上依次是:
- 硬件与驱动层:GPU、CPU、内存、存储,以及对应的驱动和计算库。这一层决定了你能跑多大的模型、多快的速度。
- 运行时与框架层:Python解释器、深度学习框架(PyTorch、TensorFlow等)、推理引擎(ONNX Runtime、TensorRT等)。这一层决定了你用什么工具写代码。
- 模型与数据层:预训练模型、数据集、向量数据库、特征存储。这一层是你真正要处理的核心资产。
- 服务与编排层:API服务、任务队列、缓存、编排框架(如LangChain、LlamaIndex这类)。这一层决定了你的应用怎么对外提供服务。
- 应用与交互层:前端界面、对话逻辑、业务规则。这一层是用户直接感知的部分。
为什么要先建立这个分层认知?因为每一层的选型都会影响上下层的可行性。比如你选了一个需要40GB显存才能跑的模型,那硬件层就得跟上;你选了一个只支持特定框架的推理引擎,那运行时层就得调整。很多新手的问题就是“只见树木不见森林”,看到一个教程用某个工具就跟着用,结果和自己的实际场景完全不匹配。
2.2 为什么我不建议一上来就追最新最热的框架
技术社区有个很明显的“追新”倾向,今天出一个新框架,明天就有人写“XX已死,YY当立”。但工程实践里,稳定性比先进性重要得多。我自己的原则是:核心框架选成熟稳定的,周边工具可以尝鲜。
举个例子,PyTorch和TensorFlow之争已经好几年了,现在做研究和快速原型,PyTorch的生态明显更友好;但如果你要做大规模生产部署,TensorFlow Serving那套东西依然有它的价值。再比如推理引擎,ONNX Runtime通用性好、上手快,TensorRT性能强但绑定NVIDIA生态,你得根据自己的硬件环境来选。
提示:选型的时候不要只看“哪个最火”,要看“哪个社区活跃、文档齐全、出问题能搜到答案”。一个冷门框架哪怕性能再好,你遇到bug没人帮你解决,项目就卡死了。
2.3 环境准备阶段最容易忽略的三件事
第一件事是Python版本管理。很多人系统里装了好几个Python版本,pip装包的时候装到了错误的环境里,跑代码的时候又用了另一个版本,报错报得莫名其妙。我的建议是:不管你是用conda、venv还是poetry,一个项目一个独立环境,这是铁律。项目开始第一件事就是创建虚拟环境,别偷懒。
第二件事是CUDA版本和框架版本的对应关系。这个坑几乎每个人都踩过。PyTorch 2.x对CUDA版本有明确要求,你装了个CUDA 12.1但PyTorch编译时用的是11.8,跑起来就会报各种奇怪的错误。正确的做法是:先去PyTorch官网查版本对应表,确定你要装的PyTorch版本支持哪个CUDA版本,再反过来装对应的驱动。
第三件事是磁盘空间和内存的预估。一个7B参数的模型,FP16精度下大约需要14GB显存,加上推理时的KV Cache和中间激活值,实际占用可能到18-20GB。如果你只有一张12GB的卡,那就得考虑量化或者用CPU推理。这些数字在动手之前就要算清楚,别等装完了才发现跑不起来。
3. 模型选型不是“越大越好”,先搞清楚你的任务类型
3.1 判别式任务和生成式任务的选型逻辑完全不同
很多人把“AI工程”等同于“大模型应用”,这是一个很大的误区。AI工程涵盖的任务类型非常广,粗略分可以分成两大类:
判别式任务:分类、检测、分割、排序、推荐。这类任务通常有明确的输入输出映射关系,模型的作用是学习这个映射。典型场景包括图像分类、文本情感分析、异常检测等。这类任务往往不需要特别大的模型,一个几百万参数的CNN或者BERT-base就能做得很好。
生成式任务:文本生成、图像生成、代码生成、对话。这类任务没有唯一正确答案,模型需要学习数据的分布。典型场景就是现在最火的大模型应用。这类任务对模型规模和推理成本的要求高得多。
为什么要区分这两类?因为选型逻辑完全不一样。判别式任务优先考虑精度和推理速度的平衡,模型越小越好;生成式任务优先考虑生成质量和上下文长度,模型规模往往是第一约束。你拿一个7B的生成模型去做文本分类,效果可能还不如一个微调过的BERT,但成本高了十倍不止。
3.2 开源模型和闭源API怎么选
这是每个做AI工程的人都会面临的问题。我的判断框架是这样的:
| 维度 | 开源模型 | 闭源API |
|---|---|---|
| 数据隐私 | 数据不出本地,可控 | 数据要传到第三方 |
| 成本结构 | 前期硬件投入高,边际成本低 | 按调用量付费,前期成本低 |
| 定制能力 | 可以微调、量化、改结构 | 只能通过提示词和少量参数调整 |
| 运维复杂度 | 需要自己部署、监控、扩缩容 | 基本不用管运维 |
| 响应延迟 | 取决于本地硬件 | 取决于网络和对方服务状态 |
| 模型更新 | 自己控制升级节奏 | 对方随时可能更新或下线 |
如果你的场景涉及敏感数据、需要深度定制、调用量很大,开源模型是更好的选择。如果你只是做个原型验证、调用量不大、团队没有运维能力,闭源API能让你快速跑起来。很多团队的做法是混合使用:核心业务用开源模型保证可控性,边缘功能用API快速迭代。
3.3 量化:让小显存也能跑大模型的关键手段
量化是我认为每个AI工程师都必须掌握的基本功。简单说,量化就是把模型参数从高精度(如FP16)转换成低精度(如INT8、INT4),从而减少显存占用和计算量。代价是精度会有一定损失,但很多时候这个损失在可接受范围内。
常见的量化方案有几种:
- GPTQ:训练后量化,适合GPU推理,4bit量化效果不错。
- AWQ:激活感知量化,对激活值分布敏感的模型效果更好。
- GGUF:适合CPU和混合推理,llama.cpp生态用得多。
- bitsandbytes:Hugging Face生态里集成度高,8bit和4bit都支持。
我实测下来的经验是:7B模型用4bit量化后,显存占用从14GB降到4GB左右,推理速度反而可能更快(因为内存带宽瓶颈缓解了),生成质量在大多数任务上下降不明显。13B模型4bit量化后大约需要8GB显存,一张消费级显卡就能跑。量化不是万能的,但在资源受限的场景下,它是性价比最高的手段之一。
注意:量化后的模型在某些需要精确计算的任务上(如数学推理、代码生成)质量下降会比较明显,选型时要针对具体任务做评估,不能一概而论。
4. 从代码到服务:把模型跑起来只是开始
4.1 推理服务的三种典型架构
模型能在本地跑通之后,下一步就是把它变成服务。根据规模和需求不同,有三种常见的架构:
第一种:单机脚本模式。最简单的方式,一个Python脚本加载模型,处理请求,返回结果。适合个人实验和小规模内部使用。缺点是并发能力差,一个请求处理完才能处理下一个。
第二种:Web服务模式。用FastAPI、Flask这类框架把模型包装成HTTP接口,可以同时处理多个请求。这是最常见的生产部署方式。关键要考虑的是:模型加载一次还是每次请求都加载?显然要加载一次,常驻内存。那多个请求同时进来怎么办?这就涉及到并发处理。
第三种:分布式推理模式。当单机扛不住的时候,就需要把模型拆分到多张卡或多台机器上。常见方案有张量并行(把一层拆到多张卡)、流水线并行(把不同层放到不同卡)、数据并行(每张卡跑完整模型,处理不同请求)。这一层复杂度高很多,一般是模型特别大或者吞吐量要求特别高的时候才用。
4.2 并发处理:Python的GIL不是借口
很多人说Python有GIL,做不了高并发。这话对了一半。GIL确实限制了同一时刻只有一个线程执行Python字节码,但模型推理的大部分时间是在C++/CUDA层面执行的,这时候GIL是释放的。所以用多线程处理推理请求是可行的。
更稳妥的方案是用异步框架。FastAPI原生支持async/await,配合uvicorn可以处理不错的并发量。但要注意:如果你的推理代码是同步阻塞的,放在async函数里会阻塞事件循环。正确的做法是用run_in_executor把同步推理放到线程池里执行。
import asyncio from concurrent.futures import ThreadPoolExecutor from fastapi import FastAPI app = FastAPI() executor = ThreadPoolExecutor(max_workers=4) def sync_inference(prompt): # 这里是同步的模型推理代码 result = model.generate(prompt) return result @app.post("/generate") async def generate(prompt: str): loop = asyncio.get_event_loop() result = await loop.run_in_executor(executor, sync_inference, prompt) return {"result": result}这段代码的核心思路是:把阻塞的推理任务丢到线程池,主事件循环继续接收新请求。max_workers的设置要根据你的GPU显存和模型大小来定,不是越大越好。显存不够的时候,多个推理任务同时跑会OOM。
4.3 批处理:提升吞吐量的关键技巧
单条推理的时候,GPU的利用率其实很低,大部分时间在等数据搬运。批处理就是把多个请求攒在一起,一次性送进模型,这样GPU的计算单元能跑满,吞吐量可以提升几倍甚至十几倍。
但批处理有个矛盾:攒批会增加延迟。你等的时间越长,批越大,吞吐越高,但每个请求的响应时间也越长。所以需要根据业务场景找平衡点。常见策略是设置一个最大等待时间(比如50ms),在这个时间内攒到的请求一起处理,超时了就发车。
vLLM这个推理框架在这方面做得很好,它实现了PagedAttention和连续批处理(continuous batching),可以动态地把新请求加入正在执行的批次,不需要等当前批次全部完成。实测下来,同样的硬件,vLLM的吞吐量比朴素实现高一个数量级。
4.4 监控和日志:上线之后你才知道哪里会出问题
服务跑起来只是第一步,真正的问题往往在上线之后才暴露。我见过太多项目,本地测试好好的,一上线就各种超时、OOM、响应变慢。所以监控和日志不是可选项,是必选项。
需要监控的核心指标包括:
- 延迟:P50、P95、P99分位延迟,不能只看平均值。
- 吞吐量:每秒处理的请求数,以及随并发量变化的曲线。
- GPU利用率:显存占用、计算单元利用率、温度。
- 错误率:超时、OOM、模型报错的比例。
- 队列长度:等待处理的请求数,队列太长说明处理能力不足。
日志方面,每个请求的输入输出、处理时间、使用的模型版本都要记录下来。出了问题能快速定位是哪个环节的锅。别等用户投诉了才去查日志,那时候已经晚了。
5. 那些教程里不会写的踩坑记录
5.1 显存泄漏:跑着跑着就OOM了
这是最让人头疼的问题之一。服务刚启动的时候好好的,跑几个小时之后显存越用越多,最后OOM崩溃。原因通常有几个:
第一,PyTorch的缓存没有释放。PyTorch会缓存已经分配的显存,方便下次快速分配。但如果你的输入尺寸变化很大,缓存会越积越多。解决办法是定期调用torch.cuda.empty_cache(),但注意这个操作会强制同步,频繁调用会影响性能。
第二,中间变量没有及时释放。推理的时候如果用了torch.no_grad(),中间激活值不会保留计算图,但如果你忘了加这个上下文管理器,显存就会持续增长。这是一个非常常见的低级错误。
第三,Python对象引用没有释放。比如你把每次请求的结果都存到一个全局列表里做“调试”,时间长了内存和显存都会被占满。这种问题最隐蔽,因为代码逻辑看起来完全正常。
我的排查方法是:在服务里加一个定时任务,每隔一段时间打印当前显存占用和Python对象数量。如果发现显存持续增长,就用tracemalloc和torch.cuda.memory_summary()来定位泄漏点。
5.2 模型加载慢:每次重启都要等好几分钟
大模型加载确实慢,一个7B模型从磁盘加载到显存可能要一两分钟。如果每次服务重启都要等这么久,开发和运维效率会非常低。几个优化方向:
- 使用更快的存储:NVMe SSD比普通SATA SSD快好几倍,模型文件放在NVMe上加载时间能缩短一半以上。
- 模型格式转换:把PyTorch的bin文件转成safetensors格式,加载速度更快,而且更安全(safetensors不会执行任意代码)。
- 预热加载:服务启动时在后台异步加载模型,加载完成前先返回“服务未就绪”,避免请求堆积。
- 多进程共享:如果一台机器上要跑多个服务实例,可以用共享内存的方式让它们共用同一份模型权重,避免重复加载。
5.3 中文乱码和编码问题
这个问题看起来很小,但实际项目中非常常见。模型输出的中文变成乱码,或者前端显示问号,排查起来很费时间。根本原因通常是编码不一致:模型输出的是UTF-8,但某个环节用了GBK或者Latin-1来解码。
我的经验是:全链路统一用UTF-8。从模型输出、API传输、数据库存储到前端展示,每个环节都明确指定UTF-8编码。在FastAPI里设置JSONResponse的media_type为application/json; charset=utf-8,在数据库连接字符串里指定charset=utf8mb4。这些细节看起来琐碎,但不注意就会出问题。
5.4 提示词注入和输出过滤
如果你做的是面向用户的生成式应用,提示词注入是一个必须考虑的安全问题。用户可能会输入一些特殊构造的文本,试图让模型忽略之前的指令,输出不该输出的内容。虽然这不是传统意义上的“安全漏洞”,但会影响应用的稳定性和用户体验。
基本的防护措施包括:对用户输入做长度限制和特殊字符过滤;在系统提示词里明确边界;对模型输出做后处理,过滤掉明显不合适的內容。没有百分百完美的防护,但基本的防线要有。
6. 持续迭代:AI工程不是一次性的项目
6.1 建立评估体系比调参更重要
我见过很多团队,模型上线之后就开始“盲调”——今天改改提示词,明天换个模型,但效果好不好全靠感觉。这是非常危险的。没有评估体系,你就不知道改动是变好了还是变坏了。
评估体系的核心是:准备一批有代表性的测试用例,定义清晰的评估指标,每次改动后跑一遍评估,用数据说话。评估指标根据任务类型不同而不同:
- 分类任务:准确率、召回率、F1值。
- 生成任务:BLEU、ROUGE、BERTScore,或者人工评估。
- 检索任务:召回率、MRR、NDCG。
- 对话任务:人工评分、对话轮次、任务完成率。
评估集要覆盖各种边界情况:短输入、长输入、特殊字符、多语言混合。评估集的质量决定了你迭代的方向是否正确。
6.2 版本管理和回滚机制
AI应用的版本管理比传统软件复杂,因为涉及三个维度的版本:代码版本、模型版本、数据版本。任何一个变了,效果都可能变。所以需要一套机制来记录“哪个版本的代码+哪个版本的模型+哪个版本的数据=什么效果”。
我的做法是用一个配置文件记录当前使用的模型路径、版本号、关键参数,每次部署时把这个配置文件一起打包。出了问题可以快速回滚到上一个已知良好的组合。别小看这个机制,线上出问题的时候能救命。
6.3 成本控制:推理成本可能比你想象的高
如果你用的是按量付费的API,成本控制相对直观。但如果是自己部署,成本计算就复杂了:GPU租用费用、电费、运维人力、闲置浪费。我见过一些团队,模型效果很好,但成本算下来根本不可持续。
控制成本的手段包括:根据请求量动态调整实例数量(高峰期扩容、低峰期缩容);对请求做分级处理,简单的走小模型,复杂的走大模型;设置请求频率限制,防止滥用;定期审查日志,找出可以优化的环节。成本意识要贯穿整个项目周期,不能等账单来了才后悔。
6.4 团队协作:AI工程不是一个人的事
最后说一点软性的东西。AI工程项目往往涉及多个角色:算法工程师负责模型选型和微调,后端工程师负责服务化和部署,产品经理负责需求定义和效果验收,运维负责监控和稳定性。这些角色之间的沟通成本往往比技术难度更大。
我的经验是:尽早建立统一的术语表和接口约定。比如“推理延迟”到底指什么?是从请求发出到收到第一个token,还是到收到完整响应?这些定义不统一,后面扯皮的事情就多。另外,文档要写清楚每个环节的输入输出格式、依赖关系、失败处理方式。好的文档能省下大量沟通时间。
7. 我个人的一些实操体会
说了这么多,最后分享几个我自己在项目中总结的小经验,不一定对所有人适用,但希望能给你一些参考。
第一个体会是:先跑通再优化。很多人喜欢一开始就追求“最佳实践”,结果在环境配置和工具选型上花了太多时间,真正核心的功能反而没做。我的建议是先用一个最简单的方式把整个链路跑通,哪怕性能很差、代码很丑,跑通之后再逐步替换和优化。有了一个能工作的基线,后面的改进才有方向。
第二个体会是:遇到问题先看日志,再看文档,最后才问人。日志里通常有最直接的线索,文档里有官方的解释,问人之前先自己排查一遍,效率更高,也能学到更多。当然,如果卡了很久确实解决不了,及时求助也是必要的,但要把自己已经尝试过的方案和具体的报错信息整理清楚。
第三个体会是:保持对新技术的好奇,但不要轻易替换生产环境的东西。技术更新很快,今天出的新框架可能确实解决了老框架的一些痛点。但在生产环境里,稳定性是第一位的。新东西可以在测试环境里验证,确认没问题再逐步迁移。不要为了用新技术而用新技术。
第四个体会是:记录踩过的坑。我习惯用一个文档记录每次遇到的问题、排查过程、最终解决方案。时间长了这就是一笔宝贵的财富,下次遇到类似问题能快速定位。而且写下来的过程本身也是梳理思路的过程,很多时候写着写着就找到答案了。
AI工程这个方向变化很快,但底层的工程思维是相对稳定的:理解系统分层、做好选型权衡、重视可观测性、建立评估体系、控制成本、团队协作。把这些基础打牢,具体的技术工具换来换去,你都能快速上手。