精度调优方法及工具使用

精度调优方法

数据集清洗

在构建高质量机器学习模型的过程中,数据集清洗是一项至关重要的前期任务,旨在提升模型训练效果,防止过拟合及生成内容的冗余。

  • 非目标语言与低质量样本剔除:剔除非目标任务语言、丢弃低perplexity数据、删去标点/符号过多或过长过短的句子、删除具有某些特定词汇(如HTML标签、链接、脏话、敏感词)的句子。
  • 去重:删除包含大量重复词汇或短语的句子;删除重复率(词/n-grams共现)过高的段落;删除训练集中可能与测试集相关度过高的内容。这样可以提高训练集质量,缓解语言模型生成内容重复的问题,避免测试集泄露带来的过拟合问题。

当遇到loss或者perplexity尖刺,如果确定是数据批次引起的问题,可以跳过尖刺期间看到的一些数据批次,从以前正常的检查点重新开始训练。

超参数配置调优

  • batch size配置:在模型训练过程中,batch size的设定通常较大,旨在充分利用大规模训练数据,增强模型训练过程的稳定性。例如,设置batch size为8196,在样本序列长度为2k的情况下,可使每个批次处理大约16M个token输入。大样本的梯度平均值使得参数更新更精确,从而缓解不精确参数更新导致的loss跳变。

  • 学习率:学习率一般较小,且包含warm up设置,以确保训练平稳。比如在前0.1%~0.5%的训练步骤中,设置一个线性的学习率递增。峰值学习率一般在10-4以下,之后,会采用cosine衰减或者线性衰减学习率调度策略,逐渐减小学习率,在收敛前将学习率下降到峰值学习率的10%左右。

  • 优化器:优化器一般采用Adam、AdamW以及Adafactor。其中,Adafactor是Adam的一个节约显存的变体。也有使用SGD作为优化器的例子,但并不常见。

  • 权重初始化:正确初始化权重可以帮助模型更快地收敛并提高性能。例如,通常使用小的高斯噪声或者使用T-fixup初始化。

  • 其他稳定训练技术

    • 梯度裁剪(Gradient Clipping):作为一种常用的稳定训练手段,通常设定裁剪阈值为1.0,防止梯度过大引发训练不稳定。
    • Weight Decay(L2正则化):设置合理的权重衰减率,如0.1,有助于防止过拟合,增强模型泛化能力。
    • 特殊层的调整:embedding层往往存在较大的梯度异常情况,故需根据实际情况适度调整相关参数。

特定计算操作对计算精度的要求

在大规模深度神经网络的设计与训练过程中,针对不同计算操作选择适宜的精度类型至关重要,这一决策既要基于深厚的数学原理分析,又需丰富的实践经验支持。本节内容总结自大量论文中的实践经验,介绍影响模型性能的关键计算操作对精度的具体要求。

  • softmax函数:softmax函数中有指数运算,容易发生上溢出和下溢出,并且分母有累加操作,目前主流的算子库只使用FP32来实现softmax。
  • Cross Entropy损失计算:cross_entropy用来计算交叉熵损失,和很多损失函数一样,需要使用FP32计算以保持数值稳定性,所以算子实现只接受FP32输入。
  • 矩阵乘法(Matmul):matmul矩阵乘法一般来说可以使用半精度数,但模型的attention层,attention分数与V张量的矩阵乘法需要用FP32,以便维持数值稳定性,避免上溢,减小累积错误。
  • 归一化函数:归一化函数如layer_norm、Batch_norm、RMSNorm需要用FP32计算。layer_norm要计算方差,有累加操作,如果用FP16,则计算过程中容易发生上溢和比较大的误差,从而影响最后的收敛。Batch_norm与layer_norm类似,也需要将输入张量upcast到FP32计算。RMSNorm是layer_norm的扩展,在开源LLaMA模型中使用,PyTorch目前并没有提供RMSNorm的实现。如果开发者自行实现,需要注意将输入张量强制upcast到FP32计算,结果可以转回半精度数,主要原因也是它需要计算L2均值,涉及累加操作,低精度运算容易累积误差。
  • 卷积操作与池化:卷积操作属于逐单元矩阵乘法,对精度不敏感,可以使用BF16,不会对模型收敛性产生影响。和卷积类似的还有池化,同样可以使用BF16。ReLU和GeLU属于逐点运算,同样对精度不敏感。
  • 循环神经网络(RNNs):RNN一般对精度比较敏感,LSTM-cell对精度不敏感,可以用BF16。
  • 模型起始与结束层:通常模型的第一层和最后一层对精度敏感,比如embedding层需要使用FP32。一般的输出层即便不是softmax,计算和输出结果也需要是FP32。
  • 通信算子:涉及到梯度或者激活值累加的通信算子如all_reduce、reduce_scatter,都需要把输入张量upcast到FP32以保证数值稳定性。

