91欧美超碰AV自拍|国产成年人性爱视频免费看|亚洲 日韩 欧美一厂二区入|人人看人人爽人人操aV|丝袜美腿视频一区二区在线看|人人操人人爽人人爱|婷婷五月天超碰|97色色欧美亚州A√|另类A√无码精品一级av|欧美特级日韩特级

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

PyTorch如何實(shí)現(xiàn)自定義CUDA算子并調(diào)用的方法且測(cè)量CUDA程序耗時(shí)

深度學(xué)習(xí)自然語言處理 ? 來源:算法碼上來 ? 作者:算法碼上來 ? 2021-03-30 15:58 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

最近因?yàn)楣ぷ餍枰?,學(xué)習(xí)了一波CUDA。這里簡(jiǎn)單記錄一下PyTorch自定義CUDA算子的方法,寫了一個(gè)非常簡(jiǎn)單的example,再介紹一下正確的PyTorch中CUDA運(yùn)行時(shí)間分析方法。

完整流程

下面我們就來詳細(xì)了解一下PyTorch是如何調(diào)用自定義的CUDA算子的。

首先我們可以看到有四個(gè)代碼文件:

main.py,這是python入口,也就是你平時(shí)寫模型的地方。

add2.cpp,這是torch和CUDA連接的地方,將CUDA程序封裝成了python可以調(diào)用的庫。

add2.h,CUDA函數(shù)聲明。

add2.cu,CUDA函數(shù)實(shí)現(xiàn)。

然后逐個(gè)文件看一下是怎么調(diào)用的。

CUDA算子實(shí)現(xiàn)

首先最簡(jiǎn)單的當(dāng)屬add2.h和add2.cu,這就是普通的CUDA實(shí)現(xiàn)。

void launch_add2(float *c,

const float *a,

const float *b,

int n);

__global__ void add2_kernel(float* c,

const float* a,

const float* b,

int n) {

for (int i = blockIdx.x * blockDim.x + threadIdx.x;

i 《 n; i += gridDim.x * blockDim.x) {

c[i] = a[i] + b[i];

}

}

void launch_add2(float* c,

const float* a,

const float* b,

int n) {

dim3 grid((n + 1023) / 1024);

dim3 block(1024);

add2_kernel《《《grid, block》》》(c, a, b, n);

}

這里實(shí)現(xiàn)的功能是兩個(gè)長(zhǎng)度為的tensor相加,每個(gè)block有1024個(gè)線程,一共有個(gè)block。具體CUDA細(xì)節(jié)就不講了,本文重點(diǎn)不在于這個(gè)。

add2_kernel是kernel函數(shù),運(yùn)行在GPU端的。而launch_add2是CPU端的執(zhí)行函數(shù),調(diào)用kernel。注意它是異步的,調(diào)用完之后控制權(quán)立刻返回給CPU,所以之后計(jì)算時(shí)間的時(shí)候要格外小心,很容易只統(tǒng)計(jì)到調(diào)用的時(shí)間。

Torch C++封裝

這里涉及到的是add2.cpp,這個(gè)文件主要功能是提供一個(gè)PyTorch可以調(diào)用的接口。

#include 《torch/extension.h》

#include “add2.h”

void torch_launch_add2(torch::Tensor &c,

const torch::Tensor &a,

const torch::Tensor &b,

int n) {

launch_add2((float *)c.data_ptr(),

(const float *)a.data_ptr(),

(const float *)b.data_ptr(),

n);

}

PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {

m.def(“torch_launch_add2”,

&torch_launch_add2,

“add2 kernel warpper”);

}

torch_launch_add2函數(shù)傳入的是C++版本的torch tensor,然后轉(zhuǎn)換成C++指針數(shù)組,調(diào)用CUDA函數(shù)launch_add2來執(zhí)行核函數(shù)。

這里用pybind11來對(duì)torch_launch_add2函數(shù)進(jìn)行封裝,然后用cmake編譯就可以產(chǎn)生python可以調(diào)用的.so庫。但是我們這里不直接手動(dòng)cmake編譯,具體方法看下面的章節(jié)。

