Deepseek篇--开源技术DeepGEMM详解

232 阅读10分钟

2 月 24 日,DeepSeek 启动 “开源周”,第三个开源的代码库为 DeepGEMM,并向 CUTLASS 团队致敬。DeepGEMM 使用了大量与 Hopper 架构绑定的技术。毫无疑问,H100/H800 绝对是 DeepSeek 的小心肝。另外,从 Day1 到 Day3 的几个创新,都不是一般算法设计能完成的工作,必须要同时对 LLM、CUDAPTX、GPU 架构有深入了解才能迅速跟进这类技术。

1 DeepGEMM 简介

DeepGEMM 是一个支持 FP8 精度的高性能 GEMM(通用矩阵乘法)库,适用于矩阵和 MoE(Mixture of Experts,专家混合模型)计算。DeepGEMM 具有细粒度缩放功能,采用 CUDA 编写,使用轻量级即时 (JIT) 模块在运行时编译所有内核,PTX 和 SASS 优化轮番上。

细粒度量化与提高累加精度(来源:DeepSeek)

DeepGEMM 反映了 “OpenSeek” 推动 AGI 探索的承诺。X 的 帖子强调其 “无过多依赖”“像教程一样简洁”,这与 DeepSeek 的“社区驱动创新” 理念一致。通过开放 GEMM 底层工具,DeepSeek 不仅展示技术实力,还为 CUTLASS 研究者和开发者提供了高性能计算的参考实现。

DeepGEMM 在某些矩阵尺寸下比 CUTLASS 快 2.7 倍,尤其在 MoE 模型的分组 GEMM 上表现优异。这是非常显著的优势。

项目地址为:

GitHub - deepseek-ai/DeepGEMM: DeepGEMM: clean and efficient FP8 GEMM kernels with fine-grained scaling

与前几个开源类似,DeepGEMM 同样需要 Hopper 架构 GPU,CUDA 12.3 或更高版本。

2 GEMM 与 TensorCore

2.1 GEMM

GEMM(General Matrix Multiplications)即通用矩阵乘法,是将两个矩阵的进行相乘的计算。这种方法称为一般矩阵乘法 (GEMM)。科学计算库(如 Numpy、BLAS 等)和大模型都使用了 GEMM。此实现仅适用于方阵。这样做是为了避免使算法过于复杂而无法处理矩形矩阵。

标准 GEMM 示例(来源:互联网)

在 GPU 中,GEMM 定义为运算 C=αAB+βC

其中 A 和 B 作为矩阵输入,α 和 β 作为标量输入,C 作为预先存在的矩阵,被输出覆盖。普通矩阵乘积 AB 是 α 等于 1 且 β 等于 0 的 GEMM。例如,在全连接层的正向传递中,权重矩阵为参数 A,传入激活为参数 B,α 和 β 通常分别为 1 和 0。在某些情况下,β 可以是 1。

GPU 通过将输出矩阵划分为图块来实现 GEMM,然后将其分配给线程块。图块大小(Tile Size)通常是指这些图块的尺寸。每个线程块通过单步执行图块中的 K 维度,从 A 和 B 矩阵加载所需的值,然后将它们相乘并累加到输出中来计算其输出图块。

GPU 中 GEMM 的一般计算方法(来源:互联网)

2.2 Tensor Core

英伟达 GPU 引入了 Tensor Core(张量核心) 来最大限度地提高 GEMM 的速度。使用 Tensor Core 的要求取决于 英伟达库的版本。

Tensor Core 基本结构(来源:英伟达)

第一代 Tensor Core 是随 Volta 架构引入的,从 V100 开始,随着数据格式的变化,Tensor Core 也在不断更新。

Tensor Core 支持的数据格式(来源:英伟达)

GEMM 的实现效率与 Tensor Core 结构和数据格式密切相关,受数据的调度方式影响很大。因此基于 Tensor Core 的硬件架构进行计算优化就显得十分重要。好的优化往往能取得数倍的性能提升。

3 DeepEP 的关键技术与未来优化

FP8 的精度改进(来源:中存算半导体)

