模型部署——cuda編程入門

CUDA中的線程與線程束

在這里插入圖片描述

  • kernel是在device上線程中并行執行的函數,核函數用__global__符號聲明,在調用時需要用<<<grid_size, block_size>>>來指定kernel要執行的線程數量。在CUDA中,每一個線程都要執行核函數,并且每個線程會分配一個唯一的線程號thread ID,這個ID值可以通過核函數的內置變量threadIdx來獲得。
  • synchronize是同步的意思,有幾種synchronize
    cudaDeviceSynchronize: CPU與GPU端完成同步,CPU不執行之后的語句,直到這個語句以前的所有cuda操作結束
    cudaStreamSynchronize: 跟cudaDeviceSynchronize很像,但是這個是針對某一個stream的。只同步指定的stream中的cpu/gpu操作,其他的不管
    cudaThreadSynchronize: 現在已經不被推薦使用的方法
    __syncthreads: 線程塊內同步
  • 核函數編寫和調用舉例
#include <cuda_runtime.h>
#include <stdio.h>// 核函數
__global__ void print_idx_kernel(){printf("block idx: (%3d, %3d, %3d), thread idx: (%3d, %3d, %3d)\n",blockIdx.z, blockIdx.y, blockIdx.x,threadIdx.z, threadIdx.y, threadIdx.x);
}void print_one_dim(){int inputSize = 8;int blockDim = 4;int gridDim = inputSize / blockDim;dim3 block(blockDim);dim3 grid(gridDim);// 核函數調用print_idx_kernel<<<grid, block>>>();cudaDeviceSynchronize();
}

.cu與.cpp的相互引用及Makefile

編譯器:gcc g++ nvcc
舉個例子:

nvcc print_index.cu -o app -I /usr/local/cuda/include

獲取編譯器選項:

g++ --help
nvcc --help

Makefile編寫(是否可以使用CMakeLists.txt?)
.cpp中不能直接調用核函數,需要在.cu中提供調用接口

使用CUDA進行MATMUL計算

host端與device端數據傳輸

在這里插入圖片描述
host端與device端數據傳輸代碼實現:

void MatmulOnDevice(float *M_host, float *N_host, float* P_host, int width, int blockSize){/* 設置矩陣大小 */int size = width * width * sizeof(float);/* 分配M, N在GPU上的空間*/float *M_device;float *N_device;cudaMalloc(&M_device, size);cudaMalloc(&N_device, size);/* 分配M, N拷貝到GPU上*/cudaMemcpy(M_device, M_host, size, cudaMemcpyHostToDevice);cudaMemcpy(N_device, N_host, size, cudaMemcpyHostToDevice);/* 分配P在GPU上的空間*/float *P_device;cudaMalloc(&P_device, size);/* 調用kernel來進行matmul計算, 在這個例子中我們用的方案是:將一個矩陣切分成多個blockSize * blockSize的大小 */dim3 dimBlock(blockSize, blockSize);dim3 dimGrid(width / blockSize, width / blockSize);MatmulKernel <<<dimGrid, dimBlock>>> (M_device, N_device, P_device, width);/* 將結果從device拷貝回host*/cudaMemcpy(P_host, P_device, size, cudaMemcpyDeviceToHost);cudaDeviceSynchronize();/* Free */// free與malloc的順序是反著的cudaFree(P_device);cudaFree(N_device);cudaFree(M_device);
}

cuda core矩陣乘法核函數編寫

/* matmul的函數實現*/
__global__ void MatmulKernel(float *M_device, float *N_device, float *P_device, int width){/* 我們設定每一個thread負責P中的一個坐標的matmul所以一共有width * width個thread并行處理P的計算*/// 確定負責計算的結果元素的索引int y = blockIdx.y * blockDim.y + threadIdx.y;int x = blockIdx.x * blockDim.x + threadIdx.x;float P_element = 0;/* 對于每一個P的元素,我們只需要循環遍歷width次M和N中的元素就可以了*/for (int k = 0; k < width; k ++){float M_element = M_device[y * width + k];float N_element = N_device[k * width + x];P_element += M_element * N_element;}P_device[y * width + x] = P_element;
}

