GPU 顯存不足怎麼辦?

2021-02-14 機器學習算法與自然語言處理

點擊上方「MLNLP」,選擇「星標」公眾號

重磅乾貨,第一時間送達

作者:老宋的茶書會

知乎專欄:NLP與深度學習

研究方向:自然語言處理

前言

最近跑的模型都比較大,尤其是Bert, 這真的是難為我 1080ti 了, 在Bert的Example中,官方提供了一些 Trick 來幫助我們加速訓練,很良心, 但感覺還不夠,於是花費一些時間整理出一個 Trick 集合,來幫助我們在顯存不足的時候來嘿嘿嘿。

本文分為兩大部分,第一部分引入一個主題:如何估計模型所需顯存, 第二個主題:GPU顯存不足時的各種 Trick 。

監控 GPU

監控GPU最常用的當然是 nvidia-smi ,但有一個工具能夠更好的展示信息:gpustat 。

nvidia-smi
watch --color -n1 gpustat -cpu # 動態事實監控GPU

推薦在配置文件中配置別名,反正我每次 gpu 一下,信息就全出來了,很方便。

下面有同學推薦nvtop, 我簡單試了試,的確挺好的,展現出現的信息很豐富 , 推薦試一試。

如何估計模型顯存 [1]

首先,思考一個問題:模型中的哪些東西佔據了我的顯存,咋就動不動就 out of memory?

其實一個模型所佔用的顯存主要包含兩部分: 模型自身的參數, 優化器參數, 模型每層的輸入輸出。

模型自身參數

模型自身的參數指的就是各個網絡層的 Weight 和Bias,這部分顯存在模型加載完成之後就會被佔用, 注意到的是,有些層是有參數的,如CNN, RNN;而有些層是無參數的, 如激活層, 池化層等。

從Pytorch 的角度來說,當你執行 model.to(device) 是, 你的模型就加載完畢,此時你的模型就已經加載完成了。

對於Pytorch來說,模型參數存儲在 model.parameters() 中,因此,我們不需要自己計算,完全可以通過Pytorh來直接列印:

print('Model {} : params: {:4f}M'.format(model._get_name(), para * type_size / 1000 / 1000))

優化器參數

優化器參數指的是模型在優化過程即反向傳播中所產生的參數, 這部分參數主要指的就是 dw, 即梯度,在SGD中, 其大小與參數一樣, 因此在優化期間, 模型的參數所佔用的顯存會翻倍。

值得注意的是,不同的優化器其所需保存的優化參數不同, 對於 Adam, 由於其還需要保存其餘參數, 模型的參數量會在優化區間翻 4 倍。

模型每層的輸入輸出

首先,第一點是輸入數據所佔用的顯存, 這部分所佔用的顯存其實並不大,這是因為我們往往採用迭代器的方式讀取數據,這意味著我們其實並不是一次性的將所有數據讀入顯存,而這保證每次輸入所佔用的顯存與整個網絡參數來比是微不足道的。

然後,在模型進行前向傳播與反向傳播時, 一個很重要的事情就是計算並保存每一層的輸出以及其對應的梯度, 這意味著,這也佔據了很大一部分顯存。

最後, 模型輸出的顯存佔用可以總結為:

那麼有沒有辦法通過Pytorch來計算這部分參數量呢?答案是有的,我們可以假設一個batch的樣本,然後通過 model.modules() 來對每一層進行遍歷,獲得每一層的輸出shape, 然後就能夠獲得一個batch的數據的輸出參數量。[2]

所有的顯存佔用計算

顯存佔用 = 模型自身參數 × n + batch size × 輸出參數量 × 2 + 一個batch的輸入數據(往往忽略)

其中,n是根據優化算法來定的,如果選用SGD, 則 n = 2, 如果選擇Adam, 則 n = 4.

一個很棒的實現如下, 我懶得再重新寫了,你可以根據這個改一改,問題不大。

# 模型顯存佔用監測函數
# model:輸入的模型
# input:實際中需要輸入的Tensor變量
# type_size 默認為 4 默認類型為 float32

def modelsize(model, input, type_size=4):
para = sum([np.prod(list(p.size())) for p in model.parameters()])
print('Model {} : params: {:4f}M'.format(model._get_name(), para * type_size / 1000 / 1000))

input_ = input.clone()
input_.requires_grad_(requires_grad=False)

mods = list(model.modules())
out_sizes = []

