基于pytorch的自動(dòng)混合精度(AMP)

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】

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末嚎京,一起剝皮案震驚了整個(gè)濱河市嗡贺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鞍帝,老刑警劉巖诫睬,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異帕涌,居然都是意外死亡摄凡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門宵膨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)架谎,“玉大人,你說我怎么就攤上這事辟躏」瓤郏” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)会涎。 經(jīng)常有香客問我裹匙,道長(zhǎng),這世上最難降的妖魔是什么末秃? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任概页,我火速辦了婚禮,結(jié)果婚禮上练慕,老公的妹妹穿的比我還像新娘惰匙。我一直安慰自己,他們只是感情好铃将,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布项鬼。 她就那樣靜靜地躺著,像睡著了一般劲阎。 火紅的嫁衣襯著肌膚如雪绘盟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天悯仙,我揣著相機(jī)與錄音龄毡,去河邊找鬼。 笑死锡垄,一個(gè)胖子當(dāng)著我的面吹牛沦零,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播偎捎,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蠢终,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了茴她?” 一聲冷哼從身側(cè)響起寻拂,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎丈牢,沒想到半個(gè)月后祭钉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡己沛,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年慌核,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片申尼。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡垮卓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出师幕,到底是詐尸還是另有隱情粟按,我是刑警寧澤诬滩,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站灭将,受9級(jí)特大地震影響疼鸟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜庙曙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一空镜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧捌朴,春花似錦吴攒、人聲如沸瑟匆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)督惰。三九已至察皇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間泽台,已是汗流浹背什荣。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留怀酷,地道東北人稻爬。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蜕依,于是被迫代替她去往敵國(guó)和親桅锄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容