寫一個 RTX 5080 上的 cuda gemm fp16

1. cpu 計算 fp16 四則運算

? ? ? ? 由于會用到cpu 的gemm 與 gpu gemm 的對比驗證,所以,這里稍微解釋一下 cpu 計算fp16 gemm 的過程。這里為了簡化理解,cpu 中不使用 avx 相關的 fp16 運算器,而是直接使用 cpu 原先的 ALU 功能。這里使用一個示例來做這件事情。

1.1. 源碼編譯運行

hello_fp16.cu


#include <stdio.h>
#include "cuda_fp16.h"int main()
{half x = half(3.333);half y = half(7.777);half z = half(0.0);z = x*y;printf("sizeof(half) = %ld x = %f \n", sizeof(x), float(z));return 0;
}

編譯運行:

nvcc -g --gpu-architecture=sm_120 hello_fp16.cu -o hello_fp16

1.2. 調試追蹤 fp16 的相關功能

? ? ? ? 這里有兩個目標:

? ? ? ? 一個是類型轉換,怎么樣得到一個 fp16 的變量值;

? ? ? ? 一個是 fp16 類型變量之間的乘法(四則運算)。

? ? ? ? 現在看一下其中的 half(3.333) 的執行,通過使用 step,經歷如下幾個斷點:

在 408 行 時,使用gdb )s 會跳到下圖代碼 549 行處:

繼續使用 (cuda-gdb) s 會跳到下圖:

? ? 然后 使用 (cuda-gdb) next ,會經歷上圖代碼的主體邏輯,也就是一些位運算的那些邏輯。

? ? 結論便是,cuda 程序對 cpu 的 half(3.333) 使用了 cpu 軟件算法模擬了這個轉換過程,將 double 類型轉換成 fp16,即 half 類型?

? ? 同時接下來會發現,兩個 half 類型的變量做乘法運算,會先將兩個 half 轉成 float,也是通過類似的軟件模擬的轉換方式,然后使用 cpu 的 float 乘法指令計算乘積,最后將 float 類型的乘積再轉回 half 類型,存入 half 類型的變量內存中。

接下來調試 half 的乘法運算符 * :

在 執行到 z = x*y; 時,使用(cuda-gdb) step,會跳進half 類型的乘法運算符 * 的實現代碼中,這里使用了 cpp 的重載功能(Operator Overloading) ,對運算符 * 做了重新實現?:

可以看到,operator * 重載時,函數體中調用了 __hmul(...) 來實現具體功能。

接下來繼續使用 (cuda-gdb) step,看看 __hmul(...) 的實現:

? ? ? ? 這里的 NV_IF_ELSE_TARGET(cond, , ) 表示可能存在兩種可能的實現方式,根絕第一個表達式的真假來選擇后邊的第二個或者第三個表達式。因為我們使用了 sm_120, 不等于 sm_53,可以初步猜測是調用了后邊的第三個表達式的內容來實現乘法。接下來通過 cuda-gdb 來單步調試驗證一下。

? ? ? ? 我們已經猜測會執行后邊三行代碼:2653,2654,2655等,但是為了驗證,這里做了個新函數 hhhaddd(),插入到第三個表達式的中間 float xfa = hhhaddd(fa); :

? ? ? ? 這會導致計算結果必然是錯誤的,但是可以給這個 hhhaddd 打斷點,然后直接 continue,果然停在了這個函數上。

? ? ? ? 說明執行了這三行代碼,即,half 的乘法,是使用 float32 的乘法指令來實現的:

    const float fa = __half2float(a);const float fb = __half2float(b);return __float2half(fa * fb);

2. 寫個 cpu gemm_fp16

? ? ? ? 矩陣小一點,方便驗證,其中的輸出格式,是為了能夠簡單地放進matlab 做對比驗證:


