195 lines
5.7 KiB
Python
195 lines
5.7 KiB
Python
import sys
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
import torch
|
|
from sgl_kernel.scalar_type import scalar_types
|
|
|
|
from sglang.jit_kernel.gptq_marlin import gptq_marlin_gemm
|
|
from sglang.srt.layers.quantization.marlin_utils import (
|
|
check_marlin_supported,
|
|
marlin_make_workspace,
|
|
)
|
|
from sglang.srt.layers.quantization.marlin_utils_fp4 import (
|
|
apply_fp4_marlin_linear,
|
|
nvfp4_marlin_process_global_scale,
|
|
prepare_nvfp4_layer_for_marlin,
|
|
)
|
|
from sglang.srt.utils.common import is_sm80_supported, is_sm90_supported
|
|
from sglang.test.ci.ci_register import register_cuda_ci
|
|
from sglang.test.test_marlin_utils import (
|
|
awq_marlin_quantize,
|
|
make_nvfp4_weight_and_ref,
|
|
marlin_quantize,
|
|
)
|
|
|
|
register_cuda_ci(est_time=13, suite="base-b-kernel-unit-1-gpu-large")
|
|
register_cuda_ci(est_time=120, suite="nightly-kernel-1-gpu", nightly=True)
|
|
|
|
MNK_FACTORS = [
|
|
(1, 1, 1),
|
|
(1, 4, 8),
|
|
(13, 17, 67),
|
|
(257, 13, 11),
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize("k_chunk", [128])
|
|
@pytest.mark.parametrize("n_chunk", [64, 256])
|
|
@pytest.mark.parametrize("quant_type", [scalar_types.uint4, scalar_types.uint4b8])
|
|
@pytest.mark.parametrize("group_size", [-1, 128])
|
|
@pytest.mark.parametrize("mnk_factors", MNK_FACTORS)
|
|
@pytest.mark.parametrize("act_order", [False, True])
|
|
def test_gptq_marlin_gemm(
|
|
k_chunk,
|
|
n_chunk,
|
|
quant_type,
|
|
group_size,
|
|
mnk_factors,
|
|
act_order,
|
|
):
|
|
m_factor, n_factor, k_factor = mnk_factors
|
|
has_zp = quant_type in [scalar_types.uint4, scalar_types.uint8]
|
|
|
|
size_m = m_factor
|
|
size_k = k_chunk * k_factor
|
|
size_n = n_chunk * n_factor
|
|
|
|
if act_order:
|
|
if group_size == -1:
|
|
return
|
|
if group_size == size_k:
|
|
return
|
|
if has_zp:
|
|
return
|
|
|
|
if size_k % group_size != 0:
|
|
return
|
|
|
|
a_input = torch.randn((size_m, size_k), dtype=torch.float16, device="cuda")
|
|
b_weight = torch.randn((size_k, size_n), dtype=torch.float16, device="cuda")
|
|
|
|
if has_zp:
|
|
w_ref, marlin_q_w, marlin_s, marlin_zp = awq_marlin_quantize(
|
|
b_weight, quant_type, group_size
|
|
)
|
|
g_idx = None
|
|
sort_indices = None
|
|
marlin_s2 = None
|
|
else:
|
|
w_ref, marlin_q_w, marlin_s, g_idx, sort_indices, _ = marlin_quantize(
|
|
b_weight, quant_type, group_size, act_order
|
|
)
|
|
marlin_zp = None
|
|
marlin_s2 = None
|
|
|
|
workspace = marlin_make_workspace(w_ref.device)
|
|
|
|
output = gptq_marlin_gemm(
|
|
a_input,
|
|
None,
|
|
marlin_q_w,
|
|
marlin_s,
|
|
marlin_s2,
|
|
marlin_zp,
|
|
g_idx,
|
|
sort_indices,
|
|
workspace,
|
|
quant_type,
|
|
a_input.shape[0],
|
|
b_weight.shape[1],
|
|
a_input.shape[1],
|
|
is_k_full=True,
|
|
use_atomic_add=False,
|
|
use_fp32_reduce=False,
|
|
is_zp_float=False,
|
|
)
|
|
|
|
output_ref = torch.matmul(a_input, w_ref)
|
|
torch.cuda.synchronize()
|
|
|
|
# JIT kernel should produce approximately correct results vs torch.matmul
|
|
max_diff = torch.mean(torch.abs(output - output_ref)) / torch.mean(
|
|
torch.abs(output_ref)
|
|
)
|
|
assert max_diff < 0.04
|
|
|
|
|
|
@pytest.mark.skip(reason="Skip, test pass locally but compiling takes too long in CI")
|
|
@pytest.mark.skipif(
|
|
not (is_sm80_supported() or is_sm90_supported()),
|
|
reason="NVFP4 Marlin fallback tests require CUDA SM8X/SM9X",
|
|
)
|
|
@pytest.mark.parametrize("dtype", [torch.float16, torch.bfloat16])
|
|
def test_nvfp4_marlin_support_and_scale_transforms_sm80_sm90(dtype):
|
|
major, minor = torch.cuda.get_device_capability()
|
|
capability = major * 10 + minor
|
|
assert check_marlin_supported(
|
|
scalar_types.float4_e2m1f,
|
|
group_size=16,
|
|
has_zp=False,
|
|
device_capability=capability,
|
|
)
|
|
|
|
global_scale = torch.tensor(1.0, dtype=dtype, device="cuda")
|
|
actual_global_scale = nvfp4_marlin_process_global_scale(global_scale)
|
|
assert actual_global_scale.is_cuda
|
|
assert actual_global_scale.ndim == 1
|
|
assert actual_global_scale.numel() == 1
|
|
if dtype == torch.float16:
|
|
assert actual_global_scale.item() == 128.0
|
|
else:
|
|
assert actual_global_scale.item() == 2.0**119
|
|
|
|
|
|
@pytest.mark.skip(reason="Skip, test pass locally but compiling takes too long in CI")
|
|
@pytest.mark.skipif(
|
|
not (is_sm80_supported() or is_sm90_supported()),
|
|
reason="NVFP4 Marlin dense numeric test requires CUDA SM80, SM86, or SM90",
|
|
)
|
|
@pytest.mark.parametrize("dtype", [torch.float16, torch.bfloat16])
|
|
def test_nvfp4_marlin_dense_matches_dequant_reference(dtype):
|
|
torch.manual_seed(0)
|
|
|
|
size_m = 17
|
|
size_k = 256
|
|
size_n = 192
|
|
group_size = 16
|
|
|
|
a_input = torch.randn((size_m, size_k), dtype=dtype, device="cuda") / 10
|
|
fp4_weight, scales, global_scale, weight_ref = make_nvfp4_weight_and_ref(
|
|
size_n, size_k, dtype, group_size=group_size
|
|
)
|
|
|
|
layer = torch.nn.Module()
|
|
layer.quant_config = SimpleNamespace(group_size=group_size)
|
|
layer.output_size_per_partition = size_n
|
|
layer.input_size_per_partition = size_k
|
|
layer.params_dtype = dtype
|
|
layer.weight = torch.nn.Parameter(fp4_weight, requires_grad=False)
|
|
layer.weight_scale = torch.nn.Parameter(scales, requires_grad=False)
|
|
layer.weight_global_scale = torch.nn.Parameter(
|
|
global_scale.reshape(1), requires_grad=False
|
|
)
|
|
prepare_nvfp4_layer_for_marlin(layer)
|
|
|
|
output = apply_fp4_marlin_linear(
|
|
a_input,
|
|
layer.weight,
|
|
layer.weight_scale,
|
|
layer.weight_global_scale,
|
|
layer.workspace,
|
|
size_n,
|
|
size_k,
|
|
use_fp32_reduce=True,
|
|
)
|
|
|
|
output_ref = torch.matmul(a_input, weight_ref.T)
|
|
torch.cuda.synchronize()
|
|
|
|
torch.testing.assert_close(output, output_ref, rtol=0.04, atol=0.04)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(pytest.main([__file__, "-v", "-s"]))
|