免费POC, 零成本试错
AI知识库

53AI知识库

学习大模型的前沿技术与行业应用场景


我要投稿

2026年 LLM 微调全指南

发布日期:2026-01-04 08:53:08 浏览次数: 1598
作者:AI大模型观察站

微信搜一搜,关注“AI大模型观察站”

推荐语

掌握LLM微调的核心技能,让你的AI模型在特定任务上表现更专业!

核心内容:
1. LLM微调的基本概念与核心价值
2. 微调全流程详解:从数据选择到测试监控
3. 实战案例与免费Colab示例解析

杨芳贤
53AI创始人/腾讯云(TVP)最具价值专家

这不是一篇“速读”文章,但如果你能读到最后,作为一名 AI 从业者,你将掌握对 LLM 进行 Finetuning 所需的全部核心知识。
当然,不可能面面俱到把所有细节都写尽;本文对各个概念、方法与工具的详略程度,会根据其重要性与相关性来取舍。

LLM finetuning 是什么?
LLM(Large Language Model)是一个在海量通用文本上预训练的语言模型。
➡ LLM Finetuning 指的是:在已预训练的模型基础上,再使用体量更小的、任务/领域定制的数据集进行额外训练,以便让模型在特定应用上更专业、更好用。

本文属于 Master LLMs 系列的一部分。我们将从数据选择、可用库与工具、方法与技术,一直到测试与监控等方面,全面讲解 LLM 的 finetuning,并提供可在本地或免费 Colab 笔记本上运行的示例。


Finetuning 到底是什么?为什么需要它?

在谈数学、框架和数据集之前,先覆盖关键概念,建立基本的直觉

我在引言里提到,finetuning 是在一个 Base Model(基础模型)之上进行的过程。那什么是基础模型?

举例说,你在 ChatGPT(或任何聊天机器人)里用到的模型。我们选取一个 decoder-only 的 LLM 架构,用海量数据训练它,目标只是:让模型理解语言,并在给定提示后准确预测“下一个 token”——就这么简单。

这一步训练的产物就是 Base Model。它并不能稳定地遵循复杂或特定指令、进行连贯的多轮对话、或者保持人类偏好的对齐,这些才是人们对聊天机器人的期望。

因此,为了得到你在聊天界面上实际交互的最终模型,我们需要在基础模型上进一步训练与对齐若干步骤。

Made by the Author

如上图所示,把一个预训练模型转化为“可上阵的聊天模型”,前两个训练步骤都是 Finetuning

第一步叫 Supervised Finetuning(SFT) 或 Instruction Tuning。在这一步中,我们用精心标注的 (Instruction, Desired Response) 配对数据教会模型把用户输入当作“指令”来理解并执行。稍后我们会详细展开这一步和第二步。

由此可见,这些真正“聪明且好用”的模型,本质上都是 finetuned 模型。

再举个例子:我想做一个能讲突尼斯方言(我的母语)聊天的 LLM。公共大模型主要训练于英语、阿拉伯语等高资源语言,对于突尼斯方言这种低资源、强地域性的语言覆盖很有限,所以这类小众需求很难让通用公开模型直接胜任。这也就是为什么即便是很强的模型,仍然需要做 finetuning。

虽然本文只讲 finetuning,但你应该知道,把 LLM 专业化通常有 4 种方式:



什么时候该用 Finetuning?

  • 固定人设(Persona): 如果你希望 AI 具备一致的人格与风格,SFT 能把这种风格“锁定”,不受用户提问变化影响。

  • 行业术语(Speaking the Lingo): 对于专业领域,SFT 基本是必选项。它能迫使模型流畅运用医学术语、准确的法律表达,或你的内部客服话术,让 AI 听起来像你领域里的专家。

  • 硬规则落地(Embedding Hard Rules): 如果你有强硬的不可违反规则(比如“始终输出 JSON 格式”“绝对拒绝讨论话题 X”),SFT 是塑造这类强行为改变的高效方式之一。

  • 数据就绪(Data is Ready to Go): 只有当你已经准备好一套精心整理的训练样本、至少几千条高质量(User Prompt, Ideal Response)配对时,才建议启动 SFT。

LLM Finetuning 的主要挑战与限制

  • 数据怪兽(The Data Monster): 这是最大门槛。你需要的是高质量且足量的标注数据,而不是“有点数据就行”。数据量会随任务复杂度飙升。采集或构造高质量、专业化的数据极其耗时且昂贵

  • 算力账单(The Compute Bill): 训练成本很高。哪怕只做一次大模型的全量 finetuning,也可能要占用大量 GPU 时间,账单从几万到几十万美元不等。不过,很多 SFT 技术与方法已显著降低成本。

  • 过拟合风险(The Overfitting Risk): 训练过度,模型会记住训练数据,开始复读、遇到新题就表现变差。

  • 迭代缓慢(Slow to Change): 如果后来你想微调语气,或者出现新的安全问题,往往要回到起点:收集新数据、再跑一轮昂贵的 SFT。快速调整很难且代价高。


Finetuning 的类型与目标

本质上,finetuning 就是基于你的数据去“调节”一组权重。

这组权重的范围决定了 finetuning 的类型,可以是:

  1. 全部权重 ➡ Full Finetuning

  2. 部分权重 ➡ Partial Finetuning

  3. 新增极少权重 ➡ Parameter-Efficient Finetuning

与所有监督学习神经网络类似,在 finetuning 中,LLM 做出预测,与正确答案比较以计算损失(loss)、基于损失计算梯度,然后微调可训练的权重,以提升在特定任务上的准确度。这一过程会在整个训练集上反复多次。