#include <stdio.h>
#include <stdlib.h>
#include "cuda_fp16.h"void init_matrix(half *A, int lda, int m, int n, bool colMajor)
{if(colMajor){for(int j=0; j<n; j++){for(int i=0; i<m; i++){half x = half(rand()*1.0f/RAND_MAX);A[i + j*lda] = x;printf(" %f",  float(x));}}printf("\n\n");}else{for(int i=0; i<m; i++){for(int j=0; j<n; j++){half x = half(rand()*1.0f/RAND_MAX);A[i*lda + j] = x;printf(" %f",  float(x));}}}
}void print_matrix(half *A, int lda, int m, int n, bool colMajor)
{printf("[ ...\n");for(int i=0; i<m; i++){for(int j=0; j<n; j++){if(colMajor)printf(" %5.4f, ", float(A[i + j*lda]));elseprintf(" %5.4f, ", float(A[i*lda + j]));}printf(" ; ...\n");}printf("]\n");
}void gemm_fp16_cpu(int M, int N, int K,half* A, int lda,half* B, int ldb,half* C, int ldc,half alpha, half beta)
{for(int i=0; i<M; i++){for(int j=0; j<N; j++){half sigma = half(0.0);for(int k=0; k<K; k++){sigma += A[i + k*lda]*B[k + j*lda];}C[i + j*ldc] = alpha*sigma + beta*C[i + j*ldc];}}
}int main()
{int m = 4;int n = 4;int k = 4;int lda = m;int ldb = k;int ldc = m;half *A_h;half *B_h;half *C_h;half alpha = half(1.0);half beta  = half(1.0);A_h = (half*)malloc(lda * k * sizeof(half));B_h = (half*)malloc(ldb * n * sizeof(half));C_h = (half*)malloc(ldc * n * sizeof(half));init_matrix(A_h, lda, m, k, true);init_matrix(B_h, ldb, k, n, true);init_matrix(C_h, ldc, m, n, true);printf("A_h =");print_matrix(A_h, lda, m, k, true);printf("B_h =");print_matrix(B_h, ldb, k, n, true);printf("C_h =");print_matrix(C_h, ldc, m, n, true);gemm_fp16_cpu(m, n, k, A_h, lda, B_h, ldb, C_h, ldc, alpha, beta);printf("C_h =");print_matrix(C_h, ldc, m, n, true);return 0;
}

Makefile

EXE := hello_gemm.fp16all: $(EXE)%: %.cunvcc --gpu-architecture=sm_120 -g $< -o $@ -I /usr/local/cuda/include.PHONY: clean
clean:-rm -rf $(EXE)

編譯運行

$ make

octave 驗證

誤差范圍內,結果是相等的。

3. GPU 的最簡單版本 gemm_v01

? ? ? ? 簡單主要是指沒有任何優化考慮。單個warp 工作,也不考慮數據復用、異步加載,不考慮 tensor core 加速,流水線等都不考慮。

我們可以先稍微看看 RTX 5080 的硬件信息:

10752 個cuda core,每個warp 占 32 個 cuda core【注,從 Ampere 開始,每個warp 同時占用 32 個 cuda core;之前架構是 16 個 cuda core 迭代兩次完成 32 個 thread? 的任務;】,
總共含 84 個sm,
所以,每個sm 存在 128個 cuda core,也就是 128/32 = 4 個 同時運行的 warp,也即 4 個 tensor core/sm;也就是每個 block 最多可以同時占用 4 個tensor core。

這個 v01 版本不考慮使用 tensor core,僅啟動單個warp 工作。

ex/hello_gemm.fp16.cu


