AMP:Automatic mixed precision辜梳,自動(dòng)混合精度泡态,可以在神經(jīng)網(wǎng)絡(luò)推理過程中官疲,針對(duì)不同的層,采用不同的數(shù)據(jù)精度進(jìn)行計(jì)算亮隙,從而實(shí)現(xiàn)節(jié)省顯存和加快速度的目的途凫。
1、什么是自動(dòng)混合精度訓(xùn)練溢吻?
2维费、為什么需要自動(dòng)混合精度果元?
3、如何在PyTorch中使用自動(dòng)混合精度犀盟?
Pytorch 1.6版本以后而晒,Pytorch將amp的功能吸收入官方庫(kù),位于torch.cuda.amp模塊下阅畴。
【介紹】
torch.cuda.amp提供了對(duì)混合精度的支持倡怎。為實(shí)現(xiàn)自動(dòng)混合精度訓(xùn)練,需要結(jié)合使用如下兩個(gè)模塊:
torch.cuda.amp.autocast:autocast主要用作上下文管理器或者裝飾器贱枣,來(lái)確定使用混合精度的范圍监署。
torch.cuda.amp.GradScalar:GradScalar主要用來(lái)完成梯度縮放。
【張量】
1纽哥、它們是專門用于特定類型矩陣運(yùn)算的專用內(nèi)核钠乏。
可以將兩個(gè)FP16矩陣相乘并將其添加到FP16 / FP32矩陣中,從而得到FP16 / FP32矩陣春塌。Tensor內(nèi)核支持混合精度數(shù)學(xué)晓避,即輸入為半精度(FP16),輸出為全精度(FP32)只壳。上面的操作對(duì)于許多深度學(xué)習(xí)任務(wù)具有內(nèi)在的價(jià)值俏拱,并且Tensor內(nèi)核為該操作提供了專用的硬件。
現(xiàn)在吕世,使用FP16和FP32主要有兩個(gè)好處彰触。
FP16需要較少的內(nèi)存,因此更易于訓(xùn)練和部署大型神經(jīng)網(wǎng)絡(luò)命辖。它還減少了數(shù)據(jù)移動(dòng)况毅。
使用Tensor Core,數(shù)學(xué)運(yùn)算的運(yùn)行速度大大降低了精度尔艇。NVIDIA提供的Volta GPU的確切數(shù)量是:FP16中為125 TFlops尔许,而FP32中為15.7 TFlops(加速8倍)
但是也有缺點(diǎn)。從FP32轉(zhuǎn)到FP16時(shí)终娃,必然會(huì)降低精度味廊。
FP32與FP16:FP32具有八個(gè)指數(shù)位和23個(gè)小數(shù)位,而FP16具有五個(gè)指數(shù)位和十個(gè)小數(shù)位棠耕。
但是需要FP32嗎余佛?
FP16實(shí)際上可以很好地表示大多數(shù)權(quán)重和漸變。因此窍荧,擁有存儲(chǔ)和使用FP32所需的所有這些額外位只是浪費(fèi)辉巡。
【如何使用Tensor Core】
NVIDIA可以輕松地將Tensor內(nèi)核與自動(dòng)混合精度一起使用,并提供了幾行代碼蕊退。需要在代碼中做兩件事:
1.FP32所需的操作(如Softmax)被分配給FP32郊楣,而FP16可以完成的操作(如Conv)被自動(dòng)分配給FP16憔恳。
2.使用損耗定標(biāo)保留較小的梯度值。梯度值可能超出FP16的范圍净蚤。在這種情況下钥组,將對(duì)梯度值進(jìn)行縮放,使其落在FP16范圍內(nèi)今瀑。
pytorch從1.6版本開始程梦,已經(jīng)內(nèi)置了torch.cuda.amp,采用自動(dòng)混合精度訓(xùn)練就不需要加載第三方NVIDIA的apex庫(kù)了放椰。
【優(yōu)點(diǎn)】
? ?1.減少顯存占用作烟;
2.加快訓(xùn)練和推斷的計(jì)算砾医,能帶來(lái)多一倍速的體驗(yàn)拿撩;
3.張量核心的普及(NVIDIA Tensor Core),低精度計(jì)算是未來(lái)深度學(xué)習(xí)的一個(gè)重要趨勢(shì)如蚜。
但凡事都有兩面性压恒,F(xiàn)P16也帶來(lái)了些問題:1.溢出錯(cuò)誤;2.舍入誤差错邦;
1.溢出錯(cuò)誤:由于FP16的動(dòng)態(tài)范圍比FP32位的狹窄很多探赫,因此,在計(jì)算過程中很容易出現(xiàn)上溢出和下溢出撬呢,溢出之后就會(huì)出現(xiàn)"NaN"的問題伦吠。在深度學(xué)習(xí)中,由于激活函數(shù)的梯度往往要比權(quán)重梯度小魂拦,更易出現(xiàn)下溢出的情況毛仪。
2.舍入誤差
舍入誤差指的是當(dāng)梯度過小時(shí),小于當(dāng)前區(qū)間內(nèi)的最小間隔時(shí)芯勘,該次梯度更新可能會(huì)失斚溲ァ:
為了消除torch.HalfTensor也就是FP16的問題,需要使用以下兩種方法:
1)混合精度訓(xùn)練
在內(nèi)存中用FP16做儲(chǔ)存和乘法從而加速計(jì)算荷愕,而用FP32做累加避免舍入誤差衡怀。混合精度訓(xùn)練的策略有效地緩解了舍入誤差的問題安疗。
什么時(shí)候用torch.FloatTensor,什么時(shí)候用torch.HalfTensor呢抛杨?這是由pytorch框架決定的,在pytorch1.6的AMP上下文中荐类,以下操作中Tensor會(huì)被自動(dòng)轉(zhuǎn)化為半精度浮點(diǎn)型torch.HalfTensor:
__matmul__
addbmm
addmm
addmv
addr
baddbmm
bmm
chain_matmul
conv1d
conv2d
conv3d
conv_transpose1d
conv_transpose2d
conv_transpose3d
linear
matmul
mm
mv
prelu
2)損失放大(Loss scaling)
即使了混合精度訓(xùn)練蝶桶,還是存在無(wú)法收斂的情況,原因是激活梯度的值太小掉冶,造成了溢出真竖。可以通過使用torch.cuda.amp.GradScaler厌小,通過放大loss的值來(lái)防止梯度的underflow(只在BP時(shí)傳遞梯度信息使用恢共,真正更新權(quán)重時(shí)還是要把放大的梯度再unscale回去);
反向傳播前璧亚,將損失變化手動(dòng)增大2^k倍讨韭,因此反向傳播時(shí)得到的中間變量(激活函數(shù)梯度)則不會(huì)溢出;
反向傳播后癣蟋,將權(quán)重梯度縮小2^k倍透硝,恢復(fù)正常值。
【使用】
pytorch1.6及以上版本
有兩個(gè)接口:autocast和Gradscaler
1) autocast
導(dǎo)入pytorch中模塊torch.cuda.amp的類autocast
from torch.cuda.amp import autocast as autocast
model=Net().cuda()
optimizer=optim.SGD(model.parameters(),...)
for input,target in data:
optimizer.zero_grad()
with autocast():
output=model(input)
loss = loss_fn(output,target)
loss.backward()
optimizer.step()
可以使用autocast的context managers語(yǔ)義(如上)疯搅,也可以使用decorators語(yǔ)義濒生。當(dāng)進(jìn)入autocast上下文后,在這之后的cuda ops會(huì)把tensor的數(shù)據(jù)類型轉(zhuǎn)換為半精度浮點(diǎn)型幔欧,從而在不損失訓(xùn)練精度的情況下加快運(yùn)算罪治。而不需要手動(dòng)調(diào)用.half(),框架會(huì)自動(dòng)完成轉(zhuǎn)換。
不過礁蔗,autocast上下文只能包含網(wǎng)絡(luò)的前向過程(包括loss的計(jì)算)觉义,不能包含反向傳播,因?yàn)锽P的op會(huì)使用和前向op相同的類型浴井。
2)GradScaler
使用前晒骇,需要在訓(xùn)練最開始前實(shí)例化一個(gè)GradScaler對(duì)象,例程如下:
from torch.cuda.amp import autocast as autocast
model=Net().cuda()
optimizer=optim.SGD(model.parameters(),...)
scaler = GradScaler() #訓(xùn)練前實(shí)例化一個(gè)GradScaler對(duì)象
for epoch in epochs:
for input,target in data:
optimizer.zero_grad()
with autocast():』钦恪#前后開啟autocast
output=model(input)
loss = loss_fn(output,targt)
scaler.scale(loss).backward() #為了梯度放大
#scaler.step() 首先把梯度值unscale回來(lái)洪囤,如果梯度值不是inf或NaN,則調(diào)用optimizer.step()來(lái)更新權(quán)重,否則屠缭,忽略step調(diào)用箍鼓,從而保證權(quán)重不更新。
scaler.step(optimizer)
scaler.update() #準(zhǔn)備著呵曹,看是否要增大scaler款咖。
scaler的大小在每次迭代中動(dòng)態(tài)估計(jì),為了盡可能減少梯度underflow奄喂,scaler應(yīng)該更大铐殃;但太大,半精度浮點(diǎn)型又容易o(hù)verflow(變成inf或NaN).所以跨新,動(dòng)態(tài)估計(jì)原理就是在不出現(xiàn)if或NaN梯度的情況下富腊,盡可能的增大scaler值。在每次scaler.step(optimizer)中域帐,都會(huì)檢查是否有inf或NaN的梯度出現(xiàn):
? ? ? ?1.如果出現(xiàn)inf或NaN,scaler.step(optimizer)會(huì)忽略此次權(quán)重更新(optimizer.step())赘被,并將scaler的大小縮惺钦(乘上backoff_factor);
∶窦佟2.如果沒有出現(xiàn)inf或NaN,那么權(quán)重正常更新浮入,并且當(dāng)連續(xù)多次(growth_interval指定)沒有出現(xiàn)inf或NaN,則scaler.update()會(huì)將scaler的大小增加(乘上growth_factor)羊异。
對(duì)于分布式訓(xùn)練事秀,由于autocast是thread local的,要注意以下情形:
1)torch.nn.DataParallel:
以下代碼分布式是不生效的
model = MyModel()
dp_model = nn.DataParallel(model)
with autocast():
output=dp_model(input)
loss=loss_fn(output)
需使用autocast裝飾model的forward函數(shù)
2)torch.nn.DistributedDataParallel:
同樣野舶,對(duì)于多GPU,也需要autocast裝飾model的forward方法易迹,保證autocast在進(jìn)程內(nèi)部生效。
【注意】
1.判斷GPU是否支持FP16平道,支持Tensor core的GPU(2080Ti,Titan,Tesla等)睹欲,不支持的(Pascal系列)不建議;
2.常數(shù)范圍:為了保證計(jì)算不溢出巢掺,首先保證人工設(shè)定的常數(shù)不溢出句伶。如epsilon,INF等;
3.Dimension最好是8的倍數(shù):維度是8的倍數(shù)陆淀,性能最好考余;
4.涉及sum的操作要小心,容易溢出轧苫,softmax操作楚堤,建議用官方API,并定義成layer寫在模型初始化里含懊;
5.模型書寫要規(guī)范:自定義的Layer寫在模型初始化函數(shù)里身冬,graph計(jì)算寫在forward里;
6.一些不常用的函數(shù)岔乔,使用前要注冊(cè):amp.register_float_function(torch,'sogmoid')
7.某些函數(shù)不支持FP16加速酥筝,建議不要用;
8.需要操作梯度的模塊必須在optimizer的step里雏门,不然AMP不能判斷grad是否為NaN嘿歌。
torch.HalfTensor的優(yōu)勢(shì)就是存儲(chǔ)小、計(jì)算快茁影、更好的利用CUDA設(shè)備的Tensor Core宙帝。因此訓(xùn)練的時(shí)候可以減少顯存的占用(可以增加batchsize了),同時(shí)訓(xùn)練速度更快募闲;
torch.HalfTensor的劣勢(shì)就是:數(shù)值范圍胁脚А(更容易Overflow / Underflow)、舍入誤差(Rounding Error,導(dǎo)致一些微小的梯度信息達(dá)不到16bit精度的最低分辨率靴患,從而丟失)仍侥。
可見,當(dāng)有優(yōu)勢(shì)的時(shí)候就用torch.HalfTensor蚁廓,而為了消除torch.HalfTensor的劣勢(shì)访圃,我們帶來(lái)了兩種解決方案:
1,梯度scale相嵌,這正是上一小節(jié)中提到的torch.cuda.amp.GradScaler,通過放大loss的值來(lái)防止梯度的underflow(這只是BP的時(shí)候傳遞梯度信息使用况脆,真正更新權(quán)重的時(shí)候還是要把放大的梯度再unscale回去)饭宾;
2,回落到torch.FloatTensor格了,這就是混合一詞的由來(lái)看铆。那怎么知道什么時(shí)候用torch.FloatTensor,什么時(shí)候用半精度浮點(diǎn)型呢盛末?這是PyTorch框架決定的弹惦。
【Working with Multiple GPUs】
針對(duì)多卡訓(xùn)練的情況,只影響autocast的使用方法悄但,GradScaler的用法與之前一致棠隐。
.1 DataParallel in a single process
有效的調(diào)用方式如下所示:
【 DistributedDataParallel, multiple GPUs per process】
與DataParallel的使用相同,在模型構(gòu)建時(shí)檐嚣,對(duì)forward函數(shù)的定義方式進(jìn)行修改助泽,保證autocast在進(jìn)程內(nèi)部生效。
【參考文章:https://zhuanlan.zhihu.com/p/408610877】