"ai-engineering-from-scratch"这个标题,看起来像是一个GitHub仓库名,但它背后其实是所有打算跨进AI工程领域的人都要面对的一份路线图。我已经在这个行业里摸爬滚打了几年,带过的实习生一只手数不过来,他们中最常问我的问题不是"这个模型怎么调",而是"我到底应该先学什么"。AI工程不是一个单一技术,它是数据管理、模型开发、服务部署、性能优化、监控评估这一整套链条的集合。市面上讲单个环节的资料一抓一大把,但从零开始把整条链串起来、还能跟着动手复现的内容,确实少之又少。这篇文章就想充当那份"少之又少"的参考——我会从全貌讲起,再把每个关键环节拆开,最后给出可直接上手的实操路径。它适合刚入行的学生、想转岗的工程师,也适合那些已经在做算法、却总觉得工程端缺一块的同行。
1. 内容整体设计与思路拆解
1.1 为什么偏偏要"从零开始"
这个项目的名字里最关键的两个词,一个是"ai-engineering",一个是"from scratch"。前者定义领域,后者定义方法。我见过太多想入行的人,上车的方式是买一门"ChatGPT应用开发课",学完之后会调用大模型API,却完全不知道模型背后发生了什么;也见过另一类人,捧着《动手学深度学习》啃了大半年,推导都会,但让他把一个小模型部署成服务就抓瞎。问题出在哪儿?出在他们把AI工程当成了一条"线性路径",以为学完A自然就会B。实际上,AI工程是一个网络结构,数据、模型、服务、观测互相纠缠,任何一个环节的短板都会成为系统上限。
所以"from scratch"在这里不是"从数学基础开始"的意思,而是从"一个最小的可运行系统"开始。我提倡的做法是,第一周就搭出一个能跑通的端到端雏形,哪怕这个雏形只是用现成API做了一个带界面的问答机器人。先建立整体的肌肉记忆,然后再一层层往深处挖。这个思路和学外语很像:不可能等语法全学完才开始说话,一定是先开口说,在说的过程中补语法。AI工程也一样,先跑起来,再在跑的过程中理解每一环为什么存在。
1.2 AI工程的技术栈全景图
我在开头提到过,AI工程和传统后端工程的本质区别在于:传统后端处理的是确定性逻辑,AI工程处理的是概率性输出。这个区别不是文字游戏,它决定了你在技术选型和系统设计时的思路。按我的习惯,会把AI工程需要掌握的能力拆成四层,每一层对应一类核心问题。第一层是数据层,要回答"数据从哪来、质量如何保证、如何版本化管理";第二层是模型层,要回答"用哪个模型、要不要微调、怎么评估好坏";第三层是服务层,要回答"模型怎么封装成可用接口、延迟和成本怎么控制";第四层是观测层,要回答"线上表现如何监控、模型退化了怎么发现、如何迭代"。
这四层不是孤立的。举个最常见的例子:你在服务层做了缓存,结果命中缓存后返回的答案过时了,这看似是缓存问题,实际上是观测层缺失导致的——你没有对答案的时效性做校验。所以学AI工程,最重要的不是逐层精通,而是建立"跨层思维",遇到任何一个现象,都要能在四层之间来回排查。这个分层结构,就是整个学习路线图的骨架。后面所有章节,无论是讲数据清洗、模型微调还是服务部署,本质上都是在往这四层骨架里填充肌肉和血液,让系统从一个空壳变成一个活物。
2. 核心细节解析与实操要点
2.1 数据工程:第一个必须踩的坑
写数据工程这一节,我想先把一个反直觉的事实摆在前面:模型上线后出问题,十有八九不是模型的问题,而是数据的问题。我处理过不少线上事故,最后指向都是数据分布变化、标注错误或者数据泄漏。如果你只能记住一个知识点,我希望是:在AI工程里,数据质量决定了系统的上限,模型只是去逼近这个上限。
接下来按实操顺序讲三个重点。第一个是数据泄漏的排查。泄漏的形态很多,最隐蔽的一种是"时间穿越"。比如你做一个销售预测模型,训练数据里用了"是否A/B测试组"这个特征,但A/B测试的分配是在预测时刻之后才确定的,那么线上你根本拿不到这个特征——模型在训练时看到的是"未来信息"。检查泄漏有一个笨办法但很有效:把每个特征的产生时间列出来,和你预测目标的时间对比,凡是特征时间晚于预测时间的,全部剔除。
第二个是标签噪声的清洗。人工标注的准确率能做到90%上下已经很不错,剩下的10%噪声,对模型的影响比你想象中大得多。我常用的手段是"交叉验证清洗法":先用一个简单模型预测一遍,把预测结果和原始标签不一致的样本挑出来,交给标注员二次确认。这里有个细节要注意:做二次清洗时,尽量不要让同一批标注员处理自己标过的数据,换一批人或者让模型辅助筛选,否则"惯性标注"会让错误反复出现。
第三个是数据版本管理。我经历过一次惨痛教训:一个训练实验效果不错,但两周后想复现,却记不清当时用的是哪份数据。从那天起,我所有的训练实验都会强制记录三要素:代码commit id、数据版本id、模型配置hash。如果你只靠文件夹加日期命名,早晚会在某个深夜翻车。趁项目还小的时候,就把数据版本管理的习惯养起来,等数据量大了再回头补,成本至少翻十倍。
2.2 模型微调:不是只有炼丹
一提到微调,很多人的第一反应就是上GPU、跑训练、看loss曲线,好像这是一件只要砸算力就能搞定的事。但在AI工程视角下,微调的真正难点有两个:该不该微调,以及微调之后怎么不把模型搞坏。
关于该不该微调,我建议遵循"渐近路线":先用提示词工程试,加几个Few-shot示例,再上RAG,最后才动微调。这样做的理由有三个:成本、可控性、迭代速度。提示词和RAG每次改动都在分钟级,微调一次至少小时级,而且每微调一次,你对训练数据的依赖就多一分,排错难度也直线上升。我见过的情况是,很多任务用现成的基座模型加一点提示词工程就能解决,根本不需要微调,你花几千块训练成本,最后效果可能还不如改写两行Prompt。
关于怎么不把模型搞坏,核心是防灾难性遗忘。模型在特定任务上微调,等价于让它在某个方向上走得太远,结果其他方向的泛化能力被侵蚀。常用的对抗手段有三个:一是控制训练轮数,微调通常1到3个epoch就够了,不要反复跑;二是把学习率调小,让更新的步长变短,不至于偏离预训练参数太远;三是训练样本里混入一部分通用语料,保持模型的一般能力。这些手段没有哪个是银弹,要组合使用。另外,微调之前,先把评估集和评测指标定好,不要边训练边想怎么评估,那样很容易被训练集上的漂亮曲线带偏。
2.3 评估体系:如何定义"好"与"坏"
模型做出来了,怎么证明它好?这是AI工程里最微妙的问题。学术上常用BLEU、ROUGE、准确率这些指标,但这些指标和生产环境的"好"并不等价。举个例子,一个问答系统在离线测试集上准确率98%,但用户问的是测试集覆盖不到的边缘问题,系统全都答非所问,那这个98%就没有意义。所以我在工程实践中会把评估拆成两层。
第一层是模型指标,如准确率、召回率、幻觉率、上下文命中率。这些指标用来评估模型单独的能力。计算幻觉率时,要注意定义统一,我的做法是人工抽样检查,判断答案中的每个关键信息点是否都能在检索到的上下文中找到依据。第二层是业务指标,如用户采纳率、解决率、客服从工单量、成本节省额度。这些指标直接反映系统是否在真实场景中创造了价值。模型指标和业务指标之间往往不是线性关系——模型指标从90%涨到95%,业务指标可能只涨0.3%;但模型指标从80%跌到75%,业务指标可能就是断崖式下跌。所以在做优化时,不要盲目追求模型指标,要盯着业务侧的变化。
评估还有一个工程化的要求:可重复。我强烈建议把评估过程脚本化,每一次模型更新,都跑同一套评测集,产出同一格式的报告,然后归档对比。没有可重复的评估,就没有真正意义上的模型迭代。很多时候你训练了十个版本,如果评估脚本都不一样,你就无法判断哪个版本在真实场景中更可靠。
3. 实操过程与核心环节实现
3.1 从零搭一个RAG问答系统
选RAG作为第一个实战项目,是因为它足够完整,又足够接地气。一个完整的RAG系统会经过:数据处理、向量化、索引、召回、重排、生成、服务化、反馈收集,几乎把AI工程四层能力都覆盖到了。更关键的是,它对算力要求不高,一台普通开发机就能跑通,很适合"from scratch"这个定位。
我先说拆块。文档来了,你不能整篇扔给模型——检索的最小单元是块,块的质量直接影响检索质量。我的经验是,先按文档自带的结构(章节、小节)作为天然边界,如果某个章节还是太长,再按段落切,最后用token数做强行截断。我在操作时通常会把目标块长设定在300到500 token之间,重叠控制在50 token左右,这样既能保证语义完整,又不至于让上下文过于臃肿。块太大,检索精度会下降;块太小,上下文信息不完整,生成效果会变差,所以这个参数值得你花时间调。
然后是embedding模型的选型。不要只盯着排行榜,要看三个标准:一是在你的领域样本上的相似度排序是否合理,二是最大输入长度是否覆盖你的块长,三是向量维度是否匹配你的数据库索引方案。维度太高会抬高存储成本和检索延迟,维度太低可能装不下语义信息。在索引构建的时候,把文档的元信息(标题、章节路径、更新时间)一并存进去,方便后续做字段过滤。
检索这一步,我前面提过要混合召回。把BM25关键词召回和向量语义召回的结果做RRF融合排序。RRF的原理不复杂:对每条候选结果,给一个基于排名的分数,然后把两个列表的分数相加排序。这样做的好处在实际场景里很明显——向量召回擅长语义相似,但偶尔会把意思相近但无关的片段捞上来;关键词召回擅长精确命中,但对同义改写无能为力,两者互补。下面是一段简化的召回逻辑示意:
# 伪代码:混合检索主流程 from rank_bm25 import BM25Okapi from vector_db import VectorDB def hybrid_search(query, top_k=5): # 1. 关键词召回 bm25_scores = bm25_index.get_scores(tokenize(query)) keyword_ids = get_top_n(bm25_scores, n=10) # 2. 向量召回 query_vec = embed(query) vector_ids = vector_db.search(query_vec, n=10) # 3. RRF融合排序 merged = rrf_fuse(keyword_ids, vector_ids, k=60) return merged[:top_k]生成端的调校也值得花时间。除了在system prompt里写死"如果文档中没有依据就直接回答未找到相关信息"之外,我还会要求模型在输出时标注引用来源的编号,这样前端可以展示"答案来自文档第X节",既增加可信度,也方便追溯错误。格式上,建议用统一的JSON结构返回,方便下游解析。
最后是服务化接口设计。我建议至少暴露三个接口:上传文档、查询、反馈。查询接口应该支持流式输出,这样用户体验更好;上传接口要做好格式校验和异步索引,避免大文档阻塞主流程;反馈接口收集用户对答案的点赞/点踩,后续评估全靠它积累真实数据。这个闭环一旦建立起来,你的RAG系统就不只是一个demo,而是一个可以持续迭代的产品。
3.2 推理服务性能优化实录
模型推理服务化之后,性能优化是绕不开的一关。我遇到的第一个项目,7B模型在GPU上推理延迟1秒多,看上去能用,但一到业务高峰期,并发一上来,排队时间直接飙升到10秒以上,用户马上流失。所以后来我总结了一句话:推理优化的目标不是让单个请求变快,而是让系统在目标并发下依然稳定。这一节,我挑三个实际用到的优化手段讲清楚。
第一个是量化。量化就好比把一张高清照片从无损格式转成有损压缩格式,肉眼看着差不多,但文件小了很多。在模型推理里,常见的是INT8量化,把模型权重从FP16压缩到INT8,体积减少一半,推理速度提升两到三倍。但量化不是直接转就完事,如果你的模型对精度很敏感,一定要做校准——用一小批代表性数据跑一遍,观察量化误差,调整每个层的缩放因子。我见过有人直接量化后模型输出就开始胡言乱语,就是因为跳过了校准。
第二个是动态批处理。在线推理服务里,请求到达的时间是稀疏的,如果每个请求都单独占一次推理,GPU算力大部分时间在空转。动态批处理做的就是"攒一批再算":把短时间内到达的几个请求合并成一个batch,一次性推理。这么做要注意权衡:攒的时间太长,单个请求的延迟会变大;攒的时间太短,batch太小,吞吐提升不明显。这个攒批窗口通常需要压测来确定,我从50毫秒起调,逐步加大,找到一个吞吐和延迟的平衡点。
第三个是语义缓存。这是成本杀手锏。很多业务场景里,用户的问题高度重复,比如企业内部问答系统里"年假怎么休""报销流程是什么"这类问题占了很大比重。对这类问题,没必要每次都跑一次大模型。做法是,把query先向量化,在缓存里找相似度高的历史query,命中就直接返回上次的答案。我用这个手段,把重复性场景的调用成本直接砍掉一半以上,效果比任何模型优化都来得快。下面是我记录的一组实测数据,同一个模型、同一批测试负载,经过三层优化之后的效果对比:
| 优化手段 | 平均延迟 | 吞吐量(TPS) | 成本变化 |
|---|---|---|---|
| 未优化基线 | 580ms | 1.7 | 基准 |
| +INT8量化 | 210ms | 4.6 | 降约40% |
| +动态批处理 | 180ms | 9.8 | 降约50% |
| +语义缓存 | 95ms(命中时) | 12.5 | 降约60% |
需要强调的是,性能优化永远是"最后一公里"的事。在系统还没跑通之前,不要过早陷入这些优化细节。先把功能做对,再把性能做快,顺序反了会很容易卡在泥潭里。
4. 常见问题与排查技巧实录
4.1 模型上线后的十大典型故障
模型上线和代码上线完全不一样。代码上线,行为是确定的,出了bug可以靠日志定位;模型上线,行为是概率性的,出了错你甚至说不清它是"逻辑错了"还是"概率没抽中"。所以我多年来养成一个习惯:以"模型会出问题"为前提设计系统,而不是以"模型不会出错"为前提。我把频繁遇到的故障整理成一张速查表,你可以直接保存下来当排查手册:
| 现象 | 可能原因 | 排查步骤 | 解决方案 |
|---|---|---|---|
| 线上效果远低于离线测试 | 训练与线上数据分布不一致 | 对比特征分布、样本来源 | 校准线上/离线数据管道 |
| 答案偶尔完全离谱 | 模型幻觉 | 检查检索上下文是否充分 | 加强"不知道"约束,增加兜底 |
| 请求延迟突然升高 | 并发超预期或模型退化 | 看队列长度和GPU利用率 | 限流、扩容、动态批处理 |
| 缓存命中率极低 | 查询分布太分散 | 分析query聚类 | 调低阈值,改用语义缓存 |
| 输出格式不稳定 | Prompt被覆盖或few-shot漂移 | 对比历史输出结构 | 固定模板,加输出校验 |
| 新增数据后效果变差 | 新数据质量未校验 | 检查新增样本标签分布 | 上线前加数据质量门禁 |
| GPU显存溢出 | 序列长度不受控 | 统计输入长度分布 | 设置硬性最大长度 |
| 模型重复输出同一句话 | 解码参数设置不合理 | 检查repetition_penalty | 调高重复惩罚 |
| 小流量正常,大流量报警 | 容量规划不足 | 监控资源水位 | 弹性伸缩 |
| 观测无异常,用户投诉多 | 评估指标和业务目标脱节 | 检查指标定义 | 补充业务指标 |
这张表看着简单,但每一条背后都是真金白银的教训。我自己最常犯的是第一种:离线指标漂漂亮亮,上线就翻车。后来我把排查流程固定成三步:先看数据分布有没有变,再看特征管道有没有走样,最后才怀疑模型本身。这个顺序救了我很多次。
4.2 学习路线复盘与几个常见误区
除了技术故障,学习路线本身也有坑。我复盘自己从零到一的过程,发现有几类问题最具迷惑性。第一类是"只见树木不见森林"。刚入行的时候,我喜欢死磕一个点,比如花两个礼拜研究某个注意力机制的变体,却对自己的系统为什么延迟高一无所知。后来才明白,AI工程的能力是"T字形"的:先保证一横是广度,能看懂整条链路;再在某一个方向深挖一竖,建立真正的优势。不要一开始就挖井,先把地图看完。
第二类是"用学习代替实战"。看十篇教程不如亲手部署一个小模型。我见过有人书单拉了一长串,GitHub上的star数量比我还多,但问他"线上模型效果不好怎么排查",他答不出来。建议用项目来倒逼学习,每学一个知识点,就把它落进现有的实战项目里。第三类是"忽视软技能"。AI工程师不是纯技术人员,你往往要跟业务方沟通需求、跟产品经理对齐指标、跟运维配合容器化部署。如果总结能力不行、写文档敷衍、不爱做复盘,技术再强也会被卡在瓶颈上。我个人的习惯是,每个实验都写一页实验日志,记录目标、做法、结果、结论四个字段,短但有用。
5. 学习路径规划与工具选型建议
5.1 新人如何安排学习节奏
如果让我给一个完全没有经验的"从零开始"学习者画一条节奏线,我会这样安排。第一到两周,目标是把整个技术栈"看一遍"。不要深究细节,用现成的大模型API或者开源小模型,搭一个最简单的问答demo,让它能跑起来。这期间你会接触到环境安装、API调用、前后端联动,先建立"我能搞定"的信心。第三到四周,进入数据层。找一份有标签的数据集,做清洗、做切分、做版本管理,理解数据泄漏和标签噪声这两个大坑。这周的目标不是学算法,而是学会"折腾数据"。
第五到八周,进入模型层。试着用开源模型做一个领域微调,同时把评估集和评估脚本搭好。这里的关键是别只盯训练指标,要把"离线评估-线上表现"的对应关系搞清楚。第九到十二周,进入服务层和观测层。把自己的模型或API封装成服务,加上性能优化和日志监控,最后能跑出一个带页面的完整应用,并且能回答"某个指标去哪里看、系统变慢了怎么排查"这类问题。这个节奏的核心是每两到四周就有一个看得见的产出。如果某一周落后了,不要慌,把时间拉长,但始终要保住"端到端一个系统在运行"的底线。
5.2 关键工具与资源选择逻辑
工具的选择逻辑,比工具清单本身更重要。我的原则是:优先选被验证过的、生态好的、关键时候有人替你踩坑的工具;新兴的工具可以先观望,等它稳定一点再切。在数据层面,我推荐用Pandas做清洗是基础,但尽早引入数据版本管理工具。在模型层面,不要一上来就追最新模型,挑一个文档成熟、社区讨论多的开源模型,等你踩过几轮坑再横向比较。在服务层面,容器化部署和API框架是基本功,建议先学会用FastAPI这类轻量框架写服务,再考虑用专门的推理服务框架进行规模化部署。
关于学习资料,我的观点是:少看二手教程,多读一手文档和源码。教程可以帮助你建立全局观,但细节和真知都在官方文档、API参考和开源项目的PR讨论里。遇到问题,先查文档,再搜issue,最后才问大模型或社区,这个顺序能帮你养成独立解决问题的能力。工具不在多,在于你能真正地把它的原理讲清楚。比如推荐系统,很多人只是调用了函数,却不知道里面的索引结构为什么快,这样一旦遇到性能问题,就只能干瞪眼。
最后再分享一个我从复盘里沉淀下来的小习惯:无论项目大小,我都会写一份一页纸的实验日志,记录"目标—做法—结果—结论"四段。别小看这件事,它会在你同时跑着五六个实验、过了三周想不起来当时为什么选这个参数的时候,救命。AI工程这条路上,最大的浪费不是踩坑,而是在同一个坑里踩两次。