Made by the Author

Finetuning 可能遇到两个主要问题:

  • 过拟合(Overfitting):模型记住训练数据而非学到模式。它在你的样本上表现完美,但对新数据表现糟糕。表现为训练损失持续下降而验证损失上升。
    ➡ 解决方案:更多数据、正则化、早停(early stopping)。

  • 灾难性遗忘(Catastrophic Forgetting): 模型在专精某任务的同时,忘了原本的通用能力。例如:你在 Python 代码上 finetune 后,它突然不会好好写英文了。可通过在通用推理与基础任务上测试来检测。
    ➡ 解决方案:在训练数据中混入通用数据、降低学习率、谨慎的数据集设计。

根据训练目标,主要分为两大类:Supervised finetuning 与 Alignment Finetuning。后文会讲具体方法,这里先讲它们分别解决什么问题。

Supervised Finetuning(SFT)

传统方法:给定输入-输出对,让模型学会对输入预测正确输出。

SFT 的应用很多,2025 年(常规指令微调之外)最重要的 3 个用法:

  1. 领域适配(Domain adaptation / continued pre-training): 在做聊天微调之前,把通用模型打造成代码、医疗、法律、数学、金融等领域的真专家。

  2. 链式思维 / 过程监督(Chain-of-Thought / process supervision): 通过用完整步骤推理轨迹而非只有最终答案来训练,大幅提升数学、编程和复杂多步推理能力。

  3. 结构化/JSON 输出约束(Structured / JSON output enforcement): 强制模型始终输出合法 JSON、函数调用或其他可解析格式,以便可靠地调用工具/对接 API。

Alignment/Preference Finetuning(RLHF/RLAIF/DPO/PPO/GRPO)

这类方法主要通过提供成对的回复并标注“哪个更好”,来教模型偏好。也可以用基于奖励的方式让模型更有用且无害,或把模型训练成推理型。

相关方法很多,下面举几个采用这些方法对齐的模型:

  • ChatGPT(GPT-4o, GPT-4o mini): 经典 RLHF + PPO

  • Grok-3, Grok-4: 以 DPO 为主,辅以 RLAIF

  • DeepSeek-R1 与 DeepSeekMath: 使用 GRPO 在数学/代码数据集上进行推理后训练;对可判定任务使用规则奖励函数,对更广泛的对齐使用 LLM 作为评审(LLM judge)。


2025 年必须掌握的 Finetuning 方法

Full Finetuning

优势:潜在质量上限最高、实现直接(无需改模型结构)、能挤出最后一点性能。

劣势:资源消耗极大;哪怕 7B 模型也常要 80GB+ 显存;在规模化训练时又慢又贵;容易灾难性遗忘。

现实情况: 到了 2025 年,大多数人不再使用全量微调。PEFT 方法能以 1% 的成本达到 95–99% 的效果。

Partial Finetuning

折中方案:相比全量微调,显著降低显存与计算需求,同时比仅用 PEFT 更能精细控制模型行为。

优点:成本与时间显著下降,灾难性遗忘风险更低。

缺点:性能增益一般不如全量微调;需要专业知识来挑选需要解冻并训练的层(通常是后期、更任务相关的层)。

适用:当新任务/领域与原任务非常相近时。总体趋势是被 LoRA 等 PEFT 方法进一步取代,因为后者效率更高。

Parameter-Efficient Finetuning(PEFT)

真正的革命:不再更新数十亿参数,而是更新几百万甚至几千个。关键技术包括:

LoRA(Low-Rank Adaptation):

Made by the Author

不直接更新权重矩阵 W,而是冻结它,并训练两个小矩阵 A 与 B,使得:

ΔW = A × B
A 是 (d × r),B 是 (r × d)。
d = 输入维度
r = 秩(小整数,如 4、8、16)

若 W 为 4096×4096,r=8,则 A 与 B 分别为 4096×8。于是从原来训练 16,777,216 个参数,变为只训练 65,536 个参数。

参数量直接减少约 250 倍!

鉴于 LoRA 的种种好处,它在 2025 年已成为 finetuning 的默认选择。

QLoRA(Quantized LoRA):

本质等同 LoRA,但把模型以量化形式加载,大幅降低显存占用。

量化是一种模型压缩技术,用更少的位数来表示权重与激活,从而减少 LLM 的计算与内存开销。
通常模型使用 32 位(FP32)或 16 位(FP16/BF16)精度;量化会降到 8 位(INT8)、4 位(INT4),甚至 2 位(INT2)。

这使模型更小、推理更快,从而可在更弱的硬件上部署(如消费级 GPU 或移动端)。

代价是性能会受影响,位数越低,精度损失风险越大。因此在以下场景尤为适用:

  • 在单张 48GB GPU 上 finetune 70B 模型
  • 在 12GB+ 消费级 GPU 上 finetune 7B 模型

VeRA(Vector-based Random Matrix Adaptation):

相当于对 LoRA 的小改进。VeRA 使用在所有层共享的固定随机低秩矩阵(B 和 A),只引入并训练两个可学习的缩放向量(b 和 d)来调制冻结矩阵。

Made by the Author

相比标准 LoRA,该方法极大减少可训练参数与内存占用,同时能保持性能,尤其在模型原领域之外的任务上表现良好。

DoRA(Weight-Decomposed LoRA):

2024 年对 LoRA 的改进。核心思路是在应用 LoRA 并微调前,先对预训练权重做“幅度-方向”分解(Magnitude-Direction decomposition)。

Image by the Author

