资讯动态

RAG与微调工程实战指南:从文档拆分到评测门禁

发布时间:2026/9/25 23:32:56 来源:尧图企业网站定制
简介《AI工程实战指南》是由斯坦福讲师Chip Huyen撰写的一本面向AI工程师与技术决策者的实战手册系统讲解基于基础模型构建AI应用的全流程涵盖提示工程、检索增强生成RAG、代理系统、微调与数据工程等核心技术并针对延迟、成本、幻觉等生产环境中的真实挑战给出可落地的解决方案。这份PDF资源共1个文件大小64.7MB目前已有2414人下载学习。书中结合大量真实案例与行业最佳实践详细阐述从模型选择、数据集处理、评估基准到高效部署的完整链路帮助读者掌握将生成式AI集成到产品中的具体方法。无论是希望快速验证原型的开发者还是负责企业级AI落地的技术决策者都能从这本兼顾理论框架与工程实操的指南中获益。1. AI工程实战指南从跑通到上线中间隔着三道墙把模型跑通 demo 不算 AI 工程把它稳定交付出去才是。我拆完这份《AI工程实战指南》后最想说的是RAG 和 Finetuning 在这里是工程工具不是概念名词。整本指南不教你从零预训练大模型而是把一条完整落地链路拆给你看——文档拆成多碎召回率最高、相似度阈值该拍多少、什么情况下微调纯属浪费算力、评测集怎么建才不会自己骗自己。适合两类人手上有跑通的原型、正卡在上线前那一步的后端或算法工程师以及准备进 RAG/微调项目、想提前摸清坑在哪的从业者。开篇先给一个反直觉的结论大部分 RAG 项目效果差根因不在模型选型而在文档拆分粒度和召回参数上。我们就从这条链路拆起。2. RAG 落地链路文档拆分与召回参数先搞懂这三个数字2.1 为什么拆分比模型选型更影响召回RAG 的召回质量取决于两个环节文档被切成了什么样子以及检索时用什么标准把片段捞出来。绝大多数团队第一反应是换个更强的 embedding 模型但模型窗口是固定的文档被切碎之后语义完整性已经被决定了。embedding 模型负责把文本编码成向量它不负责理解被拦腰截断的句子。打个比方召回就是在简历库里搜人。你把一份简历按每 200 字硬切成五段那“精通分布式系统”和“三年 Kafka 生产环境运维”可能被分到了两张纸上搜索“消息队列专家”时哪一段都匹配不完整。文档拆分就是这个切简历的动作它决定了后续所有环节的上限。我在实际项目里的经验是先花半天把拆分策略定下来比花一周换模型更划算。拆分策略的核心就三个数字——chunk_size、overlap、分隔符优先级。把这三个数字调明白召回命中率通常能涨 10 到 15 个百分点。2.2 拆分参数模板chunk_size、overlap、分隔符优先级先看一套可以直接抄走的拆分实现。这里用的是递归字符分割的思路也是目前 RAG 项目里最常见做法from langchain.text_splitter import RecursiveCharacterTextSplitter text_splitter RecursiveCharacterTextSplitter( chunk_size512, # 每个片段的目标字符数 chunk_overlap50, # 相邻片段重叠 50 字符 separators[\n\n, \n, 。, , , , , , ], # 按优先级从高到低段落 换行 句号 逗号 空格 ) chunks text_splitter.split_text(document) # 返回的是字符串列表每个元素就是一个待 embedding 的片段这段代码的逻辑很直白优先在段落边界切段落太长就在句号处切句号也没有才退到逗号。chunk_size512是经验值——大多数中文 embedding 模型对 512 字符以内的文本编码质量最稳定超过这个长度向量会开始稀释关键语义。chunk_overlap50大概占 chunk 的 10%它的作用是补偿切分带来的上下文断裂一个知识点恰好跨在两个片段边界时重叠部分能保证关键词至少完整出现在其中一个片段里。两个参数注意点。第一oversized chunk 的场景比如一段代码或一个超长表格要单独处理不要硬塞进 512 的模板里——代码按缩进切表格按行切。第二中文文本不要把separators里的中文标点去掉纯按空格切中文会得到一堆残句。在我的项目里逗号是最后一道防线它只用来兜底正常情况下不会走到那一步。2.3 召回侧top_k、相似度阈值以及 Rerank 的介入时机拆分搞定之后召回的参数同样不能拍脑袋。最常见的翻车方式是把top_k设成 3相似度阈值设成 0.7理由是“看起来合理”。但每个 embedding 模型产出的相似度分布完全不一样有的模型相似度普遍在 0.8 以上有的在 0.5 左右波动0.7 这个阈值对前者形同虚设对后者会把所有结果全部误杀。我一般会先跑一次纯检索把相似度分数分布打出来再看import chromadb collection client.get_or_create_collection(docs) results collection.query( query_texts[模型微调需要多少条数据], n_results10, # 先取 10 条观察分数分布 ) for doc, dist in zip(results[documents][0], results[distances][0]): similarity 1 - dist # chroma 默认返回距离转成相似度 print(f{similarity:.3f} {doc[:50]})跑完这一步你会看到真实分布。比如大部分相关片段的相似度在 0.55 到 0.65 之间不相关的在 0.4 以下那阈值就应该设在 0.5给边界情况留点余地。top_k我的习惯是先取 5然后看前几名里真正有用的占几个。如果前五名经常只有一条有用说明拆分或者 embedding 有问题而不是把 k 调大能解决的。如果前五名有三条以上有用但最终答案不准问题出在生成环节跟检索无关。另外一个关键判断要不要上 Rerank。当候选文档池超过 50 条或者业务场景要求高精度比如法律、医疗问答时我建议在向量召回后用 Rerank 模型对 top 50 候选重新排序。向量召回负责宽Rerank 负责准两者是配合关系不是替代关系。Rerank 的延迟通常在 20 到 50 毫秒对多数场景可接受。提示相似度阈值不是配一次就固定不变的。数据更新、embedding 模型升级都会改变分数分布每次调整后都要重跑一遍分数分布统计。3. Finetuning 边界什么时候不该微调以及一套参数模板3.1 判别框架加 prompt 和 RAG 能解决的先不微调微调是成本最高的优化手段也是最容易被滥用的手段。很多项目在 prompt 还没写好、检索还没调优时就急着上 LoRA结果训出来的模型既丢了通用能力又没解决实际问题。微调只应该解决三类问题输出格式不稳定、领域术语和表达风格固定不下来、模型对某些指令的遵循能力系统性偏差。我手头有一个训练好的判别框架用它来过滤微调需求症状优先方案何时才考虑微调答案内容不对调 RAG 召回召回已达标但生成仍乱答格式不稳定JSON、表格、固定话术强化 prompt 约束 输出解析兜底prompt 写满仍频繁格式出错领域术语用错在 RAG 文档里加术语表术语表已加生成仍不遵循需要模型“记住”用户长期偏好会话上下文缓存上下文太长成本不可接受这里的关键判断是微调解决的是模型的行为习惯不是知识量。知识缺失用 RAG 补行为不对才考虑微调。如果你发现模型总是漏掉文档里的关键数字那不是微调能解决的是检索没召回“关键数字”所在的那段文本。3.2 数据构造从真实日志里挖而不是让模型生成决定微调效果的不是数据的量是数据的来源。用大模型批量生成训练样本的做法短平快但样本分布和真实请求分布往往对不上——生成的数据太“标准”真实用户的问题千奇百怪。我现在的做法是从线上日志里捞失败样本。具体流程捞最近两周内模型回答质量被用户反馈或规则拦截标记为“差”的请求然后人工改写标准答案构造(指令, 正确答案)对。每个问题再配一个拒绝样本模型最容易答错的那个相似问题标注“这是错误输入不要按此回复”。拒绝样本的作用是告诉模型边界的形状比单纯堆正向样本更有效地压低幻觉。数据量上我的经验是 50 到 500 条高质量样本就能看到明显变化。少于 50 条模型学不到稳定的模式多于 500 条收益开始递减。你不需要十万条数据你需要的是五十条覆盖了所有典型错误模式的样本。3.3 参数模板LoRA 还是 QLoRArank、epoch、lr 怎么设底座模型参数量决定你用 LoRA 还是 QLoRA。7B 到 14B 的模型单卡 24G 显存就跑得动 LoRA70B 级别才需要 QLoRA 做 4bit 量化。在小模型上用 QLoRA省下的显存换不来质量提升反而因为量化误差让训练更不稳定。这是我踩过的坑之一。下面是一套验证过多次的训练参数模板from transformers import AutoModelForCausalLM, AutoTokenizer from peft import LoraConfig, get_peft_model model AutoModelForCausalLM.from_pretrained(Qwen/Qwen2.5-7B-Instruct) tokenizer AutoTokenizer.from_pretrained(Qwen/Qwen2.5-7B-Instruct) lora_config LoraConfig( r32, # rank低秩矩阵维度不是越大越好 lora_alpha64, # 缩放系数一般取 r 的 2 倍 target_modules[q_proj, k_proj, v_proj, o_proj], lora_dropout0.05, # 防止过拟合0.05 或 0.1 够用 biasnone, task_typeCAUSAL_LM, ) model get_peft_model(model, lora_config) model.print_trainable_parameters() # 输出示例trainable params: 33.5M || all params: 7.6B # 可训练参数只占 0.44%这就是 LoRA 省资源的根本原因参数怎么理解r32控制低秩矩阵的秩秩越大模型能学到的模式越复杂但也越容易过拟合。7B 模型上 r16 到 32 是甜点区超过 64 基本就是在赌。lora_alpha64是缩放系数它和 r 的比值影响最终权重更新的强度习惯上设成 r 的两倍效果最稳。target_modules只改注意力层的四个投影矩阵这是 LoRA 论文验证过的最有效组合不需要额外动 FFN 层。训练参数上epochs固定 1 到 2。微调不是预训练数据量小跑的轮数多了必过拟合。学习率从2e-4起步如果 loss 震荡就降到1e-4。优化器用 AdamWweight_decay设 0.01。关键看验证集 loss如果 loss 在下降但验证集准确率不再提升立即停止那就是过拟合的起点。from transformers import Trainer, TrainingArguments training_args TrainingArguments( output_dir./lora-out, num_train_epochs2, # 1~2 轮不要多 per_device_train_batch_size4, learning_rate2e-4, # 震荡就降到 1e-4 weight_decay0.01, logging_steps10, save_strategyepoch, fp16True, # 显存紧张时开启 ) trainer Trainer( modelmodel, argstraining_args, train_datasettrain_dataset, eval_dataseteval_dataset, # 微调也要留验证集不能省 ) trainer.train()注意训练完的 LoRA 权重只是“补丁”评估时必须合并回底座模型或者用适配器加载不要单独拿 LoRA 权重做推理。合并后的模型要跑一遍通用能力回归——微调最常见的副作用是领域能力涨了、通用能力掉了。4. 混合架构取舍RAG 与微调的先后级以及评测指标怎么定4.1 路由设计让知识问答走 RAG指令遵循走微调当你既做了 RAG 又做了微调下一个问题是谁先谁后。答案是不要试图让一个模型同时干两件事而是用一个路由层把请求分到不同处理链路。知识型问题走 RAG行为型问题走微调后的模型两边各司其职系统的整体行为才可预测。路由可以用简单规则请求里包含明确的实体、产品名、文档关键词时走 RAG请求是“把这段文字改成表格”“用更正式的语气重写”这类指令操作时走微调模型。更松一点的方案是用 LLM 做分类输出一个标签决定路由去向。规则方案的优点是零延迟零成本缺点是边界情况会露馅LLM 方案更灵活但多一次调用多一百毫秒延迟。我一般先用规则顶住 80% 的流量剩余边界情况再升级 LLM 路由。请求特征路由去向原因含具体产品名/术语RAG需要检索最新文档“总结/改写/格式化”微调模型行为指令不需要知识模糊开放式提问RAG 微调模型双路双路结果再融合多轮对话中的指代先解析指代再按上述规则指代不清会污染检索这里一个容易被忽略的点双路融合不是简单拼接。如果 RAG 返回了一条带引用来源的答案微调模型返回了一段流畅但无出处的回答优先采纳 RAG 的结果——因为可溯源在当前阶段比流畅度重要。这不是模型能力的问题是业务风险的问题。4.2 端到端评测召回命中率、幻觉率、淘汰标准没有评测指标的工程优化都是自嗨。RAG 和微调上线之前必须定义一组可量化的指标并且设定淘汰线。我的评测指标只有三个召回命中率、答案准确率、幻觉率外加一个延迟上限。指标定义淘汰线召回命中率标准答案的关键信息是否出现在检索结果中低于 80% 不许上线答案准确率生成答案与标准答案语义一致的占比低于 85% 回滚幻觉率答案包含检索结果之外的关键信息占比高于 5% 必须修P95 延迟请求处理耗时超过 3 秒降级方案幻觉率的测量需要一个技巧把生成答案里的关键实体和检索来源文本做集合比对凡是出现在答案里但不在检索来源里的实体标记为疑似幻觉。这种自动化检测办法不完美但能抓出大部分问题。我见过太多项目只盯准确率不看幻觉率结果准确率 90%一问细节全是编的。4.3 迭代节奏先锁检索再调生成最后动模型真正上手 AI 工程实践的人都会发现迭代顺序比迭代速度重要。调优的顺序必须是先锁检索再做生成——因为生成模型的行为依赖输入质量如果检索结果东一块西一块生成模型再怎么调都是无米之炊。我通常这样定节奏第一周只调拆分参数和阈值不动生成侧的任何东西目标是召回命中率达到 85%。第二周固定检索参数开始调 prompt 和输出解析逻辑目标是准确率达到 80%。第三周如果准确率还上不去才考虑微调。这个顺序有两层逻辑每一层的问题都要在下层先排除嫌疑另外永远不要在模型上找 prompt 就能解决的毛病。有同行把工程方法比作写小说的方法论——先搭骨架、再填血肉、最后反复打磨章节。AI 工程的骨架就是这条迭代顺序血肉是各个参数细节。骨架歪了后续所有调整都是在给歪楼加固。5. 工程避坑记录六个常见错误与排查顺序5.1 小 chunk 导致上下文割裂现象召回命中率不低但生成答案前言不搭后语引用来源时断时续。核查检索结果发现命中的片段经常是半句话。原因chunk_size 设得小比如 128 字符句子被从中间切断关键词虽然被向量模型捕获了但上下文不完整生成模型只能靠猜补全。解决把 chunk_size 提到 400 以上overlap 保持在 50 左右同时把句号纳入分隔符优先级。改完之后同一问题的召回结果从“残句”变成“完整段落”答案质量立即改善。5.2 元数据在分块时被丢弃现象检索命中了正确的片段但生成模型答不出文档标题、所属章节和日期。追问细节时模型完全不知道信息来源。原因分块时只保留了纯文本内容标题、页码、文档路径这类元数据没有跟着 chunk 走。生成模型接收到的上下文里没有来源信息自然无法引用。解决分块时把元数据存进每个 chunk 的metadata字段检索返回时把metadata一并传给生成模型。同时在 embedding 时把标题拼进 chunk 文本——标题通常包含整段内容的核心主题对检索匹配也有帮助。5.3 固定相似度阈值导致结果倒挂现象某次更新知识库后检索结果开始出现大量明显不相关的文档而真正相关的文档反而排到后面。原因新文档和旧文档的风格差异导致向量分布偏移原来 0.7 的阈值在新分布下把一半的相关片段都拦截掉了剩下的都是“平均分高”但实际不相关的文本。解决不要设硬阈值改用“相对排名 动态阈值”策略——先取 top 20计算第 5 名和第 20 名之间的分数落差落差超过某个幅度才截断。每次更新知识库后必须重跑一次分数分布统计。5.4 LoRA rank 设得太大训练没结束就过拟合现象训练 loss 正常下降但验证集准确率在第 1 轮中途就开始回落模型输出变得机械重复。原因rank 从 64 一路加到 128可训练参数量翻倍小数据集上模型快速记住了训练样本的特征泛化能力提前崩塌。解决rank 回到 32同时把lora_dropout从 0.05 提到 0.1增加正则化。经验法则可训练参数占比超过 1% 时几乎必然过拟合。5.5 评测集“熵增”测来测去都是同一批问题现象连续两周评测指标都在涨但上线后用户反馈问题依旧和评测结论完全相反。原因评测集从立项起就没换过题模型和 prompt 都在向这 100 道题“过拟合”——不是变强了是变得擅长回答这 100 道题。解决评测集采用滚动更新制度。每两周淘汰 20% 的旧题补充 20% 来自最近真实日志的新题。固定题目占比不超过 60%剩下的必须来自线上流量。5.6 没有日志埋点故障定位全靠猜现象线上报告答案质量下降但既查不出耗时在哪个环节也判断不了是召回问题还是生成问题。原因RAG 链路没有分环节埋点检索耗时、召回来源、相似度分数、模型生成耗时全部混在一起。解决每个请求打印结构化日志query 原文、命中文档 ID 和相似度、生成模型名称和参数版本。出了问题先看日志里“哪一环的耗时异常变大哪一环的相似度分布偏移”比瞎猜快十倍。6. 验证闭环把评测指标做成发布前的一键检查前面定义了指标和避坑但它们只有落到自动化流程里才算真正生效。我的做法是把评测做成一键脚本每次改动——无论是拆分参数、prompt 还是微调权重——先跑评测再决定是否合并整个过程压到十分钟以内。脚本的逻辑很简单固定一个包含 100 条问题的评测集其中 60 条是历史核心问题40 条是最近两周从真实日志里抽的新问题。跑一遍全链路输出一份 JSON 报告import json # 评测完成后汇总指标 report { recall_hit_rate: 0.87, # 召回命中率目标 0.85 answer_accuracy: 0.82, # 答案准确率目标 0.80 hallucination_rate: 0.03, # 幻觉率目标 0.05 p95_latency_ms: 2100, # 耗时目标 3000 } # 门禁判断任何一项不达标就标记失败 gate { recall_hit_rate: (0.85, ), answer_accuracy: (0.80, ), hallucination_rate: (0.05, ), p95_latency_ms: (3000, ), } failed [] for key, (limit, op) in gate.items(): value report[key] passed value limit if op else value limit if not passed: failed.append(f{key}{value:.3f}, 期望 {op} {limit}) if failed: print(FAIL:, ; .join(failed)) else: print(PASS: 可以进入发布流程)代码里最值得留意的是淘汰线的设置——它是分阶段的。项目初期我把答案准确率达标线放在 0.80跑到稳定期后提到 0.85幅度不要一次跨太大。幻觉率的淘汰线始终是 0.05 不动因为这个指标和业务风险绑定不能妥协。我吃了第二次评测集不更新的亏之后强制自己每次调参前都写一行备注这次改了什么、目标是什么、预期影响哪个指标。每次改动只动一个变量跑完评测对比前后两条 JSON哪个指标变了、变化符不符合预期一目了然。从那以后我每次拉起一条新的基线都强制走一遍全套评测加门禁脚本不通过不许进下一步。这套流程不高级但它是整个工程里最低成本、最高杠杆的一环。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