main
py 566 lines 19.5 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
5 import numpy as np
6 import torch
7
8 from vllm.platforms import current_platform
9 from vllm.triton_utils import tl, triton
10 from vllm.utils.torch_utils import direct_register_custom_op
11
12 from .base import RotaryEmbeddingBase
13 from .yarn_scaling_rope import YaRNScalingRotaryEmbedding, yarn_get_mscale
14
15
16 @triton.jit
17 def _triton_mrope_forward(
18 q_ptr,
19 k_ptr,
20 cos,
21 sin,
22 num_tokens,
23 q_token_stride,
24 k_token_stride,
25 n_qh: tl.constexpr,
26 n_kh: tl.constexpr,
27 hd: tl.constexpr,
28 rd: tl.constexpr,
29 pad_n_qh: tl.constexpr,
30 pad_n_kh: tl.constexpr,
31 pad_rd: tl.constexpr,
32 mrope_section_t: tl.constexpr,
33 mrope_section_h: tl.constexpr,
34 mrope_section_w: tl.constexpr,
35 is_interleaved: tl.constexpr,
36 is_neox_style: tl.constexpr,
37 ):
38 # Adapted from
39 # https://github.com/linkedin/Liger-Kernel/blob/main/src/liger_kernel/ops/qwen2vl_mrope.py
40 # This version supports flatten input tensors from vllm
41 # and supports cos and sin cache with shape (3, num_tokens, rotary_dim // 2)
42 # instead of (3, bsz, seq_len, head_dim), also supports interleaved rotary
43 pid = tl.program_id(0)
44 # locate start address (int64: num_tokens * row_stride exceeds 2^31 for
45 # worst-case ViT forwards, e.g. MiniMax M3 packs ~768k patches/item)
46 q_ptr = q_ptr + pid.to(tl.int64) * q_token_stride
47 k_ptr = k_ptr + pid.to(tl.int64) * k_token_stride
48
49 # ####################################################################
50 # get the cos(mθ_{i...d/2}) and sin(mθ_{i...d/2}) for token position
51 # m of this program instance
52 # ####################################################################
53 # Note: cos and sin now have shape (3, num_tokens, rotary_dim // 2)
54
55 # Updated stride calculation for half rotary_dim
56 half_rd = rd // 2
57 t_cos = cos + pid * half_rd
58 h_cos = t_cos + num_tokens * half_rd
59 w_cos = h_cos + num_tokens * half_rd
60 t_sin = sin + pid * half_rd
61 h_sin = t_sin + num_tokens * half_rd
62 w_sin = h_sin + num_tokens * half_rd
63
64 # Updated offsets for half rotary_dim
65 cos_offsets = tl.arange(0, pad_rd // 2)
66 if is_interleaved:
67 valid_mask = cos_offsets < half_rd
68 h_mask = (
69 valid_mask & ((cos_offsets % 3) == 1) & (cos_offsets <= 3 * mrope_section_h)
70 )
71 w_mask = (
72 valid_mask & ((cos_offsets % 3) == 2) & (cos_offsets <= 3 * mrope_section_w)
73 )
74 t_mask = valid_mask & ~(h_mask | w_mask)
75 else:
76 t_end = mrope_section_t
77 h_end = t_end + mrope_section_h
78 t_mask = cos_offsets < mrope_section_t
79 h_mask = (t_end <= cos_offsets) & (cos_offsets < h_end)
80 w_mask = (h_end <= cos_offsets) & (cos_offsets < half_rd)
81
82 t_cos_row = tl.load(t_cos + cos_offsets, mask=t_mask, other=0)
83 h_cos_row = tl.load(h_cos + cos_offsets, mask=h_mask, other=0)
84 w_cos_row = tl.load(w_cos + cos_offsets, mask=w_mask, other=0)
85 t_sin_row = tl.load(t_sin + cos_offsets, mask=t_mask, other=0)
86 h_sin_row = tl.load(h_sin + cos_offsets, mask=h_mask, other=0)
87 w_sin_row = tl.load(w_sin + cos_offsets, mask=w_mask, other=0)
88
89 cos_row = t_cos_row + h_cos_row + w_cos_row
90 sin_row = t_sin_row + h_sin_row + w_sin_row
91
92 # ####################################################################
93 # Load the two values in each rotary pair for the current token.
94 # NeoX pairs the first and second halves, while GPT-J pairs
95 # adjacent values.
96 # ####################################################################
97 if is_neox_style:
98 rotary_offsets = tl.arange(0, pad_rd // 2)
99 first_q_offsets = tl.arange(0, pad_n_qh)[:, None] * hd + rotary_offsets[None, :]
100 first_k_offsets = tl.arange(0, pad_n_kh)[:, None] * hd + rotary_offsets[None, :]
101 first_q_mask = (tl.arange(0, pad_n_qh)[:, None] < n_qh) & (
102 rotary_offsets[None, :] < rd // 2
103 )
104 first_k_mask = (tl.arange(0, pad_n_kh)[:, None] < n_kh) & (
105 rotary_offsets[None, :] < rd // 2
106 )
107
108 q_tile_1 = tl.load(q_ptr + first_q_offsets, mask=first_q_mask, other=0).to(
109 sin_row.dtype
110 )
111 k_tile_1 = tl.load(k_ptr + first_k_offsets, mask=first_k_mask, other=0).to(
112 sin_row.dtype
113 )
114
115 second_q_offsets = first_q_offsets + (rd // 2)
116 second_k_offsets = first_k_offsets + (rd // 2)
117 q_tile_2 = tl.load(q_ptr + second_q_offsets, mask=first_q_mask, other=0).to(
118 sin_row.dtype
119 )
120 k_tile_2 = tl.load(k_ptr + second_k_offsets, mask=first_k_mask, other=0).to(
121 sin_row.dtype
122 )
123
124 new_q_tile_1 = q_tile_1 * cos_row - q_tile_2 * sin_row
125 tl.store(q_ptr + first_q_offsets, new_q_tile_1, mask=first_q_mask)
126 new_q_tile_2 = q_tile_2 * cos_row + q_tile_1 * sin_row
127 tl.store(q_ptr + second_q_offsets, new_q_tile_2, mask=first_q_mask)
128
129 new_k_tile_1 = k_tile_1 * cos_row - k_tile_2 * sin_row
130 tl.store(k_ptr + first_k_offsets, new_k_tile_1, mask=first_k_mask)
131 new_k_tile_2 = k_tile_2 * cos_row + k_tile_1 * sin_row
132 tl.store(k_ptr + second_k_offsets, new_k_tile_2, mask=first_k_mask)
133 else:
134 # Load and store adjacent rotary pairs contiguously. Using stride-two
135 # even/odd offsets makes Triton emit scalar 16-bit memory operations on
136 # AMD, while split/interleave only rearranges values in registers.
137 rotary_offsets = tl.arange(0, pad_rd)
138 q_offsets = tl.arange(0, pad_n_qh)[:, None] * hd + rotary_offsets[None, :]
139 k_offsets = tl.arange(0, pad_n_kh)[:, None] * hd + rotary_offsets[None, :]
140 q_mask = (tl.arange(0, pad_n_qh)[:, None] < n_qh) & (
141 rotary_offsets[None, :] < rd
142 )
143 k_mask = (tl.arange(0, pad_n_kh)[:, None] < n_kh) & (
144 rotary_offsets[None, :] < rd
145 )
146
147 q_tile = tl.load(q_ptr + q_offsets, mask=q_mask, other=0).to(sin_row.dtype)
148 k_tile = tl.load(k_ptr + k_offsets, mask=k_mask, other=0).to(sin_row.dtype)
149 q_tile_1, q_tile_2 = tl.split(tl.reshape(q_tile, (pad_n_qh, pad_rd // 2, 2)))
150 k_tile_1, k_tile_2 = tl.split(tl.reshape(k_tile, (pad_n_kh, pad_rd // 2, 2)))
151
152 new_q_tile_1 = q_tile_1 * cos_row - q_tile_2 * sin_row
153 new_q_tile_2 = q_tile_2 * cos_row + q_tile_1 * sin_row
154 new_q_tile = tl.interleave(new_q_tile_1, new_q_tile_2)
155 tl.store(q_ptr + q_offsets, new_q_tile, mask=q_mask)
156
157 new_k_tile_1 = k_tile_1 * cos_row - k_tile_2 * sin_row
158 new_k_tile_2 = k_tile_2 * cos_row + k_tile_1 * sin_row
159 new_k_tile = tl.interleave(new_k_tile_1, new_k_tile_2)
160 tl.store(k_ptr + k_offsets, new_k_tile, mask=k_mask)
161
162
163 def triton_mrope(
164 q: torch.Tensor,
165 k: torch.Tensor,
166 cos: torch.Tensor,
167 sin: torch.Tensor,
168 mrope_section: list[int],
169 head_size: int,
170 rotary_dim: int,
171 mrope_interleaved: bool,
172 is_neox_style: bool,
173 ) -> tuple[torch.Tensor, torch.Tensor]:
174 """Qwen2VL mrope kernel.
175
176 Args:
177 q: [num_tokens, num_heads * head_size]
178 k: [num_tokens, num_kv_heads * head_size]
179 cos: [3, num_tokens, rotary_dim // 2]
180 (T/H/W positions with multimodal inputs)
181 sin: [3, num_tokens, rotary_dim // 2]
182 (T/H/W positions with multimodal inputs)
183 mrope_section: [t, h, w]
184 head_size: int
185 rotary_dim: Number of leading dimensions rotary is applied to.
186 mrope_interleaved: Whether the T/H/W sections are interleaved rather
187 than concatenated.
188 is_neox_style: Whether rotary pairs use split-half (NeoX) or
189 adjacent (GPT-J) layout.
190
191 """
192 # The kernel indexes each token row through an explicit row stride and
193 # requires dense rows.
194 assert q.dim() == 2 and q.stride(-1) == 1
195 assert k.dim() == 2 and k.stride(-1) == 1
196 n_row, n_q_head_head_dim = q.shape
197 n_q_head = n_q_head_head_dim // head_size
198 n_kv_head = k.shape[1] // head_size
199 pad_rd = triton.next_power_of_2(rotary_dim)
200 pad_n_q_head = triton.next_power_of_2(n_q_head)
201 pad_n_kv_head = triton.next_power_of_2(n_kv_head)
202
203 cos = cos.contiguous()
204 sin = sin.contiguous()
205
206 # Small adjacent-pair tiles perform best with one wave per program on
207 # ROCm. Keep the existing launch shape for larger rotary dimensions,
208 # NeoX, and other backends.
209 use_single_wave = current_platform.is_rocm() and not is_neox_style and pad_rd <= 64
210 num_warps = 1 if use_single_wave else 4
211 _triton_mrope_forward[(n_row,)](
212 q,
213 k,
214 cos,
215 sin,
216 n_row,
217 q.stride(0),
218 k.stride(0),
219 n_q_head,
220 n_kv_head,
221 head_size,
222 rotary_dim,
223 pad_n_q_head,
224 pad_n_kv_head,
225 pad_rd,
226 mrope_section[0],
227 mrope_section[1],
228 mrope_section[2],
229 mrope_interleaved,
230 is_neox_style,
231 num_warps=num_warps,
232 )
233 return q, k
234
235
236 def apply_interleaved_rope(x: torch.Tensor, mrope_section: list[int]) -> torch.Tensor:
237 """Apply interleaved MRoPE to 3D rotary embeddings.
238 Reorganizes frequency layout from chunked [TTT...HHH...WWW] to
239 interleaved [THWTHWTHW...TT], preserving frequency continuity.
240 """
241 channels = torch.arange(x.shape[-1], device=x.device)
242 is_height = (channels % 3 == 1) & (channels < mrope_section[1] * 3)
243 is_width = (channels % 3 == 2) & (channels < mrope_section[2] * 3)
244
245 result = torch.where(is_height, x[1], x[0])
246 return torch.where(is_width, x[2], result)
247
248
249 def _mrope_apply(
250 positions: torch.Tensor,
251 query: torch.Tensor,
252 key: torch.Tensor,
253 cos_sin_cache: torch.Tensor,
254 head_size: int,
255 rotary_dim: int,
256 mrope_section_t: int,
257 mrope_section_h: int,
258 mrope_section_w: int,
259 mrope_interleaved: bool,
260 is_neox_style: bool,
261 ) -> None:
262 cos_sin = cos_sin_cache[positions]
263 cos, sin = cos_sin.chunk(2, dim=-1)
264 query_out, key_out = triton_mrope(
265 query,
266 key,
267 cos,
268 sin,
269 [mrope_section_t, mrope_section_h, mrope_section_w],
270 head_size,
271 rotary_dim,
272 mrope_interleaved,
273 is_neox_style,
274 )
275 if query_out.data_ptr() != query.data_ptr():
276 query.copy_(query_out.view_as(query))
277 if key_out.data_ptr() != key.data_ptr():
278 key.copy_(key_out.view_as(key))
279
280
281 def _mrope_apply_fake(
282 positions: torch.Tensor,
283 query: torch.Tensor,
284 key: torch.Tensor,
285 cos_sin_cache: torch.Tensor,
286 head_size: int,
287 rotary_dim: int,
288 mrope_section_t: int,
289 mrope_section_h: int,
290 mrope_section_w: int,
291 mrope_interleaved: bool,
292 is_neox_style: bool,
293 ) -> None:
294 return
295
296
297 direct_register_custom_op(
298 op_name="mrope",
299 op_func=_mrope_apply,
300 mutates_args=["query", "key"],
301 fake_impl=_mrope_apply_fake,
302 )
303
304
305 class MRotaryEmbedding(RotaryEmbeddingBase):
306 """Rotary Embedding with Multimodal Sections."""
307
308 def __init__(
309 self,
310 head_size: int,
311 rotary_dim: int,
312 max_position_embeddings: int,
313 base: float,
314 is_neox_style: bool,
315 dtype: torch.dtype,
316 mrope_section: list[int] | None = None,
317 mrope_interleaved: bool = False,
318 # YaRN parameters.
319 *,
320 scaling_factor: float | None = None,
321 beta_fast: int = 32,
322 beta_slow: int = 1,
323 mscale: float | None = None,
324 mscale_all_dim: float | None = None,
325 attention_factor: float | None = None,
326 truncate: bool = True,
327 ) -> None:
328 self.scaling_factor = scaling_factor
329 self.beta_fast = beta_fast
330 self.beta_slow = beta_slow
331 self.truncate = truncate
332 if self.scaling_factor is not None:
333 # Get n-d magnitude scaling corrected for interpolation
334 if attention_factor is not None:
335 self.mscale = float(attention_factor)
336 elif mscale and mscale_all_dim:
337 self.mscale = float(
338 yarn_get_mscale(self.scaling_factor, mscale)
339 / yarn_get_mscale(self.scaling_factor, mscale_all_dim)
340 )
341 else:
342 self.mscale = float(yarn_get_mscale(self.scaling_factor))
343 else:
344 self.mscale = 1.0
345
346 # In Qwen2.5-VL, the maximum index value is related to the duration of
347 # the input video. We enlarge max_position_embeddings to 4 times to get
348 # a larger the cos and sin cache.
349 self.cache_max_position_num = max_position_embeddings * 4
350 super().__init__(
351 head_size,
352 rotary_dim,
353 self.cache_max_position_num,
354 base,
355 is_neox_style,
356 dtype,
357 )
358
359 self.mrope_section = mrope_section
360 self.mrope_interleaved = mrope_interleaved
361 if self.mrope_section:
362 assert sum(self.mrope_section) == rotary_dim // 2
363
364 def _compute_inv_freq(self, base: float) -> torch.Tensor:
365 if self.scaling_factor is None:
366 return super()._compute_inv_freq(base)
367 return YaRNScalingRotaryEmbedding._compute_inv_freq(self, base)
368
369 def _compute_cos_sin_cache(self) -> torch.Tensor:
370 if self.scaling_factor is None:
371 return super()._compute_cos_sin_cache()
372 return YaRNScalingRotaryEmbedding._compute_cos_sin_cache(self)
373
374 def forward_native(
375 self,
376 positions: torch.Tensor,
377 query: torch.Tensor,
378 key: torch.Tensor | None = None,
379 offsets: torch.Tensor | None = None,
380 ) -> tuple[torch.Tensor, torch.Tensor | None]:
381 """PyTorch-native implementation equivalent to forward().
382
383 Args:
384 positions:
385 [num_tokens,] (text only) or
386 [3, num_tokens] (T/H/W positions with multimodal inputs)
387 query: [num_tokens, num_heads * head_size]
388 key: [num_tokens, num_kv_heads * head_size]
389 offsets: Optional per-token position offsets added to positions.
390
391 """
392 assert positions.ndim == 1 or positions.ndim == 2
393 assert key is not None
394
395 cos_sin_cache = self._match_cos_sin_cache_dtype(query)
396 num_tokens = positions.shape[-1]
397 cos_sin = cos_sin_cache[positions]
398 cos, sin = cos_sin.chunk(2, dim=-1)
399 if positions.ndim == 2:
400 assert self.mrope_section
401 if self.mrope_interleaved:
402 cos = apply_interleaved_rope(cos, self.mrope_section)
403 sin = apply_interleaved_rope(sin, self.mrope_section)
404 else:
405 cos = torch.cat(
406 [m[i] for i, m in enumerate(cos.split(self.mrope_section, dim=-1))],
407 dim=-1,
408 )
409 sin = torch.cat(
410 [m[i] for i, m in enumerate(sin.split(self.mrope_section, dim=-1))],
411 dim=-1,
412 )
413
414 query_shape = query.shape
415 query = query.view(num_tokens, -1, self.head_size)
416 query_rot = query[..., : self.rotary_dim]
417 query_pass = query[..., self.rotary_dim :]
418 query_rot = self.apply_rotary_emb.forward_native(
419 query_rot,
420 cos,
421 sin,
422 )
423 query = torch.cat((query_rot, query_pass), dim=-1).reshape(query_shape)
424
425 key_shape = key.shape
426 key = key.view(num_tokens, -1, self.head_size)
427 key_rot = key[..., : self.rotary_dim]
428 key_pass = key[..., self.rotary_dim :]
429 key_rot = self.apply_rotary_emb.forward_native(
430 key_rot,
431 cos,
432 sin,
433 )
434 key = torch.cat((key_rot, key_pass), dim=-1).reshape(key_shape)
435 return query, key
436
437 def forward_cuda(
438 self,
439 positions: torch.Tensor,
440 query: torch.Tensor,
441 key: torch.Tensor | None = None,
442 offsets: torch.Tensor | None = None,
443 ) -> tuple[torch.Tensor, torch.Tensor | None]:
444 assert positions.ndim == 1 or positions.ndim == 2
445 assert key is not None
446
447 cos_sin_cache = self._match_cos_sin_cache_dtype(query)
448 num_tokens = positions.shape[-1]
449 cos_sin = cos_sin_cache[positions]
450 cos, sin = cos_sin.chunk(2, dim=-1)
451 query_shape = query.shape
452 key_shape = key.shape
453 if positions.ndim == 2:
454 assert self.mrope_section
455
456 q, k = triton_mrope(
457 query,
458 key,
459 cos,
460 sin,
461 self.mrope_section,
462 self.head_size,
463 self.rotary_dim,
464 self.mrope_interleaved,
465 self.is_neox_style,
466 )
467
468 return q.reshape(query_shape), k.reshape(key_shape)
469
470 query = query.view(num_tokens, -1, self.head_size)
471 query_rot = query[..., : self.rotary_dim]
472 query_pass = query[..., self.rotary_dim :]
473 query_rot = self.apply_rotary_emb(
474 query_rot,
475 cos,
476 sin,
477 )
478 query = torch.cat((query_rot, query_pass), dim=-1).reshape(query_shape)
479
480 key = key.view(num_tokens, -1, self.head_size)
481 key_rot = key[..., : self.rotary_dim]
482 key_pass = key[..., self.rotary_dim :]
483 key_rot = self.apply_rotary_emb(
484 key_rot,
485 cos,
486 sin,
487 )
488 key = torch.cat((key_rot, key_pass), dim=-1).reshape(key_shape)
489 return query, key
490
491 def forward_hip(
492 self,
493 positions: torch.Tensor,
494 query: torch.Tensor,
495 key: torch.Tensor | None = None,
496 offsets: torch.Tensor | None = None,
497 ) -> tuple[torch.Tensor, torch.Tensor | None]:
498 if positions.ndim == 2:
499 assert key is not None
500 assert self.mrope_section
501 query_shape = query.shape
502 key_shape = key.shape
503 cos_sin_cache = self._match_cos_sin_cache_dtype(query)
504 torch.ops.vllm.mrope(
505 positions,
506 query,
507 key,
508 cos_sin_cache,
509 self.head_size,
510 self.rotary_dim,
511 self.mrope_section[0],
512 self.mrope_section[1],
513 self.mrope_section[2],
514 self.mrope_interleaved,
515 self.is_neox_style,
516 )
517 return query.reshape(query_shape), key.reshape(key_shape)
518 return super().forward_hip(positions, query, key)
519
520 def forward_cpu(
521 self,
522 positions: torch.Tensor,
523 query: torch.Tensor,
524 key: torch.Tensor | None = None,
525 offsets: torch.Tensor | None = None,
526 ) -> tuple[torch.Tensor, torch.Tensor | None]:
527 return self.forward_native(positions, query, key, offsets)
528
529 def forward_xpu(
530 self,
531 positions: torch.Tensor,
532 query: torch.Tensor,
533 key: torch.Tensor | None = None,
534 offsets: torch.Tensor | None = None,
535 ) -> tuple[torch.Tensor, torch.Tensor | None]:
536 return self.forward_cuda(positions, query, key, offsets)
537
538 @staticmethod
539 def get_next_input_positions(
540 mrope_position_delta: int,
541 context_len: int,
542 seq_len: int,
543 ) -> list[list[int]]:
544 return [
545 list(
546 range(
547 context_len + mrope_position_delta, seq_len + mrope_position_delta
548 )
549 )
550 for _ in range(3)
551 ]
552
553 @staticmethod
554 def get_next_input_positions_tensor(
555 out: np.ndarray,
556 out_offset: int,
557 mrope_position_delta: int,
558 context_len: int,
559 num_new_tokens: int,
560 ):
561 values = np.arange(
562 mrope_position_delta + context_len,
563 mrope_position_delta + context_len + num_new_tokens,
564 dtype=out.dtype,
565 )
566 out[:, out_offset : out_offset + num_new_tokens] = values