|
| 1 | +import dataclasses |
| 2 | +import random |
| 3 | + |
| 4 | +import pytest |
| 5 | +import torch |
| 6 | + |
| 7 | +import flag_gems |
| 8 | +from flag_gems.utils.device_info import get_device_capability |
| 9 | + |
| 10 | +from . import base |
| 11 | + |
| 12 | + |
| 13 | +def is_support_fp8e4nv(): |
| 14 | + major, minor = get_device_capability() |
| 15 | + return major * 10 + minor >= 89 |
| 16 | + |
| 17 | + |
| 18 | +VLLM_REF_AVAILABLE = hasattr( |
| 19 | + torch.ops._C, "fused_deepseek_v4_qnorm_rope_kv_rope_quant_insert" |
| 20 | +) |
| 21 | +HEAD_DIM = 512 |
| 22 | +ROPE_DIM = 64 |
| 23 | +HEAD_BYTES = 584 |
| 24 | + |
| 25 | + |
| 26 | +@dataclasses.dataclass |
| 27 | +class TestParam: |
| 28 | + # Instruct pytest to ignore this class |
| 29 | + __test__ = False |
| 30 | + |
| 31 | + num_tokens: int |
| 32 | + num_heads: int |
| 33 | + num_tokens_insert: int |
| 34 | + block_size: int |
| 35 | + max_pos: int |
| 36 | + eps: float |
| 37 | + dtype: torch.dtype = torch.bfloat16 |
| 38 | + device: torch.device = flag_gems.device |
| 39 | + |
| 40 | + |
| 41 | +_random_counter = 0 |
| 42 | + |
| 43 | + |
| 44 | +class FusedDeepseekV4QnormRopeKVRopeQuantInsertBenchmark(base.Benchmark): |
| 45 | + def __init__(self): |
| 46 | + super().__init__( |
| 47 | + "fused_deepseek_v4_qnorm_rope_kv_rope_quant_insert", |
| 48 | + torch.ops._C.fused_deepseek_v4_qnorm_rope_kv_rope_quant_insert, |
| 49 | + [torch.bfloat16], |
| 50 | + ) |
| 51 | + self.set_gems(flag_gems.fused_deepseek_v4_qnorm_rope_kv_rope_quant_insert) |
| 52 | + |
| 53 | + def set_shapes(self, shape_file_path=None): |
| 54 | + self.shapes = [] |
| 55 | + |
| 56 | + def get_input_iter(self, dtype): |
| 57 | + _ = dtype |
| 58 | + for ( |
| 59 | + param |
| 60 | + ) in ( |
| 61 | + FusedDeepseekV4QnormRopeKVRopeQuantInsertBenchmark.get_performance_test_params() |
| 62 | + ): |
| 63 | + yield from FusedDeepseekV4QnormRopeKVRopeQuantInsertBenchmark.make_input( |
| 64 | + param |
| 65 | + ) |
| 66 | + |
| 67 | + @staticmethod |
| 68 | + def get_performance_test_params(): |
| 69 | + cases = [ |
| 70 | + TestParam( |
| 71 | + num_tokens, |
| 72 | + num_heads, |
| 73 | + num_tokens_insert=num_tokens, |
| 74 | + block_size=64, |
| 75 | + max_pos=4096, |
| 76 | + eps=1e-6, |
| 77 | + ) |
| 78 | + for num_tokens in [ |
| 79 | + 1, |
| 80 | + 4, |
| 81 | + 17, |
| 82 | + 64, |
| 83 | + 1024, |
| 84 | + 2048, |
| 85 | + 8192, |
| 86 | + 32768, |
| 87 | + 65536, |
| 88 | + 98304, |
| 89 | + 131072, |
| 90 | + ] |
| 91 | + for num_heads in [64, 128] |
| 92 | + ] |
| 93 | + return cases |
| 94 | + |
| 95 | + @staticmethod |
| 96 | + def init_seed(seed): |
| 97 | + random.seed(seed) |
| 98 | + torch.manual_seed(seed) |
| 99 | + |
| 100 | + @staticmethod |
| 101 | + def make_cos_sin_cache(max_pos: int, rope_dim: int, dtype, device): |
| 102 | + if max_pos <= 8192: |
| 103 | + base = 10000.0 |
| 104 | + elif max_pos <= 32768: |
| 105 | + base = 20000.0 |
| 106 | + elif max_pos <= 65536: |
| 107 | + base = 40000.0 |
| 108 | + elif max_pos <= 98304: |
| 109 | + base = 60000.0 |
| 110 | + else: |
| 111 | + base = 100000.0 |
| 112 | + |
| 113 | + inv_freq = 1.0 / ( |
| 114 | + base |
| 115 | + ** ( |
| 116 | + torch.arange(0, rope_dim, 2, dtype=torch.float32, device=device) |
| 117 | + / rope_dim |
| 118 | + ) |
| 119 | + ) |
| 120 | + t = torch.arange(max_pos, dtype=torch.float32, device=device) |
| 121 | + freqs = torch.einsum("i,j -> ij", t, inv_freq) # [max_pos, rope_dim/2] |
| 122 | + cache = torch.cat((freqs.cos(), freqs.sin()), dim=-1) # [max_pos, rope_dim] |
| 123 | + return cache.to(dtype) |
| 124 | + |
| 125 | + @staticmethod |
| 126 | + def make_input(param: TestParam): |
| 127 | + num_tokens = param.num_tokens |
| 128 | + num_heads = param.num_heads |
| 129 | + num_tokens_insert = param.num_tokens_insert |
| 130 | + block_size = param.block_size |
| 131 | + max_pos = max(param.max_pos, num_tokens) |
| 132 | + eps = param.eps |
| 133 | + dtype = param.dtype |
| 134 | + device = param.device |
| 135 | + |
| 136 | + global _random_counter |
| 137 | + FusedDeepseekV4QnormRopeKVRopeQuantInsertBenchmark.init_seed(_random_counter) |
| 138 | + _random_counter = _random_counter + 1 |
| 139 | + |
| 140 | + q = torch.randn(num_tokens, num_heads, HEAD_DIM, dtype=dtype, device=device) |
| 141 | + kv = torch.randn(num_tokens, HEAD_DIM, dtype=dtype, device=device) |
| 142 | + positions = torch.arange(num_tokens, dtype=torch.int64, device=device) |
| 143 | + cos_sin_cache = ( |
| 144 | + FusedDeepseekV4QnormRopeKVRopeQuantInsertBenchmark.make_cos_sin_cache( |
| 145 | + max_pos, ROPE_DIM, torch.float32, device |
| 146 | + ) |
| 147 | + ) |
| 148 | + |
| 149 | + num_blocks = (num_tokens + block_size - 1) // block_size + 1 |
| 150 | + slot_mapping = torch.arange(num_tokens_insert, dtype=torch.int64, device=device) |
| 151 | + k_cache = torch.zeros( |
| 152 | + num_blocks, block_size * HEAD_BYTES, dtype=torch.uint8, device=device |
| 153 | + ) |
| 154 | + yield (q, kv, k_cache, slot_mapping, positions, cos_sin_cache, eps, block_size) |
| 155 | + |
| 156 | + |
| 157 | +@pytest.mark.fused_deepseek_v4_qnorm_rope_kv_rope_quant_insert |
| 158 | +@pytest.mark.skipif( |
| 159 | + not VLLM_REF_AVAILABLE, reason="The referenced vLLM implementation is not installed" |
| 160 | +) |
| 161 | +@pytest.mark.skipif( |
| 162 | + not is_support_fp8e4nv(), reason="Do not support fp8e4nv when capability < 89" |
| 163 | +) |
| 164 | +def test_fused_deepseek_v4_qnorm_rope_kv_rope_quant_insert(): |
| 165 | + bench = FusedDeepseekV4QnormRopeKVRopeQuantInsertBenchmark() |
| 166 | + bench.run() |
0 commit comments