"""Tests for the MOE layers. Run `pytest tests/kernels/test_moe.py`. """ from typing import List import pytest import torch from moe._ops import ops from moe.fused_moe import fused_moe, fused_topk, moe_align_block_size from moe.fused_marlin_moe import fused_marlin_moe from moe.scalar_type import scalar_types from moe.utils.marlin_utils_test import marlin_quantize from .utils import compute_max_diff, opcheck def stack_and_dev(tensors: List[torch.Tensor]): dev = tensors[0].device return torch.stack(tensors, dim=0).to(dev) NUM_EXPERTS = [8, 64] TOP_KS = [2, 6] @pytest.mark.parametrize("m", [1, 33, 64, 222]) @pytest.mark.parametrize("n", [128, 2048]) @pytest.mark.parametrize("k", [128, 1024]) @pytest.mark.parametrize("e", NUM_EXPERTS) @pytest.mark.parametrize("topk", TOP_KS) @pytest.mark.parametrize("group_size", [-1, 32, 128]) @pytest.mark.parametrize("act_order", [True, False]) @pytest.mark.parametrize("num_bits", [4, 8]) @pytest.mark.parametrize("is_k_full", [True, False]) # @pytest.mark.skipif(current_platform.is_rocm(), reason="Skip for rocm") def test_fused_marlin_moe( m: int, n: int, k: int, e: int, topk: int, group_size: int, act_order: bool, num_bits: int, is_k_full: bool, ): torch.manual_seed(7) # Filter act_order if act_order: if group_size == -1: return if group_size in (k, n): return else: if not is_k_full: return quant_type = scalar_types.uint4b8 if num_bits == 4 else scalar_types.uint8b128 dtype = torch.float16 a = torch.randn((m, k), device="cuda", dtype=dtype) / 10 w1 = torch.randn((e, 2 * n, k), device="cuda", dtype=dtype) / 10 w2 = torch.randn((e, k, n), device="cuda", dtype=dtype) / 10 w_ref1_l = [] qweight1_l = [] scales1_l = [] g_idx1_l = [] sort_indices1_l = [] for i in range(w1.shape[0]): test_perm = torch.randperm(k) w_ref1, qweight1, scales1, g_idx1, sort_indices1, _ = marlin_quantize( w1[i].transpose(1, 0), quant_type, group_size, act_order, test_perm ) w_ref1_l.append(w_ref1) qweight1_l.append(qweight1) scales1_l.append(scales1) g_idx1_l.append(g_idx1) sort_indices1_l.append(sort_indices1) w_ref1 = stack_and_dev(w_ref1_l) qweight1 = stack_and_dev(qweight1_l).contiguous() scales1 = stack_and_dev(scales1_l) g_idx1 = stack_and_dev(g_idx1_l) sort_indices1 = stack_and_dev(sort_indices1_l) w_ref2_l = [] qweight2_l = [] scales2_l = [] g_idx2_l = [] sort_indices2_l = [] for i in range(w2.shape[0]): test_perm = torch.randperm(n) w_ref2, qweight2, scales2, g_idx2, sort_indices2, _ = marlin_quantize( w2[i].transpose(1, 0), quant_type, group_size, act_order, test_perm ) w_ref2_l.append(w_ref2) qweight2_l.append(qweight2) scales2_l.append(scales2) g_idx2_l.append(g_idx2) sort_indices2_l.append(sort_indices2) w_ref2 = stack_and_dev(w_ref2_l) qweight2 = stack_and_dev(qweight2_l).contiguous() scales2 = stack_and_dev(scales2_l) g_idx2 = stack_and_dev(g_idx2_l) sort_indices2 = stack_and_dev(sort_indices2_l) score = torch.randn((m, e), device="cuda", dtype=dtype) topk_weights, topk_ids = fused_topk(a, score, topk, False) triton_output = fused_moe( a, w_ref1.transpose(1, 2).contiguous(), w_ref2.transpose(1, 2).contiguous(), score, topk, renormalize=False, ) marlin_output = fused_marlin_moe( a, qweight1, qweight2, scales1, scales2, score, topk_weights, topk_ids, g_idx1=g_idx1, g_idx2=g_idx2, sort_indices1=sort_indices1, sort_indices2=sort_indices2, num_bits=num_bits, is_k_full=is_k_full, ) assert compute_max_diff(marlin_output, triton_output) < 4e-2 token_expert_indicies = torch.empty(m, topk, dtype=torch.int32, device=a.device) opcheck( ops.topk_softmax, ( topk_weights, topk_ids, token_expert_indicies, score.float(), ), ) block_size_m = 4 sorted_token_ids, _, _ = moe_align_block_size(topk_ids, block_size_m, e) max_workspace_size = ((m + 255) // 256) * (max(2 * n, k) // 64) * 16 workspace = torch.zeros( max_workspace_size, dtype=torch.int, device="cuda", requires_grad=False ) zp = torch.empty((0, 0), dtype=dtype, device="cuda", requires_grad=False) opcheck( ops.marlin_gemm_moe, ( a, qweight1, sorted_token_ids, topk_weights, topk_ids, scales1, zp, g_idx1, sort_indices1, workspace, quant_type.id, m, 2 * n, k, True, e, topk, block_size_m, True, False, ), ) def test_moe_align_block_size_opcheck(): num_experts = 4 block_size = 4 topk_ids = torch.randint(0, num_experts, (3, 4), dtype=torch.int32, device="cuda") max_num_tokens_padded = topk_ids.numel() + num_experts * (block_size - 1) sorted_ids = torch.empty( (max_num_tokens_padded,), dtype=torch.int32, device=topk_ids.device ) sorted_ids.fill_(topk_ids.numel()) max_num_m_blocks = max_num_tokens_padded // block_size expert_ids = torch.empty( (max_num_m_blocks,), dtype=torch.int32, device=topk_ids.device ) num_tokens_post_pad = torch.empty((1), dtype=torch.int32, device=topk_ids.device) opcheck( ops.moe_align_block_size, ( topk_ids, num_experts, block_size, sorted_ids, expert_ids, num_tokens_post_pad, ), )