想做这一学习系列挺久了,其一是希望自己更加深入大模型的学习,其二是希望通过自己的尝试让大家对大模型的学习使用更加得心应手。

可能会有一些专业术语,我尽量用大白话解释清楚,如果不够清晰,可以找豆师傅解释一下(豆包)

废话不多说,我玩的就是真实 ~~ 开干!

一、前言

在训练之前,我们总要想清楚为什么需要对大模型进行训练:

The field of machine learning has an obsessive relationship with optimisation. We fixate on loss curves, model architectures, and throughput; after all, machine learning is fundamentally about optimising the loss function of a model. Yet before diving into these technical details, there’s a more fundamental question that often goes unasked: should we even be training this model?

机器学习领域与优化有着一种痴迷的关系。我们专注于损失曲线、模型架构和吞吐量;毕竟,机器学习从根本上来说就是关于优化模型的损失函数。然而,在深入研究这些技术细节之前,有一个更基本的问题往往被忽略:我们真的应该训练这个模型吗?

Which raises an uncomfortable truth: maybe you don’t need to train your own model .

这揭示了一个令人不安的事实:或许你并不需要训练自己的模型。

This might seem like an odd way to start an “LLM training guide”. But many failed training projects didn’t fail because of bad hyperparameters or buggy code, they failed because someone decided to train a model they didn’t need. So before you commit to training, and dive into how to execute it, you need to answer two questions: why are you training this model? And what model should you train? Without clear answers, you’ll waste months of compute and engineering time building something the world already has, or worse, something nobody needs.

这似乎是开始一份“大语言模型训练指南”的奇怪方式。但许多失败的训练项目并非因为糟糕的超参数或有漏洞的代码而失败,而是因为有人决定去训练一个他们并不需要的模型。因此,在你投入训练并深入研究如何执行之前,你需要回答两个问题:你为什么要训练这个模型?以及你应该训练什么样的模型?如果没有明确的答案,你会浪费数月的计算资源和工程时间,去构建一个世界上已经存在的东西,或者更糟的是,一个没人需要的东西。

本文旨在通过实践来科普这一问题。我在查阅资料时发现,关于是否需要训练,不同的资料给出了截然相反的结论,这让我想起了"小马过河"的寓言——只有亲自尝试才能知道答案。因此,我秉持着"纸上得来终觉浅,绝知此事要躬行"的态度,决定通过实际操作来为大家解答这个问题。

二、大模型的训练方式

介绍几种耳熟能详的训练方式,让大家有个简单的概念

  • 预训练(Pre-training):让模型从海量无标签通用数据中学习基础规律(语言结构、世界知识、逻辑关联等),构建通用的知识储备和表征能力,成为一个 “知识面广但不精通特定任务” 的基础模型。

  • 微调(Fine-tuning):在预训练模型的基础上,用少量有标签的特定任务数据(如情感分析的 “正面 / 负面” 标签、翻译的 “源语言 - 目标语言” 成对数据)调整模型参数,让模型适配具体任务,从 “通用型” 变成 “专用型”。

  • 直接偏好优化(DPO):通过直接优化模型输出与人类偏好一致的目标函数,来提升生成文本的自然度和相关性。需收集人类偏好反馈数据,训练偏好模型评估文本质量,据此设计目标函数以调整模型参数,常用于文本生成场景。

  • 提示学习(Prompt - based Learning / Instruction Tuning):不直接微调整个模型,而是通过调整提示或创建可学习的提示嵌入来引导模型针对特定任务产生良好表现,能在少样本或零样本情况下发挥作用。

  • 基于人类反馈的强化学习(RLHF):属于强化学习的一种,通过人机交互获取奖励信号来优化模型行为,使模型生成的结果更符合人类期望,常用于对话系统等。

基于个人能力与资源,篇一会选择使用微调(Fine-tuning)来进行尝试

三、微调的意义

