Avatar

Qianqiu

Master in PKU Research Direction: VLM, RLHF, MLsys Hobbies: Game, Web novel, Anime

  1. WeChat
  1. Home
  2. Diary
  3. Research
  4. Entertain
  5. Search
  6. Archives
  7. About
    1. Dark Mode Light Mode

Table of contents

    1. 冻结参数 = Attention层 + FFN层的所有权重矩阵
Diary

转码day15:colab收尾

Feb 04, 2026

下午两点半开始学习。QLoRA(Quantized LoRA)是指量化LoRA,其中quantized表示使用4-bit量化。

1
2
3
4
5
6
quantization_config = BitsAndBytesConfig(
    load_in_4bit=True,              # 使用4-bit量化
    bnb_4bit_quant_type="nf4",     # NF4量化类型
    bnb_4bit_compute_dtype=torch.bfloat16,
    bnb_4bit_use_double_quant=True, # 双重量化
)
  • 将模型权重从 FP16/BF16 (16-bit) 压缩到 4-bit
  • 使用 NF4 (4-bit NormalFloat) 量化,这是专门为神经网络权重设计的量化格式
  • 双重量化:连量化参数本身也量化,进一步节省显存 LoRA底秩适配:
1
2
3
4
5
lora_config = LoraConfig(
    r=8,                    # 秩为8的低秩矩阵
    lora_alpha=32,
    target_modules=["q_proj", "k_proj", "v_proj", ...],
)

常规全量微调:

  • 模型权重: 1.5B × 2 bytes = 3GB
  • 优化器状态 (Adam): 1.5B × 8 bytes = 12GB
  • 梯度: 1.5B × 2 bytes = 3GB
  • 总计: ~18GB

QLoRA:

  • 量化权重: 1.5B × 0.5 bytes = 0.75GB
  • LoRA参数: 8M × 2 bytes = 16MB
  • 优化器状态: 8M × 8 bytes = 64MB
  • 总计: ~1GB ⬇️ 降低95%

LoRA能够有效微调模型的原因在于低秩假设:微调过程主要在低维子空间中完成。

这让我联想到去年11月Kaiming He发表的论文Back to Basics: Let Denoising Generative Models Denoise,文中写道:“According to the manifold assumption, natural data should lie on a low-dimensional manifold …"。这就是流形假设。流形假设与LoRA的低秩假设有着共同的洞察:虽然参数空间或数据空间是高维的,但"有效信息"却集中在低维子空间中。 这篇文章认为:预测干净图像(x-prediction)比预测噪声(ε/v-prediction)更优,因为在流形假设下,干净图像位于低维流形上,而噪声则分布在高维且"偏离流形"的空间中,当网络容量有限时,预测噪声更容易失败。

我的理解是:预测噪声本质上是预测高维无序的数据,这对同一神经网络而言更为困难。因为神经元的预测能力存在局限性,高维度的信息往往需要更多神经元和更深的网络层才能有效识别。

论证逻辑:

  1. 流形假设:自然图像是高维像素空间中的低维流形,而噪声则分布在更"饱满"的高维空间中
  2. 形式化分析:预测空间(x/ε/v)和损失空间(x/ε/v)可独立选择,组合成9种不等价的方案
  3. 玩具实验验证:当低维数据投影到高维观测空间时,只有x-pred在高维情况下仍能正常工作,而ε/v会遭遇灾难性失败

方法(JiT):直接使用ViT/DiT在像素patch上进行x-pred,无需tokenizer、潜空间、预训练或额外损失函数。

实验结论:

  • 在ImageNet 256×256、patch=16的设定下,只有x-pred稳定有效,ε/v会出现灾难性失败
  • 在低维设置(64×64、patch=4)时差距不大,说明问题的本质在于高维token的信息传播
  • 单纯调整loss权重无法挽救ε/v,而x-pred在不同loss-space下都能正常工作
  • 提高噪声水平或加宽网络并非根本解法,甚至在某些瓶颈设计下,x-pred的FID指标更优