cuda core 每個線程執行核函數計算一個結果元素
GPU剛開始執行核函數的時候,會存在一個warmup階段,耗時會比較長
CPU與GPU的浮點運算會存在誤差,誤差控制在e-4以內是ok的
CUDA中規定,一個block中可以分配的thread的數量最大是1024個線程,如果大于1024會顯示配置錯誤
為什么block size = 1的時候比等于16的時候慢很多?

cuda中的error handler

在這里插入圖片描述

獲取GPU的硬件信息

利用cuda runtime api打印硬件信息 & LOG
#include <stdio.h>
#include <cuda_runtime.h>
#include <string>#include "utils.hpp"int main(){int count;int index = 0;cudaGetDeviceCount(&count);while (index < count) {cudaSetDevice(index);cudaDeviceProp prop;cudaGetDeviceProperties(&prop, index);LOG("%-40s",             "*********************Architecture related**********************");LOG("%-40s%d%s",         "Device id: ",                   index, "");LOG("%-40s%s%s",         "Device name: ",                 prop.name, "");LOG("%-40s%.1f%s",       "Device compute capability: ",   prop.major + (float)prop.minor / 10, "");LOG("%-40s%.2f%s",       "GPU global meory size: ",       (float)prop.totalGlobalMem / (1<<30), "GB");LOG("%-40s%.2f%s",       "L2 cache size: ",               (float)prop.l2CacheSize / (1<<20), "MB");LOG("%-40s%.2f%s",       "Shared memory per block: ",     (float)prop.sharedMemPerBlock / (1<<10), "KB");LOG("%-40s%.2f%s",       "Shared memory per SM: ",        (float)prop.sharedMemPerMultiprocessor / (1<<10), "KB");LOG("%-40s%.2f%s",       "Device clock rate: ",           prop.clockRate*1E-6, "GHz");LOG("%-40s%.2f%s",       "Device memory clock rate: ",    prop.memoryClockRate*1E-6, "Ghz");LOG("%-40s%d%s",         "Number of SM: ",                prop.multiProcessorCount, "");LOG("%-40s%d%s",         "Warp size: ",                   prop.warpSize, "");LOG("%-40s",             "*********************Parameter related************************");LOG("%-40s%d%s",         "Max block numbers: ",           prop.maxBlocksPerMultiProcessor, "");LOG("%-40s%d%s",         "Max threads per block: ",       prop.maxThreadsPerBlock, "");LOG("%-40s%d:%d:%d%s",   "Max block dimension size:",     prop.maxThreadsDim[0], prop.maxThreadsDim[1], prop.maxThreadsDim[2], "");LOG("%-40s%d:%d:%d%s",   "Max grid dimension size: ",     prop.maxGridSize[0], prop.maxGridSize[1], prop.maxGridSize[2], "");index ++;printf("\n");}return 0;
}
Roofline model(待補充)

Nsight system and Nsight compute

谷歌搜索下載:官網鏈接

Nsight system

參考鏈接

安裝目錄:
ls /usr/local/bin |grep nsys
nsys
nsys-ui啟動GUI界面
sudo ./nsys-ui(不加sudo會存在權限問題)

舉個例子:
配置可執行文件以及感興趣內容:
在這里插入圖片描述可視化分析:
在這里插入圖片描述詳細使用手冊:官網文檔

Nsight compute
查看可安裝版本:
sudo apt policy nsight-compute-2022.2.1
安裝:
sudo apt install nsight-compute-2022.2.1
查看安裝位置:
dpkg -L nsight-compute-2022.2.1
路徑:/opt/nvidia/nsight-compute/2022.2.1/
文件:ncu ncu-ui等啟動:
sudo ./ncu-ui

舉個例子:
基本配置:replay mode: application
在這里插入圖片描述選擇感興趣內容:
在這里插入圖片描述launch即可,第一次運行會比較慢,會重復運行很多次。
結果:
在這里插入圖片描述不知道為什么roofline model沒有正常顯示出來,需要查一查?

擴展知識

共享內存以及BANK CONFLICT

shared memory

硬件結構

