【大模型学习cuda】cuda调度波次wave
传统 GEMM 按固定分块划分任务,当任务总数超过 SM 数时,必然需要多个 Wave。
传统 GEMM 计算可能因任务大小不均产生多个调度波次(Waves),导致 SM 间等待和资源闲置。什么意思?为什么导致SM间等待和资源闲置, 举例 ?
。在GPU中,计算任务被组织为线程块(Block),而线程块被调度到流多处理器(SM)上执行。一个wave(波次)是指GPU上同时运行的一组线程块,其数量等于GPU上SM的数量(因为每个SM一次只能处理一个线程块)。当所有线程块在同一个wave内执行时,它们几乎同时开始,同时结束,这样可以避免SM之间的等待。
然而,当GEMM计算任务的总线程块数量大于GPU的SM数量时,就需要多个wave(波次)来完成。每个wave执行完一部分线程块,然后下一个wave再执行另一部分。问题在于,不同的wave之间需要顺序执行(即一个wave完成后才能开始下一个wave),并且每个wave内的线程块执行时间可能不同,这就可能导致:
资源闲置:如果一个wave中的某些线程块先执行完,而其他线程块还在执行,那么先完成任务的SM就会闲置,直到整个wave完成(因为下一个wave必须等待当前wave的所有线程块完成才能开始)。
SM间等待:由于不同SM上分配的线程块可能计算量不同(例如,由于矩阵分块不均匀,或者每个线程块处理的数据量不同),导致完成时间不一致。这样,在同一个wave内,完成早的SM必须等待完成晚的SM,才能进入下一个wave。
FlashMLA的Stream-K如何解决?
根据知识库内容,同一Wave内的所有CTA不需要全部完成后才能启动下一个Wave的执行。具体分析如下:
-
Wave的调度机制
GPU调度器可以动态切换不同Wave的执行。如文献所述,调度程序通过在准备执行的Wave之间切换来隐藏延迟,这意味着即使前一个Wave中的CTA尚未全部完成,只要SM资源允许(如寄存器、共享内存等未占满),新的Wave可以被启动。 -
CTA的独立性
CTA(线程块)是独立的执行单元,彼此之间不直接依赖。因此,GPU可以并行调度多个Wave中的CTA到不同的SM上,而无需等待前一个Wave的所有CTA完成。例如,文献提到“两个Wave能够完全隐藏RAW时延”,说明Wave之间可以重叠执行。 -
Wave与SM资源的关系
Wave的大小取决于SM的数量和Kernel的占用率(Occupancy)。当SM的资源(如寄存器、共享内存)未被当前Wave的CTA占满时,调度器会立即分配下一个Wave的CTA到空闲的SM上,从而实现多Wave并发。
结论:GPU的Wave调度是非阻塞式的,同一Wave内的CTA未完成时,其他Wave的CTA可能已被调度到不同的SM上执行。因此,原问题的说法不正确。
这个问题触及了 GPU 并行计算中任务调度的核心挑战。我们来分解一下:
核心概念:
- Wave: GPU 硬件调度器一次能同时启动的最大 CTA 数量。它等于 GPU 的 SM 数量。例如,一个有 80 个 SM 的 GPU,其 Wave 大小就是 80。
- 任务分配: 在传统 GEMM(或其他并行任务)中,任务(通常是输出矩阵的一个分块)被分配到一个个 CTA 上执行。如果任务总数 (
gridDim
) 大于 Wave 大小 (SM 数量),就需要多个 Wave 来完成所有任务。 - 问题根源:任务执行时间不均等。 这是关键!理想情况下,所有任务(CTA)应该同时开始、同时结束。但现实中,任务执行时间往往不同:
- 数据依赖差异: 访问全局内存的延迟可能因数据位置不同而差异巨大(缓存命中/未命中)。
- 计算复杂度差异: 即使分块大小相同,计算路径也可能因数据值不同而有差异(如涉及分支、特殊函数计算)。
- 资源争用: 同一 SM 上多个 CTA 争用共享资源(如共享内存带宽、寄存器文件访问)。
- 外部干扰: 系统其他进程或内核可能占用资源。
“导致 SM 间等待和资源闲置” 的含义与举例:
假设我们有一个 GPU,拥有 4 个 SM,因此一个 Wave 最多可以同时运行 4 个 CTA。
现在有一个 GEMM 计算,需要执行 8 个任务(即 gridDim = 8
)。每个任务由一个 CTA 完成。
- Wave 1: GPU 调度器启动前 4 个 CTA (Task 1, 2, 3, 4),分别分配到 4 个 SM 上执行。
- Wave 2: 当 Wave 1 中任何一个 SM 上的 CTA 完成时,调度器就会把下一个等待的 CTA (Task 5) 分配到那个空闲的 SM 上。Wave 1 中其他 CTA 陆续完成后,调度器依次分配 Task 6, 7, 8 到空闲的 SM 上。
问题就出在 Wave 1 中各个 CTA 的执行时间很可能不一致!
场景举例:
-
理想情况 (均匀负载): 如果所有 4 个任务 (Task 1-4) 执行时间完全相同,比如都是 100ms。
- 所有 4 个 SM 在 0ms 开始工作。
- 所有 4 个 SM 在 100ms 同时完成任务。
- Wave 2 的 Task 5-8 在 100ms 被分配到空闲的 SM 上开始执行。
- 资源利用率高,SM 闲置时间短 (只有调度开销)。
-
实际情况 (负载不均): 假设 Task 1, 2, 3 执行较快 (70ms),但 Task 4 执行很慢 (130ms),因为它访问的数据缓存未命中严重。
-
时间线:
- 0ms: SM1(Task1), SM2(Task2), SM3(Task3), SM4(Task4) 同时开始。
- 70ms: SM1(Task1) 完成 ✅ -> 调度器立即分配 Task5 给 SM1 开始执行。
- 70ms: SM2(Task2) 完成 ✅ -> 调度器立即分配 Task6 给 SM2 开始执行。
- 70ms: SM3(Task3) 完成 ✅ -> 调度器立即分配 Task7 给 SM3 开始执行。
- 70ms: SM4 仍在执行 Task4 (还剩60ms)。此时 SM1, SM2, SM3 已开始执行 Task5,6,7。
- 130ms: SM4(Task4) 终于完成 ✅ -> 调度器分配 Task8 给 SM4 开始执行。
- 140ms: SM1(Task5) 完成 (70ms + 70ms) ✅
- 140ms: SM2(Task6) 完成 (70ms + 70ms) ✅
- 140ms: SM3(Task7) 完成 (70ms + 70ms) ✅
- 200ms: SM4(Task8) 完成 (130ms + 70ms) ✅ (假设 Task5-8 也各需 70ms)
-
SM 等待和资源闲置分析:
- Wave 1 结束时的等待 (70ms - 130ms): 在 70ms 时,SM1, SM2, SM3 已经完成了 Wave 1 的任务并立刻开始了 Wave 2 的任务 (Task5,6,7)。但是 SM4 还在执行 Task4。 此时并没有 SM 处于空闲等待状态,因为它们都忙 (SM1-3 执行新任务,SM4 执行旧任务)。
- 关键闲置发生在 Wave 2 结束时 (140ms - 200ms): 在 140ms 时,SM1, SM2, SM3 完成了它们的 Wave 2 任务 (Task5,6,7)。然而,SM4 还在执行 Task8 (从130ms开始,需要70ms,到200ms结束)。
- SM1, SM2, SM3 从 140ms 到 200ms 整整 60ms 处于完全空闲状态! 它们没有任务可做。
- SM4 则从 130ms 到 200ms 持续工作。
- 整体资源利用率低下: 在 140ms - 200ms 这 60ms 期间,4 个 SM 中只有 1 个 (SM4) 在工作,其他 3 个 (SM1,2,3) 在“看戏”。GPU 的整体计算能力被严重浪费。总计算时间也被最慢的 SM4 拖长 (200ms)。
-
为什么说“多个调度波次(Waves)加剧了这个问题”?
- 尾波效应放大: 在多个 Wave 的场景下,最后一个 Wave 通常无法填满所有 SM。如果最后一个 Wave 中的任务执行时间差异很大(就像上面例子中 Wave 2 的 Task8 比其他 Task5-7 慢),那么先完成任务的 SM 会闲置更长时间,等待最后一个慢任务完成。多个 Wave 意味着这种“最后一个慢任务拖后腿”的情况可能发生多次(虽然主要影响在最后一个Wave)。
- 累积效应: 前面 Wave 中的负载不均会导致 SM 完成时间错开,进而导致后续 Wave 的开始时间错开。这种错开会像涟漪一样传递下去,使得后续 Wave 内部的任务也更容易出现开始和结束时间不一致,进一步放大闲置窗口。
- 调度开销: 虽然调度本身很快,但在任务粒度较小、Wave 数量很多时,多次 Wave 的调度延迟累积起来也可能变得显著。更重要的是,调度器需要不断地监控哪些 SM 空闲并分配新任务,这也消耗资源。
- 资源碎片化: 当 SM 在 Wave 之间等待新任务分配时,或者当一个 SM 上的任务很快完成而其他 SM 还在忙时,该 SM 上的计算资源(CUDA核心、寄存器、共享内存等)就处于碎片化的闲置状态,无法被充分利用去做其他有用的工作。
总结:
传统 GEMM 按固定分块划分任务,当任务总数超过 SM 数时,必然需要多个 Wave。任务执行时间的不均衡性是导致 SM 闲置的根本原因。这种不均衡在多个 Wave 的调度框架下会被放大,尤其是在每个 Wave 的末尾和最后一个 Wave。具体表现为:
- SM间等待: 在一个 Wave 内部或 Wave 之间,先完成任务的 SM 必须等待同 Wave 中最慢的 SM 完成任务,才能一起进入下一个调度点(对于最后一个Wave,则是等待整个内核结束)。
- 资源闲置: 在等待期间,空闲的 SM 上的计算单元(CUDA核心)、寄存器、片上内存等资源无法得到有效利用,降低了 GPU 的整体吞吐率和能效比。
FlashMLA 的 Stream-K 通过将总工作量均匀切分成 正好等于 SM 数量 的大任务块,并只启动一个 Wave,完美规避了多 Wave 调度带来的负载不均衡放大效应和尾波等待问题,最大化利用了宝贵的 SM 计算资源。
所有评论(0)