PyTorch系列 | 如何加快你的模型训练速度呢?
圖片來源:Pixabay,作者:talha khalil
2019 年第 67 篇文章,總第 91 篇文章
本文大約?6500?字,建議收藏閱讀!
原題?| Speed Up your Algorithms Part 1 — PyTorch
作者?| Puneet Grover
譯者?| kbsc13("算法猿的成長"公眾號作者)
原文?| https://towardsdatascience.com/speed-up-your-algorithms-part-1-pytorch-56d8a4ae7051
聲明?| 翻譯是出于交流學習的目的,歡迎轉載,但請保留本文出于,請勿用作商業或者非法用途
前言
本文將主要介紹如何采用?cuda?和?pycuda?檢查、初始化 GPU 設備,并讓你的算法跑得更快。
PyTorch?是?torch?的 python 版本,它是 Facebook AI 研究組開發并開源的一個深度學習框架,也是目前非常流行的框架,特別是在研究人員中,短短幾年已經有追上?Tensorflow?的趨勢了。這主要是因為其簡單、動態計算圖的優點。
pycuda?是一個?python?第三方庫,用于處理 Nvidia 的 CUDA 并行計算 API 。
本文目錄如下:
如何檢查?cuda?是否可用?
如何獲取更多?cuda?設備的信息?
在 GPU 上存儲 Tensors 和運行模型的方法
有多個 GPU 的時候,如何選擇和使用它們
數據并行
數據并行的比較
torch.multiprocessing
本文的代碼是用 Jupyter notebook,Github 地址為:
https://nbviewer.jupyter.org/github/PuneetGrov3r/MediumPosts/blob/master/SpeedUpYourAlgorithms/1%29%20PyTorch.ipynb
1. 如何檢查 cuda 是否可用?
檢查?cuda?是否可用的代碼非常簡單,如下所示:
import?torch torch.cuda.is_available() #?True2. 如何獲取更多 cuda 設備的信息?
獲取基本的設備信息,采用?torch.cuda?即可,但如果想得到更詳細的信息,需要采用?pycuda?。
實現的代碼如下所示:
import?torch import?pycuda.driver?as?cuda cuda.init() ##?Get?Id?of?default?device torch.cuda.current_device() #?0 cuda.Device(0).name()?#?'0'?is?the?id?of?your?GPU #?Tesla?K80或者如下所示:
torch.cuda.get_device_name(0)?#?Get?name?device?with?ID?'0' #?'Tesla?K80'這里寫了一個簡單的類來獲取?cuda?的信息:
#?A?simple?class?to?know?about?your?cuda?devices import?pycuda.driver?as?cuda import?pycuda.autoinit?#?Necessary?for?using?its?functions cuda.init()?#?Necesarry?for?using?its?functionsclass?aboutCudaDevices():def?__init__(self):passdef?num_devices(self):"""返回?cuda?設備的數量"""return?cuda.Device.count()def?devices(self):"""獲取所有可用的設備的名稱"""num?=?cuda.Device.count()print("%d?device(s)?found:"%num)for?i?in?range(num):print(cuda.Device(i).name(),?"(Id:?%d)"%i)def?mem_info(self):"""獲取所有設備的總內存和可用內存"""available,?total?=?cuda.mem_get_info()print("Available:?%.2f?GB\nTotal:?????%.2f?GB"%(available/1e9,?total/1e9))def?attributes(self,?device_id=0):"""返回指定?id?的設備的屬性信息"""return?cuda.Device(device_id).get_attributes()def?__repr__(self):"""輸出設備的數量和其id、內存信息"""num?=?cuda.Device.count()string?=?""string?+=?("%d?device(s)?found:\n"%num)for?i?in?range(num):string?+=?(?"????%d)?%s?(Id:?%d)\n"%((i+1),cuda.Device(i).name(),i))string?+=?("??????????Memory:?%.2f?GB\n"%(cuda.Device(i).total_memory()/1e9))return?string#?You?can?print?output?just?by?typing?its?name?(__repr__): aboutCudaDevices() #?1?device(s)?found: #????1)?Tesla?K80?(Id:?0) #??????????Memory:?12.00?GB如果想知道當前內存的使用情況,查詢代碼如下所示:
import?torch #?Returns?the?current?GPU?memory?usage?by? #?tensors?in?bytes?for?a?given?device #?返回當前使用的?GPU?內存,單位是字節 torch.cuda.memory_allocated() #?Returns?the?current?GPU?memory?managed?by?the #?caching?allocator?in?bytes?for?a?given?device #?返回當前緩存分配器中的?GPU?內存 torch.cuda.memory_cached()清空?cuda?緩存的代碼如下所示:
#?Releases?all?unoccupied?cached?memory?currently?held?by #?the?caching?allocator?so?that?those?can?be?used?in?other #?GPU?application?and?visible?in?nvidia-smi #?釋放所有非占用的內存 torch.cuda.empty_cache()但需要注意的是,上述函數并不會釋放被?tensors?占用的 GPU 內存,因此并不能增加當前可用的 GPU 內存。
3. 在 GPU 上存儲 Tensors 和運行模型的方法
如果是想存儲變量在 cpu 上,可以按下面代碼所示這么寫:
a?=?torch.DoubleTensor([1.,?2.])變量?a?將保持在 cpu 上,并在 cpu 上進行各種運算,如果希望將它轉換到 gpu 上,需要采用?.cuda?,可以有以下兩種實現方法
#?方法1 a?=?torch.FloatTensor([1.,?2.]).cuda() #?方法2 a?=?torch.cuda.FloatTensor([1.,?2.])這種做法會選擇默認的第一個 GPU,查看方式有下面兩種:
#?方法1 torch.cuda.current_device() #?0#?方法2 a.get_device() #?0另外,也可以在 GPU 上運行模型,例子如下所示,簡單使用?nn.Sequential?定義一個模型:
sq?=?nn.Sequential(nn.Linear(20,?20),nn.ReLU(),nn.Linear(20,?4),nn.Softmax() )然后放到 GPU 上運行:
model?=?sq.cuda()怎么判斷模型是否在 GPU 上運行呢,可以通過下述方法查看模型的參數是否在 GPU 上來判斷:
#?From?the?discussions?here:?discuss.pytorch.org/t/how-to-check-if-model-is-on-cuda #?參考?https://discuss.pytorch.org/t/how-to-check-if-model-is-on-cuda/180next(model.parameters()).is_cuda #?True4. 有多個 GPU 的時候,如何選擇和使用它們
假設有 3 個 GPU ,我們可以初始化和分配?tensors?到任意一個指定的 GPU 上,代碼如下所示,這里分配?tensors?到指定 GPU 上,有 3 種方法:
初始化 tensor 時,指定參數?device
.to(cuda_id)
.cuda(cuda_id)
當你有多個 GPU 的時候,就可以將應用的工作劃分,但這里存在相互之間交流的問題,不過如果不需要頻繁的交換信息,那么這個問題就可以忽略。
實際上,還有另一個問題,在?PyTorch?中所有 GPU 的運算默認都是異步操作。但在 CPU 和 GPU 或者兩個 GPU 之間的數據復制是需要同步的,當你通過函數?torch.cuda.Stream()?創建自己的流時,你必須注意這個同步問題。
下面是官方文檔上一個錯誤的示例:
cuda?=?torch.device('cuda') #?創建一個流 s?=?torch.cuda.Stream()?? A?=?torch.empty((100,?100),?device=cuda).normal_(0.0,?1.0) with?torch.cuda.stream(s):#?because?sum()?may?start?execution?before?normal_()?finishes!#?sum()?操作可能在?normal_()?結束前就可以執行了B?=?torch.sum(A)如果想完全利用好多 GPU,應該按照如下做法:
將所有 GPU 用于不同的任務或者應用;
在多模型中,每個 GPU 應用單獨一個模型,并且各自有預處理操作都完成好的一份數據拷貝;
每個 GPU 采用切片輸入和模型的拷貝,每個 GPU 將單獨計算結果,并將結果都發送到同一個 GPU 上進行進一步的運算操作。
5. 數據并行
數據并行的操作要求我們將數據劃分成多份,然后發送給多個 GPU 進行并行的計算。
PyTorch 中實現數據并行的操作可以通過使用?torch.nn.DataParallel。
下面是一個簡單的示例。要實現數據并行,第一個方法是采用?nn.parallel?中的幾個函數,分別實現的功能如下所示:
復制(Replicate):將模型拷貝到多個 GPU 上;
分發(Scatter):將輸入數據根據其第一個維度(通常就是 batch 大小)劃分多份,并傳送到多個 GPU 上;
收集(Gather):從多個 GPU 上傳送回來的數據,再次連接回一起;
并行的應用(parallel_apply):將第三步得到的分布式的輸入數據應用到第一步中拷貝的多個模型上。
實現代碼如下所示:
#?Replicate?module?to?devices?in?device_ids replicas?=?nn.parallel.replicate(module,?device_ids) #?Distribute?input?to?devices?in?device_ids inputs?=?nn.parallel.scatter(input,?device_ids) #?Apply?the?models?to?corresponding?inputs outputs?=?nn.parallel.parallel_apply(replicas,?inputs) #?Gather?result?from?all?devices?to?output_device result?=?nn.parallel.gather(outputs,?output_device)實際上,還有一個更簡單的也是常用的實現方法,如下所示,只需一行代碼即可:
model?=?nn.DataParallel(model,?device_ids=device_ids) result?=?model(input)6. 數據并行的比較
根據文章 https://medium.com/@iliakarmanov/multi-gpu-rosetta-stone-d4fa96162986 以及 Github:https://github.com/ilkarman/DeepLearningFrameworks 得到的不同框架在采用單個 GPU 和 4 個 GPU 時運算速度的對比結果,如下所示:
從圖中可以看到數據并行操作盡管存在多 GPU 之間交流的問題,但是提升的速度還是很明顯的。而?PyTorch?的運算速度僅次于?Chainer?,但它的數據并行方式非常簡單,一行代碼即可實現。
7. torch.multiprocessing
torch.multiprocessing?是對 Python 的?multiprocessing?模塊的一個封裝,并且百分比兼容原始模塊,也就是可以采用原始模塊中的如?Queue?、Pipe、Array?等方法。并且為了加快速度,還添加了一個新的方法--share_memory_(),它允許數據處于一種特殊的狀態,可以在不需要拷貝的情況下,任何進程都可以直接使用該數據。
通過該方法,可以共享?Tensors?、模型的參數?parameters?,可以在 CPU 或者 GPU 之間共享它們。
下面展示一個采用多進程訓練模型的例子:
#?Training?a?model?using?multiple?processes: import?torch.multiprocessing?as?mp def?train(model):for?data,?labels?in?data_loader:optimizer.zero_grad()loss_fn(model(data),?labels).backward()optimizer.step()??#?This?will?update?the?shared?parameters model?=?nn.Sequential(nn.Linear(n_in,?n_h1),nn.ReLU(),nn.Linear(n_h1,?n_out)) model.share_memory()?#?Required?for?'fork'?method?to?work processes?=?[] for?i?in?range(4):?#?No.?of?processesp?=?mp.Process(target=train,?args=(model,))p.start()processes.append(p) for?p?in?processes:?p.join()更多的使用例子可以查看官方文檔:
https://pytorch.org/docs/stable/distributed.html
參考:
https://documen.tician.de/pycuda/
https://pytorch.org/docs/stable/notes/cuda.html
https://discuss.pytorch.org/t/how-to-check-if-model-is-on-cuda
https://pytorch.org/tutorials/beginner/blitz/data_parallel_tutorial.html
https://medium.com/@iliakarmanov/multi-gpu-rosetta-stone-d4fa96162986
歡迎關注我的微信公眾號--算法猿的成長,或者掃描下方的二維碼,大家一起交流,學習和進步!
如果覺得不錯,在看、轉發就是對小編的一個支持!
總結
以上是生活随笔為你收集整理的PyTorch系列 | 如何加快你的模型训练速度呢?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 硬件:实用的电脑维护小常识
- 下一篇: 分享几个路由器设置小技巧,总有用得到的一