CUDA-MODE 第一课: 如何在 PyTorch 中 profile CUDA kernels

我的课程笔记,欢迎关注:https://github.com/BBuf/how-to-optim-algorithm-in-cuda/tree/master/cuda-mode

第一课: 如何在 PyTorch 中 profile CUDA kernels

这里是课程规划,有三位讲师 Andreas, Thomas, Mark,然后大概2周出一个 CUDA 主题的讲解以及工程或者结对编程的视频。课程讨论的主题是根据 《Programming Massively Parallel Processors》这本书来的,Mark 也是在8分钟的时候强推了这本书。另外在6分钟左右 Mark 指出,学习 CUDA 的困难之处在于对于新手来说,可能会陷入不断循环查找文档的状态,非常痛苦。

这里是说Lecture 1的目标是如何把一个 CUDA kernel 嵌入到 PyTorch 里面,以及如何对它进行 Profile 。相关的代码都在:https://github.com/cuda-mode/lectures/tree/main/lecture_001 。Mark 还提到说这个课程相比于以前的纯教程更加关注的是我们可以利用 CUDA 做什么事情,而不是让读者陷入到 CUDA 专业术语的细节中,那会非常痛苦。

这一页 Slides 中的代码在 https://github.com/cuda-mode/lectures/blob/main/lecture_001/pytorch_square.py

import torcha = torch.tensor([1., 2., 3.])print(torch.square(a))
print(a ** 2)
print(a * a)def time_pytorch_function(func, input):# CUDA IS ASYNC so can't use python time module# CUDA是异步的,所以你不能使用python的时间模块,而应该使用CUDA Eventstart = torch.cuda.Event(enable_timing=True)end = torch.cuda.Event(enable_timing=True)# Warmup (防止CUDA Context初始化影响时间记录的准确性)for _ in range(5):func(input)start.record()func(input)end.record()# 程序完成之后需要做一次 CUDA 同步torch.cuda.synchronize()return start.elapsed_time(end)b = torch.randn(10000, 10000).cuda()def square_2(a):return a * adef square_3(a):return a ** 2time_pytorch_function(torch.square, b)
time_pytorch_function(square_2, b)
time_pytorch_function(square_3, b)print("=============")
print("Profiling torch.square")
print("=============")# Now profile each function using pytorch profiler
with torch.autograd.profiler.profile(use_cuda=True) as prof:torch.square(b)print(prof.key_averages().table(sort_by="cuda_time_total", row_limit=10))print("=============")
print("Profiling a * a")
print("=============")with torch.autograd.profiler.profile(use_cuda=True) as prof:square_2(b)print(prof.key_averages().table(sort_by="cuda_time_total", row_limit=10))print("=============")
print("Profiling a ** 2")
print("=============")with torch.autograd.profiler.profile(use_cuda=True) as prof:square_3(b)print(prof.key_averages().table(sort_by="cuda_time_total", row_limit=10))

这里通过在 PyTorch 中实现平方和立方函数并使用 autograd profiler 工具进行 profile 。time_pytorch_function 这个函数的计时功能和 torch.autograd.profiler.profile 类似,第三页 Slides 里面我们可以通过 PyTorch Profiler 的结果看到当前被 torch.autograd.profiler.profile context manager 包起来的 PyTorch 程序 cuda kernel 在 cpu, cuda 上的执行时间以及占比以及 kernel 的调用次数,当前 kernel 的执行时间占总时间的比例。

这一页Slides是对 https://github.com/cuda-mode/lectures/blob/main/lecture_001/pt_profiler.py 这个文件进行讲解,之前我也翻译过PyTorch Profiler TensorBoard 插件教程,地址在 https://zhuanlan.zhihu.com/p/692749819

可以看到aten::square实际上是调用的aten::pow,然后aten::pow下方的cud指的是cuda kernel dispatch也就是启动CUDA kernel,我们还可以看到这个CUDA kernel的名字是naive_vectorized_elementwise_kernel<4, ..>,其中4表示Block的数量。但是这里的问题是,我们只能看到kernel的名称,无法知道它运行得多快。然后up主推荐去了解和学习PyTorch的.cu实现,这些实现是一个很好的工具。