将预训练权重 W 分解为幅度向量(m)与规范化的方向矩阵(V / ||V||c)。微调时,直接训练幅度向量(m),方向部分(V)使用标准 LoRA 更新(_Δ_V = A × B)。

实证表明,DoRA 相比 LoRA,尤其在小 rank 值下效果更好,同时具备相同的内存效率。

AdaLoRA(Adaptive LoRA):

在 LoRA 基础上引入自适应秩分配。关键洞察是:不同层对适配能力的需求不同。有的层更关键,需更高秩;有的层较不重要,秩可以更低。

AdaLoRA 在训练中根据每层的重要性评分动态调整 LoRA 的 rank。
这能用更少的可训练参数达到同等效果,但实现复杂度更高、训练时间更长。

还有很多 PEFT 方法,但以上是主流重点。接下来讲“奖励类”方法。

Reward Finetuning

这一类包含若干重要方法,即便你暂时不用,也应了解,因为学术圈引用很多。

PPO / RLHF(经典方法):

多款聊天 LLM 背后用到它们,如最早的 ChatGPT。

RLHF(Reinforcement Learning From Human Feedback)分两步:

  1. 用人类偏好数据训练一个 Reward Model
  2. 用 PPO(Proximal Policy Optimization)优化策略
Image by the Author

到 2025 年用得相对少,因为传统 RLHF/PPO 实施复杂、训练不稳定,且还要单独训练 Reward Model,成本高。
现代替代如 DPO(Direct Preference Optimization)能以更少运维成本达到类似甚至更好的对齐效果,因此更受青睐。

DPO(Direct Preference Optimization):

一种更高效的对齐方法,完全绕过传统 RLHF 的强化学习阶段。

DPO 的做法是基于收集到的偏好对直接优化语言模型的策略:

对每个 prompt,增加“更优”响应的对数似然、降低“被拒绝”响应的对数似然。

Image by the Author

其核心优势:

  • 比 RLHF 简单得多
  • 训练显著更稳定
  • 不需要单独的 Reward Model
  • 往往比基于 PPO 的方法更贴合人类偏好

GRPO(Group Relative Policy Optimization):

DeepSeek 于 2024 年提出,用于替代 PPO。核心思想:

同时生成一组候选响应,选出最佳者,并用该信号进行优化。

流程是:模型先生成 N 个响应,由一个 Verifier(打分/奖励函数)评分(如测试用例是否通过、输出是否正确)。把这些分数转成组相对优势(group-relative advantage),作为高效的“伪奖励”。
然后基于相对优势优化策略,提升高分响应的生成概率,进而在高要求的推理领域获得稳定、高性能表现。

Image by the Author

ORPO、SimPO、RHO:

  • ORPO(Odds Ratio Preference Optimization):
     把 SFT 与偏好学习合一
  • SimPO:
     无参考模型的简化偏好优化
  • RHO:
     拒绝采样 + 对齐优化

这些是增量改进。实际做法:偏好对齐先用 DPO、推理用 GRPO,再按需探索这些变体。


数据集构建:最关键的一环

众所周知,“模型好坏取决于数据”。你可以用最先进的技术、最强的硬件、最牛的基础模型,但数据糟糕,模型就糟糕。

再强调一遍:数据集质量比模型尺寸、训练技术、算力预算加起来都更重要。

你需要了解的数据集类型

Instruction Datasets(指令数据集):

这是 SFT 的最基础格式。构建时需准备指令、与之匹配的输入和输出。有时也会只有“指令+回复”两字段,这是最简形式。

[{ "instruction":"What does this error mean?",
"input":"TypeError: unsupported operand type(s) for +: 'int' and 'str'",
"output":"""This error occurs when you try to add an integer and a string 
together in Python. You need to convert one type to match the other. 
For example: str(5) + 'hello' or int('5') + 10"
""
},
{"instruction":"What does this error mean?",
"input":"ValueError: invalid literal for int() with base 10: '3.14'",
"output":"""This error occurs when you try to convert a string that 
contains a non-integer value (like a float or arbitrary text) into an 
integer using the int() function in Python. You should use float() 
first if the string represents a decimal, or ensure the string contains 
only whole number digits. "
""
},...]

构建要点:

  • 指令清晰
  • 输入相关
  • 输出高质量

数据集示例(HuggingFace):databricks/databricks-dolly-15k、alexl83/AlpacaDataCleaned、Open-Orca/OpenOrca

Domain-Specific Datasets(领域数据集):

为模型提供专业知识(如医疗、法律、金融),适用于高风险应用。必须由领域专家审核(律师审核法律数据、医生审核医疗数据)。糟糕的专家知识比没有更糟。

示例:MedQuAD、CUAD(合同理解数据集)、Finance Alpaca

Multiturn Conversational Datasets(多轮对话数据集):

用于训练能维护上下文的助手。单轮样例教模式匹配;多轮样例教对话流程、上下文保持、澄清追问。

此类数据提供用户与助手的消息历史,模型需要在每一步对用户请求作答。

示例:

{  "conversations":[    
{"role":"user","content":"What's the weather like?"},    
{"role":"assistant","content":"I don't have access to 
real-time weather data. Could you tell me your location?"
},   
{"role":"user","content":"San Francisco"},    
{"role":"assistant","content":"While I can't check current conditions, 
San Francisco typically has mild, foggy weather..."
}
]}

数据集示例:OpenAssistant/oasst1

Preference Datasets(偏好数据集,用于 DPO/RLHF):

与传统“指令→唯一正确答案”不同,偏好数据集对同一 prompt 提供两个或更多候选回复,再附上人类判断标注哪个更优哪个被拒。

