点击上方,选择星标,每天给你送干货!


来自:算法码上来

最近因为工作需要,学习了一波CUDA。这里简单记录一下PyTorch自定义CUDA算子的方法,写了一个非常简单的example,再介绍一下正确的PyTorch中CUDA运行时间分析方法。

所有的代码都放在了github上,地址是:https://github.com/godweiyang/torch-cuda-example

完整流程

下面我们就来详细了解一下PyTorch是如何调用自定义的CUDA算子的。

首先我们可以看到有四个代码文件:

  • main.py,这是python入口,也就是你平时写模型的地方。

  • add2.cpp,这是torch和CUDA连接的地方,将CUDA程序封装成了python可以调用的库。

  • add2.h,CUDA函数声明。

  • add2.cu,CUDA函数实现。

然后逐个文件看一下是怎么调用的。

CUDA算子实现

首先最简单的当属add2.hadd2.cu,这就是普通的CUDA实现。

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);
}

这里实现的功能是两个长度为的tensor相加,每个block有1024个线程,一共有个block。具体CUDA细节就不讲了,本文重点不在于这个。

add2_kernel是kernel函数,运行在GPU端的。而launch_add2是CPU端的执行函数,调用kernel。注意它是异步的,调用完之后控制权立刻返回给CPU,所以之后计算时间的时候要格外小心,很容易只统计到调用的时间。

Torch C++封装

这里涉及到的是add2.cpp,这个文件主要功能是提供一个PyTorch可以调用的接口。

#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函数传入的是C++版本的torch tensor,然后转换成C++指针数组,调用CUDA函数launch_add2来执行核函数。

这里用pybind11来对torch_launch_add2函数进行封装,然后用cmake编译就可以产生python可以调用的.so库。但是我们这里不直接手动cmake编译,具体方法看下面的章节。

Python调用

最后就是python层面,也就是我们用户编写代码去调用上面生成的库了。

import time
import numpy as np
import torch
from torch.utils.cpp_extension import loadcuda_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 = 10def show_time(func):times = list()res = list()# GPU warm upfor _ in range(10):func()for _ in range(ntest):# sync the threads to get accurate cuda running timetorch.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, resdef run_cuda():cuda_module.torch_launch_add2(cuda_c, a, b, n)return cuda_cdef run_torch():# return None to avoid intermediate GPU memory application# for accurate time statisticsa + breturn Noneprint("Running cuda...")
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函数就是用来自动编译上面的几个cpp和cu文件的。最主要的就是sources参数,指定了需要编译的文件列表。然后就可以通过cuda_module.torch_launch_add2,也就是我们封装好的接口来进行调用。

接下来的代码就随心所欲了,这里简单写了一个测量运行时间,对比和torch速度的代码,这部分留着下一章节讲解。

总结一下,主要分为三个模块:

  • 先编写CUDA算子和对应的调用函数。

  • 然后编写torch cpp函数建立PyTorch和CUDA之间的联系,用pybind11封装。

  • 最后用PyTorch的cpp扩展库进行编译和调用。

运行时间分析

我们知道,CUDA kernel函数是异步的,所以不能直接在CUDA函数两端加上time.time()测试时间,这样测出来的只是调用CUDA api的时间,不包括GPU端运行的时间。

所以我们要加上线程同步函数,等待kernel中所有线程全部执行完毕再执行CPU端后续指令。这里我们将同步指令加在了python端,用的是torch.cuda.synchronize函数。

具体来说就是形如下面代码:

torch.cuda.synchronize()
start_time = time.time()
func()
torch.cuda.synchronize()
end_time = time.time()

其中第一次同步是为了防止前面的代码中有未同步还在GPU端运行的指令,第二次同步就是为了等fun()所有线程执行完毕后再统计时间。

这里我们torch和cuda分别执行10次看看平均时间,此外执行前需要先执行10次做一下warm up,让GPU达到正常状态。

我们分别测试四种情况,分别是:

  • 两次同步

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

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

  • 两次不同步

这里我们采用英伟达的Nsight Systems来可视化运行的每个时刻指令执行的情况。

安装命令为:

sudo apt install nsight-systems

然后在运行python代码时,在命令前面加上nsys profile就行了:

