Add Hopper FP8 grouped blockwise GEMM (sparse-groups) CuTeDSL example#3195
Open
Johnsonms wants to merge 4 commits into
Open
Add Hopper FP8 grouped blockwise GEMM (sparse-groups) CuTeDSL example#3195Johnsonms wants to merge 4 commits into
Johnsonms wants to merge 4 commits into
Conversation
CuTeDSL port of CUTLASS Example 68's sparse-groups variant
(68_..._grouped_gemm_with_blockwise_scaling_with_sparse_groups). Same
per-row SFA (ScaleGranularityM = 1) + blockwise SFB (ScaleGranularityN
= 128) FP8 grouped GEMM as the dense Example 68 port; the host driver
adds support for problem distributions where many groups have zero
problem sizes.
Sparse-groups behaviour:
- --problem_sizes accepts groups with any zero dim. Validation only
requires multiples of 128 for non-empty groups.
- Empty groups share a single stub GMEM allocation, so the metadata
pointer table is always valid (the kernel never reads from the
stubs).
- The host zeros all dims of every empty group before handing the
problem-size table to the kernel. The persistent group tile
scheduler computes a group's tile count from M*N alone, so a group
with M, N > 0 but K = 0 would otherwise consume M*N linear tile
slots and offset every later group's tiles. Forcing all dims to
zero makes the scheduler reserve zero linear tiles for empty
groups uniformly. The original sizes are kept for the reference
and bandwidth paths.
- Reported GBPS uses the original (un-padded) sizes; empty groups
contribute nothing to throughput.
- --sparse_fraction (with --seed) randomly empties a fraction of
groups; the all-empty case is short-circuited before kernel launch.
Schedule note: the C++ source uses
KernelPtrArrayTmaWarpSpecializedPingpongFP8Blockwise. This Python port
keeps the cooperative schedule from the dense Example 68 variant
(atom_layout_mnk = (2, 1, 1)). The sparse-groups host-side semantics
are independent of pingpong vs cooperative scheduling, and the per-WG
tensormap workspace required for true pingpong with grouped GEMM is
left as a follow-up.
Same register split as PR1/PR2/PR3. Effect on PR4 is small (within noise) because PR4 shares PR3's mainloop and PR3 was already near the C++ ratio at 86-88%; the spill that this trick eliminates simply was not the binding constraint here. Measured on H100 (M=N=K=2048 per active group, cluster 1,2, sparse 0.5, seed 11, 200 iters): 16 groups (8 active) 863 GFLOPS (was 863, ~0%) 64 groups (32 active) 926 (was 929, ~0% within noise) Kept for consistency with the rest of the FP8 family.
Insert griddepcontrol_wait at kernel entry, pass use_pdl=True on launch. Same change as PR1/PR2/PR3. Measured on H100 (M=N=K=2048 per active group, cluster 1,2, sparse 0.5, seed 11, 200 iters): 16 groups (8 active) 870 GFLOPS (was 863, +0.8%) 64 groups (32 active) 926 (was 926, ~0%)
Move producer_acquire from after producer_commit to before the next TMA store. Same change as PR1/PR2/PR3. Measured on H100 (M=N=K=2048 per active group, cluster 1,2, sparse 0.5, seed 11, 200 iters): 16 groups (8 active) 873 GFLOPS (was 870, +0.3%) 64 groups (32 active) 931 (was 926, +0.5%)
Collaborator
|
@depaulmillz @ANIKET-SHIVAM @IonThruster @brandon-yujie-sun plz review |
|
This PR has been labeled |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a CuTeDSL port of CUTLASS Example 68's sparse-groups variant (
68_..._grouped_gemm_with_blockwise_scaling_with_sparse_groups.cu) atexamples/python/CuTeDSL/hopper/dense_gemm_fp8_grouped_blockwise_sparse_groups.py.Same per-row SFA (
ScaleGranularityM = 1) + blockwise SFB (ScaleGranularityN = 128) FP8 grouped GEMM as the dense Example 68 port, plus host-side support for problem distributions where many groups have zero problem sizes.Sparse-groups behaviour:
--problem_sizesaccepts groups with any zero dim. Validation only requires multiples of 128 for non-empty groups.--sparse_fraction(with--seed) randomly empties a fraction of groups; the all-empty case is short-circuited before kernel launch.Schedule note: the C++ source uses
KernelPtrArrayTmaWarpSpecializedPingpongFP8Blockwise. This Python port keeps the cooperative schedule from the dense Example 68 variant (atom_layout_mnk = (2, 1, 1)). The sparse-groups host-side semantics are independent of pingpong vs cooperative scheduling, and the per-WG tensormap workspace required for true pingpong with grouped GEMM is left as a follow-up.Test plan
--sparse_fraction 0.5 / 0.9 / 1.0compute-sanitizer --tool memcheckclean (including with--use_cold_l2)JitArguments._keepaliveensures--use_cold_l2pre-generated workspaces survive the full benchmarkCorrectness
--num_groups 5 --problem_sizes "(256,256,256),(0,256,256),(128,256,256),(256,0,256),(256,256,0)"--num_groups 8 --sparse_fraction 0.5 --seed 11--num_groups 8 --sparse_fraction 0.9 --seed 3--num_groups 4 --sparse_fraction 1.0compute-sanitizer --tool memcheck, sparse +--use_cold_l2: 0 errors.Baseline performance — initial port only (H100 80GB HBM3, FP8 E4M3FN)
Numbers from the initial port (commit
e1bcd94a, no perf optimizations). All non-empty groups M=N=K=2048,--sparse_fraction 0.5 --seed 11 --cluster_shape_mn 1,2, 200 iter + 10 warmup. The C++ binary's--m=N --groups=Kmode randomizes per-group sizes, so its GFLOPS reflect a different work distribution than CuTeDSL's "exactly half-empty" — they are not directly comparable but are included as a sanity baseline.CuTeDSL numbers reflect actual non-empty work only and are the appropriate signal for "throughput on the active groups".
Performance optimizations on top of the baseline
Three small commits stacked on the initial port. PR4 shares PR3's mainloop and was already near its perf ceiling at baseline, so the cumulative delta is modest — each commit is kept primarily for structural alignment with the rest of the FP8 family.
520db59c35420264griddepcontrol.wait+use_pdl=True. Visible on the smallest-runtime configuration.e663f652tma_storeproducer_acquireCluster-aware swizzle skipped on PR4 — the grouped tile scheduler does not take a swizzle parameter. Two further follow-up opportunities are left out of scope: TMA-loaded per-row SFA staged through SMEM (see PR2 / DeepGEMM
sm90_fp8_gemm_1d2d.cuh:192-200), and the in-kernel "compute-invalid but pipeline-valid" tile path that DeepGEMM uses to skip work for unfilled rows without stalling the barrier (sm90_fp8_gemm_1d2d.cuh:255-352) — this last one is particularly relevant for very sparse PR4 workloads.Final performance
Same harness as the baseline table:
C++:
examples/68_..._with_blockwise_scaling_with_sparse_groups --m=2048 --n=2048 --k=2048 --groups=N --iterations=200. CuTeDSL:--iterations 200 --warmup_iterations 10 --sparse_fraction 0.5 --seed 11 --skip_ref_check. For an apples-to-apples C++ comparison the maintainers should generate a benchmark file (--benchmark=path.txt) listing exactly the same half-empty problem distribution.