| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | |
| 4 | from types import SimpleNamespace |
| 5 | |
| 6 | import torch |
| 7 | |
| 8 | from vllm.model_executor.layers.fused_moe.oracle.nvfp4 import NvFp4MoeBackend |
| 9 | from vllm.model_executor.layers.quantization.utils import flashinfer_fp4_moe |
| 10 | from vllm.model_executor.layers.quantization.utils.flashinfer_fp4_moe import ( |
| 11 | prepare_nvfp4_moe_layer_for_fi_or_cutlass, |
| 12 | ) |
| 13 | from vllm.model_executor.layers.quantization.utils.flashinfer_utils import ( |
| 14 | align_fp4_moe_hidden_dim_for_fi, |
| 15 | align_fp4_moe_weights_for_fi, |
| 16 | ) |
| 17 | |
| 18 | |
| 19 | def test_shared_nvfp4_input_scales_have_writable_storage(monkeypatch): |
| 20 | monkeypatch.setattr(flashinfer_fp4_moe, "swizzle_blockscale", lambda x: x) |
| 21 | |
| 22 | num_experts = 3 |
| 23 | layer = SimpleNamespace( |
| 24 | activation=SimpleNamespace(is_gated=False), |
| 25 | moe_config=SimpleNamespace( |
| 26 | moe_parallel_config=SimpleNamespace(enable_eplb=False) |
| 27 | ), |
| 28 | ) |
| 29 | w13 = torch.zeros((num_experts, 2, 1), dtype=torch.uint8) |
| 30 | w2 = torch.zeros((num_experts, 2, 1), dtype=torch.uint8) |
| 31 | w13_scale = torch.zeros((num_experts, 2, 1), dtype=torch.float8_e4m3fn) |
| 32 | w2_scale = torch.zeros((num_experts, 2, 1), dtype=torch.float8_e4m3fn) |
| 33 | weight_scale = torch.ones(num_experts) |
| 34 | |
| 35 | outputs = prepare_nvfp4_moe_layer_for_fi_or_cutlass( |
| 36 | backend=NvFp4MoeBackend.FLASHINFER_CUTLASS, |
| 37 | layer=layer, |
| 38 | w13=w13, |
| 39 | w13_scale=w13_scale, |
| 40 | w13_scale_2=weight_scale, |
| 41 | a13_scale=torch.tensor([1.0, 2.0, 3.0]), |
| 42 | w2=w2, |
| 43 | w2_scale=w2_scale, |
| 44 | w2_scale_2=weight_scale, |
| 45 | a2_scale=torch.tensor([4.0, 5.0, 6.0]), |
| 46 | is_act_and_mul=False, |
| 47 | ) |
| 48 | a13_scale, a2_scale = outputs[3], outputs[7] |
| 49 | |
| 50 | torch.testing.assert_close(a13_scale, torch.full((num_experts,), 3.0)) |
| 51 | torch.testing.assert_close(a2_scale, torch.full((num_experts,), 6.0)) |
| 52 | distinct_values = torch.arange(num_experts, dtype=torch.float32) |
| 53 | a13_scale.copy_(distinct_values) |
| 54 | a2_scale.copy_(distinct_values) |
| 55 | torch.testing.assert_close(a13_scale, distinct_values) |
| 56 | torch.testing.assert_close(a2_scale, distinct_values) |
| 57 | |
| 58 | |
| 59 | def test_align_trtllm_fp4_moe_hidden_dim_noop(): |
| 60 | w13 = torch.arange(2 * 8 * 256, dtype=torch.uint8).reshape(2, 8, 256) |
| 61 | w13_scale = torch.arange(2 * 8 * 32, dtype=torch.uint8).reshape(2, 8, 32) |
| 62 | w2 = torch.arange(2 * 512 * 4, dtype=torch.uint8).reshape(2, 512, 4) |
| 63 | w2_scale = torch.arange(2 * 512 * 1, dtype=torch.uint8).reshape(2, 512, 1) |
| 64 | |
| 65 | out_w13, out_w13_scale, out_w2, out_w2_scale, padded_hidden = ( |
| 66 | align_fp4_moe_hidden_dim_for_fi(w13, w13_scale, w2, w2_scale) |
| 67 | ) |
| 68 | |
| 69 | assert padded_hidden == 512 |
| 70 | assert out_w13 is w13 |
| 71 | assert out_w13_scale is w13_scale |
| 72 | assert out_w2 is w2 |
| 73 | assert out_w2_scale is w2_scale |
| 74 | |
| 75 | |
| 76 | def test_align_trtllm_fp4_moe_hidden_dim_pads_to_256_multiple(): |
| 77 | hidden_dim = 2688 |
| 78 | padded_hidden_dim = 2816 |
| 79 | |
| 80 | w13 = torch.arange(2 * 12 * (hidden_dim // 2), dtype=torch.uint8).reshape( |
| 81 | 2, 12, hidden_dim // 2 |
| 82 | ) |
| 83 | w13_scale = torch.arange(2 * 12 * (hidden_dim // 16), dtype=torch.uint8).reshape( |
| 84 | 2, 12, hidden_dim // 16 |
| 85 | ) |
| 86 | |
| 87 | w2 = torch.arange(2 * hidden_dim * 6, dtype=torch.uint8).reshape(2, hidden_dim, 6) |
| 88 | w2_scale = torch.arange(2 * hidden_dim * 2, dtype=torch.uint8).reshape( |
| 89 | 2, hidden_dim, 2 |
| 90 | ) |
| 91 | |
| 92 | out_w13, out_w13_scale, out_w2, out_w2_scale, out_hidden_dim = ( |
| 93 | align_fp4_moe_hidden_dim_for_fi(w13, w13_scale, w2, w2_scale) |
| 94 | ) |
| 95 | |
| 96 | assert out_hidden_dim == padded_hidden_dim |
| 97 | assert out_w13.shape == (2, 12, padded_hidden_dim // 2) |
| 98 | assert out_w13_scale.shape == (2, 12, padded_hidden_dim // 16) |
| 99 | assert out_w2.shape == (2, padded_hidden_dim, 6) |
| 100 | assert out_w2_scale.shape == (2, padded_hidden_dim, 2) |
| 101 | |
| 102 | torch.testing.assert_close(out_w13[:, :, : hidden_dim // 2], w13) |
| 103 | torch.testing.assert_close(out_w13_scale[:, :, : hidden_dim // 16], w13_scale) |
| 104 | torch.testing.assert_close(out_w2[:, :hidden_dim, :], w2) |
| 105 | torch.testing.assert_close(out_w2_scale[:, :hidden_dim, :], w2_scale) |
| 106 | |
| 107 | assert torch.count_nonzero(out_w13[:, :, hidden_dim // 2 :]) == 0 |
| 108 | assert torch.count_nonzero(out_w13_scale[:, :, hidden_dim // 16 :]) == 0 |
| 109 | assert torch.count_nonzero(out_w2[:, hidden_dim:, :]) == 0 |
| 110 | assert torch.count_nonzero(out_w2_scale[:, hidden_dim:, :]) == 0 |
| 111 | |
| 112 | |
| 113 | def test_align_trtllm_fp4_moe_intermediate_pads_gate_and_up_separately(): |
| 114 | # Qwen3.8-Flash-Next has intermediate_size=640. At TP4 each rank owns |
| 115 | # 160 values, which TRTLLM pads to the next 64-value boundary (192). |
| 116 | intermediate = 160 |
| 117 | padded_intermediate = 192 |
| 118 | hidden_dim = 32 |
| 119 | |
| 120 | gate = torch.ones((1, intermediate, hidden_dim // 2), dtype=torch.uint8) |
| 121 | up = torch.full_like(gate, 2) |
| 122 | w13 = torch.cat((gate, up), dim=1) |
| 123 | gate_scale = torch.full((1, intermediate, hidden_dim // 16), 3, dtype=torch.uint8) |
| 124 | up_scale = torch.full_like(gate_scale, 4) |
| 125 | w13_scale = torch.cat((gate_scale, up_scale), dim=1) |
| 126 | w2 = torch.full((1, hidden_dim, intermediate // 2), 5, dtype=torch.uint8) |
| 127 | w2_scale = torch.full((1, hidden_dim, intermediate // 16), 6, dtype=torch.uint8) |
| 128 | |
| 129 | out_w13, out_w13_scale, out_w2, out_w2_scale, out_intermediate = ( |
| 130 | align_fp4_moe_weights_for_fi( |
| 131 | w13, |
| 132 | w13_scale, |
| 133 | w2, |
| 134 | w2_scale, |
| 135 | is_act_and_mul=True, |
| 136 | min_alignment=64, |
| 137 | ) |
| 138 | ) |
| 139 | |
| 140 | assert out_intermediate == padded_intermediate |
| 141 | torch.testing.assert_close(out_w13[:, :intermediate], gate) |
| 142 | torch.testing.assert_close( |
| 143 | out_w13[:, padded_intermediate : padded_intermediate + intermediate], up |
| 144 | ) |
| 145 | torch.testing.assert_close(out_w13_scale[:, :intermediate], gate_scale) |
| 146 | torch.testing.assert_close( |
| 147 | out_w13_scale[:, padded_intermediate : padded_intermediate + intermediate], |
| 148 | up_scale, |
| 149 | ) |
| 150 | |
| 151 | assert torch.count_nonzero(out_w13[:, intermediate:padded_intermediate]) == 0 |
| 152 | assert torch.count_nonzero(out_w13[:, padded_intermediate + intermediate :]) == 0 |
| 153 | assert torch.count_nonzero(out_w13_scale[:, intermediate:padded_intermediate]) == 0 |
| 154 | assert ( |
| 155 | torch.count_nonzero(out_w13_scale[:, padded_intermediate + intermediate :]) == 0 |
| 156 | ) |
| 157 | torch.testing.assert_close(out_w2[:, :, : intermediate // 2], w2) |
| 158 | torch.testing.assert_close(out_w2_scale[:, :, : intermediate // 16], w2_scale) |
| 159 | assert torch.count_nonzero(out_w2[:, :, intermediate // 2 :]) == 0 |
| 160 | assert torch.count_nonzero(out_w2_scale[:, :, intermediate // 16 :]) == 0 |