Python調(diào)用

最后就是python層面,也就是我們用戶編寫代碼去調(diào)用上面生成的庫了。

import time

import numpy as np

import torch

from torch.utils.cpp_extension import load

cuda_module = load(name=“add2”,

sources=[“add2.cpp”, “add2.cu”],

verbose=True)

# c = a + b (shape: [n])

n = 1024 * 1024

a = torch.rand(n, device=“cuda:0”)

b = torch.rand(n, device=“cuda:0”)

cuda_c = torch.rand(n, device=“cuda:0”)

ntest = 10

def show_time(func):

times = list()

res = list()

# GPU warm up

for _ in range(10):

func()

for _ in range(ntest):

# sync the threads to get accurate cuda running time

torch.cuda.synchronize(device=“cuda:0”)

start_time = time.time()

r = func()

torch.cuda.synchronize(device=“cuda:0”)

end_time = time.time()

times.append((end_time-start_time)*1e6)

res.append(r)

return times, res

def run_cuda():

cuda_module.torch_launch_add2(cuda_c, a, b, n)

return cuda_c

def run_torch():

# return None to avoid intermediate GPU memory application

# for accurate time statistics

a + b

return None

print(“Running cuda.。?!保?/p>

cuda_time, _ = show_time(run_cuda)

print(“Cuda time: {:.3f}us”.format(np.mean(cuda_time)))

print(“Running torch.。。”)

torch_time, _ = show_time(run_torch)

print(“Torch time: {:.3f}us”.format(np.mean(torch_time)))

這里6-8行的torch.utils.cpp_extension.load函數(shù)就是用來自動(dòng)編譯上面的幾個(gè)cpp和cu文件的。最主要的就是sources參數(shù),指定了需要編譯的文件列表。然后就可以通過cuda_module.torch_launch_add2,也就是我們封裝好的接口來進(jìn)行調(diào)用。

接下來的代碼就隨心所欲了,這里簡(jiǎn)單寫了一個(gè)測(cè)量運(yùn)行時(shí)間,對(duì)比和torch速度的代碼,這部分留著下一章節(jié)講解。

總結(jié)一下,主要分為三個(gè)模塊:

先編寫CUDA算子和對(duì)應(yīng)的調(diào)用函數(shù)。

然后編寫torch cpp函數(shù)建立PyTorch和CUDA之間的聯(lián)系,用pybind11封裝。

最后用PyTorch的cpp擴(kuò)展庫進(jìn)行編譯和調(diào)用。

運(yùn)行時(shí)間分析

我們知道,CUDA kernel函數(shù)是異步的,所以不能直接在CUDA函數(shù)兩端加上time.time()測(cè)試時(shí)間,這樣測(cè)出來的只是調(diào)用CUDA api的時(shí)間,不包括GPU端運(yùn)行的時(shí)間。

所以我們要加上線程同步函數(shù),等待kernel中所有線程全部執(zhí)行完畢再執(zhí)行CPU端后續(xù)指令。這里我們將同步指令加在了python端,用的是torch.cuda.synchronize函數(shù)。

具體來說就是形如下面代碼:

torch.cuda.synchronize()

start_time = time.time()

func()

torch.cuda.synchronize()

end_time = time.time()

其中第一次同步是為了防止前面的代碼中有未同步還在GPU端運(yùn)行的指令,第二次同步就是為了等fun()所有線程執(zhí)行完畢后再統(tǒng)計(jì)時(shí)間。

這里我們torch和cuda分別執(zhí)行10次看看平均時(shí)間,此外執(zhí)行前需要先執(zhí)行10次做一下warm up,讓GPU達(dá)到正常狀態(tài)。

我們分別測(cè)試四種情況,分別是:

兩次同步

第一次同步,第二次不同步

第一次不同步,第二次同步

兩次不同步

這里我們采用英偉達(dá)的Nsight Systems來可視化運(yùn)行的每個(gè)時(shí)刻指令執(zhí)行的情況。

安裝命令為:

sudo apt install nsight-systems

然后在運(yùn)行python代碼時(shí),在命令前面加上nsys profile就行了:

nsys profile python3 main.py

然后就會(huì)生成report1.qdstrm和report1.sqlite兩個(gè)文件,將report1.qdstrm轉(zhuǎn)換為report1.qdrep文件:

QdstrmImporter -i report1.qdstrm

最后將生成的report1.qdrep文件用Nsight Systems軟件打開,我這里是mac系統(tǒng)。

兩次同步

這是正確的統(tǒng)計(jì)時(shí)間的方法,我們打開Nsight Systems,放大kernel運(yùn)行那一段可以看到下圖:

0256c144-8e8f-11eb-8b86-12bb97331649.png

其中第1和第3個(gè)框分別是cuda和torch的GPU warm up過程,這部分沒有進(jìn)行線程同步(上面的黃色塊)。

而第2和第4個(gè)框就分別是cuda和torch的加法執(zhí)行過程了,我們可以放大來看看。

02cd92ec-8e8f-11eb-8b86-12bb97331649.png

可以看出,每執(zhí)行一次(一個(gè)框)都經(jīng)過了三個(gè)步驟:先是調(diào)用api(左上角藍(lán)色框),然后執(zhí)行kernel(下方藍(lán)色框),最后線程同步(右上角黃色框)。

所以最后算出來的時(shí)間就是這三個(gè)步驟的耗時(shí),也就是下圖選中的范圍:

032b61ce-8e8f-11eb-8b86-12bb97331649.png

時(shí)間大概在29us左右,和我們實(shí)際代碼測(cè)出來的也是比較接近的:

039a9be8-8e8f-11eb-8b86-12bb97331649.png

其實(shí)我們實(shí)際想要知道的耗時(shí)并不包括api調(diào)用和線程同步的時(shí)間,但是這部分時(shí)間在python端不好去掉,所以就加上了。

第一次同步,第二次不同步

放大每次執(zhí)行的過程:

可以看出,雖然長(zhǎng)的和上一種情況幾乎一模一樣,但是在api調(diào)用完之后,立刻就進(jìn)行計(jì)時(shí)了,所以耗時(shí)只有8us左右,實(shí)際測(cè)出來情況也是這樣的:

047e113e-8e8f-11eb-8b86-12bb97331649.png

第一次不同步,第二次同步

我們先來看一下實(shí)際統(tǒng)計(jì)的時(shí)間:

04eba01e-8e8f-11eb-8b86-12bb97331649.png

很奇怪是不是,第一次運(yùn)行耗時(shí)非常久,那我們可視化看看到底怎么回事:

055a53ec-8e8f-11eb-8b86-12bb97331649.png

可以看出,因?yàn)榈谝淮伍_始計(jì)時(shí)前沒有同步線程,所以在GPU warm up調(diào)用api完畢后,第一次cuda kernel調(diào)用就開始了。然后一直等到warm up執(zhí)行完畢,才開始執(zhí)行第一次cuda kernel,然后是線程同步,結(jié)束后才結(jié)束計(jì)時(shí)。這個(gè)過程非常長(zhǎng),差不多有130us左右。然后第二次開始執(zhí)行就很正常了,因?yàn)閗ernel結(jié)束的同步相當(dāng)于是下一次執(zhí)行之前的同步。