微调的意义在于让一个通用的预训练模型能够更好地适应特定任务或领域的需求。预训练模型通常在海量通用数据上进行训练,学习到广泛的语言知识和表示能力。然而,在面对某些特定任务(例如法律文本分析、医学诊断、特定行业报告生成等)时,通用模型可能表现不佳,因为它没有接触过这些领域的专业知识和语言习惯。

微调通过在特定任务数据上进行训练,使得模型能够:

  1. 学习特定领域的知识和术语: 模型能够理解和使用特定领域的专业词汇和概念。

  2. 适应特定任务的模式: 模型可以学习如何更好地完成特定任务,例如分类、摘要、问答等。

  3. 提升性能: 在特定任务上取得比通用模型更好的性能。

  4. 减少模型大小和计算成本: 对于某些任务,微调一个较小的模型可能比使用一个巨大的通用模型更有效率。

3.1 是否需要大量的数据才有意义?

不一定。微调的优势之一就是数据效率

  • 对于高度复杂的任务或领域: 如果特定任务非常复杂,或者领域知识非常深奥且与通用知识差异大,那么确实需要相对较多的数据才能达到良好的微调效果。

  • 对于与预训练任务相似的任务: 如果你的特定任务与预训练模型的任务(例如文本生成、问答)非常相似,即使只有少量的高质量数据,微调也能带来显著的提升。这是因为模型已经具备了基础能力,只需要“调整”一下来适应新数据。

  • 迁移学习的优势: 微调本质上是一种迁移学习。预训练模型已经学习了大量的通用特征,微调是在此基础上进行“精调”,而不是从头开始学习。因此,它通常比从头训练一个模型所需的数据量要少得多。

总结: 虽然更多高质量的数据通常能带来更好的效果,但微调的魅力在于即使是相对少量的数据也能带来有意义的性能提升,特别是当任务与预训练任务有一定相关性时。

3.2 与RAG(Retrieval-Augmented Generation)的区别

RAG和微调都是提升语言模型在特定任务上表现的方法,但它们的原理和应用场景有显著不同。

微调 (Fine-tuning):

  • 修改模型参数: 微调通过更新模型的权重和参数来改变模型的行为。模型“内化”了新的知识和模式。

  • 知识来源: 微调期间模型学习的知识直接编码在模型的参数中。

  • 实时性: 模型一旦微调完成,其知识就是固定的,除非再次微调。

  • 适用场景:

    • 需要模型学习特定领域的语言风格、专业术语和推理模式。

    • 任务需要模型具有“内部化”的知识来生成连贯、领域相关的文本。

    • 当需要提升模型在特定任务上的泛化能力时。

  • 缺点: 知识更新不灵活;可能会有“幻觉”问题(模型生成错误但听起来合理的回答)。

RAG (Retrieval-Augmented Generation):

  • 不修改模型参数(或仅部分微调检索器/生成器): RAG的核心思想是在生成答案之前,先从一个外部知识库中检索相关信息,然后将这些信息作为上下文输入给语言模型进行生成。基础语言模型本身的参数不一定改变。

  • 知识来源: 模型的知识主要来源于外部知识库(如文档、数据库),而不是模型本身的参数。

  • 实时性: 知识库可以随时更新,模型无需重新训练或微调即可获取最新信息。

  • 适用场景:

    • 需要模型访问最新、准确、事实性的信息。

    • 需要模型引用具体来源来支持其回答。

    • 知识库内容经常更新或规模巨大,难以全部通过微调嵌入模型参数。

    • 减少模型的“幻觉”现象。

  • 缺点: 检索质量直接影响生成质量;可能需要额外的索引和检索系统。

简单类比:

  • 微调: 就像一个学生通过学习和记忆一本书的知识来提高自己的能力。

  • RAG 就像一个学生在回答问题时,可以随时查阅图书馆(知识库)的资料来获取信息,然后组织自己的回答。