nsys profile python3 main.py

然后就会生成report1.qdstrmreport1.sqlite两个文件,将report1.qdstrm转换为report1.qdrep文件:

QdstrmImporter -i report1.qdstrm

最后将生成的report1.qdrep文件用Nsight Systems软件打开,我这里是mac系统。

两次同步

这是正确的统计时间的方法,我们打开Nsight Systems,放大kernel运行那一段可以看到下图:

其中第1和第3个框分别是cuda和torch的GPU warm up过程,这部分没有进行线程同步(上面的黄色块)。

而第2和第4个框就分别是cuda和torch的加法执行过程了,我们可以放大来看看。

可以看出,每执行一次(一个框)都经过了三个步骤:先是调用api(左上角蓝色框),然后执行kernel(下方蓝色框),最后线程同步(右上角黄色框)。

所以最后算出来的时间就是这三个步骤的耗时,也就是下图选中的范围:

时间大概在29us左右,和我们实际代码测出来的也是比较接近的:

其实我们实际想要知道的耗时并不包括api调用和线程同步的时间,但是这部分时间在python端不好去掉,所以就加上了。

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

放大每次执行的过程:

可以看出,虽然长的和上一种情况几乎一模一样,但是在api调用完之后,立刻就进行计时了,所以耗时只有8us左右,实际测出来情况也是这样的:

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

我们先来看一下实际统计的时间:

很奇怪是不是,第一次运行耗时非常久,那我们可视化看看到底怎么回事:

可以看出,因为第一次开始计时前没有同步线程,所以在GPU warm up调用api完毕后,第一次cuda kernel调用就开始了。然后一直等到warm up执行完毕,才开始执行第一次cuda kernel,然后是线程同步,结束后才结束计时。这个过程非常长,差不多有130us左右。然后第二次开始执行就很正常了,因为kernel结束的同步相当于是下一次执行之前的同步。

两次不同步

先来看看执行情况:

可以看出因为没有任何同步,所有GPU warm up和cuda kernel的api调用全接在一起了,执行也是。所以计时只计算到了每个api调用的时间,差不多在7us左右。

上面四种情况,torch指令情形几乎一样,因此不再赘述。

小结

通过这篇文章,应该可以大致了解PyTorch实现自定义CUDA算子并调用的方法,也能知道怎么正确的测量CUDA程序的耗时。

当然还有一些内容留作今后讲解,比如如何实现PyTorch神经网络的自定义前向和反向传播CUDA算子、如何用TensorFlow调用CUDA算子等等。

说个正事哈

由于微信平台算法改版,公号内容将不再以时间排序展示,如果大家想第一时间看到我们的推送,强烈建议星标我们和给我们多点点【在看】。星标具体步骤为:

(1)点击页面最上方深度学习自然语言处理”,进入公众号主页。

(2)点击右上角的小点点,在弹出页面点击“设为星标”,就可以啦。

感谢支持,比心

投稿或交流学习,备注:昵称-学校(公司)-方向,进入DL&NLP交流群。

方向有很多:机器学习、深度学习,python,情感分析、意见挖掘、句法分析、机器翻译、人机对话、知识图谱、语音识别等。

记得备注呦

整理不易,还望给个在看!

