训练大型网络模型的技巧
本文主要介绍模型训练中速度和内存的优化策略,针对以下几种情况:
-
我明天就要答辩了,今天必须把这十个实验跑完 -
我的模型有些大,好不容易放到一张卡上,训完一亿样本之前我就可以领N+1了 -
我想出了一个绝妙的T6模型,却加载不进12GB的卡里,又拿不到今年的best paper了
一
梯度累加 Gradient Accumulation
for i, (inputs, labels) in enumerate(training_set):
loss = model(inputs, labels) # 计算loss
optimizer.zero_grad() # 清空梯度
loss.backward() # 反向计算梯度
optimizer.step() # 更新参数
for i, (inputs, labels) in enumerate(training_set):
loss = model(inputs, labels) # 计算loss
loss = loss / accumulation_steps # Normalize our loss (if averaged)
loss.backward() # 反向计算梯度,累加到之前梯度上
if (i+1) % accumulation_steps == 0:
optimizer.step() # 更新参数
model.zero_grad() # 清空梯度
二
梯度检查点 Gradient Checkpointing
三
混合精度训练 Mixed Precision Training
//FP32的gelu运算float gelu(float x)
{
float cdf = 0.5f * (1.0f + tanhf((0.7978845608028654f * (x + 0.044715f * x * x * x))));
return x * cdf;
}
//FP16的gelu运算half2 gelu(half2 val)
{
half2 val_pow3 = __hmul2(val, __hmul2(val, val)); //同时计算两个x*x*x
float2 tmp_pow = __half22float2(val_pow3);
float2 cdf = __half22float2(val);
//由于tanhf不支持half2类型,只能分开算
cdf.x = 0.5f * (1.0f + tanhf((0.7978845608028654f * (cdf.x + 0.044715f * tmp_pow.x))));
cdf.y = 0.5f * (1.0f + tanhf((0.7978845608028654f * (cdf.y + 0.044715f * tmp_pow.y))));
//同时计算两个x * cdf;return __hmul2(val, __float22half2_rn(cdf));
}
-
混合精度训练不是单纯地把FP32转成FP16去计算就可以了,只用FP16会造成80%的精度损失 -
Loss scaling:由于梯度值都很小,用FP16会下溢,因此先用FP32存储loss并放大,使得梯度也得到放大,可以用FP16存储,更新时变成FP32再缩放 -
在涉及到累加操作时,比如BatchNorm、Softmax,FP16会上溢,需要用FP32保存,一般使用GPU中TensorCore的FP16*FP16+FP32=FP32运算