?
最近經(jīng)常有同學(xué)反饋 GPU 利用率低,嚴(yán)重浪費(fèi) GPU 資源的問題,經(jīng)過對(duì)一些實(shí)例分析后,借著這篇文檔和大家分享一下解決方案,希望能對(duì)使用 GPU 的同學(xué)有些幫助。
一、GPU 利用率的定義
本文的 GPU 利用率主要指 GPU 在時(shí)間片上的利用率,即通過 nvidia-smi 顯示的 GPU-util 這個(gè)指標(biāo)。統(tǒng)計(jì)方式為:在采樣周期內(nèi),GPU 上面有 kernel 執(zhí)行的時(shí)間百分比。
二、GPU 利用率低的本質(zhì)
常見 GPU 任務(wù)運(yùn)行流程圖如下:
如上圖所示,GPU 任務(wù)會(huì)交替的使用 CPU 和 GPU 進(jìn)行計(jì)算,當(dāng) CPU 計(jì)算成為瓶頸時(shí),就會(huì)出現(xiàn) GPU 等待的問題,GPU 空跑那利用率就低了。那么優(yōu)化的方向就是縮短一切使用 CPU 計(jì)算環(huán)節(jié)的耗時(shí),減少 CPU 計(jì)算對(duì) GPU 的阻塞情況。常見的 CPU 計(jì)算操作如下:
數(shù)據(jù)加載
數(shù)據(jù)預(yù)處理
模型保存
loss 計(jì)算
評(píng)估指標(biāo)計(jì)算
日志打印
指標(biāo)上報(bào)
進(jìn)度上報(bào)
三、常見 GPU 利用率低原因分析
1、數(shù)據(jù)加載相關(guān)
1)存儲(chǔ)和計(jì)算跨城了,跨城加載數(shù)據(jù)太慢導(dǎo)致 GPU 利用率低
說明:例如數(shù)據(jù)存儲(chǔ)在“深圳 ceph”,但是 GPU 計(jì)算集群在“重慶”,那就涉及跨城使用了,影響很大。
優(yōu)化:要么遷移數(shù)據(jù),要么更換計(jì)算資源,確保存儲(chǔ)及計(jì)算是同城的。
2)存儲(chǔ)介質(zhì)性能太差
說明:不同存儲(chǔ)介質(zhì)讀寫性能比較:本機(jī) SSD > ceph > cfs-1.5 > hdfs > mdfs
優(yōu)化:將數(shù)據(jù)先同步到本機(jī) SSD,然后讀本機(jī) SSD 進(jìn)行訓(xùn)練。本機(jī) SSD 盤為“/dockerdata”,可先將其他介質(zhì)下的數(shù)據(jù)同步到此盤下進(jìn)行測試,排除存儲(chǔ)介質(zhì)的影響。
3)小文件太多,導(dǎo)致文件 io 耗時(shí)太長
說明:多個(gè)小文件不是連續(xù)的存儲(chǔ),讀取會(huì)浪費(fèi)很多時(shí)間在尋道上
優(yōu)化:將數(shù)據(jù)打包成一個(gè)大的文件,比如將許多圖片文件轉(zhuǎn)成一個(gè) hdf5/pth/lmdb/TFRecord 等大文件
lmdb 格式轉(zhuǎn)換樣例:
https://github.com/Lyken17/Efficient-PyTorch#data-loader
其他格式轉(zhuǎn)換方式請(qǐng)自行谷歌
4)未啟用多進(jìn)程并行讀取數(shù)據(jù)
說明:未設(shè)置 num_workers 等參數(shù)或者設(shè)置的不合理,導(dǎo)致 cpu 性能沒有跑起來,從而成為瓶頸,卡住 GPU
優(yōu)化:設(shè)置 torch.utils.data.DataLoader 方法的 num_workers 參數(shù)、tf.data.TFRecordDataset 方法的 num_parallel_reads 參數(shù)或者 tf.data.Dataset.map 的 num_parallel_calls 參數(shù)。
5)未啟用提前加載機(jī)制來實(shí)現(xiàn) CPU 和 GPU 的并行
說明:未設(shè)置 prefetch_factor 等參數(shù)或者設(shè)置的不合理,導(dǎo)致 CPU 與 GPU 在時(shí)間上串行,CPU 運(yùn)行時(shí) GPU 利用率直接掉 0
優(yōu)化:設(shè)置 torch.utils.data.DataLoader 方法的 prefetch_factor 參數(shù) 或者 tf.data.Dataset.prefetch()方法。prefetch_factor 表示每個(gè) worker 提前加載的 sample 數(shù)量 (使用該參數(shù)需升級(jí)到 pytorch1.7 及以上),Dataset.prefetch()方法的參數(shù) buffer_size 一般設(shè)置為:tf.data.experimental.AUTOTUNE,從而由 TensorFlow 自動(dòng)選擇合適的數(shù)值。
6)未設(shè)置共享內(nèi)存 pin_memory
說明:未設(shè)置 torch.utils.data.DataLoader 方法的 pin_memory 或者設(shè)置成 False,則數(shù)據(jù)需從 CPU 傳入到緩存 RAM 里面,再給傳輸?shù)?GPU 上
優(yōu)化:如果內(nèi)存比較富裕,可以設(shè)置 pin_memory=True,直接將數(shù)據(jù)映射到 GPU 的相關(guān)內(nèi)存塊上,省掉一點(diǎn)數(shù)據(jù)傳輸時(shí)間
2、數(shù)據(jù)預(yù)處理相關(guān)
1)數(shù)據(jù)預(yù)處理邏輯太復(fù)雜
說明:數(shù)據(jù)預(yù)處理部分超過一個(gè) for 循環(huán)的,都不應(yīng)該和 GPU 訓(xùn)練部分放到一起
優(yōu)化:a、設(shè)置 tf.data.Dataset.map 的 num_parallel_calls 參數(shù),提高并行度,一般設(shè)置為 tf.data.experimental.AUTOTUNE,可讓 TensorFlow 自動(dòng)選擇合適的數(shù)值。
b、將部分?jǐn)?shù)據(jù)預(yù)處理步驟挪出訓(xùn)練任務(wù),例如對(duì)圖片的歸一化等操作,提前開啟一個(gè) spark 分布式任務(wù)或者 cpu 任務(wù)處理好,再進(jìn)行訓(xùn)練。
c、提前將預(yù)處理部分需要用到的配置文件等信息加載到內(nèi)存中,不要每次計(jì)算的時(shí)候再去讀取。
d、關(guān)于查詢操作,多使用 dict 加速查詢操作;減少 for、while 循環(huán),降低預(yù)處理復(fù)雜度。
2)利用 GPU 進(jìn)行數(shù)據(jù)預(yù)處理 -- Nvidia DALI
說明:Nvidia DALI 是一個(gè)專門用于加速數(shù)據(jù)預(yù)處理過程的庫,既支持 GPU 又支持 CPU
優(yōu)化:采用 DALI,將基于 CPU 的數(shù)據(jù)預(yù)處理流程改造成用 GPU 來計(jì)算
DALI 文檔如下:https://zhuanlan.zhihu.com/p/105056158
3、模型保存相關(guān)
1)模型保存太頻繁
說明:模型保存為 CPU 操作,太頻繁容易導(dǎo)致 GPU 等待
優(yōu)化:減少保存模型(checkpoint)的頻率
4、指標(biāo)相關(guān)
1)loss 計(jì)算太復(fù)雜
說明:含有 for 循環(huán)的復(fù)雜 loss 計(jì)算,導(dǎo)致 CPU 計(jì)算時(shí)間太長從而阻塞 GPU
優(yōu)化:該用低復(fù)雜度的 loss 或者使用多進(jìn)程或多線程進(jìn)行加速
2)指標(biāo)上報(bào)太頻繁
說明:指標(biāo)上報(bào)操作太頻繁,CPU 和 GPU 頻繁切換導(dǎo)致 GPU 利用率低
優(yōu)化:改成抽樣上報(bào),例如每 100 個(gè) step 上報(bào)一次
5、日志相關(guān)
1)日志打印太頻繁
說明:日志打印操作太頻繁,CPU 和 GPU 頻繁切換導(dǎo)致 GPU 利用率低
優(yōu)化:改成抽樣打印,例如每 100 個(gè) step 打印一次
四、常見數(shù)據(jù)加載方法說明
1、pytorch 的 torch.utils.data.DataLoader
DataLoader(dataset, batch_size=1, shuffle=False, sampler=None,
batch_sampler=None, num_workers=0, collate_fn=None, pin_memory=False, drop_last=False, timeout=0, worker_init_fn=None, *, prefetch_factor=2, persistent_workers=False)
從參數(shù)定義中,我們可以看到 DataLoader 主要支持以下幾個(gè)功能:
支持加載 map-style 和 iterable-style 的 dataset,主要涉及到的參數(shù)是 dataset
自定義數(shù)據(jù)加載順序,主要涉及到的參數(shù)有 shuffle, sampler, batch_sampler, collate_fn
自動(dòng)把數(shù)據(jù)整理成 batch 序列,主要涉及到的參數(shù)有 batch_size, batch_sampler, collate_fn, drop_last
單進(jìn)程和多進(jìn)程的數(shù)據(jù)加載,主要涉及到的參數(shù)有 num_workers, worker_init_fn
自動(dòng)進(jìn)行鎖頁內(nèi)存讀取 (memory pinning),主要涉及到的參數(shù) pin_memory
支持?jǐn)?shù)據(jù)預(yù)加載,主要涉及的參數(shù) prefetch_factor
參考文檔:https://pytorch.org/docs/stable/data.html
2、tensorflow 的 tf.data.Dataset
ds_train = tf.data.Dataset.from_tensor_slices((x,y))
.shuffle(5000) .batch(batchs) .map(preprocess,num_parallel_calls=tf.data.experimental.AUTOTUNE) .prefetch(tf.data.experimental.AUTOTUNE)
Dataset.prefetch(): 可以讓數(shù)據(jù)集對(duì)象 Dataset 在 ? 訓(xùn)練時(shí)預(yù)取出若干個(gè)元素,使得在 GPU 訓(xùn)練的同時(shí) CPU 可以準(zhǔn)備數(shù)據(jù),提升訓(xùn)練流程的效率
Dataset.map(f): 轉(zhuǎn)換函數(shù) f 映射到數(shù)據(jù)集每一個(gè)元素; 可以利用多 CPU 資源,充分利用多核心的優(yōu)勢對(duì)數(shù)據(jù)進(jìn)行并行化變換, num_parallel_calls 設(shè)置為 tf.data.experimental.AUTOTUNE 以讓 TensorFlow 自動(dòng)選擇合適的數(shù)值,數(shù)據(jù)轉(zhuǎn)換過程多進(jìn)程執(zhí)行,設(shè)置 num_parallel_calls 參數(shù)能發(fā)揮 cpu 多核心的優(yōu)勢
Dataset.shuffle(buffer_size): 將數(shù)據(jù)集打亂,取出前 buffer_size 個(gè)元素放入,并從緩沖區(qū)中隨機(jī)采樣,采樣后的數(shù)據(jù)用后續(xù)數(shù)據(jù)替換
Dataset.batch(batch_size):將數(shù)據(jù)集分成批次,即對(duì)每 batch_size 個(gè)元素,使用 tf.stack() 在第 0 維合并,成為一個(gè)元素
參考文檔:https://www.tensorflow.org/api_docs/python/tf/data/Dataset#methods_2
五、分布式任務(wù)常見的 GPU 利用率低問題
分布式任務(wù)相比單機(jī)任務(wù)多了一個(gè)機(jī)器間通信環(huán)節(jié)。如果在單機(jī)上面運(yùn)行的好好的,擴(kuò)展到多機(jī)后出現(xiàn) GPU 利用率低,運(yùn)行速度慢等問題,大概率是機(jī)器間通信時(shí)間太長導(dǎo)致的。請(qǐng)排查以下幾點(diǎn):
1、機(jī)器節(jié)點(diǎn)是否處在同一 modules?
答:機(jī)器節(jié)點(diǎn)處于不同 modules 時(shí),多機(jī)間通信時(shí)間會(huì)長很多,deepspeed 組件已從平臺(tái)層面增加調(diào)度到同一 modules 的策略,用戶不需要操作;其他組件需聯(lián)系我們開啟。
2、多機(jī)時(shí)是否啟用 GDRDMA?
答:能否啟用 GDRDMA 和 NCCL 版本有關(guān),經(jīng)測試,使用 PyTorch1.7(自帶 NCCL2.7.8)時(shí),啟動(dòng) GDRDMA 失敗,和 Nvidia 的人溝通后確定是 NCCL 高版本的 bug,暫時(shí)使用的運(yùn)行注入的方式來修復(fù);使用 PyTorch1.6(自帶 NCCL2.4.8)時(shí),能夠啟用 GDRDMA。經(jīng)測試,“NCCL2.4.8 + 啟用 GDRDMA ” 比 “NCCL2.7.8 + 未啟用 GDRDMA”提升 4%。通過設(shè)置 export NCCL_DEBUG=INFO,查看日志中是否出現(xiàn)[receive] via NET/IB/0/GDRDMA 和 [send] via NET/IB/0/GDRDMA,出現(xiàn)則說明啟用 GDRDMA 成功,否則失敗。
3、pytorch 數(shù)據(jù)并行是否采用 DistributedDataParallel ?
答:PyTorch 里的數(shù)據(jù)并行訓(xùn)練,涉及 nn.DataParallel (DP) 和nn.parallel.DistributedDataParallel (DDP) ,我們推薦使用 nn.parallel.DistributedDataParallel (DDP)。
編輯:黃飛
評(píng)論
查看更多