CUDA 編程模型

概述

一、 主機與設備

CUDA 編程模型將 CPU 作為主機 (Host) GPU 作為協處理器 (co-processor) 或者設備 (Device). 在一個系統中可以存在一個主機和多個設備。 CPU 主要負責進行邏輯性強的事物處理和串行計算, GPU 則專注于執行高度線程化的并行處理任務。 CPU GPU 各自擁有相互獨立的存儲器地址空間:主機端的內存和設備端的顯存。 CUDA 對內存的操作與一般的 C 程序基本相同,但增加了一種新的 pinned memory

二、運行在 GPU 上的 CUDA 并行計算函數稱為 kernel( 內核函數 ) 。一個 kernel 函數并不是一個完整的程序,而是整個 CUDA 程序中的一個可以被并行執行的步驟。

三、 CPU 串行代碼完成的工作包括在 kernel 啟動前進行數據準備和設備初始化的工作,以及在 kernel 之間進行一些串行計算。理想情況是, CPU 串行代碼的作用應該只是清理上一個內核函數,并啟動下一個內核函數。這樣,就可以在設備上完成盡可能多的工作,減少主機與設備之間的數據傳輸。

Kernel 函數的定義與調用

一、 運行在 GPU 上的程序稱為 kernel( 內核函數 )

內核函數必須通過 _global_ 函數類型限定符定義,并且只能在主機端代碼中調用。在調用時,必須聲明內核函數的執行參數。

注意: __global__ 下劃線在Visual studio中的寫法。

使用一種新 <<<…>>> 執行配置語法指定執行某一指定內核調用的線程數。必須先為 Kernel 中用到的數組或變量分配好足夠的空間,再調用 kernel 函數

二、 在設備端運行的線程之間是并行執行的,每個線程有自己的 blockID threadID 用于與其他線程相區分。 BlockID threadID 只能在 kernel 中通過內建變量訪問。

三、 內建變量不需由程序員定義,是由設備中的專用寄存器提供的。所以,內建變量是只讀的,并且只能在 GPU 端得 kernel 函數中使用。

線程結構

Kernel 是以 block 為單位執行的, CUDA 引入 grid 來表示一系列可以被并行執行的 block 的集合。各 block 是并行執行的, block 之間無法通信,也沒有執行順序。

?

block 內通信原理

在同一個 block 中的線程通過共享存儲器 (shared memory) 交換數據,并通過柵欄同步保證線程間能夠正確地共享數據。具體來說,可以在 kernel 函數中需要同步的位置調用 _syncthreads() 函數。

為了保證線程塊中的各個線程能夠有效協作,訪問共享存儲器的延遲必須很小。所以在 GPU 中,共享存儲器與執行單元的物理距離必須很小,處于同一個處理核心中。而為了在硬件上用很小的代價就能實現 _syncthreads() 函數,一個 block 中所有線程的數據都必須交由同一處理核心進行處理。所以,這導致每個線程塊中的線程數量、共享存儲器大小、寄存器數量都要受到處理核心硬件資源的限制。目前,每個 block 里最多只能有 512 個線程。

計算單元

GPU 內部, SM 代表流多處理器,即計算核心。每個 SM 中又包含 8 個標量流處理器 SP 以及少量的其他計算單元。實際上, SP 只是執行單元,并不是完整的處理核心。處理核心必須包含取指、解碼、分發邏輯和執行單元。隸屬同一 SM 8 SP 共用同一套取指和發射單元,也共用一塊共享存儲器。

一個 block 必須被分配到一個 SM 中,但是一個 SM 中同一時刻可以有多個活動線程塊等待執行。這可以更好地利用執行單元的資源,當一個 block 進行同步或者訪問顯存等高延遲操作時,另一個 block 就可以占用 GPU 執行資源。

目前,一個內核函數只有一個 grid ,但在支持 DirectX 11 的硬件中,這一限制將會解除。

真正的執行單元

在實際運行中, block 會被分割為更小的線程束 (warp) warp 的大小由硬件的計算能力版本決定。在采用 Tesla 架構的 GPU 中,一個線程束由連續的 32 個線程組成。 Warp 中的線程只與線程 thread? ID 有關。在每發射一條 warp 指令, SM 中的 8 SP 將執行這條指令 4 遍。

執行模型

CUDA 采用了 SIMT( 單指令多線程 ) 執行模型。在 SIMT 模型中,如果需要控制單個線程的行為,這會大大降低效率。