溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Python怎么實現(xiàn)GPU加速的基本操作

發(fā)布時間:2021-08-31 23:03:48 來源:億速云 閱讀:571 作者:chen 欄目:開發(fā)技術(shù)

本篇內(nèi)容介紹了“Python怎么實現(xiàn)GPU加速的基本操作”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!

目錄
  • CUDA的線程與塊

    • 用GPU打印線程編號

    • 用GPU打印塊編號

    • 用GPU打印塊的維度

    • 用GPU打印線程的維度

    • 總結(jié)

  • GPU所支持的最大并行度

    • GPU的加速效果

      CUDA的線程與塊

      GPU從計算邏輯來講,可以認為是一個高并行度的計算陣列,我們可以想象成一個二維的像圍棋棋盤一樣的網(wǎng)格,每一個格子都可以執(zhí)行一個單獨的任務(wù),并且所有的格子可以同時執(zhí)行計算任務(wù),這就是GPU加速的來源。那么剛才所提到的棋盤,每一列都認為是一個線程,并有自己的線程編號;每一行都是一個塊,有自己的塊編號。我們可以通過一些簡單的程序來理解這其中的邏輯:

      用GPU打印線程編號

      # numba_cuda_test.py
      
      from numba import cuda
      
      @cuda.jit
      def gpu():
          print ('threadIdx:', cuda.threadIdx.x)
      
      if __name__ == '__main__':
          gpu[2,4]()
      threadIdx: 0
      threadIdx: 1
      threadIdx: 2
      threadIdx: 3
      threadIdx: 0
      threadIdx: 1
      threadIdx: 2
      threadIdx: 3

      用GPU打印塊編號

      # numba_cuda_test.py
      
      from numba import cuda
      
      @cuda.jit
      def gpu():
          print ('blockIdx:', cuda.blockIdx.x)
      
      if __name__ == '__main__':
          gpu[2,4]()
      blockIdx: 0
      blockIdx: 0
      blockIdx: 0
      blockIdx: 0
      blockIdx: 1
      blockIdx: 1
      blockIdx: 1
      blockIdx: 1

      用GPU打印塊的維度

      # numba_cuda_test.py
      
      from numba import cuda
      
      @cuda.jit
      def gpu():
          print ('blockDim:', cuda.blockDim.x)
      
      if __name__ == '__main__':
          gpu[2,4]()
      blockDim: 4
      blockDim: 4
      blockDim: 4
      blockDim: 4
      blockDim: 4
      blockDim: 4
      blockDim: 4
      blockDim: 4

      用GPU打印線程的維度

      # numba_cuda_test.py
      
      from numba import cuda
      
      @cuda.jit
      def gpu():
          print ('gridDim:', cuda.gridDim.x)
      
      if __name__ == '__main__':
          gpu[2,4]()
      gridDim: 2
      gridDim: 2
      gridDim: 2
      gridDim: 2
      gridDim: 2
      gridDim: 2
      gridDim: 2
      gridDim: 2

      總結(jié)

      我們可以用如下的一張圖來總結(jié)剛才提到的GPU網(wǎng)格的概念,在上面的測試案例中,我們在GPU上劃分一塊2*4大小的陣列用于我們自己的計算,每一行都是一個塊,每一列都是一個線程,所有的網(wǎng)格是同時執(zhí)行計算的內(nèi)容的(如果沒有邏輯上的依賴的話)。

      Python怎么實現(xiàn)GPU加速的基本操作

      GPU所支持的最大并行度

      我們可以用幾個簡單的程序來測試一下GPU的并行度,因為每一個GPU上的網(wǎng)格都可以獨立的執(zhí)行一個任務(wù),因此我們認為可以分配多少個網(wǎng)格,就有多大的并行度。本機的最大并行應(yīng)該是在\(2^40\),因此假設(shè)我們給GPU分配\(2^50\)大小的網(wǎng)格,程序就會報錯:

      # numba_cuda_test.py
      
      from numba import cuda
      
      @cuda.jit
      def gpu():
          pass
      
      if __name__ == '__main__':
          gpu[2**50,1]()
          print ('Running Success!')

      運行結(jié)果如下:

      Traceback (most recent call last):
      File "numba_cuda_test.py", line 10, in <module>
      gpu[2**50,1]()
      File "/home/dechin/.local/lib/python3.7/site-packages/numba/cuda/compiler.py", line 822, in __call__
      self.stream, self.sharedmem)
      File "/home/dechin/.local/lib/python3.7/site-packages/numba/cuda/compiler.py", line 966, in call
      kernel.launch(args, griddim, blockdim, stream, sharedmem)
      File "/home/dechin/.local/lib/python3.7/site-packages/numba/cuda/compiler.py", line 699, in launch
      cooperative=self.cooperative)
      File "/home/dechin/.local/lib/python3.7/site-packages/numba/cuda/cudadrv/driver.py", line 2100, in launch_kernel
      None)
      File "/home/dechin/.local/lib/python3.7/site-packages/numba/cuda/cudadrv/driver.py", line 300, in safe_cuda_api_call
      self._check_error(fname, retcode)
      File "/home/dechin/.local/lib/python3.7/site-packages/numba/cuda/cudadrv/driver.py", line 335, in _check_error
      raise CudaAPIError(retcode, msg)
      numba.cuda.cudadrv.driver.CudaAPIError: [1] Call to cuLaunchKernel results in CUDA_ERROR_INVALID_VALUE

      而如果我們分配一個額定大小之內(nèi)的網(wǎng)格,程序就可以正常的運行:

      # numba_cuda_test.py
      
      from numba import cuda
      
      @cuda.jit
      def gpu():
          pass
      
      if __name__ == '__main__':
          gpu[2**30,1]()
          print ('Running Success!')

      這里加了一個打印輸出:

      Running Success!

      需要注意的是,兩個維度上的可分配大小是不一致的,比如本機的上限是分配230*210大小的空間用于計算:

      # numba_cuda_test.py
      
      from numba import cuda
      
      @cuda.jit
      def gpu():
          pass
      
      if __name__ == '__main__':
          gpu[2**30,2**10]()
          print ('Running Success!')

      同樣的,只要在允許的范圍內(nèi)都是可以執(zhí)行成功的:

      Running Success!

      如果在本機上有多塊GPU的話,還可以通過select_device的指令來選擇執(zhí)行指令的GPU編號:

      # numba_cuda_test.py
      
      from numba import cuda
      cuda.select_device(1)
      import time
      
      @cuda.jit
      def gpu():
          pass
      
      if __name__ == '__main__':
          gpu[2**30,2**10]()
          print ('Running Success!')

      如果兩塊GPU的可分配空間一致的話,就可以運行成功:

      Running Success!

      GPU的加速效果

      前面我們經(jīng)常提到一個詞叫GPU加速,GPU之所以能夠?qū)崿F(xiàn)加速的效果,正源自于GPU本身的高度并行性。這里我們直接用一個數(shù)組求和的案例來說明GPU的加速效果,這個案例需要得到的結(jié)果是\(b_j=a_j+b_j\),將求和后的值賦值在其中的一個輸入數(shù)組之上,以節(jié)省一些內(nèi)存空間。當(dāng)然,如果這個數(shù)組還有其他的用途的話,是不能這樣操作的。具體代碼如下:

      # gpu_add.py
      
      from numba import cuda
      cuda.select_device(1)
      import numpy as np
      import time
      
      @cuda.jit
      def gpu(a,b,DATA_LENGHTH):
          idx = cuda.threadIdx.x + cuda.blockIdx.x * cuda.blockDim.x
          if idx < DATA_LENGHTH:
              b[idx] += a[idx]
      
      if __name__ == '__main__':
          np.random.seed(1)
          DATA_EXP_LENGTH = 20
          DATA_DIMENSION = 2**DATA_EXP_LENGTH
          np_time = 0.0
          nb_time = 0.0
          for i in range(100):
              a = np.random.randn(DATA_DIMENSION).astype(np.float32)
              b = np.random.randn(DATA_DIMENSION).astype(np.float32)
              a_cuda = cuda.to_device(a)
              b_cuda = cuda.to_device(b)
              time0 = time.time()
              gpu[DATA_DIMENSION,4](a_cuda,b_cuda,DATA_DIMENSION)
              time1 = time.time()
              c = b_cuda.copy_to_host()
              time2 = time.time()
              d = np.add(a,b)
              time3 = time.time()
              if i == 0:
                  print ('The error between numba and numpy is: ', sum(c-d))
                  continue
              np_time += time3 - time2
              nb_time += time1 - time0
          print ('The time cost of numba is: {}s'.format(nb_time))
          print ('The time cost of numpy is: {}s'.format(np_time))

      需要注意的是,基于Numba實現(xiàn)的Python的GPU加速程序,采用的jit即時編譯的模式,也就是說,在運行調(diào)用到相關(guān)函數(shù)時,才會對其進行編譯優(yōu)化。換句話說,第一次執(zhí)行這一條指令的時候,事實上達不到加速的效果,因為這個運行的時間包含了較長的一段編譯時間。但是從第二次運行調(diào)用開始,就不需要重新編譯,這時候GPU加速的效果就體現(xiàn)出來了,運行結(jié)果如下:

      $ python3 gpu_add.py The error between numba and numpy is: 0.0
      The time cost of numba is: 0.018711328506469727s
      The time cost of numpy is: 0.09502553939819336s

      可以看到,即使是相比于Python中優(yōu)化程度十分強大的的Numpy實現(xiàn),我們自己寫的GPU加速的程序也能夠達到5倍的加速效果(在前面一篇博客中,針對于特殊計算場景,加速效果可達1000倍以上),而且可定制化程度非常之高。

      “Python怎么實現(xiàn)GPU加速的基本操作”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!

      向AI問一下細節(jié)

      免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

      AI