兩次不同步

先來看看執(zhí)行情況:

05ef66a8-8e8f-11eb-8b86-12bb97331649.png

可以看出因?yàn)闆]有任何同步,所有GPU warm up和cuda kernel的api調(diào)用全接在一起了,執(zhí)行也是。所以計(jì)時(shí)只計(jì)算到了每個(gè)api調(diào)用的時(shí)間,差不多在7us左右。

上面四種情況,torch指令情形幾乎一樣,因此不再贅述。

小結(jié)

通過這篇文章,應(yīng)該可以大致了解PyTorch實(shí)現(xiàn)自定義CUDA算子并調(diào)用的方法,也能知道怎么正確的測(cè)量CUDA程序的耗時(shí)。

當(dāng)然還有一些內(nèi)容留作今后講解,比如如何實(shí)現(xiàn)PyTorch神經(jīng)網(wǎng)絡(luò)的自定義前向和反向傳播CUDA算子、如何用TensorFlow調(diào)用CUDA算子等等。
編輯:lyn

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • python
    +關(guān)注

    關(guān)注

    57

    文章

    4877

    瀏覽量

    90078
  • CUDA
    +關(guān)注

    關(guān)注

    0

    文章

    127

    瀏覽量

    14482
  • pytorch
    +關(guān)注

    關(guān)注

    2

    文章

    813

    瀏覽量

    14856