for i in range(1, len(mods)):
m = mods[i]
if isinstance(m, nn.ReLU):
if m.inplace:
continue
out = m(input_)
out_sizes.append(np.array(out.size()))
input_ = out

total_nums = 0
for i in range(len(out_sizes)):
s = out_sizes[i]
nums = np.prod(np.array(s))
total_nums += nums


print('Model {} : intermedite variables: {:3f} M (without backward)'
.format(model._get_name(), total_nums * type_size / 1000 / 1000))
print('Model {} : intermedite variables: {:3f} M (with backward)'
.format(model._get_name(), total_nums * type_size*2 / 1000 / 1000))

GPU 顯存不足時的Trick [2]

此處不討論多GPU, 分布式計算等情況,只討論一些常規的 Trick, 會不定時進行更新。

降低batch size

這應該很好理解,適當降低batch size, 則模型每層的輸入輸出就會成線性減少, 效果相當明顯。這裡需要注意的一點是, dev batch size 的調整也有助於降低顯存, 同時,不要將 dev 或 test 的batch size 設置為樣本集長度, 我最近就幹了這個傻事,害的我調試了一天才調出來是這個問題。

選擇更小的數據類型

一般默認情況下, 整個網絡中採用的是32位的浮點數,如果切換到 16位的浮點數,其顯存佔用量將接近呈倍數遞減。

精簡模型

在設計模型時,適當的精簡模型,如原來兩層的LSTM轉為一層;原來使用LSTM, 現在使用GRU;減少卷積核數量;儘量少的使用 Linear 等。

數據角度

對於文本數據來說,長序列所帶來的參數量是呈線性增加的, 適當的縮小序列長度可以極大的降低參數量。

total_loss

考慮到 loss 本身是一個包含梯度信息的 tensor, 因此,正確的求損失和的方式為:

total_loss += loss.item()

釋放不需要的張量和變量

採用del釋放你不再需要的張量和變量,這也要求我們在寫模型的時候注意變量的使用,不要隨心所欲,漫天飛舞。

Relu 的 inplace 參數

激活函數 Relu() 有一個默認參數 inplace ,默認為Flase, 當設置為True的時候,我們在通過relu() 計算得到的新值不會佔用新的空間而是直接覆蓋原來的值,這表示設為True, 可以節省一部分顯存。

梯度累積

首先, 要了解一些Pytorch的基本知識:

我們知道, batch size 與佔用顯存息息相關,但有時候我們的batch size 又不能設置的太小,這咋辦呢?答案就是梯度累加。

我們先來看看傳統訓練:

for i,(feature,target) in enumerate(train_loader):
outputs = model(feature) # 前向傳播
loss = criterion(outputs,target) # 計算損失

optimizer.zero_grad() # 清空梯度
loss.backward() # 計算梯度
optimizer.step() # 反向傳播, 更新網絡參數

而加入梯度累加之後,代碼是這樣的:

for i,(features,target) in enumerate(train_loader):
outputs = model(images) # 前向傳播
loss = criterion(outputs,target) # 計算損失
loss = loss/accumulation_steps # 可選,如果損失要在訓練樣本上取平均

loss.backward() # 計算梯度
if((i+1)%accumulation_steps)==0:
optimizer.step() # 反向傳播,更新網絡參數
optimizer.zero_grad() # 清空梯度

其實,這塊有兩種理解方式(受到評論區同學啟發), 我談談在 bert 裡面最常見的那種。

比較來看, 我們發現,梯度累加本質上就是累加 accumulation_steps 個 batchsize/accumulationsteps 的梯度, 再根據累加的梯度來更新網絡參數,以達到真實梯度類似batch_size 的效果。在使用時,需要注意適當的擴大學習率。

更詳細來說, 我們假設 batch size = 4 , accumulation steps = 8 , 梯度積累首先在前向傳播的時候以 batch_size=4 來計算梯度,但是不更新參數,將梯度積累下來,直到我們計算了 accumulation steps 個 batch, 我們再更新參數。其實本質上就等價於:

真正的 batch_size = batch_size * accumulation_steps

梯度積累能很大程度上緩解GPU顯存不足的問題,推薦使用。

在Bert的倉庫中,就使用了這個Trick,十分實用,簡直是我們這種乞丐實驗室的良心Trick。

梯度檢查點

這個Trick我沒用過,畢竟模型還沒有那麼那麼大。

等我用過再更新吧,先把坑挖下。

最後

