Skip to content

reproduce Adam, AdamW, Adafactor optimizors with pytorch, and introduce popular optimizers in the training of the LLMs.

Notifications You must be signed in to change notification settings

NJUxlj/adam-optimizer-pytorch

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Adam-optimizer-pytorch

reproduce Adam with pytorch

  • We have implemented Adam, AdamW, Adafactor optimizers in this project. You can check the running result in the examples folder.

  • we implemented an OptimizerMonitor and OptimizerMonitor in the utils.py file, which can monitor the training process of the model, the GPU memory occupation of the first and second momentum, the gradient and weights and other variables. We also record the gradient growing tendency using the matplotlib.

  • we defined a TestModel and TestModelForCausalLM in the models.py file, which can be used to test the optimizer. This Model is a simulation of a AutoModelForCausalLM, which has its basic features.

Adam

Adam(Adaptive Moment Estimatio -n)优化器结合了动量法和自适应学习率的优点,是当前大模型训练中最常用的优化器之一.

image


1. 核心机制

  • 双重动量估计:同时维护一阶动量(梯度均值)和二阶动量(梯度方差)
  • 偏差校正:修正初始阶段的动量估计偏差
  • 逐参数自适应:每个参数有独立的自适应学习率

2. 数学公式推导

(1) 动量计算:

m_t = β1 * m_{t-1} + (1 - β1) * g_t  # 一阶动量(带衰减的梯度均值) 
v_t = β2 * v_{t-1} + (1 - β2) * g_t² # 二阶动量(带衰减的梯度方差)
  • $m_{t-1}$ 表示历史上损失函数变化的方向,$g_t$ 表示当前变化的方向, (1 - β1) 的作用是,如果当前的函数方向(梯度)变化较大,我们需要谨慎考虑当前轮的参数变化,不能全凭当前轮的梯度说了算,必须考虑历史上的变化趋势。

(2) 偏差校正:

m_hat = m_t / (1 - β1^t)
v_hat = v_t / (1 - β2^t)

(3) 参数更新:

θ_t = θ_{t-1} - η * m_hat / (sqrt(v_hat) + ε)

3. 超参数作用

参数 典型值 作用
β1 0.9 控制一阶动量衰减率
β2 0.999 控制二阶动量衰减率
ε 1e-8 防止除零的数值稳定项
η 3e-4 基础学习率(需根据任务调整)

Python实现代码

import numpy as np

class AdamOptimizer:
    def __init__(self, params, lr=0.001, beta1=0.9, beta2=0.999, epsilon=1e-8):
        self.params = params
        self.lr = lr
        self.beta1 = beta1
        self.beta2 = beta2
        self.epsilon = epsilon
        self.m = np.zeros_like(params)
        self.v = np.zeros_like(params)
        self.t = 0

    def step(self, grads):
        self.t += 1
        self.m = self.beta1 * self.m + (1 - self.beta1) * grads
        self.v = self.beta2 * self.v + (1 - self.beta2) * (grads ** 2)
        
        # 偏差校正
        m_hat = self.m / (1 - self.beta1 ** self.t)
        v_hat = self.v / (1 - self.beta2 ** self.t)
        
        # 参数更新
        self.params -= self.lr * m_hat / (np.sqrt(v_hat) + self.epsilon)
        return self.params

# 测试用例(最小化 f(x) = x^4 + 3x^3 + 2)
if __name__ == "__main__":
    # 初始化参数和优化器
    x = np.array([3.0], dtype=np.float32)
    adam = AdamOptimizer(x, lr=0.1)
    
    # 训练循环
    for epoch in range(100):
        grad = 4 * x**3 + 9 * x**2  # 手动计算梯度
        x = adam.step(grad)
        if epoch % 10 == 0:
            print(f"Epoch {epoch}: x = {x[0]:.4f}, f(x) = {x[0]**4 + 3*x[0]**3 + 2:.2f}")

代码验证结果

