以下是最簡單的帶參數的核函數使用過程:
#include<iostream>
#include<cstdio>
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
__global__ void add(int a,int b,int *c) {*c = a + b;
}
int main() {int c;int* dev_c;cudaMalloc((void **)&dev_c,sizeof(int));add << <1, 1 >> > (2, 7, dev_c);cudaMemcpy(&c, dev_c, sizeof(int), cudaMemcpyDeviceToHost);//這里會隱式同步,等待核函數執行完printf("2+7=%d\n", c);cudaFree(dev_c);cudaError_t err = cudaDeviceSynchronize();if (err != cudaSuccess) {std::cerr << "CUDA Error: " << cudaGetErrorString(err) << std::endl;}return 0;
}
add的參數:
-
int a
,int b
:兩個通過值傳遞的整型參數。 -
int *c
:一個指針,指向 GPU 設備內存,用于返回結果。
cudaMalloc
的作用
cudaError_t cudaMalloc(void **devPtr, size_t size);
-
作用:在 GPU 設備內存上分配一塊大小為
size
字節的內存空間。 -
參數說明:
-
void **devPtr
:一個指向設備指針的指針,cudaMalloc
會將申請到的設備內存地址寫入這個指針。 -
size
:需要分配的內存字節數。
-
-
返回值:返回
cudaSuccess
(表示成功),或者其他錯誤碼。
為什么要用 cudaMalloc
?
因為 GPU 上運行的核函數(__global__
)不能訪問 CPU 的內存(host memory),所以:
-
要傳遞結果回 CPU,必須在 GPU 內存中有一塊空間存放結果;
-
你不能直接傳一個 CPU 指針(如
int* c
)給核函數,否則會產生非法內存訪問; -
所以你要用
cudaMalloc
分配一塊 GPU 內存(例如int* dev_c
),傳給核函數用于寫入結果。
?示意圖:
+----------------+ cudaMemcpy +----------------+
| Host (CPU) | <----------------------> | Device (GPU) |
| int c; | | int* dev_c |
+----------------+ cudaMalloc +----------------+↑dev_c = GPU上的內存地址
在主機代碼中,不能對 dev_c
解引用,只能通過 cudaMemcpy
把它里面的數據拷回來后使用。
區域 | 地址空間 | 是否可以解引用該地址? |
---|---|---|
主機內存(Host) | 主機地址 | 只能在 CPU 代碼中解引用 |
設備內存(Device) | 顯存地址 | 只能在 GPU 核函數中解引用 |
?但是在主機上可以對它進行參數傳遞等不涉及訪問內存的操作。
cudaMemcpy
cudaError_t cudaMemcpy(void* dst, const void* src, size_t count, cudaMemcpyKind kind);
參數名 | 說明 |
---|---|
dst | 目標地址(可以是主機或設備內存) |
src | 源地址(可以是主機或設備內存) |
count | 拷貝的字節數 |
kind | 拷貝類型,說明從哪拷到哪(見下表) |
類型 | 含義 |
---|---|
cudaMemcpyHostToDevice | 從主機(Host)拷貝到設備(Device) |
cudaMemcpyDeviceToHost | 從設備(Device)拷貝到主機(Host) |
cudaMemcpyDeviceToDevice | 設備內存之間拷貝(GPU → GPU) |
cudaMemcpyHostToHost | 主機內存之間拷貝(等價于 memcpy ) |
cudaMemcpy
是 同步操作,它會阻塞主機線程直到拷貝完成。這保證了數據安全,但也意味著它會造成 CPU 等待。
?? 如果你希望異步傳輸,需要使用
cudaMemcpyAsync
并配合 CUDA 流(streams)。
釋放顯存的函數 —— cudaFree
。
cudaError_t cudaFree(void* devPtr);
?在 GPU 上釋放之前通過 cudaMalloc
分配的內存。
-
devPtr
:需要釋放的設備指針(即之前通過cudaMalloc
得到的 GPU 地址)。 -
返回值:返回一個
cudaError_t
類型的錯誤碼(cudaSuccess
表示成功)。
就像在 CPU 上使用 malloc
后要調用 free
,在 GPU 上使用 cudaMalloc
分配內存后也必須調用 cudaFree
來釋放顯存:
-
否則會造成 顯存泄漏;
-
長時間運行或循環分配會 耗盡 GPU 顯存;
-
導致 CUDA 程序崩潰或性能嚴重下降。
核函數的參數規則
一、核函數參數的基本要求
參數必須是可以被復制(copiable)的數據類型
-
標量類型(如
int
,float
,double
,char
) -
指針類型(如
int*
,float*
) -
結構體或類(必須是 trivially copyable)
不支持的:
-
引用類型(如
int&
) ? -
虛函數、繼承的類等復雜對象 ?
二、參數傳遞方式:值傳遞(by value)
CUDA 核函數的參數都是 按值傳遞,即參數會從主機拷貝一份副本傳遞給設備。
-
標量變量:直接復制一份到 GPU。
-
指針變量:復制的是主機這邊的指針值(指向 GPU 內存的地址)。
三、指針指向的內存必須是設備內存
你傳給內核的指針必須指向顯存(設備內存),否則會導致錯誤或非法訪問:
反例:
int c;
add<<<1, 1>>>(2, 3, &c); // ? 錯誤!主機地址傳入設備代碼,非法訪問
四、核函數參數個數限制
CUDA 對核函數的參數總字節數有限制(包括傳入的所有變量)。
-
通常限制為 最多 256 字節(不同架構可能有差異);
-
如果你傳入很多參數(如結構體),建議:
-
封裝為一個結構體;
-
或使用顯存中數據結構代替參數(通過指針傳入)。
-
-
不能使用可變參數
五、結構體作為參數的限制
你可以將結構體作為核函數參數傳入,但有幾點要注意:
-
結構體必須是簡單結構體(POD 類型,不能有構造函數、虛函數、繼承);
-
會被按值拷貝到設備上;
-
如果結構體內部有指針,那些指針必須指向設備內存。
六、核函數參數必須可在 host 代碼中準備好
由于核函數只能從 host 調用,所有參數必須能在 host 端構造并傳入(不能傳 GPU 上運行時才能生成的數據結構,比如 GPU 上的臨時指針等)。
查詢設備
cudaGetDeviceCount
cudaError_t cudaGetDeviceCount(int* count);
獲取當前系統中 可用的 CUDA 設備數量(即 GPU 的個數)。
參數:
-
count
: 一個指針,用來存放返回的設備數量。
返回值:
-
cudaSuccess
表示成功; -
否則返回錯誤碼(如沒有安裝驅動、無 GPU 等)。
cudaGetDeviceProperties
cudaError_t cudaGetDeviceProperties(cudaDeviceProp* prop, int device);
功能:
獲取指定編號 GPU 的詳細屬性(例如顯存大小、線程數量、SM 架構等)。
參數:
-
prop
: 指向cudaDeviceProp
結構體的指針,用于接收設備信息; -
device
: 設備編號,范圍是[0, count - 1]
。
struct?cudaDeviceProp
struct cudaDeviceProp {char name[256]; // GPU 名稱字符串size_t totalGlobalMem; // 全局顯存總大小(單位:字節)size_t sharedMemPerBlock; // 每個線程塊可用的共享內存大小int regsPerBlock; // 每個線程塊可用的寄存器數量int warpSize; // 一個 warp 中的線程數量(通常為32)size_t memPitch; // 最大內存復制寬度(以字節為單位)int maxThreadsPerBlock; // 每個線程塊支持的最大線程數量int maxThreadsDim[3]; // 每個線程塊在 x, y, z 三維的最大線程數int maxGridSize[3]; // 每個網格在 x, y, z 三維的最大塊數int clockRate; // 時鐘頻率(kHz)size_t totalConstMem; // 常量內存總大小(字節)int major; // 計算能力主版本號int minor; // 計算能力次版本號size_t textureAlignment; // 紋理對齊要求(字節)size_t texturePitchAlignment; // 對二維紋理中行對齊的要求(字節)int deviceOverlap; // 是否支持設備與主機的重疊執行(1 是,0 否)int multiProcessorCount; // SM(流式多處理器)數量int kernelExecTimeoutEnabled; // 是否啟用了內核執行超時(1 是,0 否)int integrated; // 是否為集成 GPU(1 是,0 否)int canMapHostMemory; // 是否支持映射主機內存到設備地址空間int computeMode; // 計算模式(0: 默認,1: 僅主機訪問,2: 禁止訪問)int maxTexture1D; // 最大 1D 紋理尺寸int maxTexture1DMipmap; // 最大 1D Mipmap 紋理尺寸int maxTexture1DLinear; // 最大 1D 線性紋理尺寸(僅支持一維紋理)int maxTexture2D[2]; // 最大 2D 紋理尺寸(width, height)int maxTexture2DMipmap[2]; // 最大 2D Mipmap 尺寸int maxTexture2DLinear[3]; // 最大 2D 線性紋理尺寸(width, height, pitch)int maxTexture2DGather[2]; // 最大 2D Gather 紋理尺寸int maxTexture3D[3]; // 最大 3D 紋理尺寸(width, height, depth)int maxTexture3DAlt[3]; // 替代的最大 3D 紋理尺寸int maxTextureCubemap; // 最大立方體紋理尺寸int maxTexture1DLayered[2]; // 最大 1D 分層紋理尺寸(width, layers)int maxTexture2DLayered[3]; // 最大 2D 分層紋理尺寸(width, height, layers)int maxTextureCubemapLayered[2]; // 最大立方體分層紋理尺寸(width, layers)int maxSurface1D; // 最大 1D surface 尺寸int maxSurface2D[2]; // 最大 2D surface 尺寸int maxSurface3D[3]; // 最大 3D surface 尺寸int maxSurface1DLayered[2]; // 最大 1D 分層 surface 尺寸int maxSurface2DLayered[3]; // 最大 2D 分層 surface 尺寸int maxSurfaceCubemap; // 最大立方體 surface 尺寸int maxSurfaceCubemapLayered[2]; // 最大立方體分層 surface 尺寸size_t surfaceAlignment; // surface 對齊要求(字節)int concurrentKernels; // 是否支持多個 kernel 并發執行int ECCEnabled; // 是否啟用 ECC(錯誤檢查與糾正)int pciBusID; // PCI 總線 IDint pciDeviceID; // PCI 設備 IDint pciDomainID; // PCI 域 IDint tccDriver; // 是否為 TCC 驅動(用于專業顯卡如 Tesla)int asyncEngineCount; // 同時支持異步傳輸與執行的引擎數量int unifiedAddressing; // 是否支持統一虛擬地址空間(UVA)int memoryClockRate; // 顯存時鐘頻率(kHz)int memoryBusWidth; // 顯存總線寬度(位)int l2CacheSize; // L2 緩存大小(字節)int maxThreadsPerMultiProcessor; // 每個 SM 支持的最大線程數int streamPrioritiesSupported; // 是否支持流優先級int globalL1CacheSupported; // 是否支持全局 L1 緩存int localL1CacheSupported; // 是否支持本地 L1 緩存size_t sharedMemPerMultiprocessor; // 每個 SM 可用的共享內存大小(字節)int regsPerMultiprocessor; // 每個 SM 可用的寄存器數量int managedMemory; // 是否支持托管內存int isMultiGpuBoard; // 是否為多 GPU 板卡的一部分int multiGpuBoardGroupID; // 多 GPU 板卡上的組 ID
};
配合使用,獲取設備屬性
#include <iostream>
#include <cuda_runtime.h>int main() {int deviceCount = 0;cudaError_t err = cudaGetDeviceCount(&deviceCount);if (err != cudaSuccess) {std::cerr << "cudaGetDeviceCount failed: " << cudaGetErrorString(err) << std::endl;return -1;}std::cout << "Found " << deviceCount << " CUDA device(s).\n";for (int i = 0; i < deviceCount; ++i) {cudaDeviceProp prop;cudaGetDeviceProperties(&prop, i);std::cout << "\nDevice " << i << ": " << prop.name << "\n";std::cout << " Total Global Memory: " << (prop.totalGlobalMem >> 20) << " MB\n";std::cout << " Compute Capability: " << prop.major << "." << prop.minor << "\n";std::cout << " Multiprocessors: " << prop.multiProcessorCount << "\n";std::cout << " Max Threads Per Block: " << prop.maxThreadsPerBlock << "\n";std::cout << " Max Threads Dim: ("<< prop.maxThreadsDim[0] << ", "<< prop.maxThreadsDim[1] << ", "<< prop.maxThreadsDim[2] << ")\n";std::cout << " Max Grid Size: ("<< prop.maxGridSize[0] << ", "<< prop.maxGridSize[1] << ", "<< prop.maxGridSize[2] << ")\n";}return 0;
}
cudaGetDevice
cudaGetDevice
是 CUDA 運行時 API 中的一個函數,用來獲取當前線程所使用的 CUDA 設備編號(Device ID)。它的常見用途是:
-
查詢當前使用的是哪一個 GPU。
-
和
cudaSetDevice(int device)
搭配使用,切換或記錄設備上下文。
cudaError_t cudaGetDevice(int* device);
參數說明:
-
int* device
:一個指向整數的指針,函數會把當前設備的 ID(從 0 開始)寫入這里。
配合使用:cudaSetDevice
cudaSetDevice(1); // 綁定設備1
cudaGetDevice(&id); // 確認當前設備 id 是 1
cudaChooseDevice
cudaChooseDevice
是 CUDA Runtime API 中的一個函數,它的作用是:根據你指定的一些性能偏好,選擇最適合的 CUDA 設備(GPU)并返回設備編號(ID)。
cudaError_t cudaChooseDevice(int* device, const cudaDeviceProp* prop);
參數說明:
-
int* device
:返回選擇的設備編號。 -
const cudaDeviceProp* prop
:你的“理想設備”配置(可以只設置關鍵字段)。
使用方式:
cudaDeviceProp desiredProp = {};desiredProp.major = 7; // 至少計算能力為 7.x(如 Volta, Turing, Ampere)desiredProp.totalGlobalMem = 4L * 1024 * 1024 * 1024; // 至少 4GB 顯存int chosenDevice = -1;cudaError_t err = cudaChooseDevice(&chosenDevice, &desiredProp);