使用Monitor工具调试模型收敛问题

当遇到模型收敛问题时,一般的做法是将怀疑有问题的张量进行全量或者压缩后打印,这种方法存在以下缺点:

  1. 自行添加的打印代码可能会对性能产生较大的影响,导致问题无法复现或者性能受到严重影响,无法跟随训练长期监视。
  2. 添加张量打印代码需要对训练框架或者模型结构非常了解,并且需要较长时间的代码测试。导致自行添加张量打印门槛较高。
  3. 当调试人员和模型开发人员不属于同一个组织时,调试人员无法访问所有的训练脚本,无法快速添加张量打印代码,并根据调试情况修改dump代码。

因此,为了帮助昇腾客户在实际调试模型过程中解决各种困难,开发了基于张量探查技术的Monitor调试工具。

Monitor工具介绍

Monitor工具具备以下功能:

  • 打印模型层次结构和训练使用的集合通信API,以便于接下来指定要监控的网络层和通信。
  • 支持添加几行代码就可以分层按需探查训练中指定的各种中间张量,例如参数梯度、激活值梯度、权重和Adam优化器状态、梯度同步“all-reduce/reduce-scatter”和参数“all-gather”集合通信算子的输入输出张量。
  • 支持多种张量压缩操作,例如norm、max、min和zeros。
  • 支持张量特征值异常波动监测和告警。

Monitor工具的详细使用方法请参见LINK

使用Monitor工具解决训练问题

  • 确定llama2 loss尖刺根因

    某位昇腾用户在训练类llama2-13b模型时,遇到频繁的loss尖刺,怀疑是因为Adam优化器超参配置不合理所导致的。随后使用Monitor工具,采集了词嵌入层的实际更新张量,公式如下:

    从采集结果可以看出,在训练稳定后的很长一段时间,词嵌入层几乎没有更新。然而,在遇到一个特定(不一定是坏数据)的batch时,词嵌入层突然发生了剧烈更新。最终确认是由于Adam优化器beta2偏大所导致的问题,当降低beta2后,问题得到解决。

  • 确定Open Sora在deepspeed ZeRO-1设置下不收敛问题

    有用户反映在DeepSpeed ZeRO-1下进行训练时,使用FP32类型的反向梯度计算可以正常收敛,但使用BF16的混合精度训练却无法收敛。通过使用Monitor工具,对比Open Sora模型靠近输入的网络层(早期层)的权重梯度在dtype为FP32和BF16时的变化趋势。发现在loss跑飞前的几百步,早期层梯度在FP32和BF16下都会有上升,但由于BF16的浮点精度问题,BF16下的梯度波动更为剧烈。当用户将控制梯度裁剪的max norm值设为1,远大于正常训练时的gnorm(约为1e-4),导致梯度裁剪无法工作。因此,降低梯度裁剪的max norm参数到正确范围, BF16下的混合精度可以正常收敛。

使用工具来调试训练问题,相比于盲目调整超参,更系统化和具有确定性。在大规模训练场景下,解决问题的时间会大大缩短,节约了大量计算资源。所以我们推荐在遇到训练收敛问题时,首先使用Monitor工具打开训练过程。

训练状态监控

在模型训练过程中,为确保训练的稳定性与有效性,需密切关注多项关键指标以评估训练状态,其中包括但不限于perplexity (PPL)、gradient norm (GNorm)、activation norm、内存占用情况以及Loss scale等参数。推荐采用TensorBoard工具进行数据可视化。

图 1 TensorBoard上的数据可视化

另外,在模型的训练中,我们可以通过PyTorch中的hook机制对容易出现问题的某些层配置hook,监控这些层的梯度信息,及时处理出现异常的step以减少对模型训练效果的影响。具体操作如下:

  1. 在train方法中获取模型结构后,将模型传入指定的collector中。

  2. 对指定层的tensor注册tensor hook,该类型hook只返回对应tensor的梯度信息,该hook会在每份micro batch数据完成反向传播后调用,即对于每份micro batch数据均有梯度值的记录。

对该梯度信息进行监控和分析,可以检查训练中的一些异常状态,收集梯度值时,建议只采集梯度的最大值、最小值、平均值等统计信息作为训练状态的监控指标。

除了梯度爆炸问题的监控,我们也要关注梯度下溢问题。特别是对于FP16,如果梯度出现大量0,往往意味着训练出现不正常,这时候需要停止训练,找出问题(例如伪FP32,实际是混合精度)。如果训练器使用了Loss scale,则监控Loss scale是必须的。