Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions tests/kernels/quantization/test_scaled_mm_kernel_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,21 @@
TritonInt8ScaledMMLinearKernel,
_get_linear_backend,
_resolve_backend_kernels,
choose_scaled_mm_linear_kernel,
init_fp8_linear_kernel,
init_int8_linear_kernel,
register_linear_kernel,
)
from vllm.model_executor.kernels.linear.scaled_mm.marlin import (
MarlinFP8ScaledMMLinearKernel,
)
from vllm.model_executor.layers.quantization.utils.humming import prioritize_humming
from vllm.model_executor.layers.quantization.utils.quant_utils import (
kFp8Dynamic128Sym,
kFp8DynamicTensorSym,
kFp8DynamicTokenSym,
kFp8Static128BlockSym,
kFp8StaticChannelSym,
kFp8StaticTensorSym,
)
from vllm.platforms import PlatformEnum
Expand Down Expand Up @@ -422,3 +429,113 @@ def test_humming_priority_only_reorders_humming_and_marlin(possible_kernels):
del names[humming]
assert names == [k.__name__ for k in original if "Humming" not in k.__name__]
assert kernels == original


def _fp8_config(
*,
activation_quant_key,
weight_quant_key,
) -> FP8ScaledMMLinearLayerConfig:
return FP8ScaledMMLinearLayerConfig(
activation_quant_key=activation_quant_key,
weight_quant_key=weight_quant_key,
input_dtype=torch.bfloat16,
out_dtype=torch.bfloat16,
weight_shape=(128, 128),
)


def test_marlin_fp8_rejects_per_token_activation():
"""Marlin is W8A16 weight-only and must not claim PTPC configs."""
ok, reason = MarlinFP8ScaledMMLinearKernel.can_implement(
_fp8_config(
activation_quant_key=kFp8DynamicTokenSym,
weight_quant_key=kFp8StaticChannelSym,
)
)
assert not ok
assert reason is not None
assert "per-token" in reason
assert "W8A16" in reason


def test_marlin_fp8_accepts_per_tensor_activation():
"""Weight-only Marlin remains available for per-tensor activation FP8."""
ok, reason = MarlinFP8ScaledMMLinearKernel.can_implement(
_fp8_config(
activation_quant_key=kFp8DynamicTensorSym,
weight_quant_key=kFp8StaticTensorSym,
)
)
assert ok, reason


def test_humming_fp8_rejects_per_token_activation():
"""Humming is W8A16 weight-only and must not claim PTPC configs."""
ok, reason = HummingFP8ScaledMMLinearKernel.can_implement(
_fp8_config(
activation_quant_key=kFp8DynamicTokenSym,
weight_quant_key=kFp8StaticChannelSym,
)
)
assert not ok
assert reason is not None
assert "per-token" in reason
assert "W8A16" in reason


def test_humming_fp8_accepts_per_tensor_activation():
"""Weight-only Humming remains available for per-tensor activation FP8."""
ok, reason = HummingFP8ScaledMMLinearKernel.can_implement(
_fp8_config(
activation_quant_key=kFp8DynamicTensorSym,
weight_quant_key=kFp8StaticTensorSym,
)
)
assert ok, reason


@patch.object(HummingFP8ScaledMMLinearKernel, "is_supported", return_value=(True, None))
@patch.object(MarlinFP8ScaledMMLinearKernel, "is_supported", return_value=(True, None))
@patch("vllm.model_executor.kernels.linear.current_platform")
def test_choose_scaled_mm_surfaces_marlin_per_token_rejection(
platform_mock, _marlin_supported, _humming_supported
):
"""When no earlier kernel can implement PTPC, the chooser must raise with
Marlin and Humming rejection reasons instead of silently selecting either.
"""
platform_mock._enum = PlatformEnum.CUDA

class _RejectingKernel(MarlinFP8ScaledMMLinearKernel):
@classmethod
def is_supported(cls, compute_capability=None):
return True, None

@classmethod
def can_implement(cls, c):
return False, "mocked earlier rejection"

possible = {
PlatformEnum.CUDA: [
_RejectingKernel,
MarlinFP8ScaledMMLinearKernel,
HummingFP8ScaledMMLinearKernel,
],
}
config = _fp8_config(
activation_quant_key=kFp8DynamicTokenSym,
weight_quant_key=kFp8StaticChannelSym,
)

with pytest.raises(ValueError, match="Reasons:") as exc_info:
choose_scaled_mm_linear_kernel(
config,
possible,
quantization="fp8_w8a8",
)