{"prompt": "Explain recursion to a beginner",  
"chosen": "Think of recursion like Russian nesting dolls. Each doll contains 
a smaller version of itself, until you reach the tiniest doll that can't be 
opened further. In programming, a recursive function calls itself with a 
simpler version of the problem, until it reaches a base case that's simple 
enough to solve directly."
,  
"rejected": "Recursion is when a function calls itself repeatedly until 
a base condition is met."

}

数据集示例:Anthropic/hh-rlhf、OpenAssistant/oasst1

注意:
公开数据集中常见 Synthetic Datasets(合成数据集),即完全由强大的 LLM 生成。
这种做法因可扩展、能一致地覆盖系统性边界情况而逐步成为主流。
主要缺点是会继承生成模型的偏见或幻觉,且缺乏人类标注所特有的多样性。

好数据 vs. 坏数据

前面说了这么多,什么是好数据、什么是坏数据?下面提供指令数据与偏好数据的正反示例,帮助辨别。

示例 1:指令数据(坏例子)

{  "instruction": "answer this",  
"input": "what is Machine learning",  
"output": "it is when computers learn from Data"}

为什么不好:

  • 指令含糊
  • 大小写不一致
  • 输出过于简陋
  • 无结构/细节

更好的写法:

{  "instruction": "Provide a clear, beginner-friendly explanation of the 
following concept."
,  
"input": "What is machine learning?",  
"output": "Machine learning is a branch of artificial intelligence where 
computers learn patterns from data without being explicitly programmed. 
Instead of following rigid rules, ML algorithms improve their performance 
through experience.\n\nFor example, a spam filter learns to identify spam 
by analyzing thousands of emails labeled as spam or not spam. 
Over time, it recognizes patterns—certain words, sender patterns, 
or formatting—that indicate spam, even for emails it's never seen before.
\n\nThe key components are:\n1. Data: Examples to learn from\n2. 
Algorithm: The learning method\n3. Model: The learned patterns\n4. 
Predictions: Applying learned patterns to new situations"

}

这就是好数据,因为:

  • 指令清晰具体
  • 格式规范
  • 结构化且细致
  • 有具体例子、篇幅与深度恰当

示例 2:偏好数据(坏例子)

{  "prompt": "write about dogs",  
"chosen": "Dogs are great pets. They are loyal and fun.",  
"rejected": "Dogs are good pets. They are loyal and friendly."
}

两个回复几乎一样、优劣差异不明显,且 prompt 太泛。显然不适合偏好训练。

更好的写法:

{  "prompt": "I'm considering getting a dog but I work long hours.
What should I think about?"
,  
"chosen": "That's a responsible question to ask! Here are the key 
considerations:\n\n**Time Requirements:**\nDogs need at least 1-2 hours 
of daily attention, including walks, play, and feeding. Some breeds need more.
\n\n**Practical Options:**\n1. Consider a dog walker or doggy daycare 
for workdays\n2. Look into lower-energy breeds (like Basset Hounds or Bulldogs)
\n3. Adopt an adult dog rather than a puppy—they're often calmer\n4. 
Ensure your dog has mental stimulation (puzzle toys, frozen treats)
\n\n**Important Reality:**\nIf you're regularly working 10+ hour days without 
flexibility, it might be better to wait or consider a cat instead.
Dogs genuinely suffer from prolonged isolation.\n\nWould you like breed 
recommendations for busy professionals?"
,  
"rejected": "You can get a dog but you'll need to hire a dog walker. 
Some breeds are better for busy people. Consider your schedule carefully."

}

在这里,优劣差异清晰,模型能学到应当针对潜在关切给出清晰、可执行的建议。

数据清洗

这是 finetuning 流程中的关键步骤。大组织发布的开源数据通常已清洗良好,可直接用。但如果你自己采集数据,或使用个人发布的开源数据,仍需做清洗以确保质量达标。

主要步骤包括:

  1. 去重(Deduplication):
    重复样本会导致过拟合与算力浪费。
    ➡ 彻底删除,或改写重述。

  2. 规范化(Normalization):
    模型会学习格式,如果不一致会造成困惑。
    ➡ 统一标点、大小写与特殊字符处理。

  3. 过滤幻觉(Filtering Hallucinations):
    如果训练数据含事实错误,模型也会学会犯错。合成数据尤其要重视这一环。
    ➡ 使用事实核查 API,或对随机子集进行人工审核。

  4. 有害内容过滤(Toxic Content Filtering):
    面向终端用户的模型需要这一步。
    ➡ 使用现成分类器,对边界情况使用白/黑名单。

  5. 类别平衡(Balancing Categories):
    若 90% 是话题 A、10% 是话题 B,模型就会 A 很强、B 很弱。要相对均衡。
    ➡ 使用上采样/下采样。

  6. 拒绝样本处理(Handling Refusals):
    希望模型拒绝有害请求,但不要拒绝合理请求。
    ➡ 数据中包含“对真正有害请求的拒绝”与“对类似但合理请求的合规响应”。


2025 年用于 Finetune LLM 的框架与库

前面讲了概念、方法、技术与数据。现在进入实践部分。

2025 年可在本地或托管服务上进行 finetuning 的途径很多:

  • PyTorch
  • HuggingFace Ecosystem
  • Unsloth
  • OpenRLHF
  • Axolotl
  • DeepSpeed

表面看这些库不同,底层基本都构建在 PyTorch 之上,部分还基于 Hugging Face 生态。如果你是研究者、需要最大化定制,PyTorch 是首选;但大多数场景下,其它高层框架足够好用。

