CUDA編程筆記(1)--最簡單的核函數

本文用來記錄cuda編程的一些筆記以及知識

本筆記運行在windows系統,vs編譯器中,cuda版本是12.6

先看一下最基本的代碼例子:

#include<iostream>
#include<cstdio>
#include "cuda_runtime.h"
#include "device_launch_parameters.h"//它聲明了你在 CUDA kernel 函數中會使用的一些 內置變量
__global__ void kernel() {printf("hello world");
}
int main() {kernel <<<1, 1 >>> ();cudaError_t err = cudaDeviceSynchronize();if (err != cudaSuccess) {std::cerr << "CUDA Error: " << cudaGetErrorString(err) << std::endl;}return 0;
}

下面我會介紹所有出現的東西,以讓大家對cuda編程有一個最基本的理解

#include "cuda_runtime.h"

? 提供 CUDA 運行時 API(Runtime API)的聲明

該頭文件包含了調用 CUDA 核函數所需的所有基本功能聲明,例如:

🚀 設備管理類函數

  • cudaGetDevice()

  • cudaSetDevice()

  • cudaGetDeviceCount()

🧠 內存管理函數

  • cudaMalloc():在 GPU 上分配內存

  • cudaFree():釋放 GPU 上的內存

  • cudaMemcpy():主機和設備之間的數據拷貝

🧮 執行控制函數

  • cudaDeviceSynchronize():等待所有 GPU 上的任務完成

  • cudaDeviceReset():復位設備(常用于程序退出前)

  • cudaGetLastError():獲取上一次 CUDA 操作的錯誤


? 支持 kernel 函數的 <<<>>> 調用語法

沒有這個頭文件,編譯器就不會正確識別:

kernel<<<numBlocks, threadsPerBlock>>>(...);

這種語法就是 CUDA 的核函數啟動方式,必須在包含 cuda_runtime.h 后才有效。


? 加載所有 device API 所需的類型和結構體

