天津行業(yè)建站長(zhǎng)春網(wǎng)站優(yōu)化流程
混合精度訓(xùn)練原理之float16和float32數(shù)據(jù)之間的互相轉(zhuǎn)換
本篇文章參考:全網(wǎng)最全-混合精度訓(xùn)練原理
- 上述文章已經(jīng)講解的比較詳細(xì),本文只是從數(shù)值角度分析:
1. float32轉(zhuǎn)入float16的精度誤差
2. 在深度學(xué)習(xí)的混精度訓(xùn)練當(dāng)中,當(dāng)參數(shù)值(float32)獲取到計(jì)算得到的梯度值(float16)后的更新過(guò)程。
問(wèn)題一:float32轉(zhuǎn)入float16的精度誤差
首先,我們還是回顧一下float16和float32在計(jì)算機(jī)當(dāng)中的存儲(chǔ)格式:
有了上述內(nèi)容之后,我們通過(guò)一個(gè)實(shí)際的例子來(lái)觀察,float32到float16的舍入誤差:(我們使用python的numpy作為例子演示,當(dāng)用到具體框架如pytorch將其轉(zhuǎn)化為Tensor再進(jìn)行轉(zhuǎn)化是一樣的)
- 當(dāng)我們創(chuàng)建了一個(gè)十進(jìn)制數(shù)據(jù)[1.12156456132],并通過(guò)float32進(jìn)行存儲(chǔ)。
big_value32 = np.array([1.12156456132], np.float32)
print(f"big_value32:{big_value32[0]:.23f}")//result:
big_value32:1.12156450748443603515625
不考慮計(jì)算機(jī)存儲(chǔ),我們使用十進(jìn)制轉(zhuǎn)二進(jìn)制得到【1.12156456132 】的二進(jìn)制表示為【1.000111110001111011011010111001110011100011010100001…】,它是一個(gè)不能在有限精度內(nèi)存儲(chǔ)的數(shù)據(jù),根據(jù)上述float32的存儲(chǔ)空間表示,我們知道它只能保存23位有效數(shù)字(不包含首位的1),截取后,他在計(jì)算機(jī)中的表示為【1.00011111000111101101101】(截取時(shí)查看后一位,為1時(shí)進(jìn)一位,為0時(shí)不進(jìn)位),故其十進(jìn)制表示為【1.12156450748443603515625】(所以在存取數(shù)字時(shí)是有損失的),舍入為float16時(shí),計(jì)算的存儲(chǔ)表示為【1.0001111100】,表示為十進(jìn)制為 【1.12109375】 ,這個(gè)時(shí)候便產(chǎn)生了舍入誤差。
問(wèn)題二:在深度學(xué)習(xí)的混精度訓(xùn)練當(dāng)中,當(dāng)參數(shù)值(float32)獲取到計(jì)算得到的梯度值(float16)后的更新過(guò)程。
- 在混精度訓(xùn)練中,為什么一定要保存一份float32的權(quán)重副本用于和計(jì)算得出的float16數(shù)據(jù)做更新?因?yàn)槿绻胒loat16和float16做計(jì)算(加法),相對(duì)于float32,會(huì)造成精度的損失,甚至導(dǎo)致無(wú)法更新。
- 我們?nèi)匀慌e一個(gè)例子來(lái)說(shuō)明這個(gè)問(wèn)題,我們考慮一個(gè)十進(jìn)制的原始數(shù)據(jù)(權(quán)重),假設(shè)為【1.125】,利用float16二進(jìn)制存儲(chǔ)為【0 01111 0010000000】,float32二進(jìn)制存儲(chǔ)為【0 01111111 00100000000000000000000】。
- 同時(shí)考慮一個(gè)計(jì)算得出的float16梯度,假設(shè)為【0.12457275190625】,二進(jìn)制float16表示為【0 01011 1111111001】。
- 我們使用float16的原始權(quán)重去做更新:即【0 01111 0010000000】+【0 01011 1111111001】,由于他們的指數(shù)部分不相同,我們需要將指數(shù)較小的數(shù)據(jù)的小數(shù)點(diǎn)向左移,以保證他們的指數(shù)部分對(duì)齊,【0 01011 1111111001】將指數(shù)部分對(duì)齊【0 01111 0010000000】后,小數(shù)點(diǎn)需要向左移4位,在左邊補(bǔ)0,移位之后的結(jié)果為【0 01111 0001111111】。
- 將兩者的有效位部分相加,即【0010000000】(0)+【0001111111】(1),括號(hào)里面表示第11位有效位數(shù)值,用于進(jìn)位,根據(jù)二進(jìn)制加法,我們綜合符號(hào)位和指數(shù)為,得到最后結(jié)果為:【0 01111 0100000000】,轉(zhuǎn)換為十進(jìn)制為【1.250】。
- 跟上個(gè)例子一樣,但我們考慮使用float32去和新計(jì)算出來(lái)的float16數(shù)據(jù)做計(jì)算,在計(jì)算過(guò)程中,float16會(huì)被轉(zhuǎn)換為更高精度的float32參與計(jì)算。
- 二進(jìn)制float16【0 01011 1111111001】轉(zhuǎn)換為float32為【0 01111011 11111110010000000000000】(指數(shù)位+122,有效位數(shù)后面補(bǔ)0)。
- 進(jìn)行加法計(jì)算【0 01111111 00100000000000000000000】+【0 01111011 11111110010000000000000】,算得最后結(jié)果為【0 01111111 00111111111001000000000】,轉(zhuǎn)換為十進(jìn)制為【1.24957275390625】。從中我們可以看到精度的損失,這也是為什么要保存權(quán)重的高精度副本用于更新。
- 如果還有理解不到位的地方,可以配合這兩個(gè)工具食用:
- 在線IEEE浮點(diǎn)二進(jìn)制計(jì)算器
- 在線進(jìn)制轉(zhuǎn)換
附:1.更新失效例子(mindspore代碼):
import numpy as np
import mindspore as ms
from mindspore import Tensor
import structdef float_to_bin(num):return format(struct.unpack('!I', struct.pack('!f', num))[0], '032b')def float16_to_bin(num):float16 = np.float16(num)int_bits = np.frombuffer(float16.tobytes(), dtype=np.uint16)[0]bin_str = format(int_bits, '016b')return bin_strbig_value32 = np.array([1.125], np.float32)
big_value16 = big_value32.astype(np.float16) # 轉(zhuǎn)換為float16print(f"weight_float16:{big_value16[0]:.23f}")
print(f"weight_float32:{big_value32[0]:.23f}")
print(float16_to_bin(big_value16[0]))
print(float_to_bin(big_value32[0]))small_value_float16 = np.array([0.00041999], np.float16) print(f"grad_float16:{small_value_float16[0]:.23f}")
print(float16_to_bin(small_value_float16[0]))
print(float_to_bin(small_value_float16[0]))# 在MindSpore中進(jìn)行計(jì)算
small_tensor_float16 = Tensor(small_value_float16, ms.float16)
big_tensor16 = Tensor(big_value16, ms.float16)
big_tensor32 = Tensor(big_value32, ms.float32)# float32與float16相加
print(f"------epoch 0--------")
result1 = big_tensor32 + small_tensor_float16
print(f"float32 + float16: {result1.asnumpy()[0]:.23f}",result1.dtype)
result2 = big_tensor16 + small_tensor_float16
print(f"float16 + float16: {result2.asnumpy()[0]:.23f}",result2.dtype)
for i in range(100):print(f"------epoch {i+1}--------")result1 += small_tensor_float16print(f"float32 + float16: {result1.asnumpy()[0]:.23f}",result1.dtype)result2 += small_tensor_float16print(f"float16 + float16: {result2.asnumpy()[0]:.23f}",result2.dtype)print("float32 + float16:", result1.asnumpy(),result1.dtype)
print("float16 + float16:", result2.asnumpy(),result2.dtype)if not np.isclose(result1.asnumpy(), result2.asnumpy()):print("The results are different!")
else:print("The results are the same!")