<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>RLHF on 一知半解</title>
        <link>https://oi8io.cn/tags/rlhf/</link>
        <description>Recent content in RLHF on 一知半解</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh</language>
        <copyright>CC BY-NC-SA 4.0</copyright>
        <lastBuildDate>Thu, 29 Oct 2026 23:00:00 +0800</lastBuildDate><atom:link href="https://oi8io.cn/tags/rlhf/index.xml" rel="self" type="application/rss+xml" /><item>
            <title>大模型微调原理与实战：SFT、LoRA、DPO、GRPO 与 Text-to-SQL</title>
            <link>https://oi8io.cn/ai/llm-fine-tuning-guide/</link>
            <pubDate>Thu, 29 Oct 2026 23:00:00 +0800</pubDate>
            <guid>https://oi8io.cn/ai/llm-fine-tuning-guide/</guid>
            <description>&lt;img src=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/cover.jpg&#34; alt=&#34;Featured image of post 大模型微调原理与实战：SFT、LoRA、DPO、GRPO 与 Text-to-SQL&#34; /&gt;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;开源大模型越来越强，可一放进你公司的业务里，它就像个刚入职的高材生：什么都懂一点，偏偏不懂“我们这儿的规矩”。微调，就是给它上的那堂岗前培训。&lt;/p&gt;&#xA;&lt;p&gt;这篇文章沿着一条主线，把微调从“为什么需要它”一路讲到“亲手训一个、部署成接口”。读完你应该能回答三个问题：&lt;strong&gt;什么时候该微调？微调到底改了模型的什么？LoRA、DPO、GRPO 这些名词，各自解决的是哪一个问题？&lt;/strong&gt;&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;写在前面&#34;&gt;写在前面&#xA;&lt;/h2&gt;&lt;p&gt;全文用一个例子贯穿始终：&lt;strong&gt;Text-to-SQL&lt;/strong&gt;，让模型把业务人员的一句话翻译成能直接执行的 SQL。选它是因为对错能自动判断（SQL 跑一下，结果对就是对），而且基座模型会犯很具体的错：它 SQL 写得很溜，却不知道你们公司“销售额”怎么算、金额存的是分还是元、取消的订单算不算。这些“业务口径”，正是微调要教的东西。&lt;/p&gt;&#xA;&lt;p&gt;阅读本文只需要会写 Python、会用命令行。损失函数、梯度、矩阵的秩这些概念，用到的时候都会从头讲。&lt;/p&gt;&#xA;&lt;p&gt;全文分四个部分：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;第一部分：大模型的训练流程与微调的定位。&lt;/strong&gt; 模型是怎么一步步练成的，微调在其中的哪个位置，以及什么情况下真的需要微调。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;第二部分：监督微调（SFT）与参数高效微调（PEFT）。&lt;/strong&gt; SFT 在数学上到底在做什么，为什么全参数微调那么贵，LoRA 的原理和超参数怎么选。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;第三部分：偏好对齐与强化学习。&lt;/strong&gt; 从 RLHF 讲到 PPO、DPO、GRPO，以及“SFT 记忆、RL 泛化”这句话该怎么理解。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;第四部分：Text-to-SQL 模型微调实战。&lt;/strong&gt; 用 LLaMA-Factory 和手写 PEFT 代码两条路线，把 Qwen2.5-Coder-1.5B 微调成一个懂业务口径的 Text-to-SQL 模型，评测、合并，再用 FastAPI 部署成接口。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;1-大模型的训练流程与微调的定位&#34;&gt;1 大模型的训练流程与微调的定位&#xA;&lt;/h2&gt;&lt;h3 id=&#34;11-预训练目标下一个词预测与交叉熵损失&#34;&gt;1.1 预训练目标：下一个词预测与交叉熵损失&#xA;&lt;/h3&gt;&lt;p&gt;要理解微调在改什么，得先知道模型最初是怎么被训练出来的。&lt;/p&gt;&#xA;&lt;p&gt;今天所有的大语言模型，不管是 GPT、Claude，还是 Qwen、DeepSeek，底层都建立在一个简单到出人意料的任务上：&lt;strong&gt;预测下一个词&lt;/strong&gt;（Next Token Prediction）。&lt;/p&gt;&#xA;&lt;p&gt;给模型看一段文字的前半截，让它猜下一个 token 是什么（token 可以先理解成“词”或者“字”，模型处理文字的最小单位）。比如输入“中国的首都是”，模型要给出词表里&lt;strong&gt;每一个&lt;/strong&gt;词出现在下一个位置的概率。理想情况下，“北京”的概率应该很高。&lt;/p&gt;&#xA;&lt;p&gt;模型猜完，我们拿它的预测和真实答案比一比，算出一个叫&lt;strong&gt;损失&lt;/strong&gt;（loss）的数，表示“猜得有多离谱”。最常用的损失叫&lt;strong&gt;交叉熵&lt;/strong&gt;，公式只有一行：&lt;/p&gt;&#xA;$$&#xA;\mathcal{L} = -\log p_\theta(\text{正确答案})&#xA;$$&lt;p&gt;$p_\theta$ 表示参数为 $\theta$ 的模型给出的概率，$\theta$ 就是模型里那几十亿个数字。这个公式的意思很直白：&lt;strong&gt;正确答案的概率越高，损失越小&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;训练的本质：调整下一个词的概率分布&#34; class=&#34;gallery-image&#34; data-flex-basis=&#34;461px&#34; data-flex-grow=&#34;192&#34; height=&#34;1040&#34; loading=&#34;lazy&#34; sizes=&#34;(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px&#34; src=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/01-next-token.png&#34; srcset=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/01-next-token_hu_78b3496ca441ac11.png 800w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/01-next-token_hu_ff119c85667c30ce.png 1600w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/01-next-token.png 2000w&#34; width=&#34;2000&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;举个数字例子。训练刚开始时，模型对“北京”只给了 0.10 的概率，损失是 $-\log 0.10 \approx 2.30$；训练一段时间后，“北京”的概率涨到 0.82，损失降到 $-\log 0.82 \approx 0.20$。&lt;/p&gt;&#xA;&lt;p&gt;那么怎么让概率涨上去？靠的是&lt;strong&gt;梯度下降&lt;/strong&gt;。损失是参数的函数，对每个参数求导，就知道“这个参数往哪个方向动一点，损失会变小”，这个导数叫&lt;strong&gt;梯度&lt;/strong&gt;。然后让每个参数都沿着让损失变小的方向挪一小步：&lt;/p&gt;&#xA;$$&#xA;\theta \leftarrow \theta - \eta \cdot \nabla_\theta \mathcal{L}&#xA;$$&lt;p&gt;$\nabla_\theta \mathcal{L}$ 就是梯度，$\eta$ 是&lt;strong&gt;学习率&lt;/strong&gt;，决定每一步迈多大。求梯度的过程叫&lt;strong&gt;反向传播&lt;/strong&gt;，由 PyTorch 这类框架自动完成，我们不用手算。&lt;/p&gt;&#xA;&lt;p&gt;在几万亿个 token 上反复做这件事，模型被逼着学会了语法、事实、逻辑甚至一些推理能力，因为想在各种语境里都猜对下一个词，没有捷径可走，只能真正“消化”文本里的规律。&lt;/p&gt;&#xA;&lt;p&gt;这里有一个贯穿全文的关键认知，请先记住：&lt;strong&gt;模型的输出本质上是一个概率分布，所谓“训练”，归根结底就是调整这个分布。&lt;/strong&gt; 后面要讲的预训练、SFT、RLHF，区别只在于“想让哪些输出的概率变高”，以及“用什么信号来告诉模型哪些输出是好的”。&lt;/p&gt;&#xA;&lt;h3 id=&#34;12-大模型训练的四个阶段预训练mid-trainingsft强化学习&#34;&gt;1.2 大模型训练的四个阶段：预训练、Mid-training、SFT、强化学习&#xA;&lt;/h3&gt;&lt;p&gt;只做完“预测下一个词”的模型，叫&lt;strong&gt;基座模型&lt;/strong&gt;（Base Model）。它很博学，却不太好用：你问它一个问题，它可能接着写出三个新问题，因为在互联网文本里，一个问题后面经常跟着的就是另一个问题。它还没学会“被提问时应该回答”这个规矩。&lt;/p&gt;&#xA;&lt;p&gt;从基座模型到我们日常用的聊天助手，中间还要经过几个阶段。现代大模型的能力开发，通常可以拆成四个环节：&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;一个大模型是怎么一步步练成的&#34; class=&#34;gallery-image&#34; data-flex-basis=&#34;461px&#34; data-flex-grow=&#34;192&#34; height=&#34;1040&#34; loading=&#34;lazy&#34; sizes=&#34;(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px&#34; src=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/02-pipeline.png&#34; srcset=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/02-pipeline_hu_8970e8cac6e28de5.png 800w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/02-pipeline_hu_70caf22fd40f934c.png 1600w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/02-pipeline.png 2000w&#34; width=&#34;2000&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;第一步，预训练（Pre-training）&lt;/strong&gt;：在海量互联网文本上预测下一个词。好比一个人读完了整座图书馆，博学，但还不会好好答题。这一步是能力的地基，也是最贵的一步，动辄几百万到几千万美元，普通团队不会碰。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;第二步，Mid-training（中期训练，也叫继续预训练）&lt;/strong&gt;：从一个已有的基座模型出发，在特定领域的文本上继续做预测下一个词。比如一个主要用英文训练的模型几乎看不懂韩语，就可以拿大量韩语语料再训一轮；一家医疗公司想让模型真正“懂医学”，也可以拿大量医学文献来训。好比让一个通才集中研修几本专业教材。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;第三步，SFT（Supervised Fine-Tuning，监督微调）&lt;/strong&gt;：用几百到几万条人工准备好的“问题 → 标准回答”示范，教模型“该用什么格式、什么风格、什么流程来回答”。好比老师给学生示范标准答案怎么写。这一步把一个“有知识”的模型，变成一个“听得懂指令、输出规整”的助手。它便宜、快、稳，几乎所有上线的模型都会经过这一步。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;第四步，RL（Reinforcement Learning，强化学习）&lt;/strong&gt;：不再给标准答案让模型照抄，而是让模型自己去答，答得好就奖励，答得差就惩罚，把好回答的概率调高、差回答的概率调低。好比让学生自己下场做题、按得分复盘。用人类的偏好当奖励，就是大家常听到的 &lt;strong&gt;RLHF&lt;/strong&gt;（基于人类反馈的强化学习）。&lt;/p&gt;&#xA;&lt;p&gt;前两步决定模型“知道什么、会什么”，后两步决定模型“怎么用它知道的东西”。后两步合起来叫&lt;strong&gt;后训练&lt;/strong&gt;（Post-training）。&lt;/p&gt;&#xA;&lt;h3 id=&#34;13-微调的定义与分类&#34;&gt;1.3 微调的定义与分类&#xA;&lt;/h3&gt;&lt;p&gt;“微调”（Fine-tuning）是一个用得很宽泛的词，不同人说的可能不是一回事。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;广义上&lt;/strong&gt;，凡是在预训练好的模型上继续训练、改动它的参数，都可以叫微调。Mid-training、SFT、RLHF 都算。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;狭义上&lt;/strong&gt;，大多数人说“我要微调一个模型”，指的是 &lt;strong&gt;SFT&lt;/strong&gt;，而且通常是用 LoRA 这类省资源的方法做的 SFT。本文第四部分要做的，就是这个意思。&lt;/p&gt;&#xA;&lt;p&gt;你可能还听过“无监督微调”“自监督微调”的说法。其实上面说的 Mid-training 就是一种自监督微调：它不需要人工标注答案，文本本身就是答案（下一个词就是标签）。为了避免混淆，本文后面统一这样用词：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;&#x9;&lt;thead&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;本文的说法&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;改参数吗&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;用什么数据&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;典型用途&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&lt;/thead&gt;&#xA;&#x9;&lt;tbody&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;Mid-training / 继续预训练&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;改&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;大量领域原始文本，无需标注&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;补领域知识、补语言能力&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;SFT / 监督微调&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;改&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;几百到几万条“输入 → 输出”示范&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;教格式、风格、流程、口径&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;偏好对齐 / 强化学习&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;改&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;好坏回答对，或一个能打分的奖励&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;让回答“更好”，而不只是“像示范”&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;参数高效微调（PEFT，如 LoRA）&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;只改一小部分&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;同上&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;一种&lt;strong&gt;省资源的实现方式&lt;/strong&gt;，可以和上面任意一种结合&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;最后一行要特别注意：&lt;strong&gt;LoRA 不是和 SFT、RLHF 并列的另一种“微调类型”，而是一种实现手段。&lt;/strong&gt; 你可以用 LoRA 做 SFT，也可以用 LoRA 做 DPO 或 GRPO。常说的“全参数微调”和“部分参数微调”，区分的就是这个维度。&lt;/p&gt;&#xA;&lt;h3 id=&#34;14-提示工程rag-与微调的选型&#34;&gt;1.4 提示工程、RAG 与微调的选型&#xA;&lt;/h3&gt;&lt;p&gt;微调要准备数据、要租 GPU、要反复调参，出了问题还不好排查。所以在动手之前，最值得花时间的一件事，是判断你的问题&lt;strong&gt;到底需不需要改模型参数&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;想让模型“更懂你的业务”，一共有三条路：&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;让模型更懂你的业务有三条路&#34; class=&#34;gallery-image&#34; data-flex-basis=&#34;510px&#34; data-flex-grow=&#34;212&#34; height=&#34;940&#34; loading=&#34;lazy&#34; sizes=&#34;(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px&#34; src=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/03-three-ways.png&#34; srcset=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/03-three-ways_hu_a10569b3a3a44062.png 800w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/03-three-ways_hu_1134c2d639be84dd.png 1600w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/03-three-ways.png 2000w&#34; width=&#34;2000&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;提示工程&lt;/strong&gt;（包括在提示词里放几个示例的“上下文学习”）改的是模型的&lt;strong&gt;输入&lt;/strong&gt;。好比考前递给学生一张小抄。它零成本、改完立刻生效，永远应该第一个尝试。缺点是规则一多，提示词越写越长，每次调用都更贵更慢，而且模型面对一大堆规则时容易顾此失彼。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;RAG&lt;/strong&gt;（Retrieval-Augmented Generation，检索增强生成）改的是模型能看到的&lt;strong&gt;参考资料&lt;/strong&gt;。用户提问时，系统先去知识库里检索相关的文档片段，拼进提示词，再让模型回答。好比开卷考试。它最适合处理&lt;strong&gt;会变化、需要溯源&lt;/strong&gt;的事实：今天的促销活动、上周更新的产品手册、某条规定出自哪份文件。知识库一更新，模型立刻就“知道”了，不用重新训练。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;微调&lt;/strong&gt;改的是&lt;strong&gt;模型本身的参数&lt;/strong&gt;。好比让学生去上一期培训班，把东西真正学进脑子里。学会之后，推理时不需要冗长的提示词，行为也更稳定。但它需要数据和算力，而且知识一旦写进参数，想修改就得重新训练。&lt;/p&gt;&#xA;&lt;p&gt;这三条路不是互斥的。成熟的系统往往同时用：微调负责把行为和格式固定下来，RAG 负责提供最新的事实，提示词再做一些临场的调整。&lt;/p&gt;&#xA;&lt;h4 id=&#34;141-sft-适合学习格式与规则不适合注入事实知识&#34;&gt;1.4.1 SFT 适合学习格式与规则，不适合注入事实知识&#xA;&lt;/h4&gt;&lt;p&gt;这里有一个很多人会踩的坑，值得单独拿出来说。&lt;/p&gt;&#xA;&lt;p&gt;常见的一种做法，是想通过 SFT 让模型记住公司的内部知识，比如准备几十条“我们公司的退货政策是什么”这样的问答。这在技术上能做到，几十条问答训几轮，模型确实能背下来。但它学到的往往是&lt;strong&gt;这几种问法对应的答案&lt;/strong&gt;，而不是真正理解了这条知识。换一种问法，或者把这条知识和别的知识组合起来问，它就可能答不上来，甚至开始一本正经地胡编。&lt;/p&gt;&#xA;&lt;p&gt;原因藏在 SFT 的训练方式里（第二部分会细讲）：SFT 的目标是让模型&lt;strong&gt;尽量复现示范答案里的每一个字&lt;/strong&gt;。几十条问答，只是反复强化了少数几条“问题 → 答案”的映射路径，远不足以让一条知识在模型内部和其他知识建立起广泛的联系。&lt;/p&gt;&#xA;&lt;p&gt;所以业界有一个经验性的分工：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;SFT 擅长的是“协议性知识”&lt;/strong&gt;：该用什么格式输出、什么语气说话、按什么流程办事、某个词在我们这里指什么。这类东西规则稳定、模式清晰，几百条示范就能见效。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;事实性知识&lt;/strong&gt;，如果量不大、会变化、需要溯源，交给 RAG；如果量很大、很稳定、需要模型真正“内化”（比如让模型学一门新语言、一个全新的专业领域），用 Mid-training 拿大量原始文本去训，而不是靠几十条问答。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;我们的 Text-to-SQL 案例正好落在 SFT 擅长的那一格：模型本来就会写 SQL，缺的是公司内部的一套“口径”，比如“销售额”要用成交价而不是标价算、要排除取消和退款的订单、金额要从分换算成元、日期函数要用 SQLite 的写法。这些规则不多、很稳定，但每次都写进提示词又很啰嗦，而且小模型经常记不住，正适合用微调“固化”下来。&lt;/p&gt;&#xA;&lt;h4 id=&#34;142-选型流程与-passk-判断&#34;&gt;1.4.2 选型流程与 pass@k 判断&#xA;&lt;/h4&gt;&lt;p&gt;把上面的思路整理一下，就是下面这张图。从上往下逐个问，越靠前的办法越便宜：&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;要不要微调：先诊断缺的是什么&#34; class=&#34;gallery-image&#34; data-flex-basis=&#34;375px&#34; data-flex-grow=&#34;156&#34; height=&#34;1280&#34; loading=&#34;lazy&#34; sizes=&#34;(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px&#34; src=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/04-decision.png&#34; srcset=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/04-decision_hu_2e886258ba4c1d54.png 800w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/04-decision_hu_1fccef698e97910c.png 1600w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/04-decision.png 2000w&#34; width=&#34;2000&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;有一个实用的判断方法，来自《深入理解 AI Agent》：在你的测试题上，不只让模型答一次，而是让它&lt;strong&gt;采样多次&lt;/strong&gt;，看看“至少答对一次”的比例。如果单次答对的概率是 $p$，独立采样 $k$ 次至少对一次的概率是：&lt;/p&gt;&#xA;$$&#xA;\text{pass@}k = 1 - (1 - p)^k&#xA;$$&lt;ul&gt;&#xA;&lt;li&gt;如果 pass@1 很低，但 pass@10 明显更高，说明&lt;strong&gt;正确答案在模型的能力范围内，只是不稳定&lt;/strong&gt;。这时 SFT 或强化学习能把这个“偶尔做对”的行为放大成“稳定做对”。&lt;/li&gt;&#xA;&lt;li&gt;如果采样很多次也几乎全错（pass@k 接近 0），说明模型&lt;strong&gt;根本不具备这个能力&lt;/strong&gt;。这时候直接做 SFT 或强化学习，效果会很差，应该先补底座（Mid-training），或者换一个更强的基座模型。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;这条经验后面讲强化学习时还会再用到。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;2-监督微调与参数高效微调&#34;&gt;2 监督微调与参数高效微调&#xA;&lt;/h2&gt;&lt;h3 id=&#34;21-sft-的原理指令数据对话模板与损失屏蔽&#34;&gt;2.1 SFT 的原理：指令数据、对话模板与损失屏蔽&#xA;&lt;/h3&gt;&lt;p&gt;很多人第一次接触 SFT，会以为它是一种全新的训练方法。其实不是。&lt;strong&gt;SFT 和预训练在数学上是同一件事：都是预测下一个词，都在最小化同一个交叉熵损失。&lt;/strong&gt; 区别只有两点：数据不一样，以及损失只算一部分。&lt;/p&gt;&#xA;&lt;h4 id=&#34;211-sft-数据格式alpaca-与-messages&#34;&gt;2.1.1 SFT 数据格式：Alpaca 与 messages&#xA;&lt;/h4&gt;&lt;p&gt;预训练用的是互联网上什么都有的原始文本；SFT 用的是整理成“输入 → 输出”的示范对。最常见的是 Alpaca 格式：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;system&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;你是一名资深数据分析师，负责把业务人员的问题翻译成可以直接执行的 SQLite 查询。&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;instruction&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;数据库表结构如下：\nCREATE TABLE orders (...)\n...\n\n问题：2024-08 的 GMV 是多少\n\n只输出一条 SQL，不要解释。&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;input&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;output&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;SELECT ROUND(SUM(oi.quantity * oi.unit_price_cent) / 100.0, 2) FROM orders o JOIN order_items oi ON o.id = oi.order_id WHERE o.status IN (&amp;#39;paid&amp;#39;, &amp;#39;shipped&amp;#39;, &amp;#39;completed&amp;#39;) AND strftime(&amp;#39;%Y-%m&amp;#39;, o.order_date) = &amp;#39;2024-08&amp;#39;;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;system&lt;/code&gt; 是系统提示词，设定模型的角色；&lt;code&gt;instruction&lt;/code&gt; 是用户的指令；&lt;code&gt;input&lt;/code&gt; 是可选的补充输入（比如用户贴过来的一段报错信息）；&lt;code&gt;output&lt;/code&gt; 是我们希望模型给出的标准回答。另一种常见格式是 OpenAI 风格的 &lt;code&gt;messages&lt;/code&gt; 列表（&lt;code&gt;[{&amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;, ...}, {&amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;, ...}]&lt;/code&gt;），适合多轮对话，本质是一样的。&lt;/p&gt;&#xA;&lt;h4 id=&#34;212-对话模板chat-template&#34;&gt;2.1.2 对话模板（chat template）&#xA;&lt;/h4&gt;&lt;p&gt;模型并不认识 JSON。训练前，这条样本要先按照模型自带的&lt;strong&gt;对话模板&lt;/strong&gt;（chat template）拼成一整条文本。我们用的 Qwen 系列采用 ChatML 格式，拼出来是这样：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&amp;lt;|im_start|&amp;gt;system&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;你是一名资深数据分析师，负责把业务人员的问题翻译成可以直接执行的 SQLite 查询。&amp;lt;|im_end|&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&amp;lt;|im_start|&amp;gt;user&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;数据库表结构如下：……问题：2024-08 的 GMV 是多少……&amp;lt;|im_end|&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&amp;lt;|im_start|&amp;gt;assistant&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SELECT ROUND(SUM(oi.quantity * oi.unit_price_cent) / 100.0, 2) FROM ……;&amp;lt;|im_end|&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;&amp;lt;|im_start|&amp;gt;&lt;/code&gt;、&lt;code&gt;&amp;lt;|im_end|&amp;gt;&lt;/code&gt; 是特殊 token，用来标记“谁在说话”和“这段话说完了”。每个模型家族的模板都不一样（Llama、DeepSeek、Qwen 各有各的写法），&lt;strong&gt;训练时用什么模板，推理时就必须用同一个模板&lt;/strong&gt;，否则模型会“听不懂”。LLaMA-Factory 里的 &lt;code&gt;template&lt;/code&gt; 参数、Transformers 里的 &lt;code&gt;tokenizer.apply_chat_template()&lt;/code&gt;，管的都是这件事。这是新手最容易踩的坑之一：训练完一测，模型胡言乱语或者停不下来，十有八九是模板对不上。&lt;/p&gt;&#xA;&lt;h4 id=&#34;213-损失屏蔽与-sft-损失函数&#34;&gt;2.1.3 损失屏蔽与 SFT 损失函数&#xA;&lt;/h4&gt;&lt;p&gt;拼好的整条序列会一起喂给模型，但计算损失时，&lt;strong&gt;只有 assistant 回答的那部分参与&lt;/strong&gt;，系统提示词和用户问题的部分都被屏蔽掉。这叫&lt;strong&gt;损失屏蔽&lt;/strong&gt;（loss masking）。&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;SFT 的关键细节：只在回答上算损失&#34; class=&#34;gallery-image&#34; data-flex-basis=&#34;558px&#34; data-flex-grow=&#34;232&#34; height=&#34;860&#34; loading=&#34;lazy&#34; sizes=&#34;(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px&#34; src=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/05-loss-mask.png&#34; srcset=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/05-loss-mask_hu_8e148e177652f4ce.png 800w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/05-loss-mask_hu_7aad076d018f2389.png 1600w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/05-loss-mask.png 2000w&#34; width=&#34;2000&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;具体实现非常朴素：准备一个和输入等长的 &lt;code&gt;labels&lt;/code&gt; 数组，提示词位置填 &lt;code&gt;-100&lt;/code&gt;，回答位置填真实的 token 编号。PyTorch 的交叉熵损失会自动跳过标签为 &lt;code&gt;-100&lt;/code&gt; 的位置。第四部分手写训练代码时，你会看到这就是几行列表拼接。&lt;/p&gt;&#xA;&lt;p&gt;为什么要这么做？因为我们只想让模型学“怎么回答”，不想让它学“怎么提问”。在我们的案例里还有一个更实际的原因：每条样本的提示词里都有一大段表结构，要是这部分也算损失，模型会把大量精力花在背诵表结构上。&lt;/p&gt;&#xA;&lt;p&gt;注意回答末尾的 &lt;code&gt;&amp;lt;|im_end|&amp;gt;&lt;/code&gt; 也要算损失。模型必须学会“话说完了就停”，否则推理时它会一直往下编。&lt;/p&gt;&#xA;&lt;p&gt;把上面这些写成公式，SFT 的损失是：&lt;/p&gt;&#xA;$$&#xA;\mathcal{L}_{\text{SFT}} = -\frac{1}{|y|}\sum_{t=1}^{|y|} \log p_\theta\left(y_t \mid x,\ y_{\lt t}\right)&#xA;$$&lt;p&gt;逐项翻译一下：$x$ 是提示词（系统提示 + 用户问题），$y$ 是标准回答，$y_t$ 是回答的第 $t$ 个 token，$y_{\lt t}$ 是它前面已经出现的回答部分。整个式子的意思是：&lt;strong&gt;在给定提示词和前文的条件下，让标准回答里每一个 token 的概率都尽量高，然后取平均。&lt;/strong&gt; 和第 1.1 节的预训练损失比，唯一的区别就是求和只覆盖回答部分。&lt;/p&gt;&#xA;&lt;p&gt;这个公式也解释了上一节说的“SFT 擅长教规矩、不擅长灌知识”：它的目标是&lt;strong&gt;逐字复现示范&lt;/strong&gt;。示范里的格式、措辞、流程，会被非常高效地学进去；但如果示范只覆盖了某条知识的两三种问法，模型学到的也就只是这两三条路径。&lt;/p&gt;&#xA;&lt;h3 id=&#34;22-训练循环与超参数&#34;&gt;2.2 训练循环与超参数&#xA;&lt;/h3&gt;&lt;p&gt;知道了损失怎么算，再来看训练循环。不管是 LLaMA-Factory 还是手写代码，底层都是同一个循环，重复几百上千次：&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;一次训练迭代在做什么&#34; class=&#34;gallery-image&#34; data-flex-basis=&#34;400px&#34; data-flex-grow=&#34;166&#34; height=&#34;1200&#34; loading=&#34;lazy&#34; sizes=&#34;(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px&#34; src=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/06-train-loop.png&#34; srcset=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/06-train-loop_hu_9a5e5775b8f7427d.png 800w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/06-train-loop_hu_1f84d11fe5edc32a.png 1600w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/06-train-loop.png 2000w&#34; width=&#34;2000&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;LLaMA-Factory 界面上那一排训练参数，都能在这个循环里找到位置：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;批大小（batch size）&lt;/strong&gt;：第 ① 步一次取几条样本。越大，梯度估计越准、训练越稳，但显存占用越高。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;梯度累积（gradient accumulation）&lt;/strong&gt;：显存放不下大 batch 时的变通办法。每次只算一小批，但先不更新参数，把梯度攒够 N 批再更新一次。效果上等价于把 batch 放大 N 倍。&lt;strong&gt;等效 batch = 批大小 × 累积步数 × GPU 数量。&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;学习率（learning rate）&lt;/strong&gt;：第 ⑤ 步每次迈多大。太大会“一步迈过头”，损失上蹿下跳甚至发散；太小学得很慢。LoRA 微调常用 1e-4 到 2e-4，全参数微调通常要小一个数量级（1e-5 左右）。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;学习率调度（scheduler）与预热（warmup）&lt;/strong&gt;：学习率不是一成不变的。常见做法是开头几十步从 0 慢慢升到设定值（预热，避免一开始梯度太大把模型带偏），然后按余弦曲线（cosine）逐渐减小，越到后面步子越小，方便“精细收敛”。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;训练轮数（epoch）&lt;/strong&gt;：整个训练集完整过几遍。数据量几百到几千条时，2 到 5 轮比较常见。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;截断长度（cutoff_len）&lt;/strong&gt;：超过这个 token 数的样本会被截掉。要比你最长的样本长一些，否则回答被截断，模型会学到“说到一半就停”。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;最大梯度范数（max_grad_norm）&lt;/strong&gt;：某一步梯度特别大时，按比例把它缩小到这个上限以内，防止参数被一下子推飞（梯度爆炸）。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;计算精度（bf16 / fp16 / fp32）&lt;/strong&gt;：用几位二进制数存参数和做运算。bf16 在新一代 GPU（30 系、40 系、A100 及以后）上是默认选择，比 fp32 省一半显存，数值也比 fp16 稳定。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h4 id=&#34;221-用训练损失和验证损失判断欠拟合与过拟合&#34;&gt;2.2.1 用训练损失和验证损失判断欠拟合与过拟合&#xA;&lt;/h4&gt;&lt;p&gt;训练过程中最重要的仪表盘是损失曲线。很多教程会说“尽可能把损失降到最低”，这句话要补一个重要的限定：&lt;strong&gt;降到最低的应该是验证集损失，而不是训练集损失。&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;训练集上的损失几乎一定会一路下降，因为模型可以把训练数据背下来。真正说明问题的，是模型&lt;strong&gt;没见过&lt;/strong&gt;的验证集上的损失：&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;看损失曲线判断训练状态&#34; class=&#34;gallery-image&#34; data-flex-basis=&#34;545px&#34; data-flex-grow=&#34;227&#34; height=&#34;880&#34; loading=&#34;lazy&#34; sizes=&#34;(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px&#34; src=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/07-loss-curves.png&#34; srcset=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/07-loss-curves_hu_f263724d6f892f1a.png 800w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/07-loss-curves_hu_75ebec45e394cca.png 1600w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/07-loss-curves.png 2000w&#34; width=&#34;2000&#34;&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;欠拟合&lt;/strong&gt;：两条线都还在明显下降，说明还没学够，可以加轮数或调大学习率。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;刚刚好&lt;/strong&gt;：验证损失走平，在这附近停下。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;过拟合&lt;/strong&gt;：训练损失还在降，验证损失却掉头向上，说明模型开始死记硬背训练集的细节，对新问题的表现反而变差。这时应该提前停止、减少轮数，或者补充更多样化的数据。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;所以训练时一定要留一份验证集（LLaMA-Factory 里的 &lt;code&gt;val_size&lt;/code&gt;，比如 0.05 表示拿 5% 的数据做验证），并且最终效果要用一份&lt;strong&gt;完全独立的测试集&lt;/strong&gt;来评估，而不是看训练损失有多低。&lt;/p&gt;&#xA;&lt;h3 id=&#34;23-sft-数据的来源构造与质量控制&#34;&gt;2.3 SFT 数据的来源、构造与质量控制&#xA;&lt;/h3&gt;&lt;p&gt;SFT 的效果，一半取决于模型，另一半取决于数据。这里说的“数据”，重点不在数量，而在质量和覆盖面。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;数据从哪来？&lt;/strong&gt; 业界基本就三条路，经常组合使用：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;人工示范&lt;/strong&gt;：质量上限最高，但贵且慢。适合用来写“种子数据”，定义好格式和风格。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;用更强的模型生成&lt;/strong&gt;（合成数据）：让强模型按种子数据的样子批量产出新样本，再过滤。常说的“数据增强”，主要就是这条路。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;拒绝采样&lt;/strong&gt;：让模型对同一个问题生成多个答案，用程序或人工挑出正确的留下，再拿来训练自己。我们的 Text-to-SQL 特别适合这么做，因为 SQL 的对错能自动判断。&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;&lt;strong&gt;怎么造得好？&lt;/strong&gt; 一条稳妥的流程是：先定义清楚任务的输入输出格式，再批量生成候选，然后用规则检查、格式校验加人工抽检过滤，最后去重、平衡各类样本的比例。第四部分的数据脚本就是按这个思路写的：人工写好二十来个“问题模板 + 标准 SQL”，程序填入不同的参数放大成几百条，&lt;strong&gt;每一条 SQL 都在真实数据库上执行一遍&lt;/strong&gt;，执行失败、结果为空或者排名有并列（标准答案不唯一）的样本全部丢弃。&lt;/p&gt;&#xA;&lt;p&gt;有几条值得记住的经验：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;宁缺毋滥。&lt;/strong&gt; 数据里的每一处错误，SFT 都会忠实地学进去。与其堆十万条脏数据，不如精修一千条干净数据。对于格式、口径这类任务，几百到几千条高质量样本往往就够了。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;注意均衡。&lt;/strong&gt; 我们第一次生成数据时，“按客户姓名查订单数”这一类因为姓名多、组合多，一下子占了一半样本。模型会因此“偏科”。脚本里给每类问题设了上限，就是为了避免这个问题。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;测试集要“够陌生”。&lt;/strong&gt; 只从训练数据里随机抽一部分当测试集，测出来的分数往往偏高，因为题型都见过。更严格的做法是留出几类&lt;strong&gt;训练中完全没出现过的题型&lt;/strong&gt;，单独测一次，看模型学到的是规则本身，还是只记住了句式。第四部分的评测会分别统计这两个分数。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;小心灾难性遗忘。&lt;/strong&gt; 在一个窄任务上猛训，模型可能把原来的通用能力忘掉一部分，这叫&lt;strong&gt;灾难性遗忘&lt;/strong&gt;。缓解办法包括：用 LoRA 而不是全参数微调（原权重被冻结，扰动更小）、学习率和轮数别太激进、在训练数据里混入一些通用对话数据。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;24-全参数微调的显存开销&#34;&gt;2.4 全参数微调的显存开销&#xA;&lt;/h3&gt;&lt;p&gt;讲完 SFT 训练什么，再来看用什么方式训练。最直接的做法是&lt;strong&gt;全参数微调&lt;/strong&gt;：模型里的所有参数都参与更新。效果通常最好，但代价很高。代价主要不在计算，而在&lt;strong&gt;显存&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;训练时显存里要放的不只是模型本身。以最常见的“混合精度 + AdamW 优化器”为例，每个参数要占用：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;权重本身&lt;/strong&gt;：bf16 格式，2 字节；&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;梯度&lt;/strong&gt;：每个参数都要存一个梯度，bf16 格式，2 字节；&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;优化器状态&lt;/strong&gt;：AdamW 要给每个参数记两个“动量”（可以理解为梯度的历史平均和历史波动），为了数值稳定用 fp32 存，共 8 字节；&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;fp32 主权重&lt;/strong&gt;：混合精度训练通常还要保留一份 fp32 的权重副本用来累积更新，4 字节。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;加起来，&lt;strong&gt;每个参数大约 16 字节&lt;/strong&gt;，这还没算前向传播时的中间结果（激活值），后者随 batch 大小和序列长度变化，常常还要再加几 GB 到几十 GB。&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;显存账本：微调一个 7B 模型要多少显存&#34; class=&#34;gallery-image&#34; data-flex-basis=&#34;510px&#34; data-flex-grow=&#34;212&#34; height=&#34;940&#34; loading=&#34;lazy&#34; sizes=&#34;(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px&#34; src=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/08-memory.png&#34; srcset=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/08-memory_hu_a6dd6130df5d6985.png 800w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/08-memory_hu_6c8923b7567ee512.png 1600w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/08-memory.png 2000w&#34; width=&#34;2000&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;按这个算法，全参数微调一个 7B 模型需要大约 112 GB 显存，一张 80 GB 的 A100 都放不下。即使是我们实战用的 1.5B 小模型，也要约 25 GB，一张 24 GB 的 RTX 4090 同样不够。（不同框架和优化手段下的实测数字会有出入，比如《大模型基础》引用的实验中，LLaMA2-7B 全量微调约需 60 GB，但量级上的结论是一致的：全参数微调远超消费级显卡的能力。）&lt;/p&gt;&#xA;&lt;p&gt;仔细看这本账会发现：权重本身只占小头，&lt;strong&gt;大头是梯度和优化器状态&lt;/strong&gt;，而它们只有“需要训练的参数”才有。那么，如果只训练一小部分参数呢？这就是&lt;strong&gt;参数高效微调&lt;/strong&gt;（Parameter-Efficient Fine-Tuning，PEFT）的出发点。&lt;/p&gt;&#xA;&lt;h3 id=&#34;25-参数高效微调peft的三类方法&#34;&gt;2.5 参数高效微调（PEFT）的三类方法&#xA;&lt;/h3&gt;&lt;p&gt;PEFT 的思路是：&lt;strong&gt;冻结原模型的绝大部分参数，只训练很少一部分&lt;/strong&gt;（新加的，或者从原模型里挑出来的），用不到 1% 的可训练参数，换来接近全参数微调的效果。&lt;/p&gt;&#xA;&lt;p&gt;除了省显存，PEFT 还有两个好处：一是&lt;strong&gt;存储&lt;/strong&gt;省，每个任务只需要保存那一小部分参数，几十 MB 而不是几个 GB；二是&lt;strong&gt;灵活&lt;/strong&gt;，同一个基座模型可以挂载不同任务的“插件”，切换起来很方便。&lt;/p&gt;&#xA;&lt;p&gt;根据“训练的那一小部分参数从哪来”，《大模型基础》把主流 PEFT 方法分成三大类。我们先看一张总图，看它们分别“插”在模型的什么位置：&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;参数高效微调：各种方法分别插在模型的哪里&#34; class=&#34;gallery-image&#34; data-flex-basis=&#34;400px&#34; data-flex-grow=&#34;166&#34; height=&#34;1200&#34; loading=&#34;lazy&#34; sizes=&#34;(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px&#34; src=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/09-peft-family.png&#34; srcset=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/09-peft-family_hu_2f9ab79e7b6d0cf1.png 800w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/09-peft-family_hu_b16dbcf899174bda.png 1600w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/09-peft-family.png 2000w&#34; width=&#34;2000&#34;&gt;&lt;/p&gt;&#xA;&lt;h4 id=&#34;251-参数附加方法prompt-tuningprefix-tuningadapterproxy-tuning&#34;&gt;2.5.1 参数附加方法：Prompt-tuning、Prefix-tuning、Adapter、Proxy-tuning&#xA;&lt;/h4&gt;&lt;p&gt;在模型里&lt;strong&gt;附加&lt;/strong&gt;一些新的、很小的可训练模块，原模型参数全部冻结。按加的位置，又分三种：&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;加在输入：Prompt-tuning。&lt;/strong&gt; 平时我们写的提示词是离散的文字，叫“硬提示”。Prompt-tuning 换个思路：在输入的词向量序列前面，拼上 $m$ 个&lt;strong&gt;可训练的向量&lt;/strong&gt;（叫“软提示”）。设输入文本经过嵌入层变成矩阵 $X \in \mathbb{R}^{n \times d}$（$n$ 个 token，每个 $d$ 维），软提示是 $P \in \mathbb{R}^{m \times d}$，模型实际的输入是二者拼接 $[P; X]$。训练时只更新 $P$。它本质上是让模型&lt;strong&gt;自己学一段最优的“提示词”&lt;/strong&gt;，只不过这段提示词不是人能读懂的文字，而是一组向量。参数量小到惊人：以 T5-XXL（110 亿参数）为例，软提示长度为 5 时，只需要训练 20480 个参数。软提示长度一般在 20 以上效果才比较稳定，而且模型越大，Prompt-tuning 的效果越接近全参数微调。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;加在模型内部：Prefix-tuning 和 Adapter。&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Prefix-tuning&lt;/strong&gt; 是 Prompt-tuning 的加强版：不只在输入层，而是在&lt;strong&gt;每一层&lt;/strong&gt;注意力的 Key 和 Value 前面都拼上一段可训练的前缀向量，可训练参数比 Prompt-tuning 多，表达能力也更强。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Adapter&lt;/strong&gt; 在 Transformer 每个子层（注意力层、前馈层）后面插入一个“瓶颈”结构的小网络：先用一个矩阵 $W_d \in \mathbb{R}^{d \times r}$ 把 $d$ 维特征压缩到很低的 $r$ 维，经过激活函数，再用 $W_u \in \mathbb{R}^{r \times d}$ 还原回 $d$ 维，最后加上原来的输入（残差连接）：&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;$$&#xA;A^{(l)} = \sigma\left(H^{(l-1)} W_d\right) W_u + H^{(l-1)}&#xA;$$&lt;p&gt;每个 Adapter 的参数量是 $2dr + d + r$（两个投影矩阵加偏置），$r$ 远小于 $d$ 时非常省。Adapter 的问题在于：它是&lt;strong&gt;串联&lt;/strong&gt;在原来的计算路径上的，推理时每一层都要多算一次，会带来额外的延迟。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;加在输出：Proxy-tuning。&lt;/strong&gt; 这是一种很巧妙的思路：如果大模型太大，或者根本拿不到权重（比如只能调 API 的闭源模型），能不能不碰它，也让它学会新任务？Proxy-tuning 的做法是，拿一个&lt;strong&gt;同词表的小模型&lt;/strong&gt;，复制两份：一份微调成“专家”$M^+$，一份保持原样当“反专家”$M^-$。生成每个 token 时，用两个小模型输出分数的&lt;strong&gt;差值&lt;/strong&gt;去修正大模型 $M$ 的输出分数：&lt;/p&gt;&#xA;$$&#xA;\tilde{s} = s_M + \left(s_{M^+} - s_{M^-}\right)&#xA;$$&lt;p&gt;$s_{M^+} - s_{M^-}$ 恰好代表了“微调学到了什么”，把它加到大模型上，就相当于把小模型学到的东西“嫁接”了过去。&lt;/p&gt;&#xA;&lt;h4 id=&#34;252-参数选择方法bitfitchild-tuning&#34;&gt;2.5.2 参数选择方法：BitFit、Child-tuning&#xA;&lt;/h4&gt;&lt;p&gt;不加任何新参数，而是从原模型里&lt;strong&gt;挑出一小部分参数&lt;/strong&gt;来训练，其余冻结。&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;BitFit&lt;/strong&gt; 规则最简单：只训练每一层的偏置项（bias），它们只占总参数的约 0.08%~0.09%。在 BERT 这类模型上，效果能和全参数微调相当。不过很多现代大模型的线性层干脆没有偏置项，这个方法也就无从下手了。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Child-tuning&lt;/strong&gt; 用一个和参数矩阵同样大小的 0-1 掩码 $M$ 来决定“谁参与更新”：更新时梯度要先乘上掩码，$W \leftarrow W - \eta \left(\frac{\partial \mathcal{L}}{\partial W} \odot M\right)$，掩码为 0 的位置就不动。掩码可以随机生成（每一步按一定比例随机挑），也可以根据任务数据计算每个参数的重要性（用费舍尔信息衡量“这个参数对任务的输出影响有多大”），只挑最重要的那部分。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;参数选择方法的好处是推理时没有任何额外开销；难点在于“挑哪些参数”本身就是个难题，而且挑出来的参数散落在矩阵各处，在 GPU 上并不好高效实现。&lt;/p&gt;&#xA;&lt;h4 id=&#34;253-低秩适配方法与三类方法对比&#34;&gt;2.5.3 低秩适配方法与三类方法对比&#xA;&lt;/h4&gt;&lt;p&gt;第三类是今天最常用的 &lt;strong&gt;LoRA&lt;/strong&gt;（Low-Rank Adaptation，低秩适配）及其各种变体。它几乎成了“微调”的代名词，下一节专门展开。&lt;/p&gt;&#xA;&lt;p&gt;先用一张表把三类方法放在一起比较：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;&#x9;&lt;thead&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;类别&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;代表方法&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;训练什么&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;推理开销&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;主要问题&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&lt;/thead&gt;&#xA;&#x9;&lt;tbody&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;参数附加&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;Prompt-tuning、Prefix-tuning&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;输入或每层前面的软提示向量&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;占用一部分上下文长度&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;训练不太稳定，小模型上效果一般&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;参数附加&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;Adapter&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;每层插入的瓶颈小网络&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;有，每层多一次计算&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;串联结构带来延迟&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;参数附加&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;Proxy-tuning&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;一个小的专家模型&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;有，要同时跑三个模型&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;需要同词表的小模型&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;参数选择&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;BitFit、Child-tuning&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;原模型中的一部分参数&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;无&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;选哪些参数难；实现不够高效&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;低秩适配&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;LoRA 及变体&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;并联在线性层旁的两个低秩矩阵&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;strong&gt;无（可合并）&lt;/strong&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;秩太低时表达能力受限&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h3 id=&#34;26-lora-的原理&#34;&gt;2.6 LoRA 的原理&#xA;&lt;/h3&gt;&lt;h4 id=&#34;261-矩阵的秩&#34;&gt;2.6.1 矩阵的秩&#xA;&lt;/h4&gt;&lt;p&gt;LoRA 的名字里有个“低秩”，所以得先花两分钟理解&lt;strong&gt;矩阵的秩&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;教科书上的定义是：秩是矩阵中&lt;strong&gt;线性无关&lt;/strong&gt;的行或列的最大数量，它反映了矩阵包含的&lt;strong&gt;有效信息量&lt;/strong&gt;。这句话有点抽象，看个例子就明白了：&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;秩的直觉&#34; class=&#34;gallery-image&#34; data-flex-basis=&#34;510px&#34; data-flex-grow=&#34;212&#34; height=&#34;940&#34; loading=&#34;lazy&#34; sizes=&#34;(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px&#34; src=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/10-rank.png&#34; srcset=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/10-rank_hu_23629f31cc5e89c9.png 800w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/10-rank_hu_1973ae3107e9cbc5.png 1600w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/10-rank.png 2000w&#34; width=&#34;2000&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;右边这个 4×4 矩阵有 16 个数，看起来各不相同。但仔细看，第二行是第一行的 2 倍，第三行是 3 倍，第四行是 4 倍，每一行都只是第一行的倍数，没有提供任何新的“方向”。所以它的秩是 1。它其实完全由左边一个 4×1 的列向量和一个 1×4 的行向量相乘得到，&lt;strong&gt;16 个数只由 8 个数决定&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;推广一下：一个 $d \times k$ 的矩阵如果秩为 $r$，就可以拆成一个 $d \times r$ 矩阵乘以一个 $r \times k$ 矩阵，需要存的数从 $d \times k$ 个降到 $r \times (d + k)$ 个。当 $d = k = 4096$、$r = 8$ 时，从约 1678 万个降到 65536 个，只有原来的 0.39%。&lt;/p&gt;&#xA;&lt;h4 id=&#34;262-低秩假设与-lora-公式&#34;&gt;2.6.2 低秩假设与 LoRA 公式&#xA;&lt;/h4&gt;&lt;p&gt;模型里绝大多数参数都在一个个巨大的权重矩阵里（注意力的 q、k、v、o 投影，前馈网络的 gate、up、down 投影）。微调，就是给这些矩阵加上一个&lt;strong&gt;变化量&lt;/strong&gt; $\Delta W$：&lt;/p&gt;&#xA;$$&#xA;W = W_0 + \Delta W&#xA;$$&lt;p&gt;$W_0$ 是预训练好的原权重。全参数微调时，$\Delta W$ 和 $W_0$ 一样大，每个元素都要训练。&lt;/p&gt;&#xA;&lt;p&gt;LoRA 的论文（微软，2021 年）提出了一个大胆的假设：&lt;strong&gt;微调时的变化量 $\Delta W$ 是“低秩”的。&lt;/strong&gt; 背后的直觉是，预训练模型已经学到了非常丰富的通用表示，适配一个具体任务时，只需要在少数几个“方向”上做调整，而不需要动用矩阵的全部自由度。这个想法有理论依据：此前的研究已经发现，大模型的“固有维度”（intrinsic dimension）其实很低，在一个很小的子空间里优化，就能达到接近全参数优化的效果。&lt;/p&gt;&#xA;&lt;p&gt;既然 $\Delta W$ 是低秩的，那就干脆直接把它写成两个瘦矩阵的乘积：&lt;/p&gt;&#xA;$$&#xA;\Delta W = B A, \qquad B \in \mathbb{R}^{d \times r},\ A \in \mathbb{R}^{r \times k},\ r \ll \min(d, k)&#xA;$$&lt;p&gt;训练时冻结 $W_0$，只训练 $A$ 和 $B$。前向计算变成：&lt;/p&gt;&#xA;$$&#xA;h = W_0 x + \frac{\alpha}{r} B A x&#xA;$$&lt;p&gt;&lt;img alt=&#34;LoRA 结构&#34; class=&#34;gallery-image&#34; data-flex-basis=&#34;428px&#34; data-flex-grow=&#34;178&#34; height=&#34;1120&#34; loading=&#34;lazy&#34; sizes=&#34;(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px&#34; src=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/11-lora.png&#34; srcset=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/11-lora_hu_f6c44a4025e31fe6.png 800w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/11-lora_hu_3dc464f06b06d86a.png 1600w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/11-lora.png 2000w&#34; width=&#34;2000&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;逐项解释这张图和这个公式：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;主干&lt;/strong&gt; $W_0 x$：原来的计算，权重冻结，不更新。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;旁路&lt;/strong&gt; $BAx$：输入 $x$ 先经过 $A$ 被压缩到只有 $r$ 维（这就是“瓶颈”），再经过 $B$ 还原回原来的维度。旁路的输出和主干相加，就是最终输出。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;缩放系数&lt;/strong&gt; $\frac{\alpha}{r}$：$\alpha$（代码里叫 &lt;code&gt;lora_alpha&lt;/code&gt;）是一个超参数。乘上 $\frac{\alpha}{r}$ 的作用是让旁路输出的量级不随 $r$ 的变化而剧烈变化，这样换一个 $r$ 时，不用重新调学习率。实践中常设 $\alpha = 2r$ 或 $\alpha = r$。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;初始化&lt;/strong&gt;：$A$ 用随机高斯分布初始化，$B$ &lt;strong&gt;全部初始化为 0&lt;/strong&gt;。这样训练开始时 $BA = 0$，旁路输出为零，模型和原来一模一样。训练是从原模型“平稳出发”的，不会一上来就被随机的旁路搅乱。（《大模型基础》第 4 章写的是 B 随机、A 为零，和 LoRA 原论文正好相反。两种方式都能保证起点为零，但原论文和 PEFT 等主流实现都是 A 随机、B 为零，以它们为准。）&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h4 id=&#34;263-lora-的参数量计算&#34;&gt;2.6.3 LoRA 的参数量计算&#xA;&lt;/h4&gt;&lt;p&gt;先看《大模型基础》里的例子。LLaMA2-7B 第一个前馈层的权重矩阵是 11008 × 4096，全参数微调要训练 45,088,768 个参数；用 $r = 4$ 的 LoRA，只需要 11008 × 4 + 4 × 4096 = 60,416 个，不到原来的千分之一点五。&lt;/p&gt;&#xA;&lt;p&gt;再看我们实战要用的 Qwen2.5-Coder-1.5B。它有 28 层，每层有 7 个线性层可以挂 LoRA。隐藏维度是 1536，前馈层中间维度是 8960；因为用了分组查询注意力（GQA），k、v 投影的输出只有 256 维。取 $r = 16$，每层的 LoRA 参数量是：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;&#x9;&lt;thead&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;线性层&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;形状（输入 → 输出）&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;LoRA 参数 = r × (输入 + 输出)&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&lt;/thead&gt;&#xA;&#x9;&lt;tbody&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;q_proj、o_proj&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;1536 → 1536&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;各 16 × 3072 = 49,152&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;k_proj、v_proj&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;1536 → 256&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;各 16 × 1792 = 28,672&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;gate_proj、up_proj&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;1536 → 8960&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;各 16 × 10496 = 167,936&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;down_proj&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;8960 → 1536&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;16 × 10496 = 167,936&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;strong&gt;每层合计&lt;/strong&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;strong&gt;659,456&lt;/strong&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;28 层一共约 1846 万个可训练参数，只占模型总参数（约 15.4 亿）的 &lt;strong&gt;1.2%&lt;/strong&gt;。第四部分训练时，代码会打印出这个数字，你可以亲眼核对。&lt;/p&gt;&#xA;&lt;p&gt;这 1.2% 的参数需要梯度和优化器状态，剩下 98.8% 的冻结参数只需要存一份 bf16 权重。回到上一节的显存账本：1.5B 模型全参数微调要约 25 GB，换成 LoRA 后，权重约 3 GB 加上 LoRA 部分的零头，再加上激活值，一张 24 GB 的显卡绰绰有余。&lt;/p&gt;&#xA;&lt;h4 id=&#34;264-权重合并与多-lora-挂载&#34;&gt;2.6.4 权重合并与多 LoRA 挂载&#xA;&lt;/h4&gt;&lt;p&gt;LoRA 最漂亮的地方在训练之后。因为 $W_0 x + BAx = (W_0 + BA)x$，训练结束后，可以直接把 $BA$（乘上缩放系数）&lt;strong&gt;加回原权重&lt;/strong&gt;：&lt;/p&gt;&#xA;$$&#xA;W = W_0 + \frac{\alpha}{r} B A&#xA;$$&lt;p&gt;合并后的模型和原模型结构完全一样，推理速度也完全一样，&lt;strong&gt;没有任何额外开销&lt;/strong&gt;。这是它相对 Adapter 的最大优势，也是实战中“训练结束后合并权重”那一步的原理。&lt;/p&gt;&#xA;&lt;p&gt;也可以不合并。$A$ 和 $B$ 单独保存下来，只有几十 MB，相当于一个“插件”。同一个基座模型可以按需挂载不同任务的 LoRA：一个负责写 SQL，一个负责写周报，一个负责客服话术。一些推理框架（比如 vLLM）支持在一个服务里同时加载多个 LoRA，按请求切换，非常适合多租户场景。&lt;/p&gt;&#xA;&lt;h3 id=&#34;27-lora-超参数qlora-与-lora-变体&#34;&gt;2.7 LoRA 超参数、QLoRA 与 LoRA 变体&#xA;&lt;/h3&gt;&lt;h4 id=&#34;271-rlora_alphalora_dropout-与-target_modules&#34;&gt;2.7.1 r、lora_alpha、lora_dropout 与 target_modules&#xA;&lt;/h4&gt;&lt;p&gt;LoRA 主要有四个超参数：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;r&lt;/code&gt;（秩）&lt;/strong&gt;：旁路的“宽度”，决定了 LoRA 的表达能力。常见取值 8、16、32、64。任务越复杂、数据越多，可以设得越大；数据很少时设小一点，不容易过拟合。格式、口径这类任务，16 或 32 一般就够了。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;lora_alpha&lt;/code&gt;（缩放系数）&lt;/strong&gt;：真正生效的缩放是 $\alpha / r$。常见设成 $r$ 的 1 到 2 倍。（有的资料说 alpha“通常与 r 成反比”，这个说法容易误导：和 $r$ 成反比的是缩放系数 $\alpha/r$，不是 $\alpha$ 本身。实际操作中，换 $r$ 时一般让 $\alpha$ 跟着同比例变化，比如始终保持 $\alpha = 2r$。）&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;lora_dropout&lt;/code&gt;&lt;/strong&gt;：对旁路输入做随机丢弃，防止过拟合，常用 0.05 左右，数据多时可以设为 0。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;target_modules&lt;/code&gt;（挂在哪些层上）&lt;/strong&gt;：最初的 LoRA 论文只挂在注意力的 q、v 两个投影上。后来的大量实践表明，&lt;strong&gt;挂在所有线性层上（包括参数量最大的前馈层）效果明显更好&lt;/strong&gt;，而增加的参数并不多。LLaMA-Factory 里设 &lt;code&gt;lora_target: all&lt;/code&gt; 就是这个意思。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;学习率方面，Thinking Machines Lab 在《LoRA Without Regret》中总结过一条实用经验：&lt;strong&gt;LoRA 的最优学习率大约是全参数微调的 10 倍&lt;/strong&gt;。全参数微调常用 1e-5 级别，LoRA 常用 1e-4 级别，本文实战用的是 2e-4。&lt;/p&gt;&#xA;&lt;h4 id=&#34;272-qlora4-位量化与-lora-结合&#34;&gt;2.7.2 QLoRA：4 位量化与 LoRA 结合&#xA;&lt;/h4&gt;&lt;p&gt;LoRA 省掉了梯度和优化器状态，但冻结的权重本身还要占地方：7B 模型的 bf16 权重就要 14 GB。&lt;strong&gt;QLoRA&lt;/strong&gt;（2023 年）进一步把这部分也压缩了：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;冻结的原权重用 4 位精度存储&lt;/strong&gt;（一种叫 NF4 的格式，专门为神经网络权重的正态分布设计），7B 模型的权重从 14 GB 压到 4 GB 左右；&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;LoRA 的 A、B 仍然用 bf16 训练&lt;/strong&gt;，计算时把 4 位权重临时还原成 bf16 参与运算；&lt;/li&gt;&#xA;&lt;li&gt;再配合“双重量化”（对量化用的缩放常数再量化一次）和“分页优化器”（显存不够时把优化器状态临时挪到内存），进一步省显存。&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;结果是：一张 24 GB 的消费级显卡就能微调 30B 级别的模型，效果和 16 位 LoRA 相差无几。代价是训练速度会慢一些（每次都要反量化），而且合并时要注意：4 位权重加上 LoRA 后通常要先还原成 16 位模型再合并导出。在 LLaMA-Factory 里，只需要加一行 &lt;code&gt;quantization_bit: 4&lt;/code&gt; 就能开启。&lt;/p&gt;&#xA;&lt;h4 id=&#34;273-lora-变体reloraadaloradoralorahub&#34;&gt;2.7.3 LoRA 变体：ReLoRA、AdaLoRA、DoRA、LoRAHub&#xA;&lt;/h4&gt;&lt;p&gt;LoRA 并非完美。在数学推理这类复杂任务上，它和全参数微调之间仍然可能存在差距；研究发现全参数微调学到的变化量，秩比 LoRA 高 10 到 100 倍。围绕这些问题，出现了很多改进方法，了解它们的思路即可：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;打破低秩瓶颈：ReLoRA。&lt;/strong&gt; 训练过程中周期性地把 LoRA 合并进原权重，然后重新初始化 A、B 继续训练。每一段都是低秩更新，但多段累加起来，总的变化量就可以是高秩的。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;动态分配秩：AdaLoRA。&lt;/strong&gt; 不同层对任务的重要性不一样，没必要都用同一个 $r$。AdaLoRA 把变化量写成类似奇异值分解的形式 $\Delta W = P \Lambda Q$，训练中根据重要性剪掉不重要的奇异值，相当于自动给重要的层多分配一些秩。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;优化训练过程：DoRA。&lt;/strong&gt; 把权重拆成“大小”和“方向”两部分，只对方向部分用 LoRA，大小部分单独训练。实验表明这样更稳定，效果更接近全参数微调。在 PEFT 库里加一个 &lt;code&gt;use_dora=True&lt;/code&gt; 就能用。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;组合多个 LoRA：LoRAHub。&lt;/strong&gt; 既然 LoRA 是插件，能不能把“摘要”“翻译”“写代码”几个任务的 LoRA 加权组合起来，去解决一个新任务？LoRAHub 就是这个思路，组合的权重只需要用新任务的几个例子来搜索，不需要训练。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;3-偏好对齐与强化学习&#34;&gt;3 偏好对齐与强化学习&#xA;&lt;/h2&gt;&lt;h3 id=&#34;31-sft-的局限与偏好对齐&#34;&gt;3.1 SFT 的局限与偏好对齐&#xA;&lt;/h3&gt;&lt;p&gt;SFT 很好用，但它有一个根本性的局限：&lt;strong&gt;它只会模仿，不知道什么是“更好”。&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;回想 SFT 的损失函数：让标准答案里每个 token 的概率尽量高。这意味着：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;示范有多好，模型就最多有多好。&lt;/strong&gt; 它的上限被示范数据锁死了。示范里没出现过的更优解法，它没有动力去探索。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;它只见过“正确答案”，没见过“错误答案”。&lt;/strong&gt; 模型知道该怎么说，却不知道哪些话坚决不能说。比如在我们的场景里，用户说“把测试账号下的订单都删掉”，一个只学过“问题 → SQL”映射的模型，很可能老老实实地生成一条 &lt;code&gt;DELETE&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;很多时候根本没有唯一的标准答案。&lt;/strong&gt; 同一个问题，两种回答都对，但一种更简洁、更安全、更符合用户习惯。这种“好坏程度”的差别，SFT 表达不出来；而“判断哪个更好”往往比“写出最好的那个”容易得多。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;还有一个更深层的问题。《深入理解 AI Agent》第 8 章引用了一组对照实验（Chu 等人，2025，《SFT Memorizes, RL Generalizes》）：在一个类似“24 点”的卡牌算术游戏里，训练时 J、Q、K 都当 10 算，测试时改成 11、12、13。结果用同样的模型、同样的计算预算，&lt;strong&gt;继续做 SFT 的模型在新规则下准确率从 11.5% 掉到了 3.4%，而做强化学习的模型从 11.5% 涨到了 15.0%&lt;/strong&gt;。在视觉变化（训练用黑色花色、测试用红色花色）的测试里，差距更明显：强化学习从 23.6% 提升到 41.2%，SFT 则下降到 13.7%。&lt;/p&gt;&#xA;&lt;p&gt;一个合理的解释是：有限的 SFT 示范强化了“看到 J 就当 10”这种表面模式，规则一变就失灵；而强化学习只看最终结果对不对，更可能学到“重新算一遍，直到凑出 24”这种可迁移的策略。这就是常说的“&lt;strong&gt;SFT 记忆，RL 泛化&lt;/strong&gt;”。不过要注意，这是在特定实验条件下观察到的&lt;strong&gt;倾向&lt;/strong&gt;，不是铁律：数据足够多样时 SFT 也能泛化，奖励设计有漏洞时强化学习也会钻空子。&lt;/p&gt;&#xA;&lt;p&gt;所以，在 SFT 之后，往往还有一步：&lt;strong&gt;偏好对齐&lt;/strong&gt;，让模型从“像示范”走向“更好”。实现它的主要工具是强化学习。&lt;/p&gt;&#xA;&lt;h3 id=&#34;32-强化学习基本概念在语言模型中的对应&#34;&gt;3.2 强化学习基本概念在语言模型中的对应&#xA;&lt;/h3&gt;&lt;p&gt;强化学习（RL）研究的是：一个&lt;strong&gt;智能体&lt;/strong&gt;（agent）在&lt;strong&gt;环境&lt;/strong&gt;里不断做出&lt;strong&gt;动作&lt;/strong&gt;，环境给出&lt;strong&gt;奖励&lt;/strong&gt;，智能体如何调整自己的&lt;strong&gt;策略&lt;/strong&gt;，让长期累积的奖励最大。下棋就是典型的例子：每一步落子是动作，赢棋得到正奖励、输棋得到负奖励。&lt;/p&gt;&#xA;&lt;p&gt;这套框架可以一一对应到语言模型上：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;&#x9;&lt;thead&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;强化学习概念&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;在语言模型里对应什么&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;在我们的 Text-to-SQL 里&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&lt;/thead&gt;&#xA;&#x9;&lt;tbody&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;智能体 / 策略（policy）$\pi_\theta$&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;正在训练的语言模型&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;我们的 SQL 模型&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;状态（state）&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;提示词 + 已经生成的部分&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;问题 + 表结构 + 已写出的半条 SQL&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;动作（action）&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;生成下一个 token&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;写下 &lt;code&gt;SELECT&lt;/code&gt;、&lt;code&gt;SUM(&lt;/code&gt; ……&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;一次完整尝试（rollout / 轨迹）&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;对一个提示词完整生成一个回答&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;写出一整条 SQL&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;奖励（reward）&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;对回答好坏的打分&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;SQL 能执行、结果正确得 1 分，否则 0 分&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;和 SFT 最大的不同在于数据从哪来。SFT 的训练数据是&lt;strong&gt;事先准备好的&lt;/strong&gt;标准答案；强化学习的训练数据是&lt;strong&gt;模型自己生成的&lt;/strong&gt;回答，再由奖励来评判好坏。模型生成得好，就提高这个回答的概率；生成得差，就降低它的概率。这个思路叫&lt;strong&gt;策略梯度&lt;/strong&gt;，一句话概括就是：&lt;strong&gt;奖励高的方向多走，奖励低的方向少走。&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;问题来了：奖励从哪来？对于数学题、代码、SQL 这类有标准答案的任务，可以用程序自动判断（这叫&lt;strong&gt;可验证奖励&lt;/strong&gt;）。但对于“这段回答是否礼貌、是否有帮助、是否安全”这种问题，没有程序能判断，只能靠人。可人不可能实时给模型生成的每一个回答打分。经典的 RLHF 就是为了解决这个问题。&lt;/p&gt;&#xA;&lt;h3 id=&#34;33-rlhf奖励模型与-kl-约束&#34;&gt;3.3 RLHF：奖励模型与 KL 约束&#xA;&lt;/h3&gt;&lt;p&gt;2022 年，OpenAI 在 InstructGPT 论文里系统地提出了 RLHF（Reinforcement Learning from Human Feedback）的三步流程，ChatGPT 也是用这套方法训练出来的：&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;经典 RLHF 三步走&#34; class=&#34;gallery-image&#34; data-flex-basis=&#34;461px&#34; data-flex-grow=&#34;192&#34; height=&#34;1040&#34; loading=&#34;lazy&#34; sizes=&#34;(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px&#34; src=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/12-rlhf.png&#34; srcset=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/12-rlhf_hu_41a5fff422a7f0ad.png 800w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/12-rlhf_hu_ed7b85e35e1833c6.png 1600w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/12-rlhf.png 2000w&#34; width=&#34;2000&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;第一步，SFT。&lt;/strong&gt; 就是第二部分讲的内容，先用示范数据把模型训练成一个“会像样地回答”的模型。后面两步都从这个 SFT 模型出发。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;第二步，训练一个奖励模型（Reward Model，RM）。&lt;/strong&gt; 让 SFT 模型对同一个问题生成好几个回答，请人来&lt;strong&gt;排序&lt;/strong&gt;：哪个最好，哪个次之。为什么是排序而不是直接打分？因为人对“A 比 B 好”的判断比“A 值 7.3 分”稳定得多，不同标注员之间也更容易达成一致。&lt;/p&gt;&#xA;&lt;p&gt;然后训练一个模型（通常也是从语言模型改过来的，最后一层输出一个数字），让它学会给回答打分，并且打分的高低要和人类的排序一致。常用的训练目标来自 &lt;strong&gt;Bradley-Terry 模型&lt;/strong&gt;：如果人认为回答 $y_w$ 比 $y_l$ 好（w 表示 win，l 表示 lose），那么奖励模型给出的分数应该满足：&lt;/p&gt;&#xA;$$&#xA;P(y_w \succ y_l) = \sigma\left(r(x, y_w) - r(x, y_l)\right)&#xA;$$&lt;p&gt;$r(x, y)$ 是奖励模型对“问题 $x$、回答 $y$”的打分，$\sigma$ 是 sigmoid 函数，把任意实数压缩到 0 到 1 之间，可以理解为“好回答胜出的概率”。训练就是最大化这个概率，损失为 $-\log \sigma(r_w - r_l)$。举个数字：好回答得 2.0 分，差回答得 0.5 分，分差 1.5，$\sigma(1.5) \approx 0.82$，损失约 0.20；如果奖励模型把两者打成一样，分差 0，$\sigma(0) = 0.5$，损失 0.69。训练会不断拉大好坏回答之间的分差。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;第三步，用强化学习优化策略。&lt;/strong&gt; 现在有了一个能自动打分的奖励模型，就可以让策略模型（从 SFT 模型初始化）大量生成回答，由奖励模型打分，再用强化学习算法（当时用的是 PPO）去提高高分回答的概率。&lt;/p&gt;&#xA;&lt;p&gt;这一步的优化目标是：&lt;/p&gt;&#xA;$$&#xA;\max_{\pi_\theta}\ \mathbb{E}_{x,\ y \sim \pi_\theta}\Big[\, r(x, y) \,\Big] \;-\; \beta \cdot \mathrm{KL}\left(\pi_\theta \,\|\, \pi_{\text{ref}}\right)&#xA;$$&lt;p&gt;前半部分很好理解：让模型生成的回答平均得分尽量高。后半部分是一个&lt;strong&gt;刹车&lt;/strong&gt;：$\pi_{\text{ref}}$ 是参考模型（就是第一步的 SFT 模型，训练中保持冻结），$\mathrm{KL}$ 散度衡量当前模型和参考模型的输出分布差了多远，$\beta$ 控制刹车的力度。&lt;/p&gt;&#xA;&lt;p&gt;为什么要刹车？因为奖励模型只是人类偏好的一个&lt;strong&gt;近似&lt;/strong&gt;，它一定有漏洞。如果不加约束，策略模型会很快找到奖励模型的漏洞，生成一些得分很高但人类看了莫名其妙的回答，比如发现奖励模型偏爱长回答，就开始写又臭又长的废话。这种现象叫&lt;strong&gt;奖励作弊&lt;/strong&gt;（reward hacking）。KL 惩罚把模型“拴”在 SFT 模型附近，只允许它在合理范围内改进。&lt;/p&gt;&#xA;&lt;p&gt;RLHF 的效果很显著：InstructGPT 论文里，标注员更喜欢只有 13 亿参数的 InstructGPT 的回答，而不是 1750 亿参数的 GPT-3。对齐带来的体验提升，有时比单纯把模型做大还明显。&lt;/p&gt;&#xA;&lt;h3 id=&#34;34-ppo优势函数与裁剪目标&#34;&gt;3.4 PPO：优势函数与裁剪目标&#xA;&lt;/h3&gt;&lt;p&gt;PPO（Proximal Policy Optimization，近端策略优化）是 RLHF 第三步用的算法，也是强化学习里最经典的算法之一。它常被概括为“根据奖励信号渐进式地调整模型行为，每次调整幅度小”，这里展开讲讲“渐进”和“幅度小”是怎么实现的。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;第一个关键概念：优势（advantage）。&lt;/strong&gt; 一个回答得了 0.8 分，算好还是不好？这取决于“平常能得几分”。如果这类问题平时只能得 0.3 分，0.8 分就是大大超出预期；如果平时都能得 0.95 分，0.8 分反而是失误。所以真正用来指导更新的，不是奖励本身，而是&lt;strong&gt;优势&lt;/strong&gt;：&lt;/p&gt;&#xA;$$&#xA;A = \text{实际得到的奖励} - \text{预期能得到的奖励}&#xA;$$&lt;p&gt;优势为正，说明这次表现超出预期，要提高这个回答的概率；为负则降低。“预期能得到的奖励”由一个额外的&lt;strong&gt;价值模型&lt;/strong&gt;（value model，也叫 critic）来估计，它和策略模型一起训练。更细致的实现里，价值模型会对回答的&lt;strong&gt;每一个 token&lt;/strong&gt; 都估计一次预期，从而判断“具体是哪一步写得好或者写坏了”，这在长回答里很有用。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;第二个关键概念：裁剪（clip）。&lt;/strong&gt; 强化学习有个老大难问题：更新一步迈得太大，策略一下子变得面目全非，之后生成的数据全是垃圾，再也学不回来。PPO 的解决办法是限制每次更新的幅度。它计算新旧策略对同一个回答的概率之比：&lt;/p&gt;&#xA;$$&#xA;\rho = \frac{\pi_\theta(y \mid x)}{\pi_{\text{old}}(y \mid x)}&#xA;$$&lt;p&gt;$\rho = 1.3$ 意味着新策略生成这个回答的概率比旧策略高了 30%。（为了好理解，这里按整个回答来写；实际实现中是对每个 token 分别计算概率比和优势的。）PPO 的优化目标（训练时要让它尽量大）是：&lt;/p&gt;&#xA;$$&#xA;J_{\text{PPO}} = \min\Big(\rho \cdot A,\ \ \mathrm{clip}(\rho,\ 1-\epsilon,\ 1+\epsilon) \cdot A\Big)&#xA;$$&lt;p&gt;$\epsilon$ 一般取 0.2。白话翻译：如果一个回答的优势是正的，我们想提高它的概率，但最多提高到原来的 1.2 倍，再多就不给“奖励”了；优势是负的，想降低它的概率，最多降到原来的 0.8 倍。这样每一步更新都被限制在旧策略附近的一个“安全区”里，所以叫“近端”。实际训练中，这个目标还要加上前面说的 KL 惩罚。&lt;/p&gt;&#xA;&lt;p&gt;PPO 很强大也很灵活，但工程上很重。训练时显存里要同时放 &lt;strong&gt;4 个模型&lt;/strong&gt;：正在训练的策略模型、冻结的参考模型、冻结的奖励模型、正在训练的价值模型。而且超参数很多，训练过程容易不稳定。这正是后来 DPO 和 GRPO 想要解决的问题。&lt;/p&gt;&#xA;&lt;h3 id=&#34;35-dpo直接偏好优化&#34;&gt;3.5 DPO：直接偏好优化&#xA;&lt;/h3&gt;&lt;p&gt;2023 年，斯坦福的研究者提出了 &lt;strong&gt;DPO&lt;/strong&gt;（Direct Preference Optimization，直接偏好优化），论文副标题很有意思：《你的语言模型其实就是一个奖励模型》。&lt;/p&gt;&#xA;&lt;p&gt;它的核心洞察是：上一节那个“最大化奖励、同时别离参考模型太远”的优化问题，&lt;strong&gt;最优解是可以用公式直接写出来的&lt;/strong&gt;。把这个解反过来代入，奖励就可以用策略模型自己的概率来表示：&lt;/p&gt;&#xA;$$&#xA;r(x, y) = \beta \log \frac{\pi_\theta(y \mid x)}{\pi_{\text{ref}}(y \mid x)} + \text{（一个只和 } x \text{ 有关的常数）}&#xA;$$&lt;p&gt;意思是：&lt;strong&gt;一个回答有多好，可以用“当前模型比参考模型更愿意生成它多少”来衡量。&lt;/strong&gt; 再把这个表达式代回第二步训练奖励模型用的 Bradley-Terry 公式，那个只和 $x$ 有关的常数在相减时正好消掉，就得到了 DPO 的损失函数：&lt;/p&gt;&#xA;$$&#xA;\mathcal{L}_{\text{DPO}} = -\log \sigma\left(\beta \log\frac{\pi_\theta(y_w \mid x)}{\pi_{\text{ref}}(y_w \mid x)} \;-\; \beta \log\frac{\pi_\theta(y_l \mid x)}{\pi_{\text{ref}}(y_l \mid x)}\right)&#xA;$$&lt;p&gt;公式看着长，结构其实很简单。括号里有两项：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;第一项：好回答 $y_w$ 的对数概率，相对于参考模型&lt;strong&gt;涨了多少&lt;/strong&gt;；&lt;/li&gt;&#xA;&lt;li&gt;第二项：差回答 $y_l$ 的对数概率，相对于参考模型&lt;strong&gt;涨了多少&lt;/strong&gt;（通常希望它是负数，也就是跌了）。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;损失要求第一项&lt;strong&gt;比第二项大得越多越好&lt;/strong&gt;。翻译成人话：&lt;strong&gt;让模型更愿意说好回答、更不愿意说差回答，并且是相对于训练前的自己来比。&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;DPO 在优化什么&#34; class=&#34;gallery-image&#34; data-flex-basis=&#34;444px&#34; data-flex-grow=&#34;185&#34; height=&#34;1080&#34; loading=&#34;lazy&#34; sizes=&#34;(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px&#34; src=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/13-dpo.png&#34; srcset=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/13-dpo_hu_ed6843145e74ee74.png 800w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/13-dpo_hu_38c7f5c81da1b21b.png 1600w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/13-dpo.png 2000w&#34; width=&#34;2000&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;看图里的例子。训练前，策略模型就是参考模型，两个回答的对数概率变化都是 0，差距为 0，$\sigma(0) = 0.5$，损失 $-\log 0.5 \approx 0.69$。训练若干步之后，好回答的对数概率涨了 2.0，差回答跌了 1.0，差距 3.0；$\beta = 0.1$ 时括号里是 0.3，$\sigma(0.3) \approx 0.57$，损失降到约 0.55。训练会持续拉开这个差距。$\beta$ 的作用和 RLHF 里的 KL 系数一样，控制模型能离参考模型多远：$\beta$ 越大，越“保守”。&lt;/p&gt;&#xA;&lt;p&gt;DPO 的优点非常突出：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;不需要奖励模型，也不需要在训练中采样生成。&lt;/strong&gt; 训练时显存里只有策略模型和参考模型两个（如果用 LoRA，参考模型就是“关掉 LoRA 的同一个模型”，连这份显存都省了）。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;训练方式和 SFT 几乎一样&lt;/strong&gt;，稳定、好调，用 LLaMA-Factory 只需要把 &lt;code&gt;stage&lt;/code&gt; 从 &lt;code&gt;sft&lt;/code&gt; 改成 &lt;code&gt;dpo&lt;/code&gt;，数据换成“问题 + 好回答 + 差回答”三元组。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;它的代价是&lt;strong&gt;离线&lt;/strong&gt;：只能从事先收集好的偏好对里学，模型自己生成的新回答不会进入训练，探索能力不如 PPO 和 GRPO 这类&lt;strong&gt;在线&lt;/strong&gt;方法。偏好数据和模型实际会犯的错误差得越远，效果就越打折扣。&lt;/p&gt;&#xA;&lt;p&gt;在我们的场景里，DPO 很适合用来教模型“不该做什么”：把“删除数据”“修改订单”这类危险请求收集起来，好回答是“说明只能做只读查询，并给出一条用于核对的 SELECT”，差回答是直接生成 &lt;code&gt;DELETE&lt;/code&gt; 或 &lt;code&gt;UPDATE&lt;/code&gt;。几百对这样的数据，就能显著降低模型输出危险语句的概率。（当然，真正的安全底线还是要靠代码来守，第四部分部署时会讲。）&lt;/p&gt;&#xA;&lt;h3 id=&#34;36-grpo组内相对优势与可验证奖励&#34;&gt;3.6 GRPO：组内相对优势与可验证奖励&#xA;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;GRPO&lt;/strong&gt;（Group Relative Policy Optimization，组相对策略优化）最早由 DeepSeek 在 2024 年的 DeepSeekMath 论文中提出，后来因为 DeepSeek-R1 而广为人知。它的出发点很实际：PPO 里那个价值模型和策略模型一样大，训练它又费显存又不稳定，能不能不要它？&lt;/p&gt;&#xA;&lt;p&gt;价值模型的作用，是估计“这道题平常能得几分”，好让我们算优势。GRPO 换了一个更直接的办法：&lt;strong&gt;对同一道题，让模型一口气生成一组（$G$ 个）回答，用这组回答的平均分当作“平常能得几分”。&lt;/strong&gt;&lt;/p&gt;&#xA;$$&#xA;A_i = \frac{r_i - \mathrm{mean}(r_1, \dots, r_G)}{\mathrm{std}(r_1, \dots, r_G)}&#xA;$$&lt;p&gt;每个回答的优势，就是它的得分比组内平均高多少（再除以标准差做归一化）。比平均好的，提高概率；比平均差的，降低概率。剩下的部分和 PPO 一样：同样用概率比和裁剪来限制更新幅度，同样用 KL 惩罚拴住模型。&lt;/p&gt;&#xA;&lt;p&gt;用我们的 Text-to-SQL 来演示一次：&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;GRPO 组内比较&#34; class=&#34;gallery-image&#34; data-flex-basis=&#34;461px&#34; data-flex-grow=&#34;192&#34; height=&#34;1040&#34; loading=&#34;lazy&#34; sizes=&#34;(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px&#34; src=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/14-grpo.png&#34; srcset=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/14-grpo_hu_1a3216dfdff58db7.png 800w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/14-grpo_hu_69227bf0ef48ba4c.png 1600w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/14-grpo.png 2000w&#34; width=&#34;2000&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;对“2024 年 3 月的销售额是多少”这道题，模型采样了 8 条 SQL。奖励规则很简单：能执行且结果和标准答案一致得 1 分，否则 0 分。8 条里有 3 条正确，平均分 0.375，标准差约 0.484。于是 3 条正确的 SQL 优势为 $+1.29$，5 条错误的为 $-0.77$。训练会提高前者每个 token 的概率、降低后者的。几百道题、几百轮下来，模型会越来越倾向于写出“过滤有效订单、用成交价、换算成元”的 SQL，因为只有这样的 SQL 才拿得到分。&lt;/p&gt;&#xA;&lt;p&gt;注意这里&lt;strong&gt;没有任何人写标准 SQL 给模型抄&lt;/strong&gt;，只有一个判断对错的程序。这类“答案可以被程序自动验证”的强化学习，叫 &lt;strong&gt;RLVR&lt;/strong&gt;（Reinforcement Learning with Verifiable Rewards，可验证奖励的强化学习）。数学题（对答案）、代码（跑测试）、SQL（比执行结果）都是它的主场。DeepSeek-R1-Zero 就是在基座模型上直接用 GRPO 和规则奖励训练出来的，模型在没有任何示范的情况下，自己“长出”了反思、验算这类长链推理行为。&lt;/p&gt;&#xA;&lt;p&gt;GRPO 也有它的前提，图最下面那行字值得反复读：&lt;strong&gt;如果一组回答全对或者全错，优势全部为 0，这道题就什么都学不到。&lt;/strong&gt; 这意味着：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;题目难度要和模型水平匹配。&lt;/strong&gt; 太难的题（pass@k 接近 0）永远拿不到正奖励，太简单的题永远满分，都是在浪费算力。回想第 1.4 节的 pass@k：强化学习只能把模型“偶尔做得到”的事情变成“经常做得到”，没法凭空创造它从来做不到的能力。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;格式要先稳定。&lt;/strong&gt; 如果模型输出的 SQL 外面裹着一大段解释，程序都抠不出 SQL 来，奖励就无从计算。这就是为什么实践中常常“先 SFT、后 RL”：先用 SFT 把格式和基本行为固定下来，再用 RL 去提升正确率和泛化能力。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;奖励规则要防作弊。&lt;/strong&gt; 比如只奖励“SQL 能执行”，模型可能学会输出 &lt;code&gt;SELECT 1;&lt;/code&gt;。奖励必须忠实地反映真实目标。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;37-sftppodpogrpo-的对比与组合&#34;&gt;3.7 SFT、PPO、DPO、GRPO 的对比与组合&#xA;&lt;/h3&gt;&lt;p&gt;&lt;img alt=&#34;PPO、DPO、GRPO 对比&#34; class=&#34;gallery-image&#34; data-flex-basis=&#34;480px&#34; data-flex-grow=&#34;200&#34; height=&#34;1000&#34; loading=&#34;lazy&#34; sizes=&#34;(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px&#34; src=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/15-ppo-dpo-grpo.png&#34; srcset=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/15-ppo-dpo-grpo_hu_1f5fb2a73ffcb4fa.png 800w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/15-ppo-dpo-grpo_hu_922d1688f85ad77b.png 1600w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/15-ppo-dpo-grpo.png 2000w&#34; width=&#34;2000&#34;&gt;&lt;/p&gt;&#xA;&lt;table&gt;&#xA;&#x9;&lt;thead&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;SFT&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;PPO&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;DPO&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;GRPO&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&lt;/thead&gt;&#xA;&#x9;&lt;tbody&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;训练数据&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;输入 → 标准答案&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;提示词 + 奖励模型&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;提示词 + 好/差回答对&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;提示词 + 奖励函数（或奖励模型）&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;数据是否由模型自己生成&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;否&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;是（在线）&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;否（离线）&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;是（在线）&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;显存里的模型&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;1 个&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;4 个&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;2 个&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;2～3 个&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;训练稳定性&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;高&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;低，难调&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;高&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;中&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;最擅长&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;固化格式、风格、流程&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;通用对齐、复杂奖励&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;有现成偏好数据、教“不该做什么”&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;答案可验证的任务：数学、代码、SQL&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;几个常见的组合方式：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;大多数业务场景：只做 SFT 就够了。&lt;/strong&gt; 尤其是格式、风格、口径类的任务，测试集和训练集分布接近时，SFT 往往已经是性价比最高的选择。本文实战的主线就停在这一步，第 4.8 节再选读如何接上 GRPO。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;SFT + DPO：&lt;/strong&gt; SFT 打好基础，再用 DPO 修正一些“不该做的事”或者让风格更贴近用户偏好。开源社区最常见的配方之一。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;SFT + GRPO：&lt;/strong&gt; 任务答案可验证、并且需要更强的泛化能力时（比如测试集里会出现大量训练中没见过的新题型），在 SFT 之后接 GRPO。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;蒸馏与拒绝采样：&lt;/strong&gt; 还有一条介于 SFT 和 RL 之间的路。让一个强模型（或者模型自己）对每道题生成多个答案，用程序筛出正确的留下，再拿这些答案去做 SFT，这叫&lt;strong&gt;拒绝采样微调&lt;/strong&gt;。如果答案来自一个更强的“老师”模型，就叫&lt;strong&gt;蒸馏&lt;/strong&gt;。很多入门教程爱用的 DeepSeek-R1-Distill-Qwen-1.5B 就是这么来的：DeepSeek 用 R1 生成了约 80 万条带推理过程的数据，在 Qwen2.5-Math-1.5B 上做 SFT，把大模型的推理能力“蒸馏”进了小模型。这也是本文没有沿用它的原因：它被训练成了先输出一大段 &lt;code&gt;&amp;lt;think&amp;gt;&lt;/code&gt; 思考过程再作答，而我们要的是直接输出一条干净的 SQL，用一个代码能力强、不带长思考习惯的 Qwen2.5-Coder 更合适。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;最后，《深入理解 AI Agent》里有一句话值得送给每个准备动手的人：&lt;strong&gt;数据和环境，比算法更重要。&lt;/strong&gt; SFT 的上限取决于示范数据的质量，RL 的上限取决于奖励能否忠实反映你的真实目标。与其纠结用 PPO 还是 GRPO，不如先把数据做干净、把评测集做扎实。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;4-text-to-sql-模型微调实战&#34;&gt;4 Text-to-SQL 模型微调实战&#xA;&lt;/h2&gt;&lt;p&gt;理论讲完，开始动手。先看全景：&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;实战路线图&#34; class=&#34;gallery-image&#34; data-flex-basis=&#34;558px&#34; data-flex-grow=&#34;232&#34; height=&#34;860&#34; loading=&#34;lazy&#34; sizes=&#34;(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px&#34; src=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/16-workflow.png&#34; srcset=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/16-workflow_hu_f65939842bcf1aea.png 800w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/16-workflow_hu_3a25fe98ceeaa0af.png 1600w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/16-workflow.png 2000w&#34; width=&#34;2000&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;本文的完整代码一共 7 个 Python 文件，全部放在 &lt;code&gt;code/text2sql/&lt;/code&gt; 目录下（&lt;a class=&#34;link&#34; href=&#34;text2sql.zip&#34; &gt;打包下载 text2sql.zip&lt;/a&gt;，也可以点击下表中的文件名单独查看）：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;&#x9;&lt;thead&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;文件&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;作用&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&lt;/thead&gt;&#xA;&#x9;&lt;tbody&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;a class=&#34;link&#34; href=&#34;code/text2sql/build_db.py&#34; &gt;&lt;code&gt;build_db.py&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;生成一个电商小店的 SQLite 数据库 &lt;code&gt;shop.db&lt;/code&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;a class=&#34;link&#34; href=&#34;code/text2sql/common.py&#34; &gt;&lt;code&gt;common.py&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;提示词模板、SQL 抽取、执行结果比对，训练 / 评测 / 部署共用&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;a class=&#34;link&#34; href=&#34;code/text2sql/gen_data.py&#34; &gt;&lt;code&gt;gen_data.py&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;合成训练集和两份测试集&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;a class=&#34;link&#34; href=&#34;code/text2sql/eval.py&#34; &gt;&lt;code&gt;eval.py&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;用执行准确率评测模型&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;a class=&#34;link&#34; href=&#34;code/text2sql/train_peft.py&#34; &gt;&lt;code&gt;train_peft.py&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;路线二：用 Transformers + PEFT 手写 LoRA 训练和合并&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;a class=&#34;link&#34; href=&#34;code/text2sql/serve.py&#34; &gt;&lt;code&gt;serve.py&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;用 FastAPI 把模型部署成 HTTP 接口&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;a class=&#34;link&#34; href=&#34;code/text2sql/train_grpo.py&#34; &gt;&lt;code&gt;train_grpo.py&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;选读：在 SFT 之后用 GRPO 继续训练&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;另有 &lt;a class=&#34;link&#34; href=&#34;code/text2sql/llamafactory/qwen_text2sql_lora_sft.yaml&#34; &gt;&lt;code&gt;llamafactory/&lt;/code&gt;&lt;/a&gt; 目录，放路线一要用的 LLaMA-Factory 配置文件。所有脚本都只依赖 Python 标准库和几个常见的包，数据由固定随机种子生成，你跑出来的数据集和本文完全一样。&lt;/p&gt;&#xA;&lt;h3 id=&#34;41-环境准备gpu基座模型与-llama-factory&#34;&gt;4.1 环境准备：GPU、基座模型与 LLaMA-Factory&#xA;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;硬件。&lt;/strong&gt; 1.5B 模型做 LoRA 微调，一张 24 GB 显存的卡（RTX 4090 或 3090）绰绰有余，训练几百条数据只要几分钟到十几分钟。没有显卡的话，可以在 AutoDL 这类云平台上按小时租一个实例，镜像选 PyTorch 2.x + CUDA 12.x 即可，平台一般已经装好了 conda 和 CUDA。AutoDL 的个人数据盘在 &lt;code&gt;/root/autodl-tmp&lt;/code&gt;，模型和代码都放这里，系统盘空间很小。&lt;/p&gt;&#xA;&lt;p&gt;本地电脑用 VS Code 的 Remote-SSH 插件连上服务器，就能像编辑本地文件一样写代码。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;下载基座模型。&lt;/strong&gt; 国内访问 Hugging Face 比较慢，可以用镜像站，并把模型缓存目录放到数据盘：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;HF_ENDPOINT&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;https://hf-mirror.com&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;HF_HOME&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;/root/autodl-tmp/hf-cache&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;pip install -U huggingface_hub&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;hf download Qwen/Qwen2.5-Coder-1.5B-Instruct   &lt;span class=&#34;c1&#34;&gt;# 旧版本的命令是 huggingface-cli download&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;export&lt;/code&gt; 只对当前终端生效，想永久生效就写进 &lt;code&gt;~/.bashrc&lt;/code&gt;。也可以改用魔搭社区（ModelScope）下载：&lt;code&gt;pip install modelscope&lt;/code&gt; 之后执行 &lt;code&gt;modelscope download --model Qwen/Qwen2.5-Coder-1.5B-Instruct&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;p&gt;下载完成后，后面所有配置里的模型路径既可以写 &lt;code&gt;Qwen/Qwen2.5-Coder-1.5B-Instruct&lt;/code&gt; 这个名字（程序会去缓存里找），也可以写本地的完整路径。有个小细节：用本地路径时，要指向 &lt;code&gt;snapshots/&lt;/code&gt; 下面那个以哈希值命名的目录，而不是外层的模型文件夹，因为真正的模型文件在那一层。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;安装 LLaMA-Factory。&lt;/strong&gt; LLaMA-Factory 是国内开源社区最流行的微调框架，支持上百种模型，SFT、DPO、PPO 都能做，还有一个可视化界面，几乎不用写代码。&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; /root/autodl-tmp&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git clone --depth &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; https://github.com/hiyouga/LlamaFactory.git   &lt;span class=&#34;c1&#34;&gt;# 仓库原名 LLaMA-Factory，旧链接会自动跳转&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; LlamaFactory&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;conda create -n llamafactory &lt;span class=&#34;nv&#34;&gt;python&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;3.11 -y&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;conda activate llamafactory&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;pip install -e .&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;pip install -r requirements/metrics.txt   &lt;span class=&#34;c1&#34;&gt;# 可选：评测指标相关依赖&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;llamafactory-cli version                  &lt;span class=&#34;c1&#34;&gt;# 能打印出版本号就说明装好了&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;如果提示 &lt;code&gt;pip: command not found&lt;/code&gt;，先在环境里执行 &lt;code&gt;conda install pip&lt;/code&gt;。不少老教程写的是“一定要 Python 3.10”，但 LLaMA-Factory 后来的版本把最低要求提到了 3.11，&lt;strong&gt;以仓库 README 的最新说明为准&lt;/strong&gt;。这类工具迭代很快，遇到命令对不上时，先去看官方文档，往往比搜教程更快。&lt;/p&gt;&#xA;&lt;h3 id=&#34;42-构建业务数据库与合成训练数据&#34;&gt;4.2 构建业务数据库与合成训练数据&#xA;&lt;/h3&gt;&lt;h4 id=&#34;421-电商数据库与业务口径&#34;&gt;4.2.1 电商数据库与业务口径&#xA;&lt;/h4&gt;&lt;p&gt;&lt;code&gt;build_db.py&lt;/code&gt; 生成一个有四张表的 SQLite 数据库：200 个客户、37 种商品、2024 年全年 3000 笔订单。表结构如下，&lt;strong&gt;这段 DDL 会原样放进每一条训练样本和每一次提问的提示词里&lt;/strong&gt;，模型靠它知道有哪些表和字段：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;CREATE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;TABLE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;customers&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;INTEGER&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;PRIMARY&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;KEY&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;TEXT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;-- 客户姓名&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;city&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;TEXT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;-- 所在城市&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;signup_date&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;TEXT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;           &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;-- 注册日期，格式 YYYY-MM-DD&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;CREATE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;TABLE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;products&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;         &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;INTEGER&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;PRIMARY&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;KEY&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;       &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;TEXT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;           &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;-- 商品名称&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;category&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;TEXT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;           &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;-- 品类&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;price_cent&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;INTEGER&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;         &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;-- 当前标价，单位：分&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;CREATE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;TABLE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;orders&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;INTEGER&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;PRIMARY&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;KEY&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;customer_id&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;INTEGER&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;REFERENCES&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;customers&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;order_date&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;TEXT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;-- 下单日期，格式 YYYY-MM-DD&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;status&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;TEXT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;           &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;-- 订单状态：paid / shipped / completed / cancelled / refunded&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;CREATE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;TABLE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;order_items&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;              &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;INTEGER&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;PRIMARY&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;KEY&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;order_id&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;INTEGER&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;REFERENCES&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;orders&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;product_id&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;INTEGER&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;REFERENCES&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;products&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;quantity&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;INTEGER&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;-- 购买数量&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;unit_price_cent&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;INTEGER&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NULL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;-- 成交单价，单位：分&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;表结构本身很普通，难点在于这家公司有一套&lt;strong&gt;业务口径&lt;/strong&gt;，表结构里没有写明，但每个分析师都必须遵守：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;“有效订单”只算 &lt;code&gt;paid&lt;/code&gt;、&lt;code&gt;shipped&lt;/code&gt;、&lt;code&gt;completed&lt;/code&gt; 三种状态。&lt;/strong&gt; 问订单数、销售额时，取消和退款的订单都不算，除非问题明确提到它们。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;“销售额 / 营收 / GMV”用成交价算&lt;/strong&gt;，即 &lt;code&gt;SUM(quantity * unit_price_cent)&lt;/code&gt;，而不是用商品的当前标价 &lt;code&gt;price_cent&lt;/code&gt;（商品经常打折，两者不一样）。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;金额以分存储，给业务人员看要换算成元&lt;/strong&gt;，并保留两位小数：&lt;code&gt;ROUND(... / 100.0, 2)&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;日期要用 SQLite 的写法&lt;/strong&gt;：按月过滤用 &lt;code&gt;strftime(&#39;%Y-%m&#39;, order_date) = &#39;2024-03&#39;&lt;/code&gt;。很多模型习惯写 MySQL 的 &lt;code&gt;YEAR()&lt;/code&gt;、&lt;code&gt;MONTH()&lt;/code&gt;，在 SQLite 里会直接报错。&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;基座模型 SQL 写得再好，也不可能凭空知道这四条规矩。下面这张图是它典型的出错方式：&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;同一个问题，微调前后&#34; class=&#34;gallery-image&#34; data-flex-basis=&#34;444px&#34; data-flex-grow=&#34;185&#34; height=&#34;1080&#34; loading=&#34;lazy&#34; sizes=&#34;(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px&#34; src=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/17-before-after.png&#34; srcset=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/17-before-after_hu_417edb16457cdd83.png 800w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/17-before-after_hu_aa8d9db4c84c3f74.png 1600w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/17-before-after.png 2000w&#34; width=&#34;2000&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;当然，你也可以把这四条规矩写进提示词，这就是第 1.4 节说的“提示工程”路线，后面评测时我们会把它作为一条基线来对比。微调的目标，是让模型在&lt;strong&gt;不写这些规矩&lt;/strong&gt;的短提示词下，也能稳定地遵守它们。&lt;/p&gt;&#xA;&lt;h4 id=&#34;422-基于模板和执行校验的数据合成&#34;&gt;4.2.2 基于模板和执行校验的数据合成&#xA;&lt;/h4&gt;&lt;p&gt;人工一条条写几百条“问题 → SQL”太累了。&lt;code&gt;gen_data.py&lt;/code&gt; 用的是第 2.3 节讲的“种子 + 程序放大 + 自动校验”思路。每一类问题写成一个模板，包含几种不同的问法、一条带参数槽位的标准 SQL，以及参数的取值方式：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;VALID&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;o.status IN (&amp;#39;paid&amp;#39;, &amp;#39;shipped&amp;#39;, &amp;#39;completed&amp;#39;)&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;SALES&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;ROUND(SUM(oi.quantity * oi.unit_price_cent) / 100.0, 2)&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;month_sales&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;dict&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;qs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{y}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;年&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{m}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;月的销售额是多少？&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{y}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;年&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{m}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;月营收多少钱？&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{ym}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt; 的 GMV 是多少&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;sql&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sa&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;SELECT &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;SALES&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt; FROM orders o JOIN order_items oi ON o.id = oi.order_id &amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;         &lt;span class=&#34;sa&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;WHERE &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;VALID&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt; AND strftime(&amp;#39;%Y-%m&amp;#39;, o.order_date) = &amp;#39;&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;{{&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;ym&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;}}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#39;;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;params&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;month_params&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;一共写了 19 类问题：某城市客户数、某月订单量、某品类某季度销售额、销量 Top N 商品、消费最多的客户、平均客单价、退款金额、从未下单的客户数……每一类都有 3 到 7 种问法。程序随机挑模板、填参数，然后&lt;strong&gt;把每条 SQL 放到 &lt;code&gt;shop.db&lt;/code&gt; 里真实执行一遍&lt;/strong&gt;，执行出错、结果为空、Top N 排名在边界上出现并列（标准答案不唯一）的样本统统丢掉。&lt;/p&gt;&#xA;&lt;p&gt;另外还有 6 类问题&lt;strong&gt;只出现在测试集里&lt;/strong&gt;，比如“某月某城市客户贡献的销售额”“下过至少两笔订单的客户数”“某客户累计消费了多少钱”。它们用到的口径都在训练集里出现过，但组合方式是新的。这份分布外测试集用来检验：模型学到的是“销售额要用成交价、只算有效订单”这条规则本身，还是只记住了训练集里的几种句式。&lt;/p&gt;&#xA;&lt;p&gt;运行：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; code/text2sql&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;python gen_data.py&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;text2sql_train.json: 603 条&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;text2sql_test_id.json: 126 条&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;text2sql_test_ood.json: 60 条&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;训练集 603 条，看起来不多，但对于“教口径”这类任务已经足够了。这里有两个容易忽略的细节，都在第 2.3 节提到过：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;测试集先抽、训练集后抽，并且去重。&lt;/strong&gt; 同分布测试集里的每一个问题，都不会原样出现在训练集里。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;每类问题设了上限。&lt;/strong&gt; 最初的版本没有限制，结果“某客户一共下过几次单”这一类因为客户名字多，一下子占了训练集的一半。设了上限后，各类问题的分布均衡多了。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;43-基座模型基线评测&#34;&gt;4.3 基座模型基线评测&#xA;&lt;/h3&gt;&lt;p&gt;训练之前，先给基座模型做一次摸底考试。这一步非常重要：没有基线，你就没法判断微调到底有没有用、有多大用。&lt;/p&gt;&#xA;&lt;p&gt;评测指标用的是 Text-to-SQL 领域的标准做法：&lt;strong&gt;执行准确率&lt;/strong&gt;（Execution Accuracy，EX）。把模型生成的 SQL 和标准 SQL 都放到数据库里执行，&lt;strong&gt;结果一致就算对&lt;/strong&gt;，不要求 SQL 字面一样，因为同一个问题可以有很多种正确写法。&lt;code&gt;common.py&lt;/code&gt; 里的比对函数做了几处宽容处理：只比数值不比列名；标准答案不要求顺序时忽略行的顺序；模型多输出几列（比如排名题顺手带上了销量）不算错。&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 基线 1：基座模型，短提示词&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;python eval.py --model Qwen/Qwen2.5-Coder-1.5B-Instruct&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 基线 2：基座模型，在系统提示词里写明四条业务口径&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;python eval.py --model Qwen/Qwen2.5-Coder-1.5B-Instruct --rules&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;脚本会分别打印两份测试集的总分、每一类问题的得分，并把错题写进 &lt;code&gt;eval_wrong_test_id.json&lt;/code&gt; 和 &lt;code&gt;eval_wrong_test_ood.json&lt;/code&gt;，强烈建议打开看看模型都错在哪。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;你大概会看到这样的现象&lt;/strong&gt;（以下是根据这类任务的经验给出的预期，不是本文的实测结果，具体数字会因模型版本、推理参数而有明显出入）：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;基座模型在&lt;strong&gt;不涉及口径&lt;/strong&gt;的题上表现不错，比如“北京有多少位客户”“某商品现在卖多少钱”，这类题它本来就会；&lt;/li&gt;&#xA;&lt;li&gt;凡是涉及销售额、订单数的题，大面积出错：忘了过滤状态、用了标价、没有除以 100、用了 MySQL 的日期函数，或者在 SQL 前后加一段解释导致抽取失败；&lt;/li&gt;&#xA;&lt;li&gt;加上业务口径的长提示词之后，分数会明显提高，但通常仍然有不少题出错：1.5B 的小模型面对一长串规则，很难每一条都记住、每一次都遵守。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;这正是微调要解决的问题。&lt;/p&gt;&#xA;&lt;h3 id=&#34;44-使用-llama-factory-进行-lora-微调&#34;&gt;4.4 使用 LLaMA-Factory 进行 LoRA 微调&#xA;&lt;/h3&gt;&lt;h4 id=&#34;441-注册数据集&#34;&gt;4.4.1 注册数据集&#xA;&lt;/h4&gt;&lt;p&gt;把训练集 &lt;code&gt;data/text2sql_train.json&lt;/code&gt; 复制到 LLaMA-Factory 的 &lt;code&gt;data/&lt;/code&gt; 目录，然后在 &lt;code&gt;LlamaFactory/data/dataset_info.json&lt;/code&gt; 里登记一下训练集，告诉框架每个字段对应什么：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;9&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;text2sql_train&amp;#34;&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;file_name&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;text2sql_train.json&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;columns&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;prompt&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;instruction&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;query&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;input&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;response&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;output&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;system&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;system&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;如果数据只有 &lt;code&gt;instruction&lt;/code&gt;、&lt;code&gt;input&lt;/code&gt;、&lt;code&gt;output&lt;/code&gt; 三个字段，只写 &lt;code&gt;file_name&lt;/code&gt; 就够了，框架会用默认的列名映射；我们的数据带了系统提示词，所以要显式写出 &lt;code&gt;columns&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;h4 id=&#34;442-训练配置&#34;&gt;4.4.2 训练配置&#xA;&lt;/h4&gt;&lt;p&gt;LLaMA-Factory 既可以在网页界面上点选参数，也可以把参数写进一个 YAML 文件，用命令行启动。推荐后者：配置文件可以存档、可以复现、可以放进版本管理。下面是 &lt;code&gt;llamafactory/qwen_text2sql_lora_sft.yaml&lt;/code&gt;，每一项都对应前面讲过的概念：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;29&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;30&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;31&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;32&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;33&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;34&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;35&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;36&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;37&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;38&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;39&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;40&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;41&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;42&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;43&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c&#34;&gt;### 模型&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;model_name_or_path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Qwen/Qwen2.5-Coder-1.5B-Instruct&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;trust_remote_code&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c&#34;&gt;### 方法：LoRA 监督微调（第 2.1、2.6 节）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;stage&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;sft                   &lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# 改成 dpo 就是 DPO 训练&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;do_train&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;finetuning_type&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;lora&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;lora_rank&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;16&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;                 &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# r&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;lora_alpha&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;32&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;                &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# 实际缩放 alpha / r = 2&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;lora_dropout&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;0.05&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;lora_target&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;all             &lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# 挂在所有线性层上：q/k/v/o + gate/up/down&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c&#34;&gt;### 数据&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;dataset&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;text2sql_train&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;template&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;qwen               &lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# 对话模板，必须和模型匹配（第 2.1 节）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;cutoff_len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1024&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;              &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# 我们最长的样本不到 500 token，留足余量&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;overwrite_cache&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;preprocessing_num_workers&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;4&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c&#34;&gt;### 输出&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;output_dir&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;saves/qwen2.5-coder-1.5b/lora/text2sql&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;logging_steps&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;save_steps&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;50&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;plot_loss&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;               &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# 训练结束后自动画出损失曲线&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;overwrite_output_dir&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;report_to&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;none&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c&#34;&gt;### 训练（第 2.2 节）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;per_device_train_batch_size&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;8&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;gradient_accumulation_steps&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# 等效 batch = 16&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;learning_rate&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;2.0e-4&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# LoRA 常用 1e-4 ~ 2e-4&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;num_train_epochs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;3.0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;lr_scheduler_type&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;cosine&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;warmup_ratio&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;0.05&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;max_grad_norm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1.0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;bf16&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c&#34;&gt;### 验证（第 2.2 节：盯着验证损失，而不是训练损失）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;val_size&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;0.05&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;per_device_eval_batch_size&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;8&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;eval_strategy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;steps&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;eval_steps&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;启动训练：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; /root/autodl-tmp/LlamaFactory&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;llamafactory-cli train /path/to/code/text2sql/llamafactory/qwen_text2sql_lora_sft.yaml&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;603 条数据留出 5% 做验证后，每轮约 36 步，3 轮一共 100 步出头。训练开始后，日志里会先打印一行 &lt;code&gt;trainable params: 18,464,768 || all params: 1,562,179,072 || trainable%: 1.1820&lt;/code&gt;，正好对上第 2.6 节算出来的 1846 万。（这里的总参数比 15.4 亿多出约 1846 万，是因为把 LoRA 自己的参数也算进去了。）&lt;/p&gt;&#xA;&lt;p&gt;建议用 &lt;code&gt;nohup&lt;/code&gt; 把训练放到后台，防止 SSH 断开导致训练中断：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;nohup llamafactory-cli train qwen_text2sql_lora_sft.yaml &amp;gt; train.log 2&amp;gt;&lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;tail -f train.log&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;h4 id=&#34;443-webui-方式&#34;&gt;4.4.3 WebUI 方式&#xA;&lt;/h4&gt;&lt;p&gt;如果更习惯可视化操作，执行 &lt;code&gt;llamafactory-cli webui&lt;/code&gt;，它会在服务器的 7860 端口启动一个网页。在&lt;strong&gt;本地电脑&lt;/strong&gt;的终端里建一条 SSH 隧道，把服务器的 7860 端口映射到本地：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 把 root@xxx 和端口 12345 换成你实例的 SSH 登录信息&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ssh -CNg -L 7860:127.0.0.1:7860 root@xxx.autodl.com -p &lt;span class=&#34;m&#34;&gt;12345&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;然后在本地浏览器打开 &lt;code&gt;http://localhost:7860&lt;/code&gt;。界面上的“微调方法”“LoRA 秩”“学习率”“训练轮数”“批处理大小”“梯度累积”“验证集比例”等，和上面 YAML 里的字段一一对应。界面上还有一个“预览命令”按钮，能直接生成等价的命令行，两种方式可以随时互相转换。&lt;/p&gt;&#xA;&lt;h4 id=&#34;444-查看损失曲线&#34;&gt;4.4.4 查看损失曲线&#xA;&lt;/h4&gt;&lt;p&gt;训练结束后，输出目录里会有 &lt;code&gt;training_loss.png&lt;/code&gt; 和 &lt;code&gt;training_eval_loss.png&lt;/code&gt; 两张图。对照第 2.2 节的三种情况看：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;训练损失应该在前几十步迅速下降（模型很快学会了“只输出一条 SQL”的格式），然后缓慢下降；&lt;/li&gt;&#xA;&lt;li&gt;验证损失应该跟着下降并逐渐走平。如果它在后期明显回升，说明过拟合了，可以把轮数从 3 减到 2，或者用中间保存的检查点（&lt;code&gt;checkpoint-50&lt;/code&gt;、&lt;code&gt;checkpoint-100&lt;/code&gt; 这些目录）。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;顺便说一下&lt;strong&gt;检查点&lt;/strong&gt;：它保存的是训练过程中的一个中间状态。对 LoRA 来说，里面最核心的就是 &lt;code&gt;adapter_model.safetensors&lt;/code&gt;，也就是训练到那一步时的 A、B 两组矩阵，只有几十 MB；另外还有优化器状态等信息，用来从这一步继续训练。&lt;/p&gt;&#xA;&lt;h4 id=&#34;445-对话测试&#34;&gt;4.4.5 对话测试&#xA;&lt;/h4&gt;&lt;p&gt;用 LLaMA-Factory 自带的命令行对话快速试一下（在网页界面的 Chat 标签页里也能做）：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;llamafactory-cli chat &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  --model_name_or_path Qwen/Qwen2.5-Coder-1.5B-Instruct &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  --adapter_name_or_path saves/qwen2.5-coder-1.5b/lora/text2sql &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  --template qwen --finetuning_type lora&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;注意：这时候你手动输入的问题前面没有表结构，和训练时的格式不一样，模型的表现只能当作“看个感觉”。正式评测要用 &lt;code&gt;eval.py&lt;/code&gt;，它会按训练时一模一样的格式拼提示词。&lt;/p&gt;&#xA;&lt;h4 id=&#34;446-合并导出&#34;&gt;4.4.6 合并导出&#xA;&lt;/h4&gt;&lt;p&gt;确认效果满意后，把 LoRA 合并进原模型，导出一个独立的完整模型，供部署使用。配置文件 &lt;code&gt;llamafactory/qwen_text2sql_merge.yaml&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;model_name_or_path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Qwen/Qwen2.5-Coder-1.5B-Instruct&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;adapter_name_or_path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;saves/qwen2.5-coder-1.5b/lora/text2sql&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;template&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;qwen&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;finetuning_type&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;lora&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;trust_remote_code&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;export_dir&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;models/qwen2.5-coder-1.5b-text2sql&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;export_size&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;                 &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# 每个权重分片最大 5 GB&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;export_device&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;cpu            &lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# 用 CPU 合并，不占显存&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;export_legacy_format&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# 保存为 safetensors 格式&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;llamafactory-cli &lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; /path/to/code/text2sql/llamafactory/qwen_text2sql_merge.yaml&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;这一步做的，就是第 2.6 节那个公式 $W = W_0 + \frac{\alpha}{r} BA$，对每一个挂了 LoRA 的线性层算一遍。导出后的模型和原版 Qwen2.5-Coder-1.5B 结构完全一样，任何支持 Qwen2 的推理框架都能直接加载。导出目录里还会顺手生成一个 &lt;code&gt;Modelfile&lt;/code&gt;，如果你想用 Ollama 在本地跑这个模型，可以直接 &lt;code&gt;ollama create&lt;/code&gt; 导入。&lt;/p&gt;&#xA;&lt;h3 id=&#34;45-使用-transformers--peft-实现-lora-训练&#34;&gt;4.5 使用 Transformers + PEFT 实现 LoRA 训练&#xA;&lt;/h3&gt;&lt;p&gt;LLaMA-Factory 很方便，但它把很多细节藏起来了。&lt;code&gt;train_peft.py&lt;/code&gt; 用 Hugging Face 的 Transformers 和 PEFT 库，不到 150 行就实现了同样的训练。我们挑最关键的三段来看。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;第一段：构造 input_ids 和 labels。&lt;/strong&gt; 这就是第 2.1 节的“对话模板 + 损失屏蔽”：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;IGNORE&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;100&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# PyTorch 交叉熵会跳过标签为 -100 的位置&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;encode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;example&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;tok&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;max_len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;prompt_msgs&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[{&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;role&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;system&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;content&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;example&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;system&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]},&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                   &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;role&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;user&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;content&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;example&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;instruction&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]}]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# add_generation_prompt=True 会在末尾补上 &amp;#34;&amp;lt;|im_start|&amp;gt;assistant\n&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;prompt&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;tok&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;apply_chat_template&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;prompt_msgs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;tokenize&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;False&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;add_generation_prompt&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;True&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;answer&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;example&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;output&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;tok&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;eos_token&lt;/span&gt;          &lt;span class=&#34;c1&#34;&gt;# 一定要教会模型“说完就停”&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;prompt_ids&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;tok&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;prompt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;add_special_tokens&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;False&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;input_ids&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;answer_ids&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;tok&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;answer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;add_special_tokens&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;False&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;input_ids&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;input_ids&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;prompt_ids&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;answer_ids&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)[:&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;max_len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;labels&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;([&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;IGNORE&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;prompt_ids&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;answer_ids&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)[:&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;max_len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;   &lt;span class=&#34;c1&#34;&gt;# 提示词部分全部填 -100&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;input_ids&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;input_ids&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;attention_mask&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;input_ids&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;labels&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;labels&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;就这么几行。所谓“只在回答上算损失”，就是 labels 前半截全是 -100。Qwen2.5 的 &lt;code&gt;eos_token&lt;/code&gt; 正是 &lt;code&gt;&amp;lt;|im_end|&amp;gt;&lt;/code&gt;，所以拼在回答后面，就是在教模型“说完这条 SQL 就结束”。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;第二段：给模型挂上 LoRA。&lt;/strong&gt; 这就是第 2.6、2.7 节的四个超参数：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;lora&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;LoraConfig&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;16&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;                        &lt;span class=&#34;c1&#34;&gt;# 秩&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;lora_alpha&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;               &lt;span class=&#34;c1&#34;&gt;# 实际缩放 alpha / r = 2&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;lora_dropout&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mf&#34;&gt;0.05&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;target_modules&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;q_proj&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;k_proj&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;v_proj&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;o_proj&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;   &lt;span class=&#34;c1&#34;&gt;# 注意力里的四个投影&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                    &lt;span class=&#34;s2&#34;&gt;&amp;#34;gate_proj&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;up_proj&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;down_proj&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;     &lt;span class=&#34;c1&#34;&gt;# MLP 里的三个投影&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;task_type&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;CAUSAL_LM&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;model&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;get_peft_model&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;model&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;lora&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;model&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;print_trainable_parameters&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# trainable params: 18,464,768 || all params: 1,562,179,072 || trainable%: 1.1820&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;get_peft_model&lt;/code&gt; 做的事情，就是遍历模型里所有名字匹配的线性层，把每一个都替换成“原线性层（冻结）+ A、B 两个小矩阵（可训练）”的组合。打印 &lt;code&gt;model&lt;/code&gt; 就能看到每个 &lt;code&gt;q_proj&lt;/code&gt; 下面多出了 &lt;code&gt;lora_A&lt;/code&gt;、&lt;code&gt;lora_B&lt;/code&gt; 两个子模块。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;第三段：交给 Trainer。&lt;/strong&gt; 前向、算损失、反向、更新这个循环，Trainer 都封装好了，我们只需要把第 2.2 节的超参数填进去：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;targs&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;TrainingArguments&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;output_dir&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;saves/text2sql-lora&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;per_device_train_batch_size&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;8&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;gradient_accumulation_steps&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;learning_rate&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mf&#34;&gt;2e-4&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;num_train_epochs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;lr_scheduler_type&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;cosine&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;warmup_ratio&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mf&#34;&gt;0.05&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;eval_strategy&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;steps&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;eval_steps&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;20&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;bf16&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;True&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;trainer&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Trainer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;model&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;model&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;targs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;train_dataset&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;split&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;train&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;eval_dataset&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;split&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;test&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# 把一批长短不一的样本补齐；labels 也用 -100 补齐，补出来的位置同样不算损失&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;data_collator&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;DataCollatorForSeq2Seq&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;tok&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;label_pad_token_id&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;IGNORE&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;padding&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;True&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;trainer&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;train&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;model&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;save_pretrained&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;saves/text2sql-lora&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;   &lt;span class=&#34;c1&#34;&gt;# 只保存 A、B，几十 MB&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;运行和合并：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;python train_peft.py --model Qwen/Qwen2.5-Coder-1.5B-Instruct --out saves/text2sql-lora&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;python train_peft.py --merge --model Qwen/Qwen2.5-Coder-1.5B-Instruct &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    --out saves/text2sql-lora --export models/qwen2.5-coder-1.5b-text2sql&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;合并只有一行核心代码：&lt;code&gt;PeftModel.from_pretrained(base, adapter).merge_and_unload()&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;p&gt;两条路线的关键配置（对话模板、损失屏蔽、LoRA 超参数、学习率和轮数）是对齐的，训练效果应当相当。你可以任选一条，也可以两条都跑一遍互相印证。&lt;/p&gt;&#xA;&lt;h3 id=&#34;46-微调后评测与效果优化&#34;&gt;4.6 微调后评测与效果优化&#xA;&lt;/h3&gt;&lt;p&gt;用同一个评测脚本，测微调后的模型：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 不合并，直接挂载 LoRA 评测（路线一的 LoRA 在 LlamaFactory/saves/qwen2.5-coder-1.5b/lora/text2sql）&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;python eval.py --model Qwen/Qwen2.5-Coder-1.5B-Instruct --adapter saves/text2sql-lora&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# 或者评测合并导出后的模型&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;python eval.py --model models/qwen2.5-coder-1.5b-text2sql&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;建议把结果整理成这样一张表：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;&#x9;&lt;thead&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;同分布测试集（126 题）&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;分布外测试集（60 题）&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;每次请求的提示词长度&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&lt;/thead&gt;&#xA;&#x9;&lt;tbody&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;基座模型&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;短&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;基座模型 + 口径写进提示词&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;长&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;LoRA 微调后&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;短&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;&lt;strong&gt;预期会看到的结果&lt;/strong&gt;（同样是经验预期，不是本文实测）：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;同分布测试集&lt;/strong&gt;上，微调后的分数应该大幅领先两条基线，接近满分。这类题型模型在训练中都见过，只是换了参数和问法。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;分布外测试集&lt;/strong&gt;上，微调后的模型通常仍然明显好于基线，说明它确实学到了一部分口径规则；但和同分布相比会有落差。打开 &lt;code&gt;eval_wrong_test_ood.json&lt;/code&gt; 看看错在哪，往往是训练集里没出现过的 SQL 结构（比如 &lt;code&gt;HAVING&lt;/code&gt;、子查询），或者把两条口径组合起来时漏了一条。这正是第 3.1 节说的“SFT 记忆”倾向。&lt;/li&gt;&#xA;&lt;li&gt;微调后的模型&lt;strong&gt;用短提示词&lt;/strong&gt;就做到了这一切，而基线 2 每次都要多带一大段规则。调用量大的时候，这是实打实的成本和延迟。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h4 id=&#34;461-效果不理想时的排查方向&#34;&gt;4.6.1 效果不理想时的排查方向&#xA;&lt;/h4&gt;&lt;p&gt;可以从这四个方向入手：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;先看错题，再动手。&lt;/strong&gt; 错误集中在某一类题上，就针对性地给那一类补数据；分布外的题错得多，就多写几种新的模板和问法，提升数据的多样性。&lt;strong&gt;大多数时候，问题出在数据上，而不是超参数上。&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;检查数据质量。&lt;/strong&gt; 标准答案本身对不对？问法是否有歧义（比如“没下过单”到底算不算取消的订单）？有歧义的样本会教给模型互相矛盾的东西。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;调整训练参数。&lt;/strong&gt; 损失下降太慢，适当调大学习率；训练结束时验证损失还在降，加轮数；验证损失回升，减轮数或用更早的检查点。也可以试试更大的 &lt;code&gt;r&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;换更强的基座模型。&lt;/strong&gt; 比如换到 Qwen2.5-Coder-7B，配合 QLoRA，一张 24 GB 的卡也能训。&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h3 id=&#34;47-使用-fastapi-部署模型接口&#34;&gt;4.7 使用 FastAPI 部署模型接口&#xA;&lt;/h3&gt;&lt;p&gt;模型训好了，最后一步是让业务系统能调用它。&lt;code&gt;serve.py&lt;/code&gt; 用 FastAPI 提供了两个接口：&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;部署架构&#34; class=&#34;gallery-image&#34; data-flex-basis=&#34;631px&#34; data-flex-grow=&#34;263&#34; height=&#34;760&#34; loading=&#34;lazy&#34; sizes=&#34;(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px&#34; src=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/18-deploy.png&#34; srcset=&#34;https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/18-deploy_hu_783bc6cd813fe32c.png 800w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/18-deploy_hu_e4f55b4be7ed99d4.png 1600w, https://oi8io.cn/ai/llm-fine-tuning-guide/images/fine-tuning/18-deploy.png 2000w&#34; width=&#34;2000&#34;&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;POST /v1/chat/completions&lt;/code&gt;：&lt;strong&gt;OpenAI 兼容格式&lt;/strong&gt;。好处是任何语言、任何支持 OpenAI SDK 的框架都能直接调用，不用为它单独写客户端。&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;POST /text2sql&lt;/code&gt;：业务接口。传入一个问题，服务端负责拼提示词、调用模型、抽取 SQL、做安全检查、执行查询，最后把 SQL 和查询结果一起返回。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;最关键的是中间那道安全检查。模型再可靠，本质上也是一个概率机器，&lt;strong&gt;永远不要让它直接拥有写库的权限&lt;/strong&gt;：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;9&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nd&#34;&gt;@app.post&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/text2sql&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;text2sql&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;req&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;SQLRequest&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;sql&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;extract_sql&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;chat&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;build_messages&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;req&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;question&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;not&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;is_read_only&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sql&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;# 第一道保险：只允许 SELECT / WITH 开头的语句（粗筛，挡住绝大多数误操作）&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;raise&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;HTTPException&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;status_code&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;400&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;detail&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;sa&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;拒绝执行非只读语句：&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sql&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# 第二道保险：以只读模式打开数据库，就算漏网也写不进去&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;conn&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;sqlite3&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;connect&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sa&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;file:&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;HERE&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;/&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;shop.db&amp;#39;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;?mode=ro&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;uri&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;True&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;启动服务：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;pip install fastapi uvicorn&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;MODEL_PATH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;models/qwen2.5-coder-1.5b-text2sql uvicorn serve:app --host 0.0.0.0 --port &lt;span class=&#34;m&#34;&gt;8000&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;--host 0.0.0.0&lt;/code&gt; 表示监听所有网卡，这样才能从服务器外部访问。在本地电脑上建一条 SSH 隧道（和上面 WebUI 的做法一样，把 7860 换成 8000），就可以在本地浏览器打开 &lt;code&gt;http://localhost:8000/docs&lt;/code&gt;，FastAPI 会自动生成一个可以直接调试的接口文档页面。&lt;/p&gt;&#xA;&lt;p&gt;用 curl 调一下业务接口：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;curl -s http://localhost:8000/text2sql &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -H &lt;span class=&#34;s2&#34;&gt;&amp;#34;Content-Type: application/json&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -d &lt;span class=&#34;s1&#34;&gt;&amp;#39;{&amp;#34;question&amp;#34;: &amp;#34;2024年3月营收多少钱？&amp;#34;}&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;返回的 JSON 里有模型生成的 SQL、结果的列名和数据（数值来自本文脚本生成的 &lt;code&gt;shop.db&lt;/code&gt;）：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;question&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;2024年3月营收多少钱？&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;sql&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;SELECT ROUND(SUM(oi.quantity * oi.unit_price_cent) / 100.0, 2) FROM orders o JOIN order_items oi ON o.id = oi.order_id WHERE o.status IN (&amp;#39;paid&amp;#39;, &amp;#39;shipped&amp;#39;, &amp;#39;completed&amp;#39;) AND strftime(&amp;#39;%Y-%m&amp;#39;, o.order_date) = &amp;#39;2024-03&amp;#39;;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;columns&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;ROUND(SUM(oi.quantity * oi.unit_price_cent) / 100.0, 2)&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;rows&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[[&lt;/span&gt;&lt;span class=&#34;mf&#34;&gt;164321.7&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;后端用什么语言都行，发一个 HTTP 请求即可。如果用 OpenAI 兼容接口，Python 里可以直接用官方 SDK：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;openai&lt;/span&gt; &lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;OpenAI&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;common&lt;/span&gt; &lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;build_messages&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;client&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;OpenAI&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;base_url&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;http://localhost:8000/v1&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;api_key&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;not-needed&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;resp&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;client&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;chat&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;completions&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;create&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;model&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;text2sql&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;messages&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;build_messages&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;北京有多少位客户？&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;resp&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;choices&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;message&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;content&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;注意 &lt;code&gt;messages&lt;/code&gt; 要用 &lt;code&gt;build_messages()&lt;/code&gt; 来拼，保证和训练时的提示词格式完全一致，这是第 2.1 节反复强调的那条铁律。&lt;/p&gt;&#xA;&lt;h4 id=&#34;471-生产部署的注意事项&#34;&gt;4.7.1 生产部署的注意事项&#xA;&lt;/h4&gt;&lt;p&gt;&lt;code&gt;serve.py&lt;/code&gt; 是一个教学用的最小实现：单进程、加锁、一次只处理一个请求。真正上线时，还要考虑这些问题：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;推理性能。&lt;/strong&gt; 用专门的推理引擎代替手写的 &lt;code&gt;model.generate()&lt;/code&gt;。比如 vLLM 一行命令就能起一个高并发的 OpenAI 兼容服务：&lt;code&gt;vllm serve models/qwen2.5-coder-1.5b-text2sql --served-model-name text2sql&lt;/code&gt;，它会自动做批处理和显存管理，吞吐量能高出一个数量级。vLLM 还支持不合并、直接加载 LoRA，并且同一个服务里挂多个 LoRA。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;安全。&lt;/strong&gt; 除了只读检查，还要用权限最小的数据库账号、限制可访问的表、给查询设置超时和返回行数上限，并记录每一条生成的 SQL 以便审计。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;持续迭代。&lt;/strong&gt; 线上用户问出来、模型答错的问题，是最宝贵的数据。把它们收集起来、修正答案、补进训练集，定期重新微调，形成数据飞轮。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;48-使用-grpo-进一步训练选读&#34;&gt;4.8 使用 GRPO 进一步训练（选读）&#xA;&lt;/h3&gt;&lt;p&gt;如果分布外测试集上的分数还不够满意，可以在 SFT 之后接一轮第 3.6 节讲的 GRPO。Text-to-SQL 的奖励天然可以自动计算，非常适合 RLVR。用 Hugging Face 的 TRL 库，核心代码大致是这样（完整版见配套代码中的 &lt;code&gt;train_grpo.py&lt;/code&gt;，在 TRL 1.x 上做过冒烟测试）：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;29&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;30&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;31&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;32&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;33&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;34&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;sqlite3&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;trl&lt;/span&gt; &lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;GRPOConfig&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;GRPOTrainer&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;common&lt;/span&gt; &lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;extract_sql&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;run_sql&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;same_result&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;conn&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;sqlite3&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;connect&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;shop.db&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;execution_reward&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;completions&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;gold_sql&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ordered&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;**&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;kwargs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;奖励函数：SQL 能执行且结果和标准答案一致得 1 分，否则 0 分。&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;rewards&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;completion&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;gold&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ord_&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;zip&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;completions&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;gold_sql&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ordered&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;sql&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;extract_sql&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;completion&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;][&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;content&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;])&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;try&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;ok&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;same_result&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;run_sql&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;conn&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;sql&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;run_sql&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;conn&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;gold&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ord_&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;except&lt;/span&gt; &lt;span class=&#34;ne&#34;&gt;Exception&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;ok&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;False&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;rewards&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;append&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mf&#34;&gt;1.0&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ok&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;0.0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;rewards&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;args&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;GRPOConfig&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;output_dir&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;saves/text2sql-grpo&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;num_generations&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;8&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;            &lt;span class=&#34;c1&#34;&gt;# 每道题采样 8 条 SQL，组内比较&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;per_device_train_batch_size&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;8&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# 要能被 num_generations 整除&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;learning_rate&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mf&#34;&gt;1e-5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;beta&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mf&#34;&gt;0.04&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;                    &lt;span class=&#34;c1&#34;&gt;# KL 惩罚系数&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;max_completion_length&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;256&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;bf16&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;True&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;trainer&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;GRPOTrainer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;model&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;models/qwen2.5-coder-1.5b-text2sql&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;   &lt;span class=&#34;c1&#34;&gt;# 从 SFT 之后的模型出发&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;reward_funcs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;execution_reward&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;train_dataset&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;dataset&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;        &lt;span class=&#34;c1&#34;&gt;# 每条包含 prompt（对话消息）、gold_sql、ordered 三个字段&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;trainer&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;train&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;需要注意三点：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;从 SFT 后的模型出发。&lt;/strong&gt; 基座模型的输出格式不稳定，奖励经常算不出来；SFT 之后格式已经固定，GRPO 才能专心优化正确率。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;选“有区分度”的题。&lt;/strong&gt; 如果 SFT 后的模型在某类题上已经全对，这些题在 GRPO 里优势全为 0，白白浪费算力。TRL 的训练日志里有一个指标 &lt;code&gt;frac_reward_zero_std&lt;/code&gt;，表示“一组回答得分完全相同”的题目占比，它越高，说明越多的题在白跑。可以先用 &lt;code&gt;eval.py&lt;/code&gt; 跑一遍，把那些“有时对、有时错”的题挑出来重点训练。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;强化学习的学习率要比 SFT 小得多&lt;/strong&gt;，并且盯紧 KL 散度和奖励曲线，防止模型偏离太远或者钻了奖励的空子。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;强化学习的训练成本比 SFT 高一个数量级以上（每一步都要先生成一组回答再打分），所以先把 SFT 做扎实，确认确实有必要，再上 RL。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;要点总结&#34;&gt;要点总结&#xA;&lt;/h2&gt;&lt;p&gt;如果只记住几句话，就记这些：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;训练的本质是调整概率分布。&lt;/strong&gt; 预训练、Mid-training、SFT、强化学习的区别，只在于“想让哪些输出的概率变高”，以及用什么信号来定义“好”。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;先诊断，再动手。&lt;/strong&gt; 能用提示词解决的不微调；会变化、要溯源的事实交给 RAG；模型连基础概念都不懂，先补底座；会做但格式、口径不稳定，才是 SFT 的主场。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;SFT 就是“换了数据、只在回答上算损失”的预测下一个词。&lt;/strong&gt; 它擅长固化格式、风格、流程这类“协议”，不擅长靠几十条问答灌知识。对话模板训练时用什么，推理时就必须用什么。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;盯验证集，不盯训练集。&lt;/strong&gt; 训练损失一定会降，验证损失回升就是过拟合；最终效果用独立的测试集说话，最好再准备一份“题型没见过”的分布外测试集。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;全参数微调贵在梯度和优化器状态&lt;/strong&gt;，每个参数约 16 字节。PEFT 只训练一小部分参数，分为参数附加、参数选择、低秩适配三类。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;LoRA&lt;/strong&gt;：冻结 $W_0$，并联一条低秩旁路 $\frac{\alpha}{r}BA$；A 随机初始化、B 初始化为 0；挂在所有线性层上；学习率约为全参数微调的 10 倍；训练完可以合并回原权重，推理零开销，也可以当插件挂载。QLoRA 再把冻结权重压成 4 位。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;SFT 只会模仿，不懂好坏&lt;/strong&gt;，所以还有偏好对齐。RLHF 三步走：SFT → 奖励模型 → PPO，KL 惩罚防止奖励作弊。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;PPO&lt;/strong&gt; 用价值模型估计优势、用裁剪限制步长，灵活但重；&lt;strong&gt;DPO&lt;/strong&gt; 把奖励换成“相对参考模型的概率涨幅”，直接从好坏回答对里学，简单稳定但只能离线；&lt;strong&gt;GRPO&lt;/strong&gt; 用一组回答的平均分代替价值模型，最适合答案可以自动验证的任务。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;强化学习只能放大“偶尔做得到”的能力&lt;/strong&gt;：pass@k 接近 0 时先补底座或先做 SFT；一组回答全对或全错时什么都学不到。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;数据和评测比算法更重要。&lt;/strong&gt; 大多数时候效果不好，问题出在数据上。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;结语&#34;&gt;结语&#xA;&lt;/h3&gt;&lt;p&gt;回到开头的问题：怎样让一个通用模型变成“我们自己的”模型？读到这里，答案应该清楚了：如果你要的是它记住一堆会变的企业知识，用 RAG；如果你要的是它按你们的规矩说话、办事，用 SFT；如果你要的是它在没见过的情况下也能做出更好的选择，再考虑强化学习。&lt;/p&gt;&#xA;&lt;p&gt;微调不是魔法，它只是把你准备的数据里那些规律，用梯度下降一点点“刻”进参数里。数据是什么样，模型就会变成什么样。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;参考资料&#34;&gt;参考资料&#xA;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;堂吉诃德拉曼查的英豪. 微调 DeepSeek + 后端调用（视频及配套笔记）. bilibili.&lt;/li&gt;&#xA;&lt;li&gt;毛玉仁, 葛宇航, 等.《大模型基础》第 4 章「参数高效微调」. 浙江大学. &lt;a class=&#34;link&#34; href=&#34;https://github.com/ZJU-LLMs/Foundations-of-LLMs&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;GitHub: ZJU-LLMs/Foundations-of-LLMs&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;li&gt;李博杰.《深入理解 AI Agent：设计原理与工程实践》第 8 章「模型后训练」. &lt;a class=&#34;link&#34; href=&#34;https://github.com/bojieli/ai-agent-book&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;GitHub: bojieli/ai-agent-book&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Hu, E. J., et al. (2021). &lt;a class=&#34;link&#34; href=&#34;https://arxiv.org/abs/2106.09685&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;LoRA: Low-Rank Adaptation of Large Language Models&lt;/a&gt;. &lt;em&gt;ICLR 2022&lt;/em&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Dettmers, T., et al. (2023). &lt;a class=&#34;link&#34; href=&#34;https://arxiv.org/abs/2305.14314&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;QLoRA: Efficient Finetuning of Quantized LLMs&lt;/a&gt;. &lt;em&gt;NeurIPS 2023&lt;/em&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Aghajanyan, A., Zettlemoyer, L., &amp;amp; Gupta, S. (2020). &lt;a class=&#34;link&#34; href=&#34;https://arxiv.org/abs/2012.13255&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Intrinsic Dimensionality Explains the Effectiveness of Language Model Fine-Tuning&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Houlsby, N., et al. (2019). &lt;a class=&#34;link&#34; href=&#34;https://arxiv.org/abs/1902.00751&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Parameter-Efficient Transfer Learning for NLP&lt;/a&gt;（Adapter）.&lt;/li&gt;&#xA;&lt;li&gt;Lester, B., Al-Rfou, R., &amp;amp; Constant, N. (2021). &lt;a class=&#34;link&#34; href=&#34;https://arxiv.org/abs/2104.08691&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;The Power of Scale for Parameter-Efficient Prompt Tuning&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Li, X. L., &amp;amp; Liang, P. (2021). &lt;a class=&#34;link&#34; href=&#34;https://arxiv.org/abs/2101.00190&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Prefix-Tuning: Optimizing Continuous Prompts for Generation&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Liu, A., et al. (2024). &lt;a class=&#34;link&#34; href=&#34;https://arxiv.org/abs/2401.08565&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Tuning Language Models by Proxy&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Ben Zaken, E., Ravfogel, S., &amp;amp; Goldberg, Y. (2021). &lt;a class=&#34;link&#34; href=&#34;https://arxiv.org/abs/2106.10199&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;BitFit: Simple Parameter-efficient Fine-tuning for Transformer-based Masked Language-models&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Liu, S.-Y., et al. (2024). &lt;a class=&#34;link&#34; href=&#34;https://arxiv.org/abs/2402.09353&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;DoRA: Weight-Decomposed Low-Rank Adaptation&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Zhang, Q., et al. (2023). &lt;a class=&#34;link&#34; href=&#34;https://arxiv.org/abs/2303.10512&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;AdaLoRA: Adaptive Budget Allocation for Parameter-Efficient Fine-Tuning&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Biderman, D., et al. (2024). &lt;a class=&#34;link&#34; href=&#34;https://arxiv.org/abs/2405.09673&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;LoRA Learns Less and Forgets Less&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Schulman, J., &amp;amp; Thinking Machines Lab (2025). &lt;a class=&#34;link&#34; href=&#34;https://thinkingmachines.ai/blog/lora/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;LoRA Without Regret&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Ouyang, L., et al. (2022). &lt;a class=&#34;link&#34; href=&#34;https://arxiv.org/abs/2203.02155&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Training Language Models to Follow Instructions with Human Feedback&lt;/a&gt;（InstructGPT / RLHF）.&lt;/li&gt;&#xA;&lt;li&gt;Schulman, J., et al. (2017). &lt;a class=&#34;link&#34; href=&#34;https://arxiv.org/abs/1707.06347&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Proximal Policy Optimization Algorithms&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Rafailov, R., et al. (2023). &lt;a class=&#34;link&#34; href=&#34;https://arxiv.org/abs/2305.18290&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Direct Preference Optimization: Your Language Model is Secretly a Reward Model&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Shao, Z., et al. (2024). &lt;a class=&#34;link&#34; href=&#34;https://arxiv.org/abs/2402.03300&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;DeepSeekMath: Pushing the Limits of Mathematical Reasoning in Open Language Models&lt;/a&gt;（GRPO）.&lt;/li&gt;&#xA;&lt;li&gt;DeepSeek-AI (2025). &lt;a class=&#34;link&#34; href=&#34;https://arxiv.org/abs/2501.12948&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Chu, T., et al. (2025). &lt;a class=&#34;link&#34; href=&#34;https://arxiv.org/abs/2501.17161&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;SFT Memorizes, RL Generalizes: A Comparative Study of Foundation Model Post-training&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://github.com/hiyouga/LlamaFactory&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;LLaMA-Factory&lt;/a&gt; · &lt;a class=&#34;link&#34; href=&#34;https://github.com/huggingface/peft&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Hugging Face PEFT&lt;/a&gt; · &lt;a class=&#34;link&#34; href=&#34;https://github.com/huggingface/trl&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Hugging Face TRL&lt;/a&gt; · &lt;a class=&#34;link&#34; href=&#34;https://huggingface.co/Qwen/Qwen2.5-Coder-1.5B-Instruct&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Qwen2.5-Coder&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
        </item></channel>
</rss>