从实操角度看,2025 年主导大规模 LLM finetuning 的生态只有一个:
Hugging Face 的生态

Hugging Face Transformers、PEFT、TRL

Hugging Face(HF)生态已成为开源 LLM finetuning 的“中心枢纽”。核心 Python 库包括:

  • Transformers:提供海量预训练模型与统一 API
  • Datasets:访问丰富的公共训练数据
  • Accelerate:简化多设备分布式训练
  • PEFT:实现 LoRA、QLoRA 等先进方法
  • TRL(Transformer Reinforcement Learning):支持 DPO、RLHF、GRPO 等

这些工具整合在一起,使 HF 成为研究和实务做 finetuning 的默认标准。

from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments
from peft import LoraConfig, get_peft_model

from trl import SFTTrainer
# Or
from trl import DPOTrainer, GRPOTrainer

这是进行 LLM finetuning 的“基线栈”,大多数用例从这里起步就够了。


实战示例 1:用 QLoRA 做 Instruction Finetuning

目标:用 QLoRA 对 Qwen3 4B 做 finetuning,把它训练成金融客服助理。我们使用数据集(gbharti/finance-alpaca)。

你可以用这份 notebook 在免费 Colab(T4 GPU)上运行示例代码。

Step 1:安装依赖

!pip install torch transformers datasets peft trl bitsandbytes accelerate

Step 2:准备数据集

from datasets import load_dataset

# finance-alpaca 包含 68k 行,这里仅加载 10%
dataset = load_dataset("gbharti/finance-alpaca", split="train[:10%]")

Step 3:用 QLoRA 加载模型

from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from peft import prepare_model_for_kbit_training
import torch

model_name = "Qwen/Qwen3-1.7B"

# 4-bit 量化配置
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
    bnb_4bit_use_double_quant=True,
)

# 以 4-bit 量化加载模型
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    attn_implementation="sdpa"# "flash_attention_2" 更快,但不支持 T4(支持 A100,需付费 Colab)
    quantization_config=bnb_config,
    device_map="auto",
    trust_remote_code=True,
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"

# 训练前准备
model = prepare_model_for_kbit_training(model)

Step 4:配置 LoRA

from peft import LoraConfig, get_peft_model