四、微调: 方式确定

4.1 维度1: 训练目标/方法(做什么)

  • SFT(Supervised Fine-Tuning):用标注的输入-输出对训练

  • RLHF(Reinforcement Learning from Human Feedback):用人类反馈作为奖励信号,强化学习训练

  • DPO(Direct Preference Optimization):直接用偏好对(好回答vs坏回答)训练,不需要奖励模型

  • PPO(Proximal Policy Optimization):RLHF中常用的强化学习算法

4.2 维度2:参数更新方式(怎么做)

  • 全量微调(Full Fine-tuning):更新所有参数

  • LoRA:只更新新增的低秩矩阵

  • QLoRA:量化+LoRA

  • Adapter等其他参数高效方法

基于现有的情况,本次课程选择SFT+LoRA的形式进行微调

五、微调: 数据准备

5.1 SFT (Supervised Fine-Tuning) - 有监督微调

核心思想: SFT 是最直接、最常见的微调方法。就像它的名字一样,它需要“监督”。你提供一系列的“输入”和对应的“期望输出”,模型通过学习这些成对的例子来调整自己的参数,从而学会生成你想要的输出。

原理:

  1. 数据格式: 通常是 (prompt, response)(instruction, input, output) 这样的对子。

  2. 训练过程: 模型接收 promptinstruction + input 作为输入,然后尝试生成 responseoutput。训练的目标是让模型生成的文本尽可能与你提供的“期望输出”一致。这通常通过计算生成文本与期望文本之间的差异(损失函数)并进行反向传播来更新模型参数实现。

  3. 目标: 让模型学会根据特定指令或上下文生成特定格式、风格或内容的文本。

通俗示例: 想象你有一个小学毕业的学生(预训练模型),他懂得很多词汇和语法,但是不知道如何做特定的家庭作业。

你给他一本“作业辅导书”(SFT 数据集),里面写满了:

  • 输入: “请计算 2 + 3 = ?” 期望输出: “5”

  • 输入: “请将‘苹果’翻译成英文。” 期望输出: “Apple”

  • 输入: “请总结以下文章...” 期望输出: “文章主要讲了...”

这个学生通过反复看这些“输入-期望输出”的例子,并练习回答,逐渐学会了如何正确地回答这些类型的作业。他现在能够自己根据“请计算 5 + 6 = ?”输出“11”,而不是随便说一堆无关的词。

因此我们的数据结构大概是:

{"instruction": "指令内容", "input": "输入内容(可选)", "output": "期望模型生成的输出"}

示例:

[
  {
    "instruction": "请描述 [组件名称] 的功能。",
    "input": "组件名称:用户管理模块",
    "output": "用户管理模块负责处理用户账户的生命周期管理,包括用户注册、登录、信息更新、密码重置、账户激活与禁用,以及分配用户角色和权限。"
  },
  {
    "instruction": "解释一下 API 网关的作用。",
    "input": "", // 在这里,指令本身已经足够明确,不需要额外的input
    "output": "API 网关(API Gateway)是微服务架构中的一个关键组件,作为客户端请求的单一入口点。它负责请求路由、负载均衡、认证、授权、流量管理、缓存和协议转换等功能,将外部请求转发到正确的微服务。"
  },
  {
    "instruction": "如果用户在登录时遇到密码错误,哪个模块会处理这个验证过程?",
    "input": "系统概述:本系统包含认证模块、授权模块、用户管理模块。",
    "output": "当用户在登录时输入密码错误,认证模块(Authentication Module)会处理这个验证过程。它会核对用户提供的凭据,并拒绝不正确的登录尝试。"
  }
]

5.2 测评集准备

  • 训练集与测试集划分:

    • 你需要将数据集划分为训练集 (Train Set)测试集 (Test Set)

    • 训练集用于模型学习。

    • 测试集用于评估模型在未见过的数据上的表现。通常按 80/20 或 90/10 的比例划分。

