偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

借助Numba和CUDA,用Python編寫你的第一個GPU內(nèi)核

譯文 精選
開發(fā) 后端 算法
我們在本文中將使用一個向量加法的常見示例,并使用Numba將簡單的CPU代碼轉(zhuǎn)換成CUDA內(nèi)核。向量加法是并行機(jī)制的理想例子,因?yàn)榭鐔蝹€索引的加法與其他索引無關(guān)。這是完美的SIMD場景,因此所有索引可以同時相加,從而在一次運(yùn)算中完成向量加法。

譯者 | 布加迪

審校 | 重樓

Python 速度提升80倍?探究如何用一行代碼將你的代碼變成GPU猛獸!

GPU非常適合處理需要對不同數(shù)據(jù)執(zhí)行相同操作的任務(wù)。這種方法名為單指令多數(shù)據(jù)(SIMD。與只有幾個強(qiáng)大核心的CPU不同,GPU擁有數(shù)千個較小的核心,它們可以同時運(yùn)行這些重復(fù)性操作。你會在機(jī)器學(xué)習(xí)中經(jīng)??吹竭@種模式,比如在對大型向量進(jìn)行加法或乘法時,因?yàn)槊總€計(jì)算都是獨(dú)立的。這是使用GPU通過并行機(jī)制加快處理任務(wù)的理想場景。

英偉達(dá)創(chuàng)建了CUDA,以便開發(fā)者編寫在GPU而不是CPU上運(yùn)行的程序。它基于C語言,允許你編寫名為內(nèi)核的特殊函數(shù),這些函數(shù)可以同時運(yùn)行多個操作。問題在于,用C C++編寫CUDA對初學(xué)者來說并不友好,必須處理諸如手動分配內(nèi)存、協(xié)調(diào)線程以及理解GPU底層工作原理之類的問題。這可能會讓初學(xué)者感到不知所措,尤其是如果習(xí)慣用 Python編寫代碼的話。

這時候Numba可以助你一臂之力。它允許借助Python編寫CUDA內(nèi)核,并使用LLVM(低級虛擬機(jī))編譯器基礎(chǔ)架構(gòu),將Python代碼直接編譯成與CUDA兼容的內(nèi)核。借助即時(JIT編譯,你可以使用裝飾器注釋函數(shù),其余所有工作交由Numba處理。

我們在本文中將使用一個向量加法的常見示例,并使用Numba將簡單的CPU代碼轉(zhuǎn)換成CUDA內(nèi)核。向量加法是并行機(jī)制的理想例子,因?yàn)榭鐔蝹€索引的加法與其他索引無關(guān)。這是完美的SIMD場景,因此所有索引可以同時相加,從而在一次運(yùn)算中完成向量加法。

請注意,你需要一個CUDA GPU才能遵循本文操作。你可以使用Colab的免費(fèi)T4 GPU或已安裝英偉達(dá)工具包和NVCC的本地GPU。

搭建環(huán)境并安裝Numba

NumbaPython軟件包的形式提供,你可以使用pip來安裝。此外,我們將使用numpy用于向量運(yùn)算。使用以下命令搭建Python環(huán)境:

python3 -m venv venv
source venv/bin/activate
pip install numba-cuda numpy

CPU上的向量加法

我們舉一個簡單的向量加法例子。對于兩個給定的向量,我們將每個索引對應(yīng)的值相加以獲得最終值。我們將使用numpy生成隨機(jī)的float32向量,并使用for循環(huán)生成最終輸出。

import numpy as np 
N = 10_000_000 # 10 million elements 
a = np.random.rand(N).astype(np.float32) 
b = np.random.rand(N).astype(np.float32) 
c = np.zeros_like(a) # Output array 
def vector_add_cpu(a, b, c): 
 """Add two vectors on CPU""" 
 for i in range(len(a)): 
 c[i] = a[i] + b[i]

代碼分解如下:

  • 初始化兩個向量,每個向量包含1000萬個隨機(jī)浮點(diǎn)數(shù)。
  • 我們還創(chuàng)建一個空向量c來存儲結(jié)果。
  • vector_add_cpu函數(shù)只是循環(huán)遍歷每個索引,并將ab中的元素相加,將結(jié)果存儲在c中。

這是一個串行操作;每個加法操作都是一個接一個進(jìn)行。雖然這種方法運(yùn)行良好,但它并非最高效的方法,尤其是對大型數(shù)據(jù)集而言。由于每個加法彼此獨(dú)立,因此非常適合在GPU上并行執(zhí)行。