原文標(biāo)題:【進(jìn)階】PyTorch自定義CUDA算子教程與運(yùn)行時(shí)間分析

文章出處:【微信號(hào):zenRRan,微信公眾號(hào):深度學(xué)習(xí)自然語言處理】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    借助NVIDIA CUDA Tile IR后端推進(jìn)OpenAI Triton的GPU編程

    NVIDIA CUDA Tile 是基于 GPU 的編程模型,其設(shè)計(jì)目標(biāo)是為 NVIDIA Tensor Cores 提供可移植性,從而釋放 GPU 的極限性能。CUDA Tile 的一大優(yōu)勢(shì)是允許開發(fā)者基于其構(gòu)建自定義的 DS
    的頭像 發(fā)表于 02-10 10:31 ?266次閱讀

    如何在NVIDIA CUDA Tile中編寫高性能矩陣乘法

    本博文是系列課程的一部分,旨在幫助開發(fā)者學(xué)習(xí) NVIDIA CUDA Tile 編程,掌握構(gòu)建高性能 GPU 內(nèi)核的方法,并以矩陣乘法作為核心示例。
    的頭像 發(fā)表于 01-22 16:43 ?4862次閱讀
    如何在NVIDIA <b class='flag-5'>CUDA</b> Tile中編寫高性能矩陣乘法

    如何在TensorFlow Lite Micro中添加自定義操作符(2)

    reshape算子進(jìn)行說明,如何將reshape算子注冊(cè)到解析器中,接下來介紹如果我們想自定義一個(gè)算子需要干些什么。
    的頭像 發(fā)表于 12-26 10:53 ?1161次閱讀

    NVIDIA CUDA Tile的創(chuàng)新之處、工作原理以及使用方法

    NVIDIA CUDA 13.1 推出 NVIDIA CUDA Tile,這是自 2006 年 NVIDIA CUDA 平臺(tái)發(fā)明以來,最大的一次技術(shù)進(jìn)步。這一令人振奮的創(chuàng)新引入了一套面向
    的頭像 發(fā)表于 12-24 10:17 ?482次閱讀
    NVIDIA <b class='flag-5'>CUDA</b> Tile的創(chuàng)新之處、工作原理以及使用<b class='flag-5'>方法</b>

    NVIDIA CUDA 13.1版本的新增功能與改進(jìn)

    NVIDIA CUDA 13.1 是自 CUDA 二十年前發(fā)明以來,規(guī)模最大、內(nèi)容最全面的一次更新。
    的頭像 發(fā)表于 12-13 10:08 ?2219次閱讀

    無圖形界面模式下自定義檢查工具的應(yīng)用

    此前文章已介紹 ANSA 中的自定義檢查工具。本文將探討該功能在無圖形界面(No-GUI)模式下的應(yīng)用,旨在滿足標(biāo)準(zhǔn)化工作流程的需求,適用于需要高度自動(dòng)化的前處理場(chǎng)景。通過集成自定義檢查,用戶可實(shí)現(xiàn)工作流程的高效自動(dòng)化運(yùn)行。
    的頭像 發(fā)表于 11-30 14:13 ?594次閱讀
    無圖形界面模式下<b class='flag-5'>自定義</b>檢查工具的應(yīng)用

    軟硬件協(xié)同技術(shù)分享 - 任務(wù)劃分 + 自定義指令集

    SoC自帶NICE協(xié)處理器接口,支持傳輸自定義指令。本設(shè)計(jì)在軟件層面利用C語言內(nèi)聯(lián)函數(shù)的方式實(shí)現(xiàn)了6條自定義函數(shù)的定義。 軟件上傳輸參
    發(fā)表于 10-28 08:03

    采用匯編指示符來使用自定義指令

    具體實(shí)現(xiàn) 1、采用.word .half .dword等匯編指示符直接插入自定義指令,這種方法需要自己指定寄存器。其中.word為插入一個(gè)字的數(shù)據(jù)即32位,.half為插入半字即16位
    發(fā)表于 10-28 06:02

    進(jìn)迭時(shí)空同構(gòu)融合RISC-V AI CPU的Triton算子編譯器實(shí)踐

    Pytorch已能做到100%替換CUDA,國內(nèi)也有智源研究院主導(dǎo)的FlagGems通用算子庫試圖構(gòu)建起不依賴CUDA的AI計(jì)算生態(tài),截至今日,F(xiàn)lagGems已進(jìn)入Pyto
    的頭像 發(fā)表于 07-15 09:04 ?1896次閱讀
    進(jìn)迭時(shí)空同構(gòu)融合RISC-V AI CPU的Triton<b class='flag-5'>算子</b>編譯器實(shí)踐

    大彩講堂:VisualTFT軟件如何自定義圓形進(jìn)度條

    VisualTFT軟件如何自定義圓形進(jìn)度條
    的頭像 發(fā)表于 07-07 17:10 ?1654次閱讀
    大彩講堂:VisualTFT軟件如何<b class='flag-5'>自定義</b>圓形進(jìn)度條

    KiCad 中的自定義規(guī)則(KiCon 演講)

    “ ?Seth Hillbrand 在 KiCon US 2025 上為大家介紹了 KiCad 的規(guī)則系統(tǒng),詳細(xì)講解了自定義規(guī)則的設(shè)計(jì)與實(shí)例。? ” ? 演講主要圍繞 加強(qiáng) KiCad 中的自定義
    的頭像 發(fā)表于 06-16 11:17 ?2161次閱讀
    KiCad 中的<b class='flag-5'>自定義</b>規(guī)則(KiCon 演講)

    HarmonyOS應(yīng)用自定義鍵盤解決方案

    增強(qiáng)用戶輸入的安全性,避免敏感信息被截取或者泄露。本文介紹了自定義鍵盤的實(shí)現(xiàn),結(jié)合自定義鍵盤和系統(tǒng)鍵盤的切換、自定義鍵盤的布局避讓等場(chǎng)景,
    的頭像 發(fā)表于 06-05 14:19 ?2414次閱讀

    如何使用自定義設(shè)置回調(diào)函數(shù)?

    你好,我正在嘗試編寫自己的自定義設(shè)置回調(diào)函數(shù),使用 fastEnum=false。 是否有任何代碼示例或資料可供我參考? void CyU3PUsbRegisterSetupCallback
    發(fā)表于 05-21 06:11

    LabVIEW運(yùn)動(dòng)控制(三):EtherCAT運(yùn)動(dòng)控制器的高效加工指令自定義封裝

    LabVIEW高效加工指令自定義封裝
    的頭像 發(fā)表于 04-08 13:49 ?3643次閱讀
    LabVIEW運(yùn)動(dòng)控制(三):EtherCAT運(yùn)動(dòng)控制器的高效加工指令<b class='flag-5'>自定義</b>封裝

    如何添加自定義單板

    在開發(fā)過程中,用戶有時(shí)需要?jiǎng)?chuàng)建自定義板配置。本節(jié)將通過一個(gè)實(shí)例講解用戶如何創(chuàng)建屬于自己的machine,下面以g2l-test.conf為例進(jìn)行說明。
    的頭像 發(fā)表于 03-12 14:43 ?1366次閱讀