#include <stdio.h>
#include <stdlib.h>
#include "cuda_fp16.h"void init_matrix(half *A, int lda, int m, int n, bool colMajor)
{if(colMajor){for(int j=0; j<n; j++){for(int i=0; i<m; i++){half x = half(rand()*1.0f/RAND_MAX);A[i + j*lda] = x;printf(" %f",  float(x));}}printf("\n\n");}else{for(int i=0; i<m; i++){for(int j=0; j<n; j++){half x = half(rand()*1.0f/RAND_MAX);A[i*lda + j] = x;printf(" %f",  float(x));}}}
}void print_matrix(half *A, int lda, int m, int n, bool colMajor)
{printf("[ ...\n");for(int i=0; i<m; i++){for(int j=0; j<n; j++){if(colMajor)printf(" %5.4f, ", float(A[i + j*lda]));elseprintf(" %5.4f, ", float(A[i*lda + j]));}printf(" ; ...\n");}printf("]\n");
}void gemm_fp16_cpu(int M, int N, int K,half* A, int lda,half* B, int ldb,half* C, int ldc,half alpha, half beta)
{for(int i=0; i<M; i++){for(int j=0; j<N; j++){half sigma = half(0.0);for(int k=0; k<K; k++){sigma += A[i + k*lda]*B[k + j*lda];}C[i + j*ldc] = alpha*sigma + beta*C[i + j*ldc];}}
}__global__ void gemm_v01_fp16_all(int M, int N, int K,half* A, int lda,half* B, int ldb,half* C, int ldc,half alpha, half beta)
{unsigned int i = threadIdx.x;unsigned int j = threadIdx.y;if(i==16) printf("%d ", j);
printf("threadIdx.x=%d  ", threadIdx.x);half sigma = half(0.0);for(unsigned int k = 0; k<K; k++){sigma += A[i + k*lda]*B[k + j*ldb];}C[i + j*ldc] = alpha*sigma + beta*C[i + j*ldc];
}void gemm_v01_test(int m, int n, int k,half* Ah, int lda,half* Bh, int ldb,half* Ch, int ldc,half alpha, half beta,half* Cd2h)
{//1. alloc ABC_dhalf * Ad = nullptr;half * Bd = nullptr;half * Cd = nullptr;cudaMalloc((void**)Ad, lda*k*sizeof(half));cudaMalloc((void**)Bd, ldb*n*sizeof(half));cudaMalloc((void**)Cd, ldc*n*sizeof(half));//2. cpy H2DcudaMemcpy(Ad, Ah, lda*k*sizeof(half), cudaMemcpyHostToDevice);cudaMemcpy(Bd, Bh, ldb*n*sizeof(half), cudaMemcpyHostToDevice);cudaMemcpy(Cd, Ch, ldc*n*sizeof(half), cudaMemcpyHostToDevice);//3. Gemm_v01, simple cuda core gemmdim3 block_;dim3 grid_;block_.x = 32;block_.y = 32;grid_.x = 1;grid_.y = 1;printf("__00________\n");gemm_v01_fp16_all<<<grid_,block_>>>(m, n, k, Ad, lda, Bd, ldb, Cd, ldc, alpha, beta);
printf("##11########\n");//4. cpy D2HcudaMemcpy(Cd2h, Cd, ldc*n*sizeof(half), cudaMemcpyDeviceToHost);//5. free ABC_dcudaFree(Ad);cudaFree(Bd);cudaFree(Cd);
}
int main()
{int m = 32;int n = 32;int k = 32;int lda = m;int ldb = k;int ldc = m;half *A_h;half *B_h;half *C_h;half *C_d2h;half alpha = half(1.0);half beta  = half(1.0);A_h = (half*)malloc(lda * k * sizeof(half));B_h = (half*)malloc(ldb * n * sizeof(half));C_h = (half*)malloc(ldc * n * sizeof(half));C_d2h = (half*)malloc(ldc * n * sizeof(half));init_matrix(A_h, lda, m, k, true);init_matrix(B_h, ldb, k, n, true);init_matrix(C_h, ldc, m, n, true);printf("A_h =");print_matrix(A_h, lda, m, k, true);printf("B_h =");print_matrix(B_h, ldb, k, n, true);printf("C_h =");print_matrix(C_h, ldc, m, n, true);gemm_fp16_cpu(m, n, k, A_h, lda, B_h, ldb, C_h, ldc, alpha, beta);printf("C_h =");print_matrix(C_h, ldc, m, n, true);gemm_v01_test(m, n, k, A_h, lda, B_h, ldb, C_h, ldc, alpha, beta, C_d2h);printf("C_d2h =");print_matrix(C_d2h, ldc, m, n, true);return 0;
}

未完待續

。。。。

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

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

相關文章

web滲透PHP反序列化漏洞

web滲透PHP反序列化漏洞1&#xff09;PHP反序列化漏洞反序列我們可以控制對象中的值進行攻擊O:1:"C":1:{s:3:"cmd";s:8:"ipconfig";}http://127.0.0.1/1.php?xO:1:%22C%22:1:{s:3:%22cmd%22;s:3:%22ver%22;}常見的反序列化魔術方法&#xff1a;…

FPGA學習筆記——SPI讀寫FLASH

