[CI] Slim JIT kernel unit tests (#36887)
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import itertools
|
||||
import math
|
||||
import sys
|
||||
|
||||
@@ -7,6 +8,7 @@ import torch
|
||||
import torch.nn.functional as F
|
||||
from scipy.linalg import hadamard
|
||||
|
||||
from sglang.kernels.jit.utils import get_ci_test_range
|
||||
from sglang.kernels.ops.quantization.hadamard import (
|
||||
hadamard_transform,
|
||||
hadamard_transform_12n,
|
||||
@@ -16,7 +18,8 @@ from sglang.kernels.ops.quantization.hadamard import (
|
||||
)
|
||||
from sglang.test.ci.ci_register import register_cuda_ci
|
||||
|
||||
register_cuda_ci(est_time=128, stage="base-b-kernel-unit", runner_config="1-gpu-large")
|
||||
register_cuda_ci(est_time=32, stage="base-b-kernel-unit", runner_config="1-gpu-large")
|
||||
register_cuda_ci(est_time=128, stage="nightly", runner_config="1-gpu-large")
|
||||
|
||||
# Exact M×N Hadamard matrices (±1 entries) copied from
|
||||
# python/sglang/kernels/jit/csrc/fast-hadamard-transform/code_gen.py.
|
||||
@@ -218,12 +221,42 @@ def hadamard_transform_mn_ref(x, multiple, scale=1.0):
|
||||
return x[..., : x_shape[-1]].reshape(*x_shape)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("dtype", [torch.float32, torch.float16, torch.bfloat16])
|
||||
@pytest.mark.parametrize(
|
||||
"dim",
|
||||
# Power-of-2 dims from python/sglang/kernels/aot/tests/test_hadamard.py (old AOT test)
|
||||
[1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768],
|
||||
_DTYPES = [torch.float32, torch.float16, torch.bfloat16]
|
||||
_POWER_OF_TWO_DIMS = [
|
||||
1,
|
||||
2,
|
||||
4,
|
||||
8,
|
||||
16,
|
||||
32,
|
||||
64,
|
||||
128,
|
||||
256,
|
||||
512,
|
||||
1024,
|
||||
2048,
|
||||
4096,
|
||||
8192,
|
||||
16384,
|
||||
32768,
|
||||
]
|
||||
_POWER_OF_TWO_CASES = get_ci_test_range(
|
||||
list(itertools.product(_POWER_OF_TWO_DIMS, _DTYPES)),
|
||||
[
|
||||
(1, torch.float32),
|
||||
(2, torch.float16),
|
||||
(4, torch.bfloat16),
|
||||
(32, torch.bfloat16),
|
||||
(256, torch.float32),
|
||||
(2048, torch.float16),
|
||||
(8192, torch.bfloat16),
|
||||
(16384, torch.float32),
|
||||
(32768, torch.float16),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("dim,dtype", _POWER_OF_TWO_CASES)
|
||||
def test_hadamard_transform(dim, dtype):
|
||||
device = "cuda"
|
||||
|
||||
@@ -248,13 +281,18 @@ def test_hadamard_transform(dim, dtype):
|
||||
torch.testing.assert_close(out.float(), out_ref, rtol=rtol, atol=atol)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("dtype", [torch.float32, torch.float16, torch.bfloat16])
|
||||
@pytest.mark.parametrize(
|
||||
"dim",
|
||||
# Non-power-of-2 dims to test the padding path
|
||||
# (137 from python/sglang/kernels/aot/tests/test_hadamard.py, 500/1000 added for coverage)
|
||||
[137, 500, 1000],
|
||||
_NON_POWER_OF_TWO_CASES = get_ci_test_range(
|
||||
list(itertools.product([137, 500, 1000], _DTYPES)),
|
||||
[
|
||||
(137, torch.float32),
|
||||
(137, torch.bfloat16),
|
||||
(500, torch.float16),
|
||||
(1000, torch.bfloat16),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("dim,dtype", _NON_POWER_OF_TWO_CASES)
|
||||
def test_hadamard_transform_non_power_of_two(dim, dtype):
|
||||
device = "cuda"
|
||||
|
||||
@@ -327,8 +365,18 @@ _28N_DIMS = [28 * (2**k) for k in range(2, 9)] # 112, 224, ... , 7168
|
||||
_40N_DIMS = [40 * (2**k) for k in range(2, 9)] # 160, 320, ... , 10240
|
||||
|
||||
|
||||
@pytest.mark.parametrize("dtype", [torch.float32, torch.float16, torch.bfloat16])
|
||||
@pytest.mark.parametrize("dim", _12N_DIMS)
|
||||
def _mn_cases(dims):
|
||||
return get_ci_test_range(
|
||||
list(itertools.product(dims, _DTYPES)),
|
||||
[
|
||||
(dims[0], torch.float32),
|
||||
(dims[len(dims) // 2], torch.float16),
|
||||
(dims[-1], torch.bfloat16),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("dim,dtype", _mn_cases(_12N_DIMS))
|
||||
def test_hadamard_transform_12n(dim, dtype):
|
||||
device = "cuda"
|
||||
|
||||
@@ -351,8 +399,7 @@ def test_hadamard_transform_12n(dim, dtype):
|
||||
torch.testing.assert_close(out.float(), out_ref, rtol=rtol, atol=atol)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("dtype", [torch.float32, torch.float16, torch.bfloat16])
|
||||
@pytest.mark.parametrize("dim", _20N_DIMS)
|
||||
@pytest.mark.parametrize("dim,dtype", _mn_cases(_20N_DIMS))
|
||||
def test_hadamard_transform_20n(dim, dtype):
|
||||
device = "cuda"
|
||||
|
||||
@@ -375,8 +422,7 @@ def test_hadamard_transform_20n(dim, dtype):
|
||||
torch.testing.assert_close(out.float(), out_ref, rtol=rtol, atol=atol)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("dtype", [torch.float32, torch.float16, torch.bfloat16])
|
||||
@pytest.mark.parametrize("dim", _28N_DIMS)
|
||||
@pytest.mark.parametrize("dim,dtype", _mn_cases(_28N_DIMS))
|
||||
def test_hadamard_transform_28n(dim, dtype):
|
||||
device = "cuda"
|
||||
|
||||
@@ -399,8 +445,7 @@ def test_hadamard_transform_28n(dim, dtype):
|
||||
torch.testing.assert_close(out.float(), out_ref, rtol=rtol, atol=atol)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("dtype", [torch.float32, torch.float16, torch.bfloat16])
|
||||
@pytest.mark.parametrize("dim", _40N_DIMS)
|
||||
@pytest.mark.parametrize("dim,dtype", _mn_cases(_40N_DIMS))
|
||||
def test_hadamard_transform_40n(dim, dtype):
|
||||
device = "cuda"
|
||||
|
||||
|
||||
@@ -32,8 +32,9 @@ from sglang.kernels.ops.quantization.per_token_group_quant import (
|
||||
)
|
||||
from sglang.test.ci.ci_register import register_cuda_ci
|
||||
|
||||
register_cuda_ci(est_time=90, stage="base-b-kernel-unit", runner_config="1-gpu-large")
|
||||
register_cuda_ci(est_time=65, stage="base-b-kernel-unit", runner_config="1-gpu-large")
|
||||
register_cuda_ci(est_time=90, stage="base-b-kernel-unit", runner_config="4-gpu-b200")
|
||||
register_cuda_ci(est_time=120, stage="nightly", runner_config="1-gpu-large")
|
||||
|
||||
G = 128
|
||||
FMAX = float(fp8_max) # 448 for e4m3
|
||||
@@ -220,9 +221,20 @@ def test_ue8m0_row_packed_bitexact(hidden):
|
||||
# fp32 / int8 scale paths: exact stored scale + dequant round-trip (the codes
|
||||
# are not bit-reproducible under fast-math division).
|
||||
# --------------------------------------------------------------------------- #
|
||||
@pytest.mark.parametrize("hidden", [4096, 768])
|
||||
@pytest.mark.parametrize("column_major", [False, True])
|
||||
@pytest.mark.parametrize("dtype", [torch.bfloat16, torch.float16])
|
||||
FP32_SCALE_CASES = get_ci_test_range(
|
||||
list(
|
||||
itertools.product([torch.bfloat16, torch.float16], [False, True], [4096, 768])
|
||||
),
|
||||
[
|
||||
(torch.bfloat16, False, 4096),
|
||||
(torch.bfloat16, True, 768),
|
||||
(torch.float16, False, 768),
|
||||
(torch.float16, True, 4096),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("dtype,column_major,hidden", FP32_SCALE_CASES)
|
||||
def test_fp32_scale(dtype, column_major, hidden):
|
||||
"""fp32 scale (row-major contiguous / col-major TMA view): the stored scale
|
||||
is amax/FMAX (a single multiply, bit-exact) and dequant round-trips within
|
||||
@@ -342,12 +354,19 @@ MASKED_CASES = get_ci_test_range(
|
||||
list(itertools.product([2, 5], [2048, 4096], [128, 384])),
|
||||
[(2, 2048, 128), (5, 4096, 384)],
|
||||
)
|
||||
MASKED_TEST_CASES = get_ci_test_range(
|
||||
list(itertools.product(MASKED_CASES, [None, 4], [torch.int32, torch.int64])),
|
||||
[
|
||||
((2, 2048, 128), None, torch.int32),
|
||||
((2, 2048, 128), 4, torch.int64),
|
||||
((5, 4096, 384), None, torch.int32),
|
||||
((5, 4096, 384), 4, torch.int64),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("masked_m_dtype", [torch.int32, torch.int64])
|
||||
@pytest.mark.parametrize("expected_m", [None, 4])
|
||||
@pytest.mark.parametrize("num_experts,hidden,tokens_pad", MASKED_CASES)
|
||||
def test_masked(num_experts, hidden, tokens_pad, expected_m, masked_m_dtype):
|
||||
@pytest.mark.parametrize("shape,expected_m,masked_m_dtype", MASKED_TEST_CASES)
|
||||
def test_masked(shape, expected_m, masked_m_dtype):
|
||||
"""Masked EP-MoE schedule (col-packed ue8m0, plain quant -- no silu, so the
|
||||
quant is bit-reproducible): rows < masked_m[e] are bit-exact vs the torch
|
||||
reference; rows >= masked_m[e] stay zero (untouched). Fusion numerics are
|
||||
@@ -359,6 +378,7 @@ def test_masked(num_experts, hidden, tokens_pad, expected_m, masked_m_dtype):
|
||||
expected_m=4 shrinks the grid's token axis far below masked_m, so the
|
||||
grid-stride token loop must still cover every valid token -- guards the
|
||||
host-hint-only contract (a wrong hint can never drop tokens)."""
|
||||
num_experts, hidden, tokens_pad = shape
|
||||
torch.manual_seed(num_experts * 1000 + hidden + tokens_pad)
|
||||
x = torch.randn(
|
||||
num_experts, tokens_pad, hidden, device="cuda", dtype=torch.bfloat16
|
||||
@@ -431,9 +451,26 @@ def test_masked_fused():
|
||||
assert torch.all(x_q[e, m:].view(torch.int8) == 0), "padding touched"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("poison", [float("nan"), float("inf"), -float("inf")])
|
||||
@pytest.mark.parametrize("scale_ue8m0", [False, True])
|
||||
@pytest.mark.parametrize("masked", [False, True])
|
||||
NON_FINITE_CASES = get_ci_test_range(
|
||||
list(
|
||||
itertools.product(
|
||||
[float("nan"), float("inf"), -float("inf")],
|
||||
[False, True],
|
||||
[False, True],
|
||||
)
|
||||
),
|
||||
[
|
||||
(float("nan"), False, False),
|
||||
(float("nan"), True, True),
|
||||
(float("inf"), False, True),
|
||||
(float("inf"), True, False),
|
||||
(-float("inf"), False, False),
|
||||
(-float("inf"), True, True),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("poison,scale_ue8m0,masked", NON_FINITE_CASES)
|
||||
def test_non_finite_inputs_are_sanitized(poison, scale_ue8m0, masked):
|
||||
"""CUDA-graph capture warmup runs the model on reused, uninitialized
|
||||
buffers, so quant inputs can contain NaN/Inf bit patterns. The v1/v2/Triton
|
||||
|
||||
Reference in New Issue
Block a user