| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | import torch |
| 4 | from torch._higher_order_ops.auto_functionalize import auto_functionalized |
| 5 | |
| 6 | import vllm._custom_ops as ops |
| 7 | from vllm.config import VllmConfig, get_layers_from_vllm_config |
| 8 | from vllm.logger import init_logger |
| 9 | from vllm.model_executor.layers.attention import MLAAttention |
| 10 | from vllm.model_executor.layers.attention.attention import get_attention_context |
| 11 | from vllm.model_executor.layers.rotary_embedding import RotaryEmbedding |
| 12 | from vllm.utils.torch_utils import ( |
| 13 | _USE_LAYERNAME, |
| 14 | LayerNameType, |
| 15 | _encode_layer_name, |
| 16 | _resolve_layer_name, |
| 17 | direct_register_custom_op, |
| 18 | ) |
| 19 | |
| 20 | from ..vllm_inductor_pass import VllmFusionPatternMatcherPass, VllmPatternReplacement |
| 21 | from .matcher_utils import MatcherDeepseekScalingRotaryEmbedding, MatcherRotaryEmbedding |
| 22 | |
| 23 | logger = init_logger(__name__) |
| 24 | |
| 25 | |
| 26 | def fused_rope_unified_mla_kv_cache_update_impl( |
| 27 | positions: torch.Tensor, |
| 28 | q_pe: torch.Tensor, |
| 29 | k_pe: torch.Tensor, |
| 30 | kv_c: torch.Tensor, |
| 31 | cos_sin_cache: torch.Tensor, |
| 32 | is_neox: bool, |
| 33 | kv_cache_dtype: str, |
| 34 | kv_cache_scale: torch.Tensor, |
| 35 | layer_name: LayerNameType, |
| 36 | ) -> torch.Tensor: |
| 37 | layer_name = _resolve_layer_name(layer_name) |
| 38 | attn_metadata, attn_layer, kv_cache, layer_slot_mapping = get_attention_context( |
| 39 | layer_name |
| 40 | ) |
| 41 | attn_layer.prepare_kv_cache_update(attn_metadata) |
| 42 | if layer_slot_mapping is not None: |
| 43 | ops.concat_and_cache_mla_rope_fused( |
| 44 | positions, |
| 45 | q_pe, |
| 46 | k_pe, |
| 47 | kv_c, |
| 48 | cos_sin_cache, |
| 49 | is_neox, |
| 50 | layer_slot_mapping, |
| 51 | kv_cache.squeeze(1), |
| 52 | kv_cache_dtype, |
| 53 | kv_cache_scale, |
| 54 | ) |
| 55 | return torch.empty(0, device=kv_c.device, dtype=kv_c.dtype) |
| 56 | |
| 57 | |
| 58 | def fused_rope_unified_mla_kv_cache_update_fake( |
| 59 | positions: torch.Tensor, |
| 60 | q_pe: torch.Tensor, |
| 61 | k_pe: torch.Tensor, |
| 62 | kv_c: torch.Tensor, |
| 63 | cos_sin_cache: torch.Tensor, |
| 64 | is_neox: bool, |
| 65 | kv_cache_dtype: str, |
| 66 | kv_cache_scale: torch.Tensor, |
| 67 | layer_name: LayerNameType, |
| 68 | ) -> torch.Tensor: |
| 69 | return torch.empty(0, dtype=kv_c.dtype, device=kv_c.device) |
| 70 | |
| 71 | |
| 72 | direct_register_custom_op( |
| 73 | op_name="fused_rope_unified_mla_kv_cache_update", |
| 74 | op_func=fused_rope_unified_mla_kv_cache_update_impl, |
| 75 | fake_impl=fused_rope_unified_mla_kv_cache_update_fake, |
| 76 | mutates_args=["q_pe", "k_pe"], |
| 77 | ) |
| 78 | |
| 79 | |
| 80 | class MLARoPEKVCacheCatPattern(VllmPatternReplacement): |
| 81 | FUSED_OP = torch.ops.vllm.fused_rope_unified_mla_kv_cache_update.default |
| 82 | |
| 83 | def __init__( |
| 84 | self, |
| 85 | layer: MLAAttention, |
| 86 | is_neox: bool, |
| 87 | use_flashinfer: bool = False, |
| 88 | use_deepseek_scaling: bool = False, |
| 89 | ) -> None: |
| 90 | self.layer_name = layer.layer_name |
| 91 | self.kv_cache_dtype = layer.kv_cache_dtype |
| 92 | self.num_heads = layer.num_heads |
| 93 | self.num_kv_heads = layer.num_kv_heads |
| 94 | self.kv_lora_rank = layer.kv_lora_rank |
| 95 | self.qk_rope_head_dim = layer.qk_rope_head_dim |
| 96 | self.is_neox = is_neox |
| 97 | self.use_flashinfer = use_flashinfer |
| 98 | self._ln = _encode_layer_name(self.layer_name) |
| 99 | |
| 100 | if use_deepseek_scaling: |
| 101 | self.rope_matcher = MatcherDeepseekScalingRotaryEmbedding( |
| 102 | is_neox=self.is_neox, |
| 103 | head_size=self.qk_rope_head_dim, |
| 104 | num_heads=self.num_heads, |
| 105 | num_kv_heads=self.num_kv_heads, |
| 106 | use_flashinfer=self.use_flashinfer, |
| 107 | ) |
| 108 | else: |
| 109 | self.rope_matcher = MatcherRotaryEmbedding( # type: ignore |
| 110 | is_neox=self.is_neox, |
| 111 | head_size=self.qk_rope_head_dim, |
| 112 | num_heads=self.num_heads, |
| 113 | num_kv_heads=self.num_kv_heads, |
| 114 | use_flashinfer=self.use_flashinfer, |
| 115 | ) |
| 116 | |
| 117 | def get_inputs(self) -> list[torch.Tensor]: |
| 118 | T = 5 |
| 119 | L = 4096 |
| 120 | q_pe = self.empty_bf16(T, self.num_heads, self.qk_rope_head_dim) |
| 121 | k_pe = self.empty_bf16(T, self.qk_rope_head_dim) |
| 122 | kv_c_normed = self.empty_bf16(T, self.kv_lora_rank) |
| 123 | cos_sin_cache = self.empty_bf16(L, self.qk_rope_head_dim) |
| 124 | positions = self.empty(T, dtype=torch.int64) |
| 125 | k_scale = self.empty(0, dtype=torch.float32) |
| 126 | inputs = [ |
| 127 | q_pe, |
| 128 | k_pe, |
| 129 | kv_c_normed, |
| 130 | positions, |
| 131 | cos_sin_cache, |
| 132 | k_scale, |
| 133 | ] |
| 134 | if _USE_LAYERNAME: |
| 135 | inputs.append(self._ln) |
| 136 | return inputs |
| 137 | |
| 138 | @property |
| 139 | def pattern(self): |
| 140 | _ln = self._ln |
| 141 | |
| 142 | if _USE_LAYERNAME: |
| 143 | |
| 144 | def _pattern_with_ln( |
| 145 | q_pe: torch.Tensor, |
| 146 | k_pe: torch.Tensor, |
| 147 | kv_c_normed: torch.Tensor, |
| 148 | positions: torch.Tensor, |
| 149 | cos_sin_cache: torch.Tensor, |
| 150 | k_scale: torch.Tensor, |
| 151 | layer_name: LayerNameType, |
| 152 | ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: |
| 153 | k_pe_unsqueezed = k_pe.unsqueeze(1) |
| 154 | q_pe, k_pe = self.rope_matcher( |
| 155 | positions, q_pe, k_pe_unsqueezed, cos_sin_cache |
| 156 | ) |
| 157 | dummy = torch.ops.vllm.unified_mla_kv_cache_update( |
| 158 | kv_c_normed, k_pe, layer_name, self.kv_cache_dtype, k_scale |
| 159 | ) |
| 160 | return dummy, q_pe, k_pe |
| 161 | |
| 162 | return _pattern_with_ln |
| 163 | |
| 164 | def _pattern( |
| 165 | q_pe: torch.Tensor, |
| 166 | k_pe: torch.Tensor, |
| 167 | kv_c_normed: torch.Tensor, |
| 168 | positions: torch.Tensor, |
| 169 | cos_sin_cache: torch.Tensor, |
| 170 | k_scale: torch.Tensor, |
| 171 | ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: |
| 172 | k_pe_unsqueezed = k_pe.unsqueeze(1) |
| 173 | q_pe, k_pe = self.rope_matcher( |
| 174 | positions, q_pe, k_pe_unsqueezed, cos_sin_cache |
| 175 | ) |
| 176 | dummy = torch.ops.vllm.unified_mla_kv_cache_update( |
| 177 | kv_c_normed, k_pe, _ln, self.kv_cache_dtype, k_scale |
| 178 | ) |
| 179 | return dummy, q_pe, k_pe |
| 180 | |
| 181 | return _pattern |
| 182 | |
| 183 | @property |
| 184 | def replacement(self): |
| 185 | _ln = self._ln |
| 186 | |
| 187 | if _USE_LAYERNAME: |
| 188 | |
| 189 | def _replacement_with_ln( |
| 190 | q_pe: torch.Tensor, |
| 191 | k_pe: torch.Tensor, |
| 192 | kv_c_normed: torch.Tensor, |
| 193 | positions: torch.Tensor, |
| 194 | cos_sin_cache: torch.Tensor, |
| 195 | k_scale: torch.Tensor, |
| 196 | layer_name: LayerNameType, |
| 197 | ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: |
| 198 | at = auto_functionalized( |
| 199 | self.FUSED_OP, |
| 200 | positions=positions, |
| 201 | q_pe=q_pe, |
| 202 | k_pe=k_pe, |
| 203 | kv_c=kv_c_normed, |
| 204 | cos_sin_cache=cos_sin_cache, |
| 205 | is_neox=self.is_neox, |
| 206 | kv_cache_dtype=self.kv_cache_dtype, |
| 207 | kv_cache_scale=k_scale, |
| 208 | layer_name=layer_name, |
| 209 | ) |
| 210 | dummy, q_pe, k_pe_squeezed = at |
| 211 | k_pe = k_pe_squeezed.unsqueeze(1) |
| 212 | return dummy, q_pe, k_pe |
| 213 | |
| 214 | return _replacement_with_ln |
| 215 | |
| 216 | def _replacement( |
| 217 | q_pe: torch.Tensor, |
| 218 | k_pe: torch.Tensor, |
| 219 | kv_c_normed: torch.Tensor, |
| 220 | positions: torch.Tensor, |
| 221 | cos_sin_cache: torch.Tensor, |
| 222 | k_scale: torch.Tensor, |
| 223 | ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: |
| 224 | at = auto_functionalized( |
| 225 | self.FUSED_OP, |
| 226 | positions=positions, |
| 227 | q_pe=q_pe, |
| 228 | k_pe=k_pe, |
| 229 | kv_c=kv_c_normed, |
| 230 | cos_sin_cache=cos_sin_cache, |
| 231 | is_neox=self.is_neox, |
| 232 | kv_cache_dtype=self.kv_cache_dtype, |
| 233 | kv_cache_scale=k_scale, |
| 234 | layer_name=_ln, |
| 235 | ) |
| 236 | dummy, q_pe, k_pe_squeezed = at |
| 237 | k_pe = k_pe_squeezed.unsqueeze(1) |
| 238 | return dummy, q_pe, k_pe |
| 239 | |
| 240 | return _replacement |
| 241 | |
| 242 | |
| 243 | class MLARoPEKVCacheCatFusionPass(VllmFusionPatternMatcherPass): |
| 244 | def __init__(self, config: VllmConfig) -> None: |
| 245 | super().__init__(config, "mla_rope_kv_cache_fusion_pass") |
| 246 | |
| 247 | attn_layers = get_layers_from_vllm_config(config, MLAAttention) |
| 248 | |
| 249 | for _, layer in attn_layers.items(): |
| 250 | for is_neox in [False, True]: |
| 251 | for use_deepseek_scaling in [False, True]: |
| 252 | if RotaryEmbedding.enabled(): |
| 253 | for use_flashinfer in [False, True]: |
| 254 | self.register( |
| 255 | MLARoPEKVCacheCatPattern( |
| 256 | layer, |
| 257 | is_neox, |
| 258 | use_flashinfer, |
| 259 | use_deepseek_scaling, |
| 260 | ) |
| 261 | ) |
| 262 | else: |
| 263 | self.register( |
| 264 | MLARoPEKVCacheCatPattern( |
| 265 | layer, |
| 266 | is_neox, |
| 267 | use_deepseek_scaling=use_deepseek_scaling, |
| 268 | ) |
| 269 | ) |
| 270 | |
| 271 | if _USE_LAYERNAME: |
| 272 | break |
| 273 | |
| 274 | self.dump_patterns(config, self.pm_pass) |