这进一步印证了流形假设:虽然像素空间是高维的,但"有效生成信息"集中在低维流形上。x-pred本质上是"把样本从噪声状态拉回流形”,而ε-pred要求网络在高维空间保留噪声信息,对网络容量的要求更为苛刻。

回到LoRA话题:

1
2
3
4
5
6
7
# 原始前向传播(冻结)
h = W @ x  # W是冻结的原始权重

# LoRA前向传播
h = W @ x + (B @ A) @ x  # B,A是可训练的旁路矩阵
    ^^^^^^   ^^^^^^^^^
    冻结     可训练 (秩r)

冻结参数 = Attention层 + FFN层的所有权重矩阵

具体来说,每个Transformer Block包含:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16

TransformerBlock:
├─ Self-Attention
│  ├─ W_q (冻结) + LoRA_q (可训练)  ← 只在这些矩阵加LoRA
│  ├─ W_k (冻结) + LoRA_k (可训练)
│  ├─ W_v (冻结) + LoRA_v (可训练)
│  └─ W_o (冻结) + LoRA_o (可训练)
│
├─ LayerNorm (冻结,不加LoRA)
│
├─ FeedForward (FFN)
│  ├─ W_gate (冻结) + LoRA_gate (可训练)
│  ├─ W_up (冻结) + LoRA_up (可训练)
│  └─ W_down (冻结) + LoRA_down (可训练)
│
└─ LayerNorm (冻结,不加LoRA)
  • 反向传播时梯度只流经 B 和 A → 显存占用大幅降低
  • 原始权重 W 被冻结 → 无需计算梯度 → 不占用梯度显存

在FFN层中,激活函数采用SwiGLU比GELU、ReLU、SiLU效果更佳:

  1. 门控机制:gate * up 让网络动态控制信息流动
  2. 更丰富的非线性:双重投影+乘法增加了表达能力
  3. Swish/SiLU激活:x * sigmoid(x),比ReLU平滑,在负区间有输出 LoRA后的FFN前向传播:
1
2
3
gate = (W_gate + B_gate @ A_gate) @ x  # 原始权重 + LoRA
up = (W_up + B_up @ A_up) @ x
output = (W_down + B_down @ A_down) @ (SiLU(gate) * up)

关于量化参数,刚开始我对这个概念理解得不够透彻。 实际上,量化并不是直接把权重变成4-bit,而是需要额外的元数据来还原原始值:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# ============= 标准量化过程 =============

原始权重 (FP16):
W = [0.123, -0.456, 0.789, -0.234, ...]  # 每个数2 bytes

# 步骤1: 找到最大最小值,计算缩放因子
max_val = max(W) = 0.789
min_val = min(W) = -0.456
scale = (max_val - min_val) / 15  # 4-bit有16个级别(0-15)
zero_point = 量化后的零点

# 步骤2: 量化公式
W_quantized = round((W - zero_point) / scale)
# 结果: [7, 2, 15, 4, ...]  # 每个数只用4 bits

# 步骤3: 反量化(推理时需要)
W_dequantized = W_quantized * scale + zero_point

量化过程需要保存scale和zero_point这两个量化参数(通常以FP32或FP16存储)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# ============= 单次量化 =============
原始权重 (FP16): 1.5B × 2 bytes = 3 GB
    ↓ 量化
4-bit权重: 1.5B × 0.5 bytes = 0.75 GB
量化参数: 23M × 4 bytes = 93.75 MB  ← 还是不小!
-----------------------------------------
总计: 0.75 + 0.094 ≈ 0.844 GB


# ============= 双重量化 =============
原始权重 (FP16): 1.5B × 2 bytes = 3 GB
    ↓ 第一次量化
