如何高效实现矩阵乘?万文长字带你从CUDA初学者的角度入门(4)

简介: 如何高效实现矩阵乘?万文长字带你从CUDA初学者的角度入门

Block 级优化


Block 在 GPU 上基本等同于不同的 kernel 在 GPU 上运行了,所以它们之间的联系并不是特别强烈。而它们之间的相互关系在 GEMM 语境下基本就只有 wave 和 L2 cache(一个 wave 里的 Block 共享这一块 cache)了,良好的 Block Tiling 能提升相当可观的 L2 cache 命中率。


但这一部分属于 sgemm 并不是特别关心的部分,因为本身 FFMA 单元算的就不是很快,所以 Block Tiling 随便搞搞就能够满足 FFMA 单元的带宽和延迟需求了。因此,这一节的内容主要是为了有些有用到 tensor core 的同学提一个需要注意的性能提升点,其次就是有些同学可能会发现自己写的 kernel 可能会比本文中的示例慢一点(大约 10% 左右),因此在此提一下在 sgemm 中应该怎么随便搞搞 Block Tiling。


Wave & L2 cache Hit Rate


首先明确一下 wave 的概念,即一个 GPU 上能够同时运行的 Block 数量。关于 GPU 是如何决定一个 wave 由哪些 Block 组成的我并没有找到非常明确的文档说明,但我一拍脑袋想,说不定就是朴素的按顺序决定的,即 index 处于 范围内的 Block 处在第一个 wave 中,后面的 Block 依此类推。后面试了试好像的确是这样划分的。


在明确了 wave 的概念后,我们便可以对 L2 cache 命中率做一个简单的分析了。我们指定 代表一个 wave 同时运行的 Block 数量,假设一个 wave 刚好能计算 C 矩阵的整数行,那么我们不难发现对于一个 wave 而言,它需要从 Global Memory 中读取 个 float。但由于有 L2 cache 的存在,假设一个 wave 读取的数据全能被 L2 cache 装下,那么实际只读取了 数据。最终 L2 cache 的命中率为:



差距越大,L2 cache 的命中就越低。那么如果想要去优化 L2 cache 命中,一个比较直接的想法就是尽可能把一个 wave 的 Block 变成方的。但就算不搞,sgemm 也不在乎,因为其实对性能来讲并没有什么区别,所以就没搞。



SGEMM Block Tiling


而在 sgemm 的语境下,假设最坏的情况即一个 wave 都不能覆盖目标矩阵 C 的一行,且 RTX 2080 有 46 个 SM,一个 SM 能跑两个 Block,此时




带入上式可得,此时 L2 cache 命中率大概是 49.4%。这里我们并没有考虑访问 C 矩阵的影响,在实践中会把 L2 cache 的命中率拉低一点。但即便是如此,前文我们分析过只要 L2 cache 命中达到 20%,在带宽上就不会造成性能瓶颈了。因此发现,就算我们采用朴素的 Block Tiling,Global Memory 访问也不会成为访存瓶颈。


但事实真的是这样吗?


细心的同学可能会发现,上图所采用的 tiling 方式并不是直觉上的用 blockIdx.x 表示 Block 在 M 维上的位置,而是用 blockIdx.y 表示 Block 在 M 上的位置。而我们简单调换一下代码中 blockIdx.x 与 blockIdx.y 的顺序,瞬间就有了 10% 左右的性能差距。目前网上并没有针对这个现象的分析(因为几乎所有人都是用的 col major 的 data layout,而且李少侠直接就在代码里使用了更优的 tiling 方式,所以没有人遇到这个问题),因此我这里提出一点个人的猜想,如果猜的不对还请指正。



L2 cache


首先我们看一下这两种 tiling 方式的区别在哪。最为直观的区别就是当 N 或 M 足够大时,采用上图中的 tiling 方式的 wave 形状是横着的,而另一种 tiling 方式的 wave 形状是竖着的,而这种竖着的形状看起来就不是 cache 友好的访存方式。


为什么这么说呢?因为我采用的是行主序的方式存储的矩阵,因此如果一个 wave 的形状是扁平的,那么每个 Block 在每一次循环遍历 B 矩阵时只会有 次 cache miss。这是由于 L2 cache 的 cache line 大小为 128 bytes,因此当数据从 Global Memory 中移动到 L2 cache 后,许多 Block 就能直接从 L2 cache 中读取数据了。然而如果一个 wave 的形状是狭长的。那么每个 Block 在第一次访问 A 矩阵的每一行时都会有 cache miss 的情况出现,即产生 次 cache miss,而后 31 次的遍历都不会有 cache miss。虽然两种 tiling 方式最终 cache miss 的次数是一样的,但这种短时间爆发的 cache miss 所带来的延迟是非常难被各种优化手段覆盖的,因为这种延迟不仅短时间内有很多次,同时每一次的延迟都很长,所以会造成性能损失。因此以后高性能代码的开发中,也要注意合理的把 cache miss 分配到 kernel 运行的各个阶段。


Bank Conflict


在查看两种 Tiling 方式的 profile 我发现,采用横着 Tiling 方式的 kernel bank conflict 更低一些。