DeepGEMM 主要特点包括:

  • FP8 支持优化:DeepGEMM 采用了 CUDA 核心两级累加。(换句话说这也是 NV TensorCore 的设计不足之处)FP8 是一种低比特浮点格式,能够在保持一定计算精度的同时大幅提升计算效率。FP8 在大量累加时会累积出现随机误差。例如 FP8 GEMM 在英伟达 H800 GPU 上的累加精度保留 14 位左右,明显低于 FP32 累加精度。以 K= 4096 的两个随机矩阵的 GEMM 运算为例,Tensor Core 中的有限累加精度可导致最大相对误差接近 2%。DeepSeek 将中间结果储存计算升级为 FP32(32 位浮点),实行高精度累加,然后再转换回 FP8,以降低大量微小误差累加带来的训练偏差。
  • 支持分组 GEMM:与 CUTLASS 中传统的分组 GEMM 不同,DeepGEMM 仅对 M 轴进行分组,而 N 和 K 可保持不变。(可专门针对 MoE 模型中的专家量身定制)
  • 即时编译 Just-In-Time , JIT :通过 JIT 技术,代码可以在运行时动态生成和优化,进一步提升性能和灵活性。这也是跟 Cutlass 的最大区别。
  • 支持 Hopper 架构中的 TMA 加速:包括 LHS、LHS 缩放因子和 RHS 矩阵的 TMA 负载,TMA 存储输出矩阵,TMA 多播(LHS 矩阵特有)
  • 使用 PTX 指令 进行性能优化:使用 stmatrix PTX 指令。
  • FFMA SASS 交错:DeepSeek 深入分析了 SASS 编译结果,在 FFMA/FADD 中调整 SASS 指令,提高了细粒度 FP8 GEMM 的性能。(这一点很有趣,说明 DeepSeek 的编译 / 反编译团队做活儿很细,已然不是普通牛马)
  • 高性能:在 Hopper GPU(例如 H100)上,可达到 1350+ TFLOPS 的 FP8 计算性能,这表明 DeepSeek 针对 Hopper 进行了深度优化。(把英伟达编译团队该干的活儿干了)特别是对 Dense 模型的加速比 MoE 更明显。
  • 仍在发展:DeepGEMM 在某些形状上的表现并不是很好。

最后致敬了 Cutlass 团队:估计是英雄爱英雄,github 情谊四射。

4 DeepGEMM 架构分析

DeepGEMM 明确仅支持 NVIDIA Hopper 架构的张量核心,依赖 H100 等 GPU 的 FP8 计算能力。FP8 是一种低比特浮点格式,相比 FP16 或 FP32,可以显著提高计算吞吐量,但精度较低。

1)两级累加结构

为解决 FP8 张量核心累加的精度不足,DeepGEMM 使用了 CUDA 核心进行两级累加)。这种设计在保持高性能的同时,弥补了硬件本身的局限性。

2) JIT 即时编译 )设计

与其他传统 GEMM 库(如 CUTLASS)需要预编译不同,DeepGEMM 的 JIT 设计允许在运行时动态生成内核。这带来以下优势:

  • 灵活性:无需为不同矩阵大小或硬件配置预先编译多个版本
  • 简便性:用户安装时无需复杂依赖或编译环境。
  • 性能优化:JIT 可以根据实际输入动态调整代码,可能提升缓存命中率或指令调度效率。

在 JIT 中:

  • GEMM 形状、块大小和流水线阶数被视为编译常量,从而可能获得更多优化。
  • 可自动选择区块大小、warpgroups 数量、最佳流水线阶段和 TMA cluster 大小
  • 展开 MMA 流水线,可使编译器进行更多优化

3) 三种 GEMM 类型支持

DeepGEMM 支持两种主要 GEMM 类型:

  • 常规稠密 GEMM:通过函数 deep_gemm.gemm_fp8_fp8_bf16_nt 调用,适用于常规矩阵乘法。
  • 分组 GEMM(连续布局, Contiguous Layout :针对 MoE 模型优化,仅对 M 轴分组,N 和 K 保持固定。这种设计适用于 MoE 专家共享相同形状的情况。将多个专家的 token 拼接成单一连续张量,适用于训练前向或推理预填充阶段。每个专家段需对齐到 GEMM 的 M 块大小。
  • 分组 GEMM(掩码分组, Masked Grouped GEMM ): 支持推理解码阶段,结合 CUDA Graph,适应动态 token 分配。这种分组策略与 CUTLASS 的传统分组 GEMM 不同,体现了 DeepGEMM 对 MoE 模型的针对性优化。

4) 调度优化

