☰
从零构建AI工程:拆解数据管线、模型微调与推理服务全链路
2026/10/2 20:40:42 网站建设 项目流程

如果你最近在逛 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 阶段够用,在工程阶段几乎没有参考价值。

入手做评估集时,我会建议分成两个部分:一是核心固定集(回归集),二是对抗/边界集。核心固定集选大约 200~500 条能代表真实业务最常见的输入,每次任何环节发生变化——数据处理、提示词、模型切换、微调权重——都要跑一遍,防止性能“悄悄下滑”。对抗/边界集则是你故意挑的那些刁钻例子,比如用户的输入里包含恶意符号、超长文本、语义模糊的表达或者完全不在知识范围内的提问。

拿这个仓库标题所指向的“AI engineer”视角来说,评估集还有一个特殊使命:排优先级。实际工作中,评估发现的问题往往是几十个同时存在的,比如问答完整度不够、引用格式错误、中文指令下偶尔返回英文、空值回答太多。如果一股脑全部去改,精力就被稀释了。正确做法是每次只抓当前长板列表里最影响用户体验的 1~2 个问题,改完再重新跑全套评估。这种以评估驱动迭代的方式,才是工程化迭代的正确姿势,而不是“加了个高深的 RAG 组件,就以为系统整体变强了”。

3. 模型选型与微调落地:组合推理比单点性能更重要

3.1 不要用“模型越强越好”来掩盖架构缺失

在博客和热词里大家总在聊 ai-engineering,但在实际项目里,很多人做的不太像“工程”,更像“换模型大赛”。观察项目标题里的 from scratch 你就会明白:真正的工程,不是每次模型厂商发了新版就无缝切换,而是你的系统架构能对不同模型保持适配和可替换性。

现在选型主要面对三类选择:闭源大模型 API、开源全能模型、开源小参数模型(7B 级以下)。它们没有绝对优劣,只有适合不适合。闭源最大的便利是省心,但你的服务体验完全取决于对方平台的稳定性和策略;开源全能模型适合做企业私有化部署或需要深度定制的工作流;开源小参数模型适合高并发、低延迟、任务相对标准的场景(比如文本分类、实体抽取)。

我使用“三层选择法”来决定用什么模型,每次都很管用:

  1. 先根据任务复杂度判断最简方案。不需要任何推理,纯抽取或改写的任务,直接上小参数微调模型,速度快且可控。
  2. 需要一定程度理解与结构化输出的任务,考虑 7B~14B 级别的开源模型,搭好提示词与解析层。
  3. 开放式生成、复杂推理、多步工具调用的任务,才考虑更大的底座或闭源大模型。

很多人先把第 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 总显存 ≈ 2(K 和 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 工程”路线,不是要求你把所有基础组件都自己重写一遍,而是要求你对每一层技术都有足够深的理解和选择权。保持这个原则,项目再复杂,你也能在不确定的技术浪潮里站稳脚跟。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询