ARTICLE DETAIL

资讯详情

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

从零构建AI工程:拆解数据管线、模型微调与推理服务全链路

从零构建AI工程:拆解数据管线、模型微调与推理服务全链路 如果你最近在逛 GitHub、刷技术社区大概率会看到那个有些特别的仓库名ai-engineering-from-scratch。这个“from scratch”不是指从零手写神经网络也不是让你造 GPU而是指一条几乎不靠平台封装、把 AI 应用的每个环节都自己动手过一遍的工程化路线。从数据管线、模型微调、评测集设计、推理服务到可观测性每一层都像拆零件一样摊开来看。这篇内容就是基于这个项目标题拆出来的完整工程路径。适合谁看两种人。一种是刚把 AI 应用跑在 demo 上、想进一步把模型接进真实业务里的后端工程师另一种是已经做了两三个 LLM 应用、但总觉得“调用 API 没法沉淀核心能力”的算法工程师。你会看到一个 AI 工程从 0 到 1 需要经过哪些决策点、每种选型背后的理由、以及我实际落地的过程中踩过并且已经解决的坑。1. 从零开始之前的清醒AI 工程和写模型是两码事1.1 “from scratch”到底在 scratch 什么先说一个很多初学者最容易误解的地方AI 工程不是写 Transformer也不是用 PyTorch 从头训练一个大模型。它更像搭一条完整的生产流水线——原料是数据中间环节是模型处理最终端是稳定对外提供服务。真正的难点在流水线的稳定性和可迭代性而不是单个环节的先进性。这个仓库名字里的“from scratch”更有意思。它强调一件事情今天很多 AI 应用是单纯组装出来的——调好现成的模型 API写几个提示词再从向量数据库里检索一下内容。这种做法的优势是快但问题也很明显你没有自己的评估集没有数据回流没有模型版本管理甚至连一次有效的指令微调都没做过。一旦基础模型升级你的应用表现可能整体漂移可你根本不知道原因出在哪。从零开始构建 AI 工程就是要补偿这些被“封装”掩盖掉的认知盲区。所以整个项目的思路不是某种研究导向而是工程导向假设我现在只有一个业务目标比如“做一个能回答企业内部知识库问题的助手”那我应该怎么一步步把它变得更可靠、更快、更可控。1.2 工程和研究的边界以及你会遇到的第一道坎当我真开始梳理这个标题对应的完整技术栈时发现最值得拿出来讲的并不是某一个框架而是贯穿始终的思维模式工程化的 AI 系统设计跟做研究有非常明显的区别。研究在意的是上限一个指标能不能 SOTA一个模型能不能有新的涌现能力。工程在意的是下限稳定复现、可观测、可回滚、可监控、可赔偿如果做商用。举个例子。我用一个私有部署的对话模型做了个客服助手。研究视角下我关心的是这个模型在我标注的 200 条测试集上的 ROUGE 分数或 GPT-4 打分工程视角下我关心的是用户乱输入符号时会不会导致服务崩溃、并发 200 时延迟涨到几秒、某个回答连续 5 次出现同样幻觉时能不能被日志捕捉到。这两套逻辑会在同一个系统里打架。你费尽心思加了请求批处理结果模型推理显存不够直接 OOM你为了让服务更稳做了全局超时限制结果长文档总结任务频繁被截断。这些就是 from scratch 路上的第一道坎从零开始的核心不在于有没有一个模型而在于能不能把模型塞进一个真实系统里并保证系统的整体表现可预期。2. 数据优先没有高质量数据流后面的工程全是空谈2.1 你的数据策略应该比模型选型更早确定很多做 AI 应用的人有个惯性先找个强模型再拿业务数据去“喂”它。但从 zero 构建的角度看正确的顺序正好反过来。先想清楚你有哪些数据、这些数据长什么样、它们能不能被用于评估和微调然后再决定要不要换一个更强的底座模型。这背后的逻辑其实很朴素。模型的能力是被数据“暴露”出来的。同一个对话系统如果领域样本只有几百条你用开源 7B 还是闭源大模型差别会很大但如果你的数据侧是结构化的、干净的核心内容库有 10 万条问答对那么一个 7B 模型微调后的真实表现完全可能压过没微调的大参数模型。数据决定了你的模式是“通用调用”还是“领域深度适配”。我在实际规划这个项目时把数据部分拆成了三块这也是我认为最健康的拆法数据模块解决什么问题关键产出数据接入与清洗把杂乱格式变成统一内容实体一份可重复使用的清洗管线数据标注与变换把业务内容变成训练/评估可用的样本高质量种子语料数据版本与回滚每次改数据不会导致结果连锁漂移带版本信息的数据集包如果你现在的项目还停留在“导入 PDF、切分文本、灌进向量库”这个阶段那数据侧还处于最原始的阶段。上面这张表里你最需要注意的是第三行“数据版本与回滚”。我见过很多团队改了一版标注规则结果新模型效果整体下跌但旧版本的训练数据已经被覆盖了完全找不到回归样本。这类问题工程端必须一开始就规避。2.2 清洗管线的一手经验脏数据比模型幻觉更致命说一个我在做的过程中特别深的体会脏数据带来的负面影响比模型幻觉要隐蔽得多也难查得多。用生活化一点的类比来说——模型幻觉像是厨师把菜做咸了你吃一口立刻能发现能跟厨师反馈脏数据却是这个厨师用的食材本身变质了做出来的菜颜色和摆盘都正常但你吃完之后身体出问题而且很难直接把它跟某个具体环节关联起来。在我维护的清洗管线里吃过几次大亏后总结出了一个黄金操作顺序第一步先做样例可视化。不要一上来就切文本先随机抽 100 条样本打印出来人工看一下格式整体长什么样。往往是这一步就能发现大量问题比如所有表格都被转换成了错位的逗号分隔、所有 URL 都带多余转义符、某些网页模板把正文重复了 3 遍。第二步写规则过滤而非模型过滤。比如长度过滤、重复段落过滤、语言一致性过滤这些能用正则和简单哈希解决的就别上模型。用模型做过滤听起来很高端但它本身也会出错而且运行成本高。一次性正则能把 90% 的明显噪声清理掉剩下的再交给后续的向量化环节。第三步保留确定性操作。对同一份输入清洗逻辑每次运行必须得到完全相同的结果。这听起来基础但很多工程化不足的管线会在这里出问题比如某些清洗流程引用了随机采样、用了时间戳命名都会导致版本不可复现。第四步把清洗后的每条样本都挂上原始来源 ID。这个操作一开始看起来多余但当你发现某条数据引发了明显偏见、需要追溯来源时它简直救命。有一次我发现模型老是用不恰当的语气回答咨询类问题排查到最后问题根本不在指令微调参数而是训练集里混入了一段论坛互相调侃的对话且这段对话源头指向一个几乎没人会留意的旧帖。2.3 评估数据集的构建不是越多越好是要贴住真实分布大多数人做 AI 应用最忽略的就是评估集。你以为的性能“感觉不错”往往是因为你只拿十几条顺手的例子试过。这种评估方式在 demo 阶段够用在工程阶段几乎没有参考价值。入手做评估集时我会建议分成两个部分一是核心固定集回归集二是对抗/边界集。核心固定集选大约 200500 条能代表真实业务最常见的输入每次任何环节发生变化——数据处理、提示词、模型切换、微调权重——都要跑一遍防止性能“悄悄下滑”。对抗/边界集则是你故意挑的那些刁钻例子比如用户的输入里包含恶意符号、超长文本、语义模糊的表达或者完全不在知识范围内的提问。拿这个仓库标题所指向的“AI engineer”视角来说评估集还有一个特殊使命排优先级。实际工作中评估发现的问题往往是几十个同时存在的比如问答完整度不够、引用格式错误、中文指令下偶尔返回英文、空值回答太多。如果一股脑全部去改精力就被稀释了。正确做法是每次只抓当前长板列表里最影响用户体验的 12 个问题改完再重新跑全套评估。这种以评估驱动迭代的方式才是工程化迭代的正确姿势而不是“加了个高深的 RAG 组件就以为系统整体变强了”。3. 模型选型与微调落地组合推理比单点性能更重要3.1 不要用“模型越强越好”来掩盖架构缺失在博客和热词里大家总在聊 ai-engineering但在实际项目里很多人做的不太像“工程”更像“换模型大赛”。观察项目标题里的 from scratch 你就会明白真正的工程不是每次模型厂商发了新版就无缝切换而是你的系统架构能对不同模型保持适配和可替换性。现在选型主要面对三类选择闭源大模型 API、开源全能模型、开源小参数模型7B 级以下。它们没有绝对优劣只有适合不适合。闭源最大的便利是省心但你的服务体验完全取决于对方平台的稳定性和策略开源全能模型适合做企业私有化部署或需要深度定制的工作流开源小参数模型适合高并发、低延迟、任务相对标准的场景比如文本分类、实体抽取。我使用“三层选择法”来决定用什么模型每次都很管用先根据任务复杂度判断最简方案。不需要任何推理纯抽取或改写的任务直接上小参数微调模型速度快且可控。需要一定程度理解与结构化输出的任务考虑 7B~14B 级别的开源模型搭好提示词与解析层。开放式生成、复杂推理、多步工具调用的任务才考虑更大的底座或闭源大模型。很多人先把第 3 层拿来做所有事导致成本高、延迟大、且完全依赖平台。这就是没有用工程思维去设计架构。3.2 指令微调的实操要点不是“喂了”就有效真正开始做指令微调时你会发现自己面对的不只是训练开销问题更多的是一连串“技术性选择”。我公开放一条自己整理过的指令微调操作路径很适合从零起步的人参考。准备语料。把业务数据和标注数据整理成统一的对话模板这个模板要和你推理时用的提示词格式严格一致。我见过太多人训练时用 A 格式部署时用 B 格式微调效果直接被格式差异吃掉一半。加入“通用能力保留”语料。纯业务数据微调很容易让模型开始“失忆”不再回答通用常识。建议混合 5%~10% 的通用指令数据。控制轮数。别盲目训练 5 个 epoch我常见的做法是从 1~2 个 epoch 起步观察损失曲线和实际评估分数。如果 eval loss 上升但 train loss 下降说明在过拟合需要增加数据量或正则。权重合并。使用 LoRA 这类参数高效微调时不要忽略合并权重这一步。有些推理框架直接加载 adapter 也能跑但容易在量化后出现精度损失建议把 adapter 合并回 base 之后再做量化和部署。构建微调前后对比报告。我每次微调都固定记录同一套评估集上的结果哪怕只有几个关键指标。这样可以回答一个核心问题“这次微调到底带来了什么改变”如果答不上来那你就是在做无效微调。我还想展开一个细节微调不是“内容注入”的工具。想象一下你想让模型知道公司内部的报销政策很多人自然想到“我直接把政策文档丢进去微调”。但这是错误的预期。微调主要作用于行为模式、输出格式和任务结构具体的事实性知识应该交给 RAG 或外部检索来补足。如果想把特定事实塞进微调权重你需要的是大量重复、覆盖不同表述的高质量样本不是三五条文档改写。3.3 RAG 还是微调别再纠结二选一了每次有人问这个问题我都会说工程系统里它们不是竞争关系而是互补关系。用一句话记住它们的边界RAG 是知识和变化。微调是风格和结构。具体到某个任务如果模型总是不知道最新的信息一定是 RAG 端有问题知识库没更新、检索召回没找到如果模型总是以错误格式回答、语气不对、行为不稳定大概率需要微调来解决。我搭这套 from scratch 项目时对 RAG 管线做了非常多细致的调整印象最深的是两个容易被忽视的点。第一检索阶段要对用户问题做“重写”不要拿用户原始语句直接去检索。用户经常说“帮我看看上个月那封邮件里提到的预算变更”直接检索“上个月”“那封邮件”这样的词会命中一堆无关内容。更好的做法是先用一个小模型或规则改写为“日期实体关键形容词”组合再去做向量检索。第二检索到的内容不是越多越好。过去我总觉得“多给模型点上下文总没坏处”实际测试发现当上下文中噪声比例升高时回答的幻觉率会明显上涨。把 top-k 从 8 降到 4天真的你以为会降低召回率但由于上下文更干净了最终回答准确率反而提升了。4. 推理服务与性能把模型压进真实流量之前必须做的事4.1 不仅仅是“部署上去”工程化到这一步很多人才真正体会到为什么标题里要强调 from scratch。部署一个模型 API 不是简单写几行 FastAPI、把模型 load 到显存里就完事。真实推理服务要处理并发排队、梯度优化、显存预算、容错恢复等问题。让我给一个显存估算的实操方式这个特别适合刚把模型跑到本地的人。假设你要部署一个 7B 模型权重精度是 FP16它的理论权重显存大约是 14GB按每参数 2 字节估算。如果跑推理时会引入 KV Cache它的占用和序列长度、批次大小直接相关。一个粗略计算方法是KV Cache 总显存 ≈ 2K 和 V 两套× Layers层数× Hidden Size隐藏维× 序列长度 × 批次大小 × 精度字节数。算完这笔账你就知道很多 OOM 不是模型权重太大而是 KV Cache 把你的预算吃光了。实践中的处理方式有几种上线前先用固定批次跑压力测试一次把序列长度拉到业务最大值记录峰值显存然后反推部署配置。如果不确定就先把 vLLM 这类带 PagedAttention 的推理框架组起来你会发现显存利用率瞬间高了很多。4.2 流式输出的工程陷阱对话类 AI 应用几乎都用流式输出就是回答内容一个字一个词蹦出来的效果。它体验好但工程复杂度高。很多人第一次做流式就遇到两种情况要么用户端看到内容卡住要么整个请求直接超时。我在实践中总结出三个避坑原则。第一超时设置必须区分“首字延迟”和“总耗时”。如果给流式请求设置一个固定 60 秒超时遇到长回答时会误杀正常请求。正确做法是关心首个 token 的时间控制在 1~2 秒内后面就不应该中断。第二流式请求最好打上 request_id用于追踪生成到哪一步。第三业务侧必须处理后端中途断流的问题比如客户端断了生成任务要立刻显存标记取消而不是傻傻继续把序列生成完白占资源。这些细节很多现成框架不会帮你自动解决。4.3 监控与可观测性应该看什么模型指标比基础运维指标难定义得多。CPU、内存、GPU 利用率这些是底层指标它们能反映服务是否健康充裕但无法反映模型有没有在犯傻。我推荐一套务实的模型观测方案也是 from scratch 项目中后期最值得投入的部分响应级指标首 token 延迟TTFT、生成吞吐tokens/s。单请求总时长、token 总数、最大输出长度。语义级指标输出不符合 JSON 格式的比例。回答长度分布——如果某一时刻突然所有回答都变极短很可能是模型被错误上下文带偏了。检测特定模式的异常回复比如“没有找到相关信息”这样的兜底句出现得太频繁。成本指标每天的 token 消耗量、按链路拆分成本检索、生成、重试。单次回答成本是否异常上升有助于发现是否有用户恶意灌超长文本。你可以用一个简单的 MySQL 或 ClickHouse 表来存这些内容不要动不动就上重型日志平台。工程化的核心是用最合适的工具解决当下问题from scratch 尤其要控制复杂度递增的节奏。5. 编排与泛化让 AI 组件之间学会协作5.1 工作流编排别一股脑塞给模型当你开始做多步骤任务比如“先检索内容再总结最后翻译成英文”很容易产生一种冲动把所有步骤塞进一个大提示词里让模型自己一把梭。demo 阶段确实能跑但工程化以后你会发现这种方式最大的问题是不可控——你无法定位是哪一步出错也无法单独优化某一个子任务。更好的做法是把工作流拆成多个小步骤每个步骤对应一个函数或一个小模型调用判断用户意图分类检索最相关文档RAG根据检索结果生成候选回答生成对回答做校验或格式化后处理。这种策略的好处在失败追踪时尤其突显。你明确知道如果回答质量变差到底是检索端没找到好资料还是生成端没用上资料又或者是后处理把内容截断了。我也因此更倾向于把系统做成“流水线 关键节点缓存”的结构。同一个问题、同一个检索结果不需要每次重复调用生成模型命中缓存时直接返回可以有效降低成本和延迟。5.2 让模型可以和工具交互近一年 ai-engineering 领域最火的方向之一就是让模型具备工具调用能力。从 langchain 时代开始大家就在尝试让模型自主决定调用外部工具、查数据库、执行代码。但工程化落地的时候冲动与冷静必须并存。我的经验是所有工具调用的边界要在代码里写死不要让模型随便穿。明确列出哪些工具是可用的、入参是什么、返回结构是什么。即便设置了这些约束仍然要在关键工具调用上加上“人工确认”选项或“强制规则校验”。举个例子如果模型自动触发了发送邮件的工具结果邮件内容里有用户信息汇总错误造成的后果可能是灾难性的所以邮件发送这类敏感工具必须加一道规则校验或二次确认。失败重试机制也很重要。工具调用不是每次都能成功网络抖动、参数解析失败、权限不足等异常都需要对应的错误分支。要么让模型换一种表达方式重新调用要么直接把错误反馈给用户绝对不能静默失败。我踩过这么一次坑给模型配了一个计算器工具入参要求是 JSON 格式的数字表达式但模型偶尔会传成一个英文句子工具端解析失败后直接返回空结果。这个问题从外部看就是“回答不完整”排查了很久才定位到工具解析层。6. 常见故障与排查技巧实录6.1 输出不稳定的类噪声问题AI 服务最大的工程痛点是输出不可枚举、不可穷尽。你的代码可以写出 1000 个测试用例但模型仍可能产生第 1001 种异常格式。因此处理这类问题我一般从“约束”入手而不是“期待”模型自我修正。尽量使用 JSON Mode / Structured Output 强制输出格式。在后处理里写一个严格的解析器解析失败就走重试分支重新调用一次模型。给模型设置明确的“回答不了就说不知道”的指令同时后端起检测器对疑似幻觉内容做标记。有一次做法律咨询类回答用户反复出现“你的回答自相矛盾”的反馈。排查之后发现问题出在检索上用户的问题被重写后同时命中了两个对立观点的文档片段。解决方式不是换更大的模型而是在生成前增加一步冲突校验。当检索结果里出现相互矛盾的结论时先做取舍或主动让模型告知用户“存在多种说法”这样用户体验和可信度瞬间提升。6.2 显存不变但服务变慢流量压力测试时你可能遇到一种诡异现象显存占用没有明显增长但延迟整体上升。这种问题十有八九不是模型变慢了而是排队堆积。推理框架在并发太高时会将新请求排到上一批生成结束。如果你用的是动态批处理或者连续批处理慢的请求会拖住同一批中的其他请求。排查方法很直接看队列深度和 TPOT每个 token 的生成耗时。如果 TPOT 正常但整体延迟上升说明在排队。如果 TPOT 异常上升说明 GPU 利用率已饱和或显存不足导致频繁换入换出。解决方案是加个入口限流或者在框架层开启更激进的抢占调度。6.3 数据更新了模型没反应很多 RAG 系统上线后会出现一个尴尬的情况业务方说知识库里已经更新了但对话系统还是回答老内容。这类问题几乎都是缓存或索引没刷新导致。数据更新要有明确的版本号与生效时间检索时要查“最新版本”且考虑近似缓存策略。如果你的向量库导入脚本没有幂等性——就是同一批次数据重复导入了多次那很可能检索结果里出现旧版本与新版本重复的内容。我的设计习惯是所有入库文档自带 updated_at 字段检索前查询向量库中是否有同源的旧向量并进行增量替换。这能避免数据重复时把评分平均掉保证模型每次拿到的是同一份源文档最新内容。6.4 常用问题速查表现象可能原因处理建议回答总是过于简短检索未命中、上下文信息太少检查向量检索返回数适当增大 top-k回答风格大变提示词被误改、模型版本被切换对比提示词历史、固定模型版本请求频繁超时首 token 延迟过高、生成内容过长打开流式输出、拆分生成长度格式经常出错JSON 解析失败、模型未受格式约束启用结构化输出、增加重试兜底成本突然飙升某用户输入了超长上下文、连续重试设置统一限流、限制单请求 token 上限新数据不生效缓存未刷新、索引未更新检查缓存策略与向量库增量同步逻辑7. 工具链选型我在这个项目里实际用了什么既然标题叫 from scratch工具链的选型也有它的逻辑优先选可替换、开源、模块化的组件避免任何一个平台锁定整个系统的设计。下面是我在这个项目中实际用下来并觉得值得推荐的组合。语言框架Python FastAPI。Python 是 AI 生态的绝对主力FastAPI 方便写异步接口和流式响应。其他工程栈如果端口多也可以用 Go 做代理层但内部核心处理和训练相关还是一律走 Python。推理服务vLLM 或 TGI。两者都支持 PagedAttention 和连续批处理能有效提升吞吐。同一个模型建议产线里固定一个框架避免两个框架对量化精度的采样行为不一致。数据存储MySQL/SQLite 存业务元数据向量库选一个主流开源方案即可。不要一开始就上分布式向量库先用单机方案跑通业务再考虑扩容。模型微调用 PEFT/LoRA 这一类参数高效微调方案跑起来只需要很小显存一台较好的消费级显卡也能承担。评估与监控评估脚本用 Python 写死一套可复用的指标线上日志用 JSON 格式输出方便后续采集和查询。我的一个建议是在做from scratch路线的初期不要太迷恋某个特定框架。框架的替换成本很高如果你把业务逻辑和框架绑定得过深后期每换一次工具都相当于重写一遍系统。逻辑尽量抽象成可插拔模块每个模块只要对外暴露标准接口就行输入是格式统一的 prompt 或者说 data输出是结构化的结果对象。8. 结尾把这个项目真正落地的几点体会项目做到最后我发现最有价值的产出并不是那几套跑得通的代码而是对整个 AI 系统生命周期的一种掌控感。过去我用封装好的平台遇到问题只能“到处猜”现在从零搭起了每个环节再怎么出问题我都能通过日志、评估集和链路追踪快速定位到具体环节。给想复刻这条路的人三句实在建议。第一不要急着上模型先把评估集做出来哪怕只有 100 条。没有尺子你后面所有的调整都是在盲目碰撞。第二任何新组件都要先跑通“最小闭环”——比如先做一个不检索文档、直接用模型回答的版本再一点点接入 RAG、记忆、工具调用等外部能力这样每一层新增变量都是可控的。第三把商业和成本意识内置进来别只在技术里打转——我见过太多项目因为单次调用成本超预算而被迫下线这种问题从零开始时就要计算清楚。这个标题所代表的“从零构建 AI 工程”路线不是要求你把所有基础组件都自己重写一遍而是要求你对每一层技术都有足够深的理解和选择权。保持这个原则项目再复杂你也能在不确定的技术浪潮里站稳脚跟。
返回列表