CUTLASS: A Performant, Flexible, and Portable Way to Target Hopper Tensor Cores

Vijay Thakkar, Jack Kosaian
NVIDIA GTC 2024 | 2024/03/19

目录

  1. CUTLASS 简介
  2. 自 GTC'23 以来的新特性
  3. CUTLASS 3 核心概念
  4. CUTLASS 3 中的卷积
  5. 面向大语言模型 (LLMs) 的特性
  6. Epilogue Visitor Tree (EVT
  7. 教程:使用 CUTLASS 编写自定义 CUDA 核函数
  8. 结论与路线图

CUTLASS 简介

CUTLASS 是一个用于深度学习和高性能计算的 CUDA C++ 模板库。它旨在将张量计算在其所有范围和尺度上分解为其“移动部件”。

Page 2
Page 2

CUTLASS 的核心概念是分层抽象,每一层都封装了特定的功能:
- Device (设备层):通用矩阵乘(GEMM)、卷积、归约(Reduction)、BLAS3 等,支持所有数据类型、SIMT 和张量核心(Tensor Cores),并兼容所有架构。
- Kernel (核函数层):GEMM、批量 GEMM、卷积、归约、融合输出操作、融合输入操作。
- Collective (集合操作层):CUTLASS 的时序微内核(temporal micro-kernels),用于编排空间微内核(spatial micro-kernels)的异步生产者/消费者流水线。
- Atom (原子操作层):CuTe 空间微内核(Tiled MMA / Copy)。
- Thread (线程层):数值转换、<functional> 风格的数组操作、complex<T>、快速数学算法。
- Architecture intrinsic (架构内置函数层):封装架构特定的PTX指令的模板(例如 mma.cp.async, ldmatrix, cvt)。

开源社区与版本信息:
- 开源地址: https://github.com/NVIDIA/cutlass
- 社区规模: 4.4k 星标,每月 2.5M 次克隆,100+ 贡献者,以及大量活跃用户。
- 最新版本: CUTLASS 3.5
- 文档: https://github.com/NVIDIA/cutlass#documentation
- 过往 GTC 演讲: GTC'18, GTC'19, GTC'20, GTC'21, GTC'22, GTC'23


自 GTC'23 以来的新特性

自 GTC'23 以来,CUTLASS 引入了多项新功能,主要集中在对卷积、大语言模型(LLM)的支持以及通过访问者树(Visitor Tree)实现的 Epilogue 融合。

Page 3
Page 3
Page 4
Page 4

CUTLASS 3 核心概念

CUTLASS 3 概念层次结构

CUTLASS 3 对 GEMM 和卷积采用了统一的层次化抽象。

Page 6
Page 6

CUTLASS 3 API 入口点

CUTLASS 3 在每个抽象层都提供了单一的 API 入口点。

Page 7
Page 7

CUTLASS 3 中的卷积

卷积集合操作 (Convolution Collectives)

这是 3.x API 中为卷积新增的主要部分。

Page 8
Page 8

卷积提供熟悉的构建器 API

CUTLASS 3.x 提供了一个名为 cutlass::conv::collective::CollectiveBuilder<> 的构建器 API,以简化卷积核的创建。

Page 9
Page 9

用户只需提供高层级的描述,例如:

构建器 API 负责处理底层的复杂配置。

构建器完成繁重工作

CollectiveBuilder 自动将用户的高级描述映射到最优的主循环配置。

Page 10
Page 10

这个过程包括自动确定:
- 主循环类型
- 阶段计数 (stage count)
- GMMA 指令
- TMA 指令
- 共享内存布局
- 核函数调度

如上图所示,一个简洁的构建器调用(上部分代码)会被展开成一个非常复杂和高度特化的模板实例化(下部分代码),从而为特定硬件和问题配置生成最优代码。

CUTLASS 3.5 卷积 API

CUTLASS 3.5 提供了熟悉的核函数层和设备层 API 来构建完整的卷积操作。

Page 11
Page 11

构建过程分为几个步骤:
1. 构建 Epilogue 类型:使用 epilogue::collective::CollectiveBuilder 定义计算结束后的操作。
2. 构建 Mainloop 类型:使用 conv::collective::CollectiveBuilder 定义卷积的核心计算循环。
3. 在核函数层组合:使用 conv::kernel::ConvUniversal 将 mainloop 和 epilogue 组合成一个完整的核函数。问题形状类型会由 mainloop 推断出来。
4. 获取设备层句柄:使用 conv::device::ConvUniversalAdapter<ConvKernel> 创建一个可以从主机代码调用的句柄。

秩无关的卷积问题形状 (Rank Agnostic Conv Problem Shape)

CUTLASS 3.x 的卷积使用了一个面向用户的、与秩无关(rank agnostic)的参数。

Page 12
Page 12

映射到面向核函数的步长 (Kernel Facing Strides)

如何将卷积的布局标签(layout tags)映射到 CuTe 的步长(strides)?

Page 13
Page 13

这些是布局标签,而不是直接的步长定义。CUTLASS 3.x 需要一种更通用的方式来处理步长。

分层布局的力量 (Power Of Hierarchical Layouts)

在 CUTLASS 3.x 中,GEMM 被视为张量收缩(GETTs)的一种伪装。布局和步长是分层组合的。

Page 14
Page 14

你所需要的一切就是 GETT (GETTs Are All You Need)

卷积只是通用张量收缩(GETT)的一种特殊情况。

Page 15
Page 15

Hopper TMA Im2Col

用于卷积的快速简单数据移动

Page 16: 使用TMA im2col加载两个具有非平凡遍历步长的图像的示例。
Page 16: 使用TMA im2col加载两个具有非平凡遍历步长的图像的示例。

CUTLASS 3.5: 卷积支持

适用于使用 TMA Im2Col 和 WGMMA 的 Hopper 架构

Page 17: GEMM、CONV 和 GETT 关系图。
Page 17: GEMM、CONV 和 GETT 关系图。

面向大语言模型 (LLMs) 的特性

训练和部署 LLMs 需要在 GEMM 之上进行优化

大语言模型的训练和部署需要基于通用矩阵乘法(GEMM)进行额外优化。主要体现在两个方面:专家混合(Mixture of experts)和权重化(Weight quantization)。

Page 19: MoE 和权重化优化示意图。
Page 19: MoE 和权重化优化示意图。

专家混合 (MoE) 实现计算高效的 LLMs

MoE 模型的计算需求如下:

Page 24: MoE 模型的计算需求及解决方案。
Page 24: MoE 模型的计算需求及解决方案。

分组 GEMM (Grouped GEMM)

传统的串行执行方式在处理多个不同大小的 GEMM 时,会导致部分计算单元(CTA)空闲,GPU 利用率不高。

分组 GEMM 通过一次内核启动(single kernel launch)来执行一组 GEMM。它使用持久化的 CTA(persistent CTAs),这些 CTA 可以在多个小的计算问题之间动态分配工作负载,从而有效利用计算资源。

Page 29: Grouped GEMM 工作原理示意图。
Page 29: Grouped GEMM 工作原理示意图。

在 Hopper 架构上使用 Grouped GEMM

以下代码片段展示了如何在 CUTLASS 中为 Hopper 架构配置 Grouped GEMM。通过使用指向布局的指针(如 LayoutC*, LayoutA*, LayoutB*),可以在运行时为组内的每个 GEMM 指定不同的大小和数据布局。这是实现 Grouped GEMM 灵活性的关键。

Page 30: 用于 Hopper 的 Grouped GEMM CUTLASS 代码示例。
Page 30: 用于 Hopper 的 Grouped GEMM CUTLASS 代码示例。

Hopper架构下的分组GEMM增强:可修改的TMA描述符

Hopper的张量内存加速器(Tensor Memory Accelerator, TMA)需要使用描述符来执行拷贝操作。对于不同的GEMM(通用矩阵乘法)类型,其工作方式有所不同:

工作流程对比如下:
- 单个GEMM:描述符在主机端构建,并通过启动参数复制到设备端。
- 分组GEMM
- CUDA 12.3中引入了tensormap.replace PTX指令,用于在设备端更新TMA描述符。
- A和B的占位符TMA描述符在主机端构建,并通过内核启动参数传递。
- 每个协作线程块(CTA)在全局内存中创建这些占位符TMA描述符的副本。
- 每当遇到一个新问题(即组中的新GEMM),描述符的地址、形状和步长就会被更新。

Page 31
Page 31

CUTLASS中当前对分组GEMM的支持

Page 32
Page 32

用于LLM量化的混合输入GEMM

为了显著减少内存占用和带宽,大型语言模型(LLM)的量化采用了混合输入GEMM。

其核心机制是在执行Tensor Core操作之前,将“窄”数据类型(如INT4)转换为“宽”数据类型(如FP16)。

Page 33
Page 33

混合输入GEMM节省全局和共享内存

混合输入GEMM通过在数据传输的后期阶段进行类型转换来优化内存使用。权重等“窄”类型数据以其原始紧凑格式从全局内存加载到共享内存。从共享内存移动到寄存器文件以供Tensor Core MMA(矩阵乘法累加)单元使用之前,会进行优化的类型转换。这个过程节省了宝贵的全局内存和共享内存空间。

Page 34
Page 34

CUTLASS对混合输入GEMM的支持

CUTLASS 3.x通过其模板化和可组合的接口,极大地简化了混合输入GEMM的实现。对于一个标准的FP16 GEMM,其CollectiveMainloop的类型定义如下所示。

Page 35
Page 35

要将其转换为混合输入GEMM(例如,INT4权重和FP16激活值),只需将第一个操作数的类型从cutlass::half_t更改为cutlass::int4b_t即可,如下面的代码高亮部分所示。

Page 36
Page 36

支持多种混合输入组合

CUTLASS支持广泛的混合输入数据类型组合,只需在CollectiveBuilder中指定ElementAElementB的类型即可。支持的组合包括:
- FP16 x INT8
- FP16 x INT4
- BF16 x INT8
- FP8 x INT4
- INT8 x FP16
- INT8 x BF16
- INT4 x FP8
- INT2 x FP16
- INT1 x FP8
- 等等

要求:
- 必须存在针对更宽数据类型的Tensor Core指令。
- 操作数必须满足TMA的要求。

Page 37
Page 37

CUTLASS中当前对混合输入GEMM的支持情况

Page 38
Page 38

Epilogue Visitor Tree (EVT)

常见的 workloads 调用各种 epilogues

在深度学习中,许多工作负载在GEMM计算之后会调用一系列操作,这些操作统称为“epilogue”(结尾部分)。为了获得高性能,将epilogue与GEMM融合成一个单一的内核至关重要,这可以避免将中间结果写回全局内存。常见的融合模式包括:

Page 40
Page 40

为每一种融合编写新内核非常繁琐

传统的融合内核开发工作流程效率低下:
- 先前的工作流程
- 确定放置定制逻辑的位置。
- 复制现有的GEMM和epilogue文件进行修改。
- 对每个需要添加的新融合操作重复此过程。

Page 41
Page 41

Epilogue Visitor Tree (EVT): 用于组合融合epilogue的构建模块

Epilogue Visitor Tree (EVT) 是解决上述问题的方案。它提供了一组可以组合在一起构建复杂epilogue的原始节点。
- 节点类型
- Load(加载): 累加器、辅助张量、行广播、列广播、标量广播。
- Compute(计算): 元素级、二元、三元计算。
- Store(存储): 辅助张量、行规约、列规约、标量规约。

Page 42
Page 42

在CUTLASS 3.x中向GEMM添加epilogue visitor tree (C++)

下面通过一个例子展示如何在CUTLASS 3.x中使用EVT构建一个更复杂的epilogue:ReLU((alpha * accumulators) + bias + (beta * C))

首先,这是该操作的计算图。

Page 43
Page 43

接下来,我们逐步构建这个图。第一步是实现 (alpha * accumulators) + bias。这通过定义AlphaAccumBias的加载节点,然后将它们输入一个MultiplyAdd计算节点来完成。

Page 44
Page 44

然后,我们构建图的下一部分,即添加 (beta * C)。这通过定义BetaC的加载节点,并将它们与上一步的结果EVTCompute0一起输入到另一个MultiplyAdd计算节点EVTCompute1

Page 45
Page 45

为了在 CUTLASS 3.x 中使用 C++ 为通用矩阵乘法(GEMM)添加一个 Epilogue Visitor Tree (EVT),我们可以通过组合不同的计算节点来构建一个自定义的 epilogue 操作。以下示例演示了如何实现 ReLU(alpha * accumulators) + bias + (beta * C) 这个融合操作。

首先,定义计算图中的各个节点,如 AlphaAccum(累加器)、Bias 等,并定义它们之间的计算关系(例如 MultiplyAdd)。

Page 46
Page 46

然后,使用 CollectiveBuilder 将这些节点组合成一个完整的 CollectiveEpilogueCollectiveBuilder 负责根据指定的硬件架构(如 sm90)、操作类型、瓦片形状(TileShape)和 EVT 输出节点来生成最终的 epilogue 操作。

Page 47
Page 47

用于常见模式的预置别名 (Pre-baked aliases)

为了简化开发,CUTLASS 为常见的 epilogue 模式提供了预置的别名。例如,前面几步中手动构建的复杂 EVT 树,可以被一个名为 Sm90LinCombPerRowBiasEltAct 的单一别名所替代。这大大减少了样板代码,提高了可读性。

Page 48
Page 48

Python 中的 Epilogue Visitor Tree

CUTLASS 3.x 同样在 Python 接口中支持 Epilogue Visitor Tree,使得定义复杂的融合操作变得更加简单直观。整个过程可以分为以下五个步骤:

  1. 声明一个基本的 GEMM 操作: 使用 cutlass.op.Gemm 定义一个标准的 GEMM 计算。
  2. 将 epilogue 定义为一个 Python 函数: 编写一个标准的 Python 函数 my_epilogue,其内部实现了所需的融合计算逻辑,例如 D = relu(alpha * accum + beta * C + bias)
  3. 定义每个 EVT 操作数/输出的类型和形状: 为 epilogue 函数中用到的所有张量(如累加器、C、D、偏置)提供示例输入,包括它们的形状和数据类型。
  4. 构造 EVT 并将其分配给 GEMM: 调用 cutlass.epilogue.trace 函数,传入 epilogue 函数和示例输入。CUTLASS 会追踪这个 Python 函数的执行,并将其转换为一个 EVT 计算图,然后将其赋值给 GEMM 计划的 epilogue_visitor 属性。
  5. 编译并运行核函数: 定义核函数的运行时参数,然后调用 plan.run() 来执行编译好的、带有自定义 epilogue 的 GEMM 核函数。
Page 49
Page 49

EVT 如何自动编写优化的 Epilogue 循环

Epilogue Visitor Tree (EVT) 的一个核心优势是它能自动处理优化后的 epilogue 循环代码的生成,将用户定义的计算逻辑无缝注入到底层的 CUDA 核函数中。

下图展示了一个典型的消费者存储(Consumer store)warpgroup 伪代码,它负责处理 epilogue 阶段的计算和存储。这个循环遍历所有的 epilogue 子瓦片(subtiles),从共享内存(smem)加载累加器片段,执行 epilogue 计算,然后将最终结果写回到全局内存。

Page 50
Page 50

当使用 EVT 时,用户定义的计算图会被转换成一系列的回调函数(callbacks),这些回调函数被插入到 epilogue 循环的不同阶段。如下图高亮部分所示,callbacks.begin(), callbacks.previsit(...), callbacks.visit(...), callbacks.reduce(...) 等回调函数在循环的不同位置被调用,从而执行用户自定义的融合操作,而无需用户手动编写底层的循环和访存逻辑。

Page 51
Page 51

CUTLASS 中 Epilogue Visitor Tree 的支持情况

Page 52
Page 52

教程:使用 CUTLASS 编写自定义 CUDA 核函数

Ampere Gather/Scatter 卷积

这是一个使用 CUTLASS 编写自定义 CUDA 核函数的教程。

下图展示了空间稀疏卷积的原理,输入 x 和权重 w 进行卷积,只在特定的位置进行计算,生成稀疏的输出。

Page 54
Page 54

步骤 0: 核函数 API

本教程将指导如何使用 CUTLASS 编写自定义 CUDA 核函数。第一步是设置设备端代码,使其能够原生接受 CuTe 张量。下面是一个卷积函子 Conv functor 的模板定义,其 operator() 接受代表滤波器(mFlt)、激活(mAct)和输出(mOut)的 CuTe 张量作为参数。

Page 55
Page 55

步骤 1: Ampere 稠密 3D 卷积

利用 CUTLASS 3.x API 的组合能力,可以实现稠密 3D 卷积。

Page 56
Page 56

步骤 2: 表示 Gather/Scatter 张量

利用 CuTe 布局的强大表示能力来处理稀疏性。

Page 57
Page 57

步骤 3: 与现有的 Collective 组合

利用 CUTLASS collectives 的组合能力。

Page 58
Page 58

步骤 4: 优化

利用领域特定信息进行性能优化。

Page 59
Page 59

亲手实践

以下是一些使用 CUTLASS 3.x 自定义融合的案例研究,可供参考:

Page 60
Page 60

结论与路线图

结论:CUTLASS

CUTLASS GitHub: https://github.com/NVIDIA/cutlass/

Page 61 - CUTLASS 层次结构图
Page 61 - CUTLASS 层次结构图

路线图

注:可能会有变动

Page 62 - 路线图详情
Page 62 - 路线图详情