[DSV4] perf: Make FP8 quant output tensor contiguous (#27926)

Co-authored-by: liqichao <liqichao@baidu.com>
Co-authored-by: chenbong <bhchen@stu.xmu.edu.cn>
This commit is contained in:
Kaixi
2026-07-07 17:40:59 -07:00
committed by GitHub
co-authored by liqichao chenbong
parent b363249423
commit d7dcdf3efd
5 changed files with 479 additions and 7 deletions
@@ -0,0 +1,212 @@
"""DeepSeek-V4 wo_a FP8 activation quant for DeepGEMM fp8_einsum.
Covers the dedicated DSV4 wo_a quant helper: bit-exact FP8/scales against the
ordinary flat UE8M0 quant values, group-major scale storage, large / DSV4-shaped
token axes, and the DeepGEMM fp8_einsum consumer contract.
"""
import unittest
import torch
import sglang.jit_kernel.dsv4.fp8_wo_a as fp8_wo_a_module
from sglang.jit_kernel.dsv4 import sglang_per_token_group_quant_fp8_dsv4_wo_a
from sglang.srt.layers.quantization.fp8_kernel import (
fp8_dtype,
sglang_per_token_group_quant_fp8,
)
from sglang.srt.layers.quantization.fp8_utils import (
block_quant_dequant,
quant_weight_ue8m0,
transform_scale_ue8m0,
)
from sglang.srt.utils import get_device_sm
from sglang.test.ci.ci_register import register_cuda_ci
from sglang.test.test_utils import CustomTestCase
register_cuda_ci(est_time=60, stage="base-b-kernel-unit", runner_config="4-gpu-b200")
_GROUP_SIZE = 128
class TestDeepSeekV4FP8WoA(CustomTestCase):
@classmethod
def setUpClass(cls):
if not torch.cuda.is_available():
raise unittest.SkipTest("CUDA is not available")
if get_device_sm() < 100:
raise unittest.SkipTest("Test requires CUDA SM 100 or higher")
try:
import deep_gemm
except ImportError as exc:
raise unittest.SkipTest("deep_gemm is required") from exc
cls.deep_gemm = deep_gemm
def _flat_reference(self, o):
T, G, D = o.shape
q_ref, s_ref = sglang_per_token_group_quant_fp8(
o.contiguous().view(T * G, D),
_GROUP_SIZE,
scale_ue8m0=True,
)
return q_ref.view(T, G, D), s_ref.view(T, G, D // _GROUP_SIZE)
def _strided_tgd(self, T, G, D, dtype, device):
storage = (
torch.randn(T, G + 1, D, device=device, dtype=torch.float32) * 0.25
).to(dtype)
o = storage[:, 1:, :]
self.assertFalse(o.is_contiguous())
self.assertEqual(o.stride(-1), 1)
return o
def _assert_matches_flat_reference(self, o, o_fp8, o_s):
T, G, D = o.shape
q_ref, s_ref = self._flat_reference(o)
torch.cuda.synchronize()
self.assertEqual(o_fp8.shape, (T, G, D))
self.assertEqual(o_fp8.dtype, fp8_dtype)
self.assertEqual(o_s.shape, (T, G, D // _GROUP_SIZE))
self.assertEqual(o_s.dtype, torch.float32)
self.assertEqual(o_s.stride(), (D // _GROUP_SIZE, T * (D // _GROUP_SIZE), 1))
self.assertTrue(o_s[:, 0, :].is_contiguous())
self.assertTrue(
torch.equal(o_fp8.view(torch.int8), q_ref.view(torch.int8)),
"fp8 codes differ",
)
self.assertTrue(torch.equal(o_s, s_ref), "scales differ")
def test_dsv4_wo_a_quant_matches_flat_reference(self):
torch.manual_seed(1)
torch.cuda.manual_seed_all(1)
device = torch.device("cuda")
for dtype, T, G, D in [
(torch.bfloat16, 9, 5, 384),
(torch.float16, 7, 3, 512),
]:
with self.subTest(dtype=dtype, T=T, G=G, D=D):
o = (
torch.randn(T, G, D, device=device, dtype=torch.float32) * 0.25
).to(dtype)
o_fp8, o_s = sglang_per_token_group_quant_fp8_dsv4_wo_a(o)
self._assert_matches_flat_reference(o, o_fp8, o_s)
o = self._strided_tgd(T, G, D, dtype, device)
o_fp8, o_s = sglang_per_token_group_quant_fp8_dsv4_wo_a(o)
self._assert_matches_flat_reference(o, o_fp8, o_s)
def test_dsv4_wo_a_quant_empty_token_dimension(self):
o = torch.empty(0, 3, 256, device="cuda", dtype=torch.bfloat16)
o_fp8, o_s = sglang_per_token_group_quant_fp8_dsv4_wo_a(o)
self.assertEqual(o_fp8.shape, o.shape)
self.assertEqual(o_fp8.dtype, fp8_dtype)
self.assertEqual(o_s.shape, (0, 3, 2))
self.assertEqual(o_s.dtype, torch.float32)
self.assertEqual(o_s.stride(), (2, 2, 1))
def test_dsv4_wo_a_quant_large_token_dimension(self):
torch.manual_seed(2)
torch.cuda.manual_seed_all(2)
cases = [
(10_001, 1, 128, False),
(10_001, 8, 4096, False),
(300_000, 1, 128, True),
]
device = torch.device("cuda")
for T, G, D, strided in cases:
with self.subTest(T=T, G=G, D=D, strided=strided):
if strided:
o = self._strided_tgd(T, G, D, torch.bfloat16, device)
else:
o = (
torch.randn(T, G, D, device=device, dtype=torch.float32) * 0.25
).to(torch.bfloat16)
o_fp8, o_s = sglang_per_token_group_quant_fp8_dsv4_wo_a(o)
self._assert_matches_flat_reference(o, o_fp8, o_s)
def test_dsv4_wo_a_quant_uses_dedicated_jit(self):
torch.manual_seed(3)
torch.cuda.manual_seed_all(3)
original_jit_module = fp8_wo_a_module._jit_module
jit_module_calls = 0
def wrapped_jit_module(*args, **kwargs):
nonlocal jit_module_calls
jit_module_calls += 1
return original_jit_module(*args, **kwargs)
fp8_wo_a_module._jit_module = wrapped_jit_module
try:
o = self._strided_tgd(3, 2, 256, torch.bfloat16, "cuda")
sglang_per_token_group_quant_fp8_dsv4_wo_a(o)
torch.cuda.synchronize()
self.assertGreater(jit_module_calls, 0)
finally:
fp8_wo_a_module._jit_module = original_jit_module
def test_fp8_wo_a_einsum_uses_group_major_activation_scales(self):
torch.manual_seed(0)
torch.cuda.manual_seed_all(0)
cases = [
(5, 2, 256, 256),
(7, 8, 4096, 1024),
]
device = torch.device("cuda")
for T, G, D, R in cases:
with self.subTest(T=T, G=G, D=D, R=R):
token_scale = torch.linspace(0.5, 1.5, T, device=device).view(T, 1, 1)
group_scale = torch.pow(
2.0, (torch.arange(G, device=device).float() % 5) - 2.0
).view(1, G, 1)
o = (
torch.randn(T, G, D, device=device, dtype=torch.float32)
* token_scale
* group_scale
* 0.2
).to(torch.bfloat16)
weight = (
torch.randn(G, R, D, device=device, dtype=torch.float32) * 0.2
).to(torch.bfloat16)
weight_fp8, weight_s_raw = quant_weight_ue8m0(
weight, weight_block_size=[_GROUP_SIZE, _GROUP_SIZE]
)
weight_s = transform_scale_ue8m0(weight_s_raw, mn=R)
q_dsv4, s_dsv4 = sglang_per_token_group_quant_fp8_dsv4_wo_a(o)
out = torch.empty(T, G, R, device=device, dtype=torch.bfloat16)
self.deep_gemm.fp8_einsum(
"bhr,hdr->bhd",
(q_dsv4, s_dsv4),
(weight_fp8, weight_s),
out,
recipe=(1, 1, _GROUP_SIZE),
)
torch.cuda.synchronize()
o_dequant = q_dsv4.float().view(
T, G, D // _GROUP_SIZE, _GROUP_SIZE
) * s_dsv4.unsqueeze(-1)
weight_dequant = block_quant_dequant(
weight_fp8,
weight_s_raw,
block_size=[_GROUP_SIZE, _GROUP_SIZE],
dtype=torch.float32,
)
ref = torch.einsum(
"tgd,grd->tgr", o_dequant.view(T, G, D), weight_dequant.float()
).to(torch.bfloat16)
torch.testing.assert_close(out, ref, atol=1e-1, rtol=2e-2)
if __name__ == "__main__":
unittest.main()