Lazy loaded image
记录-模型训练如何使用混合精度
字数 2932阅读时长 8 分钟
2025-7-12
2025-9-6
AI智能摘要
GPT
这里是萌新AI,这篇文章介绍了混合精度训练的核心概念与实践方法。首先,文章解释了 fp16、fp32 和 bf16 等精度格式在 PyTorch 中的含义,包括它们的位表示和数值计算方式。接着,作者阐述了混合精度如何结合不同精度来加速训练并减少内存占用,同时提到了可能遇到的数值稳定性问题及其解决方案。最后,博客简要总结了混合精度的优势,并提供了实践代码以供参考。
URL
type
status
date
slug
summary
tags
category
icon
password
😀
此篇博客主要记录混合精度的学习过程,主要涉及混合精度的概念和实践代码。

📝 混合精度的概念

精度(PyTorch)

在训练模型或者复现别人的代码中,伙伴们可能会经常看到fp16,fp32和bf16的字样。大家有没有想过这些字样代表什么?在训练模型过程中会出现什么问题以及如何解决?
首先,让我们一起了解fp16,fp32和bf16到底是什么?以及在计算机中如何计算?
  • fp16:半精度浮点数据,字节数16bit,1bit符号位,5bit指数位,10bit尾数位。如下图(1):
notion image
符号位:顾名思义表示数值的正负。
指数位:这里与大家所学的进制计算不同。在Pytorch计算中,指数位代表的是数值区间。详细说明如下:5bit的指数位可以表示整数数值范围[0~2^6-1]。在Pytorch中,5bit的全0和全1具有特殊含义,不在数值范围中。所以,5bit的整数数值范围[1, 30]。由于指数位代表的是数值区间,那么这个区间应该是从一个很小很小的小数开始,而不是从[2^1,2^30)。根据上诉描述,针对这30个整数数值,一半取负数,一半取正数。由于正数和负数之间还有一个数是0,所以整数数值范围[-14,15)。根据[-14,15)的整数数值范围,则数值区间如下:
从区间图可以看出,指数位的整数数值代表的是哪一个区间。
以图1为例:指数位的数值是30,选取的区间是[2^15,2^16)。目标数值 = 2^15 + γ。如何计算γ是我们接下来要分析的!
尾数位:尾数位是在目标区间(平均划分)中获取相应的占比。fp16的尾数位占10bit,那么目标区间平均划分2^10=1,024份。尾数位的二进制数值表示1024份中占了多少份。那么γ的数值为:
则,目标数值:
总结:从上诉分析中,大家可能会发现:当区间表示数值范围较小时,在此区间的数据精度较高。在数值范围较大的区间,数据精度就会下降。以下是代码示例:
分析:为什么会出现这种情况?这是由于Pytorch的精度使用的是区间划分来表示一个具体数值,可以借鉴微积分理解,只要在区间内划分的份数越多,那么精度越高。接下来详细解释为什么会出现上诉代码中的情况(与图相集合)?
区间:[2^15,2^16)
430008:0 11110 0101000000, 43008.111 > 43008。那么,按理说43008.111的二进制应当表示为0 11110 0101000001。让我们一起计算0 11110 0101000001真实代表的数值是43040。43040大于43008。
我们借用代码来分析:
从代码中可以发现:在此区间,fp16有一个舍入范围,不再遵循大家熟悉的4舍5入。43040-43008=32,32/2=16,43008+16=43,024。≤43024为43008,>43024为43040。这里,我相信伙伴们可以看到舍入误差太大。
区间:[2^0,2^1)
1:0 01111 0000000000,1.111328125:0 01111 0001110000。伙伴们可以按照上述方法进行分析。
  • fp32:单精度浮点数据,字节数32bit,1bit符号位,8bit指数位,23bit尾数位。如下图(2):
notion image
按照fp16的方法,继续分析fp32。fp32的指数位增加到8位,那么可表示的区间也增加,如下:
fp32的尾数位也增加到23位,则每个区间被划分2^24份。在fp16的数据范围内,由于划分的更加细致,所以fp32的精度远优于fp16。但不得不提的一个通用问题:区间越靠后,精度越低。
分析:以43008和43008.111为例(代码形式):
从代码结果可以发现:在fp16的数据范围内,fp32的精度远高于fp16。
  • bf16:浮点数据,字节数16bit,1bit符号位,8bit指数位,7bit尾数位。如下图(3):