哎, 如果你看完了這篇文章,就說明了一件事情: 小夥子,你卡也不夠啊。哎, 乞丐實驗室不配深度學習,哭了。

Reference

[1]科普帖:深度學習中GPU和顯存分析

[2]如何在Pytorch中精細化利用顯存

[3]GPU捉襟見肘還想訓練大批量模型?誰說不可以

[5]PyTorch中在反向傳播前為什麼要手動將梯度清零?

[6]From zero to research — An introduction to Meta-learning

[7] Training Neural Nets on Larger Batches: Practical Tips for 1-GPU, Multi-GPU & Distributed setups

https://zhuanlan.zhihu.com/p/65002487

推薦閱讀:

就最近看的paper談談預訓練語言模型發展

如何評價Word2Vec作者提出的fastText算法?深度學習是否在文本分類等簡單任務上沒有優勢?

從Word2Vec到Bert,聊聊詞向量的前世今生(一)

相關焦點

  • 【經驗分享】GPU 顯存不足怎麼辦?
    作者:老宋的茶書會知乎專欄:NLP與深度學習研究方向:自然語言處理來自:AINLP前言最近跑的模型都比較大,尤其是Bert, 這真的是難為我 1080ti 了, 在Bert的Example中,官方提供了一些 Trick 來幫助我們加速訓練,很良心, 但感覺還不夠,於是花費一些時間整理出一個 Trick 集合,來幫助我們在顯存不足的時候來嘿嘿嘿
  • 深度學習模型訓練時如何優化GPU顯存?(附TF和Paddle優化方式)
    a)按固定比例設置譬如我們設置分配給GPU的顯存大小是0.7,可以按如下方式設置:gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=0.7)sess = tf.Session
  • 顯存不足?PyTorch 顯存使用分析與優化
    相信大家在日常模型訓練過程中,或多或少的總會遇見:torch.FatalError: cuda runtime error (2) : out of memory 那麼, 可憐的顯存到底在哪裡消耗掉了?顯存去哪了?
  • 深度學習中如何更好地利用顯存資源?
    例如,若想限定某個任務的最大顯存佔用量不超過2048MB,代碼如下:export FLAGS_gpu_memory_limit_mb=2048環境變量FLAGS_gpu_memory_limit_mb表示限定每個任務的最大顯存佔用量,為uint64類型的整數,單位為MB。
  • 2020年搞深度學習需要什麼樣的GPU:請上48G顯存
    眾所周知,當今業界性能最強(SOTA)的深度學習模型都會佔用巨大的顯存空間,很多過去性能算得上強勁的 GPU,現在可能稍顯內存不足。在 lambda 最新的一篇顯卡橫向測評文章中,開發者們探討了哪些 GPU 可以再不出現內存錯誤的情況下訓練模型。當然,還有這些 GPU 的 AI 性能。
  • Pytorch多GPU訓練
    所以這裡提供一個解決顯存使用不平衡問題的方案:首先這次的解決方案來自transformer-XL的官方代碼: https://github.com/kimiyoung/transformer-xl然後我將其中的平衡GPU顯存的代碼提取了出來(原代碼好像有點小問題)放到了github上面:https://github.com/Link-Li/Balanced-DataParallel這裡的代碼是原作者繼承了
  • pytorch多gpu並行訓練
    1.2.如何平衡DataParallel帶來的顯存使用不平衡的問題1.3.torch.nn.parallel.DistributedDataParallel2.多機多gpu訓練2.1.初始化2.1.1.初始化backend2.1.2.初始化init_method2.1.2.1.使用TCP初始化2.1.2.2
  • 顯存不夠?這個方法讓你的GPU聯手CPU
    然而,由於伺服器的顯存非常有限,隨著訓練樣本越來越大,顯存連一個樣本都容不下的現象頻頻發生。除了升級硬體(燒錢)、使用分布式訓練(費力),你知道還有哪些方法嗎?即使顯存充足,所有運算都在 GPU 上執行就是最高效嗎?只要掌握以下小知識,模型訓練的種種問題統統搞定,省時省力省錢,重點是高效!
  • C++版本demo7發布 全民GPU加速(包括win7)
    更新日誌增加右鍵菜單(只是右鍵菜單,編輯圖標和設置尚未完成點擊無效)新增gpu全局渲染包括win7系統,默認開啟無需設置,設定gpu渲染圖標上限40個,如果dock圖標超過40個之後的圖標將會使用cpu渲染節省顯存,GPU渲染使用DX11如果運行大型DX11遊戲有可能會對dock的DX11設備衝突造成設備丟失,所以使用GPU渲染的時候內存仍然會備份一個圖標來防止DXX11設備丟失時用來重新創建圖標(此過程尚未測試);新的gpu渲染仍會佔用一點
  • 深度解析MegEngine亞線性顯存優化技術
    該技術已在 MegEngine 開源,歡迎大家上手使用:https://github.com/MegEngine 深度神經網絡訓練是一件複雜的事情,它體現為模型的時間複雜度和空間複雜度,分別對應著計算和內存;而訓練時內存佔用問題是漂浮在深度學習社區上空的一塊烏雲,如何撥雲見日,最大降低神經網絡訓練的內存佔用,是一個繞不開的課題。
  • Win7電腦提示虛擬內存不足怎麼辦?Win7電腦虛擬內存不足解決方法
    我們在使用電腦的過程中,難免會遇到大大小小的問題,比如打開某個軟體或者遊戲之後,出現了電腦虛擬內存不足的問題,那麼Win7電腦提示虛擬內存不足怎麼辦?下面裝機之家分享一下Win7電腦虛擬內存不足解決方法,具體解決方法如下,來看看吧!
  • 首個Titan RTX深度學習評測結果出爐:2019年你該選擇哪款GPU?
    看起來,如果用來做深度學習訓練的話,目前性價比最高的是 RTX 2080Ti 顯卡(除非你必須要 11G 以上的顯存)。第一步:克隆基準測試的 Repogit clone https://github.com/lambdal/lambda-tensorflow-benchmark.git --recursive第二步:運行基準測試輸入正確的 gpu_index (default
  • 顯存不夠用?這個算法讓你的GPU老樹開新花
    Float16當前區間內能表示的最小間隔的時候,更新也會失敗(哭瞎┭┮﹏┭┮我怎麼這麼難鴨)              所以怎麼辦呢 總結陳詞混合精度訓練做到了在前向和後向計算過程中均使用半精度浮點數,並且沒有像之前的一些工作一樣還引入額外超參,而且重要的是,實現非常簡單卻能帶來非常顯著的收益,在顯存half以及速度double的情況下保持模型的精度,簡直不能再厲害啦
  • 深度學習GPU訓練神仙平臺,種草不虧!
    01  創建實例+搭建環境,一鍵快速搞定首先,註冊登錄gpushare.com,直奔新人禮包準沒錯,目前恆源雲的 新人福利有100元禮券 ,代金券可以直接下單,如果跑4.5正式創建實例前,選擇適合的GPU非常重要,如果模型耗顯存,可以考慮16G的5000、24G的3090、32G的V100,如果耗內存,也有單卡分配了64G CPU的機型。確定了GPU的類型、數量後,就可以搭建訓練環境了,官方鏡像裡,主流框架TensorFlow、PyTorch、MXNet、PaddlePaddle等都預裝了,可以直接勾選需要的版本。
  • 深度學習訓練時GPU溫度過高?幾個命令,為你的GPU迅速降溫
    這裡提供詳細步驟:1.克隆這個github倉庫到本地目錄/opt https://github.com/boris-dimitrov/set_gpu_fans_publiccd /optgit clone https://github.com/boris-dimitrov/set_gpu_fans_public
  • TensorFlow_GPU On OpenShift部署實踐
    = tf.layers.conv2d(random_image_cpu, 32, 7)  net_cpu = tf.reduce_sum(net_cpu)with tf.device('/gpu:0'):  random_image_gpu = tf.random_normal((100, 100, 100, 3))  net_gpu = tf.layers.conv2d
  • arnold GPU入門
    Arnold插件(如MtoA)具有Pre-Populate GPU Cache菜單命令,kick具有-gpu_warm標誌。請注意,預先填充緩存最多可能需要15分鐘。只需在安裝新的Arnold版本,更新到新的NVIDIA驅動程序或更改系統上GPU的硬體配置後重新填充緩存。選擇渲染設備只需單擊一下,即可輕鬆切換CPU和GPU。
  • 使用conda安裝tensorflow-gpu | ubuntu系統
    本篇文章以使用 conda 安裝 tensorflow-gpu==1.13.1為例,步驟如下:1.使用 conda 命令進行安裝:conda install tensorflow-gpu==1.13.1大概率會出現類似下列的信息:不過沒關係,是在我們意料之中的,剛才只是試探一下...