ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AI工程从零搭建:避开环境配置与模型选型陷阱的实战指南

AI工程从零搭建:避开环境配置与模型选型陷阱的实战指南 1. 从零搭建AI工程能力为什么大多数人卡在第一步就放弃了这两年“AI工程”这个词被说得太多了多到有点变味。打开任何一个技术社区满屏都是“大模型应用落地”“RAG实战”“Agent开发”看起来好像不跟着学就要被淘汰。但真正动手的人会发现一个很尴尬的现实教程看了一堆环境装了三遍代码跑起来报了一屏红字然后就不知道下一步该干嘛了。我自己带过几个刚入行的朋友也帮不少转岗的同事梳理过学习路径发现一个特别普遍的规律——大部分人不是学不会而是被“从零开始”这四个字吓住了。他们以为“from scratch”意味着要从线性代数、概率论、反向传播一路啃到Transformer等把这些全搞明白再动手。结果就是永远停在第一章永远在“准备阶段”。其实“ai-engineering-from-scratch”这个方向真正要解决的问题不是让你从数学公式推导开始造轮子而是帮你建立一套从环境到部署的完整工程链路认知。你需要知道一个AI应用从想法到跑起来中间到底经过哪些环节每个环节用什么工具遇到问题去哪里找答案。这套认知比你会不会手写注意力机制重要得多。这篇文章适合三类人第一类是有一定编程基础但没接触过AI工程的后端或前端开发者第二类是在校学生课程里学了不少理论但没做过完整项目第三类是从数据分析、产品岗想往AI方向转的朋友。我会按照一个真实项目的推进顺序把每个阶段的核心任务、工具选型逻辑、容易踩的坑都讲清楚。你不需要先成为算法专家但你需要知道工程上怎么把东西串起来。2. 动手之前先把“工程链路”想明白别急着装CUDA2.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生态用得多。bitsandbytesHugging 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_workers4) 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; charsetutf-8在数据库连接字符串里指定charsetutf8mb4。这些细节看起来琐碎但不注意就会出问题。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工程这个方向变化很快但底层的工程思维是相对稳定的理解系统分层、做好选型权衡、重视可观测性、建立评估体系、控制成本、团队协作。把这些基础打牢具体的技术工具换来换去你都能快速上手。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进