5.3 训练集准备

利用现有的一些飞书文档、软件说明书、需求文档等来源内容,结合大模型进行清洗数据,使其生成上方的数据集格式

5.4 测试集准备

同样通过上方的形式整理一部分相似的内容,来验证微调后,是否大模型有良好的泛化能力

六、微调: 模型选择

因为本身就是一次尝试,因此我们选择一个小模型来验证下结论:

这里解释一下模型的选择:

1. Instruct vs Thinking 模型

  • Instruct:快速直接给答案,适合简单问答;

  • Thinking:先推理再给结果,适合复杂计算 / 逻辑分析。

如果选择Thinking模型训练的话,训练集准备的数据格式需要包含thinking模块,这个在后续的篇幅中再进行扩展讲解

2. 数据格式区别

  • FP32 (单精度): 每个参数 4 字节

  • BF16 / FP16 (半精度): 每个参数 2 字节

  • FP8 (8位浮点): 每个参数 1 字节

  • 4-bit 量化 (QLoRA): 每个参数 0.5 字节 (这是因为 1 字节有 8 位,4-bit 只需要一半,但通常需要一些额外的少量开销)

模型参数大小与模型精度直接影响了模型训练的算力成本,这个换算公式大概是:

模型显存占用 (GB)=模型参数量 (Billion)×每个参数占用的字节数

因此4b模型 BF16的情况下是:4 Billion×2 bytes/param =8G

此计算公式不是完全严谨,因为真实情况训练过程会包含:梯度、优化器、激活值...等一系列参数,不过可以帮你大体预判一个大模型的训练需要的算力成本

考虑到我现有的资源,这里我就选择Qwen3-4B-Instruct-2507作为本次的模型示例

6.1 Qwen3-4B-Instruct-2507

七、SFT微调

7.1 微调工具比较

Unsloth

https://unsloth.ai/?ref=producthunt

  • 速度极快: 它的训练速度通常比原生 Transformers 快 2-5 倍。

  • 极省显存: 对于 4B 模型,Unsloth 优化后的显存占用极低,这意味着你甚至可以在 8GB-12GB 显存的消费级显卡(如 RTX 3060/4060)上轻松进行 LoRA 微调,甚至尝试全量微调。

  • 兼容性好: Unsloth 对 Qwen 的架构支持非常完美

  • 生态整合: 它底层兼容 Hugging Face 的 TRL 库,导出模型非常方便。

LLaMA Factory

https://github.com/hiyouga/LLaMA-Factory/blob/main/README_zh.md#%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8

  • WebUI 界面: 提供可视化的网页界面,鼠标点一点就能配置参数、监控 Loss 曲线。

  • 功能全面: 集成了 Unsloth(可以作为后端加速)、DeepSpeed、FlashAttention 等技术。支持 SFT(指令微调)、DPO(偏好对齐)、Reward Modeling 等多种训练模式。

  • 多模型支持: 对国内模型(Qwen, Yi, DeepSeek 等)的适配速度非常快。

  • 一键评估: 内置了模型评估功能。

个人更倾向于Unsloth,不过为了演示效果直观,我们篇一用LLaMA Factory来进行微调

7.2 安装LLaMA Factory

docker run -it --rm --gpus=all --ipc=host hiyouga/llamafactory:latest

细节我就不赘述了,这里有一个坑需要注意:

进入容器内执行查询语句,检查pytorch是否可用

docker compose exec llamafactory bash
python -c "import torch; print(torch.cuda.is_available())"

7.3 启动Web UI

进入后执行

llamafactory-cli webui

到了这一步,基本上就成功了一大半了,虽然页面上有很多配置项,但是我们一步步来分析

