2025-06-27 刚开始做实验,我懵懵懂懂,看文档也有错漏。后来才知道,我按照 modelscope 的方法练习的,是全参数微调,而不是 Lora 微调。 Lora 的方法,还需要研究。实验用过程的 ipynb 文件怎么在博客里嵌入分享,也得找时间研究下。

实验参考文档:Qwen3大模型微调实战:医学推理对话

L20 1/3 卡环境

微调 “Qwen/Qwen3-0.6B”

问题描述: 通过 AutoModelForCausalLM.from_pretrained 加载模型报错 TypeError: argument of type 'NoneType' is not iterable

怀疑使用 L20 显卡不支持 bfloat16 类型,搜索使用代码确认是支持的

1
2
3
4
5
6
 if torch.cuda.is_available():
    device = torch.device("cuda")
if torch.cuda.get_device_capability(device) >= (8, 0):
    print("当前设备支持 bfloat16")
else:
    print("当前设备不支持 bfloat16")

![[Clipboard_Screenshot_1750066476.png]]

网络检索找到最近有人反馈有同样问题。

1
2
3
4
5
6
url: https://github.com/huggingface/transformers/issues/38340#issuecomment-2908772602
title: "Errors using TinyLlama-1.1B-Chat-v1.0 and DirectML · Issue #38340 · huggingface/transformers"
description: "System Info transformers version: 4.52.3 Platform: Linux-5.15.167.4-microsoft-standard-WSL2-x86_64-with-glibc2.39 Python version: 3.12.9 Huggingface_hub version: 0.32.0 Safetensors version: 0.5.3 A..."
host: github.com
favicon: https://github.githubassets.com/favicons/favicon.svg
image: https://opengraph.githubassets.com/60fa25f8a2ddaf8269506f06eebdf3943fcf9a96022b880ea14de4b77ac27722/huggingface/transformers/issues/38340

我的环境信息如下

1
same issue for `Qwen/Qwen3-0.6B`. `transformers==4.52.4`, it also work for me

根据 issues 中点赞比较多的哥们的办法,降级 transformers 版本为 4.51.3 后解决

1
pip install transformers==4.51.3

微调 “Qwen/Qwen3-1.7B”

以下报错。

1
RuntimeError: You can't move a model that has some modules offloaded to cpu or disk.

修改参数为 “cuda”

1
2
3
# 微调 "Qwen/Qwen3-1.7B",auto 会造成报错: RuntimeError: You can't move a model that has some modules offloaded to cpu or disk.
# model = AutoModelForCausalLM.from_pretrained("/data/workspace/autodl-tmp/Qwen/Qwen3-1.7B", device_map="auto", torch_dtype=torch.bfloat16)
model = AutoModelForCausalLM.from_pretrained("/data/workspace/autodl-tmp/Qwen/Qwen3-1.7B", device_map="cuda", torch_dtype=torch.bfloat16)

OOM 问题:全参数微调需要大约32GB显存,如果你的显存大小不足,可以使用Qwen3-0.6b,或Lora微调

