Pytorch性能調優指南[模型性能調優系列1]
對于深度學習模型的性能調優,基本出發點有兩個:
- 提高計算速度和效率
- 減少IO,包括磁盤和內存數據的讀寫
以下是對 PyTorch 官方文檔《性能調優指南》(Performance Tuning Guide)的中文總結,涵蓋了提升訓練和推理效率的關鍵策略,在閱讀時可以參照以上兩點思考一下:
1. 通用優化策略
1.1. 異步數據加載與增強
torch.utils.data.DataLoader的num_workers參數默認為0,此時數據加載和訓練都是在主進程中同步執行的,效率比較低;通過設置num_workers > 0可以實現數據加載與訓練的并行處理。- 注意雖然由于python的GIL(Global Interpreter Lock)存在,其多線程并發并不是真正的并發,但并不會影響加載數據這類IO密集型任務:在等待某個worker的IO任務完成式會釋放GIL,使得其他worker繼續其數據加載任務
- 啟用
pin_memory=True可加快數據從主機到 GPU 的傳輸速度。其原理在于為GPU在內存中專門分配一塊區域用于存儲訓練數據,該內存區域不可被操作系統內核分頁,且是連續的。GPU側知道該內存區域的物理地址,可無需cpu介入通過DMA(Direct Memmory Access)直接訪問,實現了訓練+數據加載異步流式執行
1.2. 驗證和推理時禁用梯度計算
- 在執行驗證或推理任務時,使用
torch.no_grad()上下文管理器(也可以作為函數裝飾器),避免不必要的梯度計算,減少內存占用并加快執行速度。
1.3. 卷積后接 BatchNorm 時禁用偏置
nn.Conv2d層初始化時默認bias=True,后直接連接nn.BatchNorm2d時,可設置bias=False,因為 BatchNorm 會抵消偏置的影響。
1.4. 使用 param.grad = None 替代 zero_grad()
- pytorch會默認累加梯度,所以在每輪訓練任務迭代開始前需要將上輪迭代的梯度置零,以上兩種方式均可以達到目的。但通過將梯度設置為
None,可以減少內存操作,提高效率(對于參數量較大的大模型來說效果更好)。- 在下一輪迭代中,optimizer認為該tensor還沒有執行過bp,會復用內存直接assign一個新的梯度變量給它
- pytorch 1.7之后接口optimizer.zero_grad(set_to_none=True)默認為true,無需區分
1.5. 融合操作以減少內存訪問
- 在PyTorch的eager模式下,會為每個算子生成一個kernel并執行,如此每個算子都要執行:1)加載顯存數據到GPU;2)執行對應的操作;3)將結果寫回顯存。其中1和3往往是性能瓶頸,通過將多個逐元素操作(如加法、乘法、激活函數等)融合為單個內核,減少內存讀取和寫入次數(參考算子融合)。示例如下:
@torch.compile
def gelu(x):
return x * 0.5 * (1.0 + torch.erf(x / 1.41421))
1.6. 啟用 channels_last 內存格式
- 對于計算機視覺模型,使用
channels_last內存格式可提高內存訪問效率,尤其在使用混合精度訓練時效果顯著。
1.7. 中間結果檢查點(Checkpointing)
- 該策略通過只存儲部分tensor中間結果以緩解模型訓練時的內存壓力,通過
torch.utils.checkpoint,在反向傳播時重新計算部分前向傳播結果,適用于深層模型或大批量訓練。注意該策略對那些計算量小的,存儲量大的tensor尤為有效,如各種激活函數(ReLU, Sigmoid, Tanh), 上/下采樣等
1.8. 禁用調試 API
- 在常規訓練中,禁用如
torch.autograd.detect_anomaly等調試工具,以減少開銷。
2. CPU 優化策略
2.1. 利用非一致性內存訪問(NUMA)控制
在多插槽服務器上,使用 numactl 將進程綁定到特定的 CPU 節點,減少跨節點內存訪問延遲。
2.2. 配置 OpenMP 線程和親和性
通過設置環境變量(如 OMP_NUM_THREADS、GOMP_CPU_AFFINITY)優化線程使用和 CPU 親和性,提高并行計算性能。
2.3. 使用 Intel OpenMP 運行時庫(libiomp)
在 Intel 平臺上,使用 libiomp 替代默認的 GNU OpenMP,可能獲得更好的性能。
2.4. 切換內存分配器
使用 jemalloc 或 tcmalloc 替代默認的 malloc,提高內存分配和釋放的效率。
2.5. 結合 TorchScript 使用 oneDNN Graph 進行推理優化
通過 torch.jit.trace 和 torch.jit.freeze,利用 oneDNN Graph 進行操作融合,提升推理性能,特別適用于 Float32 和 BFloat16 數據類型。
2.6. 在 CPU 上使用分布式數據并行(DDP)訓練模型
對于小規模或內存受限的模型,使用 torch-ccl 和 DDP 在多核 CPU 上進行高效訓練。
3. GPU 優化策略
3.1. 啟用 Tensor Cores
在支持 Tensor Cores 的 GPU 上,使用混合精度訓練(AMP)和適當的張量尺寸(通常為 8 的倍數)以充分利用硬件加速。
3.2. 使用 CUDA Graphs
通過 torch.compile(model, mode="reduce-overhead"),減少 CPU 與 GPU 之間的上下文切換,提高執行效率。
3.3. 啟用 cuDNN 自動調優器
設置 torch.backends.cudnn.benchmark = True,讓 cuDNN 自動選擇最優的卷積算法,提高性能。
3.4. 避免不必要的 CPU-GPU 同步
盡量減少如 tensor.cpu()、tensor.item() 等操作,以避免阻塞 GPU 的執行。
3.5. 直接在目標設備上創建張量
使用 torch.rand(size, device='cuda') 直接在 GPU 上創建張量,避免額外的數據傳輸。
3.6. 使用混合精度訓練(AMP)
啟用 AMP,可在 Volta 及更新的 GPU 架構上獲得高達 3 倍的速度提升,同時減少內存使用。
3.7. 預分配內存以處理可變長度輸入
對于語音識別或 NLP 模型,預先分配最大序列長度的內存,避免訓練過程中頻繁的內存分配和釋放。
4. 分布式訓練優化
4.1. 使用高效的數據并行后端
推薦使用 torch.nn.parallel.DistributedDataParallel(DDP)替代 DataParallel,以獲得更好的性能和擴展性。
4.2. 在梯度累積時跳過不必要的 all-reduce 操作
在使用 DDP 進行梯度累積時,利用 no_sync() 上下文管理器,避免在每次反向傳播后進行 all-reduce 操作,僅在最后一次反向傳播后執行。
4.3. 確保構造函數和執行過程中層的順序一致
在使用 DistributedDataParallel(find_unused_parameters=True) 時,確保模型中層的定義順序與實際執行順序一致,以避免梯度同步問題。
參考鏈接
作者:beanmoon
出處:http://www.rzrgm.cn/beanmoon/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接。
該文章也同時發布在我的獨立博客中-豆月博客。

浙公網安備 33010602011771號