Add FP8 Blockwise GEMM Backend Flag --fp8-gemm-backend (#14379)

This commit is contained in:
b8zhong
2025-12-09 12:05:56 -08:00
committed by GitHub
parent 73df7a4e8d
commit 55504df2f7
10 changed files with 306 additions and 33 deletions

View File

@@ -1,4 +1,8 @@
from typing import Callable, List, Optional, Tuple
from __future__ import annotations
import logging
from enum import Enum
from typing import TYPE_CHECKING, Callable, List, Optional, Tuple
import torch
@@ -6,7 +10,9 @@ from sglang.srt.environ import envs
from sglang.srt.layers import deep_gemm_wrapper
from sglang.srt.layers.quantization.fp8_kernel import sglang_per_token_group_quant_fp8
from sglang.srt.layers.quantization.mxfp4_tensor import MXFP4QuantizeUtil
from sglang.srt.utils import ceil_div, is_blackwell_supported, offloader
if TYPE_CHECKING:
from sglang.srt.server_args import ServerArgs
try:
from vllm import _custom_ops as ops
@@ -29,14 +35,20 @@ from sglang.srt.layers.quantization.fp8_kernel import (
)
from sglang.srt.utils import (
ceil_align,
ceil_div,
get_bool_env_var,
get_cuda_version,
get_device_capability,
is_blackwell_supported,
is_cuda,
is_flashinfer_available,
is_hip,
is_sm90_supported,
offloader,
)
logger = logging.getLogger(__name__)
_is_hip = is_hip()
_is_cuda = is_cuda()
_is_fp8_fnuz = is_fp8_fnuz()
@@ -125,42 +137,166 @@ def normalize_e4m3fn_to_e4m3fnuz(
return weight, weight_scale, input_scale
# TODO(ch-wan): define these backends in --moe-runner-backend
def cutlass_block_fp8_supported() -> bool:
if not get_bool_env_var("SGLANG_SUPPORT_CUTLASS_BLOCK_FP8"):
return False
if _is_cuda:
major, minor = torch.cuda.get_device_capability()
sm_version = major * 10 + minor
cuda_version = tuple(map(int, torch.version.cuda.split(".")))
if cuda_version >= (12, 0) and sm_version >= 90:
return True
return False
class Fp8GemmRunnerBackend(Enum):
"""Enum for FP8 GEMM runner backend selection."""
AUTO = "auto"
FLASHINFER = "flashinfer_trtllm"
CUTLASS = "cutlass"
DEEP_GEMM = "deep_gemm"
TRITON = "triton"
AITER = "aiter"
def is_auto(self) -> bool:
return self == Fp8GemmRunnerBackend.AUTO
def is_flashinfer(self) -> bool:
return self == Fp8GemmRunnerBackend.FLASHINFER
def is_cutlass(self) -> bool:
return self == Fp8GemmRunnerBackend.CUTLASS
def is_deep_gemm(self) -> bool:
return self == Fp8GemmRunnerBackend.DEEP_GEMM
def is_triton(self) -> bool:
return self == Fp8GemmRunnerBackend.TRITON
def is_aiter(self) -> bool:
return self == Fp8GemmRunnerBackend.AITER
CUTLASS_BLOCK_FP8_SUPPORTED = cutlass_block_fp8_supported()
ENABLE_FLASHINFER_FP8_GEMM = (
envs.SGLANG_ENABLE_FLASHINFER_FP8_GEMM.get()
and is_blackwell_supported()
and is_flashinfer_available()
)
if ENABLE_FLASHINFER_FP8_GEMM:
FP8_GEMM_RUNNER_BACKEND: Fp8GemmRunnerBackend | None = None
def _check_cutlass_block_fp8_hardware_support() -> bool:
"""Return True if CUTLASS block FP8 is supported (Hopper or newer with CUDA 12.0+)."""
return is_sm90_supported() or is_blackwell_supported()
if is_blackwell_supported() and is_flashinfer_available():
from flashinfer.gemm import gemm_fp8_nt_groupwise
def dispatch_w8a8_block_fp8_linear() -> Callable:
if ENABLE_FLASHINFER_FP8_GEMM:
"""
Dispatch to the appropriate FP8 block linear implementation.
This function selects the backend based on:
1. The --fp8-gemm-backend server argument (preferred)
2. Auto-detection based on hardware capabilities
"""
backend = get_fp8_gemm_runner_backend()
# Handle explicit backend selection via --fp8-gemm-backend
if not backend.is_auto():
return _dispatch_explicit_backend(backend)
# Auto mode: Select based purely on hardware/backend availability
return _dispatch_auto_backend()
def _dispatch_explicit_backend(backend: Fp8GemmRunnerBackend) -> Callable:
"""Dispatch based on explicitly selected backend."""
if backend.is_flashinfer():
if not (is_blackwell_supported() and is_flashinfer_available()):
raise RuntimeError(
"FlashInfer FP8 GEMM requested via --fp8-gemm-backend=flashinfer_trtllm, "
"but FlashInfer is not available or not supported on this hardware. "
"FlashInfer FP8 GEMM requires Blackwell GPUs and FlashInfer to be installed."
)
return flashinfer_gemm_w8a8_block_fp8_linear
elif CUTLASS_BLOCK_FP8_SUPPORTED:
elif backend.is_cutlass():
if not _check_cutlass_block_fp8_hardware_support():
raise RuntimeError(
"CUTLASS block FP8 requested via --fp8-gemm-backend=cutlass, "
"but hardware does not support it. CUTLASS block FP8 requires "
"Hopper (SM90+) GPUs with CUDA 12.0+."
)
return cutlass_w8a8_block_fp8_linear_with_fallback
elif backend.is_aiter():
if not _use_aiter:
raise RuntimeError(
"AITER backend requested via --fp8-gemm-backend=aiter, "
"but AITER is not available. AITER requires AMD GPUs with "
"SGLANG_USE_AITER=1 environment variable set."
)
return aiter_w8a8_block_fp8_linear
elif backend.is_deep_gemm():
if not deep_gemm_wrapper.ENABLE_JIT_DEEPGEMM:
raise RuntimeError(
"DeepGEMM backend requested via --fp8-gemm-backend=deep_gemm, "
"but DeepGEMM is not available. This usually means the deep_gemm package "
"is not installed or has been disabled via SGLANG_ENABLE_JIT_DEEPGEMM=0."
)
return deepgemm_w8a8_block_fp8_linear_with_fallback
elif backend.is_triton():
return triton_w8a8_block_fp8_linear
else:
raise ValueError(f"Unknown FP8 GEMM backend: {backend}")
def _dispatch_auto_backend() -> Callable:
"""Auto-select the best backend based on hardware capabilities."""
# Priority order for auto selection:
# 1. DeepGEMM (if enabled and available)
# 2. FlashInfer TRTLLM (if Blackwell GPU and FlashInfer available)
# 3. CUTLASS (if Hopper+ GPU and CUDA 12.0+)
# 4. AITER (if AMD GPU with AITER enabled)
# 5. Triton (fallback)
if deep_gemm_wrapper.ENABLE_JIT_DEEPGEMM:
return deepgemm_w8a8_block_fp8_linear_with_fallback
elif is_blackwell_supported() and is_flashinfer_available():
return flashinfer_gemm_w8a8_block_fp8_linear
elif _check_cutlass_block_fp8_hardware_support():
return cutlass_w8a8_block_fp8_linear_with_fallback
elif _use_aiter:
return aiter_w8a8_block_fp8_linear
elif deep_gemm_wrapper.ENABLE_JIT_DEEPGEMM:
return deepgemm_w8a8_block_fp8_linear_with_fallback
else:
return triton_w8a8_block_fp8_linear
def initialize_fp8_gemm_config(server_args: ServerArgs) -> None:
"""Initialize FP8 GEMM configuration."""
global FP8_GEMM_RUNNER_BACKEND
backend = server_args.fp8_gemm_runner_backend
# TODO(brayden): Remove env-based overrides in v0.5.7, they will be fully removed in v0.5.7.
# Only check environment variables when the server args is not set, server args should take priority.
if backend == "auto":
if envs.SGLANG_ENABLE_FLASHINFER_FP8_GEMM.get():
backend = "flashinfer_trtllm"
elif envs.SGLANG_SUPPORT_CUTLASS_BLOCK_FP8.get():
backend = "cutlass"
else:
if (
envs.SGLANG_ENABLE_FLASHINFER_FP8_GEMM.get()
or envs.SGLANG_SUPPORT_CUTLASS_BLOCK_FP8.get()
):
logger.warning(
f"FP8 GEMM backend set to '{backend}' via --fp8-gemm-backend overrides "
"environment variables SGLANG_ENABLE_FLASHINFER_FP8_GEMM and "
"SGLANG_SUPPORT_CUTLASS_BLOCK_FP8. Using server argument value."
)
FP8_GEMM_RUNNER_BACKEND = Fp8GemmRunnerBackend(backend)
def get_fp8_gemm_runner_backend() -> Fp8GemmRunnerBackend:
"""Get the current FP8 GEMM runner backend."""
global FP8_GEMM_RUNNER_BACKEND
if FP8_GEMM_RUNNER_BACKEND is None:
FP8_GEMM_RUNNER_BACKEND = Fp8GemmRunnerBackend.AUTO
return FP8_GEMM_RUNNER_BACKEND
def flashinfer_gemm_w8a8_block_fp8_linear(
input: torch.Tensor,
weight: torch.Tensor,