244 lines
8.2 KiB
Python
244 lines
8.2 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
"""Tests for the per_token_group_fp8_quant helion kernel
|
|
|
|
Run `pytest tests/kernels/helion/test_per_token_group_fp8_quant.py`.
|
|
"""
|
|
|
|
from typing import Any
|
|
|
|
import pytest
|
|
import torch
|
|
from torch._subclasses.fake_tensor import FakeTensorMode
|
|
|
|
from tests.kernels.helion.utils import skip_if_platform_unsupported
|
|
from tests.kernels.quant_utils import FP8_DTYPE
|
|
from vllm.kernels.helion.case_key import CaseKey
|
|
from vllm.kernels.helion.config_manager import ConfigManager
|
|
from vllm.kernels.helion.ops.per_token_group_fp8_quant import (
|
|
_pick_cache,
|
|
baseline,
|
|
per_token_group_fp8_quant,
|
|
pick_config,
|
|
)
|
|
from vllm.model_executor.layers.quantization.utils.quant_utils import (
|
|
get_fp8_min_max,
|
|
)
|
|
from vllm.utils.import_utils import has_helion
|
|
|
|
if not has_helion():
|
|
pytest.skip(
|
|
"Helion is not installed. Install with: pip install vllm[helion]",
|
|
allow_module_level=True,
|
|
)
|
|
|
|
|
|
def _generate_fake_input(
|
|
num_tokens: int, hidden_size: int, group_size: int
|
|
) -> tuple[Any, ...]:
|
|
with FakeTensorMode():
|
|
input = torch.randn(
|
|
(num_tokens, hidden_size), device="cuda", dtype=torch.bfloat16
|
|
)
|
|
output_q = torch.empty(input.shape, device=input.device, dtype=FP8_DTYPE)
|
|
output_s = torch.empty(
|
|
(num_tokens, hidden_size // group_size),
|
|
device=input.device,
|
|
dtype=torch.float32,
|
|
)
|
|
use_ue8m0 = False
|
|
column_major = False
|
|
fp8_min, fp8_max = get_fp8_min_max()
|
|
eps = 1e-10
|
|
args = (
|
|
input,
|
|
output_q,
|
|
output_s,
|
|
group_size,
|
|
eps,
|
|
fp8_min,
|
|
fp8_max,
|
|
use_ue8m0,
|
|
column_major,
|
|
)
|
|
return args
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def reset_config_manager_singleton():
|
|
ConfigManager.reset_instance()
|
|
ConfigManager()
|
|
yield
|
|
ConfigManager.reset_instance()
|
|
|
|
|
|
class TestPerTokenGroupFp8QuantConfigPicker:
|
|
def setup_method(self):
|
|
_pick_cache.clear()
|
|
|
|
def test_config_picker_exact_match(self):
|
|
config_keys = [
|
|
CaseKey({"hidden_size": 2048, "group_size": 64, "num_tokens": 16}),
|
|
CaseKey({"hidden_size": 4096, "group_size": 128, "num_tokens": 16}),
|
|
]
|
|
|
|
args = _generate_fake_input(16, 4096, 128)
|
|
selected_key = pick_config(args, config_keys)
|
|
assert selected_key == CaseKey(
|
|
{"hidden_size": 4096, "group_size": 128, "num_tokens": 16}
|
|
)
|
|
|
|
def test_config_picker_closest_match(self):
|
|
config_keys = [
|
|
CaseKey({"hidden_size": 2048, "group_size": 64, "num_tokens": 16}),
|
|
CaseKey({"hidden_size": 2048, "group_size": 64, "num_tokens": 32}),
|
|
CaseKey({"hidden_size": 2048, "group_size": 128, "num_tokens": 16}),
|
|
CaseKey({"hidden_size": 2048, "group_size": 128, "num_tokens": 32}),
|
|
CaseKey({"hidden_size": 4096, "group_size": 64, "num_tokens": 16}),
|
|
CaseKey({"hidden_size": 4096, "group_size": 64, "num_tokens": 32}),
|
|
CaseKey({"hidden_size": 4096, "group_size": 128, "num_tokens": 16}),
|
|
CaseKey({"hidden_size": 4096, "group_size": 128, "num_tokens": 32}),
|
|
]
|
|
|
|
args = _generate_fake_input(20, 3000, 70)
|
|
selected_key = pick_config(args, config_keys)
|
|
assert selected_key == CaseKey(
|
|
{"hidden_size": 2048, "group_size": 64, "num_tokens": 32}
|
|
)
|
|
|
|
def test_config_picker_no_configs(self):
|
|
config_keys: list[dict] = []
|
|
|
|
args = _generate_fake_input(16, 4096, 128)
|
|
selected_key = pick_config(args, config_keys)
|
|
assert selected_key is None
|
|
|
|
def test_config_picker_fallback_to_largest(self):
|
|
config_keys = [
|
|
CaseKey({"hidden_size": 2048, "group_size": 64, "num_tokens": 16}),
|
|
CaseKey({"hidden_size": 2048, "group_size": 64, "num_tokens": 32}),
|
|
CaseKey({"hidden_size": 2048, "group_size": 128, "num_tokens": 16}),
|
|
CaseKey({"hidden_size": 2048, "group_size": 128, "num_tokens": 32}),
|
|
CaseKey({"hidden_size": 4096, "group_size": 64, "num_tokens": 16}),
|
|
CaseKey({"hidden_size": 4096, "group_size": 64, "num_tokens": 32}),
|
|
CaseKey({"hidden_size": 4096, "group_size": 128, "num_tokens": 16}),
|
|
CaseKey({"hidden_size": 4096, "group_size": 128, "num_tokens": 32}),
|
|
]
|
|
|
|
args = _generate_fake_input(64, 8192, 256)
|
|
selected_key = pick_config(args, config_keys)
|
|
assert selected_key == CaseKey(
|
|
{"hidden_size": 4096, "group_size": 128, "num_tokens": 32}
|
|
)
|
|
|
|
|
|
class TestPerTokenGroupFp8QuantCorrectness:
|
|
@pytest.mark.parametrize(
|
|
"shape", [(31, 128), (32, 128), (63, 256), (64, 256), (16, 512), (2048, 5120)]
|
|
)
|
|
@pytest.mark.parametrize("column_major", [False, True])
|
|
@pytest.mark.parametrize("tma_aligned", [False, True])
|
|
@pytest.mark.parametrize("scale_ue8m0", [False, True])
|
|
@pytest.mark.parametrize("group_size", [64, 128])
|
|
def test_per_token_group_fp8_quant(
|
|
self,
|
|
shape,
|
|
column_major: bool,
|
|
tma_aligned: bool,
|
|
scale_ue8m0: bool,
|
|
group_size: int,
|
|
):
|
|
skip_if_platform_unsupported("per_token_group_fp8_quant")
|
|
|
|
torch.manual_seed(42)
|
|
num_tokens, hidden_size = shape
|
|
fp8_min, fp8_max = get_fp8_min_max()
|
|
eps = 1e-10
|
|
input = (
|
|
torch.randn((num_tokens, hidden_size), device="cuda", dtype=torch.bfloat16)
|
|
* 8
|
|
)
|
|
ref_q = torch.empty(input.shape, device=input.device, dtype=FP8_DTYPE)
|
|
ops_q = ref_q.clone()
|
|
|
|
groups_per_row = hidden_size // group_size
|
|
if column_major:
|
|
if tma_aligned:
|
|
tma_alignment = 4
|
|
tma_aligned_m = (
|
|
(num_tokens + tma_alignment - 1) // tma_alignment * tma_alignment
|
|
)
|
|
shape = (num_tokens, groups_per_row)
|
|
stride = (1, tma_aligned_m)
|
|
ref_s = torch.empty_strided(
|
|
shape, stride, device=input.device, dtype=torch.float32
|
|
)
|
|
else:
|
|
ref_s = torch.empty(
|
|
(groups_per_row, num_tokens),
|
|
device=input.device,
|
|
dtype=torch.float32,
|
|
).transpose(0, 1)
|
|
else:
|
|
ref_s = torch.empty(
|
|
(num_tokens, groups_per_row), device=input.device, dtype=torch.float32
|
|
)
|
|
|
|
ops_s = ref_s.clone()
|
|
|
|
baseline(
|
|
input,
|
|
ref_q,
|
|
ref_s,
|
|
group_size,
|
|
eps,
|
|
fp8_min,
|
|
fp8_max,
|
|
scale_ue8m0,
|
|
column_major,
|
|
tma_aligned,
|
|
)
|
|
per_token_group_fp8_quant(
|
|
input,
|
|
ops_q,
|
|
ops_s,
|
|
group_size,
|
|
eps,
|
|
fp8_min,
|
|
fp8_max,
|
|
scale_ue8m0,
|
|
column_major,
|
|
tma_aligned,
|
|
)
|
|
|
|
assert torch.allclose(ref_s, ops_s)
|
|
# allow 1 ULP difference
|
|
assert (
|
|
ref_q.view(torch.uint8).to(torch.int16)
|
|
- ops_q.view(torch.uint8).to(torch.int16)
|
|
).abs().max() <= 1
|
|
|
|
|
|
class TestPerTokenGroupFp8QuantIntegration:
|
|
def test_kernel_registration_integration(self):
|
|
from vllm.kernels.helion.register import get_registered_kernels
|
|
|
|
registered_kernels = get_registered_kernels()
|
|
assert "per_token_group_fp8_quant" in registered_kernels
|
|
|
|
kernel_wrapper = registered_kernels["per_token_group_fp8_quant"]
|
|
assert kernel_wrapper.op_name == "per_token_group_fp8_quant"
|
|
assert kernel_wrapper._config_picker is not None
|
|
assert kernel_wrapper._mutates_args == ["output_q", "output_s"]
|
|
|
|
def test_fake_impl_functionality(self):
|
|
skip_if_platform_unsupported("per_token_group_fp8_quant")
|
|
from vllm.kernels.helion.register import get_registered_kernels
|
|
|
|
registered_kernels = get_registered_kernels()
|
|
kernel_wrapper = registered_kernels["per_token_group_fp8_quant"]
|
|
fake_impl = kernel_wrapper._fake_impl
|
|
|
|
args = _generate_fake_input(16, 4096, 128)
|
|
assert fake_impl(*args) is None
|