message = str(exc_info.value)
assert "mocked earlier rejection" in message
assert "MarlinFP8ScaledMMLinearKernel" in message
assert "HummingFP8ScaledMMLinearKernel" in message
assert "per-token" in message
51 changes: 51 additions & 0 deletions tests/quantization/test_fp8_per_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,54 @@ def reference(weight: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
assert torch.equal(got_w2_scale, expected_w2_scale)
assert torch.equal(got_w2, expected_w2)
assert layer._already_called_process_weights_after_loading


def test_fp8_ptpc_create_weights_surfaces_kernel_rejection_reasons(
monkeypatch,
) -> None:
"""PTPC create_weights must fail before weight allocation and include the
chooser's per-kernel rejection reasons (issue #61316).
"""
from unittest.mock import MagicMock

from vllm.config import VllmConfig, set_current_vllm_config
from vllm.model_executor.layers.quantization.online import fp8 as fp8_mod

layer = MagicMock()
layer._parameters = {}

def _raise_chooser_error(**kwargs):
raise ValueError(
"Failed to find a kernel that can implement the "
"ScaledMM linear layer. Reasons: \n"
"CutlassFP8ScaledMMLinearKernel CUTLASS FP8 kernels not "
"available.\n"
"MarlinFP8ScaledMMLinearKernel is W8A16 weight-only and "
"cannot honor per-token activation quantization."
)

monkeypatch.setattr(fp8_mod, "init_fp8_linear_kernel", _raise_chooser_error)

model_config = MagicMock()
model_config.dtype = torch.bfloat16
vllm_config = MagicMock(spec=VllmConfig)
vllm_config.model_config = model_config

with set_current_vllm_config(vllm_config):
method = Fp8PtpcOnlineLinearMethod()
with pytest.raises(ValueError, match='quantization="fp8_per_channel"') as ei:
method.create_weights(
layer,
input_size_per_partition=128,
output_partition_sizes=[256],
input_size=128,
output_size=256,
params_dtype=torch.bfloat16,
)

message = str(ei.value)
assert "Reasons:" in message
assert "MarlinFP8ScaledMMLinearKernel" in message
assert "per-token" in message
# Must fail before OnlineLinearBase allocates the meta weight.
layer.register_parameter.assert_not_called()
7 changes: 7 additions & 0 deletions vllm/model_executor/kernels/linear/scaled_mm/humming.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ def is_supported(
def can_implement(
cls, config: FP8ScaledMMLinearLayerConfig
) -> tuple[bool, str | None]:
# Humming FP8 is W8A16 weight-only: it dequantizes weights and runs in
# higher precision, so it cannot honor per-token activation FP8.
if config.activation_quant_key.scale.group_shape.is_per_token():
return False, (
"is W8A16 weight-only and cannot honor per-token "
"activation quantization"
)
return True, None

def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
Expand Down
7 changes: 7 additions & 0 deletions vllm/model_executor/kernels/linear/scaled_mm/marlin.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ def is_supported(

@classmethod
def can_implement(cls, c: FP8ScaledMMLinearLayerConfig) -> tuple[bool, str | None]:
# Marlin FP8 is W8A16 weight-only: it dequantizes weights and runs in
# higher precision, so it cannot honor per-token activation FP8.
if c.activation_quant_key.scale.group_shape.is_per_token():
return False, (
"is W8A16 weight-only and cannot honor per-token "
"activation quantization"
)
return True, None

def __init__(
Expand Down
45 changes: 20 additions & 25 deletions vllm/model_executor/layers/quantization/online/fp8.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@
from vllm.model_executor.kernels.linear.scaled_mm import (
MarlinFP8ScaledMMLinearKernel,
)
from vllm.model_executor.kernels.linear.scaled_mm.humming import (
HummingFP8ScaledMMLinearKernel,
)
from vllm.model_executor.layers.fused_moe import RoutedExperts
from vllm.model_executor.layers.fused_moe.config import FusedMoEConfig
from vllm.model_executor.layers.fused_moe.oracle.fp8 import (
Expand Down Expand Up @@ -402,6 +399,26 @@ def create_weights(
params_dtype: torch.dtype,
**extra_weight_attrs,
):
# Select the kernel before allocating weights so unsupported devices
# fail with the chooser's per-kernel rejection reasons instead of a
# post-allocation W8A16 (Marlin/Humming) guard.
weight_shape = (sum(output_partition_sizes), input_size_per_partition)
try:
self.fp8_linear = init_fp8_linear_kernel(
activation_quant_key=self.activation_quant_key,
weight_quant_key=self.weight_quant_key,
weight_shape=weight_shape,
input_dtype=self.input_dtype,
out_dtype=self.out_dtype,
module_name=self.__class__.__name__,
)
except ValueError as e:
raise ValueError(
'quantization="fp8_per_channel" has no usable kernel on '
"this device that honors per-token activation "
f"quantization. {e}"
) from e

super().create_weights(
layer,
input_size_per_partition,
Expand All @@ -412,28 +429,6 @@ def create_weights(
**extra_weight_attrs,
)

self.fp8_linear = init_fp8_linear_kernel(
activation_quant_key=self.activation_quant_key,
weight_quant_key=self.weight_quant_key,
weight_shape=layer.weight.shape,
input_dtype=self.input_dtype,
out_dtype=self.out_dtype,
module_name=self.__class__.__name__,
)
# PTPC requires per-token activation FP8; Marlin and Humming FP8 are
# W8A16 and would silently produce a weight-only fp8 model.
if isinstance(
self.fp8_linear,
(MarlinFP8ScaledMMLinearKernel, HummingFP8ScaledMMLinearKernel),
):
raise ValueError(
"FP8 PTPC online quant requires a kernel that honors "
"per-token activation quantization; "
f"{type(self.fp8_linear).__name__} is W8A16 weight-only. "
"Requires SM89+ for Cutlass FP8 or ROCm MI3xx "
"for rowwise scaled_mm."
)

def process_weights_after_loading(self, layer: Module) -> None:
if getattr(layer, "_already_called_process_weights_after_loading", False):
return
Expand Down
Loading