[Perf] Add Flashinfer DeepGEMM SM90 for SwapAB Optimization (#15514)
Co-authored-by: Brayden Zhong <b8zhong@users.noreply.github.com>
This commit is contained in:
@@ -134,7 +134,8 @@ class Fp8GemmRunnerBackend(Enum):
|
|||||||
"""Enum for FP8 GEMM runner backend selection."""
|
"""Enum for FP8 GEMM runner backend selection."""
|
||||||
|
|
||||||
AUTO = "auto"
|
AUTO = "auto"
|
||||||
FLASHINFER = "flashinfer_trtllm"
|
FLASHINFER_TRTLLM = "flashinfer_trtllm"
|
||||||
|
FLASHINFER_DEEPGEMM = "flashinfer_deepgemm"
|
||||||
CUTLASS = "cutlass"
|
CUTLASS = "cutlass"
|
||||||
DEEP_GEMM = "deep_gemm"
|
DEEP_GEMM = "deep_gemm"
|
||||||
TRITON = "triton"
|
TRITON = "triton"
|
||||||
@@ -144,7 +145,10 @@ class Fp8GemmRunnerBackend(Enum):
|
|||||||
return self == Fp8GemmRunnerBackend.AUTO
|
return self == Fp8GemmRunnerBackend.AUTO
|
||||||
|
|
||||||
def is_flashinfer(self) -> bool:
|
def is_flashinfer(self) -> bool:
|
||||||
return self == Fp8GemmRunnerBackend.FLASHINFER
|
return self == Fp8GemmRunnerBackend.FLASHINFER_TRTLLM
|
||||||
|
|
||||||
|
def is_flashinfer_deepgemm(self) -> bool:
|
||||||
|
return self == Fp8GemmRunnerBackend.FLASHINFER_DEEPGEMM
|
||||||
|
|
||||||
def is_cutlass(self) -> bool:
|
def is_cutlass(self) -> bool:
|
||||||
return self == Fp8GemmRunnerBackend.CUTLASS
|
return self == Fp8GemmRunnerBackend.CUTLASS
|
||||||
@@ -170,6 +174,10 @@ def _check_cutlass_block_fp8_hardware_support() -> bool:
|
|||||||
if is_blackwell_supported() and is_flashinfer_available():
|
if is_blackwell_supported() and is_flashinfer_available():
|
||||||
from flashinfer.gemm import gemm_fp8_nt_groupwise
|
from flashinfer.gemm import gemm_fp8_nt_groupwise
|
||||||
|
|
||||||
|
if is_sm90_supported() and is_flashinfer_available():
|
||||||
|
# FlashInfer SM90 DeepGEMM with automatic swapAB optimization for small M
|
||||||
|
from flashinfer.gemm import fp8_blockscale_gemm_sm90
|
||||||
|
|
||||||
|
|
||||||
def dispatch_w8a8_block_fp8_linear() -> Callable:
|
def dispatch_w8a8_block_fp8_linear() -> Callable:
|
||||||
"""
|
"""
|
||||||
@@ -200,6 +208,15 @@ def _dispatch_explicit_backend(backend: Fp8GemmRunnerBackend) -> Callable:
|
|||||||
)
|
)
|
||||||
return flashinfer_gemm_w8a8_block_fp8_linear_with_fallback
|
return flashinfer_gemm_w8a8_block_fp8_linear_with_fallback
|
||||||
|
|
||||||
|
elif backend.is_flashinfer_deepgemm():
|
||||||
|
if not (is_sm90_supported() and is_flashinfer_available()):
|
||||||
|
raise RuntimeError(
|
||||||
|
"FlashInfer DeepGEMM with swapAB requested via --fp8-gemm-backend=flashinfer_deepgemm, "
|
||||||
|
"but it's not available. This backend requires Hopper (SM90) GPUs and FlashInfer "
|
||||||
|
"to be installed."
|
||||||
|
)
|
||||||
|
return flashinfer_deepgemm_w8a8_block_fp8_linear_with_fallback
|
||||||
|
|
||||||
elif backend.is_cutlass():
|
elif backend.is_cutlass():
|
||||||
if not _check_cutlass_block_fp8_hardware_support():
|
if not _check_cutlass_block_fp8_hardware_support():
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
@@ -333,6 +350,60 @@ def flashinfer_gemm_w8a8_block_fp8_linear_with_fallback(
|
|||||||
return output.to(dtype=input_2d.dtype).view(*output_shape)
|
return output.to(dtype=input_2d.dtype).view(*output_shape)
|
||||||
|
|
||||||
|
|
||||||
|
def flashinfer_deepgemm_w8a8_block_fp8_linear_with_fallback(
|
||||||
|
input: torch.Tensor,
|
||||||
|
weight: torch.Tensor,
|
||||||
|
block_size: List[int],
|
||||||
|
weight_scale: torch.Tensor,
|
||||||
|
input_scale: Optional[torch.Tensor] = None,
|
||||||
|
bias: Optional[torch.Tensor] = None,
|
||||||
|
) -> torch.Tensor:
|
||||||
|
"""
|
||||||
|
FlashInfer DeepGEMM backend for SM90 (Hopper) with swapAB optimization.
|
||||||
|
|
||||||
|
Uses flashinfer.gemm.fp8_blockscale_gemm_sm90 which automatically selects
|
||||||
|
the swapAB kernel for small M dimensions (M < 32) for better performance
|
||||||
|
during decoding/low batch size scenarios.
|
||||||
|
|
||||||
|
For SM90 (Hopper), this uses the DeepGEMM JIT with automatic swapAB selection.
|
||||||
|
"""
|
||||||
|
assert input_scale is None
|
||||||
|
|
||||||
|
output_dtype = input.dtype
|
||||||
|
dtype_supported = output_dtype == torch.bfloat16
|
||||||
|
|
||||||
|
# fp8_blockscale_gemm_sm90 requires: N % 64 == 0, K % 128 == 0
|
||||||
|
shape_supported = weight.shape[0] % 64 == 0 and weight.shape[1] % 128 == 0
|
||||||
|
|
||||||
|
if not (shape_supported and dtype_supported):
|
||||||
|
if weight_scale.dtype == torch.int32:
|
||||||
|
weight_scale = _unpack_ue8m0_scale_for_triton(
|
||||||
|
weight_scale, weight.shape, block_size
|
||||||
|
)
|
||||||
|
return triton_w8a8_block_fp8_linear(
|
||||||
|
input, weight, block_size, weight_scale, input_scale, bias
|
||||||
|
)
|
||||||
|
|
||||||
|
input_2d = input.view(-1, input.shape[-1])
|
||||||
|
output_shape = [*input.shape[:-1], weight.shape[0]]
|
||||||
|
|
||||||
|
# - input: (M, K) BF16 or FP8
|
||||||
|
# - weight: (N, K) FP8 with weight_scale
|
||||||
|
# - weight_scale: (N, K//128) for per-token or (N//128, K//128) for per-block
|
||||||
|
|
||||||
|
output = fp8_blockscale_gemm_sm90(
|
||||||
|
input_2d,
|
||||||
|
weight,
|
||||||
|
input_scale=None, # BF16 input, internal quantization
|
||||||
|
weight_scale=weight_scale,
|
||||||
|
out_dtype=output_dtype,
|
||||||
|
)
|
||||||
|
|
||||||
|
if bias is not None:
|
||||||
|
output += bias
|
||||||
|
return output.view(*output_shape)
|
||||||
|
|
||||||
|
|
||||||
def cutlass_w8a8_block_fp8_linear_with_fallback(
|
def cutlass_w8a8_block_fp8_linear_with_fallback(
|
||||||
input: torch.Tensor,
|
input: torch.Tensor,
|
||||||
weight: torch.Tensor,
|
weight: torch.Tensor,
|
||||||
|
|||||||
@@ -198,6 +198,7 @@ FP8_GEMM_RUNNER_BACKEND_CHOICES = [
|
|||||||
"auto",
|
"auto",
|
||||||
"deep_gemm",
|
"deep_gemm",
|
||||||
"flashinfer_trtllm",
|
"flashinfer_trtllm",
|
||||||
|
"flashinfer_deepgemm",
|
||||||
"cutlass",
|
"cutlass",
|
||||||
"triton",
|
"triton",
|
||||||
"aiter",
|
"aiter",
|
||||||
@@ -3685,6 +3686,7 @@ class ServerArgs:
|
|||||||
"Options: 'auto' (default, auto-selects based on hardware), "
|
"Options: 'auto' (default, auto-selects based on hardware), "
|
||||||
"'deep_gemm' (JIT-compiled; enabled by default on NVIDIA Hopper (SM90) and Blackwell (SM100) when DeepGEMM is installed), "
|
"'deep_gemm' (JIT-compiled; enabled by default on NVIDIA Hopper (SM90) and Blackwell (SM100) when DeepGEMM is installed), "
|
||||||
"'flashinfer_trtllm' (optimal for Blackwell and low-latency), "
|
"'flashinfer_trtllm' (optimal for Blackwell and low-latency), "
|
||||||
|
"'flashinfer_deepgemm' (Hopper SM90 only; uses swapAB optimization for small M dimensions in decoding), "
|
||||||
"'cutlass' (optimal for Hopper/Blackwell GPUs and high-throughput), "
|
"'cutlass' (optimal for Hopper/Blackwell GPUs and high-throughput), "
|
||||||
"'triton' (fallback, widely compatible), "
|
"'triton' (fallback, widely compatible), "
|
||||||
"'aiter' (ROCm only). "
|
"'aiter' (ROCm only). "
|
||||||
|
|||||||
@@ -69,5 +69,10 @@ class TestFP8BlockwiseGemmFlashinferTrtllm(FP8BlockwiseGemmBase, unittest.TestCa
|
|||||||
backend = "flashinfer_trtllm"
|
backend = "flashinfer_trtllm"
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(get_device_sm() != 90, "Test requires CUDA SM 90")
|
||||||
|
class TestFP8BlockwiseGemmFlashinferDeepGemm(FP8BlockwiseGemmBase, unittest.TestCase):
|
||||||
|
backend = "flashinfer_deepgemm"
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user