4-bit权重: 1.5B × 0.5 bytes = 0.75 GB
量化参数1: 23M × 2 bytes (FP16) = 46.88 MB
    ↓ 第二次量化(对scale和zero_point量化)
量化参数1 (8-bit): 23M × 1 byte = 23.44 MB
量化参数2 (FP16): ~256 组 × 4 bytes = 1 KB
-----------------------------------------
总计: 0.75 + 0.023 + 0.001 ≈ 0.774 GB ⬇️ 节省~70MB

神经网络对微小扰动具有鲁棒性,同时量化参数的误差会被放大系数所缩小。双重量化是以微小的精度损失换取可观的显存节省,这个权衡在绝大多数场景下都是值得的。

QLoRA原论文(Dettmers et al., 2023)进行了详细实验:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
模型: LLaMA-65B
任务: 多种NLP benchmarks

┌─────────────────┬──────────┬──────────┬──────────┐
│ 配置            │ MMLU     │ BBH      │ 显存     │
├─────────────────┼──────────┼──────────┼──────────┤
│ FP16 全量微调   │ 63.4%    │ 51.2%    │ 780 GB   │
│ 4-bit单次量化   │ 63.2%    │ 51.0%    │ 48 GB    │
│ 4-bit双重量化   │ 63.1%    │ 50.9%    │ 41 GB    │
└─────────────────┴──────────┴──────────┴──────────┘

性能下降: 0.1-0.3个百分点 ⬇️ (可忽略)
显存节省: 7 GB ⬇️ (约15%)

r:秩(rank),推荐范围4-32,表示旁路矩阵的中间维度大小。

以 h = W @ x + (B @ A) @ x 为例:

  • W:原始预训练权重矩阵(冻结不动),维度为 [d_out, d_in]
  • A:下投影矩阵,维度为 [r, d_in]
  • B:上投影矩阵,维度为 [d_out, r]
  • r:LoRA秩(rank),是低秩分解的维度

假设原始权重 W 为 [4096, 4096](Qwen的某层维度):A 是 [8, 4096],B 是 [4096, 8],则参数量从原始的 16M → LoRA 仅 65K(减少99.6%)。

  • r 越大:模型容量越强,适应能力越好,但参数量相应增加
  • r 越小:参数极少,训练速度快,但可能欠拟合
  • 经验值:r=4~16 对大多数任务已足够,配置 r=8 是合理的选择 lora_dropout:在 B @ A 计算后、加回主路径前应用。标准dropout:应用于FFN输出、Attention输出等位置。

target_modules:指定在哪些模块上添加LoRA以节省训练成本,根据模型结构合理设置即可。

warmup_ratio:预热阶段占总训练步数的比例。预热期间学习率变化与主训练阶段不同。

计算示例:

  • 总步数 = (数据集大小 / 批次大小) × 训练轮数
  • 预热步数 = 总步数 × 0.1
  1. 预热阶段(0-10%步数):学习率从 0 线性增长到 2e-4
  2. 主训练阶段(10%-100%):学习率按余弦衰减至接近0

饭后和朋友一起打《大乱斗》,玩了好几个小时。

随后开始训练,但遇到了环境配置问题,调试了很久。最终放弃了我最习惯的uv pip虚拟环境,改用miniconda,否则会有太多适配问题。

另外我发现,让AI帮忙配置PyTorch时,它总是会自动安装2.5版本,而该版本与5070显卡不兼容。这似乎是因为其训练数据只截止到那个时期,只有明确要求联网搜索才能获得正确的版本信息。

配好环境后,我写了个脚本拉取50个样本,确认模型能够正常运行,然后使用wandb进行远程监控。

最后决定不在本地运行,将任务部署到Colab上,明天继续。已经2点了。

Related content

学习day28:

学习day27:

学习day26:

学习day25:

未学习day24:计划赶不上变化,又摆一天

© 2025 - 2026 Qianqiu
Built with Hugo
Theme Stack designed by Jimmy