拓冰建站拓冰建站
首页 / 资讯中心 / 正文

CUTLASS 依赖内核启动(Programmatic Dependent Launch / PDL)实战指南:Hopper 与 Blackwell 架构的网格间重叠执行

CUTLASS 依赖内核启动Programmatic Dependent Launch / PDL实战指南Hopper 与 Blackwell 架构的网格间重叠执行【免费下载链接】cutlassCUDA Templates and Python DSLs for High-Performance Linear Algebra项目地址: https://gitcode.com/GitHub_Trending/cu/cutlassProgrammatic Dependent LaunchPDL是 HopperSM90与 BlackwellSM100 及后续架构引入的 GPU 特性允许同一 CUDA 流中先后启动的两个内核在满足全局内存依赖的前提下以编程方式安全地重叠执行从而让下游内核提前进入启动流程。本文以 CUTLASS 仓库中 media/docs/cpp/dependent_kernel_launch.md 为骨架结合仓库内 grid_dependency_control.h、kernel_launch.h 与 示例 63 的源码实现系统讲解 PDL 的原理、CUTLASS 中的启用方式、运行时调用参数以及基于 PDL 的模型感知优化读完后你可以独立为自己的 CUTLASS GEMM 应用接入依赖内核启动并调优重叠比例。PDL 是什么让同流中的内核安全重叠执行传统的 CUDA 流语义中同一流内的内核按顺序串行执行只有前一个内核完全结束、其写入全局内存的可见性得到保证后后一个内核才会开始。对于“归一化/规约内核紧接着 GEMM”这类典型推理管线这种严格的串行化会留下 DRAM 空闲窗口前一个内核已停止写入、后一个内核却仍在等待。Hopper 与 Blackwell 架构通过Programmatic Dependent LaunchPDL改变了这一局面同一条流中的两个内核可以部分重叠执行。其核心机制是主内核primary kernel在即将结束执行时通过硬件指令主动发出“我快要写完了”的信号依赖内核dependent kernel在真正访问全局内存之前编程式地等待前一个内核完成其内存刷新memory flush随后再开始自己的内存访问。这样两个存在全局内存依赖的内核可以把“前一个内核的尾部”和“后一个内核的头部”重叠起来既保证数据正确性又压缩了流水线空隙。CUTLASS 文档强调所有具备 PDL 支持的 CUTLASS 内核都会遵守这一协议——等待前一个内核刷新输出到内存、再向后一个内核发出启动信号——因此它们可以安全地与其他同样遵循“等待前一个内核刷新内存”协议的 PDL 内核混排在任意内核序列中。CUTLASS 中 PDL 的底层实现Grid Dependency ControlGDCPDL 在 CUTLASS 中的硬件级实现依赖Grid Dependency ControlGDC指令。相关辅助代码集中在 include/cutlass/arch/grid_dependency_control.h文件头部注释明确指出它是 “Grid dependent control (GDC) helpers for programmatic dependent launches (PDL)”。该头文件提供了两个设备端辅助函数// 通知依赖内核可以提前启动不影响功能正确性只影响性能 // 启动太早会与当前内核争抢资源启动太晚则引入长延迟。 CUTLASS_DEVICE void launch_dependent_grids() { #if (defined(CUTLASS_GDC_ENABLED)) asm volatile(griddepcontrol.launch_dependents;); #endif } // 强制本条指令之前不发起任何全局内存访问保证依赖内核提前启动时的内存访问正确性。 CUTLASS_DEVICE void wait_on_dependent_grids() { #if (defined(CUTLASS_GDC_ENABLED)) asm volatile(griddepcontrol.wait; ::: memory); #endif }源码注释揭示了两个关键设计点launch_dependent_grids()对应griddepcontrol.launch_dependents指令用于提示依赖内核更早启动。它不影响功能而只影响性能——启动过早会与当前内核竞争计算与带宽资源启动过晚则白白增加延迟wait_on_dependent_grids()对应griddepcontrol.wait指令它是一道内存栅栏保证其后的全局内存访问不会越过该指令从而确保在依赖内核提前启动的情况下读到的数据是前一个内核刷新后的结果。此外该头文件还导出一个编译期常量cutlass::arch::IsGdcGloballyEnabled供内核在运行时查询 GDC 特性是否开启grid_dependency_control.h。GDC 的启用条件CUTLASS_GDC_ENABLED宏的判定条件在 grid_dependency_control.h 中非常严格需要同时满足编译期显式定义了CUTLASS_ENABLE_GDC_FOR_SM90或CUTLASS_ENABLE_GDC_FOR_SM100CUDA 编译器主版本 ≥ 12__CUDACC_VER_MAJOR__ 12当前编译目标架构满足对应代数要求SM90 需要__CUDA_ARCH__ 900且具备__CUDA_ARCH_FEAT_SM90_ALLSM100 分支则覆盖__CUDA_ARCH__为 1000/1010/1030/1100/1200/1210 等 Blackwell 及后续架构家族。只有在这些条件全部满足时griddepcontrol指令才会被真正编入内核二进制。第一步编译期启用 GDC 宏要使用 PDL首先要在构建 CUTLASS 时打开对应的 GDC 宏。原文档给出的方式是cmake . -DCUTLASS_ENABLE_GDC_FOR_SM901在 CMakeLists.txt 中可以找到这两个开关的完整定义与默认值if (CUTLASS_ENABLE_GDC_FOR_SM90) message(STATUS Grid Dependency Control (GDC) is enabled for SM90 kernels (required for programmatic dependent launches).) list(APPEND CUTLASS_CUDA_FLAGS -DCUTLASS_ENABLE_GDC_FOR_SM901) endif() # SM100 的 GDC 默认开启 if (NOT DEFINED CUTLASS_ENABLE_GDC_FOR_SM100_DEFAULT) set(CUTLASS_ENABLE_GDC_FOR_SM100_DEFAULT ON) endif() set(CUTLASS_ENABLE_GDC_FOR_SM100 ${CUTLASS_ENABLE_GDC_FOR_SM100_DEFAULT} CACHE BOOL Enables Grid Dependency Control (GDC) for SM100 kernels (required for PDL).) if (CUTLASS_ENABLE_GDC_FOR_SM100) message(STATUS Grid Dependency Control (GDC) is enabled for SM100 kernels (required for programmatic dependent launches).) list(APPEND CUTLASS_CUDA_FLAGS -DCUTLASS_ENABLE_GDC_FOR_SM1001) endif()几点与源码一致的实操细节SM90 开关默认关闭SM100 开关默认开启从 CMake 逻辑看CUTLASS_ENABLE_GDC_FOR_SM100_DEFAULT被置为ON而 SM90 没有对应的默认开启逻辑。因此如果面向 HopperSM90a构建必须显式传-DCUTLASS_ENABLE_GDC_FOR_SM901该宏的作用范围是向所有 CUDA 编译单元追加-DCUTLASS_ENABLE_GDC_FOR_SM901或 SM100编译宏即只影响内核编译为内核加入 PDL 相关指令打开 GDC 后需要配合正确的目标架构编译例如面向 Hopper 的完整构建命令为cmake .. -DCUTLASS_NVCC_ARCHS90a -DCUTLASS_ENABLE_GDC_FOR_SM901该命令取自示例 63 的构建说明见 63_hopper_gemm_with_weight_prefetch.cu。编译宏与运行时指令的关系这里要特别澄清原文档强调的一点编译期宏只是“给内核加装 PDL 指令”真正让一次启动成为依赖启动还需要运行时显式指定。也就是说宏与launch_with_pdl参数是“硬件能力”与“软件行为”两层关系编译期宏决定内核二进制中是否包含griddepcontrol指令运行时参数决定这一次内核启动是否以 PDL 方式发出并等待/传递依赖信号。第二步运行时以 PDL 方式启动 GEMM编译完成后运行时只需在Gemm::run()中把第三个参数launch_with_pdl置为truegemm.run( /* stream */ stream, /* cuda_adapter */ nullptr, /* launch_with_pdl */ true );参数在适配器接口中的传递launch_with_pdl贯穿整个启动调用链。在 include/cutlass/gemm/device/gemm_universal_adapter.h 中可以看到run()的默认参数为bool launch_with_pdl falsegemm_universal_adapter.h即默认不启用当launch_with_pdl为true时代码会拒绝同时传入自定义 cuda adapter并报错 “GemmUniversal::run() does not support launching with PDL and a custom cuda adapter.”gemm_universal_adapter.h——即 PDL 与自定义 CUDA 适配器互斥最终该参数被透传给底层cutlass::kernel_launchGemmKernel(..., launch_with_pdl)gemm_universal_adapter.h。底层启动路径cudaLaunchKernelEx 与程序化流串行化属性PDL 启动的真正实现在 include/cutlass/kernel_launch.h 的cutlass::kernel_launch()中其逻辑分支清晰launch_with_pdl false走普通路径device_kernelGemmKernelgrid_dims, block_dims, smem_size, cuda_stream(kernel_params)launch_with_pdl true先做编译期约束检查如果内核的ArchTag::kMinComputeCapability 90即目标架构低于 SM90直接返回Status::kInvalid并打印 “Programmatic dependent launch (PDL) is only supported for SM90.”编译器版本要求 CUDA ≥ 11.8__CUDACC_VER_MAJOR__ 12或 11.8否则返回kInvalid通过扩展启动 APIcudaLaunchKernelEx发起启动并携带一个关键属性attrs[0].id cudaLaunchAttributeProgrammaticStreamSerialization; attrs[0].val.programmaticStreamSerializationAllowed 1;即把该内核标记为允许程序化流串行化programmatic stream serialization这正是 PDL 在启动层面的开关启动失败时返回Status::kErrorInternal并打印cudaGetErrorString的错误信息。这段实现印证了原文档“通过扩展的 CUDA 启动 API 设置一个标志位来启用 PDL”的说法且给出了确切的 API 名称与属性值可作为排查“PDL 未生效”问题的依据。模型感知优化示例 63 的 L2 权重预取原文档提到在 示例 63 中CUTLASS 利用 PDL 做了一个“模型感知”的显式性能优化。其应用场景非常具体当你知道某个输入矩阵例如权重 weights不会被前一个内核产生时就不必等待前一个内核刷新该矩阵而可以在等待期间提前把它从全局内存搬到 L2。示例 63 是一个面向低延迟推理的非持久non-persistentwarp-specialized GEMM目标典型场景是“归一化/规约内核之后紧跟 GEMM”的推理管线。源码注释63_hopper_gemm_with_weight_prefetch.cu把它的分工讲得很清楚约定A为权重、B为激活activations因此可以支持极小的 batch/token 数初始化之后预取 warp 开始把A的 K-tile 载入共享内存的未使用区域最多可提前加载该 CTA 最终会加载的一半 K-tile负责加载B的 DMA warp 会等待前一个内核把激活刷新到全局内存之后才开始关键指令的落点在 collective/sm90_mma_tma_gmma_ss_warpspecialized_with_prefetch.hppB的 producer warp 调用cutlass::arch::wait_on_dependent_grids()第 423、600 行而负责A预取的 producer warp 则按时机调用cutlass::arch::launch_dependent_grids()第 449、456、537、544 行从而做到“A的加载不等待、B的加载等待内存刷新”。两个运行时调优参数overlap_ratio 与 prefetch_ratio示例 63 暴露了两个运行时参数见 README.md它们分别对应gemm.run中launch_with_pdl的取值与预取深度参数含义默认值说明overlap_ratio多早发出griddepcontrol.launch_dependent_grids0.5约在 DMA warp 加载完一半 K-tile 时发出取值[0.0, 1.0]越小重叠越多越大重叠越少负值则完全禁用 PDL无重叠预取随之失效prefetch_ratio预取多少比例的 K-tile-1.0[0.0, 1.0]内越大预取越多负值表示“尽力而为”的预取即一旦激活 DMA warp 开始加载收到前一个内核刷新内存的信号就停止继续发权重加载在 63_hopper_gemm_with_weight_prefetch.cu 中可以看到两者的联动逻辑CUTLASS_CHECK(gemm.run(nullptr, nullptr, /* launch_with_pdl */ options.overlap_ratio 0));即只有当overlap_ratio 0时才以 PDL 方式启动内核与 README 中“负值禁用 PDL”的描述一致。示例提供的对照实验命令见 README.md./63_hopper_gemm_with_weight_prefetch --m8192 --n1 --k8192 # 无重叠、无预取 ./63_hopper_gemm_with_weight_prefetch --o-1.0 --p-1.0 # 重叠比例 0.5尽力预取 ./63_hopper_gemm_with_weight_prefetch --o0.5 --p-1.0 # 重叠比例 0.8预取比例 0.7 ./63_hopper_gemm_with_weight_prefetch --o0.8 --p0.7需要注意两点经验性结论源码注释与 README 均明确给出这两个参数需要针对具体问题规模与 GEMM 配置逐项自动调优理想情况下应针对整个 layer 甚至整个模型中的每个算子单独调优63_hopper_gemm_with_weight_prefetch.cu默认参数往往不是好选择当prefetch_ratio未指定即-1.0时预取 warp 在每次 TMA 加载前都会try_wait一个内存屏障很多情况下会把预取拖慢到几乎无效README.md。TMA 缓存提示的配合示例 63 还展示了与 PDL 配合的 TMA 缓存策略A权重使用EvictFirst缓存提示加载权重用完即弃B激活使用EvictLast激活会被复用、希望尽量保留在缓存。这一设计使得预取进 L2 的权重不会污染激活的缓存驻留是模型感知优化的另一层细节见 README.md。将 PDL 内核集成到自有目标如果你要在自己的工程里复用示例 63 的 PDL 预取 GEMMREADME 给出了最小集成步骤README.md将该示例目录加入 include 路径并包含三个头文件#include collective/dispatch_policy_extra.hpp #include collective/builder.hpp #include kernel/sm90_gemm_tma_warpspecialized_with_prefetch.hpp从两个新的 kernel schedule 中选择一个// A、B 不分离 warp using KernelSchedule cutlass::gemm::KernelTmaWarpSpecializedFP8FastAccumWithPrefetch; // A、B 分离 warp推荐性能更优 using KernelSchedule cutlass::gemm::KernelTmaWarpSpecializedFP8FastAccumWithPrefetchAndSplitDMA;分离 DMA warp 的版本之所以更优是因为它允许内核在griddepcontrol之前就把权重载入共享内存README.md。记住当前限制该内核尚不支持大于 1 个 CTA 的 Thread Block Cluster且其实现是 TMA warp-specialized 的换用其他 kernel layer 或 collective 需要重新实现。使用前提与注意事项综合原文档与仓库源码接入 PDL 时请对照以下约束架构要求仅 HopperSM90与 BlackwellSM100 及后续架构支持启动层代码对ArchTag::kMinComputeCapability 90的内核直接拒绝kernel_launch.h工具链要求CUDA 编译器需 ≥ 11.8推荐 ≥ 12否则cudaLaunchKernelEx路径不可用编译宏要求SM90 需显式传-DCUTLASS_ENABLE_GDC_FOR_SM901SM100 默认开启但同样可通过CUTLASS_ENABLE_GDC_FOR_SM100控制CMakeLists.txt运行时要求每次gemm.run()需显式传launch_with_pdl true且不能与自定义CudaHostAdapter同时使用gemm_universal_adapter.h正确性协议所有接入 PDL 的内核必须遵守“等待前一个内核刷新内存”的协议才能与其他 PDL 内核混排性能调优overlap_ratio/prefetch_ratio没有普适最优值需要针对每个 GEMM 配置做端到端自动调优且收益主要体现在端到端应用中单独跑单个 GEMM 很难观察到明显加速README.md。总结Programmatic Dependent Launch 为 CUTLASS 在同流内核间打开了一条“安全重叠”的路径编译期通过CUTLASS_ENABLE_GDC_FOR_SM90/CUTLASS_ENABLE_GDC_FOR_SM100为内核植入griddepcontrol.launch_dependents与griddepcontrol.wait指令运行时通过gemm.run(stream, nullptr, true)以cudaLaunchAttributeProgrammaticStreamSerialization属性发起依赖启动。示例 63 进一步演示了模型感知的用法——当权重不依赖前一个内核时在等待激活刷新的窗口内预取权重到 L2配合overlap_ratio与prefetch_ratio两个运行时参数为低延迟推理场景压缩内核间的流水线空隙。接入前请务必核对架构、编译器版本、GDC 宏与运行时参数四层前提并针对具体负载做自动调优。【免费下载链接】cutlassCUDA Templates and Python DSLs for High-Performance Linear Algebra项目地址: https://gitcode.com/GitHub_Trending/cu/cutlass创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门