目錄 一、任務 二、需求分析 三、Visio圖 四、具體分析 五、IP核配置 六、代碼 七、實驗現象 一、任務 實驗任務&#xff1a; 1.按下按鍵key1&#xff0c;開啟讀ID操作&#xff0c;將讀出來的ID&#xff0c;通過串口發送至PC端顯示&#xff0c;顯示格式為“讀ID:XX-XX-XX…

一句話PHP木馬——Web滲透測試中的隱形殺手

文章目錄前言什么是"一句話木馬"&#xff1f;常見變種與隱藏技巧1. 函數變種2. 加密混淆3. 變量拆分4. 特殊字符編碼上傳技巧與繞過防御常見上傳繞過技巧檢測與防御措施1. 服務器配置2. 上傳驗證3. 代碼審計4. Web應用防火墻(WAF)實戰案例分析深度思考&#xff1a;安…

房屋租賃系統|基于SpringBoot和Vue的房屋租賃系統(源碼+數據庫+文檔)

項目介紹 : SpringbootMavenMybatis PlusVue Element UIMysql 開發的前后端分離的房屋租賃系統&#xff0c;項目分為管理端和用戶端以及房主端 項目演示: 基于SpringBoot和Vue的房屋租賃系統 運行環境: 最好是java jdk 1.8&#xff0c;我們在這個平臺上運行的。其他版本理論上…

C++動態規劃——經典題目(下)

上一篇文章沒有寫全&#xff0c;這篇再補兩道題酒鬼#include<bits/stdc.h> using namespace std; int dp[110][10]{0}; int a[1010]{0}; int n,m; int main() {cin>>n;dp[0][0]0;dp[1][0]0;dp[1][1]a[1];for(int i1;i<n;i){cin>>a[i];}for(int i2;i<n;…

介紹Ansible和實施Ansible PlayBook

第一章 介紹Ansible1. ansible的特點是什么&#xff1f;a. ansible使用yaml語法&#xff0c;語言格式簡潔明了。b. ansible不需要代理&#xff0c;僅僅通過SSH遠程連接就可以控制受管主機&#xff0c;是一種非常便捷、安全的方法。c. Ansible的功能強大&#xff0c;可以利用ans…

ComfyUI驅動的流程化大體量程序開發:構建上下文隔離的穩定系統

ComfyUI驅動的流程化大體量程序開發&#xff1a;構建上下文隔離的穩定系統 在現代軟件工程中&#xff0c;隨著程序體量的不斷增長&#xff0c;上下文污染&#xff08;Context Pollution&#xff09;和狀態依賴混亂已成為導致系統不穩定、調試困難、維護成本高昂的核心問題。尤…

基于SpringBoot的協同過濾余弦函數的美食推薦系統(爬蟲Python)的設計與實現

基于SpringBootvue的協同過濾余弦函數的個性化美食(商城)推薦系統(爬蟲Python)的設計與實現 1、項目的設計初衷&#xff1a; 隨著互聯網技術的快速發展和人們生活水平的不斷提高&#xff0c;傳統的美食消費模式已經無法滿足現代消費者日益個性化和多樣化的需求。在信息爆炸的時…

機器視覺學習-day19-圖像亮度變換

1 亮度和對比度亮度&#xff1a;圖像像素的整體強度&#xff0c;亮度提高就是所有的像素加一個固定值。對比度&#xff1a;當對比度提高時&#xff0c;圖像的暗部與亮部的差值會變大。OpenCV調整圖像亮度和對比度的公式使用一個&#xff1a;代碼實踐步驟&#xff1a;圖片輸入→…

redis詳解 (最開始寫博客是寫redis 紀念日在寫一篇redis)

Redis技術 1. Redis簡介 定義與核心特性&#xff08;內存數據庫、鍵值存儲&#xff09; Redis&#xff08;Remote Dictionary Server&#xff0c;遠程字典服務&#xff09;是一個開源的、基于內存的高性能鍵值存儲數據庫&#xff0c;由 Salvatore Sanfilippo 編寫&#xff0c;用…

【MD文本編輯器Typora】實用工具推薦之——輕量級 Markdown 編輯器Typora下載安裝使用教程 辦公學習神器