在下一節(jié)中,你將看到如何使用Numba轉(zhuǎn)換這個相同的操作以便在GPU上運(yùn)行。通過將每個元素級加法分布到數(shù)千個GPU線程上,我們可以顯著加快任務(wù)完成速度。

借助NumbaGPU上進(jìn)行向量加法

現(xiàn)在你將使用Numba定義一個可在CUDA上運(yùn)行的Python函數(shù),并在Python中執(zhí)行它。我們在執(zhí)行相同的向量加法運(yùn)算,但現(xiàn)在它可以針對Numpy數(shù)組的每個索引并行運(yùn)行,從而提高執(zhí)行速度。

以下是編寫內(nèi)核的代碼:

from numba import config
# Required for newer CUDA versions to enable linking tools. 
# Prevents CUDA toolkit and NVCC version mismatches.
config.CUDA_ENABLE_PYNVJITLINK = 1
from numba import cuda, float32
@cuda.jit
def vector_add_gpu(a, b, c):
 """Add two vectors using CUDA kernel"""
 # Thread ID in the current block
 tx = cuda.threadIdx.x
 # Block ID in the grid
 bx = cuda.blockIdx.x
 # Block width (number of threads per block)
 bw = cuda.blockDim.x
 # Calculate the unique thread position
 position = tx + bx * bw
 # Make sure we don't go out of bounds
 if position < len(a):
 c[position] = a[position] + b[position]
def gpu_add(a, b, c):
 # Define the grid and block dimensions
 threads_per_block = 256
 blocks_per_grid = (N + threads_per_block - 1) // threads_per_block
 # Copy data to the device
 d_a = cuda.to_device(a)
 d_b = cuda.to_device(b)
 d_c = cuda.to_device(c)
 # Launch the kernel
 vector_add_gpu[blocks_per_grid, threads_per_block](d_a, d_b, d_c)
 # Copy the result back to the host
 d_c.copy_to_host(c)
def time_gpu():
 c_gpu = np.zeros_like(a)
 gpu_add(a, b, c_gpu)
 return c_gpu

不妨細(xì)述一下上面發(fā)生的操作。

理解GPU函數(shù)

@cuda.jit裝飾器告訴Numba將以下函數(shù)視為CUDA內(nèi)核;這是一個將跨GPU上的多個線程并行運(yùn)行的特殊函數(shù)。在運(yùn)行時,Numba會將此函數(shù)編譯成與CUDA兼容的代碼,并為你處理C-API轉(zhuǎn)譯。

@cuda.jit
defvector_add_gpu(a, b, c):
...

該函數(shù)將同時在數(shù)千個線程上運(yùn)行。但我們需要一種方法來確定每個線程應(yīng)該處理數(shù)據(jù)的哪個部分。這就是接下來幾行代碼的作用:

  • tx 是線程在其塊中的 ID。
  • bx 是塊在網(wǎng)格中的 ID。
  • bw 是塊中有多少個線程。

我們將這些數(shù)據(jù)組合起來計(jì)算出獨(dú)特的位置,該位置告訴每個線程應(yīng)該添加數(shù)組中的哪個元素。請注意,線程和塊可能并不總是提供有效的索引,因?yàn)樗鼈円?/span>2的冪次方進(jìn)行操作。當(dāng)向量長度不符合底層架構(gòu)時,這可能會導(dǎo)致無效索引。因此,我們在執(zhí)行向量加法之前添加了一個保護(hù)條件來驗(yàn)證索引。這可以防止訪問數(shù)組時出現(xiàn)任何越界運(yùn)行時錯誤。

一旦我們知道了這個獨(dú)特位置,現(xiàn)在可以像在CPU實(shí)現(xiàn)中一樣添加值。以下代碼行與CPU實(shí)現(xiàn)一致:

c[position] = a[position] + b[position]

啟動內(nèi)核

gpu_add函數(shù)負(fù)責(zé)進(jìn)行設(shè)置:

  • 它定義了要使用的線程和塊的數(shù)量。你可以嘗試塊和線程大小的不同值,并在GPU內(nèi)核中打印輸出相應(yīng)的值。這可以幫助你理解底層GPU索引的工作原理。
  • 它將輸入數(shù)組(a、b c)從CPU內(nèi)存復(fù)制到GPU內(nèi)存,以便可以在GPU RAM中訪問這些向量。
  • 它使用vector_add_gpu[blocks_per_grid,threads_per_block]運(yùn)行GPU內(nèi)核。
  • 最后,它將結(jié)果從GPU復(fù)制回到c數(shù)組,以便我們可以在CPU上訪問這些值。