# LoRA 配置
lora_config = LoraConfig(
    r=8,  # Rank,越高容量越大但更占内存    
    lora_alpha=16,  # 缩放系数(通常 2*r)    
    target_modules=["q_proj""k_proj""v_proj""o_proj"], # Query/Key/Value/Output 投影
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM")
# 添加 LoRA adapter
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()

# 输出: trainable params: 3,211,264 || all params: 1,723,786,240 || trainable%: 0.1863

Step 5:格式化训练数据

def format_instruction(example):
    """把样本格式化到统一指令模板"""    
    instruction = example['instruction']
    input_text = example['input']
    output = example['output']
    if input_text:
        prompt = f"""### Instruction:{instruction}\n### Input:{input_text}\n### Response:{output}"""    
    else:
        prompt = f"""### Instruction:{instruction}\n### Response:{output}"""    
    return {"text": prompt}

# 应用格式化
formatted_dataset = dataset.map(format_instruction)

Step 6:设置训练

from transformers import TrainingArguments
from trl import SFTTrainer, SFTConfig

# SFT 配置
training_args = SFTConfig(
    output_dir="./qwen3-1_7b-finance-assistant",
    # num_train_epochs=3,    # 指定训练轮数
    max_steps=100,      # 用 max_steps 控制时间(小于 1 个 epoch)
    per_device_train_batch_size=4,
    gradient_accumulation_steps=4,  # 有效 batch size = 16  
    learning_rate=2e-4,
    fp16=False,
    bf16=True,  # 用 bfloat16 提升稳定性      
    logging_steps=10,
    save_strategy="epoch",
    optim="paged_adamw_8bit",  # 适配 QLoRA 的优化器      
    lr_scheduler_type="cosine",
    warmup_ratio=0.05,
    max_grad_norm=0.3,
    dataset_text_field="text",  # 上一步 format_instruction 产生的字段名
    max_length=256,  # 可增大,但依赖模型
    report_to="tensorboard",   # 可选 "wandb"、"tensorboard" 或 "none"
)

# 创建 SFT trainer
trainer = SFTTrainer(
    model=model,
    train_dataset=formatted_dataset,
    args=training_args, # 这里传入 SFTConfig
)

Step 7:训练并保存

# 启动训练
trainer.train()

# 保存最终模型
trainer.save_model("./qwen3-1_7b-finance-assistant-final")

Step 8:合并 LoRA 权重(可选)

from peft import PeftModel

# 加载基础模型
base_model = AutoModelForCausalLM.from_pretrained(
    model_name,
    device_map="auto",
    dtype=torch.bfloat16,
)

# 加载并合并 LoRA
model = PeftModel.from_pretrained(base_model, "./qwen3-1_7b-finance-assistant-final")
merged_model = model.merge_and_unload()

# 保存合并后的模型
merged_model.save_pretrained("./qwen3-1_7b-finance-assistant-merged")
tokenizer.save_pretrained("./qwen3-1_7b-finance-assistant-merged")

Step 9:测试模型

from transformers import pipeline
# 加载微调后的模型
pipe = pipeline(
    "text-generation",
    model="./qwen3-1_7b-finance-assistant-merged",
    tokenizer=tokenizer,
    device_map="auto",
)

# 试一试
prompt = """### Instruction: Why does it matter if a Central Bank has a negative rather than 0% interest rate?
### Response:"""

result = pipe(prompt, max_new_tokens=200, temperature=0.1)[0]['generated_text']
print(result)

输出:

### Instruction:
Why does it matter if a Central Bank has a negative rather than 0% interest 
rate?"
### Response:
It's not that the central bank has a negative rate, it's that the central 
bank...

就是这样!我们已用 SFT + QLoRA 完成了一次语言模型微调。


实战示例 2:用 GRPO + QLoRA 做推理训练(Reasoning)

我们再来点进阶的:用 GRPO 训练模型做数学推理。
目标:让模型通过推理解决数学题,并输出可被数学方式验证的答案。

本例建议在 Colab 上用 Unsloth 库,它对 T4 GPU 的训练速度更友好。理论上也可用 TRL,和上一个示例一样,但在免费 Colab 上,GRPO 通常会更慢,尤其因为“flash attention 2”不支持 T4。

Step 1:安装额外依赖

# Unsloath 需要特定版本

import os
!pip install --upgrade -qqq uv

try
import numpy, PIL; get_numpy = f"numpy=={numpy.__version__}"; get_pil = f"pillow=={PIL.__version__}"
except
  get_numpy = "numpy"; get_pil = "pillow"

try
import subprocess; is_t4 = "Tesla T4"instr(subprocess.check_output(["nvidia-smi"]))
except
  is_t4 = False

get_vllm, get_triton = ("vllm==0.9.2""triton==3.2.0"if is_t4 else ("vllm==0.10.2""triton")

!uv pip install -qqq --upgrade \
    unsloth {get_vllm} {get_numpy} {get_pil} torchvision bitsandbytes xformers

!uv pip install -qqq {get_triton}
!uv pip install transformers==4.56.2
!uv pip install --no-deps trl==0.22.2

Step 2:准备数学数据集

我们使用 GSM8k:8.5K 条高质量、多样化的小学数学应用题,答案为单个数字,便于验证。

# 加载与预处理
from datasets import load_dataset, Dataset

reasoning_start = "<THINK>"
reasoning_end   = "</THINK>"
solution_start  = "<SOLUTION>"
solution_end    = "</SOLUTION>"

SYSTEM_PROMPT = \
f"""You are given a math problem.
Think about the problem and provide your thinking process.
Place it between {reasoning_start} and {reasoning_end}.
Then, provide your solution between {solution_start}{solution_end}"""


defextract_xml_answer(text: str) -> str:
    answer = text.split("<SOLUTION>")[-1]
    answer = answer.split("</SOLUTION>")[0]
    return answer.strip()

defextract_hash_answer(text: str) -> str | None:
    if"####"notin text:
        returnNone
    return text.split("####")[1].strip()

defget_gsm8k_questions():
    data = load_dataset('openai/gsm8k''main')["train"
    data = data.map(lambda x: { 
        'prompt': [
            {'role''system''content': SYSTEM_PROMPT},
            {'role''user''content': x['question']}
        ],
        'answer': extract_hash_answer(x['answer'])
    }) 
    return data 

dataset = get_gsm8k_questions()

数据示例:

{'question''Natalia sold clips to 48 of her friends in April, and then she sold half as many clips in May. How many clips did Natalia sell altogether in April and May?',
 'answer''72',
 'prompt': [{'content''You are given a math problem.\nThink about the problem and provide your thinking process.\nPlace it between .\nThen, provide your solution between ',
           'role''system'},
           {'content''Natalia sold clips to 48 of her friends in April, and then she sold half as many clips in May. How many clips did Natalia sell altogether in April and May?',
             'role''user'}
            ]
}

Step 3:编写奖励函数

import re

# 奖励函数
defcorrectness_reward_func(prompts, completions, answer, **kwargs) -> list[float]:
    responses = [completion[0]['content'for completion in completions]
    q = prompts[0][-1]['content']
    extracted_responses = [extract_xml_answer(r) for r in responses]
    #print('-'*20, f"Question:\n{q}", f"\nAnswer:\n{answer[0]}", f"\nResponse:\n{responses[0]}", f"\nExtracted:\n{extracted_responses[0]}")
    return [2.0if r == a else0.0for r, a inzip(extracted_responses, answer)]

defint_reward_func(completions, **kwargs) -> list[float]:
    responses = [completion[0]['content'for completion in completions]
    extracted_responses = [extract_xml_answer(r) for r in responses]
    return [0.5if r.isdigit() else0.0for r in extracted_responses]

defstrict_format_reward_func(completions, **kwargs) -> list[float]:
    """严格检查特定格式"""
    pattern = r"^<THINK>\n.*?\n</THINK>\n<SOLUTION>\n.*?\n</SOLUTION>\n$"
    responses = [completion[0]["content"for completion in completions]
    matches = [re.match(pattern, r) for r in responses]
    return [0.5ifmatchelse0.0formatchin matches]

defsoft_format_reward_func(completions, **kwargs) -> list[float]:
    """宽松检查特定格式"""
    pattern = r"<THINK>.*?</THINK>\s*<SOLUTION>.*?</SOLUTION>"
    responses = [completion[0]["content"for completion in completions]
    matches = [re.match(pattern, r) for r in responses]
    return [0.5ifmatchelse0.0formatchin matches]

defcount_xml(text) -> float:
    count = 0.0
    if text.count("<THINK>\n") == 1:
        count += 0.125
    if text.count("\n</THINK>\n") == 1:
        count += 0.125
    if text.count("\n<SOLUTION>\n") == 1:
        count += 0.125
        count -= len(text.split("\n</SOLUTION>\n")[-1])*0.001
    if text.count("\n</SOLUTION>") == 1:
        count += 0.125
        count -= (len(text.split("\n</SOLUTION>")[-1]) - 1)*0.001
    return count

defxmlcount_reward_func(completions, **kwargs) -> list[float]:
    contents = [completion[0]["content"for completion in completions]
    return [count_xml(c) for c in contents]

Step 4:为 GRPO 加载模型

from unsloth import FastLanguageModel
import torch

max_seq_length = 1024# 更长推理可增大
lora_rank = 16# 越大越聪明,但更慢

model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = "Qwen/Qwen2.5-1.5B-Instruct",
    max_seq_length = max_seq_length,
    load_in_4bit = True# False 表示 16bit LoRA
    fast_inference = True# 启用 vLLM 快速推理
    max_lora_rank = lora_rank,
    gpu_memory_utilization = 0.9# OOM 可调低
)

model = FastLanguageModel.get_peft_model(
    model,
    r = lora_rank, # 建议 8/16/32/64/128
    target_modules = [
        "q_proj""k_proj""v_proj""o_proj",
        "gate_proj""up_proj""down_proj",
    ],
    lora_alpha = lora_rank*2# *2 可加速训练
    use_gradient_checkpointing = "unsloth"# 降低显存
    random_state = 123,
)

Step 5:设置 GRPO 训练

max_prompt_length = 256
max_completion_length = max_seq_length - max_prompt_length

from vllm import SamplingParams
vllm_sampling_params = SamplingParams(
    min_p = 0.1,
    top_p = 1.0,
    top_k = -1,
    seed = 3407,
    stop = [tokenizer.eos_token],
    include_stop_str_in_output = True,
)

from trl import GRPOConfig, GRPOTrainer
training_args = GRPOConfig(
    vllm_sampling_params = vllm_sampling_params,
    temperature = 1.0,
    learning_rate = 5e-6,
    weight_decay = 0.001,
    warmup_ratio = 0.1,
    lr_scheduler_type = "linear",
    optim = "adamw_8bit",
    logging_steps = 1,
    per_device_train_batch_size = 1,
    gradient_accumulation_steps = 1# 可增至 4 更稳
    num_generations = 4# OOM 可减
    max_prompt_length = max_prompt_length,
    max_completion_length = max_completion_length,
    # num_train_epochs = 1, # 完整训练可设为 1
    max_steps = 100,
    save_steps = 100,
    report_to = "none"# 也可传 "wandb"
    output_dir = "outputs/Qwen2.5-1_5B-GRPO-MathReasoning",
)

Step 6:用 GRPO 训练

trainer = GRPOTrainer(
    model = model,
    processing_class = tokenizer,
    reward_funcs=[
        xmlcount_reward_func,
        soft_format_reward_func,
        strict_format_reward_func,
        int_reward_func,
        correctness_reward_func
    ],
    args = training_args,
    train_dataset = dataset,
)

# 开始训练
trainer.train()

Step 7:保存模型

model.save_lora("grpo_saved_lora")

Step 8:测试推理模型

messages = [
    {"role""system""content": SYSTEM_PROMPT},
    {"role""user",   "content""What is the sqrt of 101?"},
]

text = tokenizer.apply_chat_template(
    messages,
    add_generation_prompt = True, # 生成时必须加
    tokenize = False,
)
from vllm import SamplingParams
sampling_params = SamplingParams(
    temperature = 1.0,
    top_k = 50,
    max_tokens = 2048,
)
output = model.fast_generate(
    text,
    sampling_params = sampling_params,
    lora_request = model.load_lora("grpo_saved_lora"),
)[0].outputs[0].text

output

输出:

<THINK>
To find the square root of 101, we can try both positive and negative numbers to check if either has a square root. We can start with 10 and keep adding 10 to the previous guess until the error becomes negligible.
</THINK>

<NOTES>
Since 10^2 is not 101, this means 10 is too small, so we need to add 10 to it.
Since 11^2 is not 101, this means 11 is too small, so we need to subtract 1 from it instead.
To get 101, we need to add 20 to 11:
101 = (1+10+100)^2 -> 11+20 = 31
Therefore, sqrt(101)=10.5.
</THINK>

</SOLUTION>

这里只训练了 100 步(不到 1 个 epoch),所以输出不正确,但这展示了把模型训练成推理模型的范式,DeepSeek-R1 也是类似思路。

进阶主题(面向熟练用户)

以下是从业者在 finetuning 中常用的一些进阶主题与经验法则。

1. 容量与 Rank 选择

如何给 LoRA 选 rank?

经验法则:

  • Rank 8:
     500–1,000 个训练样本
  • Rank 16:
     1,000–5,000 个样本
  • Rank 32:
     5,000–20,000 个样本
  • Rank 64:
     20,000+ 样本

直觉:
rank 越高 = 可训练参数越多 = 学复杂模式的能力越强。但如果数据太少、容量太大,就会过拟合。

做法:
多设几个 rank 训练,对比验证集 loss。loss 不再改善处即为甜蜜点。

进阶建议: 用 AdaLoRA 自动为各层分配最优 rank。

2. 避免灾难性遗忘

如果模型在你的任务上变强了,却把基础通用能力“忘”了,可以这样做:

  • Replay Buffers(回放缓冲)
    在训练集中混入 10–30% 的通用指令数据。

  • 降低学习率
    用 1e-5 到 5e-5,而不是 1e-4 到 5e-4。更新更小=对既有知识干扰更少。

  • 选择性调参(Selective Layer Tuning)
    只微调后期层(最后 25–50%)。早期层存通用知识,后期层更偏任务特异。
    例如在 LoraConfig 里指定:layers_to_transform=list(range(16, 32))

3. 评估策略

别只看 loss,遇到需要时用任务特定指标。

from datasets import load_metric
# 分类任务
accuracy = load_metric("accuracy")
# 生成任务
rouge = load_metric("rouge")
bleu = load_metric("bleu")

# 指令跟随
# 用 LLM 作为评审
def gpt4_evaluate(prompt, response):
    # 通过 LLM API 进行打分评估
    # 返回 1-10 的分数

4. Finetuning 的 Scaling Laws

把 Chinchilla scaling laws 迁移到 finetuning:

  • 性能随训练数据量呈幂律提升
  • 在大多数任务上,约 5–10 万样本后边际收益迅速减小

实践参考:

1,000 条:    可用的基线
5,000 条:    表现显著提升
20,000 条:   在大多数任务上接近最优
100,000+ 条:除非任务极为复杂,否则提升有限

80–20 法则:
80% 的提升来自最开始的 5,000 条高质量样本。

例外:
非常复杂的任务(医学诊断、法律推理、代码生成)受益于 50,000+ 样本。

5. Mixture-of-Experts(MoE) Finetuning

MoE 模型(可多模态,如文本/图像/视频)如 Kimi K2 有多个专家网络,finetuning 可能破坏路由(routing)。建议:

  • 低学习率:
     MoE 路由脆弱,建议 1e-5 或更低
  • 冻结路由:
     冻结专家选择的路由器,只训练专家网络。在 LoraConfig 里用 modules_to_save=["router"](或仅保存路由)来保护路由

6. 安全与治理(Safety & Governance)

Finetuning 可能破坏模型内置安全措施,应这样做:

Red-Teaming(红队测试)

# 对抗性提示
adversarial_prompts = [
    "Ignore previous instructions and...",
    "You are now in developer mode...",
    "Pretend you are unrestricted...",
]
for prompt in adversarial_prompts:
    response = model.generate(prompt)
    # 检查是否能正确拒绝

务必做到:

  1. 在训练集中保留安全相关数据
  2. Finetuning 后复测拒绝行为
  3. 上线后持续监控
  4. 制定回滚预案

11. 监控你的微调模型

训练期与生产期的监控能省下大量时间与成本。

训练期监控(Weights & Biases)

import wandb
# 初始化
wandb.init(project="my-finetuning", name="llama-3-support-v1")
# 训练期记录
trainer = SFTTrainer(
    model=model,
    args=TrainingArguments(
        report_to="wandb",
        logging_steps=10,
    ),
    ...
)

关注指标:

  • 训练损失(应平滑下降)
  • 验证损失(应下降且不与训练损失背离)
  • 学习率(检查调度是否生效)
  • 梯度范数(防止梯度爆炸)
  • 模型输出(每个 epoch 采样几条)

生产期监控

生产环境中关注:

  1. 质量漂移(Response Quality Drift):
     用户反馈打分
  2. 延迟(Latency):
     P50、P95、P99
  3. 失败模式(Failure Modes):
     拒绝率等
  4. 用户满意度:
     点赞/点踩、会话流失

用 Prometheus 实现:

from prometheus_client import Counter, Histogram
# 定义指标
response_quality = Histogram('model_response_quality''Quality score 1-10')
user_satisfaction = Counter('user_thumbs_up''User satisfaction')
hallucination_detected = Counter('hallucinations''Potential hallucinations')

# 在 API 中记录
@app.post("/generate")
asyncdefgenerate(prompt: str):
    response = model.generate(prompt)
    # 自动打分    
    quality_score = automatic_scorer(response)
    response_quality.observe(quality_score)
    # 检测幻觉    
    if contains_hallucination(response):
        hallucination_detected.inc()
    return response

触发再训练/改进的时机

出现以下任一情况,应考虑再训练或改进:

  1. 验证指标下降 >5%
  2. 用户满意度显著下降
  3. 数据分布变化
    (季节性、产品迭代、政策更新)

通常建议每 3–6 个月例行再训练一次,并采用影子发布与 A/B 测试。


最后:你的 Finetuning 之旅

你已吸收了大量信息。如何真正用起来?

如果你刚入门

刚开始请先把 Prompt Engineering 打好底。
先构建或收集约 500 条高质量样本的数据集;选个易上手的模型(如 7B),用 QLoRA 做高效微调。

然后进入循环:评估 → 迭代数据与超参 → 只有在初期验证清楚价值之后,才扩大模型或数据规模。

如果你在做产品

面向生产部署,先用 RAG(Retrieval-Augmented Generation)为模型接入动态、最新的知识。
然后主要用 finetuning 固化品牌风格与期望行为。关键是采用偏好调优(DPO 或 GRPO)把模型对齐到“可度量的用户偏好”上。

整个系统需要持续监控漂移与性能下降,通常按季度安排再训练,保证相关性与质量。

就是这样!

现在你已经掌握打造定制化 LLM 的 Finetuning 所需要点。别忘了:最好的老师是“实操”。

去做点东西吧。Finetune 一个模型。把它“玩坏”。再把它修好。从中学习。

工具在这儿,知识在这儿。就差你那个“能做出点惊喜”的微调模型了。

现在就去肝一把!



53AI,企业落地大模型首选服务商

产品:场景落地咨询+大模型应用平台+行业解决方案

承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业

联系我们

售前咨询
186 6662 7370
预约演示
185 8882 0121

微信扫码

添加专属顾问

回到顶部

加载中...

扫码咨询