深度学习第四版
⚪LoRA 完整公式:h = W₀ · x + (α / r) · B · A · x A 和 B 具体是什么
r(秩)决定参数量,α(缩放系数)决定数值大小,这个增量的数值越大,对原始权重的修改幅度越大。
A 和 B 就是两个普通的线性层(nn.Linear),没有偏置项。
假设原始模型中有一个线性层,权重为 W₀ ∈ R^(4096 × 4096),r = 16:
原始结构:
x → [W₀] → h
就是一个矩阵乘法:h = W₀ · x
加入 LoRA 后的结构:
┌──── [W₀] (冻结,不更新) ────┐
│ │
x ───────┤ ├── 相加 ── h
│ │
└── [A] ── [B] ── × (α/r) ────┘
多了一条并联的旁路,由两个小线性层串联组成:
-
A 是一个 nn.Linear(4096, 16),把 4096 维压缩到 16 维
-
B 是一个 nn.Linear(16, 4096),把 16 维还原到 4096 维
前向传播时,两条路径的输出直接相加:
h = W₀ · x + (α / r) · B(A(x))
用 PyTorch 代码理解
原始线性层:
self.W0 = nn.Linear(4096, 4096) # 原始权重,冻结
LoRA 加的两个层:
self.A = nn.Linear(4096, 16) # 降维,可训练
self.B = nn.Linear(16, 4096) # 升维,可训练
前向传播:
h = self.W0(x) + (alpha / r) * self.B(self.A(x))
就这么简单。A 和 B 不是什么抽象的数学概念,就是两个实实在在的小线性层,挂在原始层旁边。
训练过程中发生了什么
冻结阶段(训练开始前):
把原始模型所有参数设为不可训练:
for param in model.parameters():
param.requires_grad = False
然后给需要微调的层(通常是 attention 的 Q/K/V/O 投影)各插入一对 A、B,设为可训练。
前向传播:
输入 x 同时经过两条路径。W₀ 路径正常计算但不记录梯度,A → B 路径计算并记录梯度。两条路径的输出相加得到 h。
反向传播:
损失函数对 h 求梯度,梯度沿着 A → B 这条旁路反传,更新 A 和 B 的参数。W₀ 因为被冻结,不接收梯度,始终不变。
参数更新的具体过程:
每一步训练中,优化器(比如 AdamW)只看 A 和 B 的梯度:
optimizer = AdamW([A的参数, B的参数], lr=1e-4)
优化器根据梯度更新 A 和 B 的权重数值,使得 B · A 这个增量逐渐逼近让损失最小的方向。
一个具体的数值例子
假设训练前 B 初始化为零矩阵,所以 B · A = 0,模型行为和原始完全一样。
训练第 1 步:损失函数产生梯度 → B 的某些位置从 0 变成了微小的非零值(比如 0.001)→ B · A 不再为零 → 模型输出开始偏离原始模型
训练第 100 步:B 和 A 的值经过多次更新,B · A 已经形成了一个有意义的增量矩阵 → 这个增量就是模型为了适应下游任务而学到的"修正量"
训练结束:B · A 收敛到最优增量,合并回原始权重 W_merged = W₀ + (α/r) · B · A
为什么要拆成两个小矩阵而不是直接加一个大矩阵
如果直接加一个可训练的 ΔW ∈ R^(4096 × 4096),参数量是 4096 × 4096 = 16,777,216。
拆成 A 和 B 后,参数量是 4096 × 16 + 16 × 4096 = 131,072,只有原来的 0.78%。
代价是 ΔW = B · A 的秩最多为 r = 16,不能表示任意的 4096 × 4096 矩阵。但实验证明下游任务适配只需要很低的秩就够了,这个代价可以接受。
⚪Adam 优化器完整总结
一、核心符号定义
|
符号 |
含义 |
|---|---|
|
θ |
模型参数 |
|
g_t |
第 t 步的梯度,即 ∂L/∂θ |
|
m_t |
一阶动量(梯度的指数加权平均) |
|
v_t |
二阶动量(梯度平方的指数加权平均) |
|
η |
学习率 |
|
β₁ |
一阶动量衰减系数,通常 0.9 |
|
β₂ |
二阶动量衰减系数,通常 0.999 |
|
ε |
防止除零的极小常数,通常 1e-8 |
二、Adam 的三步计算
第一步:更新一阶动量
m_t = β₁ · m_(t-1) + (1 - β₁) · g_t。对梯度本身做指数加权平均。作用:给梯度加惯性。
-
连续多步梯度方向一致(比如一直为正)→ m_t 累积变大 → 参数更新加速
-
梯度方向来回震荡(一会正一会负)→ m_t 中正负抵消 → 参数更新变小
类比:一个球在坡上滚,方向一致就越滚越快,左右颠簸就减速。
第二步:更新二阶动量
v_t = β₂ · v_(t-1) + (1 - β₂) · g_t²,对梯度的平方做指数加权平均。
作用:衡量每个参数的梯度幅度有多大,自适应调整步长。
-
某参数历史梯度幅度大 → v_t 大 → 分母大 → 实际步长小
-
某参数历史梯度幅度小 → v_t 小 → 分母小 → 实际步长大
本质:自动给每个参数分配不同的学习率——梯度大的走小步,梯度小的走大步。
第三步:参数更新
θ_(t+1) = θ_t - η · m_t / (√v_t + ε)
三、为什么二阶动量用梯度平方而不是梯度本身
二阶动量需要衡量的是梯度的"幅度",不是"方向"。
例子: 某参数连续 4 步梯度为 +5, -5, +5, -5
-
对梯度本身求平均:(+5 - 5 + 5 - 5) / 4 = 0 → 误判为"梯度很小",给大步长 → 导致震荡
-
对梯度平方求平均:(25 + 25 + 25 + 25) / 4 = 25,√25 = 5 → 正确反映"幅度一直是5",给小步长
根本原因:梯度有正有负,直接平均会正负抵消丢失幅度信息;平方后全为正,不会抵消。
四、一阶动量与二阶动量对比
|
一阶动量 m_t |
二阶动量 v_t |
|
|---|---|---|
|
公式 |
β₁ · m_(t-1) + (1 - β₁) · g_t |
β₂ · v_(t-1) + (1 - β₂) · g_t² |
|
平均的对象 |
梯度本身 |
梯度的平方 |
|
记录什么 |
梯度方向的历史趋势 |
梯度幅度的历史大小 |
|
作用 |
加速一致方向、抑制震荡 |
自适应调整每个参数的步长 |
|
在更新公式中的位置 |
分子(决定更新方向和大小) |
分母(归一化步长) |
|
是否逐参数独立 |
是 |
是 |
六、AdamW 与 Adam 的区别
标准 Adam 的权重衰减和梯度更新是耦合的(权重衰减被加进梯度里再做自适应缩放),这会导致正则化效果被 v_t 的缩放削弱。
AdamW 将权重衰减解耦,直接在参数上减去:
θ_(t+1) = θ_t - η · m̂_t / (√v̂_t + ε) - η · λ · θ_t
其中 λ 是权重衰减系数。这样权重衰减不经过 Adam 的自适应缩放,正则化效果更稳定。
七、Adam 的显存开销
以参数量为 X 的模型为例,Adam 需要为每个参数维护:
|
存储项 |
精度 |
每参数字节数 |
总大小 |
|---|---|---|---|
|
FP32 权重主副本 |
FP32 |
4 |
4X |
|
一阶动量 m |
FP32 |
4 |
4X |
|
二阶动量 v |
FP32 |
4 |
4X |
|
合计 |
12X 字节 |
混合精度训练下的完整显存(含权重和梯度):
|
组成部分 |
精度 |
大小 |
|---|---|---|
|
模型权重(前向/反向用) |
FP16 |
2X(在训练模型前向传播中的conv值,训练结束会消失) |
|
梯度 |
FP16 |
2X(一批次前向传播结束,反向传播产生的所有模型的全部参数梯度) |
|
FP32 权重主副本 |
FP32 |
4X |
|
Adam 一阶动量 m |
FP32 |
4X |
|
Adam 二阶动量 v |
FP32 |
4X |
|
固定总计 |
16X 字节(16倍混合精度) |
|
|
激活值 |
FP16 |
视 batch size |
⚪参数更新始终在 FP32 上完成,FP16 仅用于加速计算,每一轮都会由 FP32 权重重新生成。
| 步骤 | 阶段 | 使用精度 | 涉及变量 | 做什么 | 是否保留 |
|---|---|---|---|---|---|
| 1 | 前向传播 | FP16 | W_fp16, x | 计算模型输出 y = Wx | ❌ |
| 2 | 损失计算 | FP16 | pred, label | 计算 loss | ❌ |
| 3 | 反向传播 | FP16 | grad_fp16 | 计算梯度 ∂L/∂W | ❌ |
| 4 | 梯度传递(gradscaler) | FP16 → FP32 | grad | 传给优化器 | ❌ |
| 5 | 参数更新(副本权重) | FP32 | W_fp32 | W = W - η·g(核心) | ✅ |
| 6 | 精度转换(梯度缩放回去) | FP32 → FP16 | W_fp16 | 截断用于下一轮计算 | ❌ |
| 7 | 下一轮训练 | FP16 | W_fp16 | 继续前向传播 | ❌ |
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)