PyTorch的load_inline可以把c/c++源码以函数的方式加载到模块中。接着作则还展示了一下怎么使用load_inline
加载cuda的源代码:https://github.com/cuda-mode/lectures/blob/main/lecture_001/load_inline.py 。

# Look at this test for inspiration
# https://github.com/pytorch/pytorch/blob/main/test/test_cpp_extensions_jit.pyimport torch
from torch.utils.cpp_extension import load_inline# Define the CUDA kernel and C++ wrapper
cuda_source = '''
__global__ void square_matrix_kernel(const float* matrix, float* result, int width, int height) {int row = blockIdx.y * blockDim.y + threadIdx.y;int col = blockIdx.x * blockDim.x + threadIdx.x;if (row < height && col < width) {int idx = row * width + col;result[idx] = matrix[idx] * matrix[idx];}
}torch::Tensor square_matrix(torch::Tensor matrix) {const auto height = matrix.size(0);const auto width = matrix.size(1);auto result = torch::empty_like(matrix);dim3 threads_per_block(16, 16);dim3 number_of_blocks((width + threads_per_block.x - 1) / threads_per_block.x,(height + threads_per_block.y - 1) / threads_per_block.y);square_matrix_kernel<<<number_of_blocks, threads_per_block>>>(matrix.data_ptr<float>(), result.data_ptr<float>(), width, height);return result;}
'''cpp_source = "torch::Tensor square_matrix(torch::Tensor matrix);"# Load the CUDA kernel as a PyTorch extension
square_matrix_extension = load_inline(name='square_matrix_extension',cpp_sources=cpp_source,cuda_sources=cuda_source,functions=['square_matrix'],with_cuda=True,extra_cuda_cflags=["-O2"],build_directory='./load_inline_cuda',# extra_cuda_cflags=['--expt-relaxed-constexpr']
)a = torch.tensor([[1., 2., 3.], [4., 5., 6.]], device='cuda')
print(square_matrix_extension.square_matrix(a))# (cudamode) ubuntu@ip-172-31-9-217:~/cudamode/cudamodelecture1$ python load_inline.py 
# tensor([[ 1.,  4.,  9.],
#         [16., 25., 36.]], device='cuda:0')

注意到这里的build_directory='./load_inline_cuda', 表示构建过程生成的代码一集编译的中间产物都会保存到 https://github.com/cuda-mode/lectures/tree/main/lecture_001/load_inline_cuda 这个文件夹中。

如果想避免这种编译过程,可以考虑使用Triton,它是一个Python程序。

这个是用Triton写的square kernel,下面展示了 torch.compile, naive torch, Triton 实现的kernel在A10的性能对比:

可以看到naive torch的kernel比Triton和torch.compile生产的kernel都更快一点。接着又在4090上做了实验,得到了类似的结果。作者写的kernel在:https://github.com/cuda-mode/lectures/blob/main/lecture_001/triton_square.py

Triton kernel为:

# Adapted straight from https://triton-lang.org/main/getting-started/tutorials/02-fused-softmax.html
import triton
import triton.language as tl
import torch# if @triton.jit(interpret=True) does not work, please use the following two lines to enable interpret mode
# import os
# os.environ["TRITON_INTERPRET"] = "1"@triton.jit
def square_kernel(output_ptr, input_ptr, input_row_stride, output_row_stride, n_cols, BLOCK_SIZE: tl.constexpr):# The rows of the softmax are independent, so we parallelize across thoserow_idx = tl.program_id(0)# The stride represents how much we need to increase the pointer to advance 1 rowrow_start_ptr = input_ptr + row_idx * input_row_stride# The block size is the next power of two greater than n_cols, so we can fit each# row in a single blockcol_offsets = tl.arange(0, BLOCK_SIZE)input_ptrs = row_start_ptr + col_offsets# Load the row into SRAM, using a mask since BLOCK_SIZE may be > than n_colsrow = tl.load(input_ptrs, mask=col_offsets < n_cols, other=-float('inf'))square_output = row * row# Write back output to DRAMoutput_row_start_ptr = output_ptr + row_idx * output_row_strideoutput_ptrs = output_row_start_ptr + col_offsetstl.store(output_ptrs, square_output, mask=col_offsets < n_cols)def square(x):n_rows, n_cols = x.shape# The block size is the smallest power of two greater than the number of columns in `x`BLOCK_SIZE = triton.next_power_of_2(n_cols)# Another trick we can use is to ask the compiler to use more threads per row by# increasing the number of warps (`num_warps`) over which each row is distributed.# You will see in the next tutorial how to auto-tune this value in a more natural# way so you don't have to come up with manual heuristics yourself.num_warps = 4if BLOCK_SIZE >= 2048:num_warps = 8if BLOCK_SIZE >= 4096:num_warps = 16# Allocate outputy = torch.empty_like(x)# Enqueue kernel. The 1D launch grid is simple: we have one kernel instance per row o# f the input matrixsquare_kernel[(n_rows, )](y,x,x.stride(0),y.stride(0),n_cols,num_warps=num_warps,BLOCK_SIZE=BLOCK_SIZE,)return y

