从零调教开源大模型:微调+RAG+推理部署的垂直场景落地全记录

📅 发布时间:2026/10/19 13:02:02
从零调教开源大模型:微调+RAG+推理部署的垂直场景落地全记录
这是《大模型学习记录》的第六篇。前五篇我把Transformer结构、Tokenizer、预训练、指令微调这些概念从理论上啃了一遍也跑了不少开源模型做推理实验但说实话心里一直有一个坎模型拿来聊天很顺可真要让它在某个具体领域里替我干活我总觉得隔着一层。这篇记录就是跨过那层坎的过程——从零开始把一个开源基座模型调成能回答垂直场景问题的问答助手。我验证的路线就是很多人纠结的那条微调、检索增强生成RAG、推理部署三者怎么配合落地。这篇适合谁看和我一样处在“模型能跑但不会用”的中间状态的人想在垂直场景快速搭一个问答/客服/资料检索工具的人。如果你还在纠结“该微调还是该上RAG”、“LoRA参数到底怎么设”、“为什么部署时老爆显存”那这篇里都有我踩过的真实答案。1. 这个阶段到底在学什么从“会聊”到“能干活”1.1 搭好的底子有什么用前几篇我啃完了不少概念注意力机制里QKV怎么算、tokenizer的词表怎么影响效果、预训练和指令微调的loss差异、量化是怎么回事。说实话当时学这些的时候有点泛觉得“我知道这回事”但离“我会用”还有距离。直到这次真正动手做落地项目我才发现这些底子全都能用上——看懂训练脚本里每一行的意义、判断loss曲线是否健康、定位显存爆炸的位置靠的都是之前积累的框架性认知。所以如果你想直接跳到这篇的操作步骤我建议至少先弄明白三件事Transformer的推理过程是逐token生成的所以有延迟和显存的讲究我们说的“微调”基本是在已有基座上做适配不是从头训练大模型本质是“概率接龙”它天然会编只是编得好坏的区别。这三件事不理解后面很多参数改了也不知道为什么。1.2 第六篇给自己定的三个目标这个阶段我不打算再铺新概念而是定了三个非常具体的验收标准第一让模型能回答某个垂直领域的问题而不是泛泛空谈。第二允许它说“不知道”但尽量不乱编答案把幻觉压到可控范围。第三回答延迟控制在可接受的范围至少不能让人等得不耐烦。这三个目标分别对应了三种技术手段领域数据微调解决“能回答”检索增强生成解决“不乱说”推理框架部署解决“快一点”。听起来不复杂但真正串起来的时候每一环都有不少细节坑。1.3 选一个具体的磨刀场景我给自己搭的练手项目是一个模拟的某制造企业设备检修知识库。场景很典型一堆设备操作手册、故障排查文档、维修记录散落在PDF和Word里工人查资料靠翻文件夹效率极低。希望做一个问答入口让维修工直接问“这台设备报错E302怎么处理”系统能够基于文档给出有依据的排查步骤。为什么选这个场景因为它同时覆盖了RAG的核心要素资料多、格式杂、答案必须可溯源而且对幻觉的容忍度极低——万一模型编出一个错误的检修步骤那是可能出事故的。这种场景下你才能逼自己把“可控性”做扎实而不是只把demo跑通就完事。2. 微调还是RAG先想清楚再动手2.1 两条路线的边界开始调模型之前我先花了两个晚上把“微调”和“RAG”的边界捋了一遍。我的判断标准很简单知识是变还是不变你要的是“风格”还是“事实”。对比维度微调Fine-tuning检索增强生成RAG知识更新更新成本高改知识要重训练换文档就行更新极快幻觉控制靠模型记忆容易张冠李戴答案有资料依据大幅降低编造句式风格能显著改变模型表达习惯基本保留基座模型本来风格数据需求高质量标注数据量大才有效只需要文档切块建索引不需要标注计算资源需要GPU训练成本不低只做检索推理训练成本为零适用场景调整输出格式、语气、领域术语事实性问答、文档密集的知识领域这张表做完结论已经很明显了我的设备检修场景知识基本都在文档里而且文档还会持续更新这恰好是RAG的强项。如果我只做微调等于让模型死记硬背几千页操作手册既慢又不准万一文档更新了还得重训。但如果我只做RAG、完全不做微调又会遇到另一个问题模型输出格式不可控你以为它在给答案它却在那儿长篇大论地发散。2.2 为什么我选了“RAG为主、微调为辅”最终方案是RAG提供事实依据微调负责教会模型“怎么把依据用起来”。这句话听起来轻巧实操里的分工其实很清晰。RAG侧的活儿是把几千页PDF拆成小段、转成向量、建索引用户提问后先把最相关的几个片段捞出来拼成上下文喂给模型。模型的任务就变成“基于给定资料回答问题”而不是凭空想。这就像给模型配了一个随时能翻的资料库它答完还会告诉你依据是从哪来的。微调侧的活儿则是用几百条精心构造的对话数据让模型学会输出格式。比如它必须先说结论再说步骤资料不足时必须说“根据现有资料无法确认”回答里要带上“资料来源文档X”这样的引用标记。这些事用提示词也能做个七八成但用微调去固定下来更稳定——实测下来提示词偶尔还是会“漏掉”引用标记微调后基本不会。2.3 整套方案需要准备的组件清单想清楚路线之后我列了一个组件清单接下来几周基本就是围绕这份清单逐个落地基座模型选了某开源7B对话模型中等规模24G显存的卡能跑效果也够用。LoRA微调脚本自己用训练框架写了配置脚本同时做了Q-LoRA降显存方案方便在单卡上跑。向量数据库本地部署了一个轻量向量库支持标量向量混合过滤不依赖外部服务。Embedding模型某个针对中文优化的本地向量模型维度1024保证文档切块后能转成高质量向量。重排序模型用来对召回的候选片段二次打分把真正有用的信息排到前面。推理服务部署一个支持连续批处理的推理框架对外暴露标准Chat协议接口方便应用对接。3. LoRA微调的实操记录3.1 训练数据怎么准备我先说说微调数据。我的做法不是去网上找通用指令数据而是完全基于自己的检修场景构造了600条对话对。数据量不大但对垂直场景LoRA微调来说已经够了——很多时候质量远比数量重要。数据格式用的是ChatML格式这是基座模型支持的对话结构。简单说就是每条数据分成三段system提示词、user提问、assistant回答。我给你看一条简化过的样例{ messages: [ {role: system, content: 你是设备检修助手。回答必须基于提供的资料不得编造。如果资料不足明确说无法确认。}, {role: user, content: 压缩机排气温度持续偏高可能是什么原因}, {role: assistant, content: 根据维护手册第3章排气温度偏高常见原因有1.冷却水流量不足检查冷却塔进出口阀门开度2.润滑油老化取样化验并按周期更换3.吸气过热度偏大调整膨胀阀开度。资料来源压缩机维护手册.docx。} ] }构造数据的时候有一条重要原则内容必须“有据可查”。我是从检修文档里挑出高频问题把文档里对应的段落改写成回答而不是让模型自己发挥。这样微调出来的模型它学的是“怎么组织答案、怎么引用来源”而不是学“编内容”。清洗数据时我踩过一个坑原始文档里有大量表格和特殊符号直接塞进训练样本会导致tokenizer输出一堆无意义字符loss还降不下去。后来统一做了处理表格转成文字列表、去重、删除明显乱码段落、统一标点符号为中文格式。这一步看似琐碎但对训练稳定性影响非常大。3.2 关键训练参数是怎么定下来的LoRA的核心概念不复杂冻结原模型权重在attention层旁边加一小部分可训练的低秩矩阵。需要调的核心参数就四个r秩、alpha缩放系数、学习率、batch大小。我这次用的配置如下按我的经验解释一下每个值的直觉lora_rank: 16 lora_alpha: 32 lora_dropout: 0.05 learning_rate: 2e-4 batch_size: 4 gradient_accumulation_steps: 8 max_seq_length: 1024 epochs: 3r取16意味着低秩矩阵的维度和原模型参数相比小得惊人但因为加在注意力投影层上依然能有效调整模型的输出行为。r太小比如4学不到东西r太大比如64又容易过拟合16是大多数LoRA实践里性价比很好的起点。alpha取32也就是alpha/r2这是一个经验上效果接近“正常微调”的倍率。如果你把alpha调成和r一样效果会打折扣。学习率我试过5e-5和2e-4前者收敛太慢3个epoch下来还没进入稳定状态后者是LoRA微调常用的区间实测loss曲线下降平稳。batch_size是受显存限制定的4配合梯度累积8等效于batch 32这个等效批量在600条数据上表现不错。max_seq_length定1024因为检修手册里的问答片段普遍不长太长了显存扛不住太短了会把答案截断。3.3 训练中怎么判断模型学没学会训练过程中我最关注两件事loss曲线和验证集上的定性表现。先说lossLoRA训练跑3个epochloss从2.x降到0.8左右是正常的。但千万注意loss降得越低不代表越好——我有一次跑了5个epoch训练loss降到0.3模型却开始“背诵”训练数据QA时只会原句复述换个说法提问就傻了。这就是过拟合。LoRA在垂直领域的过拟合往往不是产生幻觉而是死板。所以我的判断标准是训练集上loss下降是一回事更关键的是留出50条没训练过的真实问题每训练完一个epoch就跑一遍这些验证问题人工看回答质量。比如上面那个压缩机的问题我会变换说法再问一遍看它是否还能定位到冷却水、润滑油、膨胀阀这几个点。如果第2个epoch就基本稳定说明模型学得已经够了第3个epoch只是强化格式能力风险可控。还有一个小技巧训练时把system提示词和assistant回答的开头格式固定下来这样模型能牢牢记住“回答要以‘根据资料’开头”。这算是格式微调里性价比最高的一招。4. 知识库构建与检索链路4.1 文档切块chunk_size与overlap的取舍RAG这端第一个坑就是文档切分。原始检修手册里一段生产厂家写的技术文档可能有三四页要是整段塞进知识库检索时相关性会被大量无关内容稀释而且embedding模型对过长文本的语义捕捉也会变差。但如果切得太碎比如一句话一段又会丢失上下文导致模型拿到片段时不知道在讲哪个部件。我最后用的配置是chunk_size256token数量overlap32。这个数值不是拍脑袋定的我在测试集上跑过不同配比25632这个组合在“答案召回完整度”和“上下文连贯性”之间最平衡。overlap的作用特别重要——它相当于让相邻切片有公共部分这样如果答案恰好落在两个切片的边界上至少有一个切片能包含完整的答案。切完以后我犯过一个很低级的错误把标题和目录也当成知识块存进去了。结果是模型经常回答“详见第X章”然后给不出实质内容。处理办法是写了一个简单脚本把纯目录页、重复的页眉页脚、过于短的片段少于50字符全部过滤掉这类噪声文档对检索质量的影响比你想象中大得多。4.2 Embedding模型选型和落地Embedding是整个RAG链路的翻译官它把文本变成向量让计算机能算“语义距离”。我对embedding模型的要求有三条中文效果好、能完全本地部署、推理速度快。项目数据不能传到外部服务这是硬前提所以云端的embedding API直接排除。本地部署我测过几款最后选了某个针对中文优化的开源embedding模型输出维度1024。实测它对中文技术文档的语义理解明显比通用模型好比如“报错E302”和“高温报警代码302”这种表述不一致但语义接近的文本它能给出比较高的相似度分数。这个能力在工业检修场景里非常重要因为工人和工程师描述同一故障时用语差异特别大。存向量时我用的是某个支持向量检索的本地数据库结构很简单每条知识块存三个字段向量向量、原文文本、来源文件名。这样检索结果可以直接返回“原文出处”让模型回答时能引用来源。顺便说一下构建索引时一定要做多线程批量写入几千个文档逐个插入会慢到你怀疑人生。4.3 检索命不中怎么办重排序与混合检索第一次上线测试的时候我的检索命中率大概只有60%经常找不到正确答案所在的片段。单纯靠向量检索在小规模知识库里表现不够稳。后来我把检索链路升级成了“混合检索重排序”两段式第一步同时跑两个召回通道。通道一是BM25关键词检索擅长精确匹配“E302”这种设备编号通道二是向量语义检索擅长匹配“压缩机温度偏高”这种语义表达。各自取前20个候选合并去重。第二步用一个重排序模型对这40个候选逐条打分按相关性重新排序最终取前5条作为上下文送入大模型。这一套流程跑下来命中率从60%提到了85%以上。重排序模型非常关键它相当于在候选集里再筛一遍“到底哪些片段真正回答问题”。如果你对这套链路不熟我建议不要两头都从零写直接用成熟的检索框架搭再挂一个rerank模块性价比最高。另外有一个检索侧的小习惯问题进入检索前先做一次简单改写。比如用户问“E302怎么解决”我会先在代码里映射成“报错E302的处理方法”再检索。这个小操作能同时提升BM25的精确匹配成功率和向量检索的语义匹配质量。5. 推理部署阶段的各种坑5.1 显存估算与量化水位部署阶段的第一个问题是显存。7B模型FP16精度下光权重就占14GB加上KV cache和激活值24G显存的卡勉强能跑但余量很小。我先把显存估算方法说清楚配置方案模型权重大小推理额外显存实际建议FP16全精度约14GB8GB以上24GB卡勉强32GB卡舒适INT8量化约7GB6GB以上24GB卡流畅运行INT4量化约4.5GB5GB以上16GB卡也能跑质量有轻微损失质量有没有损失我拿测试集跑了对比INT4在多数领域问答上质量下降不明显但偶尔会出现逻辑不连贯的句子。考虑到检修场景对准确性要求极高我最后选了INT8量化保留更多信息量。这里有个容易踩的坑有些推理框架默认加载模型时不做任何显存优化你得显式开启KV cache量化、设置最大序列长度、限制并发请求数。尤其max sequence length如果你保持默认值65536显存连响都不响就直接爆OOM。我部署时把这几个参数统一配置好显存占用从峰值25GB降到了稳定16GB。5.2 推理框架部署与接口对接部署时我选了一个支持连续批处理的开源推理框架它能显著提升并发吞吐。连续批处理的意思是同一个GPU上多个请求的生成阶段可以交错进行而不是必须等一个完整生成完才能处理下一个。实测8个并发请求同时进来每个请求的首token延迟在500毫秒左右比暴力排队快得多。对外接口我是按标准Chat协议的HTTP接口暴露的这样应用侧对接成本极低——传到应用层只需要把用户问题拼成对话格式POST过去解析返回字段里的回答文本就行。上线测试时我顺手写了个脚本连续发了200个问题去压测发现只要并发数超过12延迟就会明显上升后来把服务层最大并发锁在8响应时间就稳定了。这个“并发上限”必须实测出来再写死不能靠猜。5.3 上线后关注的性能指标部署完不能只看“能回答就完事”我用三个指标评估了系统状态首token延迟TTFT衡量用户输入问题到收到第一个字的时间目标是控制在1秒内单token生成速度衡量整体回答速度这个取决于显卡算力和量化程度并发吞吐考验服务在多人同时使用时的表现。我最终线上稳定的数据是首token延迟0.6秒、生成速度每秒32个token、8并发无压力。对一个内部检修问答工具来说这个体验已经够流畅了。如果后续要支持更多并发方向很明确加一张卡做横向扩展或者把模型量化到INT4换吞吐但要在质量损失和性能之间再权衡一次。6. 这一个阶段踩过的坑清单6.1 训练侧问题排查训练阶段我遇到了三个典型问题整理成表格供你对照现象可能原因解决办法loss下降极慢或震荡学习率过大或数据噪声过多降低学习率到1e-4重新清洗训练数据loss降得很低但回答变傻过拟合训太多epoch或数据量太少减少epoch增加数据多样性生成结果总带无关符号数据清洗不彻底特殊字符干扰预处理阶段统一转中文标点、去HTML标记如果你照着我上一节的配置来跑大概率不会遇到这些坑但如果你改了大batch或加大r值记得保持观察loss曲线的习惯任何剧烈震荡都说明参数设置有问题。6.2 检索侧问题排查现象可能原因解决办法检索结果和问题毫无关联chunk切分过大语义被稀释把chunk_size降到256overlap保持32答案信息不完整答案被切块边界截断增加overlap到50或启动边界回溯设备编号检索不到向量模型对精确编号不敏感加入BM25关键词通道混合检索用户换种说法就问不中embedding模型领域匹配度不不够换成针对中文垂直领域优化的embedding模型6.3 部署侧问题排查现象可能原因解决办法启动直接OOM默认未开启KV cache优化开启量化、设置max_seq_len不要超过2048并发一高就超时单请求显存占用太高限制最大并发数或换成INT4量化回答速度特别慢热词匹配触发长上下文检查是否把无关注释也灌入了上下文偶尔返回空结果检索层拿到空片段增加兜底逻辑把问题重新检索一次说到部署还有一个小而致命的问题推理框架默认会加载整个模型词表如果你给基座模型加了自己训练的自定义token比如某些业务符号在推理阶段tokenizer不匹配会导致回答乱码。我调了一晚上才发现是这个问题最后把自定义token从微调配置里去掉让模型老老实实用原词表解决问题。这类坑文档里基本不会写但碰到了真的很折磨人。这一步系统完整跑下来我的感受是大模型落地并没有某个“神之一手”的步骤更多是把数据、微调、检索、部署各个环节都做到合格。微调和RAG不是对立关系它们解决的是不同层面的问题——一个管“怎么说话”一个管“知道什么”两者配合才能在垂直场景里形成可控、可用的闭环。我自己在实操中的体会是前期的数据准备和知识库构建要占到整个项目60%的精力训练和部署反而相对顺畅数据质量决定了效果上限模型能力只是把这份质量释放出来。希望这篇记录能给你降低一点试错成本如果你也卡在“模型能跑但不会用”这个阶段按照这条链路一步步走很快就能看到自己的垂直模型真正开始干活了。