7.4 配置LLaMA-Factory (关键项配置)

  1. 语言 (Language): zh (选择中文,因为 Qwen 是多语言,且你的数据是中文)

  2. 模型名称 (Model Name): Qwen3-4B-Instruct-2507 (选择合适的模型)

  3. 模型下载 (Model Download): 建议你们用modelscope,我这里因为有条件,因此直接用了huggingface (LlamaFactory 会从 Hugging Face 下载模型)

  4. 微调方法 (Fine-tuning Method): lora (保持不变选择LoRA,这是高效的选择)

        5. 量化方法 (Quantization Method): bnb (Bitsandbytes 的缩写)。

建议:bnb

解释: bnb 提供了 8-bit/4-bit 量化选项(通常在 LlamaFactory 的 compute_dtype 为 BF16/FP16 时,它默认就是 4-bit QLoRA)。QLoRA 可以进一步节省显存,让你在 4B 模型上可以开更大的 Batch Size,或者未来尝试更大的模型。

         6. 对话模板 (Chat Template) / RoPE 方法 (RoPE Method) / 加速方式 (Accelerator):

建议: 这些通常保持 LlamaFactory 的默认或模型推荐设置。

对话模板 qwen3_nothink 是 Qwen 模型的标准模板(非思考模型),选择这个选项。

        7. 训练类型 (Supervised Fine-tuning): Supervised Fine-tuning

        8.数据路径 (Data Path): data (保持不变,假设你的数据集放在 LLaMA-Factory/data 目录下)

        9. 数据集 (Dataset): alpaca_zh_demo (修改为你的数据集名称)

        10.学习率 (Learning Rate): 5e-5 (保持不变,或微调)。

建议:2e-45e-4 之间。

解释: 对于 LoRA 微调,学习率通常可以比全量微调(通常是 1e-52e-5)高一些,因为只更新少量参数。2e-45e-4 是常见的有效值。5e-5 也可以,但可能需要更多 epochs 来收敛。

        11.训练轮数 (Epochs): 3.0 (可以适当增加)。

建议:35

解释: 3 个 epoch 意味着模型将完整学习数据集 3 遍。对于 LoRA,3-5 个 epoch 通常是一个好的开始。如果你发现模型在训练集上表现良好但在测试集上不佳(过拟合),可以减少 epoch;如果觉得模型还没学够,可以增加。

        12.最大处理大小 (Max Processing Size): 2 (批处理大小 - Batch Size)。

建议:48 (如果显存允许)。

解释: 这是每个 GPU 上每次迭代处理的样本数量。

尝试路径:4 开始,如果发现显存占用不高(例如 nvidia-smi 显示只用了 20-30GB),可以尝试 8

        13. 梯度累积 (Gradient Accumulation): 8

建议:1 (如果你将 Batch Size 设为 4 或 8)。

解释: 梯度累积的作用是在实际 Batch Size 较小时,通过多次前向/后向传播累积梯度,以模拟更大的“逻辑 Batch Size”。

如果你将 Max Processing Size 设为 4,且 梯度累积 设为 1,那么逻辑 Batch Size 就是 4。

如果你想让逻辑 Batch Size 更大: 例如,Max Processing Size = 4梯度累积 = 4,逻辑 Batch Size 就是 16。

        14. 计算类型 (Compute Type): bf16

建议:bf16 (保持不变,符合模型标注和硬件支持)。

解释: 这会告诉 LlamaFactory 在进行 LoRA 微调时,以 BF16 精度加载模型权重并执行计算。这能利用 A6000 的 Tensor Core 加速,同时保持良好的精度。

        15. 最大句子长度 (Max Sentence Length): 2048

建议:20484096

解释: 这取决于你的数据集中的最长输入/输出序列。2048 对于大多数问答任务来说已经足够了。如果你的软件功能描述非常长,可能需要增加到 4096

注意: 序列长度越长,显存占用和计算成本会按平方级增加(因为 Attention 机制)。对于 4B 模型,2048 配合 48GB 显存是没问题的。

        16. 最大样本数 (Max Sample): 100000

