Kaze
Kaze
Published on 2026-01-16 / 2 Visits
0
0

Qwen3 高效微调实战

#AI

Qwen3 高效微调实战

1. 核心理论入门:为什么选择高效微调?

1.1 引言:微调的战略价值

在当今的 AI 应用开发浪潮中,大模型微调(Fine-tuning)已成为一项核心技术。它不同于通过 RAG 或 Agent 搭建工作流来优化模型表现,微调直接通过修改模型参数来优化其内在能力。这使得微调成为一种能够让通用大模型“永久”掌握特定领域知识或技能的强大手段。本章节将深入探讨微调的基本原理,对比不同的微调策略,并解析当前最主流的高效微调技术,为后续的实战操作奠定坚实的理论基础。

1.2 微调的基本概念与分类

什么是大模型微调?

通俗来讲,大模型微调指的是在已有的、经过大规模数据预训练的模型基础上,利用特定任务的标注数据进行进一步训练,以优化模型在特定场景下的表现。

我们可以用一个生活化的比喻来理解:这就像是让一位知识渊博的通才教授去学习一个非常具体的新兴领域,而不是让他从小学一年级从头再来。模型已经具备了广泛的语言理解和生成能力,微调的目的就是让它将这些通用能力聚焦并应用于我们的特定需求上。

全量微调 vs. 高效微调

微调技术主要分为两大类:全量微调(Full Fine-Tuning)和高效微调(Parameter-Efficient Fine-Tuning, PEFT)。它们在资源消耗和对模型的改造程度上有着显著区别。

特性 全量微调 (Full Fine-Tuning) 高效微调 (Parameter-Efficient Fine-Tuning)
核心区别 训练模型的所有参数 仅训练一小部分新增或指定的参数
算力消耗 极高,需要大量 GPU 显存和计算时间 较低,普通消费级显卡即可完成
改造程度 对模型能力改造更彻底,但也更容易过拟合 类似**“四两拨千斤”**,精准提升特定能力

高效微调通过只修改模型的一小部分参数,来调整模型的整体能力,极大地降低了硬件门槛,使得个人开发者和中小型团队也能够驾驭强大的基础模型。

微调是双刃剑?

尽管模型微调能以“永久”的方式改变模型能力,但这其实是一把双刃剑。如果数据集或训练流程设计不当,极易引发“灾难性遗忘”(Catastrophic Forgetting)——即模型在学习新知识的同时,忘记了其原有的通用能力。因此,精心设计微调数据集、审慎选择训练参数,并进行反复的实验验证,是确保微调成功的关键。

1.3 深入 LoRA 与 QLoRA:高效微调的主流技术

为什么 LoRA 是目前最主流的高效微调方法?

虽然高效微调的方法有很多,但 LoRA (Low-Rank Adaptation) 凭借其在算力消耗和技术门槛上的巨大优势,成为了当前大模型领域最主流的选择。它巧妙地平衡了微调效果与资源开销,让高效微调变得前所未有的普及。

LoRA 原理解析

LoRA 的核心思想非常巧妙:它不直接调整原始模型的庞大权重矩阵,而是在模型的特定层(如线性层)旁边插入两个小型的、可训练的低秩适配器(Adapter)层。在微调过程中,原始模型的参数被冻结,只有这些新增的适配器参数会被更新。

这种方法的优势显而易见:

  • 显存优化:需要训练的参数量极少(通常不到原始模型的 1%),显著降低了对 GPU 显存的需求。
  • 计算效率:由于更新的参数量大幅减少,训练过程中的计算负担也大大减轻,训练速度更快。
  • 灵活性:训练完成后,适配器可以作为独立的“插件”进行保存和加载。同一个基础模型可以搭配不同的 LoRA 适配器,以适应不同的任务,而无需保存多个完整的模型副本。

QLoRA:极致的显存优化

QLoRA (Quantized Low-Rank Adaptation) 是 LoRA 的一个强大扩展,其核心思想是将低秩适配器技术与模型量化技术相结合,以实现极致的资源优化。

具体来说,QLoRA 在加载基础模型时,会将其庞大的权重参数从标准的 16 位浮点数(FP16)量化为 4 位整数(INT4),并冻结它们。微调时,仅在这些量化的层之上插入并训练 LoRA 适配器。这种方法极大地降低了基础模型在内存中的占用,从而可以在显存极为有限的设备上微调更大规模的模型。

LoRA 与 QLoRA 对比