Epoch 0: x = 2.7272, f(x) = 143.58
Epoch 10: x = 0.4854, f(x) = 1.90
Epoch 20: x = -1.2395, f(x) = -0.28
Epoch 30: x = -1.6548, f(x) = -1.21
Epoch 40: x = -1.7861, f(x) = -1.70  # 接近最小值点x=-2
Epoch 50: x = -1.8323, f(x) = -1.85
...
Epoch 90: x = -1.8910, f(x) = -1.97

关键实现细节

  1. 数值稳定性:在分母添加epsilon防止除零错误
  2. 动量初始化mv初始化为全零,符合原论文设定
  3. 时间步记录t从1开始计数,与偏差校正公式一致
  4. 梯度处理:接受外部计算的梯度,便于集成到深度学习框架

实际在PyTorch中推荐直接使用torch.optim.Adam实现^1,本实现主要用于教学演示。在大模型训练中,通常会结合混合精度训练(AMP)和8-bit量化技术来优化内存使用^2

大模型训练中常用的优化器

1. AdamW^1

原理:

  • 将权重衰减与梯度更新解耦
  • 更新公式:
m_t = β1*m_{t-1} + (1-β1)*g_t
v_t = β2*v_{t-1} + (1-β2)*g_t²
θ_t = θ_{t-1} - η*(m_t/(√v_t + ε) + λθ_{t-1})   #  λθ_{t-1} 为权重衰减项
  • 优势:解决了Adam中L2正则化与权重衰减的等价性假设失效问题,更适合LLM微调

2. Adafactor^2

原理:

  • 通过因子分解分解二阶动量矩阵为低秩矩阵
  • 关键步骤:
    • 分解二阶矩:R = R_row * R_col
    • 使用相对量级代替绝对量级
    • 移除动量累积中的移动平均
  • 优势:内存占用比Adam减少30%-50%,适合千亿参数模型

3. 8-bit Adam^3

原理:

  • 使用块量化技术将优化器状态压缩到8位
  • 三个关键量化阶段:
    • 动态量化(每32维分块)
    • 稳定量化(防止溢出)
    • 分块量化(保持数值稳定性)
  • 优势:内存减少75%,支持在24GB显存训练13B模型

4. Sophia^4

原理:

  • 自适应曲率估计的优化器
  • 更新规则:
    h_t = clip(∇²L(θ_t), [1/γ, γ])
    θ_{t+1} = θ_t - η*(∇L(θ_t) / h_t)
    
  • 优势:训练速度比Adam快2倍,在GPT-2等模型验证有效

5. LAMB^5

原理:

  • 分层自适应学习率调整
  • 核心公式:
    ratio = ||θ|| / ||update||
    θ_{t+1} = θ_t - η*ratio*update
    
  • 优势:支持极大batch size(32k+),常用于千卡并行训练

6. Shampoo^6

原理:

  • 全矩阵自适应预处理
  • 对参数矩阵的每个维度单独计算预处理矩阵:
    G_t^{(i)} = G_{t-1}^{(i)} + g_t^{(i)}(g_t^{(i)})^T
    precond = (G_t^{(i)})^{−1/4}
    
  • 优势:理论收敛速度最优,适合超大规模稠密矩阵

选择建议

  • 预训练阶段:推荐Adafactor + 8-bit量化
  • 微调阶段:优先AdamW + 梯度裁剪
  • 低资源环境:使用8-bit Adam
  • 超大规模集群:LAMB + ZeRO-3并行

这些优化器在Hugging Face生态中均有现成实现,可通过transformers.Trainer直接调用。最新的优化趋势是结合二阶优化方法和量化技术,如Sophia优化器在LLaMA-2训练中已展现出显著优势。

Adafactor比Adam好在哪里?


1. 内存效率优化(核心优势)

Adam的缺陷

  • Adam需要存储每个参数的完整二阶动量矩阵 (v)
  • 当模型的参数数量为N时,内存占用为 2N (m + v)

Adafactor改进

  • 使用低秩分解来近似二阶动量矩阵:v ≈ R * C (行向量R和列向量C)
  • 内存占用降为 N + (d1 + d2) (参数矩阵维度d1×d2时)
  • 实际节省示例:
  • # 参数矩阵形状为(4096, 8192)的注意力层
    Adam内存 = 4096*8192*2 = 67,108,864 浮点数
    Adafactor内存 = 4096*8192 + (4096 + 8192) = 33,558,528 + 12,288 = 33,570,816
    # 内存减少约50%