这个kernel是Triton的fused softmax 教程改过来的,在那个教程里 Triton 的速度比 PyTorch 和 torch.compile 都要快,所以这里的性能表现似乎有点奇怪,因为两者都是element-wise操作。接着作者把上面的BLOCK_SIZE固定为1024,观察到性能有很大提升

这里如果固定了BLOCK_SIZE,那上面的Kernel也要做对应的修改比如以BLOCK_SIZE的步长来循环加载列方向的数据。

下一页Slides提到Triton现在提供了一个debugger:

开启debugger模式之后你就可以在Triton kernel里的任意一行打断点一行行检查代码,几乎所有的变量都是Tensor,你可以使用var_name.tensor来打印。

这个功能真的非常棒。

接着,up主提到可以观察Triton的PTX来发现一些有效的信息。比如上面的矩阵平方运算的Triton kernel 产生的PTX文件为:https://github.com/cuda-mode/lectures/blob/main/lecture_001/square_kernel.ptx

我们可以看到每次计算 Triton 使用了8个寄存器来对输入做平方运算,另外使用了8个寄存器来存输出。此外,通过查看PTX kernel,你可以看到对global memory和shared memory的直接操作。

你可以把PTX粘贴到ChatGPT,让它为你添加注释。

下面这张Slides提到怎么查看PyTorch的编译器生成的Triton Kernel:

这样甚至你都不需要编写Triton kernel,只编写PyTorch程序就可以了。或者以这个Triton Kernel为起点来修改,优化,学习,等等。

下一页Slides:

up主介绍了一下nsight compute profile工具,例子为:https://github.com/cuda-mode/lectures/blob/main/lecture_001/ncu_logs ,我们可以从 ncu 的profile结果得到一些性能,带宽相关的指标或者一些粗浅的调优建议。

此外,当ncu指定--set full参数后,我们可以从ncu的可视化软件中查看profile结果,就像:

我们可以直观的看到每个kernel的grid_size,block_size,计算吞吐和内存带宽吞吐等指标。另外下方白色字体后面都是根据目前kernel的指标给出的粗浅调优建议,比如这里第一条就是因为活跃wave太低给出的调整grid_size和block_size的建议。第二条是计算的理论occupancy(100.0%)和实测的实际occupancy占用(72.0%)之间的差异可能是由于 kernel 执行期间的warp调度开销或工作负载不平衡导致的。在同一kernel 的不同块之间以及块内的不同 warps 之间都可能发生负载不平衡。 第三条则是需要验证内存访问模式是否最优,是否需要使用Shared memoy。

下面一页Slides说的是,我们可以通过ncu profile的结果决定是否要处理一些尾部的需求,比如通过我们可以控制的Padding方式,或者合并内存读写,使用Shared Memory(不过Shared Memory是Triton控制的)来提升kernel性能。这页Slides还展示了使用CUDA和Triton分别可以操作哪些优化,可以看到手写Kernel可以操作任何优化,而Triton只能操作跨SM的调度。

下面一页Slides是Nsight Compute的source pages,它会展示源代码,CUDA PTX代码,代码对应的的寄存器占用情况比如全局内存读取操作。

