Jie Fang (方杰) NVIDIA GPU加速计算专家团队、高级工程师
Yuxi Chi (池宇希) NVIDIA GPU加速计算专家团队、高级工程师
CuTeDSL
CuTeDSL 作为 Tilelang 的代码生成目标 (CodeGen Target)
CUTLASS (CUDA Templates for Linear Algebra Subroutines) 是一个高性能的 CUDA C++ 模板库,它提供了一套用于生产力和性能的抽象,适用于所有范围和规模。该项目是开源的,可在 https://github.com/NVIDIA/cutlass 获取。
其架构分层,从提供更多预调优方案(recipes)的上层到底层提供更多控制(control)的原子操作。
C++ 模板编译时间过长
C++ 模板不方便
深度学习领域全面拥抱 Python 生态系统
大语言模型(LLM)可能更擅长生成 Python 程序。
这些问题引出了一个核心疑问:我们是否必须忍受 C++ 的这些不便?
在接口和概念上与 CuTe C++ 保持一致
告别 C++ 模板!
显著简化与 Python 框架的集成
与 C++ 相比,CuTeDSL 的编译速度显著提升了30至100倍。下图展示了在 Blackwell 架构上,Cutlass C++ 与 CuTe DSL 在不同应用场景下的编译时间对比。
尽管编译速度大幅提升,CuTeDSL 在数学计算效率上仍能与 C++ 实现相媲美。
下图比较了在 Blackwell 架构上,不同问题规模下 C++ 和 DSL 的数学计算效率(Math Efficiency %)。数据显示,在稠密GEMM、分组GEMM以及Flash Attention等多种场景下,DSL 的性能与 C++ 基本持平,甚至在某些情况下略有优势。
CuTeDSL 在生产力(productivity)和性能(performance)之间取得了独特的平衡。与 Torch 和 Triton 等更高层次的抽象相比,CuTeDSL 提供了接近 CUDA/PTX 的极致性能,同时保持了比直接编写底层代码更高的生产力。
下表总结了不同工具的性能和上手时间:
| 工具 | 性能 (内存密集型) | 性能 (计算密集型) | 上手时间 |
|---|---|---|---|
| Torch compile | ~90% | ~70-80% | 小时-天 (hours-days) |
| Triton | ~90% | ~80-90% | 天-周 (days-weeks) |
| Cute-DSL | 100% | 100% | 周-月 (weeks-months) |
该图表引用自 tri dao 的幻灯片。
CuTeDSL 通过支持 DLPack 协议,能够与 PyTorch 等深度学习框架无缝集成。它支持从 torch tensor 的隐式转换,并提供了 mark_layout_dynamic 和 mark_compact_shape_dynamic 等接口来处理动态的布局和形状。
CuTeDSL 支持 JIT 编译结果的缓存,允许用户定义自己的缓存机制,从而避免重复编译,进一步加快开发和执行速度。
CuTeDSL 提供了强大的打印功能,用于调试。与 Python 的 print 函数在编译时打印静态值不同,cute.printf 可以在运行时同时显示静态和动态值,这对于调试 GPU kernel 至关重要。
CuTeDSL 的编译器能够生成高效的软件流水线代码,其性能与专家手写的版本相当。下图比较了在不同问题规模和分块配置下,手写代码与编译器生成的代码在数学计算效率上的表现,两者几乎没有差异。
从最初发布的12个示例,CuTeDSL 已经扩展到最新的31个示例。示例库的增长反映了其功能的丰富和对更多新架构(如 Blackwell, Hopper)和新模型(如 Mamba2, Flash Attention)的支持。
CuTeDSL 已被业界广泛采用:
flashinfer 中。下图展示了 FlashAttention 4 在 Blackwell 架构上的性能。在 B200 GPU 上,对于前向传播(causal=True, headdim=128),FA4 的 TFLOPS 性能显著优于 cuDNN,尤其是在序列长度较长时。
CuTeDSL 使客户和框架能够轻松采用和自定义其他内核。下图展示了 QuACK 在内存密集型操作(RMSNorm, Softmax, Cross-Entropy)上的性能,并与 torch.compile、Liger Kernel 和 cuDNN 进行了比较。QuACK (基于 CuTe DSL 构建) 在 RMSNorm 和 Softmax 上表现出优越的性能。
未来的发展计划包括:
- 更多生产就绪的示例:
- Blackwell GEMM + COMM via NVSHMEM API
- Blackwell FP16/FP8 稀疏 GEMM
DSL 特性:
框架集成:
CUDA Dialect
基于 Tile 的 DSL 采用分层编程模型来分解计算任务:
- Grid level (网格层): 代表整个工作负载。用户仅控制网格级别的操作。
- Block level (块层): 网格被划分为多个块(Tiles)。用户需要控制任务的划分以及对 Tiles 的操作。
- Thread level (线程层): 块被映射到线程。用户需要控制任务划分、线程映射以及线程级别的操作。
下表比较了 OpenAI Triton、Tilelang 和 CuTeDSL 在不同抽象层次上的控制粒度。CuTeDSL 提供了最显式和手动的底层控制,而 Triton 提供了最高层次的自动化。
| 特性 | OpenAI Triton | Tilelang | CuTeDSL |
|---|---|---|---|
| 内存分配 | 自动 | 自动 T.alloc_xxx |
GMEM, SMEM, TMEM, RF |
| 内存布局 | 自动 | 自动 T.annotate_layout |
cute.Layout |
| 内存移动 | 自动 | T.Copy |
cute.CopyAtom |
| 计算策略 | 自动 | T.Gemm |
cute.MmaAtom |
| 流水线 | stages | stages | 手动 |
Tilelang 是一个硬件无关的张量中间表示(Tensor Intermediate Representation, TIR),具有以下特点:
- 显式控制: 允许对内存分配和内存移动进行显式控制。
- Tile 索引: 提供在 Tile 内进行索引的能力。
- 代码注入: 支持 C++ 和 PTX 指令注入。
- 原生代码输出:
- 适用于 NVIDIA GPUs (Volta, Ampere, Hopper 等) 的 CUDA C++
- 适用于 AMD GPUs (MI300, MI250 等) 的 HIP C++
- 适用于 CPUs 的 C++/llvm
- 适用于其他加速器的原生代码
下图展示了从初学者到专家的编程抽象层次,从高层的 Tile 程序到底层针对特定硬件(NVIDIA/AMD GPUs)的可执行文件。
Tilelang 提供了高层原语(如 T.alloc_shared, T.gemm),通过 tilelang.compile 将高级 Tilelang 代码降级为更详细的 TIR 代码。TIR 包含了如 T.vectorized, T.unroll 等更底层的原语。
以下是针对不同硬件架构的 TIR 代码生成示例:
CopyAtom 和 MmaAtom,并生成相应的 TIR 代码。TIR 原语最终会被重写为 CUDA 代码。
- 内存分配和 Mbarrier 初始化: TIR 中的内存分配和屏障创建原语会映射到 CUDA 中的 __shared__ 内存声明和 mbarrier 初始化函数。
- CUDA API 包装器: https://github.com/tile-ai/tilelang/tree/main/src/tir/templates/cuda
__tma_load, cute::gemm 等。Tilelang 使用 CuTeDSL 作为后端,以生成高效的设备代码。该后端的功能分解如下:
- Python 代码生成 (Host code):
- 生成主机端代码,包括 JIT 包装器、内核启动配置(grid, block, smem)和内核调用。
- 管理 TMA 描述符。
cp.async, ld/st.matrix)和 Hopper 特性(TMA ld/st, mbarrier, Warp Specialization, wgmma.async)进行矢量化操作。下图展示了 Python 代码如何调用后端,将 PyTorch 张量转换为 CuTe 张量,并启动编译后的 CUDA 内核。
下面是一个将 Tilelang 代码转换为 CuTeDSL 代码的具体示例:
此页面展示了如何使用 TileLang 编写一个通用的矩阵乘法(GEMM)内核,并将其编译为针对 NVIDIA Ampere 架构优化的 CuTeDSL 代码。
左侧:TileLang 代码
gemm 函数,接收张量 A, B, C 作为输入。T.celldiv 用于在线程块之间划分工作。T.alloc_shared 为 A 和 B 的分片分配共享内存 (A_shared, B_shared)。T.alloc_fragment 为 C 的分片分配寄存器文件内存 (C_local)。T.piplelined 循环是实现软件流水线的主体,用于重叠计算和数据加载。T.copy(..., async=True): 从全局内存异步复制数据到共享内存(cp_async G2S)。T.gemm: 在共享内存中的数据上执行矩阵乘法(mma GEMM)。T.copy: 将计算结果从寄存器文件写回全局内存(STG)。右侧:生成的 CuTeDSL 代码
cutlass.cute 和 cutlass.core 库。gemm_kernel 内核。tl.Thread, tl.make_fragment, tl.copy_async, tl.gemm 等,精确地映射了 Ampere 架构的硬件特性。Ampere 风格流水线 (Ampere style Pipeline)
主循环 (Mainloop): k-Loop 从 0 到 #tiles - (#stages-1)。
尾声 (Epilogue): 等待并处理最后 #stages-1 个分片。
此页面展示了针对 NVIDIA Hopper 架构的 FP8 GEMM 的一个更复杂的示例。
左侧:TileLang 代码
main 函数,其参数更复杂,以支持 Hopper 架构的特性。T.Prefetch: 使用张量内存加速器(TMA)进行数据预取。T.wgmma: 使用 Hopper 架构的 Warp Group Matrix-Multiply-Accumulate 指令进行计算。T.Pipelined 实现了双缓冲流水线,一个 warp 组负责数据加载(TMA warp),另一个 warp 组负责计算(GEMM warp)。右侧:生成的 CuTeDSL 代码
tl.mbarrier_init (多线程屏障初始化), tl.prefetch_tma_descriptor (TMA 预取描述符), tl.dot (wgmma 的 CuTeDSL 封装)。Hopper 风格的 Warp-Specialized 流水线 (Hopper style Warp-specialized Pipeline)
TMA warp: 循环处理 #stages 缓冲区。
GEMM warp: 循环处理 #stages 缓冲区。
此页面通过实验数据对比了 CuTeDSL 后端和传统的 CUDA 后端在编译时间和内核运行时间上的表现。
性能对比表
| 示例 | 后端 | 首次编译时间 | 内核运行时间 |
|---|---|---|---|
| Elementwise Add | CUDA | 11.81s | 9.622 ± 0.038 µs |
| CuTeDSL | 0.68s | 9.974 ± 0.040 µs | |
| Ampere Gemm Float16 M=N=K=8192 |
CUDA | 15.43s | 6.042 ± 0.234 ms |
| CuTeDSL | 0.59s | 5.498 ± 0.260 ms | |
| Hopper Gemm Float16 M=N=K=8192 |
CUDA | 15.38s | 3.006 ± 0.035 ms |
| CuTeDSL | 0.66s | 3.034 ± 0.032 ms |
关键结论:
备注:
本页总结了将 CuTeDSL 作为 TileLang 代码生成目标的优势。
使用 Tilelang 作为前端 DSL
使用 CuTeDSL 后端
集成
进一步加速