1
OutOfMemoryError: CUDA out of memory. Tried to allocate 24.00 MiB. GPU 0 has a total capacity of 14.00 GiB of which 24.00 MiB is free. Process 2224169 has 6.45 GiB memory in use. Process 627018 has 13.98 GiB memory in use. Of the allocated memory 13.49 GiB is allocated by PyTorch, and 146.30 MiB is reserved by PyTorch but unallocated. If reserved but unallocated memory is large try setting PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True to avoid fragmentation.  See documentation for Memory Management  (https://pytorch.org/docs/stable/notes/cuda.html#environment-variables)

06-27 知识扩展

  通过提示词 同样参数量的基础模型,lora 会比全参数微调,耗费更少内存吗? 向 Gemini 提问补充知识:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
在同样参数量的基础模型上,使用 LoRA (Low-Rank Adaptation) 微调会比全参数微调(Full Fine-Tuning)消耗显著更少的内存(主要是指显存 VRAM)。

  这是两者最核心的区别和 LoRA 最主要的优势之一。

  为什么 LoRA 内存消耗更少?

  关键在于两种方法训练的参数数量不同:

   1. 全参数微调 (Full Fine-Tuning):
       * 你需要更新模型中所有的权重参数。
       * 在训练过程中,除了模型本身的参数外,还需要为每一个参数存储它的梯度(用于计算更新方向)和优化器状态(例如 Adam
         优化器会为每个参数存储动量和方差,相当于2倍参数量的额外开销)。
       * 因此,内存消耗约等于:模型参数 + 梯度 + 优化器状态,这三者都与模型的总参数量(如 7B, 13B)成正比,所以对显存要求极高。

   2. LoRA 微调:
       * 它会冻结原始大模型的所有参数,这些参数在训练中保持不变。
       * 然后,在模型的特定层(通常是 Attention 层的权重矩阵)旁边,注入两个很小的、可训练的“低秩矩阵”(A 和 B)。
       * 训练时,你只更新这些新增的小矩阵的参数。这些参数的数量通常只占原始模型总参数量的极小一部分(例如 0.1% ~ 1%)。
       * 因此,内存消耗约等于:(冻结的)模型参数 + (LoRA部分的)梯度 + (LoRA部分的)优化器状态。由于可训练的参数极少,梯度和优化器状态占用的空间也极小,内存瓶颈大大缓解。

  一个简单的类比:

   * 全参数微调:好比你要修改一座巨大的大理石雕像,你需要把整座雕像的每一寸都打磨一遍,需要大量的工具和工作空间。
   * LoRA 微调:好比你基本保留雕像原样,只是给它设计并戴上了一顶精致的小帽子。你只需要制作这顶小帽子的材料和工具,这比修改整个雕像要轻松得多。

  总结来说,LoRA 通过只训练极少量的“增量”参数,而不是整个模型,极大地降低了训练所需的内存和计算资源,使得在消费级硬件上微调大模型成为可能。

  继续追问: 怎么预先评估 LoRA 微调的 VRAM 消耗?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
预先评估 LoRA 微调的 VRAM 消耗是一个非常实际且重要的问题。虽然无法像 2+2=4 那样精确计算,但我们可以通过一个公式和一些经验法则,得出一个相当可靠的估算。

  总 VRAM 消耗可以拆解为以下几个主要部分:

  总 VRAM ≈ 模型参数占用 + LoRA 参数及优化器状态 + 激活值占用 + 框架开销

  下面我们来逐一分析每一项:

  1. 模型参数占用 (Model Parameters)

  这是最基础、最固定的一部分。它取决于你加载的基础模型大小和它的量化精度。

   * 计算方法:模型总参数量 × 每个参数占用的字节数
   * 字节数参考:
       * FP32 (单精度): 4 字节/参数
       * FP16 / BF16 (半精度): 2 字节/参数
       * INT8 (8-bit 量化): 1 字节/参数
       * NF4 / INT4 (4-bit 量化): 约 0.5 字节/参数

  示例:
  加载一个 7B (70亿) 参数的模型:
   * 用 FP16 加载:7B * 2 bytes ≈ 14 GB
   * 用 4-bit 量化 (如 QLoRA) 加载:7B * 0.5 bytes ≈ 3.5 GB

  这是你的 VRAM 消耗基线。

  2. LoRA 参数及优化器状态 (LoRA & Optimizer States)

  这部分是 LoRA 训练时主要的额外开销,但远小于全量微调。

   * LoRA 参数:
       * 数量取决于 rank(r), alpha, 以及你选择应用 LoRA 的层 (target_modules)。
       * rank 越高,target_modules 越多,参数量越大。
       * 经验估算:对于一个 7B 模型,rank=64 时,LoRA 参数本身通常只有几百 MB。

   * 优化器状态:
       * 这是这部分的大头。像 AdamW 这样的标准优化器会为每一个可训练参数存储两份状态(动量和方差)。
       * 如果你用 FP32 的优化器,每个 LoRA 参数就需要 4 (梯度) + 4 (动量) + 4 (方差) = 12 字节的额外空间。
       * 如果用 FP16,则需要 2 + 2 + 2 = 6 字节。
       * 省内存技巧:使用 8-bit 优化器(如 paged_adamw_8bit)可以显著降低这部分开销。

  经验估算:对于 7B 模型、rank=64、应用到所有线性层,这部分总开销(LoRA参数+梯度+优化器状态)通常在 1-2 GB 左右。

  3. 激活值占用 (Activations)

  这是最难预测、也是变化最大的一部分。它是在模型前向传播时,为计算梯度而临时存储的中间结果。

   * 主要影响因素:
       1. 序列长度 (Sequence Length):影响最大。激活值大小约与序列长度的平方成正比 (O(seq_len²)),因为 Attention 机制。序列长度从 2048 增加到 4096,激活值占用会暴增约 4 倍。
       2. 批次大小 (Batch Size):与批次大小成正比。
       3. 模型隐藏层维度 (Hidden Size)。

   * 省内存技巧:
       * 梯度检查点 (Gradient
         Checkpointing):这是一个“用时间换空间”的技巧。它在前向传播时不存储所有激活值,而是在反向传播需要时重新计算它们。对于长序列训练,这个选项几乎是必须的,可以节省大量
         VRAM。
       * 梯度累积 (Gradient Accumulation):用小的 batch_size 多次计算梯度再更新一次,模拟出大 batch_size 的效果,而不增加 VRAM 峰值。

  经验估算:在开启梯度检查点的情况下,对于 7B 模型、序列长度 4096、batch_size=1,激活值占用可能在 8-14 GB 之间。这是 VRAM 消耗的大头。

  4. 框架开销 (Framework Overhead)

  包括 CUDA 内核、PyTorch/Transformers 库本身等固定开销。

  经验估算:可以预留 1-2 GB 的 buffer。

  ---

  实战估算步骤与示例

  场景:你有一张 24GB VRAM 的显卡 (如 RTX 3090/4090),想用 QLoRA (4-bit) 微调 Llama-3-8B 模型,序列长度为 4096。

   1. 计算模型基线:
       * Llama-3-8B 是 8B 模型,用 4-bit 加载。
       * VRAM = 8B * 0.5 bytes ≈ 4 GB。

   2. 估算 LoRA 和优化器:
       * 使用 rank=64 和 8-bit 优化器。
       * 预估一个相对固定的值:约 1.5 GB。

   3. 估算激活值 (最关键):
       * 序列长度 4096,batch_size=1,必须开启梯度检查点。
       * 这是一个比较大的消耗,根据社区经验,预估在 12 GB 左右。

   4. 估算框架开销:
       * 预留 1.5 GB。

   5. 求和:
       * 总 VRAM ≈ 4 (模型) + 1.5 (LoRA) + 12 (激活值) + 1.5 (框架) = 19 GB。

  结论:19 GB < 24 GB这个配置是可行的如果 VRAM 不足首要的优化措施是降低序列长度其次是降低 rank  batch_size

  我好奇的是,降低 rank 或 batch_size 会不会导致训练速度变慢,或者有其他影响

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
好问题。是的,降低 rank 或 batch_size 都会对训练过程产生显著影响,但影响的方式和侧重点完全不同。

  这是一个简明的对比表格:


  ┌─────────────────┬────────────────────────────────────┬────────────────────────────────────────────────┬───────────────────────────────┐
  │ 调整项          │ 对训练速度的影响                   │ 对模型性能/结果的影响                          │ 主要权衡                      │
  ├─────────────────┼────────────────────────────────────┼────────────────────────────────────────────────┼───────────────────────────────┤
  │ 降低 Rank (r)   │ 每个训练步骤稍微变快               │ 直接影响模型的学习能力。太低可能导致欠拟合。   │ VRAM/计算量 vs 模型的表达能力 │
  │ 降低 Batch Size │ 每个训练步骤变快,但总训练时间变慢 │ 影响梯度稳定性和收敛过程。可能需要调整学习率。 │ VRAM vs 训练效率和稳定性      │
  └─────────────────┴────────────────────────────────────┴────────────────────────────────────────────────┴───────────────────────────────┘

  ---

  详细解释

  1. 降低 LoRA Rank (r)

  rank(秩)决定了 LoRA 注入的“旁路”矩阵的大小,直接控制了可训练参数的数量。

   * 对训练速度的影响:
       * 通常会使每个训练步骤(step/iteration)稍微变快。
       * 因为可训练的参数变少了,所以反向传播计算梯度和优化器更新参数的计算量会减少。但这个加速通常不明显,因为训练的大部分时间消耗在基础模型的前向传播上,这部分是冻结的,不
         受 rank 影响。

   * 对模型性能的影响 (这是主要影响):
       * Rank 代表了 LoRA 适配器的“表达能力”或“复杂度”。
       * Rank 太低: 适配器可能过于简单,没有足够的能力去学习你想要微调的任务,导致欠拟合 (Underfitting)。模型学得不好,在验证集上表现差。
       * Rank 太高: 可能会导致过拟合 (Overfitting),即模型过于完美地学习了训练数据中的噪声和细节,但在新的、未见过的数据上泛化能力差。同时也会消耗更多 VRAM。
       * 通常存在一个“甜点”rank 值,对于特定任务,超过这个值的 rank 提升很小,甚至可能带来负面效果。常见的 rank 值在 8 到 128 之间。

  核心权衡: 你是在用 VRAM/计算量 与 模型的学习能力 做交换。降低 rank 是在牺牲一部分潜在的模型性能,来换取更少的资源消耗。

  2. 降低 Batch Size

  batch_size(批次大小)决定了每次迭代中,模型同时处理多少个训练样本。

   * 对训练速度的影响:
       * 每个单独的训练步骤(step/iteration)会变快,因为一次处理的数据量变少了。
       * 但是,要完成对整个数据集的一次完整训练(一个 epoch),你需要更多的步骤。例如,数据集有 1000 个样本,batch_size=10 需要 100 步,而 batch_size=5 则需要 200 步。
       * 由于 GPU 在处理大批量数据时效率更高(并行计算的优势),频繁地启动小批量的计算会导致总体效率下降。因此,降低 `batch_size` 会显著增加完成一个 epoch
         所需的总时间,即总训练速度会变慢。

   * 对模型性能的影响:
       * 梯度更不稳定(更“吵”): 小批次计算出的梯度是对整个数据集梯度的“有噪声的”估计。这既有坏处也有好处。
           * 坏处: 可能导致训练过程震荡,收敛变慢。你可能需要使用更小的学习率来稳定训练。
           * 好处: 这种噪声有时能起到正则化的作用,帮助模型跳出尖锐的局部最优解,找到泛化能力更好的平坦最优解。
       * 解决方案:梯度累积 (Gradient Accumulation)
           * 这是解决 batch_size 和 VRAM 矛盾的标准方法。
           * 你可以设置一个很小的物理 batch_size(例如 1 或 2)来适应 VRAM,然后设置一个累积步数(gradient_accumulation_steps)。
           * 模型会执行多次小批量的正向和反向传播,但梯度会累积起来,而不是立即更新模型。直到累积了指定步数后,才用累积的梯度更新一次模型。
           * 这样,你就能以小 batch_size 的 VRAM 消耗,实现大 batch_size 的训练效果(例如 batch_size=2, accumulation_steps=32,等效于 batch_size=64)。

  核心权衡: 你是在用 VRAM 与 训练效率和稳定性 做交换。

  总结与建议

   1. VRAM 实在不够时,首选方案是降低 `batch_size` 并配合使用梯度累积。这是在不牺牲“有效批次大小”的前提下,降低 VRAM 峰值的最佳实践。
   2. 如果怀疑模型过拟合,或者想节省一点 VRAM 和计算量,可以尝试降低 `rank`。可以从一个较高的值(如 64 或 128)开始,然后逐步降低,观察验证集上的性能变化,找到最适合你任务的
      rank。
   3. 不要为了加速而盲目降低 `batch_size`,这会拖慢你的总训练时间。加速的正确方式是尽可能用满 VRAM,使用更大的 batch_size。