最后总结一下这节课就是,让PyTorch集成 CUDA kernel 很容易,接着我们应该利用 torch.autograd.profiler 和 Nsight Compute 来做 profile 和性能优化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/396280.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Elasticsearch:用例、架构和 6 个最佳实践

1. 什么是 Elasticsearch&#xff1f; Elasticsearch 是一个开源分布式搜索和分析引擎&#xff0c;专为处理大量数据而设计。它建立在 Apache Lucene 之上&#xff0c;并由Elastic 支持。Elasticsearch 用于近乎实时地存储、搜索和分析结构化和非结构化数据。 Elasticsearch 的…

4.3.2 C++ 平面拟合的实现

4.3.2 C 平面拟合的实现 参考教程&#xff1a; gaoxiang12/slam_in_autonomous_driving: 《自动驾驶中的SLAM技术》对应开源代码 (github.com) Eigen打印输出_打印eigen矩阵-CSDN博客 1. 编写 Plane fitting 1.1 创建文件夹 通过终端创建一个名为Plane_fitting的文件夹以保…

文件操作与IO(下)

✨个人主页&#xff1a; 不漫游-CSDN博客 目录 前言 流对象 InputStream OutputStream 运用 在控制台进行输入并写入文件 进行普通文件的复制 前言 之前的文章文件操作与IO&#xff08;上&#xff09;已经介绍了文件系统的相关操作&#xff0c;这次的主角是文件内容的相关…

SpringBoot 框架学习笔记(七):Thymeleaf、拦截器 和 文件上传实现(解决了文件重名 和 按日期分目录存放问题)

1 Thymeleaf 1.1 基本介绍 &#xff08;1&#xff09;官方文档&#xff1a;Tutorial: Using Thymeleaf &#xff08;2&#xff09;Thymeleaf 是什么 Thymeleaf 是一个跟 Velocity、FreeMarker 类似的模板引擎&#xff0c;可完全替代 JSPThymeleaf 是一个 java 类库&#xf…

.net core webapi 自定义异常过滤器

1.定义统一返回格式 namespace webapi;/// <summary> /// 统一数据响应格式 /// </summary> public class Results<T> {/// <summary>/// 自定义的响应码&#xff0c;可以和http响应码一致&#xff0c;也可以不一致/// </summary>public int Co…

vue 打包时候的分包