【进阶】PyTorch自定义CUDA算子教程与运行时间分析相关推荐

  1. 详解PyTorch编译并调用自定义CUDA算子的三种方式

    点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 在上一篇教程中,我们实现了一个自定义的CUDA算子add2,用来实现两个Tensor的相加.然后用Py ...

  2. 实例:手写 CUDA 算子,让 Pytorch 提速 20 倍

    作者丨PENG Bo@知乎(已授权) 来源丨https://zhuanlan.zhihu.com/p/476297195 编辑丨极市平台 本文的代码,在 win10 和 linux 均可直接编译运行: ...

  3. PyTorch扩展自定义PyThon/C++(CUDA)算子的若干方法总结

    点上方蓝字计算机视觉联盟获取更多干货 在右上方 ··· 设为星标 ★,与你不见不散 仅作学术分享,不代表本公众号立场,侵权联系删除 转载于:作者丨奔腾的黑猫@知乎 来源丨https://zhuanla ...

  4. python interpreter 中没有torch_PyTorch扩展自定义PyThon/C++(CUDA)算子的若干方法总结

    在做毕设的时候需要实现一个PyTorch原生代码中没有的并行算子,所以用到了这部分的知识,再不总结就要忘光了= =,本文内容主要是PyTorch的官方教程的各种传送门,这些官方教程写的都很好,以后就可 ...

  5. pytorch自定义算子 native_functions.yaml

    pytorch自定义算子 native_functions.yaml 在pytorch的文件夹中搜索native_functions.yaml,可以看到,所有pytorch原生的函数都是在这里注册的 ...

  6. PyTorch 源码解读之 cpp_extension:讲解 C++/CUDA 算子实现和调用全流程

    "Python 用户友好却运行效率低","C++ 运行效率较高,但实现一个功能代码量会远大于 Python".平常学习工作中你是否常听到类似的说法?在 Pyth ...

  7. java如何给一个链表定义和传值_如何在CUDA中为Transformer编写一个PyTorch自定义层...

    如今,深度学习模型处于持续的演进中,它们正变得庞大而复杂.研究者们通常通过组合现有的 TensorFlow 或 PyTorch 操作符来发现新的架构.然而,有时候,我们可能需要通过自定义的操作符来实现 ...

  8. Pytorch环境详细安装教程【Win10+CUDA升级11.6+cudNN+Anaconda3虚拟环境+pycharm】

    Pytorch环境详细安装教程 一.安装环境 二.CUDA升级 1.更新NVIDIA显卡驱动 (1)查看自己的CUDA驱动和运行版本 (2)更新CUDA驱动 2.升级CUDA运行版本 (1)查看可用的 ...

  9. win11 系统 GPU版本pytorch、CUDA、anaconda 、pycharm详细安装教程

    win11 系统深度学习环境搭建----- GPU版本pytorch.CUDA.cuDnn.anaconda .tensorflow_gpu.pycharm详细安装教程 参考比站视频: PyTorch ...

  10. Pytorch最全安装教程(一步到位)

    目录 概述 安装 一.安装Anaconda 二.安装CUDA(也可以不安装) (一)CUDA概述 (二)安装 三.安装pytorch (一)CPU版本 (二)GPU版本 概述 PyTorch是一个开源 ...

最新文章

  1. Windows 2008远程桌面配置多用户登陆的方法
  2. HDU1812 - Count the Tetris
  3. java 全半角转换_Java 全半角转换
  4. python写一个路径选择app_django下创建多个app并设置urls方法
  5. 《荒漠甘泉》4月21日
  6. 快速导入Maven依赖的方法
  7. JavaSE学习--内部类
  8. 蘑菇街直播实战技巧带你解决直播开发难题
  9. 四家企业“无纸化办公”养成记:这才是PDF的正确打开方式!
  10. java word另存为_java实现页面另存为word
  11. 2020杭州学区房(WJP)
  12. 基于MS强度或计数的数据依赖法非标记定量蛋白质组学的蛋白质互作分析(二)
  13. OJ常用术语解释。AC、WA、TLE、CE、RE、MLE、PE等状态术语的解释
  14. SQL——-已更新或删除的行值要么不能使该行成为唯一行,要么改变了多个行
  15. Comparable的compareTo
  16. xinxin -用while循环计算皇帝的棋盘
  17. 上市公司注册城市、上市日期、成立日期以及行业和注册资本信息
  18. Android 通过appt.exe获取已安装apk的版本信息
  19. 写一个IPPBX-功能汇总(一)
  20. 车载触摸显示屏的工作原理

热门文章

  1. AVIator -- Bypass AV tool
  2. Shell脚本编程与文件系统修复
  3. MYSQL中5.7.10ROOT密码及创建用户
  4. FireMoneky 画图 Point 赋值
  5. ios程序后台运行设置(不是太懂)
  6. www.biubiujie.com BiuBiu街-要Beautiful的女孩纸逛的街
  7. 【leetcode】遍历二叉树从跟到叶子的核心代码
  8. C++中的异常处理(上)
  9. C#各种文件操作的代码与注释
  10. Android Bitmap 开源图片框架分析(精华四)