等等,既然我们之前已经处理过 bank conflict 了,那么为什么这里还会有 bank conflict 呢?这个现象其实我也不是很清楚。但目前已知的是,在没有加 double buffer 情况下是没有 bank conflict 的,但加了 double buffer 之后或多或少会出现一些 bank conflict。那么至于为什么横着 Tiling 方式的 bank conflict 更低,我就更不知道了,因此这里还请各位 dalao 赐教。


最终版本的代码在这:https://github.com/AyakaGEMM/Hands-on-GEMM/blob/main/src/cuda/double_buffer_yhs_refine_gemm.cu


Epilogue


回顾本文,也基本达成了文章开头所立的各种 flag。当然现在还是有很多问题没有解决的,如 split K、长尾问题、分块细调等等,这些权当是一些未来展望了。近期也在尝试写一下 int8 tensor core 的矩阵乘,在较小形状上(M、N、K<=2048)能有比 cublas 更高的性能,但在更大形状上就只有 80% 左右了(这还是 L2 cache 命中率为 90% 的结果,可能还有啥别的没做好),所以就没有写 int8 tensor core 的部分。不过好歹是写完了!

相关实践学习
在云上部署ChatGLM2-6B大模型(GPU版)
ChatGLM2-6B是由智谱AI及清华KEG实验室于2023年6月发布的中英双语对话开源大模型。通过本实验,可以学习如何配置AIGC开发环境,如何部署ChatGLM2-6B大模型。
相关文章
|
小程序 定位技术
微信小程序事件监听方法
微信小程序事件监听方法
1201 0
|
10月前
|
负载均衡 测试技术 调度
大模型分布式推理:张量并行与流水线并行技术
本文深入探讨大语言模型分布式推理的核心技术——张量并行与流水线并行。通过分析单GPU内存限制下的模型部署挑战,详细解析张量并行的矩阵分片策略、流水线并行的阶段划分机制,以及二者的混合并行架构。文章包含完整的分布式推理框架实现、通信优化策略和性能调优指南,为千亿参数大模型的分布式部署提供全面解决方案。
2841 4
|
4月前
|
存储 搜索推荐 Python
TorchRec大量使用Jagged Tensor
Jagged Tensor(锯齿张量)是专为变长序列设计的紧凑存储格式,用values+lengths/offsets替代padding,显著节省内存与计算。广泛应用于推荐系统中用户行为、多值标签等不等长特征处理,如HSTU模型中的拼接、拆分与矩阵乘法操作。
502 8
|
10月前
|
XML 数据格式 Python
从手动编辑到代码生成:Python 助你高效创建 Word 文档
本文介绍如何用Python实现Word文档自动化生成,结合python-docx、openpyxl和matplotlib库,高效完成报告撰写、数据插入与图表生成,大幅提升办公效率,降低格式错误,实现数据驱动的文档管理。
1328 2
|
10月前
|
NoSQL 关系型数据库 MySQL
改了 Nacos 一行配置,搞崩线上支付系统!
Nacos服务注册默认使用临时实例,通过心跳机制实时感知服务状态,适合支付、订单等动态业务。持久化实例由服务端探活,适用于MySQL、Redis等静态组件。配置中心所有配置均持久化,无“临时”概念,动态更新指内容变更而非生命周期短暂。正确理解差异,避免因`ephemeral=false`误配导致生产事故。
395 1
|
SQL 关系型数据库 Linux
搭建 PostgreSQL 流复制主从指南(适用于 CentOS 7.x)
搭建 PostgreSQL 流复制主从指南(适用于 CentOS 7.x)
847 7
|
缓存 人工智能 算法
Nvidia_Mellanox_CX5和6DX系列网卡_RDMA_RoCE_无损和有损_DCQCN拥塞控制等技术简介-一文入门RDMA和RoCE有损无损
Nvidia_Mellanox_CX5和6DX系列网卡_RDMA_RoCE_无损和有损_DCQCN拥塞控制等技术简介-一文入门RDMA和RoCE有损无损
5054 0
用555定时器接成的多谐振荡电路的介绍
用555定时器构建的多谐振荡电路 一、引言 多谐振荡电路是一种能够产生多个频率的振荡信号的电路结构。它在音乐合成器、电子琴等设备中有着广泛的应用。本文将介绍一种使用555定时器构建的多谐振荡电路。 二、555定时器简介 555定时器是一种经典的集成电路,由三个功能单元组成:比较器、RS触发器和放大器。它可以用作脉冲发生器、频率分频器、定时器等。在多谐振荡电路中,我们将利用555定时器的单稳态多谐振荡特性来实现多个频率的振荡。 三、电路设计 1. 电路原理 多谐振荡电路的基本原理是利用555定时器的单稳态多谐振荡特性。单稳态多谐振荡是指当555定时器处于单稳态时,输出信号的频率会随着电容和
2062 0
|
网络协议 NoSQL API
深入理解 RDMA 的软硬件交互机制
本文深入分析了RDMA技术在数据中心高性能网络环境下的工作原理及软硬件交互机制,通过对比传统Kernel TCP,突出了RDMA在减少延迟、提高系统性能方面的优势,同时讨论了其在内存管理、软硬交互方面的关键技术和挑战,为读者提供了全面理解RDMA技术及其应用场景的视角。
7745 8