notion image
bf16表示的数据区间和fp32一样,但是尾数位减少了很多。这代表bf16的精度更差。同样是在fp16的数据范围内,bf16的精度最差。
接着,我们一起分析一下这几个精度在实际应用中会遇到哪些问题?
  • 在计算范围上,fp32和bf16可计算的数据范围最大。在同等范围内,fp32精度最高,fp16次之,bf16最差。在占用内存方面,fp16和bf16最小,fp32最大。
  • 训练模型时,选用什么精度进行模型权重、偏置和梯度保存和更新呢?
    • 如果选用fp16,虽然节省内存,但是在计算损失进行梯度回传时很容易发生上溢出。
    • 针对fp16上溢出的问题,那么选用fp32。虽然有效防治了上溢出问题,但是占用了很大的内存。
    • 针对上诉问题,采取折中方法:bf16。bf16有着fp32的数值范围,也有着fp16节省内存的特性,但是bf16的精度是最差的。在模型参数更新时(相加),bf16带来的误差往往会更大。
最后,针对每一种精度的问题,有没有一种方法能够有效的将各个精度的优点结合起来?那就是混合精度,将各个精度应用到模型计算的不同部分,便会起到一个意想不到的作用。那么下面让我们一起了解什么是混合精度!

混合精度

在模型训练中,混合精度顾名思义就是将各个精度结合起来使用,将它们的优点发挥更大。
由于目前大部分推理芯片都为fp16计算做了优化,所以使用fp16的执行性能比fp32的更快。针对这一点,模型的前向和后向传播首选的是fp16精度。
但是,结合上面分析,fp16存在数据溢出(数值超出该数据类型的表示范围)和舍入误差(fp16的分析代码)的问题。针对fp16的问题,fp32刚好可以解决。
那么,在混合精度训练过程中引入权重备份损失放大精度累加三种相关技术。
  • 权重备份(权重按照fp32精度存储)
模型训练过程中内存分为动态内存和静态内存。在训练过程中,模型的参数和梯度随训练发生变化,需要放在动态内存。同时,大部分推理芯片是对fp16计算做了优化,所以模型的参数和梯度采用fp16精度。
那么,这里的权重备份是针对哪里操作呢?
解答:这里的权重备份发生在优化器中。这是因为模型权重更新是通过优化器进行的。则模型训练过程中权重和梯度的精度变化如下图:
notion image
权重更新公式:
更新权重时,为什么需要使用fp36的权重?当gradient_16的值很小时,若与weight_16相加很容易发生舍入误差。这种情况可能会导致weight_16更新失败。
总结:权重备份解决了权重更新时的舍入误差问题。如果损失值特别小,那么梯度也会非常小,可能导致梯度下溢或者梯度消失。eg:在训练过程中,如果发现损失值为0,那么有可能已经发生这种情况了。如何解决这种情况便是接下来要讲解的:损失放大。
  • 损失放大
损失值太小,梯度容易下溢或者消失。我们可以通过梯度放大解决这种问题。也就是说在原有的损失基础上乘以一个放大因子。eg:scale=32,64,128…。
权重更新公式:
  • 精度累加
精度累加利用了fp16精度矩阵乘法的效率和fp32精度矩阵相加的低误差。简而言之:矩阵乘法用fp16精度,矩阵相加用fp32精度。这便是精度累加的具体含义。

总结

以上便是博主对精度和混合精度的简单介绍。在日常训练模型时,我们只需要知道fp16精度主要用于模型训练加速和节省内存,fp32精度主要用于缓解舍入误差。

🤗 实践代码

Pytorch已经封装好了混合精度训练的代码,我们直接调用即可。以下便是代码演示:
如果想直接git clone下代码,请点击下方链接操作:

📎 参考文章

 
💡
以上便是混合精度学习记录分享,欢迎您在底部评论区留言,一起交流~
上一篇
记录- QLoRA微调
下一篇
书籍-《许三观卖血记》

评论
Loading...