1038 lines
38 KiB
Python
1038 lines
38 KiB
Python
from itertools import product
|
|
from typing import Tuple # noqa: UP035
|
|
|
|
import ml_dtypes
|
|
import numpy as np
|
|
import pytest
|
|
import torch
|
|
import tvm
|
|
from tvm import relax
|
|
from tvm.relax.frontend import nn
|
|
from tvm.relax.frontend.nn import spec
|
|
from tvm.s_tir import dlight as dl
|
|
|
|
from mlc_llm.compiler_pass.dispatch_triton_kernel import DispatchTritonKernel
|
|
from mlc_llm.op import batch_matmul, cutlass, moe_matmul, triton
|
|
from mlc_llm.quantization.block_scale_quantization import rowwise_group_quant_fp8
|
|
|
|
# test category "op_correctness"
|
|
pytestmark = [pytest.mark.op_correctness]
|
|
|
|
block_size = (128, 128)
|
|
fp8_dtype = "float8_e4m3fn"
|
|
|
|
torch_fp8_dtype = torch.float8_e4m3fn
|
|
torch_device = torch.device("cuda")
|
|
|
|
torch.set_grad_enabled(False)
|
|
|
|
|
|
def test_fp8_block_matmul_cutlass(M: int, N: int, K: int, dtype: str):
|
|
class TestModule(nn.Module):
|
|
def __init__(self):
|
|
pass
|
|
|
|
def cutlass_gemm(self, x: nn.Tensor, w: nn.Tensor, w_scale: nn.Tensor):
|
|
n, k = w.shape
|
|
# assert n % block_size[0] == 0
|
|
assert k % block_size[1] == 0
|
|
assert (n + block_size[0] - 1) // block_size[0] == w_scale.shape[0]
|
|
assert k // block_size[1] == w_scale.shape[1]
|
|
assert x.shape[1] == k
|
|
x_fp8, x_scale = rowwise_group_quant_fp8(
|
|
x, block_size[1], w.dtype, transpose_scale=True
|
|
)
|
|
assert x_fp8.dtype == w.dtype
|
|
assert x_scale.dtype == "float32"
|
|
o = cutlass.fp8_groupwise_scaled_gemm(x_fp8, x_scale, w, w_scale, block_size, x.dtype)
|
|
return x_fp8, x_scale, o
|
|
|
|
mod, _, ext_mods = TestModule().export_tvm(
|
|
spec={
|
|
"cutlass_gemm": {
|
|
"x": spec.Tensor(("m", K), dtype),
|
|
"w": spec.Tensor((N, K), fp8_dtype),
|
|
"w_scale": spec.Tensor(
|
|
(
|
|
(N + block_size[0] - 1) // block_size[0],
|
|
(K + block_size[1] - 1) // block_size[1],
|
|
),
|
|
"float32",
|
|
),
|
|
},
|
|
},
|
|
allow_extern=True,
|
|
)
|
|
device = tvm.cuda()
|
|
target = tvm.target.Target.from_device(device)
|
|
exec = relax.build(
|
|
mod,
|
|
target=target,
|
|
relax_pipeline=relax.backend.cuda.get_default_pipeline(target),
|
|
)
|
|
vm = relax.VirtualMachine(exec, device)
|
|
|
|
x_torch = torch.rand(M, K, dtype=getattr(torch, dtype), device=torch_device) * 2 - 1
|
|
w_full_torch = torch.rand(N, K, dtype=getattr(torch, dtype), device=torch_device) * 2 - 1
|
|
w_torch, w_scale_torch = blockwise_quant_fp8(w_full_torch, block_size, torch_fp8_dtype)
|
|
x_torch, x_fp8_torch, x_scale_torch = rowwise_quant_fp8(x_torch, block_size, torch_fp8_dtype)
|
|
o_torch = blockwise_matmul(x_fp8_torch, x_scale_torch, w_torch, w_scale_torch, x_torch.dtype)
|
|
x_tvm = tvm.runtime.tensor(x_torch.view(torch.float16).cpu().numpy().view(dtype), device=device)
|
|
w_tvm = tvm.runtime.tensor(
|
|
w_torch.view(torch.uint8).cpu().numpy().view(fp8_dtype), device=device
|
|
)
|
|
w_scale_tvm = tvm.runtime.tensor(w_scale_torch.cpu().numpy(), device=device)
|
|
x_fp8_tvm, x_scale_tvm, o_tvm = vm["cutlass_gemm"](x_tvm, w_tvm, w_scale_tvm)
|
|
x_fp8_tvm = x_fp8_tvm.numpy()
|
|
x_scale_tvm = x_scale_tvm.numpy()
|
|
o_tvm = o_tvm.numpy()
|
|
|
|
np.testing.assert_allclose(
|
|
x_fp8_tvm,
|
|
x_fp8_torch.view(torch.uint8).cpu().numpy().view(fp8_dtype),
|
|
atol=1e-1,
|
|
rtol=1e-1,
|
|
)
|
|
np.testing.assert_allclose(x_scale_tvm.T, x_scale_torch.cpu().numpy(), atol=1e-5, rtol=1e-5)
|
|
atol = 0.5
|
|
rtol = 1e-4
|
|
o_tvm_flat = o_tvm.flatten()
|
|
o_torch_flat = o_torch.view(torch.float16).cpu().numpy().view(dtype).flatten()
|
|
failed_indices = np.where(
|
|
np.abs(o_tvm_flat - o_torch_flat) > (atol + rtol * np.abs(o_torch_flat))
|
|
)[0]
|
|
if len(failed_indices) > 0:
|
|
print(f"failed_indices: {failed_indices}, size: {len(failed_indices)}")
|
|
print(f"o_tvm_flat[failed_indices]: {o_tvm_flat[failed_indices]}")
|
|
print(f"o_torch_flat[failed_indices]: {o_torch_flat[failed_indices]}")
|
|
np.testing.assert_allclose(
|
|
o_tvm,
|
|
o_torch.view(torch.float16).cpu().numpy().view(dtype),
|
|
atol=atol,
|
|
rtol=rtol,
|
|
)
|
|
|
|
|
|
def test_fp8_block_matmul_triton(M: int, N: int, K: int, dtype: str):
|
|
device = tvm.cuda()
|
|
target = tvm.target.Target.from_device(device)
|
|
|
|
class TestModule(nn.Module):
|
|
def __init__(self):
|
|
pass
|
|
|
|
def triton_gemm(self, x: nn.Tensor, w: nn.Tensor, w_scale: nn.Tensor):
|
|
n, k = w.shape
|
|
assert (n + block_size[0] - 1) // block_size[0] == w_scale.shape[0]
|
|
assert (k + block_size[1] - 1) // block_size[1] == w_scale.shape[1]
|
|
assert x.shape[1] == k
|
|
x_fp8, x_scale = rowwise_group_quant_fp8(
|
|
x, block_size[1], w.dtype, transpose_scale=False
|
|
)
|
|
assert x_fp8.dtype == w.dtype
|
|
assert x_scale.dtype == "float32"
|
|
o = triton.fp8_groupwise_scaled_gemm(
|
|
x_fp8,
|
|
x_scale,
|
|
w,
|
|
w_scale,
|
|
block_size,
|
|
x.dtype,
|
|
)
|
|
return x_fp8, x_scale, o
|
|
|
|
mod, _, ext_mods = TestModule().export_tvm(
|
|
spec={
|
|
"triton_gemm": {
|
|
"x": spec.Tensor(("m", K), dtype),
|
|
"w": spec.Tensor((N, K), fp8_dtype),
|
|
"w_scale": spec.Tensor(
|
|
(
|
|
(N + block_size[0] - 1) // block_size[0],
|
|
(K + block_size[1] - 1) // block_size[1],
|
|
),
|
|
"float32",
|
|
),
|
|
},
|
|
},
|
|
allow_extern=True,
|
|
)
|
|
mod = DispatchTritonKernel(target)(mod)
|
|
exec = relax.build(
|
|
mod,
|
|
target=target,
|
|
relax_pipeline=relax.backend.cuda.get_default_pipeline(target),
|
|
)
|
|
vm = relax.VirtualMachine(exec, device)
|
|
|
|
x_torch = torch.randn(M, K, dtype=getattr(torch, dtype), device=torch_device)
|
|
w_full_torch = torch.randn(N, K, dtype=getattr(torch, dtype), device=torch_device)
|
|
w_torch, w_scale_torch = blockwise_quant_fp8(w_full_torch, block_size, torch_fp8_dtype)
|
|
x_torch, x_fp8_torch, x_scale_torch = rowwise_quant_fp8(x_torch, block_size, torch_fp8_dtype)
|
|
o_torch = blockwise_matmul(x_fp8_torch, x_scale_torch, w_torch, w_scale_torch, x_torch.dtype)
|
|
x_tvm = tvm.runtime.tensor(x_torch.view(torch.float16).cpu().numpy().view(dtype), device=device)
|
|
w_tvm = tvm.runtime.tensor(
|
|
w_torch.view(torch.uint8).cpu().numpy().view(fp8_dtype), device=device
|
|
)
|
|
w_scale_tvm = tvm.runtime.tensor(w_scale_torch.cpu().numpy(), device=device)
|
|
x_fp8_tvm, x_scale_tvm, o_tvm = vm["triton_gemm"](x_tvm, w_tvm, w_scale_tvm)
|
|
x_fp8_tvm = x_fp8_tvm.numpy()
|
|
x_scale_tvm = x_scale_tvm.numpy()
|
|
o_tvm = o_tvm.numpy()
|
|
np.testing.assert_allclose(
|
|
x_fp8_tvm,
|
|
x_fp8_torch.view(torch.uint8).cpu().numpy().view(fp8_dtype),
|
|
atol=1e-1,
|
|
rtol=1e-1,
|
|
)
|
|
np.testing.assert_allclose(x_scale_tvm, x_scale_torch.cpu().numpy(), atol=1e-5, rtol=1e-5)
|
|
atol = 0.5
|
|
rtol = 1e-4
|
|
o_tvm_flat = o_tvm.flatten()
|
|
o_torch_flat = o_torch.view(torch.float16).cpu().numpy().view(dtype).flatten()
|
|
failed_indices = np.where(
|
|
np.abs(o_tvm_flat - o_torch_flat) > (atol + rtol * np.abs(o_torch_flat))
|
|
)[0]
|
|
if len(failed_indices) > 0:
|
|
print(f"failed_indices: {failed_indices}, size: {len(failed_indices)}")
|
|
print(f"o_tvm_flat[failed_indices]: {o_tvm_flat[failed_indices]}")
|
|
print(f"o_torch_flat[failed_indices]: {o_torch_flat[failed_indices]}")
|
|
np.testing.assert_allclose(
|
|
o_tvm,
|
|
o_torch.view(torch.float16).cpu().numpy().view(dtype),
|
|
atol=atol,
|
|
rtol=rtol,
|
|
)
|
|
|
|
|
|
def test_fp8_block_group_matmul_cutlass(M: int, N: int, K: int, dtype: str):
|
|
num_experts = 256
|
|
top_k = 8
|
|
|
|
device = tvm.cuda()
|
|
target = tvm.target.Target.from_device(device)
|
|
|
|
class TestModule(nn.Module):
|
|
def __init__(self):
|
|
pass
|
|
|
|
def cutlass_group_gemm(
|
|
self,
|
|
x: nn.Tensor,
|
|
w: nn.Tensor,
|
|
w_scale: nn.Tensor,
|
|
indptr: nn.Tensor,
|
|
):
|
|
e, n, k = w.shape
|
|
assert e == num_experts
|
|
assert (n + block_size[0] - 1) // block_size[0] == w_scale.shape[1]
|
|
assert (k + block_size[1] - 1) // block_size[1] == w_scale.shape[2]
|
|
assert x.shape[1] == k
|
|
x_fp8, x_scale = rowwise_group_quant_fp8(
|
|
x, block_size[1], w.dtype, transpose_scale=False
|
|
)
|
|
assert x_fp8.dtype == w.dtype
|
|
assert x_scale.dtype == "float32"
|
|
o = cutlass.fp8_groupwise_scaled_group_gemm(
|
|
x_fp8,
|
|
x_scale,
|
|
w,
|
|
w_scale,
|
|
indptr,
|
|
block_size,
|
|
x.dtype,
|
|
)
|
|
return x_fp8, x_scale, o
|
|
|
|
mod, _, ext_mods = TestModule().export_tvm(
|
|
spec={
|
|
"cutlass_group_gemm": {
|
|
"x": spec.Tensor(("m", K), dtype),
|
|
"w": spec.Tensor((num_experts, N, K), fp8_dtype),
|
|
"w_scale": spec.Tensor(
|
|
(
|
|
num_experts,
|
|
(N + block_size[0] - 1) // block_size[0],
|
|
(K + block_size[1] - 1) // block_size[1],
|
|
),
|
|
"float32",
|
|
),
|
|
"indptr": spec.Tensor((num_experts,), "int64"),
|
|
},
|
|
},
|
|
allow_extern=True,
|
|
)
|
|
exec = relax.build(
|
|
mod,
|
|
target=target,
|
|
relax_pipeline=relax.backend.cuda.get_default_pipeline(target),
|
|
)
|
|
vm = relax.VirtualMachine(exec, device)
|
|
|
|
# Randomly sample `top_k` experts for each token with pytorch
|
|
expert_choices = torch.randint(
|
|
0, num_experts, (M * top_k,), device=torch_device, dtype=torch.int32
|
|
)
|
|
|
|
factor = 1
|
|
# Balance so that the number of tokens for each expert is a multiple of `factor`
|
|
token_balance = 0
|
|
num_tokens_list = [int((expert_choices == i).sum().to("cpu")) for i in range(num_experts)]
|
|
for i in range(num_experts):
|
|
if token_balance > 0:
|
|
diff = min(token_balance, num_tokens_list[i])
|
|
num_tokens_list[i] -= diff
|
|
token_balance -= diff
|
|
if num_tokens_list[i] % factor != 0:
|
|
token_balance += factor - num_tokens_list[i] % factor
|
|
num_tokens_list[i] += factor - num_tokens_list[i] % factor
|
|
assert sum(num_tokens_list) == M * top_k
|
|
|
|
indptr = torch.zeros(num_experts + 1, device=torch_device, dtype=torch.int64)
|
|
for i in range(num_experts):
|
|
indptr[i + 1] = indptr[i] + (expert_choices == i).sum()
|
|
token_ids_list = []
|
|
for i in range(num_experts):
|
|
# Get the indices of the tokens that belong to the i-th expert
|
|
token_ids = torch.where(expert_choices == i)[0]
|
|
token_ids_list.append(token_ids)
|
|
|
|
x_torch = torch.randn(M * top_k, K, dtype=getattr(torch, dtype), device=torch_device)
|
|
w_full_torch = torch.randn(num_experts, N, K, dtype=getattr(torch, dtype), device=torch_device)
|
|
w_torch, w_scale_torch = blockwise_quant_fp8(w_full_torch, block_size, torch_fp8_dtype)
|
|
x_torch, x_fp8_torch, x_scale_torch = rowwise_quant_fp8(x_torch, block_size, torch_fp8_dtype)
|
|
o_torch = blockwise_group_matmul(
|
|
x_fp8_torch,
|
|
x_scale_torch,
|
|
w_torch,
|
|
w_scale_torch,
|
|
indptr,
|
|
x_torch.dtype,
|
|
)
|
|
x_tvm = tvm.runtime.tensor(x_torch.view(torch.float16).cpu().numpy().view(dtype), device=device)
|
|
w_tvm = tvm.runtime.tensor(
|
|
w_torch.view(torch.uint8).cpu().numpy().view(fp8_dtype), device=device
|
|
)
|
|
w_scale_tvm = tvm.runtime.tensor(w_scale_torch.cpu().numpy(), device=device)
|
|
indptr_tvm = tvm.runtime.tensor(indptr[1:].cpu().numpy(), device=device)
|
|
x_fp8_tvm, x_scale_tvm, o_tvm = vm["cutlass_group_gemm"](
|
|
x_tvm,
|
|
w_tvm,
|
|
w_scale_tvm,
|
|
indptr_tvm,
|
|
)
|
|
x_fp8_tvm = x_fp8_tvm.numpy()
|
|
x_scale_tvm = x_scale_tvm.numpy()
|
|
o_tvm = o_tvm.numpy()
|
|
np.testing.assert_allclose(
|
|
x_fp8_tvm,
|
|
x_fp8_torch.view(torch.uint8).cpu().numpy().view(fp8_dtype),
|
|
atol=1e-1,
|
|
rtol=1e-1,
|
|
)
|
|
np.testing.assert_allclose(x_scale_tvm, x_scale_torch.cpu().numpy(), atol=1e-5, rtol=1e-5)
|
|
atol = 0.5
|
|
rtol = 1e-4
|
|
o_tvm_flat = o_tvm.flatten()
|
|
o_torch_flat = o_torch.view(torch.float16).cpu().numpy().view(dtype).flatten()
|
|
failed_indices = np.where(
|
|
np.abs(o_tvm_flat - o_torch_flat) > (atol + rtol * np.abs(o_torch_flat))
|
|
)[0]
|
|
if len(failed_indices) > 0:
|
|
print(f"failed_indices: {failed_indices}, size: {len(failed_indices)}")
|
|
print(f"o_tvm_flat[failed_indices]: {o_tvm_flat[failed_indices]}")
|
|
print(f"o_torch_flat[failed_indices]: {o_torch_flat[failed_indices]}")
|
|
np.testing.assert_allclose(
|
|
o_tvm,
|
|
o_torch.view(torch.float16).cpu().numpy().view(dtype),
|
|
atol=atol,
|
|
rtol=rtol,
|
|
)
|
|
|
|
|
|
def test_fp8_block_group_matmul_triton(M: int, N: int, K: int, dtype: str):
|
|
num_experts = 256
|
|
top_k = 8
|
|
|
|
device = tvm.cuda()
|
|
target = tvm.target.Target.from_device(device)
|
|
|
|
class TestModule(nn.Module):
|
|
def __init__(self):
|
|
pass
|
|
|
|
def triton_group_gemm(
|
|
self,
|
|
x: nn.Tensor,
|
|
w: nn.Tensor,
|
|
w_scale: nn.Tensor,
|
|
indptr: nn.Tensor,
|
|
):
|
|
e, n, k = w.shape
|
|
assert e == num_experts
|
|
assert (n + block_size[0] - 1) // block_size[0] == w_scale.shape[1]
|
|
assert (k + block_size[1] - 1) // block_size[1] == w_scale.shape[2]
|
|
assert x.shape[1] == k
|
|
x_fp8, x_scale = rowwise_group_quant_fp8(
|
|
x, block_size[1], w.dtype, transpose_scale=False
|
|
)
|
|
assert x_fp8.dtype == w.dtype
|
|
assert x_scale.dtype == "float32"
|
|
o = triton.fp8_groupwise_scaled_group_gemm(
|
|
x_fp8,
|
|
x_scale,
|
|
w,
|
|
w_scale,
|
|
indptr,
|
|
block_size,
|
|
x.dtype,
|
|
)
|
|
return x_fp8, x_scale, o
|
|
|
|
mod, _, ext_mods = TestModule().export_tvm(
|
|
spec={
|
|
"triton_group_gemm": {
|
|
"x": spec.Tensor(("m", K), dtype),
|
|
"w": spec.Tensor((num_experts, N, K), fp8_dtype),
|
|
"w_scale": spec.Tensor(
|
|
(
|
|
num_experts,
|
|
(N + block_size[0] - 1) // block_size[0],
|
|
(K + block_size[1] - 1) // block_size[1],
|
|
),
|
|
"float32",
|
|
),
|
|
"indptr": spec.Tensor((num_experts + 1,), "int32"),
|
|
},
|
|
},
|
|
allow_extern=True,
|
|
)
|
|
mod = DispatchTritonKernel(target)(mod)
|
|
exec = relax.build(
|
|
mod,
|
|
target=target,
|
|
relax_pipeline=relax.backend.cuda.get_default_pipeline(target),
|
|
)
|
|
vm = relax.VirtualMachine(exec, device)
|
|
|
|
# Randomly sample `top_k` experts for each token with pytorch
|
|
expert_choices = torch.randint(
|
|
0, num_experts, (M * top_k,), device=torch_device, dtype=torch.int32
|
|
)
|
|
|
|
indptr = torch.zeros(num_experts + 1, device=torch_device, dtype=torch.int32)
|
|
for i in range(num_experts):
|
|
indptr[i + 1] = indptr[i] + (expert_choices == i).sum()
|
|
token_ids_list = []
|
|
for i in range(num_experts):
|
|
# Get the indices of the tokens that belong to the i-th expert
|
|
token_ids = torch.where(expert_choices == i)[0]
|
|
token_ids_list.append(token_ids)
|
|
|
|
x_torch = torch.randn(M * top_k, K, dtype=getattr(torch, dtype), device=torch_device)
|
|
w_full_torch = torch.randn(num_experts, N, K, dtype=getattr(torch, dtype), device=torch_device)
|
|
w_torch, w_scale_torch = blockwise_quant_fp8(w_full_torch, block_size, torch_fp8_dtype)
|
|
x_torch, x_fp8_torch, x_scale_torch = rowwise_quant_fp8(x_torch, block_size, torch_fp8_dtype)
|
|
o_torch = blockwise_group_matmul(
|
|
x_fp8_torch,
|
|
x_scale_torch,
|
|
w_torch,
|
|
w_scale_torch,
|
|
indptr,
|
|
x_torch.dtype,
|
|
)
|
|
x_tvm = tvm.runtime.tensor(x_torch.view(torch.float16).cpu().numpy().view(dtype), device=device)
|
|
w_tvm = tvm.runtime.tensor(
|
|
w_torch.view(torch.uint8).cpu().numpy().view(fp8_dtype), device=device
|
|
)
|
|
w_scale_tvm = tvm.runtime.tensor(w_scale_torch.cpu().numpy(), device=device)
|
|
indptr_tvm = tvm.runtime.tensor(indptr.cpu().numpy(), device=device)
|
|
x_fp8_tvm, x_scale_tvm, o_tvm = vm["triton_group_gemm"](
|
|
x_tvm,
|
|
w_tvm,
|
|
w_scale_tvm,
|
|
indptr_tvm,
|
|
)
|
|
x_fp8_tvm = x_fp8_tvm.numpy()
|
|
x_scale_tvm = x_scale_tvm.numpy()
|
|
o_tvm = o_tvm.numpy()
|
|
np.testing.assert_allclose(
|
|
x_fp8_tvm,
|
|
x_fp8_torch.view(torch.uint8).cpu().numpy().view(fp8_dtype),
|
|
atol=1e-1,
|
|
rtol=1e-1,
|
|
)
|
|
np.testing.assert_allclose(x_scale_tvm, x_scale_torch.cpu().numpy(), atol=1e-5, rtol=1e-5)
|
|
atol = 0.5
|
|
rtol = 1e-4
|
|
o_tvm_flat = o_tvm.flatten()
|
|
o_torch_flat = o_torch.view(torch.float16).cpu().numpy().view(dtype).flatten()
|
|
failed_indices = np.where(
|
|
np.abs(o_tvm_flat - o_torch_flat) > (atol + rtol * np.abs(o_torch_flat))
|
|
)[0]
|
|
if len(failed_indices) > 0:
|
|
print(f"failed_indices: {failed_indices}, size: {len(failed_indices)}")
|
|
print(f"o_tvm_flat[failed_indices]: {o_tvm_flat[failed_indices]}")
|
|
print(f"o_torch_flat[failed_indices]: {o_torch_flat[failed_indices]}")
|
|
np.testing.assert_allclose(
|
|
o_tvm,
|
|
o_torch.view(torch.float16).cpu().numpy().view(dtype),
|
|
atol=atol,
|
|
rtol=rtol,
|
|
)
|
|
|
|
|
|
def test_fp8_block_bmm_cutlass(M: int, N: int, K: int, H: int, dtype: str):
|
|
class TestModule(nn.Module):
|
|
def __init__(self):
|
|
pass
|
|
|
|
def cutlass_bmm(self, x: nn.Tensor, w: nn.Tensor, w_scale: nn.Tensor):
|
|
_, n, k = w.shape
|
|
assert w.shape[0] == x.shape[0] == H
|
|
assert n % block_size[0] == 0
|
|
assert k % block_size[1] == 0
|
|
assert n // block_size[0] == w_scale.shape[1]
|
|
assert k // block_size[1] == w_scale.shape[2]
|
|
assert x.shape[2] == k
|
|
o = batch_matmul.quantized_bmm(x, w, w_scale, block_size)
|
|
return o
|
|
|
|
mod, _, ext_mods = TestModule().export_tvm(
|
|
spec={
|
|
"cutlass_bmm": {
|
|
"x": spec.Tensor((H, "m", K), dtype),
|
|
"w": spec.Tensor((H, N, K), fp8_dtype),
|
|
"w_scale": spec.Tensor(
|
|
(
|
|
H,
|
|
(N + block_size[0] - 1) // block_size[0],
|
|
(K + block_size[1] - 1) // block_size[1],
|
|
),
|
|
"float32",
|
|
),
|
|
},
|
|
},
|
|
allow_extern=True,
|
|
)
|
|
device = tvm.cuda()
|
|
target = tvm.target.Target.from_device(device)
|
|
exec = relax.build(
|
|
mod,
|
|
target=target,
|
|
relax_pipeline=relax.backend.cuda.get_default_pipeline(target),
|
|
)
|
|
vm = relax.VirtualMachine(exec, device)
|
|
|
|
x_torch = torch.randn(H, M, K, dtype=getattr(torch, dtype), device=torch_device)
|
|
w_full_torch = torch.randn(H, N, K, dtype=getattr(torch, dtype), device=torch_device)
|
|
w_torch, w_scale_torch = blockwise_quant_fp8(w_full_torch, block_size, torch_fp8_dtype)
|
|
x_torch, x_fp8_torch, x_scale_torch = rowwise_quant_fp8(x_torch, block_size, torch_fp8_dtype)
|
|
o_torch = blockwise_bmm(x_fp8_torch, x_scale_torch, w_torch, w_scale_torch, x_torch.dtype)
|
|
x_tvm = tvm.runtime.tensor(x_torch.view(torch.float16).cpu().numpy().view(dtype), device=device)
|
|
w_tvm = tvm.runtime.tensor(
|
|
w_torch.view(torch.uint8).cpu().numpy().view(fp8_dtype), device=device
|
|
)
|
|
w_scale_tvm = tvm.runtime.tensor(w_scale_torch.cpu().numpy(), device=device)
|
|
o_tvm = vm["cutlass_bmm"](x_tvm, w_tvm, w_scale_tvm)
|
|
o_tvm = o_tvm.numpy()
|
|
atol = 0.5
|
|
rtol = 1e-4
|
|
o_tvm_flat = o_tvm.flatten()
|
|
o_torch_flat = o_torch.view(torch.float16).cpu().numpy().view(dtype).flatten()
|
|
failed_indices = np.where(
|
|
np.abs(o_tvm_flat - o_torch_flat) > (atol + rtol * np.abs(o_torch_flat))
|
|
)[0]
|
|
if len(failed_indices) > 0:
|
|
print(f"failed_indices: {failed_indices}, size: {len(failed_indices)}")
|
|
print(f"o_tvm_flat[failed_indices]: {o_tvm_flat[failed_indices]}")
|
|
print(f"o_torch_flat[failed_indices]: {o_torch_flat[failed_indices]}")
|
|
np.testing.assert_allclose(
|
|
o_tvm,
|
|
o_torch.view(torch.float16).cpu().numpy().view(dtype),
|
|
atol=atol,
|
|
rtol=rtol,
|
|
)
|
|
|
|
|
|
def test_fp8_block_gemv_tir(N: int, K: int, up: bool, dtype: str):
|
|
num_experts = 256
|
|
top_k = 8
|
|
M = 1 if up else top_k
|
|
|
|
device = tvm.cuda()
|
|
target = tvm.target.Target.from_device(device)
|
|
|
|
class TestModule(nn.Module):
|
|
def __init__(self):
|
|
pass
|
|
|
|
def tir_moe_gemv(
|
|
self,
|
|
x: nn.Tensor,
|
|
w: nn.Tensor,
|
|
w_scale: nn.Tensor,
|
|
expert_indices: nn.Tensor,
|
|
):
|
|
e, n, k = w.shape
|
|
assert e == num_experts
|
|
assert (n + block_size[0] - 1) // block_size[0] == w_scale.shape[1]
|
|
assert (k + block_size[1] - 1) // block_size[1] == w_scale.shape[2]
|
|
assert x.shape[1] == k
|
|
o = moe_matmul.dequantize_block_scale_float8_gemv(
|
|
x, w, w_scale, expert_indices, block_size, x.dtype
|
|
)
|
|
return o
|
|
|
|
mod, _, ext_mods = TestModule().export_tvm(
|
|
spec={
|
|
"tir_moe_gemv": {
|
|
"x": spec.Tensor((M, K), dtype),
|
|
"w": spec.Tensor((num_experts, N, K), fp8_dtype),
|
|
"w_scale": spec.Tensor(
|
|
(
|
|
num_experts,
|
|
(N + block_size[0] - 1) // block_size[0],
|
|
(K + block_size[1] - 1) // block_size[1],
|
|
),
|
|
"float32",
|
|
),
|
|
"expert_indices": spec.Tensor((1, top_k), "int32"),
|
|
},
|
|
},
|
|
allow_extern=True,
|
|
)
|
|
with target:
|
|
mod = dl.ApplyDefaultSchedule(
|
|
dl.gpu.Matmul(),
|
|
dl.gpu.GEMV(),
|
|
dl.gpu.Reduction(),
|
|
dl.gpu.GeneralReduction(),
|
|
dl.gpu.Fallback(),
|
|
)(mod)
|
|
exec = relax.build(
|
|
mod,
|
|
target=target,
|
|
relax_pipeline=relax.backend.cuda.get_default_pipeline(target),
|
|
)
|
|
vm = relax.VirtualMachine(exec, device)
|
|
|
|
# Randomly sample `top_k` experts for each token with pytorch
|
|
expert_choices = torch.randint(0, num_experts, (top_k,), device=torch_device, dtype=torch.int32)
|
|
indptr = torch.zeros(num_experts + 1, device=torch_device, dtype=torch.int32)
|
|
for i in range(num_experts):
|
|
indptr[i + 1] = indptr[i] + (expert_choices == i).sum()
|
|
token_ids_list = []
|
|
for i in range(num_experts):
|
|
# Get the indices of the tokens that belong to the i-th expert
|
|
token_ids = torch.where(expert_choices == i)[0]
|
|
token_ids_list.append(token_ids)
|
|
|
|
x_torch = torch.randn(M, K, dtype=getattr(torch, dtype), device=torch_device)
|
|
w_full_torch = torch.randn(num_experts, N, K, dtype=getattr(torch, dtype), device=torch_device)
|
|
w_torch, w_scale_torch = blockwise_quant_fp8(w_full_torch, block_size, torch_fp8_dtype)
|
|
x_input_torch = torch.repeat_interleave(x_torch, top_k, dim=0) if up else x_torch
|
|
o_torch = blockwise_group_matmul_unquantized(
|
|
x_input_torch, w_torch, w_scale_torch, expert_choices
|
|
)
|
|
x_tvm = tvm.runtime.tensor(x_torch.view(torch.float16).cpu().numpy().view(dtype), device=device)
|
|
w_tvm = tvm.runtime.tensor(
|
|
w_torch.view(torch.uint8).cpu().numpy().view(fp8_dtype), device=device
|
|
)
|
|
w_scale_tvm = tvm.runtime.tensor(w_scale_torch.cpu().numpy(), device=device)
|
|
expert_choices = tvm.runtime.tensor(
|
|
expert_choices.reshape(1, top_k).cpu().numpy(), device=device
|
|
)
|
|
o_tvm = vm["tir_moe_gemv"](x_tvm, w_tvm, w_scale_tvm, expert_choices)
|
|
o_tvm = o_tvm.numpy()
|
|
atol = 0.5
|
|
rtol = 1e-4
|
|
o_tvm_flat = o_tvm.flatten()
|
|
o_torch_flat = o_torch.view(torch.float16).cpu().numpy().view(dtype).flatten()
|
|
failed_indices = np.where(
|
|
np.abs(o_tvm_flat - o_torch_flat) > (atol + rtol * np.abs(o_torch_flat))
|
|
)[0]
|
|
if len(failed_indices) > 0:
|
|
print(f"failed_indices: {failed_indices}, size: {len(failed_indices)}")
|
|
print(f"o_tvm_flat[failed_indices]: {o_tvm_flat[failed_indices]}")
|
|
print(f"o_torch_flat[failed_indices]: {o_torch_flat[failed_indices]}")
|
|
np.testing.assert_allclose(
|
|
o_tvm,
|
|
o_torch.view(torch.float16).cpu().numpy().view(dtype),
|
|
atol=atol,
|
|
rtol=rtol,
|
|
)
|
|
|
|
|
|
def blockwise_matmul(
|
|
x_fp8_torch: torch.Tensor,
|
|
x_scale_torch: torch.Tensor,
|
|
w_torch: torch.Tensor,
|
|
w_scale_torch: torch.Tensor,
|
|
dtype,
|
|
):
|
|
o_torch = torch.zeros(
|
|
(x_fp8_torch.shape[0], w_torch.shape[0]), dtype=dtype, device=torch_device
|
|
)
|
|
for j in range(w_scale_torch.shape[0]):
|
|
for k in range(w_scale_torch.shape[1]):
|
|
o_torch[
|
|
:,
|
|
j * block_size[0] : min((j + 1) * block_size[0], w_torch.shape[0]),
|
|
] += (
|
|
torch.matmul(
|
|
x_fp8_torch[
|
|
:,
|
|
k * block_size[1] : min((k + 1) * block_size[1], x_fp8_torch.shape[1]),
|
|
].to(dtype),
|
|
w_torch[
|
|
j * block_size[0] : min((j + 1) * block_size[0], w_torch.shape[0]),
|
|
k * block_size[1] : min((k + 1) * block_size[1], w_torch.shape[1]),
|
|
].T.to(dtype),
|
|
)
|
|
* x_scale_torch[:, k : k + 1]
|
|
* w_scale_torch[j, k]
|
|
)
|
|
return o_torch
|
|
|
|
|
|
def blockwise_group_matmul(
|
|
x_fp8_torch: torch.Tensor,
|
|
x_scale_torch: torch.Tensor,
|
|
w_torch: torch.Tensor,
|
|
w_scale_torch: torch.Tensor,
|
|
indptr: torch.Tensor,
|
|
dtype,
|
|
):
|
|
o_torch = torch.zeros(
|
|
(x_fp8_torch.shape[0], w_torch.shape[1]), dtype=dtype, device=torch_device
|
|
)
|
|
for e in range(w_scale_torch.shape[0]):
|
|
if indptr[e + 1] - indptr[e] == 0:
|
|
continue
|
|
indices = slice(indptr[e], indptr[e + 1])
|
|
for j in range(w_scale_torch.shape[1]):
|
|
for k in range(w_scale_torch.shape[2]):
|
|
o_torch[
|
|
indices,
|
|
j * block_size[0] : min((j + 1) * block_size[0], w_torch.shape[1]),
|
|
] += (
|
|
torch.matmul(
|
|
x_fp8_torch.to(dtype)[
|
|
indices,
|
|
k * block_size[1] : min((k + 1) * block_size[1], x_fp8_torch.shape[1]),
|
|
],
|
|
w_torch[
|
|
e,
|
|
j * block_size[0] : min((j + 1) * block_size[0], w_torch.shape[1]),
|
|
k * block_size[1] : min((k + 1) * block_size[1], w_torch.shape[2]),
|
|
].T.to(dtype),
|
|
)
|
|
* x_scale_torch[indices, k : k + 1]
|
|
* w_scale_torch[e, j, k]
|
|
)
|
|
return o_torch
|
|
|
|
|
|
def blockwise_group_matmul_unquantized(
|
|
x_torch: torch.Tensor,
|
|
w_torch: torch.Tensor,
|
|
w_scale_torch: torch.Tensor,
|
|
expert_choices: torch.Tensor,
|
|
):
|
|
o_torch = torch.zeros(
|
|
(x_torch.shape[0], w_torch.shape[1]), dtype=x_torch.dtype, device=torch_device
|
|
)
|
|
for i, e in enumerate(expert_choices):
|
|
for j in range(w_scale_torch.shape[1]):
|
|
for k in range(w_scale_torch.shape[2]):
|
|
o_torch[
|
|
i,
|
|
j * block_size[0] : min((j + 1) * block_size[0], w_torch.shape[1]),
|
|
] += torch.matmul(
|
|
x_torch[
|
|
i,
|
|
k * block_size[1] : min((k + 1) * block_size[1], x_torch.shape[1]),
|
|
],
|
|
w_torch[
|
|
e,
|
|
j * block_size[0] : min((j + 1) * block_size[0], w_torch.shape[1]),
|
|
k * block_size[1] : min((k + 1) * block_size[1], w_torch.shape[2]),
|
|
].T.to(x_torch.dtype)
|
|
* w_scale_torch[e, j, k].to(x_torch.dtype),
|
|
)
|
|
return o_torch
|
|
|
|
|
|
def blockwise_bmm(
|
|
x_fp8_torch: torch.Tensor,
|
|
x_scale_torch: torch.Tensor,
|
|
w_torch: torch.Tensor,
|
|
w_scale_torch: torch.Tensor,
|
|
dtype,
|
|
):
|
|
o_torch = torch.zeros(
|
|
(x_fp8_torch.shape[0], x_fp8_torch.shape[1], w_torch.shape[1]),
|
|
dtype=dtype,
|
|
device=torch_device,
|
|
)
|
|
for j in range(w_scale_torch.shape[1]):
|
|
for k in range(w_scale_torch.shape[2]):
|
|
o_torch[
|
|
...,
|
|
j * block_size[0] : min((j + 1) * block_size[0], w_torch.shape[1]),
|
|
] += (
|
|
torch.bmm(
|
|
x_fp8_torch[
|
|
...,
|
|
k * block_size[1] : min((k + 1) * block_size[1], x_fp8_torch.shape[2]),
|
|
].to(dtype),
|
|
w_torch[
|
|
...,
|
|
j * block_size[0] : min((j + 1) * block_size[0], w_torch.shape[1]),
|
|
k * block_size[1] : min((k + 1) * block_size[1], w_torch.shape[2]),
|
|
]
|
|
.transpose(1, 2)
|
|
.to(dtype),
|
|
)
|
|
* x_scale_torch[..., k : k + 1]
|
|
* w_scale_torch[..., j : j + 1, k : k + 1]
|
|
)
|
|
return o_torch
|
|
|
|
|
|
def blockwise_quant_fp8(
|
|
w_full_torch: torch.Tensor,
|
|
block_size: Tuple[int, int], # noqa: UP006
|
|
quant_dtype: torch.dtype,
|
|
):
|
|
w_scale_shape = (
|
|
*w_full_torch.shape[:-2],
|
|
(w_full_torch.shape[-2] + block_size[0] - 1) // block_size[0],
|
|
(w_full_torch.shape[-1] + block_size[1] - 1) // block_size[1],
|
|
)
|
|
# For each (block_size[0], block_size[1]) block, compute the max abs value of `w_full_torch`
|
|
w_max_abs_torch = torch.zeros(w_scale_shape, dtype=torch.float32, device=torch_device)
|
|
for i in range(w_scale_shape[-2]):
|
|
for j in range(w_scale_shape[-1]):
|
|
w_max_abs_torch[..., i, j] = torch.max(
|
|
torch.abs(
|
|
w_full_torch[
|
|
...,
|
|
i * block_size[0] : min((i + 1) * block_size[0], w_full_torch.shape[-2]),
|
|
j * block_size[1] : min((j + 1) * block_size[1], w_full_torch.shape[-1]),
|
|
]
|
|
).flatten(-2, -1),
|
|
dim=-1,
|
|
)[0]
|
|
# Scale is the `w_max_abs_torch` divided by the max value of quant_dtype in ml_dtypes
|
|
fp8_max = float(ml_dtypes.finfo(fp8_dtype).max)
|
|
w_scale_torch = w_max_abs_torch / fp8_max
|
|
# `w_torch` is the `w_full_torch` divided by the `w_scale_torch` (with block awareness),
|
|
# clamped to (-fp8_max, fp8_max), and cast to `quant_dtype`
|
|
w_torch = torch.zeros_like(w_full_torch, dtype=quant_dtype, device=torch_device)
|
|
if len(w_scale_shape) == 2:
|
|
for i in range(w_scale_shape[-2]):
|
|
for j in range(w_scale_shape[-1]):
|
|
w_torch[
|
|
i * block_size[0] : min((i + 1) * block_size[0], w_full_torch.shape[-2]),
|
|
j * block_size[1] : min((j + 1) * block_size[1], w_full_torch.shape[-1]),
|
|
] = torch.clamp(
|
|
w_full_torch[
|
|
i * block_size[0] : min((i + 1) * block_size[0], w_full_torch.shape[-2]),
|
|
j * block_size[1] : min((j + 1) * block_size[1], w_full_torch.shape[-1]),
|
|
]
|
|
/ w_scale_torch[..., i, j],
|
|
-fp8_max,
|
|
fp8_max,
|
|
)
|
|
else:
|
|
for e in range(w_scale_shape[0]):
|
|
for i in range(w_scale_shape[-2]):
|
|
for j in range(w_scale_shape[-1]):
|
|
w_torch[
|
|
e,
|
|
i * block_size[0] : min((i + 1) * block_size[0], w_full_torch.shape[-2]),
|
|
j * block_size[1] : min((j + 1) * block_size[1], w_full_torch.shape[-1]),
|
|
] = torch.clamp(
|
|
w_full_torch[
|
|
e,
|
|
i * block_size[0] : min(
|
|
(i + 1) * block_size[0], w_full_torch.shape[-2]
|
|
),
|
|
j * block_size[1] : min(
|
|
(j + 1) * block_size[1], w_full_torch.shape[-1]
|
|
),
|
|
]
|
|
/ w_scale_torch[e, i, j],
|
|
-fp8_max,
|
|
fp8_max,
|
|
)
|
|
|
|
w_scale_torch = (
|
|
torch.rand(w_scale_torch.shape, dtype=torch.float32, device=torch_device) / fp8_max
|
|
)
|
|
return w_torch, w_scale_torch
|
|
|
|
|
|
def rowwise_quant_fp8(
|
|
x_full_torch: torch.Tensor,
|
|
block_size: Tuple[int, int], # noqa: UP006
|
|
quant_dtype: torch.dtype,
|
|
):
|
|
x_scale_shape = (
|
|
*x_full_torch.shape[:-1],
|
|
(x_full_torch.shape[-1] + block_size[1] - 1) // block_size[1],
|
|
)
|
|
# For each (block_size[1]) block, compute the max abs value of `w_full_torch`
|
|
x_max_abs_torch = torch.zeros(x_scale_shape, dtype=torch.float32, device=torch_device)
|
|
for i in range(x_scale_shape[-1]):
|
|
x_max_abs_torch[..., i] = torch.max(
|
|
torch.abs(
|
|
x_full_torch[
|
|
...,
|
|
i * block_size[1] : min((i + 1) * block_size[1], x_full_torch.shape[-1]),
|
|
]
|
|
),
|
|
dim=-1,
|
|
)[0]
|
|
# Scale is the `x_max_abs_torch` divided by the max value of quant_dtype in ml_dtypes
|
|
fp8_max = float(ml_dtypes.finfo(fp8_dtype).max)
|
|
x_scale_torch = x_max_abs_torch / fp8_max
|
|
# `x_torch` is the `x_full_torch` divided by the `x_scale_torch` (with block awareness),
|
|
# clamped to (-fp8_max, fp8_max), and cast to `quant_dtype`
|
|
x_torch = torch.zeros_like(x_full_torch, dtype=quant_dtype, device=torch_device)
|
|
for i in range(x_scale_shape[-1]):
|
|
x_torch[
|
|
...,
|
|
i * block_size[1] : min((i + 1) * block_size[1], x_full_torch.shape[-1]),
|
|
] = torch.clamp(
|
|
x_full_torch[
|
|
...,
|
|
i * block_size[1] : min((i + 1) * block_size[1], x_full_torch.shape[-1]),
|
|
]
|
|
/ x_scale_torch[..., i : i + 1],
|
|
-fp8_max,
|
|
fp8_max,
|
|
)
|
|
|
|
x_scale_torch = (
|
|
torch.rand(x_scale_torch.shape, dtype=torch.float32, device=torch_device) / fp8_max
|
|
)
|
|
for i in range(x_scale_shape[-1]):
|
|
x_full_torch[
|
|
...,
|
|
i * block_size[1] : min((i + 1) * block_size[1], x_full_torch.shape[-1]),
|
|
] = (
|
|
x_torch[
|
|
...,
|
|
i * block_size[1] : min((i + 1) * block_size[1], x_full_torch.shape[-1]),
|
|
].to(x_scale_torch.dtype)
|
|
* x_scale_torch[..., i : i + 1]
|
|
)
|
|
return x_full_torch, x_torch, x_scale_torch
|
|
|
|
|
|
@pytest.mark.skip(reason="Test requiring SM90a")
|
|
def test_cutlass_gemm():
|
|
# Cutlass GEMM
|
|
for M, (N, K), dtype in product(
|
|
[4, 128, 256, 1024, 2112],
|
|
[
|
|
(4608, 896),
|
|
(896, 2304),
|
|
(3072, 896),
|
|
(512, 896),
|
|
(3072, 512),
|
|
(4096, 512),
|
|
(896, 2048),
|
|
(129280, 896),
|
|
],
|
|
["bfloat16"],
|
|
):
|
|
print(f"Cutlass, M: {M}, N: {N}, K: {K}, dtype: {dtype}")
|
|
test_fp8_block_matmul_cutlass(M, N, K, dtype)
|
|
|
|
|
|
@pytest.mark.skip(reason="Test requiring SM90a")
|
|
def test_triton_gemm():
|
|
# Triton GEMM
|
|
for M, (N, K), dtype in product(
|
|
[1, 128, 256, 1024, 2111],
|
|
[
|
|
(4608, 896),
|
|
(896, 576),
|
|
(896, 2304),
|
|
],
|
|
["bfloat16"],
|
|
):
|
|
print(f"Triton, M: {M}, N: {N}, K: {K}, dtype: {dtype}")
|
|
test_fp8_block_matmul_triton(M, N, K, dtype)
|
|
|
|
|
|
@pytest.mark.skip(reason="Test requiring SM90a")
|
|
def test_cutlass_group_gemm():
|
|
# Cutlass group GEMM
|
|
for M, (N, K), dtype in product(
|
|
[1, 128, 256, 1024, 2111],
|
|
[
|
|
(512, 896),
|
|
(896, 256),
|
|
],
|
|
["bfloat16"],
|
|
):
|
|
print(f"Cutlass group gemm, M: {M}, N: {N}, K: {K}, dtype: {dtype}")
|
|
test_fp8_block_group_matmul_cutlass(M, N, K, dtype)
|
|
|
|
|
|
@pytest.mark.skip(reason="Test requiring SM90a")
|
|
def test_triton_group_gemm():
|
|
# Triton group GEMM
|
|
for M, (N, K), dtype in product(
|
|
[1, 128, 256, 1024, 2111],
|
|
[
|
|
(512, 896),
|
|
(896, 256),
|
|
],
|
|
["bfloat16"],
|
|
):
|
|
print(f"Triton group gemm, M: {M}, N: {N}, K: {K}, dtype: {dtype}")
|
|
test_fp8_block_group_matmul_triton(M, N, K, dtype)
|
|
|
|
|
|
@pytest.mark.skip(reason="Test requiring SM90a")
|
|
def test_cutlass_bmm():
|
|
# Cutlass BMM
|
|
for M, H, (N, K), dtype in product(
|
|
[4, 128, 256, 1024, 2112],
|
|
[16, 64, 128],
|
|
[
|
|
(512, 128),
|
|
(128, 512),
|
|
],
|
|
["bfloat16"],
|
|
):
|
|
print(f"Cutlass BMM, M: {M}, N: {N}, K: {K}, H: {H}, dtype: {dtype}")
|
|
test_fp8_block_bmm_cutlass(M, N, K, H, dtype)
|
|
|
|
|
|
@pytest.mark.skip(reason="Test requiring SM90a")
|
|
def test_tir_moe_gemv():
|
|
# TIR MoE GEMV
|
|
for (N, K), up, dtype in product(
|
|
[(512, 896), (896, 256)],
|
|
[True, False],
|
|
["bfloat16"],
|
|
):
|
|
print(f"TIR MoE GEMV, N: {N}, K: {K}, up: {up}, dtype: {dtype}")
|
|
test_fp8_block_gemv_tir(N, K, up, dtype)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
test_cutlass_gemm()
|
|
test_triton_gemm()
|
|
test_cutlass_group_gemm()
|
|
test_triton_group_gemm()
|
|
test_cutlass_bmm()
|
|
test_tir_moe_gemv()
|