ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AI工程化落地:从模型训练到部署监控的完整链路

AI工程化落地:从模型训练到部署监控的完整链路 1. AI工程不是跑通一个笔记本就完事它到底在工程什么1.1 模型代码只占工程很小一部分如果你和我一样是从“调通一个AI模型”开始接触这行的那么迟早会遇到这样一个问题模型在笔记本里跑得好好的可一旦要交给别人用数据、部署、监控哪儿哪儿都开始炸毛。我在这篇文章里想聊的AI工程从零开始不是指训练一个高分模型而是模型、数据、代码、部署、监控这条完整链路的搭建。有Python基础、跑过几个模型、但还没正经交付过一个AI服务的同学应该能从里面拿走不少直接能用的东西。我记得自己第一次正儿八经负责一个AI功能上线是做个OCR识别服务。模型在Notebook里跑准确率95%上下看起来稳得不行。可一进了测试环境同样的代码图片从手机拍改成扫描件准确率直接跳水到70%出头。后来排查了很久才发现问题根本不在模型而在数据链路训练数据里全是截图类的白底黑字线上却是五花八门的背景和光照。那段时间我最大的教训是模型代码可能只占AI工程整体的20%剩下的数据、评估、部署、监控才是真正决定项目成败的部分。很多刚入门的同学会盯着排行榜上的SOTA模型觉得只要模型够强一切问题都会迎刃而解。可在真实项目里模型只是流水线中间的一环。上游数据质量、下游接口稳定性、业务评估标准任何一个环节松动模型分再高也白搭。所谓AI工程说白了就是用工程的纪律把模型的不确定性圈在可控范围内。1.2 Notebook写法的三个致命伤Notebook用来做探索性实验非常爽改个cell马上看结果但当它变成交付物时会有几个绕不开的问题状态隐式依赖cell执行顺序会改变变量状态今天跑通了明天按顺序重跑一遍可能报错。因为某些变量可能是前一个cell偷偷定义的换台机器、换个人操作顺序一乱就全乱。环境不隔离同一个Python环境里一堆包版本错乱模型训练结果很难复现。我见过最夸张的是同事Notebook里用了两个不同版本的numpy一个给旧代码用一个给新代码用靠alias硬撑。没有入口和出口业务方没法调用一个.ipynb文件它不是一个可编排、可观测的单元。线上服务需要的是一个有明确入参、返回结果、能打日志的接口而不是一个有几百个cell的文档。所以从零开始做AI工程第一件事不是换更强的模型而是换一个工程化骨架。把数据处理、模型推理、服务接口拆成独立模块用脚本和配置文件把它们串起来。这不是形式主义是为了让系统在被别人调用、被测试、被部署的时候依然有明确的行为。1.3 工程化的核心是可重复、可观测、可回滚如果让我用三个词概括AI工程的核心那就是可重复Reproducibility、可观测Observability、可回滚Rollback。可重复的意思是同样的代码、同样的输入在任何环境跑都该得到同样结果。需要锁定依赖版本、固定随机种子、把预处理逻辑显式化。可观测的意思是接口的延迟、错误率、输入数据分布不能是黑盒。线上模型说了什么、为什么这么说要有日志可查。可回滚的意思是模型版本有问题时能在一分钟内切回旧版而不是重新训练。这三条才是工程和脚本的分水岭。没有它们你的AI项目永远只是一个大号的Demo。有一次我接手一个交付了半年的推荐系统项目代码里连个配置入口都没有模型文件路径直接写在三个脚本里。结果模型文件被人误删后整整花了两天找到还能用的备份那两天的教训比读十篇技术文章都值钱。2. 动手之前先把一个AI应用拆成五个可验收的模块2.1 为什么拿内部知识问答助手当从零起步样例我见过太多人第一次做AI项目上来就写模型调用代码结果一个月后连项目到底做成什么样算成功都说不清。所以我强烈建议动手前先用一个具体业务场景把需求边界画出来。这里我用一个很常见的场景为例做一个公司内部知识库问答助手用户提问系统检索到对应的文档内容再生成带有出处的回答。为什么选这个场景因为它不涉及太高深的算法但涵盖了AI工程的所有关键模块数据处理、向量检索、大模型生成、接口服务、评估监控。把它吃透再去做推荐、图像识别、AI Agent之类思路是共通的。而且这类需求在很多公司里都真实存在你做完可以直接拿去给业务方试用而不是永远在自娱自乐。2.2 五个必须前置拆清的模块我一般会拆成五个模块每个模块都有明确的输入、输出和验收标准模块输入输出验收标准需求边界业务方描述能力清单、不可做清单明确支持哪些问题类型不支持哪些知识库构建原始文档切分后的chunk集合及向量索引检索命中率在抽样问题上达标检索模块用户query相关chunk列表召回率、排序质量可度量生成模块query 相关chunk最终回答 引用来源回答有依据不编造评估与监控线上日志/人工标注指标报表、告警延迟、准确率波动在容忍范围很多项目出问题就是没把需求边界写清楚。比如能回答所有问题这个目标没法验收但能回答本次上线的100篇FAQ内容且每个回答都附文档链接就可以验收。上线后用户问个不在范围内的行情预测系统自然要回答暂不支持这不算bug是边界设计的一部分。我在和业务方对需求时一定会把不可做清单也过一遍省得后面天天被为什么连这个都不会的投诉轰炸。2.3 非功能需求延迟、并发、成本必须在编码前定下来除了功能模块还要在动工前定三条非业务指标延迟用户能忍受几秒出结果如果有流式要求首字延迟和完整延迟都要定。一般问答助手我会把P95延迟压在3秒内首字1秒内。并发预估多少用户同时使用决定你要不要上异步任务队列以及模型服务要几副本。成本每个请求的token消耗上限是多少不能等账单出来再惊讶。这些数字不需要精确但不能没有。有了它们你后面的技术选型才有依据否则就会掉进参数调来调去但不知道哪个更重要的坑。举个例子如果业务方说回答可以慢点但成本要低那你就该优先考虑用开源小模型而不是API大模型如果反过来回答必须快那本地部署和量化方案就要提前准备。没有这些约束选型就是拍脑袋。3. 数据与模型选型我用三个月踩出来的取舍法则3.1 先把数据管好清洗、切分、留黄金评估集如果要从零开始做知识库问答数据环节我建议你先别急着造向量索引而是先做三件事清洗、切分、留评估集。清洗解决的是脏问题。内部文档里常见的重复段落、版本过期内容、表格乱码都会让召回结果变得不可用。我踩过的坑是把一个带目录的PDF整本书喂进去Embedding后一堆章节标题成了高相关结果正经内容反而排后面。后来对文档做章节结构和纯文本抽取效果立刻好了。清洗这一步看似费时但能在后面省下几十倍调模型的时间。切分解决的是粒度问题。chunk太大会塞进过多无关内容太碎又丢失上下文。我的经验是先用标题层级做语义切分每个chunk控制在300-500字左右再按固定窗口做重叠重叠量控制在10%-20%。这个组合在多数知识库场景里都够用。你可以用一些开源文本分割库但最终切分规则要根据自己文档的标题结构不断调没有一劳永逸的方案。黄金评估集是很多人忽视的一步。你可以准备50-100条真实用户问题每条标注对应的正确答案来源文档。这个集合不参与任何调参只用来做回归测试。每次改数据处理逻辑、换Embedding模型先用它跑一遍检索看命中率有没有掉。没有这个集合你根本没法判断改动是变好还是变坏。它就像软件工程里的测试用例没有测试的改动都是裸奔。3.2 选API模型还是开源模型看三个指标生成环节到底直接调用云上的大模型API还是自己部署开源模型我的取舍法则就三条数据敏感性如果知识库内容不能出域那就必须考虑私有化部署的开源模型如果能接受第三方服务API是性价比最高的方案。有些行业对数据合规要求很严这个因素直接拍板根本不用纠结。成本结构API按token计费适合低频、按量付费的场景开源模型前期要投入机器成本适合高频稳定调用、能摊薄算力的场景。工程维护能力你是否有精力维护推理服务、处理升级和并发没有的话先走API别给自己加戏。我自己的做法是新项目一律先用API快速搭出MVP跑通流程、验证业务价值后再评估是否值得换开源模型。不要为了技术自主而盲目自建AI工程最重要的是先把闭环跑起来。我们通常高估了自己驾驭底层基础设施的能力却低估了模型接入、提示词调优、评估迭代这些真正影响业务效果的工作量。3.3 Embedding模型选型别只看榜单检索模块离不开Embedding模型。这里有个反直觉的点排行榜上分数很高的通用Embedding模型在你自己的知识库上不一定比简单的开源模型好因为领域词汇和文档分布不同。我见过有人用一个超大Embedding模型效果反而被一个参数量只有它十分之一的中文模型吊打原因就是那个大模型对内部术语完全不感冒。我的选型步骤是用黄金评估集跑离线检索算RecallK。比较每个模型的召回效果和嵌入维度、耗时。优先选可私有化、维度适中的模型方便后续和主模型一起部署。另外要提醒一个细节查询query和文档document在Embedding模型里通常要用不同的prompt模板比如有些模型要求query前面加一句指令直接用同一模板会掉点。这种细节文档里不会特意写但实测影响很大。如果不确定拿两条真实query分别测一下马上就能看出差别。3.4 什么时候才需要微调和自训练现在很多人一开口就说要不要微调我的回答一般是先别急。微调解决的是模型底层能力不够的问题但很多业务问题实际上是检索没做好或提示词没写对。如果你换几个Prompt模板、优化一下检索结果效果就上来了那就完全不需要微调。真正需要微调的场景通常有两个一是模型总在某个专业领域输出错误格式二是你的系统必须离线运行且不能调用外部API但基座模型太小能力不足。当这两个场景出现时才值得投入算力去微调。微调的数据准备和评估又是另一个工程不是今天这篇能展开的但你记住一个原则微调也要先有黄金评估集否则你根本不知道微调是变好还是过拟合。我见过太多人微调完只看几个训练loss上线后一塌糊涂原因就是没有认真做和baseline的对比实验。4. 搭工程骨架目录、配置、依赖与实验追踪缺一不可4.1 一个可以直接抄走的项目目录结构从零开始我建议你建立一个和下面这种思路相近的目录结构ai-knowledge-assistant/ ├── app/ │ ├── api.py # FastAPI 接口层 │ ├── retrieval.py # 检索模块 │ ├── generation.py # 生成模块 │ └── schemas.py # 请求/响应模型 ├── data/ │ ├── raw/ # 原始文档 │ ├── processed/ # 清洗后文本 │ └── golden/ # 黄金评估集 ├── scripts/ │ ├── build_index.py # 构建向量索引 │ └── evaluate.py # 离线评估 ├── configs/ │ └── settings.py # 配置加载 ├── tests/ │ └── test_e2e.py ├── pyproject.toml ├── .env.example └── README.md这个结构不是一个死规定但有一个共同点数据、脚本、服务、配置彼此分离。你不会在api.py里突然发现一段清洗文本的逻辑也不需要在部署时去翻Notebook。模块边界越清晰后面调试就越省力。我在实际项目里还养成了一个习惯scripts/里每个脚本都必须有--input和--output参数脚本只负责一件事。这样数据流水线里每一步都可以独立验证出问题也好定位。比如python scripts/build_index.py --input data/processed --output data/index输出索引后立刻做一次检索抽样。脚本之间不要偷偷共享全局变量因为那样和Notebook的隐式依赖没有本质区别。4.2 依赖锁定与配置管理让一切可复现AI项目最大的复现障碍是依赖版本。今天能用三个月后装新包把依赖一升级结果全变。我的做法是用pyproject.toml做项目级依赖管理再配合uv或者poetry锁定版本。以uv为例一个精简的pyproject.toml长这样[project] name ai-knowledge-assistant version 0.1.0 requires-python 3.11 dependencies [ fastapi0.115, uvicorn0.30, pydantic-settings2.4, openai1.40, sentence-transformers3.0, ]然后执行uv lock生成uv.lock部署时用uv sync --frozen恢复完全一致的环境。版本锁定这件事前期多花十分钟后期能省一整天的排查时间。我自己就踩过 pip install 一个新包结果把transformer升级了线上行为全变 这种坑从那以后所有项目一律lock文件。配置管理方面我不用硬编码参数而是用pydantic-settings读取环境变量和.env文件。比如from pydantic_settings import BaseSettings class Settings(BaseSettings): embedding_model_name: str BAAI/bge-base-zh-v1.5 index_path: str data/index api_key: str max_tokens: int 512 temperature: float 0.2 model_config {env_file: .env, env_prefix: AI_}所有模型名、路径、密钥都走配置而不是散落在代码里。.env一般不提交到仓库只提交.env.example同事clone下来照着填就能跑。密钥这种事尤其不能硬编码否则一不小心push到公开仓库就等着被扫号吧。4.3 实验追踪没有对比你就不知道模型改没改好做AI工程不记录实验等于白干。你可能今天试了chunk 300明天试了chunk 500结果忘了哪个更好。我强烈建议用简单的实验追踪工具记录每次改动。MLflow Tracking 是个不错的选择它可以把参数、指标、模型产物统一存下来。离线评估脚本长这样import mlflow with mlflow.start_run(): mlflow.log_param(chunk_size, 300) mlflow.log_param(embedding_model, bge-base-zh-v1.5) recall run_retrieval_eval(...) mlflow.log_metric(recall5, recall)记录下来的不只是数字还包括每个实验的配置和代码版本。等到发现线上效果下降时你才能快速定位是哪个配置导致的。我在实际项目里有过一次教训连续调了一周Prompt效果忽好忽坏最后靠实验记录发现是测试时用的评估集版本不一致太折腾了。所以现在我规定任何实验必须有评估集版本 关键参数 指标三件套缺一不可。5. 从本机到线上FastAPI Docker 这套组合到底怎么落地5.1 用 FastAPI 做一个能被正规调用的接口工程化的第一步是让模型推理变成一个有权限控制、有输入校验、有日志的HTTP接口。FastAPI是我用得最顺手的框架类型校验和自动文档都很省心。一个最小可用的接口大概长这样from fastapi import FastAPI from pydantic import BaseModel app FastAPI() class QueryRequest(BaseModel): message: str top_k: int 5 class QueryResponse(BaseModel): answer: str sources: list[str] [] app.post(/api/query, response_modelQueryResponse) def query(request: QueryRequest): chunks retrieve(request.message, top_krequest.top_k) answer generate(request.message, chunks) return QueryResponse(answeranswer, sources[c.source for c in chunks])这里要注意几个工程细节请求和响应都用Pydantic模型定义FastAPI会自动做类型校验非法请求直接返回422而不是让异常进入模型逻辑。retrieve和generate尽量做成独立函数方便单测和替换实现。在真正上线前接口里一定要加超时控制比如调用大模型API时设置timeout10不能让它无限挂起。5.2 在 Docker 里把模型和依赖一起打包Docker 是解决在我机器上能跑的终极工具。我见过不少项目依赖没锁版本部署时在目标机器上现场装包结果每次环境不一样行为也不一样。用Docker至少能保证镜像里的环境是固定的。给你一个可以直接抄的Dockerfile基于FastAPI 向量检索假设你用的是开源Embedding模型FROM python:3.11-slim WORKDIR /app COPY pyproject.toml uv.lock ./ RUN pip install uv uv sync --frozen COPY app/ ./app/ COPY models/ ./models/ COPY data/index/ ./data/index/ EXPOSE 8000 CMD [uv, run, uvicorn, app.api:app, --host, 0.0.0.0, --port, 8000, --workers, 2]有几个细节值得说先拷贝依赖文件并安装依赖再拷贝代码这样代码一改不会每次都重新装包构建速度快很多。如果你用的是远程API而不是本地模型那么不需要把模型文件COPY进镜像但需要通过环境变量注入密钥切记不要写在代码里。--workers 2只是个参考实际进程数取决于你的模型推理是CPU密集型还是IO密集型见下节。5.3 资源估算别等线上OOM才后悔部署前最容易被低估的是资源。如果模型是本地Embedding模型它占用的内存/显存跟模型尺寸直接相关。一个bge-base-zh-v1.5大概110M参数FP16精度下占用约220M显存或内存加载向量索引还要算额外内存比如10万条chunk、每条向量768维大概100000 * 768 * 4 bytes ≈ 300MB。如果走大模型API那主要资源是网络IO和token成本。并发一高FastAPI进程可能大量等待API响应这时worker数可以适当多开但每个worker都会占用内存。我的经验公式是先按每个worker 500MB内存估算压测后往上或往下调整。另外如果所有worker都在等同一个外部API返回连接池配置就很重要别让请求堵在连接建立阶段。5.4 健康检查与压测上线前的必要动作接口写好后别急着接业务。先做两件事健康检查在/healthz端点里返回模型是否已加载、索引是否可查询。Kubernetes或云平台的探针会依赖它做自动重启。app.get(/healthz) def healthz(): if index_loaded: return {status: ok} return {status: not_ready}简单压测用locust或者wrk发一小阵并发请求看P95延迟和错误率。我不追求压出一个好看的极限数字而是看延迟会不会随并发陡增——如果陡增说明接口内部有串行阻塞点通常出在检索部分的向量遍历或外部API连接池上。上线前压测一次能避免上线后一周内被真实流量打穿。别问我怎么知道的都是教训。有一次我自认为接口很稳结果压测时发现内存吃掉3个G因为每次请求都把向量索引浅拷贝了一遍改成全局加载后内存立刻掉下来。6. 上线只是起点监控数据漂移与回归评估才是日常6.1 两类日志必须留请求日志与预测日志模型上线后很多人只看系统有没有报错却不看模型回答得怎么样。我建议至少留两类日志请求日志记录每个query的原文、时间戳、耗时、命中的chunk ID。预测日志记录生成的answer、使用的提示词版本、模型的temperature等参数。第一类日志方便你复盘延迟和并发问题第二类日志方便你在用户反馈答得不对时能精确重现当时的输入输出。我一般在接口里加一个request_id贯穿整个调用链日志系统里按这个ID一查上下游信息全出来。没有日志的大模型服务出了问题就像黑盒只能靠用户反复截图猜原因。6.2 关键监控指标别只盯着准确率AI系统的监控指标和传统Web不太一样我把它们分成三组维度指标说明服务质量P50/P95延迟、错误率、超时率反映系统稳定性业务质量有效回答率、用户采纳率、引用来源可点击率反映回答是不是真的有用数据健康query长度分布、top文档分布、无命中率反映输入分布是否漂移其中无命中率是我非常看重的指标检索模块没有找到任何相关chunk的比例。如果这个值从5%涨到20%说明用户问的问题范围变了或者知识库太久没更新。这比直接看准确率更能提前暴露问题。因为准确率要靠人工标注或用户反馈才能算出来而无命中率是线上直接可测的跑个定时任务就能看到趋势。6.3 数据漂移检测不能等用户骂完才发现数据漂移检测的原理很简单比较线上输入query的embedding分布和训练/测试阶段的分布是否接近。可以定期把线上query向量化和黄金评估集query向量做一个相似度分布对比。如果分布差异越来越大说明模型面对的输入已经不是当初那个配方了。具体做法不需要太复杂一个简单思路是import numpy as np from scipy.stats import wasserstein_distance # golden_query_embs: 收集自上线前的评估集向量 # online_query_embs: 最近一周线上query的向量 distance wasserstein_distance(golden_query_embs.mean(axis0), online_query_embs.mean(axis0)) if distance threshold: alert(输入分布疑似漂移建议检查知识库或重新评估)阈值需要根据自己项目调重点不在于数值多严谨而在于建立一个输入分布可对比的习惯。没有这个习惯模型往往是在你毫无感知的情况下慢慢变差的。我还有一个小技巧每个周末把本周线上query里出现次数最多的新词拉出来看看。如果出现大量黄金评估集里没有的词汇那基本可以判断知识库该扩容了。6.4 触发模型更新的链路重训不是自动化的最后聊一下更新链路。有些团队做自动重训我反而持保守态度。因为大模型系统里自动重训很容易吃进脏数据和错误标注导致效果更差。我更推荐半自动链路人工或半自动从线上日志筛选出低质量回答样本。由业务方校验并补充正确答案形成新的标注数据集。把新数据并入黄金评估集先跑离线评估。评估通过后再更新检索索引或微调模型。上线后继续监控指标若三天内指标没有改善自动回滚旧版本。这个链路里最花时间的是标注和评估但也是保证质量的关键。AI系统不像传统软件改一行代码就完事它的改进需要数据回流和验证闭环。把这条链路跑顺你的项目才算是真正的AI工程而不是一个一直在线却没人敢动的演示Demo。最后说点个人体会。从零开始做AI工程最容易犯的错就是想一步到位既要最强模型又要自动重训还要Agent编排。我自己的经验是先砍掉所有加分项把最小闭环跑起来——一个API、一份评估集、简单监控几个人就能维护。等业务证明它有价值再慢慢加厚度。另外我一直坚持在写任何代码前先写验收标准哪怕只有三行字也能挡住80%的返工。AI工程的魅力不在模型跑得多快而在整个系统能不能让人睡得着觉。
返回列表