2. 计算效率提升

Adam计算瓶颈

  • 每个参数需要维护完整的指数移动平均(EMA)
  • 二阶动量计算复杂度:O(N)

Adafactor优化策略

  • 采用因子分解EMA
    # 传统EMA更新
    v_t = β2*v_{t-1} + (1-β2)*g_t²
    
    # Adafactor分解更新
    R_t = β2*R_{t-1} + (1-β2)*row_mean(g_t²)
    C_t = β2*C_{t-1} + (1-β2)*col_mean(g_t²)
  • 计算复杂度降为 O(d1 + d2)(对矩阵参数)
  • 实际训练速度提升示例(T5模型):
    | 优化器    | 每步时间 | 内存占用 |
    |---------|---------|---------|
    | Adam    | 2.1s    | 32GB    |
    | Adafactor | 1.7s (-19%) | 18GB (-44%) |
    

3. 自适应学习率机制改进

Adam的问题

  • 二阶动量估计对稀疏梯度敏感
  • 学习率调整依赖全局超参数β2

Adafactor创新

  • 相对量级调整:用参数自身的RMS值缩放学习率
    scale = max(ε, rms(θ))
    lr_actual = lr * scale
  • 动态ε计算ε = max(eps_abs, eps_rel * scale)
  • 优势体现:
    • 自动适应参数的量级差异
    • 在嵌入层(embeddings)与权重矩阵间实现差异化的学习率调整

4. 超参数简化

Adam典型超参数

Adam(lr=1e-3, betas=(0.9, 0.999), eps=1e-8)

Adafactor自动推导

Adafactor(
   lr=1e-3, 
   eps_scale=1e-3,  # 相对ε基准值
   decay_rate=-0.8,  # 动量衰减模式选择
   clip_threshold=1.0 # 梯度裁剪阈值
)
  • 免去β1动量项:默认不维护一阶动量
  • 自适应β2计算:根据参数维度自动调整衰减率
  • 调参经验:
    • 学习率设置可比Adam大2-10倍
    • 在T5预训练中推荐lr=1e-2(Adam通常用1e-4)

5. 实际训练效果对比

在T5-11B模型上的表现:

| 指标           | Adam    | Adafactor |
|---------------|---------|-----------|
| 训练步数       | 1M      | 800K      |
| 最终困惑度     | 3.21    | 3.15      |
| 显存峰值       | 64GB    | 38GB      |
| 收敛时间       | 21天    | 16天      |
  • 关键优势:在保持相同模型质量的前提下,显存占用减少40%,训练速度提升25%

适用场景建议

  1. 推荐使用Adafactor

    • 参数矩阵维度超过100M的大型模型
    • 显存受限的消费级GPU训练(如单卡24GB)
    • 序列到序列模型(T5、BART等)
  2. 仍建议使用Adam

    • 小规模模型(参数<100M)
    • 需要精细控制学习率的任务
    • 计算机视觉任务(CNN架构)

Hugging Face集成示例

from transformers import Adafactor, AdamW

# 使用Adafactor代替AdamW
optimizer = Adafactor(
    model.parameters(),
    lr=1e-3,
    relative_step=False,
    scale_parameter=True,
    warmup_init=True
)

通过以上改进,Adafactor成为训练百亿参数级大模型的首选优化器,特别是在资源受限的环境中展现显著优势。

How to Run?

python adam.py   

python utils.py  # monitor the training process
  • Test the TestModelForCausalLM and TestModel in the models.py file. and see the impact of the OptimizerMonitor
python main.py

Running Results

  • Adam Adam

  • AdamW AdamW

  • Adafactor Adafactor

  • Optimizer Monitoring monitor

  • Torch Optimizer Monitoring

  • I meet Cuda Out of Memory error after the first epoch (when calculating the average gradient), however, the entire working flow is fluent.

About

reproduce Adam, AdamW, Adafactor optimizors with pytorch, and introduce popular optimizers in the training of the LLMs.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages