[SM100] Fuse bias addition into fp8_blockwise_scaled_mm epilogue - #22686
[SM100] Fuse bias addition into fp8_blockwise_scaled_mm epilogue#22686haowen-han wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Code Review
This pull request implements bias fusion for FP8 blockwise scaled matrix multiplication on SM100 devices. Key changes include the addition of a new CUDA kernel using CUTLASS to handle fused bias, updates to the C++ and Python interfaces to support an optional bias tensor, and logic in the quantization utilities to enable this fusion on compatible hardware. Review feedback highlights a performance bottleneck caused by synchronous memory allocation within the kernel launch path, which should be replaced with a pre-allocated workspace. Additionally, there are suggestions to remove unused template parameters and type aliases, and to refactor duplicated dispatch logic to improve maintainability.
| TORCH_CHECK(can_implement == cutlass::Status::kSuccess, cutlassGetStatusString(can_implement)) | ||
|
|
||
| size_t workspace_size = gemm_op.get_workspace_size(args); | ||
| cutlass::device_memory::allocation<uint8_t> workspace(workspace_size); |
There was a problem hiding this comment.
Using cutlass::device_memory::allocation inside the kernel launch path performs a synchronous cudaMalloc and cudaFree. This is a significant performance bottleneck in high-frequency inference scenarios as it triggers device synchronization. It is highly recommended to pass a workspace tensor from the Python layer (e.g., using a pre-allocated buffer pool) and use its data pointer here instead.
| typename PerSmTileShape, | ||
| typename EpilogueTileShape, | ||
| typename ScalesPerTile, | ||
| int TileSizeM_ = 128, |
| static constexpr int AlignmentC = AlignmentD; | ||
|
|
||
| using ElementAccumulator = float; | ||
| using ElementBlockScale = float; |
| if (bias.has_value()) { | ||
| launch_sm100_fp8_blockwise_scaled_mm_with_bias< | ||
| OutType, | ||
| MmaTileShape, | ||
| PerSmTileShape, | ||
| EpilogueTileShape, | ||
| ScalesPerTile>(out, a, b, scales_a, scales_b, *bias); | ||
| } else { | ||
| launch_sm100_fp8_blockwise_scaled_mm<OutType, MmaTileShape, PerSmTileShape, EpilogueTileShape, ScalesPerTile>( | ||
| out, a, b, scales_a, scales_b); | ||
| } | ||
| } else { | ||
| using MmaTileShape = Shape<_128, _128, _128>; | ||
| using PerSmTileShape = Shape<_128, _128, _128>; | ||
| using EpilogueTileShape = Shape<_128, _64>; | ||
| using ScalesPerTile = Shape<_128, _1, _1>; | ||
| launch_sm100_fp8_blockwise_scaled_mm<OutType, MmaTileShape, PerSmTileShape, EpilogueTileShape, ScalesPerTile>( | ||
| out, a, b, scales_a, scales_b); | ||
| if (bias.has_value()) { | ||
| launch_sm100_fp8_blockwise_scaled_mm_with_bias< | ||
| OutType, | ||
| MmaTileShape, | ||
| PerSmTileShape, | ||
| EpilogueTileShape, | ||
| ScalesPerTile>(out, a, b, scales_a, scales_b, *bias); | ||
| } else { | ||
| launch_sm100_fp8_blockwise_scaled_mm<OutType, MmaTileShape, PerSmTileShape, EpilogueTileShape, ScalesPerTile>( | ||
| out, a, b, scales_a, scales_b); | ||
| } | ||
| } |
There was a problem hiding this comment.
The dispatch logic for the bias-fused variant is duplicated for both M <= 128 and M > 128 cases. This increases code verbosity and maintenance effort. Consider refactoring this to determine the template parameters first and then perform a single check for bias.has_value() to launch the appropriate kernel.
|
Hi @haowen-han, the CUTLASS SM90/SM100 fp8_blockwise_gemm_kernel.cu this PR patches has been removed from the tree (see #30438, which moved the SM120 path to JIT and deleted the SM90/SM100 CUTLASS kernels). The bias fusion idea is still valid but this PR would need a full rewrite against the new JIT kernel layout (python/sglang/kernels/jit/csrc/gemm/fp8_blockwise/). Closing as obsolete - please reopen or resubmit against the new kernels. |
Motivation
In FP8 blockwise linear layers the bias addition (output += bias) is currently a separate element-wise kernel launch after GEMM, which incurs extra global memory round-trip and kernel launch overhead on SM100 GPUs. Fusing bias into the CUTLASS epilogue eliminates this overhead.
Modifications
sgl-kernel/csrc/gemm/fp8_blockwise_gemm_kernel.cu: Addedlaunch_sm100_fp8_blockwise_scaled_mm_with_biastemplate function that usesElementC = ElementD(non-void) in the CUTLASSCollectiveBuilder, passes bias pointer with broadcast stride(0, 1, 0)along M dimension, and setsalpha=1.0, beta=1.0in epilogue arguments. Updatedsm100_fp8_blockwise_dispatch_shapeto dispatch to the bias-fused variant when bias is provided. Added explicit rejection of bias for SM90 and SM120+ code paths.sgl-kernel/csrc/common_extension.cc: Extendedfp8_blockwise_scaled_mmop schema with optionalTensor? bias=Noneparameter.sgl-kernel/include/sgl_kernel_ops.h: Updated C++ declaration accordingly.sgl-kernel/python/sgl_kernel/gemm.py: Forwarded newbiaskwarg through Python binding.python/sglang/srt/layers/quantization/fp8_utils.py: Incutlass_w8a8_block_fp8_linear_with_fallback, detect SM100 via device capability and pass bias into the fused path; non-SM100 GPUs fall back to the existing separateoutput += bias.sgl-kernel/tests/test_fp8_blockwise_gemm.py: Added_test_accuracy_once_with_biasand parametrizedtest_accuracy_with_biascovering M ∈ {1,3,5,127,128,512,1024,4096}, N ∈ {128..14080}, K ∈ {512..16384}, both bf16 and fp16, gated by SM100 skip condition.Accuracy Tests
Added
test_accuracy_with_biastest comparing fused-bias output against reference (baseline_scaled_mmwith bias) atrtol=0.02, atol=1. Test is gated to run only on SM100 GPUs via@pytest.mark.skipif.Speed Tests and Profiling
running the following benchmark code using B200 gpu:
here is the result:
============================================================ N=7168, K=7168, dtype=bf16 ============================================================ fp8_blockwise_gemm_bias_fusion: M Fused (bias in epilogue) Separate (gemm + add) 0 256.0 19.376838 23.838453 1 512.0 35.659245 42.251312 2 1024.0 67.925394 78.150920 3 2048.0 131.872603 146.603412 4 4096.0 263.217060 286.696246 5 8192.0 468.621148 579.818849 6 16384.0 918.596848 1141.510010 ============================================================ N=7168, K=16384, dtype=bf16 ============================================================ fp8_blockwise_gemm_bias_fusion: M Fused (bias in epilogue) Separate (gemm + add) 0 256.0 43.785433 47.810112 1 512.0 81.634298 86.983676 2 1024.0 153.299782 168.448712 3 2048.0 285.402742 303.855991 4 4096.0 583.059466 608.137285 5 8192.0 1082.428631 1297.316297 6 16384.0 2441.153844 2524.587359 ============================================================ N=16384, K=7168, dtype=fp16 ============================================================ fp8_blockwise_gemm_bias_fusion: M Fused (bias in epilogue) Separate (gemm + add) 0 256.0 42.899774 49.212395 1 512.0 78.094634 90.390543 2 1024.0 146.660226 167.952741 3 2048.0 293.780782 326.080557 4 4096.0 599.295009 651.446079 5 8192.0 1280.669912 1328.287951 6 16384.0 2664.572001 3237.196732 ============================================================ N=14080, K=16384, dtype=bf16 ============================================================ fp8_blockwise_gemm_bias_fusion: M Fused (bias in epilogue) Separate (gemm + add) 0 256.0 84.381665 91.406007 1 512.0 139.445754 150.312793 2 1024.0 262.667424 285.423491 3 2048.0 534.803549 565.675649 4 4096.0 1135.408000 1194.958985 5 8192.0 2607.092023 2585.369246 6 16384.0 5389.096022 5207.893213 ============================================================ N=8192, K=8192, dtype=fp16 ============================================================ fp8_blockwise_gemm_bias_fusion: M Fused (bias in epilogue) Separate (gemm + add) 0 256.0 23.325837 26.356896 1 512.0 42.567962 49.080000 2 1024.0 87.857144 97.445689 3 2048.0 166.243331 181.424160 4 4096.0 333.237237 362.018457 5 8192.0 652.479536 810.797899 6 16384.0 1271.440955 1512.180010 Benchmark finished!Checklist
Review and Merge Process
/tag-and-rerun-ci,/tag-run-ci-label,/rerun-failed-ci