打碎炼丹炉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