【深度學習訓練小技巧】1080ti與2080ti區別、apex與梯度累加

當我們沒有足夠的顯卡訓練模型時,apex和梯度累加是有效的減少顯卡資源使用的手段。

1080ti與2080ti區別

1080ti與2080ti均爲11G顯存,在訓練同樣的模型時batch_size可以調到一樣大。它們的主要區別在於2080ti具有很強的半精度浮點數計算能力。1080ti和2080ti的計算能力如下:

gtx 1080ti:
半精度浮點數:0.17TFLOPS
單精度浮點數:11.34TFLOPS
雙精度浮點數:0.33TFLOPS
rtx 2080ti:
半精度浮點數:20.14TFLOPS
單精度浮點數:10.07TFLOPS
雙精度浮點數:0.31TFLOPS

半精度浮點數即FP16,單精度浮點數即FP32,雙精度浮點數即FP64。
在不使用apex的pytorch訓練過程中,一般默認均爲單精度浮點數,從上面的數據可以看到1080ti和2080ti的單精度浮點數運算能力差不多,因此不使用apex時用1080ti和2080ti訓練模型時間上差別很小。
在文章:【庖丁解牛】從零實現RetinaNet(六):RetinaNet的訓練與測試中,不使用apex時用2個2080ti訓練時一個epoch要2h38min,而使用apex時用1個2080ti訓練時一個epoch是2h31min,兩者時間幾乎一樣,但是卻少用了一張2080ti。這是因爲在pytorch訓練中使用apex時,此時大多數運算均爲半精度浮點數運算,而2080ti的半精度浮點數運算能力是其單精度浮點數運算能力的兩倍(1080ti幾乎沒有半精度浮點數運算能力)。這就爲什麼少用了一張2080ti但是一個epoch的訓練時間差不多的原因。對於1080ti,由於其幾乎沒有半精度浮點數運算能力,即使使用apex,最終實際也是按單精度浮點數來計算的,因此起不到訓練提速效果。

在目標檢測和分割任務中使用apex

使用apex能夠顯著的降低顯存佔用25-30%,通常情況下同樣顯卡可以把batch_size擴大接近一倍,非常實用。
類似於base model第七彈中所述,我們先安裝apex工具庫。如果你是Python3.7環境,使用下列命令安裝:

git clone https://github.com/NVIDIA/apex
cd apex
pip install -v --no-cache-dir --global-option="--cpp_ext" --global-option="--cuda_ext" ./

如果你是Python3.6環境,則使用下列命令安裝:

git clone https://github.com/NVIDIA/apex
cd apex
pip install -v --no-cache-dir ./

安裝完成後,在train.py中進行下列修改:

from apex import amp
......
......
# 定義好Model和optimizer後,增加下面這行代碼:
amp.register_float_function(torch, 'sigmoid')
amp.register_float_function(torch, 'softmax')
model, optimizer = amp.initialize(model, optimizer, opt_level='O1')
# 反傳梯度原本的代碼是loss.backward(),改爲下面兩行代碼:
with amp.scale_loss(loss, optimizer) as scaled_loss:
    scaled_loss.backward()

這裏和base model第七彈中的區別是增加了兩行amp.register_float_function函數。這個函數的作用是將模型所有sigmoid和softmax層強制使用FP32精度計算。因爲FP16的位數較少,能保存數據的上限和下限的絕對值也變小了,在檢測和分割問題中sigmoid和softmax層很容易在求和時導致數據溢出。在我的實驗中,不使用amp.register_float_function時幾乎每次都會在epoch5到8時loss變爲nan,因此這個操作是十分必要的。對於分類任務,一般不加amp.register_float_function也不會溢出。

梯度累加(一般不在目標檢測中使用)

當我們的顯卡資源不夠時,還可以考慮使用梯度累加。所謂梯度累加就是把一個大的batchsize分成若干次小batchsize計算梯度,每次梯度除以次數,然後進行梯度累加,這樣若干次後累加的梯度就近似爲大batchsize計算出來的梯度。需要說明的是,這種模擬是近似的,因爲BN層仍然按照實際的小batchsize更新。
我們一般可以在分類任務中使用梯度累加,因爲分類任務中即使劃分爲小batchsize時小batchsize也比較大(一般大於32),對BN層的影響有限。對於目標檢測任務,由於總的batchsize本身就比較小,使用梯度累加會導致BN層的誤差較大,所以我們一般不在目標檢測中使用梯度累加。
在train.py中,進行下列修改,就可以實現梯度累加:

inputs, labels = inputs.cuda(), labels.cuda()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss = loss / args.accumulation_steps
loss.backward()

if iter_index % args.accumulation_steps == 0:
	optimizer.step()
	optimizer.zero_grad()

其中,loss = loss / args.accumulation_steps根據累加次數把loss做除法,使得在不修改學習率的情況下最終累加的反傳梯度數量級與原來一致(由於我們在累加的過程中考慮了多次小batchsize產生的loss,但又除以了累加次數,所以在loss.backward()多次累加後就近似相當於大batchsize計算出的loss產生的梯度)。最後,我們經過累加的梯度次數後才使用optimizer.step()更新一次網絡參數,然後使用optimizer.zero_grad()將梯度清零。

torch.no_grad()

在測試時,我們不需要對所有變量計算梯度,只需要計算前向傳播即可。我們可以用with torch.no_grad():包裹不需要計算梯度的那部分代碼。雖然測試時因爲沒有loss.backward(),即使產生了梯度也不會更新變量,但使用後可以減少這部分計算梯度的計算量和梯度佔用的顯存。
要注意model.eval()和with torch.no_grad():是不同的,model.eval()只是把模型中所有BN層設置爲評估模式,這樣有前向計算時模型的BN層均值和標準差也不會更新。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章