比如:

  • cudaError_t:CUDA 錯誤碼的類型

  • cudaMemcpyKind:用于標識拷貝方向(如 cudaMemcpyHostToDevice

?#include "device_launch_parameters.h"

? 提供 CUDA 核函數啟動時需要的一些內置變量聲明

這個頭文件聲明了你在 CUDA 核函數(__global__ 函數)中常用的以下 線程索引相關變量

變量名說明
threadIdx當前線程在 block 中的索引(例如 threadIdx.x
blockIdx當前 block 在 grid 中的索引
blockDim當前 block 中線程的維度
gridDim當前 grid 的 block 數量

這些變量雖然在 CUDA 編譯器中是“內置”的,但如果沒有包含適當的頭文件,有些編譯器(或靜態分析工具)會報聲明缺失的警告。這個頭文件就是為了避免此類問題

? 為什么要顯式 include 它?

雖然在大多數情況下 #include <cuda_runtime.h> 已經間接包含了它,但顯式寫出來有幾個好處:

  1. 可讀性更好:清晰知道 kernel 中用到哪些內置變量;

  2. 避免 IDE 報錯:有些開發工具(如 Visual Studio)如果沒有包含這個頭文件,在編輯器中會提示這些變量未聲明;

  3. 提高代碼可移植性:一些平臺或舊版本 CUDA SDK 不自動包含它。

核函數

? 一、核函數的基本語法

🔹 定義核函數

__global__ void kernel_name(parameter_list) { // GPU 上執行的代碼 }
  • __global__:CUDA 的函數修飾符,表示該函數:

    • 在 GPU 上執行

    • 由 CPU 主機代碼調用

  • kernel_name:核函數的名稱。

  • parameter_list:參數列表,可以傳遞普通數據指針(如設備內存地址)或標量值。


🔹 調用核函數(使用 <<< >>> 執行配置)

kernel_name<<<numBlocks, threadsPerBlock>>>(parameter_values);
  • numBlocks:grid 中的 block 數量(可以是一維、二維或三維)。

  • threadsPerBlock:每個 block 中的線程數量(同樣支持一維、二維或三維)。

  • parameter_values:傳遞給核函數的參數。

? 示例(最常見的一維):
__global__ void addKernel(int* data) { 
int idx = blockIdx.x * blockDim.x + threadIdx.x; data[idx] += 1; 
} 
int main() {addKernel<<<10, 256>>>(device_ptr); // 共啟動 2560 個線程 
}

? 二、核函數中常用的內置變量

變量名類型說明
threadIdxdim3當前線程在其所屬 block 中的索引
blockIdxdim3當前 block 在 grid 中的索引
blockDimdim3當前 block 中線程的數量
gridDimdim3當前 grid 中 block 的數量

注意:這些變量都是結構體 dim3 類型,支持 .x, .y, .z 維度訪問。


? 示例:全局索引計算

int globalIndex = blockIdx.x * blockDim.x + threadIdx.x;

🚧 三、核函數的注意事項

1. 核函數只能由 CPU 調用,不能被設備端其他函數調用

  • 核函數必須使用 __global__ 修飾;

  • 不能在 GPU 設備端用另一個核函數調用它;

  • 如果你想在設備端調用,可以使用 __device____host__ __device__ 修飾的函數。


2. 核函數返回類型必須是 void

__global__ void foo() {}// ? 合法 
__global__ int foo() { return 1; } ///? 不合法

3. 不能使用 std::cout,只能使用 printf()

  • 核函數中不支持 C++ 的標準流輸出。

  • 使用 #include <cstdio>printf() 進行調試。


4. 核函數中的數組必須放在共享內存或全局內存中

__global__ void kernel() { 
int arr[10]; // ? 合法,小數組在寄存器中 // 大數組建議用 `__shared__` 或 `cudaMalloc`}

5. 核函數必須同步(如 cudaDeviceSynchronize()

  • 如果主機代碼緊接著要訪問 GPU 數據,必須使用:

cudaDeviceSynchronize(); 

6. 線程索引越界要自己判斷!

  • GPU 不會自動防止訪問越界;

  • 通常你要在核函數開頭寫判斷邏輯:


7. 設備函數(__device__)可以在核函數中調用

__device__ int square(int x) { return x * x; }__global__ void kernel(int* out) {int idx = threadIdx.x;out[idx] = square(idx);
}

? 總結:核函數語法關鍵點

元素用法示例
定義方式__global__ void kernel(...)
啟動語法kernel<<<blocks, threads>>>(...)
內置索引變量blockIdx, threadIdx, blockDim, gridDim
索引計算int idx = blockIdx.x * blockDim.x + threadIdx.x
輸出方式printf()
同步函數cudaDeviceSynchronize()

grid,block,thread

? 一、三者之間的關系

Grid(網格)
└── 多個 Block(線程塊)
? ? ?└── 多個 Thread(線程)

  • Grid:一次 kernel 啟動時生成的所有線程塊的集合。

  • Block:由多個線程組成的執行單元,GPU 以 block 為基本調度單位。

  • Thread:最基本的并行執行單元。


? 二、它們的作用與結構

1. Thread(線程)

  • 執行最小單位,每個線程有自己的局部變量。

  • 每個線程可以根據自己的 ID 來處理不同的數據元素。

CUDA 中線程 ID:

threadIdx.x, threadIdx.y, threadIdx.z


2. Block(線程塊)

  • 一個線程塊內的線程可以:

    • 使用共享內存(__shared__)進行數據共享

    • 通過 __syncthreads() 進行同步

  • 每個線程塊最多可有 1024 個線程(受 GPU 限制)

CUDA 中 block ID:

blockIdx.x, blockIdx.y, blockIdx.z

CUDA 中 block 內線程維度:

blockDim.x, blockDim.y, blockDim.z


3. Grid(網格)

  • Grid 是所有 Block 的集合。

  • 所有的 Block 并行執行。

Grid 維度:

gridDim.x, gridDim.y, gridDim.z


? 三、線程索引計算:獲取線程全局 ID

一般我們會用下面公式獲取線程在整個 Grid 中的全局 ID(以一維為例):

int global_id = blockIdx.x * blockDim.x + threadIdx.x;

二維情形:

int row = blockIdx.y * blockDim.y + threadIdx.y;

int col = blockIdx.x * blockDim.x + threadIdx.x;


? 四、維度示意圖(以 2D 舉例)

GridDim (2x2)
┌────────────┬────────────┐
│ Block(0,0) │ Block(1,0) │
│ Threads ? ?│ Threads ? ?│
└────────────┴────────────┘
┌────────────┬────────────┐
│ Block(0,1) │ Block(1,1) │
│ Threads ? ?│ Threads ? ?│
└────────────┴────────────┘

每個 Block 里還有:

Threads (e.g., 16x16)
┌──────┐
│ t0 ? │
│ t1 ? │
│ ... ?│
└──────┘

? 五、核函數調用中的 <<<>>> 配置

kernel<<<gridDim, blockDim>>>(...);

  • gridDim:Grid 的尺寸(可以是 intdim3

  • blockDim:每個 Block 中線程的數量

例如:

dim3 grid(4, 4); // 4x4 個 Block

dim3 block(8, 8); // 每個 Block 8x8 個線程(共 64 個線程)

kernel<<<grid, block>>>(...);

總線程數:4×4×8×8 = 1024


? 六、一些常見注意事項

注意點說明
每個 Block 中線程總數限制通常為 1024(可查詢設備屬性)
線程越界處理你必須手動判斷是否越界,否則可能非法訪問內存
使用共享內存只能在線程塊內部共享,線程間需同步
不同 Block 不共享變量Block 之間不能通信(除非使用 global memory)
線程的層級結構是固定的Grid → Block → Thread 是一層層嵌套的


? 七、小結表格

層級名稱定義方式標識符(內置變量)
1Grid<<<gridDim, ...>>>gridDim, blockIdx
2Block<<<..., blockDim>>>blockDim, threadIdx
3Thread在核函數中自動生成threadIdx

?在示例中,<<<1,1>>>表示我們只啟動了一個線程,因此hello world只會輸出一次;我們將代碼改為如下:

#include<iostream>
#include<cstdio>
#include "cuda_runtime.h"
#include "device_launch_parameters.h"//它聲明了你在 CUDA kernel 函數中會使用的一些 內置變量
__global__ void kernel() {/*int current_thread_id = blockIdx.x * blockDim.x + threadIdx.x;printf("current thread id is :%d\n", current_thread_id);*/printf("hello world\n");
}
int main() {kernel <<<4, 8 >>> ();cudaError_t err = cudaDeviceSynchronize();if (err != cudaSuccess) {std::cerr << "CUDA Error: " << cudaGetErrorString(err) << std::endl;}return 0;
}

grid中有4個block,每個block擁有8個thread,所以總共有32個線程,也就會輸出32個hello world。

計算線程的唯一id

線程在線性空間中的全局編號(global thread id)

int global_id =threadIdx.x+ threadIdx.y * blockDim.x+ threadIdx.z * blockDim.x * blockDim.y+ blockIdx.x * blockDim.x * blockDim.y * blockDim.z+ blockIdx.y * gridDim.x * blockDim.x * blockDim.y * blockDim.z+ blockIdx.z * gridDim.x * gridDim.y * blockDim.x * blockDim.y * blockDim.z;

分解解釋:

線程在線程塊內的編號(local thread offset):

local_id = threadIdx.x+ threadIdx.y * blockDim.x+ threadIdx.z * blockDim.x * blockDim.y;

線程塊的編號(block offset):?

block_id = blockIdx.x+ blockIdx.y * gridDim.x+ blockIdx.z * gridDim.x * gridDim.y;

每個 Block 中的線程數量:

threads_per_block = blockDim.x * blockDim.y * blockDim.z;

?所以也可以寫成:

global_id = block_id * threads_per_block + local_id;

如果你處理的是三維數組索引

除了線性 global_id,你還可以直接求出每個線程在全局的 (x, y, z) 坐標:

int x = blockIdx.x * blockDim.x + threadIdx.x;
int y = blockIdx.y * blockDim.y + threadIdx.y;
int z = blockIdx.z * blockDim.z + threadIdx.z;

類比:想象一組三維數組任務

比如你現在有個數據張量:float data[64][64][64],你希望:

  • 每個線程負責處理這個數組的一個元素,比如 data[z][y][x]

  • CUDA 會把這些任務分給多個線程,你就用上面那行代碼求出:每個線程該處理哪個 x,y,z 的位置。

具體例子

假設你啟動 CUDA kernel去處理一個[8][8][8]的數組(當然也可以是一維的)

kernel<<<dim3(2,2,2), dim3(4,4,4)>>>();

這表示:

  • Grid 有 2x2x2=8 個 Block

  • 每個 Block 有 4x4x4=64 個線程

  • 所以總線程數 = 8 × 64 = 512

那么:

  • 第一個 Block(blockIdx=(0,0,0))內的線程 threadIdx=(0,0,0) 處理的是 (x=0, y=0, z=0)

  • 第二個 Block(blockIdx=(1,0,0))內的 threadIdx=(0,0,0) 處理的是 (x=4, y=0, z=0)(x 方向偏移了一個 Block)

  • 第三個 Block(blockIdx=(0,1,0))內的 threadIdx=(0,0,0) 處理的是 (x=0, y=4, z=0)(y 方向偏移了一個 Block)

  • 第四個 Block(blockIdx=(1,1,0))內的 threadIdx=(1,2,3) 處理的是 (x=5, y=6, z=3)

cudaDeviceSynchronize的作用

cudaDeviceSynchronize() 是 CUDA 編程中的一個重要函數,它的主要作用是:

阻塞當前 CPU 線程,直到 GPU 上之前所有的任務全部完成。

詳細來說:

  • CUDA 的操作(如內核函數啟動、內存拷貝等)是異步執行的,調用它們時 CPU 不會等待 GPU 完成任務就直接繼續執行后面的代碼。

  • cudaDeviceSynchronize() 會讓 CPU 阻塞,直到 GPU 上所有之前發起的任務(比如內核執行、數據傳輸等)都執行完畢,確保 GPU 任務完成后再繼續執行 CPU 代碼。

  • 它常用于調試、性能測量或者需要確保 GPU 計算結果已經完成才能進行下一步處理的場景。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/82350.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/82350.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/82350.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

系統架構中的限流實踐:構建多層防護體系(二)

系統架構中的限流實踐:構建多層防護體系 一、接入層限流:流量攔截第一關二、應用層限流(服務內限流)Java生態方案對比三、分布式限流(跨服務限流)四、數據層限流(數據庫/緩存限流)1. 數據庫防護策略2. 緩存優化方案五、中間件層限流(消息隊列/分布式服務)六、客戶端限…

AI學習筆記二十八:使用ESP32 CAM和YOLOV5實現目標檢測

若該文為原創文章&#xff0c;轉載請注明原文出處。 最近在研究使用APP如何顯示ESP32 CAM的攝像頭數據&#xff0c;看到有人實現把ESP32 CAM的數據流上傳&#xff0c;通過YOLOV5來檢測&#xff0c;實現拉流推理&#xff0c;這里復現一下。 一、環境 arduino配置esp32-cam開發環…

uni-app(5):Vue3語法基礎上

Vue (讀音 /vju?/&#xff0c;類似于 view) 是一套用于構建用戶界面的漸進式框架。與其它大型框架不同的是&#xff0c;Vue 被設計為可以自底向上逐層應用。Vue.js 的核心是一個允許采用簡潔的模板語法來聲明式地將數據渲染進 DOM 的系統&#xff0c;只關注視圖層&#xff0c;…

JAVA:Kafka 存儲接口詳解與實踐樣例

?? 1、簡述 Kafka 以其高吞吐、可擴展和高可靠性著稱,其強大性能的背后核心在于其高效的存儲設計。Kafka 不是傳統意義上的隊列,而是一個分布式日志系統,其存儲模塊是核心組成部分。 本文將深入剖析 Kafka 的存儲接口實現機制,并結合 Java 示例進行模擬驗證。 ?? 2、…

Docker 使用鏡像[SpringBoot之Docker實戰系列] - 第537篇

歷史文章&#xff08;文章累計530&#xff09; 《國內最全的Spring Boot系列之一》 《國內最全的Spring Boot系列之二》 《國內最全的Spring Boot系列之三》 《國內最全的Spring Boot系列之四》 《國內最全的Spring Boot系列之五》 《國內最全的Spring Boot系列之六》 《…

數據庫入門教程:以商品訂單系統為例

數據庫入門教程&#xff1a;以商品訂單系統為例 一、前言 數據庫是現代軟件開發中不可或缺的基礎&#xff0c;掌握數據庫的基本概念和操作&#xff0c;是每個開發者的必經之路。本文將以“商品-品牌-客戶-訂單-訂單項”為例&#xff0c;帶你快速入門數據庫的核心知識和基本操…

GeoServer樣式設置:使用本地圖標及分層/分視野顯示

GeoServer樣式設置:使用本地圖標及分層/分視野顯示 1、本地圖標生效設置2、GeoServer添加不同視野的圖標點樣式1)服務預覽效果2)本地圖標引用3)不同視野顯示不同圖標4)標注注記顯示空間的點數據,使用圖標來表示是非常常見的業務需求,而且由于在不同比例尺下,可能需要設…

DL00347-基于人工智能YOLOv11的安檢X光危險品刀具檢測含數據集

&#x1f6a8; AI技術革新&#xff0c;提升安檢效率與安全性&#xff01;YOLOv11助力X光危險品刀具檢測&#xff01; &#x1f4a1; 在安全領域&#xff0c;效率與精準度的要求從未如此迫切。作為科研人員&#xff0c;是否一直在尋找一款可以提升安檢準確率、減少人工干預、提…

測試計劃與用例撰寫指南

測試計劃與用例撰寫指南 一、測試計劃&#xff1a;項目測試的 “導航地圖”1.1 測試計劃的核心目標 1.2 測試計劃的關鍵要素 1.2.1 項目概述 1.2.2 測試策略 1.2.3 資源與進度 1.2.4 風險評估與應對 二、測試用例&#xff1a;測試執行的 “行動指南”2.1 測試用例的設計原則 2…

微服務的應用案例

從“菜市場”到“智慧超市”&#xff1a;一場微服務的變革之旅 曾經&#xff0c;我們的系統像一個熙熙攘攘的傳統菜市場。所有功能模塊&#xff08;攤販&#xff09;都擠在一個巨大的單體應用中。用戶請求&#xff08;買菜的顧客&#xff09;一多&#xff0c;整個市場就擁堵不堪…

Java設計模式之觀察者模式:從基礎到高級的全面解析

文章目錄 一、觀察者模式基礎概念1.1 什么是觀察者模式?1.2 觀察者模式的四大角色1.3 觀察者模式類圖二、觀察者模式實現步驟2.1 基礎實現步驟2.2 詳細代碼實現第一步:定義主題接口第二步:定義觀察者接口第三步:創建具體主題第四步:創建具體觀察者第五步:客戶端使用三、觀…

GATT 服務的核心函數bt_gatt_discover的介紹

目錄 概述 1 GATT 基本概念 1.1 GATT 的介紹 1.2 GATT 的角色 1.3 核心組件 1.4 客戶端操作 2 bt_gatt_discover函數的功能和應用 2.1 函數介紹 2.1 發現類型&#xff08;Discover Type&#xff09; 3 典型使用流程 3.1 服務發現示例 3.2 級聯發現模式 3.3 按UUID過…

【更新至2023年】1985-2023年全國及各省就業人數數據(無缺失)

1985-2023年全國及各省就業人數數據&#xff08;無缺失&#xff09; 1、時間&#xff1a;1985-2023年 2、來源&#xff1a;Z國統計年鑒、各省年鑒、新中國60年 3、指標&#xff1a;就業人數 4、范圍&#xff1a;全國及31省 5、缺失情況&#xff1a;無缺失 6、指標解釋&am…

0基礎學習Linux之揭開朦朧一面:環境基礎開發工具

目錄 Linux下安裝軟件的方案&#xff1a; 對于操作系統的理解&#xff1a; 操作系統的生態問題&#xff1a; 什么是好的操作系統&#xff08;os&#xff09;&#xff1a; 重新理解centos VS ubnutu VS kail&#xff1a; 關于yum: 用 yum 安裝軟件(安裝和卸載軟件一定要有r…

YOLO 算法詳解:實時目標檢測的里程碑

在計算機視覺領域&#xff0c;目標檢測一直是一個關鍵且熱門的研究方向&#xff0c;而 YOLO&#xff08;You Only Look Once&#xff09;算法憑借其出色的實時性和較高的檢測精度&#xff0c;成為了目標檢測算法中的明星選手。本文將深入探討 YOLO 算法的原理、發展歷程、技術優…

leetcode98.驗證二叉搜索樹:遞歸法中序遍歷的遞增性驗證之道

一、題目深度解析與BST核心性質 題目描述 驗證二叉搜索樹&#xff08;BST&#xff09;是算法中的經典問題&#xff0c;要求判斷給定的二叉樹是否滿足BST的定義&#xff1a; 左子樹中所有節點的值嚴格小于根節點的值右子樹中所有節點的值嚴格大于根節點的值左右子樹本身也必須…

MathQ-Verify:數學問題驗證的五步流水線,為大模型推理筑牢數據基石

MathQ-Verify&#xff1a;數學問題驗證的五步流水線&#xff0c;為大模型推理筑牢數據基石 大語言模型在數學推理領域進展顯著&#xff0c;但現有研究多聚焦于生成正確推理路徑和答案&#xff0c;卻忽視了數學問題本身的有效性。MathQ-Verify&#xff0c;通過五階段流水線嚴格…

八股戰神-JVM知識速查

1.JVM組成 JVM由那些部分組成&#xff0c;運行流程是什么&#xff1f; JVM是Java程序的運行環境 組成部分&#xff1a; 類加載器&#xff1a;加載字節碼文件到內存 運行時數據區&#xff1a;包括方法區&#xff0c;堆&#xff0c;棧&#xff0c;程序計數器&#xff0c;本地…

Maven:在原了解基礎上對pom.xml文件進行詳細解讀

一、pom.xml文件 就像項目管理軟件 Make 的 MakeFile、Ant 的 build.xml 一樣&#xff0c;Maven 項目的核心是 pom.xml。POM( Project Object Model&#xff0c;項目對象模型 ) 定義了項目的基本信息&#xff0c;用于描述項目如何構建&#xff0c;聲明項目依賴&#xff0c;等等…

Spring Cloud項目登錄認證從JWT切換到Redis + UUID Token方案

背景介紹 在傳統的Spring Boot項目中&#xff0c;用戶登錄認證常見的方案是使用JWT&#xff08;JSON Web Token&#xff09;來實現無狀態的身份驗證。JWT憑借自包含用戶信息、方便前后端分離、性能較好等優勢被廣泛采用。 然而&#xff0c;在實際項目中&#xff0c;JWT也有一…