比較實(shí)現(xiàn)和潛在加速

我們已有了CPUGPU版本的向量加法,是時候比較一下它們了。驗(yàn)證結(jié)果以及使用CUDA并行機(jī)制所能獲得的執(zhí)行提升非常重要。

import timeit
c_cpu = time_cpu()
c_gpu = time_gpu()
print("Results match:", np.allclose(c_cpu, c_gpu))
cpu_time = timeit.timeit("time_cpu()", globals=globals(), number=3) / 3
print(f"CPU implementation: {cpu_time:.6f} seconds")
gpu_time = timeit.timeit("time_gpu()", globals=globals(), number=3) / 3
print(f"GPU implementation: {gpu_time:.6f} seconds")
speedup = cpu_time / gpu_time
print(f"GPU speedup: {speedup:.2f}x")

首先,我們運(yùn)行兩種實(shí)現(xiàn),檢查它們的結(jié)果是否一致。這對于確保我們的GPU代碼正常運(yùn)行且輸出結(jié)果與CPU版本一致至關(guān)重要。

接下來,我們使用Python內(nèi)置的timeit模塊來測量每個版本的運(yùn)行時間。我們運(yùn)行每個函數(shù)幾次,取平均值以獲得可靠的時間。最后,我們計(jì)算GPU版本比CPU版本快多少倍。你應(yīng)該會看到顯著的差異,因?yàn)?/span>GPU可以同時執(zhí)行多項(xiàng)操作,而CPU在循環(huán)中一次只處理一項(xiàng)操作。

以下是Colab上英偉達(dá)T4 GPU 的預(yù)期輸出。請注意,具體的加速效果可能因CUDA版本和底層硬件而異。

Results match: True
CPU implementation: 4.033822 seconds
GPU implementation: 0.047736 seconds
GPU speedup: 84.50x

這個簡單的測試有助于展示GPU加速的強(qiáng)大功能,以及它為何對涉及大量數(shù)據(jù)和并行工作的任務(wù)如此有用。

結(jié)語

就是這樣。你現(xiàn)在已經(jīng)借助Numba編寫了第一個CUDA內(nèi)核,無需實(shí)際編寫任何CCUDA代碼。Numba提供了一個簡單的接口,可以通過Python使用GPU,這使得Python工程師更容易上手CUDA編程。

現(xiàn)在,你可以使用相同的模板來編寫高級CUDA算法,這些算法在機(jī)器學(xué)習(xí)和深度學(xué)習(xí)中非常流行。如果你發(fā)現(xiàn)遵循SIMD范式存在問題,使用GPU來提升執(zhí)行速度始終是個好主意。

完整的代碼可以在Colab筆記本上找到,可以點(diǎn)擊此處訪問。你可以隨意測試并進(jìn)行一些簡單的更改,以更好地理解CUDA索引和執(zhí)行的內(nèi)部工作原理。

原文標(biāo)題:Writing Your First GPU Kernel in Python with Numba and CUDA,作者:Kanwal Mehreen

責(zé)任編輯:姜華 來源: 51CTO
相關(guān)推薦

2014-07-24 14:35:26

Linux內(nèi)核模塊

2018-10-15 10:10:41

Linux內(nèi)核補(bǔ)丁

2013-12-19 09:46:04

垃圾收集器

2019-12-31 08:00:00

DebianLinuxApple Swift

2021-04-07 13:38:27

Django項(xiàng)目視圖

2022-10-17 10:28:05

Web 組件代碼

2010-03-15 10:37:46

Pthon腳本

2013-01-14 09:44:58

JavaScriptJSJS框架

2019-10-28 08:00:00

Keras神經(jīng)網(wǎng)絡(luò)人工智能

2023-09-28 13:21:32

2020-01-11 17:00:07

DjangoPythonWeb API

2017-09-25 08:36:01

CUDAPython編譯器

2012-05-28 09:24:49

虛擬化

2021-12-30 11:26:31

語言編譯器腳本

2011-08-29 15:12:24

UbuntuLinux模塊

2016-08-05 12:58:44

GitLinux開源

2016-08-24 15:12:41

LXDLinux容器

2018-01-31 15:45:07

前端Vue.js組件

2017-11-21 09:20:06

深度學(xué)習(xí)TensorFlow游戲AI

2023-06-01 08:24:08

OpenAIChatGPTPython
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號