飛槳(PaddlePaddle)為用戶提供技術領先、簡單易用、兼顧顯存回收與復用的顯存優化策略,在Transformer、BERT、DeepLab V3+上Max Batch Size性能優於對標開源框架,在YOLOv3、Mask-RCNN模型上顯存性能與對標開源框架持平,有興趣的同學可以試一下,上一組數據先睹為快。
測試條件 如下:
-
Paddle version:1.5.0
-
Tensorflow version:1.12.0, 1.14.0
-
Pytorch version:1.0.1, 1.1.0
-
GPU:Tesla V100-SXM2
-
CPU:Intel(R) Xeon(R) Gold 6148 CPU @ 2.40GHz,38核
-
Nvida driver: 418.39
-
NCCL VERSION:2.4.2
-
CUDNN VERSION:7.4.2.24,7.5.0.56
-
CUDA VERSION:9.0.176,單卡模式
1. 飛槳的顯存分配策略
由於原生的CUDA系統調用 cudaMalloc 和 cudaFree 均是同步操作,非常耗時。為了加速顯存分配,飛槳采用了顯存預分配的策略,具體方式如下圖所示:
(1)在分配requested_size大小的顯存時,先定義一個顯存池的大小,記為chunk_size,chunk_size由環境變量 FLAGS_fraction_of_gpu_memory_to_use 確定,表征chunk_size在全部顯存的占比,默認值為0.92,即框架預先分配顯卡92%的顯存。
-
若requested_size <= chunk_size,則框架會預先分配chunk_size大小的顯存池chunk,並從chunk中分出requested_size大小的塊返回。之后每次申請顯存都會從chunk中分配。
-
若requested_size > chunk_size,則框架會直接調用 cudaMalloc 分配requested_size大小的顯存返回。
(2)在釋放free_size大小的顯存時,
-
若free_size <= chunk_size,則框架會將該顯存放回預分配的chunk中,而不是直接返回給CUDA。
-
若free_size > chunk_size,則框架會直接調用 cudaFree 將顯存返回給CUDA。
注:若GPU卡上有其他任務占用顯存,可以適當調整chunk的占比,保證框架能預分配到合適的chunk,比如可以分配40%的顯存可以這樣設置:
export FLAGS_fraction_of_gpu_memory_to_use=0.4 # 預先40%的GPU顯存
提醒 :chunk占比應該盡可能大,只有在想測量網絡的實際顯存占用量時,可以設置該占比為0,觀察nvidia-smi顯示的顯存占用情況。
2. 飛槳的顯存優化策略
除了顯存預分配,飛槳還提供了多種通用顯存優化方法,使得同樣網絡模型及配置下的顯存占用盡可能小,從而可以支持更大batch size的訓練,來提升訓練效率,下面介紹最重要的兩種方法,分別是GC(Garbage Collection)策略和Inplace策略
2.1. GC策略: 顯存垃圾及時回收
GC(Garbage Collection)的原理是在網絡運行階段及時釋放無用變量的顯存空間,達到節省顯存的目的。GC可生效於使用Executor,ParallelExecutor做模型訓練/預測時。
GC策略由三個環境變量控制:
(1) FLAGS_eager_delete_tensor_gb
GC策略的使能開關,double類型,默認值為-1。GC策略會積攢一定大小的顯存垃圾后再統一釋放,FLAGS_eager_delete_tensor_gb 控制的是顯存垃圾的閾值,單位是GB。建議用戶設置 FLAGS_eager_delete_tensor_gb=0 。
-
若 FLAGS_eager_delete_tensor_gb=0 ,則一旦有顯存垃圾則馬上回收,最為節省顯存。
-
若 FLAGS_eager_delete_tensor_gb=1 ,則顯存垃圾積攢到1G后才觸發回收。
-
若 FLAGS_eager_delete_tensor_gb<0 ,則GC策略關閉。
(2) FLAGS_memory_fraction_of_eager_deletion
GC策略的調節flag,double類型,默認值為1,范圍為[0,1],僅適用於使用ParallelExecutor或CompiledProgram+with_data_parallel的場合。GC內部會根據變量占用的顯存大小,對變量進行降序排列,且僅回收前 FLAGS_memory_fraction_of_eager_deletion 大的變量顯存。建議用戶維持默認值,即:FLAGS_memory_fraction_of_eager_deletion=1 。
-
若 FLAGS_memory_fraction_of_eager_deletion=0.6 ,則表示僅回收顯存占用60%大的變量顯存。
-
若 FLAGS_memory_fraction_of_eager_deletion=0 ,則表示不回收任何變量的顯存,GC策略關閉。
-
若 FLAGS_memory_fraction_of_eager_deletion=1 ,則表示回收所有變量的顯存。
(3) FLAGS_fast_eager_deletion_mode
快速GC策略的開關,bool類型,默認值為True,表示使用快速GC策略。快速GC策略會不等待CUDA Kernel結束直接釋放顯存。建議用戶維持默認值,即 FLAGS_fast_eager_deletion_mode=True 。
2.2. Inplace策略: Op內部的輸出復用輸入
Inplace策略的原理是Op的輸出復用Op輸入的顯存空間。例如,reshape操作的輸出和輸入可復用同一片顯存空間。
Inplace策略可生效於使用ParallelExecutor或CompiledProgram加with_data_parallel做模型訓練和預測,通過 BuildStrategy 設置。
具體方式為:
build_strategy = fluid.BuildStrategy()
build_strategy.enable_inplace = True # 開啟Inplace策略compiled_program = fluid.CompiledProgram(train_program).with_data_parallel(loss_name=loss.name, build_strategy=build_strategy)
由於目前設計上的一些問題,打開inplace策略后必須保證后續要fetch的變量為var.persistable = True,這是因為當Inplace策略打開時,非persistable的變量的顯存空間可能被其他變量復用,導致fetch結果出錯,設置persistable的目的是防止fetch的變量被復用,保證輸出結果的正確性。
即:假如你后續需要fetch的變量為loss和acc,則必須設置:
loss.persistable = True
acc.persistable = True
我們正在積極修復該問題,並在下一個release版本中進行修復,並默認打開Inplace策略。
3. 顯存優化最佳實踐(Best Practice)
我們推薦你的最佳顯存優化策略為:
(1) 設置預分配顯存池 :
FLAGS_fraction_of_gpu_memory_to_use=0.92
(2) 開啟GC策略,設置 :
FLAGS_eager_delete_tensor_gb=0 。
FLAGS_memory_fraction_of_eager_deletion=1FLAGS_fast_eager_deletion_mode=True
(3) 開啟Inplace策略,設置 :
build_strategy.enable_inplace = True ,
fetch_list中的loss.persistable = Trueacc.persistable = True
4.優化策略效果實測
前面我們了解到,GC策略為飛槳的主要顯存優化策略,Inplace是一個輔助策略,可在GC策略基礎上進一步降低模型顯存占用,有利於進一步提高最大batch size。一般而言,使用GC策略能滿足您絕大部分模型的顯存優化需求;若您仍想進一步提高batch size,建議您打開Inplace策略作為補充。
我們以Transformer模型為例了解一下優化策略的實際效果:
4.1. Transformer模型原理介紹
Transformer 是論文《Attention Is All You Need》中提出的用以完成機器翻譯(machine translation, MT)等序列到序列(sequence to sequence, Seq2Seq)學習任務的一種全新網絡結構。其同樣使用了 Seq2Seq 任務中典型的編碼器-解碼器(Encoder-Decoder)的框架結構,但相較於此前廣泛使用的循環神經網絡(Recurrent Neural Network, RNN),其完全使用注意力(Attention)機制來實現序列到序列的建模,整體網絡結構如圖1所示。
△ 圖 1. Transformer 網絡結構圖
Encoder 由若干相同的 layer 堆疊組成,每個 layer 主要由多頭注意力(Multi-Head Attention)和全連接的前饋(Feed-Forward)網絡這兩個 sub-layer 構成。
Multi-Head Attention 在這里用於實現 Self-Attention,相比於簡單的 Attention 機制,其將輸入進行多路線性變換后分別計算 Attention 的結果,並將所有結果拼接后再次進行線性變換作為輸出。參見圖2,其中 Attention 使用的是點積(Dot-Product),並在點積后進行了 scale 的處理以避免因點積結果過大進入 softmax 的飽和區域。
Feed-Forward 網絡會對序列中的每個位置進行相同的計算(Position-wise),其采用的是兩次線性變換中間加以 ReLU 激活的結構。
此外,每個 sub-layer 后還施以 Residual Connection 和 Layer Normalization 來促進梯度傳播和模型收斂。
△ 圖 2. Multi-Head Attention
Decoder 具有和 Encoder 類似的結構,只是相比於組成 Encoder 的 layer ,在組成 Decoder 的 layer 中還多了一個 Multi-Head Attention 的 sub-layer 來實現對 Encoder 輸出的 Attention,這個 Encoder-Decoder Attention 在其他 Seq2Seq 模型中也是存在的。
4.2. 顯存優化策略在Transformer模型下的實測效果
按照模型建立項目,實際測試一下顯存的優化效果,打開Inplace前后,顯存占用和最大batch size的變化如下表所示:
可以看到,打開GC策略對於顯存優化的非常重要;雖然Inplace策略在這個實例中對顯存峰值沒有提升,但可以顯著提升最大batch size,也具有實際意義。