SM(Streaming Multiprocessor)
在CUDA編程模型中,線程被組織成線程塊(block),多個線程塊組成一個網格(grid)。每個線程塊被分配到一個SM中執行,而SM內部的warp調度器會將線程塊中的線程分成多個warp進行執行。
當一個warp中的線程需要等待某些操作(例如內存訪問)完成時,SM可以切換到另一個warp繼續執行,從而提高計算效率。
在這里插入圖片描述

核函數編寫
#include "cuda_runtime_api.h"
#include "utils.hpp"#define BLOCKSIZE 16/* 使用shared memory把計算一個tile所需要的數據分塊存儲到訪問速度快的memory中
*/
__global__ void MatmulSharedStaticKernel(float *M_device, float *N_device, float *P_device, int width){__shared__ float M_deviceShared[BLOCKSIZE][BLOCKSIZE];__shared__ float N_deviceShared[BLOCKSIZE][BLOCKSIZE];/* 對于x和y, 根據blockID, tile大小和threadID進行索引*/int x = blockIdx.x * blockDim.x + threadIdx.x;int y = blockIdx.y * blockDim.y + threadIdx.y;float P_element = 0.0;int ty = threadIdx.y;int tx = threadIdx.x;/* 對于每一個P的元素,我們只需要循環遍歷width / tile_width 次就okay了,這里有點繞,畫圖理解一下*/for (int m = 0; m < width / BLOCKSIZE; m ++) {M_deviceShared[ty][tx] = M_device[y * width + (m * BLOCKSIZE + tx)];N_deviceShared[ty][tx] = N_device[(m * BLOCKSIZE + ty)* width + x];__syncthreads(); // 上述兩句所有thread都會執行,等待所有thread執行完成for (int k = 0; k < BLOCKSIZE; k ++) {P_element += M_deviceShared[ty][k] * N_deviceShared[k][tx];}__syncthreads();}P_device[y * width + x] = P_element;
}__global__ void MatmulSharedDynamicKernel(float *M_device, float *N_device, float *P_device, int width, int blockSize){/* 聲明動態共享變量的時候需要加extern,同時需要是一維的 注意這里有個坑, 不能夠像這樣定義: __shared__ float M_deviceShared[];__shared__ float N_deviceShared[];因為在cuda中定義動態共享變量的話,無論定義多少個他們的地址都是一樣的。所以如果想要像上面這樣使用的話,需要用兩個指針分別指向shared memory的不同位置才行*/extern __shared__ float deviceShared[];int stride = blockSize * blockSize;/* 對于x和y, 根據blockID, tile大小和threadID進行索引*/int x = blockIdx.x * blockSize + threadIdx.x;int y = blockIdx.y * blockSize + threadIdx.y;float P_element = 0.0;int ty = threadIdx.y;int tx = threadIdx.x;/* 對于每一個P的元素,我們只需要循環遍歷width / tile_width 次就okay了 */for (int m = 0; m < width / blockSize; m ++) {deviceShared[ty * blockSize + tx] = M_device[y * width + (m * blockSize + tx)];deviceShared[stride + (ty * blockSize + tx)] = N_device[(m * blockSize + ty)* width + x];__syncthreads();for (int k = 0; k < blockSize; k ++) {P_element += deviceShared[ty * blockSize + k] * deviceShared[stride + (k * blockSize + tx)];}__syncthreads();}if (y < width && x < width) {P_device[y * width + x] = P_element;}
}

動態共享內存比靜態共享內存速度慢,沒有特殊情況下,使用靜態共享內存。

cuda event進行時間測算

在這里插入圖片描述

BANK CONFLICT(存儲體沖突)

在shared memory中什么是bank?

在這里插入圖片描述

什么時候會發生bank conflict

在這里插入圖片描述

按行存儲,按列訪問的時候,會發生bank conflict:
在這里插入圖片描述

如何減緩bank conflict

在這里插入圖片描述

代碼實現
#include "cuda_runtime_api.h"
#include "utils.hpp"#define BLOCKSIZE 16/* 使用shared memory把計算一個tile所需要的數據分塊存儲到訪問速度快的memory中
*/
__global__ void MatmulSharedStaticConflictPadKernel(float *M_device, float *N_device, float *P_device, int width){/* 添加一個padding,可以防止bank conflict發生,結合圖理解一下*/__shared__ float M_deviceShared[BLOCKSIZE][BLOCKSIZE + 1];__shared__ float N_deviceShared[BLOCKSIZE][BLOCKSIZE + 1];/* 對于x和y, 根據blockID, tile大小和threadID進行索引*/int x = blockIdx.x * BLOCKSIZE + threadIdx.x;int y = blockIdx.y * BLOCKSIZE + threadIdx.y;float P_element = 0.0;int ty = threadIdx.y;int tx = threadIdx.x;/* 對于每一個P的元素,我們只需要循環遍歷width / tile_width 次就okay了,這里有點繞,畫圖理解一下*/for (int m = 0; m < width / BLOCKSIZE; m ++) {/* 這里為了實現bank conflict, 把tx與tx的順序顛倒,同時索引也改變了*/M_deviceShared[tx][ty] = M_device[x * width + (m * BLOCKSIZE + ty)];N_deviceShared[tx][ty] = M_device[(m * BLOCKSIZE + tx)* width + y];__syncthreads();for (int k = 0; k < BLOCKSIZE; k ++) {P_element += M_deviceShared[tx][k] * N_deviceShared[k][ty];}__syncthreads();}/* 列優先 */P_device[x * width + y] = P_element;
}__global__ void MatmulSharedDynamicConflictPadKernel(float *M_device, float *N_device, float *P_device, int width, int blockSize){/* 聲明動態共享變量的時候需要加extern,同時需要是一維的 注意這里有個坑, 不能夠像這樣定義: __shared__ float M_deviceShared[];__shared__ float N_deviceShared[];因為在cuda中定義動態共享變量的話,無論定義多少個他們的地址都是一樣的。所以如果想要像上面這樣使用的話,需要用兩個指針分別指向shared memory的不同位置才行*/extern __shared__ float deviceShared[];int stride = (blockSize + 1) * blockSize;/* 對于x和y, 根據blockID, tile大小和threadID進行索引*/int x = blockIdx.x * blockSize + threadIdx.x;int y = blockIdx.y * blockSize + threadIdx.y;float P_element = 0.0;int ty = threadIdx.y;int tx = threadIdx.x;/* 對于每一個P的元素,我們只需要循環遍歷width / tile_width 次就okay了 */for (int m = 0; m < width / blockSize; m ++) {/* 這里為了實現bank conflict, 把tx與tx的順序顛倒,同時索引也改變了*/deviceShared[tx * (blockSize + 1) + ty]             = M_device[x * width + (m * blockSize + ty)];deviceShared[stride + (tx * (blockSize + 1) + ty)]  = N_device[(m * blockSize + tx) * width + y];__syncthreads();for (int k = 0; k < blockSize; k ++) {P_element += deviceShared[tx * (blockSize + 1) + k] * deviceShared[stride + (k * (blockSize + 1 ) + ty)];}__syncthreads();}/* 列優先 */P_device[x * width + y] = P_element;
}

STREAM和EVENT

什么是stream

在這里插入圖片描述

參考下述鏈接,理解cuda編程的一些基礎概念:
理解CUDA中的thread,block,grid和warp
cuda stream的使用

多流編程實現

單流:
在這里插入圖片描述
多流:
在這里插入圖片描述
利用nsight systems進行分析:
在這里插入圖片描述

如何利用多流進行隱藏訪存和核函數執行延遲的調度

舉一個栗子:
在這里插入圖片描述

使用CUDA進行預處理/后處理

雙線性插值

在這里插入圖片描述

雙線性插值的cuda實現

在這里插入圖片描述

查看圖片大小:
identity xx.png
可視化圖片:
feh xx.png

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

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

相關文章

WordPress不支持中文TAG標簽出現404的解決方法

我們在后臺編輯文章時輸入中文標簽會發現出現404的情況&#xff0c;其實中文TAG標簽鏈接無法打開的原因是WordPress不支持中文的編碼。那么解決的方法也很容易&#xff0c;只要改代碼讓WordPress能支持中文的編碼形式&#xff0c;也就是UTF-8和GBK編碼即可&#xff0c;無需用到…

金融信貸公司所需的技術和風控體系及其帶來的價值

金融信貸公司的技術架構通過集成傳統大型機系統與現代數據平臺&#xff0c;能夠有效支持金融信貸業務的運作&#xff0c;同時通過大數據、ETL、報表開發、數據倉庫等技術為公司帶來更高效的數據驅動決策、精準的風控分析和更靈活的業務支持。 一、公司技術架構 數據倉庫架構&…

《AI大模型應知應會100篇》第43篇:大模型幻覺問題的識別與緩解方法

第43篇&#xff1a;大模型幻覺問題的識別與緩解方法 摘要 當AI系統自信滿滿地編造"量子計算機使用香蕉皮作為能源"這類荒謬結論時&#xff0c;我們不得不正視大模型的幻覺問題。本文通過15個真實案例解析、6種檢測算法實現和3套工業級解決方案&#xff0c;帶您掌握…

計算方法實驗五 插值多項式的求法

【實驗性質】 綜合性驗 【實驗目的】 掌握Lagrange插值算法、Newton插值算法&#xff1b;理解Newton插值算法相對于Lagrange插值算法的優點。 【實驗內容】 先用C語言自帶的系統函數sin x求出 的值&#xff0c;然后分別用Lagrange、Newton方法求出的值&#xff0c;并與用…

文獻總結:TPAMI端到端自動駕駛綜述——End-to-End Autonomous Driving: Challenges and Frontiers

端到端自動駕駛綜述 1. 文章基本信息2. 背景介紹3. 端到端自動駕駛主要使用方法3. 1 模仿學習3.2 強化學習 4. 測試基準4.1 真實世界評估4.2 在線/閉環仿真測試4.3 離線/開環測試評價 5. 端到端自動駕駛面臨的挑戰5.1 多模態輸入5.2 對視覺表征的依賴5.3 基于模型的強化學習的世…

PostgreSQL:pgAdmin 4 使用教程

pgAdmin 4 是一個用于管理和維護 PostgreSQL 數據庫的強大工具。它提供了一個圖形化界面&#xff0c;使用戶能夠輕松地連接到數據庫、創建表、運行 SQL 語句以及執行其他數據庫管理任務。 安裝和使用 安裝 pgAdmin 4 安裝 pgAdmin 4 非常簡單。下載并運行安裝程序&#xff0…

Java學習手冊:關系型數據庫基礎

一、關系型數據庫概述 關系型數據庫是一種基于關系模型的數據庫&#xff0c;它將數據組織成一個或多個表&#xff08;或稱為關系&#xff09;&#xff0c;每個表由行和列組成。每一列都有一個唯一的名字&#xff0c;稱為屬性&#xff0c;表中的每一行是一個元組&#xff0c;代…

wpf CommandParameter 傳遞MouseWheelEventArgs參數

在 WPF 中通過 CommandParameter 傳遞 MouseWheelEventArgs 參數時&#xff0c;需結合 ?事件到命令的轉換機制? 和 ?參數轉換器? 來實現。以下是具體實現方案及注意事項&#xff1a; 一、核心實現方法 1. ?使用 EventToCommand 傳遞原始事件參數? 通過 Interaction.Tr…

八大排序之選擇排序

本篇文章將帶你詳細了解八大基本排序中的選擇排序 目錄 &#xff08;一&#xff09;選擇排序的時間復雜度和空間復雜度及穩定性分析 &#xff08;二&#xff09;代碼實現 (三)輸出結果 選擇排序的基本原理是&#xff1a;每次從待排序的數組中找出最大值和最小值。具體流程是…

【算法學習】哈希表篇:哈希表的使用場景和使用方法

算法學習&#xff1a; https://blog.csdn.net/2301_80220607/category_12922080.html?spm1001.2014.3001.5482 前言&#xff1a; 在之前學習數據結構時我們就學習了哈希表的使用方法&#xff0c;這里我們主要是針對哈希表的做題方法進行講解&#xff0c;都是leetcode上的經典…

Java 中如何實現自定義類加載器,應用場景是什么?

在 Java 中&#xff0c;可以通過繼承 java.lang.ClassLoader 類來實現自定義類加載器。自定義類加載器可以控制類的加載方式&#xff0c;實現一些特殊的應用場景。 實現自定義類加載器的步驟&#xff1a; 繼承 java.lang.ClassLoader 類。 重寫 findClass(String name) 方法 …

信創開發中跨平臺開發框架的選擇與實踐指南

&#x1f9d1; 博主簡介&#xff1a;CSDN博客專家、CSDN平臺優質創作者&#xff0c;高級開發工程師&#xff0c;數學專業&#xff0c;10年以上C/C, C#, Java等多種編程語言開發經驗&#xff0c;擁有高級工程師證書&#xff1b;擅長C/C、C#等開發語言&#xff0c;熟悉Java常用開…

WebRTC 服務器之Janus架構分析

1. Webrtc三種類型通信架構 1.1 1 對 1 通信 1 對 1 通信模型設計的主要?標是盡量讓兩個終端進?直聯&#xff0c;這樣即可以節省服務器的資源&#xff0c;?可以提? ?視頻的服務質量。WebRTC ?先嘗試兩個終端之間是否可以通過 P2P 直接進?通信&#xff0c;如果?法直接…

數字化轉型進階:26頁華為數字化轉型實踐分享【附全文閱讀】

本文分享了華為數字化轉型的實踐經驗和體會。華為通過數字化變革,致力于在客戶服務、供應鏈、產品管理等方面提高效率,并把數字世界帶入每個組織,構建萬物互聯的智能世界。華為的數字化轉型愿景是成為行業標桿,通過推進數字化戰略、構建面向業務數字化轉型的IT組織陣型、堅…

Hal庫下備份寄存器

首先要確保有外部電源給VBAT供電 生成后應該會有這兩個文件&#xff08;不知道為什么生成了好幾次都沒有&#xff0c;復制工程在試一次就有了&#xff09; 可以看到stm32f407有20個備份寄存器 讀寫函數 void HAL_RTCEx_BKUPWrite(RTC_HandleTypeDef *hrtc, uint32_t Backup…

使用 Vue3 + Webpack 和 Vue3 + Vite 實現微前端架構(基于 Qiankun)

在現代前端開發中&#xff0c;微前端架構逐漸成為一種流行的解決方案&#xff0c;尤其是在大型項目中。通過微前端&#xff0c;我們可以將一個復雜的單體應用拆分為多個獨立的小型應用&#xff0c;每個子應用可以獨立開發、部署和運行&#xff0c;同時共享主應用的基礎設施。本…

【c++】【STL】list詳解

目錄 list的作用list的接口構造函數賦值運算符重載迭代器相關sizeemptyfrontbackassignpush_frontpop_frontpush_backpop_backinserteraseswapresizeclearspliceremoveremove_ifuniquemergesortreverse關系運算符重載&#xff08;非成員函數&#xff09; list的模擬實現結點類迭…

Redis持久化:

什么是Redis持久化&#xff1a; Redis 持久化是指將 Redis 內存中的數據保存到硬盤等持久化存儲介質中&#xff0c;以便在 Redis 服務器重啟或出現故障時能夠恢復數據&#xff0c;保證數據的可靠性和持續性。Redis 提供了兩種主要的持久化方式&#xff1a;RDB&#xff08;Redi…

VBA 64位API聲明語句第009講

跟我學VBA&#xff0c;我這里專注VBA, 授人以漁。我98年開始&#xff0c;從源碼接觸VBA已經20余年了&#xff0c;隨著年齡的增長&#xff0c;越來越覺得有必要把這項技能傳遞給需要這項技術的職場人員。希望職場和數據打交道的朋友&#xff0c;都來學習VBA,利用VBA,起碼可以提高…

在pycharm profession 2020.3將.py程序使用pyinstaller打包成exe

一、安裝pyinstaller 在pycharm的項目的Terminal中運行pip3 install pyinstaller即可。 安裝后在Terminal中輸入pip3 list看一下是否成功 二、務必在在項目的Terminal中輸入命令打包&#xff0c;命令如下&#xff1a; python3 -m PyInstaller --noconsole --onefile xxx.py …