打碎炼丹炉!!

25 年 6 月 2 日 星期一
872 字
5 分钟

打碎炼丹炉1!

我真的缺显存---显存管理

理论基础

  • 模型参数: 模型的层数、宽度(神经元/通道数)、参数量直接影响存储参数所需的显存。
  • 激活值: 前向传播过程中每一层计算的中间结果(激活值)。这是显存消耗的大头,尤其对于深层网络和大批量大小。激活值需要在反向传播时用于计算梯度。
  • 优化器状态: 如 Adam 优化器需要存储每个参数的动量(momentum)和方差(variance)估计值。对于 Adam,每个参数需要额外 2 倍的 FP32 状态。模型参数本身通常也是 FP32。
  • 梯度: 反向传播计算的梯度,通常与模型参数同精度(FP32)。
  • 批量大小: 更大的批量大小意味着同时处理更多样本,导致每一层的激活值显存占用线性增加。
  • 数据类型: FP32 (单精度) 比 FP16 (半精度) 或 BF16 (Brain Float 16) 占用多一倍显存

及时释放显存

python
del model
torch.cuda.empty_cache()

混合精度处理

python
from torch.cuda.amp import autocast, GradScaler

scaler = GradScaler()

for inputs, labels in train_loader:
    optimizer.zero_grad()

    with autocast():
        outputs = model(inputs)
        loss = criterion(outputs, labels)

    scaler.scale(loss).backward()
    scaler.step(optimizer)
    scaler.update()

梯度清空

python
for inputs, labels in train_loader:
    scaler.update()
    optimize.zero_grad()
powershell
nvidia-smi

检查GPU和显存

对不起!五音不全---lr调节

线性

python
scheduler = torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones=[30, 80], gamma=0.1)

第30轮和第80轮lr变成0.1lr

python
scheduler = StepLR(filter_optimizer, step_size=2, gamma=0.7)

每两轮变成x0.7

余弦

python
# 标准余弦退火 (整个训练过程为一个周期)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=total_epochs, eta_min=1e-6)

# 带重启的余弦退火 (SGDR)
scheduler = torch.optim.lr_scheduler.CosineAnnealingWarmRestarts(optimizer, T_0=50, T_mult=2, eta_min=1e-6)
# T_0: 第一个周期的 epoch 数, T_mult: 后续周期 = T_0 * (T_mult)^(restart_index)

基于验证集

python
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
    optimizer,
    mode='min',       # 'min' 监控指标下降 (如 loss), 'max' 监控指标上升 (如 accuracy)
    factor=0.5,       # LR 衰减因子
    patience=10,      # 等待多少个 epoch 指标无改善
    verbose=True,     # 打印衰减信息
    threshold=1e-4,   # 改善小于此阈值视为无改善
    min_lr=1e-6       # 允许的最小 LR
)

# 在验证循环后调用
val_loss = ...  # 计算验证损失
scheduler.step(val_loss)

自定义warm-up

python
from torch.optim.lr_scheduler import _LRScheduler

class LinearWarmupScheduler(_LRScheduler):
    def __init__(self, optimizer, warmup_steps, last_epoch=-1):
        self.warmup_steps = warmup_steps
        super().__init__(optimizer, last_epoch)

    def get_lr(self):
        if self.last_epoch < self.warmup_steps:
            # 线性增长阶段
            return [base_lr * min(1.0, self.last_epoch / self.warmup_steps)
                    for base_lr in self.base_lrs]
        else:
            # 预热结束后返回基础学习率
            return self.base_lrs

# 使用示例
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
warmup_scheduler = LinearWarmupScheduler(optimizer, warmup_steps=1000)

for step in range(total_steps):
    # 训练步骤...
    optimizer.step()
    warmup_scheduler.step()

优化我真学不会

优化器好难........之后再出,常见的adamw,SGD等更应该考虑的其实是数学原理

停不下来--early-stoping

这个挺无聊的,单纯就是验证集好了就不炼了,意义不大

python
import numpy as np
import torch

class EarlyStopping:
    def __init__(self, patience=10, min_delta=0, mode='min', restore_best_weights=True):
        self.patience = patience
        self.min_delta = min_delta
        self.mode = mode
        self.restore_best_weights = restore_best_weights
        self.counter = 0
        self.best_value = np.Inf if mode == 'min' else -np.Inf
        self.best_weights = None
        self.stopped_epoch = 0

    def __call__(self, current_value, model):
        if self.mode == 'min':
            is_better = current_value < (self.best_value - self.min_delta)
        else:  # mode == 'max'
            is_better = current_value > (self.best_value + self.min_delta)

        if is_better:
            self.best_value = current_value
            self.counter = 0
            # 深拷贝当前模型状态字典作为最佳权重
            self.best_weights = {k: v.cpu().clone() for k, v in model.state_dict().items()}
        else:
            self.counter += 1
            if self.counter >= self.patience:
                self.stopped_epoch = epoch  # 假设 epoch 在外部定义
                if self.restore_best_weights and self.best_weights is not None:
                    model.load_state_dict(self.best_weights)
                return True  # 发出停止信号
        return False  # 继续训练

# 使用示例
early_stopping = EarlyStopping(patience=10, min_delta=0.001, mode='min', restore_best_weights=True)

for epoch in range(max_epochs):
    # ... 训练一个 epoch ...
    # ... 在验证集上评估 ...
    val_loss = validate(model, val_loader)  # 假设 validate 函数返回验证损失

    if early_stopping(val_loss, model):
        print(f'Early stopping triggered at epoch {epoch}! Best Val Loss: {early_stopping.best_value:.4f}')
        break

文章标题:打碎炼丹炉!!

文章作者:io-wy

文章链接:https://io-wy.github.io/posts/%E6%89%93%E7%A2%8E%E7%82%BC%E4%B8%B9%E7%82%891[复制]

最后修改时间:


商业转载请联系站长获得授权,非商业转载请注明本文出处及文章链接,您可以自由地在任何媒体以任何形式复制和分发作品,也可以修改和创作,但是分发衍生作品时必须采用相同的许可协议。
本文采用CC BY-NC-SA 4.0进行许可。