特性 LoRA QLoRA
核心技术 低秩适配器(Low-Rank Adapters) 低秩适配器 + 量化技术
适用场景 显存受限,但设备性能较好 极限显存受限或需要快速推理的设备
计算效率 提高计算效率,减少调整的参数数量 进一步提升效率,减少内存使用并加快推理
量化技术 无量化 将权重量化为低精度(如INT4
内存消耗 较低 显著降低内存消耗,适合更小的设备
训练复杂度 较简单,适用于大多数微调场景 需要额外的量化和适配工作

1.4 高效微调的四大核心应用场景

高效微调能做什么?

高效微调的应用场景非常广泛,主要可以归纳为以下四个方面:

  • 通过微调,可以让模型学习并模仿特定的对话风格。例如,将其训练成专业的客服、风趣的智能助理或严谨的法律顾问,使其在与用户互动时提供更符合场景需求的对话体验。
  • 将法律、医疗、金融等专业领域的知识快速集成到模型中。通过使用特定领域的标注数据进行微调,模型能够更好地理解行业术语和规则,从而显著提升在专业领域的问答与分析能力。
  • 针对性地提升模型处理复杂逻辑和长文本的推理能力。通过在包含复杂推理链条的数据集上进行微调,可以帮助模型更准确地理解隐含信息、提取逻辑关系,从而在解决复杂问题时减少错误。
  • 在需要模型调用外部 API 或工具的场景中,微调可以使模型更精准地理解用户意图,并生成正确的函数调用指令。这对于构建自动化服务、智能助手等复杂 Agent 应用至关重要。

理论已经清晰,接下来,我们将介绍实现这些微调所需要的具体工具链。

2. 工具链与环境:工欲善其事,必先利其器

2.1 引言:构建完整的微调生态系统

成功进行模型微调不仅需要扎实的理论知识,更依赖于一套完整、高效的工具链。本章节将系统性地介绍从核心微调框架、性能评估工具到具体的软硬件环境要求,为开发者构建一个清晰的准备路径,确保后续的实战环节顺利进行。

2.2 主流高效微调框架概览

对于初学者而言,直接使用封装层次较高的集成化微调工具是最高效的选择。它们屏蔽了底层的复杂性,让开发者可以专注于数据和模型本身。以下是当前社区中备受推崇的四款主流工具:

  • unsloth: 极致优化性能,主打速度和显存效率。
  • Llama-Factory: 功能全面,支持模型众多,堪称“瑞士军刀”。
  • ms-SWIFT: 源自魔搭社区,对国产模型和多模态支持尤为出色。
  • ColossalAI: 专注于超大规模模型的分布式训练,是处理巨型模型的利器。

Unsloth:效率之王

Unsloth 是一个专为极致微调效率而生的框架。它通过手动推导计算密集型数学步骤并手写 GPU 内核,实现了在不改变硬件的情况下,将微调速度提升 2-5 倍,同时减少约 80% 的显存占用

  • 核心优势:Unsloth 兼容 HuggingFace 生态(如 transformers, peft),用户只需修改模型加载方式即可无缝集成。其独家的 4-bit 动态量化技术在大幅压缩模型体积的同时,性能损失极小(根据研究,对 Qwen3 模型仅损失不到 1%)。
  • 局限性:其动态量化模型目前只能在单张 GPU 卡上运行,无论是推理还是微调。这限制了其在需要多卡并行以扩大吞吐量的工业场景下的应用。

LLaMA-Factory:全能型选手

LLaMA-Factory 是一个统一且功能强大的微调框架,以其广泛的兼容性和易用性著称。

  • 突出特点:支持超过 100 种大型语言模型(LLMs)和视觉语言模型(VLMs),并覆盖了从文本到多模态的多种任务类型。
  • 便捷工具:提供零代码的 CLI 和 Web UI,大大降低了上手门槛。同时集成了 LlamaBoardWandb 等丰富的实验监控工具,方便追踪和分析训练过程,并能结合 vLLM worker 实现高效推理。

ms-SWIFT:国产之光

ms-SWIFT (Scalable lightWeight Infrastructure for Fine-Tuning) 是由国内的**魔搭社区(ModelScope)**开发的微调框架,对国产模型生态支持尤为友好。

  • 模型支持:支持超过 450 种 LLMs150 多种 MLLMs,包括 Qwen、InternLM、GLM 等主流国产模型。
  • 功能多样:集成了 LoRADoRAGaLoreQ-GaLore 等多种前沿训练技术,并支持 BNBWQGPTQAQLM 等量化方法。同时提供用户友好的 Web UI,简化了从训练、评测到量化部署的全链路流程。

Colossal-AI:超大模型利器

Colossal-AI 定位于高效的分布式 AI 训练系统,旨在以最低的成本训练和微调超大规模模型。

  • 核心定位:通过提供数据并行、流水线并行、张量并行等多种并行技术,最大化提升训练效率。
  • 独特支持:它是目前唯一支持对 DeepSeek R1 非量化模型进行高效微调的框架,仅需 4 个节点、8 卡 A100 服务器即可完成,展现了其在处理巨型模型上的独特优势。

2.3 性能评估框架:EvalScope

如何科学评估微调效果?

微调完成后,不能仅凭感觉判断模型的好坏。我们需要一个标准化的评估框架来量化性能变化。EvalScope 就是为此而生的工具。

EvalScope 是由阿里巴巴魔搭社区推出的开源模型评估框架,提供了一套系统化的性能评估方案。其核心特点包括:

  • 丰富的评测数据集:内置了涵盖常识、代码、数学、中文等多个维度的标准评测集。
  • 多样的评估模式:支持单模型评估、与基线模型对比等多种模式。
  • 统一的模型接入接口:轻松接入本地模型、HuggingFace 模型或 API 服务。
  • 高度自动化的评估流程:实现评测任务全自动执行,包括客观题自动打分。
  • 完善的可视化工具:生成详细的评估报告和图表,便于横向对比分析。
  • 对部署性能测试的支持:除了模型能力,还能测试吞吐量、时延等部署关键指标。

2.4 软硬件环境要求

硬件配置:我的 GPU 够用吗?

微调所需的显存取决于模型大小和微调精度。下表清晰展示了 Qwen3 全系列模型在不同配置下的高效微调显存需求,可以帮助你判断自己的硬件是否满足要求。

模型名称 FP16 微调显存占用 4-bit 动态量化微调显存占用
Qwen3-0.6B ~1.2 GB ~0.5 GB
Qwen3-1.7B ~3.4 GB ~1.5 GB
Qwen3-4B ~8.0 GB ~3.5 GB
Qwen3-8B ~16.0 GB ~7.0 GB
Qwen3-14B ~28.0 GB ~12.0 GB
Qwen3-32B ~65.0 GB ~32.0 GB

从表中可以看出,通过 4-bit 动态量化(如 Unsloth 提供的方案),即便是 14B 的模型也可以在单张 RTX 4090 (24GB) 上进行微调,极大地降低了硬件门槛。

操作系统选择

在绝大多数工业场景下,**Linux 系统(如 Ubuntu)**是首选,因为它对多卡并行训练库(如 DeepSpeed)的支持最为完善。

不过,对于本教程中不涉及多卡并行的单卡高效微调场景,Windows 系统也完全可以运行相关的代码。

所有准备工作已就绪,接下来,我们将进入整个流程中最关键的环节——微调数据集的原理与构建。

3. 决胜关键:微调数据集的原理与构建

3.1 引言:数据是模型能力的基石

如果说框架和硬件是微调的“躯体”,那么数据集就是决定模型微调成败的“灵魂”和最关键因素。一个高质量、结构合理的数据集,能精准地引导模型学习我们期望的能力。本章将从底层原理入手,揭示模型如何通过特殊字符来理解复杂的指令,并一步步指导你如何为 Qwen3 模型构建一个高质量的混合微调数据集。

3.2 揭秘模型输入的底层格式

模型看到的“你好”和我们看到的一样吗?

答案是否定的。当我们与模型进行对话时,我们输入的文本在被模型处理前,会被转换成一种带有特殊标记符的特定格式。这些标记符就像是语言的“标点符号”,告诉模型这段文本的角色(用户、系统还是助手)、开始和结束位置。

下图清晰地展示了用户视角和模型实际输入之间的区别:

在这个例子中:

  • <|im_start|>:标记一段新消息的开始。
  • user / assistant:标记消息的发送者身份。
  • <|im_end|>:标记一段消息的结束。

这些特殊标记符的定义通常可以在模型目录下的 tokenizer_config.json 文件中找到。理解这个底层格式是构建有效微调数据的基石。

以著名的 alpaca_zh 中文微调数据集为例,一条数据通常包含 instruction, input, output 字段。

{
  "instruction": "比较以下两位著名人物,使用特征列表并以句子的形式提供输出。",
  "input": "人物:阿尔伯特·爱因斯坦\n人物2:艾萨克·牛顿",
  "output": "阿尔伯特·爱因斯坦和艾萨克·牛顿都是有史以来最具影响力的科学家..."
}

在微调时,微调脚本会自动将这样的 JSON 数据转换为模型能够理解的、带有特殊标记符的最终格式,例如: <|im_start|>user\n比较以下两位著名人物...艾萨克·牛顿<|im_end|>\n<|im_start|>assistant\n阿尔伯特·爱因斯坦和艾萨克·牛顿都是...\n<|im_end|>

模型在训练时,会学习根据 assistant 之前的所有内容,来生成 assistant 之后的内容。

3.3 构建复杂场景下的微调数据

掌握了基本原理后,我们就可以构建更复杂的数据格式来训练模型的高级能力。

如何加入系统提示词 (System Prompt)?

系统提示词用于为模型设定一个全局的角色或行为准则。这通过 <|im_start|>system...<|im_end|> 标记来实现。

示例数据:

  • 系统提示 (instruction): 你是一名助人为乐的助手。
  • 用户输入 (input): 你好,好久不见。
  • 模型输出 (output): 是的呀,好久不见,最近有什么有趣的事情要和我分享么?

模型实际输入/输出格式: <|im_start|>system\n你是一名助人为乐的助手。<|im_end|>\n<|im_start|>user\n你好,好久不见。<|im_end|>\n<|im_start|>assistant\n是的呀,好久不见...<|im_end|>

如何微调 Function Calling 能力?

要让模型学会调用外部工具,我们需要在数据中提供工具的定义(schema),并告诉模型在何种情况下生成特定的调用指令。模型通过输出一个特殊的 <tool_call> XML 标签来发起函数调用。

工具定义 (tool_schema) 示例:

[{
  "name": "get_weather",
  "description": "查询指定城市的天气信息",
  "parameters": {
    "type": "object",
    "properties": { "location": { "type": "string", "description": "要查询天气的城市名称" } },
    "required": ["location"]
  }
}]

完整报文示例 (模型实际输入/输出格式):

<|im_start|>system
你是一名助人为乐的助手。当用户查询天气的时候,请调用get_weather函数进行天气信息查询。
# Tools
You may call one or more functions to assist with the user query.
You are provided with function signatures within <tools></tools> XML tags:
<tools>
{"name": "get_weather", "description": "查询指定城市的天气信息", "parameters": {"type": "object", "properties": {"location": {"type": "string", "description": "要查询天气的城市名称"}}, "required": ["location"]}}
</tools>
For each function call, return a json object with function name and arguments
within <tool_call></tool_call> XML tags:
<tool_call>
{"name": <function-name>, "arguments": <args-json-object>}
</tool_call><|im_end|>
<|im_start|>user
你好,请帮我查询下北京天气。<|im_end|>
<|im_start|>assistant
<tool_call>
{"name": "get_weather", "arguments": {"location": "北京"}}
</tool_call><|im_end|>

注意:高效微调通常只能优化模型已有的 Function Calling 能力,而不能从零开始创造这种能力。基础模型需要预先具备这种潜力。

如何让模型学会“思考” (Chain-of-Thought)?

为了提升模型的推理能力,我们可以训练它在给出最终答案前,先生成一段“思考过程”。这通过在模型输出中加入 <think>...</think> 标签来实现。

报文示例 (模型实际输入/输出格式):

<|im_start|>system
你是一名助人为乐的助手。<|im_end|>
<|im_start|>user
你好,好久不见。<|im_end|>
<|im_start|>assistant
<think>
好的,用户发来“你好,好久不见!”,我需要回应。首先,用户可能希望得到亲切的回应,所以应该用友好的语气。
</think>
是的呀,好久不见,最近有什么有趣的事情要和我分享么?<|im_end|>

终极挑战:构建混合能力微调数据

我们可以将上述所有元素组合起来,构建一个能同时训练系统提示、思考链和函数调用能力的复杂数据集。

完整模型输入/输出格式示例:

<|im_start|>system
你是一名助人为乐的助手。当用户查询天气的时候,请调用get_weather函数进行天气信息查询。
# Tools
You may call one or more functions to assist with the user query.
You are provided with function signatures within <tools></tools> XML tags:
<tools>
{"name": "get_weather", "description": "查询指定城市的天气信息", "parameters": {"type": "object", "properties": {"location": {"type": "string", "description": "要查询天气的城市名称"}}, "required": ["location"]}}
</tools>
For each function call, return a json object with function name and arguments
within <tool_call></tool_call> XML tags:
<tool_call>
{"name": <function-name>, "arguments": <args-json-object>}
</tool_call><|im_end|>
<|im_start|>user
你好,请帮我查询下北京天气。<|im_end|>
<|im_start|>assistant
<think>
好的,用户问北京今天的天气,我应该尝试调用工具 get_weather,并将参数设置为北京。
</think>
<tool_call>
{"name": "get_weather", "arguments": {"location": "北京"}}
</tool_call><|im_end|>

3.4 实战:为 Qwen3 构建混合推理数据集

核心策略:为了在提升模型的特定能力(如数学推理)的同时,不损害其宝贵的通用对话能力,我们必须混合不同类型的数据集进行微调。

本次实战,我们将选用以下两个公开数据集:

  1. 数学推理数据集: nvidia/OpenMathReasoning (或其迷你版 unsloth/OpenMathReasoning-mini),用于提升模型的逻辑推理和数学解题能力。
  2. 通用对话数据集: mlabonne/FineTome-100k,一个高质量的 ShareGPT 风格对话数据集,用于维持模型的通用性和对话流畅度。
1. 数据集下载

我们可以使用 Hugging Face 的 datasets 库轻松下载这两个数据集。

# 伪代码
from datasets import load_dataset

# 下载数学推理数据集
reasoning_dataset = load_dataset("unsloth/OpenMathReasoning-mini", split="train")

# 下载通用对话数据集
chat_dataset = load_dataset("mlabonne/FineTome-100k", split="train")
2. 数据格式清洗与统一

下载后的数据集格式各异,需要清洗并统一为 Qwen3 对话模板要求的格式。

  • 处理 <b>OpenMathReasoning</b> 数据集: 我们需要将数据中的 problem(问题)、generated_solution(思考过程/解题步骤)和 expected_answer(最终答案)字段拼接成一个完整的对话格式。这通常通过一个映射函数(map function)来批量处理。
  • 处理 <b>FineTome-100k</b> 数据集: 该数据集采用 ShareGPT 格式。我们可以直接利用 Unsloth 提供的 standardize_sharegpt 辅助工具,快速将其转换为标准的多轮对话格式。
3. 数据集混合与采样

为了平衡不同能力的训练,我们按一定比例混合两个数据集。例如,我们可以设定 25% 的数学推理数据75% 的通用聊天数据

# 伪代码逻辑
# 1. 将清洗后的两个数据集转换为可操作的格式(如 Pandas Series)
# 2. 根据设定的比例从各自的数据集中进行随机采样
chat_sample = chat_series.sample(frac=0.75, random_state=42)
reasoning_sample = reasoning_series.sample(frac=0.25, random_state=42)

# 3. 将采样后的数据合并成一个最终的数据集
combined_dataset = pd.concat([chat_sample, reasoning_sample])

# 4. 将合并后的数据集保存为本地文件,供后续训练使用
combined_dataset.to_json("my_finetuning_data.jsonl", orient="records", lines=True)

至此,我们已经准备好了一个高质量的混合微调数据集。在拥有了坚实的数据基础后,我们将在下一章正式开启 Qwen3 的高效微调之旅。

4. Unsloth 实战:端到端高效微调 Qwen3

4.1 引言:开启高效微调之旅

本章是整个实战流程的核心。我们将理论付诸实践,引导你使用以高效著称的 Unsloth 框架,一步步完成从模型加载、参数配置、启动训练到最终模型保存的全过程。通过本章的操作,你将亲眼见证理论知识如何转化为模型能力的实际提升。

4.2 Unsloth 基础:模型加载与对话

Unsloth 是一个集模型调用和高效微调于一体的强大框架。在开始微调之前,我们先熟悉一下如何使用它加载模型并进行简单的对话。

首先,我们可以用一行代码加载一个经过 4-bit 量化的 Qwen3 模型。Unsloth 会在底层自动应用其优化,实现极低的显存占用。

from unsloth import FastLanguageModel
import torch

model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = "unsloth/Qwen3-32B-unsloth-bnb-4bit",
    max_seq_length = 2048,
    dtype = None,
    load_in_4bit = True,
)

接下来,我们使用 Unsloth 的底层 API 进行一次对话。这能让我们清晰地看到上一章节中提到的、带有特殊字符的原始输入和输出,加深对数据格式的理解。

# 准备对话内容
messages = [
    {"role": "user", "content": "你好,好久不见!"},
]

# 1. 应用对话模板,生成带有特殊字符的输入文本
input_text = tokenizer.apply_chat_template(messages, tokenize = False, add_generation_prompt = True)
print(input_text)
# 输出: <|im_start|>user\n你好,好久不见!<|im_end|>\n<|im_start|>assistant\n

# 2. 将文本分词
inputs = tokenizer([input_text], return_tensors = "pt").to("cuda")

# 3. 生成回复
outputs = model.generate(**inputs, max_new_tokens = 64, use_cache = True)
response = tokenizer.batch_decode(outputs)[0]
print(response)
# 输出: <|im_start|>user\n你好,好久不见!<|im_end|>\n<|im_start|>assistant\n你好呀!是啊,好久不见,最近怎么样?<|im_end|>

此外,Unsloth 也提供了更便捷的高层流式输出 API,适合交互式应用。

4.3 Qwen3 高效微调分步详解

现在,我们正式开始微调流程。

第 1 步:注入 LoRA 适配器

在加载好的模型中注入 LoRA 适配器层。这是实现高效微调的关键,Unsloth 会自动在模型的线性层中添加这些可训练的小模块。

model = FastLanguageModel.get_peft_model(
    model,
    r = 16, # LoRA 秩
    lora_alpha = 32, # LoRA alpha
    lora_dropout = 0.05,
    bias = "none",
    use_gradient_checkpointing = True,
    target_modules = ["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
)

第 2 步:配置微调参数

我们使用 Hugging Face trl 库中的 SFTTrainer (Supervised Fine-tuning Trainer) 来执行训练。训练的所有配置都通过 SFTConfig 对象进行管理。

参数名 含义
dataset_text_field 数据集中用于训练的字段名称,如 textprompt
per_device_train_batch_size 每张 GPU 上的 batch size
gradient_accumulation_steps 梯度累计 N 次后才进行一次反向传播(等效于总 batch size =batch_size× N)
warmup_steps 前 N 步进行 warmup(缓慢提升学习率)
max_steps 最多训练 N 步(适合调试或快速实验)
learning_rate 初始学习率(短训练可用较高值)
logging_steps 每训练 N 步就打印一次日志
optim 使用的优化器,"adamw_8bit"是 Unsloth 支持的内存优化版本
weight_decay 权重衰减,用于防止过拟合
lr_scheduler_type 学习率调度器类型,如 "linear"(从高到低线性下降)
seed 固定随机种子,确保结果可复现
report_to 日志报告平台,可设为 "wandb""none"
from trl import SFTTrainer, SFTConfig

trainer = SFTTrainer(
    model = model,
    tokenizer = tokenizer,
    train_dataset = combined_dataset, # 我们准备好的数据集
    dataset_text_field = "text",
    max_seq_length = 2048,
    args = SFTConfig(
        per_device_train_batch_size = 2,
        gradient_accumulation_steps = 4,
        warmup_steps = 5,
        max_steps = 30, # 先用少量步数进行测试
        learning_rate = 2e-4,
        logging_steps = 1,
        optim = "adamw_8bit",
        weight_decay = 0.01,
        lr_scheduler_type = "linear",
        seed = 3407,
        report_to = "none", # 暂时不使用日志平台
    ),
)

第 3 步 (可选):集成 wandb 监控

为了更好地监控训练过程,例如损失(loss)的变化趋势,我们可以集成 Weights & Biases (wandb)。只需将 SFTConfig 中的 report_to 参数设置为 "wandb" 即可。训练日志将自动同步到你的 wandb 项目中。

第 4 步:启动训练

一切准备就绪,只需一行代码即可启动训练。

trainer.train()

训练过程中,你会在终端看到类似以下的日志输出,实时显示训练进度、损失值、学习率等关键信息:

Step | Training Loss | Learning Rate
---------------------------------------
1    | 2.3456        | 4.00e-05
2    | 2.1890        | 8.00e-05
...
30   | 1.5678        | 1.86e-04

第 5 步:模型保存与合并

训练完成后,我们得到的是一个基础模型和一组独立的 LoRA 适配器权重。为了方便部署和使用,我们需要将这两部分合并成一个完整的模型。

# 将 LoRA 权重与基础模型合并,并以 FP16 精度保存到指定目录
model.save_pretrained("Qwen3-32B-finetuned-fp16") 
tokenizer.save_pretrained("Qwen3-32B-finetuned-fp16")

该命令会将训练好的 LoRA 权重与基础模型合并,并以 FP16 精度保存一个完整的、可直接部署的模型。生成的目录包含了所有必需的文件,可以被 Transformers 等框架加载,或直接用于 vLLM 部署。

4.4 微调后效果初探

在完成一整个 epoch 的训练后,我们可以立即测试模型在数学推理能力上的变化。

问题: Find all positive integers $n$ such that $n^2+n+1$ divides $n!+1$.

微调后模型的回答 (摘要):

<think>
Let $P(n) = n^2+n+1$. We are looking for positive integers $n$ such that $P(n) | n!+1$.
If $n^2+n+1$ is a prime number, then by Wilson's theorem, for a prime $p$, we have $(p-1)! \equiv -1 \pmod{p}$.
Let's check small values of $n$.
For $n=1$, $P(1)=3$ and $1!+1=2$. $3 \nmid 2$.
For $n=2$, $P(2)=7$ and $2!+1=3$. $7 \nmid 3$.
For $n=3$, $P(3)=13$ and $3!+1=7$. $13 \nmid 7$.
For $n=4$, $P(4)=21=3 \times 7$. $4!+1=25$. $21 \nmid 25$.
If $n^2+n+1$ is composite, say $n^2+n+1=ab$ for $1<a,b<n^2+n+1$. If $a,b \le n$, then $ab | n!$. So $n^2+n+1 | n!$.
This implies $n^2+n+1 | (n!+1) - n! = 1$. This is impossible for $n \ge 1$.
So $n^2+n+1$ must have a prime factor $p > n$.
Let $p$ be a prime factor of $n^2+n+1$. Then $n^2+n+1 \equiv 0 \pmod{p}$, so $n(n+1) \equiv -1 \pmod{p}$.
Also, $(n-1)(n^2+n+1) = n^3-1$, so $n^3 \equiv 1 \pmod{p}$.
The order of $n$ modulo $p$ must be 3. This means $3 | p-1$, so $p \equiv 1 \pmod{3}$.
Since $n!+1 \equiv 0 \pmod{n^2+n+1}$, it must be that $n!+1 \equiv 0 \pmod p$.
So $n! \equiv -1 \pmod p$.
If $p \le n$, then $p$ is a factor of $n!$, so $n! \equiv 0 \pmod p$. This would mean $0 \equiv -1 \pmod p$, which is impossible.
Thus, any prime factor $p$ of $n^2+n+1$ must be greater than $n$.
So $n^2+n+1$ itself must be a prime number. Let $p = n^2+n+1$.
We have $n! \equiv -1 \pmod{p}$. By Wilson's Theorem, $(p-1)! \equiv -1 \pmod{p}$.
So we need $n! \equiv (p-1)! \pmod{p}$.
This means $n! \equiv (n^2+n)! \pmod{n^2+n+1}$.
If $n > 1$, then $n+1, n+2, \dots, n^2+n$ are all less than $n^2+n+1$.
Let's consider the product $(n+1)(n+2)\cdots(n^2+n)$. We want this product to be congruent to 1 modulo $p=n^2+n+1$.
$n^2+n \equiv -1 \pmod p$.
$n^2+n-1 \equiv -2 \pmod p$.
...
$n+1 \equiv -(n^2-1) \pmod p$.
This becomes complicated.
Let's go back to $p > n$.
For $n=1$, $p=3$ is prime. $1!+1=2$. No.
For $n=2$, $p=7$ is prime. $2!+1=3$. No.
For $n=3$, $p=13$ is prime. $3!+1=7$. No.
For $n=4$, $p=21$ is not prime.
For $n=5$, $p=31$ is prime. $5!+1=121=11^2$. $31 \nmid 121$.
For $n=6$, $p=43$ is prime. $6!+1=721=7 \times 103$. $43 \nmid 721$.
Is it possible that there are no solutions?
If $n \ge p = n^2+n+1$, then $n^2+1 \le 0$, which is impossible.
If $n^2+n+1$ is a prime $p$, we have $n! \equiv -1 \pmod{p}$.
If $n$ is a solution, $n^2+n+1$ has all its prime factors greater than $n$.
Let's assume $n>1$. $n^2+n+1 = (n+1/2)^2+3/4 > 1$.
The only possibility is $n^2+n+1$ itself is a prime.
Let $p=n^2+n+1$.
This problem is related to a known result. The only solution is $n=1$, but that did not work.
Is it possible that there are NO solutions? Let me recheck my work.
For $n=1$, $3 \nmid 2$. Not a solution.
Let's check the problem statement again. Positive integers.
So $n=1,2,3...$
The argument that $p = n^2+n+1$ must be a prime seems correct.
If $n^2+n+1=k$ and $k$ is a composite number, say $k=ab$. If $a,b \leq n$, then $ab|n!$. Thus $k|n!$. But we need $k|n!+1$, so $k|1$, impossible.
So at least one prime factor $p$ of $k$ must be greater than $n$. Let $p$ be such a prime factor.
Then $n! \equiv -1 \pmod{p}$. But $p>n$, so this doesn't immediately lead to a contradiction via Wilson's Theorem in the form $(p-1)! \equiv -1$.
However, the condition is very restrictive. It's likely there are no solutions. But I should be certain.
There are no such positive integers $n$.
Final verification: if $n^2+n+1$ has a prime factor $p \le n$, then $p|n!$.
$n^2+n+1 | n!+1 \implies p | n!+1$.
So $p|1$, which is impossible.
Therefore, all prime factors of $n^2+n+1$ must be $>n$.
This implies $n^2+n+1$ is either prime or a product of primes all greater than $n$.
Let's check $n=4$: $21 = 3 \times 7$. $3 \le 4$. So this contradicts the statement. My logic is flawed.
Ah, if $n=4$, $n^2+n+1=21$. Its prime factors are 3 and 7. $3 \le 4$.
My deduction that all prime factors must be $>n$ is correct. Why did it fail for $n=4$?
Because $n=4$ is NOT a solution. $21 \nmid 25$. The logic holds for solutions.
So IF $n$ is a solution, THEN all prime factors of $n^2+n+1$ must be $>n$.
This is a strong condition. For $n \ge 2$, $n^2+n+1 > n+1$.
It seems there are no solutions.
Let me propose the answer is that there are no such positive integers.
</think>
There are no positive integers $n$ such that $n^2+n+1$ divides $n!+1$.
Let $k = n^2+n+1$.
If $n$ is a solution, then any prime factor $p$ of $k$ must satisfy $p > n$.
To see this, assume for contradiction that $k$ has a prime factor $p \le n$.
Then $p$ is a factor in $n! = 1 \cdot 2 \cdots p \cdots n$. So $n! \equiv 0 \pmod{p}$.
Since $k | n!+1$, we have $n!+1 \equiv 0 \pmod{k}$, which implies $n!+1 \equiv 0 \pmod{p}$.
This leads to $0+1 \equiv 0 \pmod{p}$, or $1 \equiv 0 \pmod{p}$, which is impossible for any prime $p$.
Thus, all prime factors of $n^2+n+1$ must be greater than $n$.

If $n=1$, $k=3$. The prime factor is $3$, which is not greater than $n=1$. This condition does not apply, we must check directly. $3 \nmid (1!+1)=2$. So $n=1$ is not a solution.
If $n=2$, $k=7$. Prime factor is $7 > 2$. Condition holds. Check directly: $7 \nmid (2!+1)=3$.
If $n=3$, $k=13$. Prime factor is $13 > 3$. Condition holds. Check directly: $13 \nmid (3!+1)=7$.
If $n \ge 2$, then $n^2+n+1 > n+1$. The smallest prime factor of $n^2+n+1$ is greater than $n$.
It can't be $n+1$ since $\text{gcd}(n+1, n^2+n+1) = \text{gcd}(n+1, (-1)^2-1+1) = \text{gcd}(n+1, 1)=1$.
So $n^2+n+1$ cannot be a power of 2, 3, etc. for small $n$.
It is a known result that for $n > 1$, $n^2+n+1$ always has a prime factor $p \le n$, except for a few cases. This is not true.
The statement "all prime factors of $n^2+n+1$ must be $>n$" is a very strong condition that likely fails for most $n$.
But the logic is sound: if $n$ is a solution, this must be true.
Let's consider $n-1 \pmod{k}$. $n \equiv \frac{-1 \pm \sqrt{-3}}{2} \pmod{p}$.
This problem appears to have no solutions.

Comment