export default defineConfig({plugins: [vue()],resolve: {alias: {: fileURLToPath(new URL(./src/, import.meta.url))}},// 分包&#xff0c;node_modules中的单独打包成名字为vendor的js文件build: {rollupOptions: {manualChunks(id) {if (id.includes(node_modules)) {r…

EF8 学习过程中的问题和解决方案

一、varchar类型字段如果为null 无法使用contains来判断是否包含字符串 1. 有问题的代码&#xff1a; contractList _dbcontext.contractHeads.Where(u > u.code.Contains(queryStr) || u.name.Contains(queryStr) || u.companyName.Contains(queryStr) || u.customerNa…

uniapp开启数据压缩的坑-SpringBoot-gzip

1、服务器配置 服务端开启的数据压缩配置 server:port: ${port:8881}servlet:# 应用上下文路径context-path: /orderserverundertow:threads:io: 4worker: 500buffer-size: 2048# 开启Gzip压缩&#xff0c;compression:# 开启压缩enabled: true# 对json格式内容进行压缩mime-…

KCTF 闯关游戏:1 ~ 7 关

前言 看雪CTF平台是一个专注于网络安全技术竞赛的在线平台&#xff0c;它提供了一个供网络安全爱好者和技术专家进行技术交流、学习和竞技的环境。CTF&#xff08;Capture The Flag&#xff0c;夺旗赛&#xff09;是网络安全领域内的一种流行竞赛形式&#xff0c;起源于1996年…

嵌入式全栈开发学习笔记---数据结构(排序算法)

目录 排序的分类 稳定排序与不稳定排序 内部排序和外部排序 算法的复杂性 常见的排序算法 直接插入排序 希尔排序 快速排序 简单选择排序 堆排序 归并排序 基数排序 常见的排序总结 到目前为止&#xff0c;数据结构的线性结构和树状结构就都讲完了&#xff0c;本节…

使用 MongoDB 构建 AI:Flagler Health 的 AI 旅程如何彻底改变患者护理

Flagler Health 致力于为慢性病患者提供支持&#xff0c;为其匹配合适的医生以提供合适的护理。 通常&#xff0c;身患严重病痛的患者面临的选择有限&#xff0c;他们往往需要长期服用阿片类药物&#xff0c;或寻求成本高昂的侵入性外科手术干预。遗憾的是&#xff0c;后一种方…

SQL语句创建数据库(增删查改)

SQL语句 一.数据库的基础1.1 什么是数据库1.2 基本使用1.2.1 连接服务器1.2.2 使用案例 1.2 SQL分类 二.库的操作2.1 创建数据库2.2 创建数据库示例2.3 字符集和校验规则2.3.1 查看系统默认字符集以及校验规则2.3.2查看数据库支持的字符集2.3.3查看数据库支持的字符集校验规则2…

Android系统Android.bp文件详解

文章目录 1. 基本语法结构2. 常见模块类型3. 模块属性常见属性包括&#xff1a; 4. 具体示例5. 高级功能5.1. 条件编译5.2. 变量定义与使用5.3. 模块继承 6. 总结 Android.bp 是 Android 构建系统&#xff08;Android Build System&#xff09;中的配置文件&#xff0c;用于描述…

go之命令行工具urfave-cli

一、urfave/cli urfave/cli 是一个声明性的、简单、快速且有趣的包&#xff0c;用于用 Go 构建命令行工具。 二、快速使用 2.1 引入依赖 go get github.com/urfave/cli/v2 2.2 demo package mainimport ("fmt""log""os""github.com/ur…

OpenCV图像滤波(9)getGaussianKernel()函数的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 功能描述 cv::getGaussianKernel() 是 OpenCV 中的一个函数&#xff0c;用于生成一维高斯核。这种核通常用于实现高斯模糊滤波器&#xff0c;该滤波器可以…

备考CISSP,看这一篇就够了!(附备考资料下载)

作者在2023年发布过一篇博文《不报辅导班一次性通过CISSP经验分享》&#xff0c;后台收到很多备考小伙伴的私信咨询&#xff0c;我就基于大家经常问的问题整理了此文章为大家答疑解惑&#xff0c;同时附上备考过程中作者收集到的全部资源&#xff08;见文末&#xff09;&#x…

EasyCVR视频汇聚平台云计算技术核心优势:高效、灵活与可扩展性深度解读

随着科技的飞速发展和社会的不断进步&#xff0c;视频监控已经成为现代社会治安防控、企业管理等场景安全管理中不可或缺的一部分。在这一背景下&#xff0c;EasyCVR视频汇聚平台凭借其强大的云计算技术&#xff0c;展现出了卓越的性能和广泛的应用前景。本文将深入解析EasyCVR…

Rust学习----Rust安装

如何安装Rust&#xff1f; 1.官网&#xff1a;https://www.rust-lang.org/zh-CN/ 2.Linux or Max: curl https://sh.rustup.rs -sSf | sh 3.Windows按官网指导安装。 4.Windows Subsystem for Linux&#xff1a; curl --proto https --tlsv1.2 -sSf https://sh.rustup.rs…

JavaDS —— 位图(BitSet)与 布隆过滤器

位图 引入问题&#xff1a;给40亿个不重复的无符号整数&#xff0c;没排过序。给一个无符号整数&#xff0c;如何快速判断一个数是否在这40亿个数中。 首先要注意 40 亿个数据如果使用 整型&#xff08;int) 来存放的话&#xff0c;就是要 40 亿个整型&#xff0c;一个整型有…

redis面试(十一)锁超时

boolean res lock.tryLock(100, 10, TimeUnit.SECONDS); RedissonLock里面有这样一个方法tryLock()&#xff0c;意思是尝试获取锁的结果。 最大等待时间100s&#xff0c;并且获取到锁之后&#xff0c;10s之内没有释放的话&#xff0c;锁会自动失效。 尝试获取锁超时 time …