373 lines
12 KiB
Python
373 lines
12 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
|
|
import torch
|
|
|
|
from vllm._custom_ops import scaled_fp4_quant
|
|
from vllm.model_executor.layers.fusion.quant_activation import (
|
|
QuantizedActivation,
|
|
as_quantized_activation,
|
|
)
|
|
from vllm.model_executor.layers.quantization.utils.nvfp4_utils import (
|
|
pad_nvfp4_activation_for_cutlass,
|
|
pad_nvfp4_weight_for_cutlass,
|
|
slice_nvfp4_output,
|
|
swizzle_blockscale,
|
|
)
|
|
from vllm.model_executor.layers.quantization.utils.quant_utils import (
|
|
QuantKey,
|
|
kNvfp4Dynamic,
|
|
)
|
|
from vllm.platforms import current_platform
|
|
from vllm.utils.flashinfer import (
|
|
flashinfer_scaled_fp4_mm,
|
|
has_flashinfer,
|
|
has_flashinfer_b12x_gemm,
|
|
)
|
|
|
|
from .base import NvFp4LinearKernel, NvFp4LinearLayerConfig
|
|
|
|
|
|
class FlashInferCuteDslNvFp4LinearKernel(NvFp4LinearKernel):
|
|
"""NVFP4 GEMM via FlashInfer's cutedsl backend."""
|
|
|
|
@classmethod
|
|
def is_supported(
|
|
cls, compute_capability: int | None = None
|
|
) -> tuple[bool, str | None]:
|
|
if not current_platform.is_device_capability_family(100):
|
|
return False, "FlashInfer cutedsl requires sm_10x"
|
|
if not has_flashinfer():
|
|
return False, "FlashInfer required"
|
|
return True, None
|
|
|
|
@classmethod
|
|
def can_implement(cls, config: NvFp4LinearLayerConfig) -> tuple[bool, str | None]:
|
|
return True, None
|
|
|
|
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
|
|
# cutedsl uses the same swizzled + padded layout as cutlass.
|
|
layer.weight_scale = torch.nn.Parameter(
|
|
swizzle_blockscale(layer.weight_scale.data), requires_grad=False
|
|
)
|
|
padded_weight, weights_padding_cols = pad_nvfp4_weight_for_cutlass(
|
|
layer.weight.data
|
|
)
|
|
layer.weight = torch.nn.Parameter(padded_weight, requires_grad=False)
|
|
layer.weights_padding_cols = weights_padding_cols
|
|
|
|
def apply_weights(
|
|
self,
|
|
layer: torch.nn.Module,
|
|
x: torch.Tensor,
|
|
bias: torch.Tensor | None = None,
|
|
) -> torch.Tensor:
|
|
output_size = layer.output_size_per_partition
|
|
output_dtype = x.dtype
|
|
output_shape = [*x.shape[:-1], output_size]
|
|
|
|
x_fp4, x_blockscale = scaled_fp4_quant(
|
|
x,
|
|
layer.input_global_scale_inv,
|
|
is_sf_swizzled_layout=True,
|
|
backend="flashinfer-cutedsl",
|
|
)
|
|
|
|
x_fp4 = pad_nvfp4_activation_for_cutlass(
|
|
x_fp4, getattr(layer, "weights_padding_cols", 0)
|
|
)
|
|
|
|
out = flashinfer_scaled_fp4_mm(
|
|
x_fp4,
|
|
layer.weight,
|
|
x_blockscale,
|
|
layer.weight_scale,
|
|
layer.alpha,
|
|
output_dtype,
|
|
backend="cute-dsl",
|
|
)
|
|
|
|
out = slice_nvfp4_output(out, output_size)
|
|
|
|
if bias is not None:
|
|
out = out + bias
|
|
return out.view(*output_shape)
|
|
|
|
|
|
class FlashInferCutlassNvFp4LinearKernel(NvFp4LinearKernel):
|
|
"""NVFP4 GEMM via FlashInfer's CUTLASS wrapper."""
|
|
|
|
def input_quant_key(self) -> QuantKey | None:
|
|
"""This kernel supports dynamic quantization of the input. By
|
|
convention, pre-quantized blockscales must use the swizzled layout."""
|
|
return kNvfp4Dynamic
|
|
|
|
@classmethod
|
|
def is_supported(
|
|
cls, compute_capability: int | None = None
|
|
) -> tuple[bool, str | None]:
|
|
from vllm.model_executor.layers.quantization.utils.nvfp4_utils import (
|
|
cutlass_fp4_supported,
|
|
)
|
|
|
|
if (
|
|
cutlass_fp4_supported()
|
|
and current_platform.has_device_capability(100)
|
|
and has_flashinfer()
|
|
):
|
|
return True, None
|
|
return False, "FlashInfer + >=sm_100 required"
|
|
|
|
@classmethod
|
|
def can_implement(cls, config: NvFp4LinearLayerConfig) -> tuple[bool, str | None]:
|
|
return True, None
|
|
|
|
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
|
|
layer.weight_scale = torch.nn.Parameter(
|
|
swizzle_blockscale(layer.weight_scale.data), requires_grad=False
|
|
)
|
|
padded_weight, weights_padding_cols = pad_nvfp4_weight_for_cutlass(
|
|
layer.weight.data
|
|
)
|
|
layer.weight = torch.nn.Parameter(padded_weight, requires_grad=False)
|
|
layer.weights_padding_cols = weights_padding_cols
|
|
|
|
def apply_weights(
|
|
self,
|
|
layer: torch.nn.Module,
|
|
x: torch.Tensor | QuantizedActivation,
|
|
bias: torch.Tensor | None = None,
|
|
) -> torch.Tensor:
|
|
output_size = layer.output_size_per_partition
|
|
weights_padding_bytes = getattr(layer, "weights_padding_cols", 0)
|
|
|
|
qa = as_quantized_activation(x, self.input_quant_key())
|
|
if qa is not None:
|
|
x_fp4, x_blockscale = qa.data, qa.scale
|
|
x_fp4 = pad_nvfp4_activation_for_cutlass(x_fp4, weights_padding_bytes)
|
|
output_dtype = qa.orig_dtype
|
|
output_shape = [*qa.orig_shape[:-1], output_size]
|
|
else:
|
|
assert isinstance(x, torch.Tensor)
|
|
output_dtype = x.dtype
|
|
output_shape = [*x.shape[:-1], output_size]
|
|
x_fp4, x_blockscale = scaled_fp4_quant(
|
|
x,
|
|
layer.input_global_scale_inv,
|
|
is_sf_swizzled_layout=True,
|
|
backend="flashinfer-cutlass",
|
|
padded_n=x.shape[-1] + weights_padding_bytes * 2,
|
|
)
|
|
|
|
out = flashinfer_scaled_fp4_mm(
|
|
x_fp4,
|
|
layer.weight,
|
|
x_blockscale,
|
|
layer.weight_scale,
|
|
layer.alpha,
|
|
output_dtype,
|
|
backend="cutlass",
|
|
)
|
|
|
|
out = slice_nvfp4_output(out, output_size)
|
|
|
|
if bias is not None:
|
|
out = out + bias
|
|
return out.view(*output_shape)
|
|
|
|
|
|
class FlashInferTrtllmNvFp4LinearKernel(NvFp4LinearKernel):
|
|
"""NVFP4 GEMM via FlashInfer's TensorRT-LLM wrapper."""
|
|
|
|
@classmethod
|
|
def is_supported(
|
|
cls, compute_capability: int | None = None
|
|
) -> tuple[bool, str | None]:
|
|
if has_flashinfer():
|
|
return True, None
|
|
return False, "FlashInfer required"
|
|
|
|
@classmethod
|
|
def can_implement(cls, config: NvFp4LinearLayerConfig) -> tuple[bool, str | None]:
|
|
return True, None
|
|
|
|
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
|
|
from flashinfer import shuffle_matrix_a, shuffle_matrix_sf_a
|
|
|
|
weight = layer.weight.data
|
|
weight_scale = layer.weight_scale.data
|
|
epilogue_tile_m = 128
|
|
|
|
layer.weight = torch.nn.Parameter(
|
|
shuffle_matrix_a(weight.view(torch.uint8), epilogue_tile_m),
|
|
requires_grad=False,
|
|
)
|
|
layer.weight_scale = torch.nn.Parameter(
|
|
shuffle_matrix_sf_a(weight_scale.view(torch.uint8), epilogue_tile_m)
|
|
.reshape(weight_scale.shape)
|
|
.view(torch.float8_e4m3fn),
|
|
requires_grad=False,
|
|
)
|
|
|
|
def apply_weights(
|
|
self,
|
|
layer: torch.nn.Module,
|
|
x: torch.Tensor,
|
|
bias: torch.Tensor | None = None,
|
|
) -> torch.Tensor:
|
|
output_size = layer.output_size_per_partition
|
|
output_dtype = x.dtype
|
|
output_shape = [*x.shape[:-1], output_size]
|
|
|
|
x_fp4, x_blockscale = scaled_fp4_quant(
|
|
x,
|
|
layer.input_global_scale_inv,
|
|
is_sf_swizzled_layout=True,
|
|
backend="flashinfer-trtllm",
|
|
)
|
|
|
|
out = flashinfer_scaled_fp4_mm(
|
|
x_fp4,
|
|
layer.weight,
|
|
x_blockscale,
|
|
layer.weight_scale,
|
|
layer.alpha,
|
|
output_dtype,
|
|
backend="trtllm",
|
|
)
|
|
|
|
out = slice_nvfp4_output(out, output_size)
|
|
|
|
if bias is not None:
|
|
out = out + bias
|
|
return out.view(*output_shape)
|
|
|
|
|
|
class FlashInferCudnnNvFp4LinearKernel(NvFp4LinearKernel):
|
|
"""NVFP4 GEMM via FlashInfer's cuDNN wrapper."""
|
|
|
|
@classmethod
|
|
def is_supported(
|
|
cls, compute_capability: int | None = None
|
|
) -> tuple[bool, str | None]:
|
|
if has_flashinfer():
|
|
return True, None
|
|
return False, "FlashInfer required"
|
|
|
|
@classmethod
|
|
def can_implement(cls, config: NvFp4LinearLayerConfig) -> tuple[bool, str | None]:
|
|
return True, None
|
|
|
|
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
|
|
# cuDNN uses the same swizzled + padded layout as CUTLASS
|
|
layer.weight_scale = torch.nn.Parameter(
|
|
swizzle_blockscale(layer.weight_scale.data), requires_grad=False
|
|
)
|
|
padded_weight, weights_padding_cols = pad_nvfp4_weight_for_cutlass(
|
|
layer.weight.data
|
|
)
|
|
layer.weight = torch.nn.Parameter(padded_weight, requires_grad=False)
|
|
layer.weights_padding_cols = weights_padding_cols
|
|
|
|
def apply_weights(
|
|
self,
|
|
layer: torch.nn.Module,
|
|
x: torch.Tensor,
|
|
bias: torch.Tensor | None = None,
|
|
) -> torch.Tensor:
|
|
output_size = layer.output_size_per_partition
|
|
output_dtype = x.dtype
|
|
output_shape = [*x.shape[:-1], output_size]
|
|
weights_padding_bytes = getattr(layer, "weights_padding_cols", 0)
|
|
|
|
x_fp4, x_blockscale = scaled_fp4_quant(
|
|
x,
|
|
layer.input_global_scale_inv,
|
|
is_sf_swizzled_layout=True,
|
|
backend="flashinfer-cudnn",
|
|
padded_n=x.shape[-1] + weights_padding_bytes * 2,
|
|
)
|
|
|
|
out = flashinfer_scaled_fp4_mm(
|
|
x_fp4,
|
|
layer.weight,
|
|
x_blockscale,
|
|
layer.weight_scale,
|
|
layer.alpha,
|
|
output_dtype,
|
|
backend="cudnn",
|
|
)
|
|
|
|
out = slice_nvfp4_output(out, output_size)
|
|
|
|
if bias is not None:
|
|
out = out + bias
|
|
return out.view(*output_shape)
|
|
|
|
|
|
class FlashInferB12xNvFp4LinearKernel(NvFp4LinearKernel):
|
|
"""NVFP4 GEMM via FlashInfer's b12x CuTe DSL warp-level MMA kernel (SM120+)."""
|
|
|
|
@classmethod
|
|
def is_supported(
|
|
cls, compute_capability: int | None = None
|
|
) -> tuple[bool, str | None]:
|
|
if current_platform.has_device_capability(120) and has_flashinfer_b12x_gemm():
|
|
return True, None
|
|
return (
|
|
False,
|
|
"FlashInfer b12x requires SM120+ and FlashInfer "
|
|
"with Sm120BlockScaledDenseGemmKernel",
|
|
)
|
|
|
|
@classmethod
|
|
def can_implement(cls, config: NvFp4LinearLayerConfig) -> tuple[bool, str | None]:
|
|
return True, None
|
|
|
|
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
|
|
layer.weight_scale = torch.nn.Parameter(
|
|
swizzle_blockscale(layer.weight_scale.data), requires_grad=False
|
|
)
|
|
padded_weight, weights_padding_cols = pad_nvfp4_weight_for_cutlass(
|
|
layer.weight.data
|
|
)
|
|
layer.weight = torch.nn.Parameter(padded_weight, requires_grad=False)
|
|
layer.weights_padding_cols = weights_padding_cols
|
|
|
|
def apply_weights(
|
|
self,
|
|
layer: torch.nn.Module,
|
|
x: torch.Tensor,
|
|
bias: torch.Tensor | None = None,
|
|
) -> torch.Tensor:
|
|
output_size = layer.output_size_per_partition
|
|
output_dtype = x.dtype
|
|
output_shape = [*x.shape[:-1], output_size]
|
|
|
|
x_fp4, x_blockscale = scaled_fp4_quant(
|
|
x,
|
|
layer.input_global_scale_inv,
|
|
is_sf_swizzled_layout=True,
|
|
backend="b12x",
|
|
)
|
|
|
|
x_fp4 = pad_nvfp4_activation_for_cutlass(
|
|
x_fp4, getattr(layer, "weights_padding_cols", 0)
|
|
)
|
|
|
|
out = flashinfer_scaled_fp4_mm(
|
|
x_fp4,
|
|
layer.weight,
|
|
x_blockscale,
|
|
layer.weight_scale,
|
|
layer.alpha,
|
|
output_dtype,
|
|
backend="b12x",
|
|
)
|
|
|
|
out = slice_nvfp4_output(out, output_size)
|
|
|
|
if bias is not None:
|
|
out = out + bias
|
|
return out.view(*output_shape)
|