DeepGEMM 遵循 CUTLASS 设计, 其内核为 warp 专用,支持重叠式的数据移动、张量核心 MMA 指令和 CUDA 核心优化。

  • TMA( Tensor Memory Accelerator :Hopper 架构的硬件特性,用于异步数据加载或移动(如 LHS 矩阵、缩放因子等),减少内存访问延迟。
  • 指令 重叠:内核采用 warp-specialized 设计,允许数据移动、张量核心 MMA(矩阵乘加)指令和 CUDA 核心累加操作重叠。
  • FP8 微调:通过修改编译后二进制的 FFMA(融合乘加)指令,调整 yield 和 reuse 位,进一步提升性能(据称在某些情况下提升 10%+)。
  • 区块调度器:通过统一的调度器调度所有非分组和分组内核,栅格化(Rasterization )以增强 L2 缓存的复用 / 重用。

这些优化使得 DeepGEMM 在大多数矩阵大小上优于专家调优的内核,同时保持代码简洁。

DeepGEMM 的 Warp 优化(来源:DeepSeek)

与其他库相比

  • CUTLASS:功能强大但代码复杂,依赖大量模板和预编译,适合通用场景。
  • CuTe:专注于张量操作的抽象,灵活但需要较深理解。
  • DeepGEMM:专注于 FP8 和 Hopper,代码简洁,易于学习和修改,适合特定需求(如 DeepSeek-V3 的 MoE 训练)。

5 DeepGEMM 代码结构

github.com/deepseek-ai…

DeepGEMM 提供了多个接口函数,包括:

  • 常规稠密 GEMM:deep_gemm.gemm_fp8_fp8_bf16_nt
  • 分组 GEMM(连续布局):m_grouped_gemm_fp8_fp8_bf16_nt_contiguous
  • 分组 GEMM(掩码布局):m_grouped_gemm_fp8_fp8_bf16_nt_masked,适用于推理解码。

环境变量包括:

  • DG_CACHE_DIR:默认 $HOME/.deep_gemm
  • DG_NVCC_COMPILER:默认从 PyTorch 获取
  • DG_DISABLE_FFMA_INTERLEAVE:0 或 1
  • DG_PTXAS_VERBOSE:0 或 1
  • DG_PRINT_REG_REUSE:0 或 1
  • DG_JIT_PRINT_NVCC_COMMAND:0 或 1
  • DG_JIT_DEBUG:0 或 1

/include/deep_gemm:存放主要的 DeepGEMM CUDA 计算库。

fp8_gemm.cuh:主要实现了一个基于 FP8(8 位浮点)的分组通用矩阵乘法(GEMM)内核,用于在 NVIDIA GPU 上进行高效的矩阵乘法计算。

mma_utils.cuh:定义了一系列用于 CUDA 设备的结构体,这些结构体封装了不同形状的矩阵乘法(GEMM)操作,使用了 Warp Group Matrix Multiply Accumulate(WGMMA)指令。

scheduler.cuh:定义了一个模板结构体Scheduler,用于调度不同类型的矩阵乘法(GEMM)操作。

tma_utils.cuh:一系列用于 CUDA TMA 加速的工具函数和模板,主要用于在 CUDA 编程中处理不同数据类型的张量映射和数据复制。

jit_kernels/ :主要的 JIT 计算库。

m_grouped_gemm.py:用于定义两个分组通用矩阵乘法(GEMM)函数,支持 FP8 输入和 BF16 输出,并且使用了即时编译(JIT)和自动调优功能。

gemm.py:用于实现 FP8(8 位浮点)输入和 BF16(16 位脑浮点)输出的矩阵乘法(GEMM)操作,并且使用即时编译(JIT)和自动调优来提高性能。

tuner.py:定义了一个名为JITTuner的类,用于即时编译(JIT)和自动调优 CUDA 内核代码。

jit / :定义 JIT 相关操作。

interleave_ffma.py:对 CUDA 生成的目标文件(.so文件)中的汇编代码进行处理,特别是针对其中的FFMA(Fused Fused Multiply-Add,融合乘加)指令段进行寄存器复用的修改。

6 DeepGEMM 的不足与未来

DeepGEMM 的现有不足:

  • 硬件依赖:仅支持 Hopper 架构,无法在 Volta、Ampere 等其他 NVIDIA GPU 上运行。
  • 功能有限:专注于 FP8 和 MoE,未提供 FP16/FP32 或更广泛的矩阵操作支持。
  • 文档不足:截至目前,README 提供了基本用法,但缺乏详细的 API 文档或性能测试数据。

DeepGEMM 是一个高效、简洁的 FP8 GEMM 库,专为 英伟达 Hopper 架构和 MoE 模型优化。其 JIT 设计、两级累加和高性能优化(如 TMA 和指令重叠)使其在 DeepSeek-V3 等项目中表现出色。