建议:100000 (或留空表示使用所有数据)。

解释: 如果你希望只使用数据集中的一部分数据进行训练,可以设置这个值。否则设置一个超过你准备的训练集的值

        17. 学习率调度器 (Learning Rate Scheduler): cosine (保持不变)。

建议:cosine (保持不变,这是常见的有效调度器)。

解释: 学习率调度器控制学习率在训练过程中的变化。Cosine 调度器会让学习率先升高再慢慢降低,有助于模型更好地收敛。

7.5 将训练数据集放入Data下

有几个注意事项

7.5.1 参考官方给的数据集格式

官方文档:https://llamafactory.readthedocs.io/zh-cn/latest/getting_started/data_preparation.html#id16

例如我们要处理指令监督微调数据集

如果是多模态模型微调则是

7.5.2 检查数据集是否正确(重要!!!!!!)

确保格式正确,不能嵌套,否则会报错

去Json校验网站确认JSON是否标准

https://jsonlint.com/

确认内部是否存在嵌套关系?

比如我的数据就存在问题,此处的input不能嵌套dict,或者list等内容,执行时会报错

将数据存放在data路径下

此处我直接更名为alpaca_zh_demo.json,然后配置目录下的dataset_info.json

参考我的配置即可,validation_split": 0.1的意思是选择10%作为测评数据

  "alpaca_zh_demo": {
    "file_name": "alpaca_zh_demo.json",
    "columns": {
      "prompt": "instruction",
      "query": "input",
      "response": "output"
    },
    "validation_split": 0.1
  }

在Web UI中加载并预览数据

八、炼丹结束,验证结论

8.1 查看Loss与日志信息

后台日志可以看到实际训练的结果

1. epoch = 3.0

***** train metrics ********** eval metrics ***** 部分都明确显示 epoch = 3.0

这说明模型已经完整地训练了 3 个周期.

2. train_runtime = 2:33:52.22

训练总共运行了 2 小时 33 分 52.22 秒

3. train_loss = 1.5865eval_loss = 1.6346

这是训练结束时的最终训练损失和评估损失。这些值与损失曲线图末尾显示的值非常吻合,再次证明训练已经收敛。

4. num_input_tokens_seen = 11276752

这表示在整个训练过程中,模型一共处理了约 1127 万个 token。这个数字是数据集大小、序列长度和 epoch 数的乘积。

5. 图表保存:

Figure saved at: saves/Qwen3-4B-Instruct-2507/lora/train_2025-11-26-12-09-58/training_loss.png
Figure saved at: saves/Qwen3-4B-Instruct-2507/lora/train_2025-11-26-12-09-58/training_eval_loss.png

这些日志条目表明训练过程结束,相关的损失图表已经生成并保存。

8.2 验证模型是否更懂我们的垂域业务知识了

记得要将我们微调后的Lora加载到检查点上哦

选择Chat,直接试试看,作为对比,我们先不加载Lora数据

随便找一个问题看看

未加载Lora模型的回答如下:

加载Lora模型的回答如下:

虽然还是有点不太准,但是说的场景有模有样的,我找了相关同事确认,明确是一本正经的胡说八道

再来一次试试

这次好像还可以

九、一些思考

  1. 整体下来发现其实没什么太大的难度,但是如何评估模型训练的好与不好,确实需要更加专业的方式

  2. 数据集的质量影响模型的学习能力,garbage in garbage out

  3. 模型的大小决定性的影响了模型训练后的效果,4b模型本身就比较笨,所谓智能涌现也是需要力大砖飞!

  4. 其实RAG是一个很好的方式,尤其是对于这些专业知识,用RAG能很好的提供大模型上下文信息,就像上文说的,一个是开卷考试,一个是闭卷,闭卷考试就会出现一本正经的胡说八道

后续会尝试更大的模型如30B,以及多模态大模型的微调,期待篇二我们再见👋

更多推荐