本文將向大家介紹一款輕量級 Markdown 編輯器——Typora&#xff0c;并詳細說明其下載、安裝與基本使用方法。 引言&#xff1a; MD 格式文檔指的是使用 Markdown 語言編寫的文本文件&#xff0c;其文件擴展名為 .md。 Markdown 是一種由約翰格魯伯&#xff08;John Gruber&am…

Vue2+Element 初學

大致實現以上效果 一、左側自動加載菜單NavMenu.vue 菜單組件&#xff0c;簡單調整了一下菜單直接的距離&#xff0c;代碼如下&#xff1a;<template><div><template v-for"item in menus"><!-- 3、有子菜單&#xff0c;設置不同的 key 和 inde…

Shell編程知識整理

文章目錄一、Shell介紹1.1 簡介1.2 Shell解釋器二、快速入門2.1 編寫Shell腳本2.2 執行Shell腳本2.3 小結三、Shell程序&#xff1a;變量3.1 語法格式3.2 變量使用3.3 變量類型四、字符串4.1 單引號4.2 雙引號4.3 獲取字符串長度4.4 提取子字符串4.5 查找子字符串五、Shell程序…

AI與低代碼的激情碰撞:微軟Power Platform融合GPT-4實戰之旅

引言 在當今數字化飛速發展的時代,AI 與低代碼技術正成為推動企業變革的核心力量。AI 憑借其強大的數據分析、預測和決策能力,為企業提供了智能化的解決方案;而低代碼開發平臺則以其可視化、快速迭代的特性,大大降低了應用開發的門檻和成本。這兩者的結合,開啟了一場全新的…

豆包1.6+PromptPilot實戰:構建智能品牌評價情感分類系統的技術探索

豆包1.6PromptPilot實戰&#xff1a;構建智能品牌評價情感分類系統的技術探索 &#x1f31f; Hello&#xff0c;我是摘星&#xff01; &#x1f308; 在彩虹般絢爛的技術棧中&#xff0c;我是那個永不停歇的色彩收集者。 &#x1f98b; 每一個優化都是我培育的花朵&#xff0c;…

如何在VsCode中使用git(免敲命令版本!保姆級!建議收藏!)

目錄 文章目錄 前言 一、電腦安裝git 二、在vscode安裝git插件 三、克隆倉庫 四、提交代碼 五、創建分支、切換分支、合并分支 1、創建分支 2、切換分支 3、合并分支 六、創建標簽和推送標簽 七、解決沖突 八、拉取、抓取倉庫 九、Reivew代碼 總結 前言 隨著Vscode的推出和普及…

3.kafka常用命令

在 0.9.0.0 之后的 Kafka&#xff0c;出現了幾個新變動&#xff0c;一個是在 Server 端增加了 GroupCoordinator 這個角色&#xff0c;另一個較大的變動是將 topic 的 offset 信息由之前存儲在 zookeeper 上改為存儲到一個特殊的 topic&#xff08;__consumer_offsets&#xff…

主從DNS和Web服務器搭建過程

完整服務器搭建流程 環境說明 主服務器&#xff1a;192.168.102.128 - DNS Web 從服務器&#xff1a;192.168.102.133 - 從DNS 網站&#xff1a;www.zhangsan.com (HTTPS加密)、www.lisi.com (HTTP) 手動配置主服務器和從服務器的ip地址&#xff0c;dns&#xff0c;網關…

信號無憂,轉決千里:耐達訊自動化PROFIBUS集線器與編碼器連接術

在工業自動化領域&#xff0c;尤其是高端裝備制造、智能產線、精密運動控制等場景中&#xff0c;系統穩定性與信號實時性一直是工程師關注的核心。隨著設備智能化程度不斷提高&#xff0c;編碼器作為運動控制的關鍵反饋元件&#xff0c;其數量與分布密度顯著增加&#xff0c;對…

大模型微調示例四之Llama-Factory-DPO

大模型微調示例四之Llama-Factory-DPO一、強化學習數據處理二、配置訓練文檔三、模型預測一、強化學習數據處理 原始數據地址&#xff1a;https://nijianmo.github.io/amazon/index.html 第一步&#xff1a;讀取 video game 信息 import codecs, json, re from random impor…