A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 15, 09:45 UTC
[PERF] Speed up Qwen2.5-VL model by speed up rotary position embedding (#17973)
Signed-off-by: Vadim Gimpelson <vadim.gimpelson@centml.ai>
Vadim Gimpelson committed
May 16, 2025 at 10:31 UTC
67da5720d4ed2aa1f615ec812031f4f3753b3f62
1 file changed
+121
-83
vllm/model_executor/models/qwen2_5_vl.py
+121
-83
@@ -25,7 +25,7 @@
25
# limitations under the License.
26
"""Inference-only Qwen2.5-VL model compatible with HuggingFace weights."""
27
from collections.abc import Iterable, Mapping
28
-from functools import partial
28
+from functools import lru_cache, partial
29
from typing import Callable, Literal, Optional, TypedDict, Union
30
31
import torch
@@ -478,8 +478,8 @@ class Qwen2_5_VisionRotaryEmbedding(nn.Module):
478
super().__init__()
479
self.dim = dim
480
self.theta = theta
481
- inv_freq = 1.0 / (theta
482
- **(torch.arange(0, dim, 2, dtype=torch.float) / dim))
481
+ inv_freq = 1.0 / (theta**(
482
+ torch.arange(0, dim, 2, dtype=torch.float, device='cpu') / dim))
483
self.register_buffer("inv_freq", inv_freq, persistent=False)
484
self._seq_len_cached = 0
485
self._freqs_cached = None
@@ -520,7 +520,7 @@ class Qwen2_5_VisionTransformer(nn.Module):
520
self.hidden_size = vision_config.hidden_size
521
self.num_heads = vision_config.num_heads
522
523
- # args for get_window_index
523
+ # args for get_window_index_thw
524
self.window_size = vision_config.window_size
525
self.patch_size = vision_config.patch_size
526
self.spatial_merge_size = vision_config.spatial_merge_size
@@ -567,65 +567,71 @@ class Qwen2_5_VisionTransformer(nn.Module):
567
def device(self) -> torch.device:
568
return self.patch_embed.proj.weight.device
569
570
- def rot_pos_emb(self, grid_thw: torch.Tensor) -> torch.Tensor:
571
- pos_ids = []
572
- for t, h, w in grid_thw:
573
- hpos_ids = torch.arange(h).unsqueeze(1).expand(-1, w)
574
- wpos_ids = torch.arange(w).unsqueeze(0).expand(h, -1)
575
- hpos_ids = hpos_ids.reshape(
576
- h // self.spatial_merge_size,
577
- self.spatial_merge_size,
578
- w // self.spatial_merge_size,
579
- self.spatial_merge_size,
580
- ).permute(0, 2, 1, 3).flatten()
581
- wpos_ids = wpos_ids.reshape(
582
- h // self.spatial_merge_size,
583
- self.spatial_merge_size,
584
- w // self.spatial_merge_size,
585
- self.spatial_merge_size,
586
- ).permute(0, 2, 1, 3).flatten()
587
- pos_ids.append(
588
- torch.stack([hpos_ids, wpos_ids], dim=-1).repeat(t, 1))
589
- pos_ids = torch.cat(pos_ids, dim=0)
590
- max_grid_size = grid_thw[:, 1:].max()
591
- rotary_pos_emb_full = self.rotary_pos_emb(max_grid_size)
570
+ def rotary_pos_emb_thw(self, t, h, w):
571
+ hpos_ids = torch.arange(h).unsqueeze(1).expand(-1, w)
572
+ wpos_ids = torch.arange(w).unsqueeze(0).expand(h, -1)
573
+ hpos_ids = hpos_ids.reshape(
574
+ h // self.spatial_merge_size,
575
+ self.spatial_merge_size,
576
+ w // self.spatial_merge_size,
577
+ self.spatial_merge_size,
578
+ ).permute(0, 2, 1, 3).flatten()
579
+ wpos_ids = wpos_ids.reshape(
580
+ h // self.spatial_merge_size,
581
+ self.spatial_merge_size,
582
+ w // self.spatial_merge_size,
583
+ self.spatial_merge_size,
584
+ ).permute(0, 2, 1, 3).flatten()
585
+ pos_ids = torch.stack([hpos_ids, wpos_ids], dim=-1).repeat(t, 1)
586
+ max_size = max(h, w)
587
+ rotary_pos_emb_full = self.rotary_pos_emb(max_size)
588
rotary_pos_emb = rotary_pos_emb_full[pos_ids].flatten(1)
589
+ rotary_pos_emb = rotary_pos_emb.reshape(
590
+ rotary_pos_emb.shape[0] // self.spatial_merge_unit,
591
+ self.spatial_merge_unit, -1)
592
+
593
return rotary_pos_emb
594
595
- def get_window_index(self, grid_thw):
596
- window_index: list = []
597
- cu_window_seqlens: list = [0]
598
- window_index_id = 0
595
+ def get_window_index_thw(self, grid_t, grid_h, grid_w):
596
vit_merger_window_size = (self.window_size //
597
self.spatial_merge_size // self.patch_size)
598
602
- for grid_t, grid_h, grid_w in grid_thw:
603
- llm_grid_h = grid_h // self.spatial_merge_size
604
- llm_grid_w = grid_w // self.spatial_merge_size
605
- index = torch.arange(grid_t * llm_grid_h * llm_grid_w).reshape(
606
- grid_t, llm_grid_h, llm_grid_w)
607
- pad_h = vit_merger_window_size - llm_grid_h % vit_merger_window_size
608
- pad_w = vit_merger_window_size - llm_grid_w % vit_merger_window_size
609
- num_windows_h = (llm_grid_h + pad_h) // vit_merger_window_size
610
- num_windows_w = (llm_grid_w + pad_w) // vit_merger_window_size
611
- index_padded = F.pad(index, (0, pad_w, 0, pad_h), 'constant', -100)
612
- index_padded = index_padded.reshape(grid_t, num_windows_h,
613
- vit_merger_window_size,
614
- num_windows_w,
615
- vit_merger_window_size)
616
- index_padded = index_padded.permute(0, 1, 3, 2, 4).reshape(
617
- grid_t, num_windows_h * num_windows_w, vit_merger_window_size,
618
- vit_merger_window_size)
619
- seqlens = (index_padded != -100).sum([2, 3]).reshape(-1)
620
- index_padded = index_padded.reshape(-1)
621
- index_new = index_padded[index_padded != -100]
622
- window_index.append(index_new + window_index_id)
623
- cu_seqlens_tmp = seqlens.cumsum(
624
- 0) * self.spatial_merge_unit + cu_window_seqlens[-1]
625
- cu_window_seqlens.extend(cu_seqlens_tmp.tolist())
626
- window_index_id += (grid_t * llm_grid_h * llm_grid_w).item()
627
- window_index = torch.cat(window_index, dim=0)
628
- return window_index, cu_window_seqlens
599
+ llm_grid_h = grid_h // self.spatial_merge_size
600
+ llm_grid_w = grid_w // self.spatial_merge_size
601
+ index = torch.arange(grid_t * llm_grid_h * llm_grid_w).reshape(
602
+ grid_t, llm_grid_h, llm_grid_w)
603
+ pad_h = vit_merger_window_size - llm_grid_h % vit_merger_window_size
604
+ pad_w = vit_merger_window_size - llm_grid_w % vit_merger_window_size
605
+ num_windows_h = (llm_grid_h + pad_h) // vit_merger_window_size
606
+ num_windows_w = (llm_grid_w + pad_w) // vit_merger_window_size
607
+ index_padded = F.pad(index, (0, pad_w, 0, pad_h), 'constant', -100)
608
+ index_padded = index_padded.reshape(grid_t, num_windows_h,
609
+ vit_merger_window_size,
610
+ num_windows_w,
611
+ vit_merger_window_size)
612
+ index_padded = index_padded.permute(0, 1, 3, 2, 4).reshape(
613
+ grid_t, num_windows_h * num_windows_w, vit_merger_window_size,
614
+ vit_merger_window_size)
615
+ seqlens = (index_padded != -100).sum([2, 3]).reshape(-1)
616
+ index_padded = index_padded.reshape(-1)
617
+ index_new = index_padded[index_padded != -100]
618
+ cu_seqlens_tmp = seqlens.cumsum(0) * self.spatial_merge_unit
619
+ cu_seqlens_tmp = cu_seqlens_tmp.to(dtype=torch.int32)
620
+ cu_seqlens_tmp = torch.unique_consecutive(cu_seqlens_tmp)
621
+
622
+ return index_new, cu_seqlens_tmp
623
+
624
+ @lru_cache(maxsize=1024) # noqa: B019
625
+ def get_rope_by_thw(self, t, h, w):
626
+ window_index_thw, cu_seqlens_window_thw = self.get_window_index_thw(
627
+ t, h, w)
628
+ rotary_pos_emb_thw = self.rotary_pos_emb_thw(t, h, w)
629
+ rotary_pos_emb_thw = rotary_pos_emb_thw[window_index_thw, :, :]
630
+ rotary_pos_emb_thw = rotary_pos_emb_thw.flatten(start_dim=0, end_dim=1)
631
+ cu_seqlens_thw = torch.repeat_interleave(
632
+ torch.tensor([h * w], dtype=torch.int32), t)
633
+ return (rotary_pos_emb_thw, window_index_thw, cu_seqlens_window_thw,
634
+ cu_seqlens_thw)
635
636
def compute_attn_mask_seqlen(
637
self,
@@ -641,45 +647,74 @@ class Qwen2_5_VisionTransformer(nn.Module):
647
def forward(
648
self,
649
x: torch.Tensor,
644
- grid_thw: torch.Tensor,
650
+ grid_thw: list[list[int]],
651
) -> torch.Tensor:
652
# patchify
653
+ seq_len, _ = x.size()
654
+ rotary_pos_emb = []
655
+ window_index: list = []
656
+ cu_window_seqlens: list = [torch.tensor([0], dtype=torch.int32)]
657
+ cu_seqlens: list = []
658
+
659
hidden_states = x.to(device=self.device, dtype=self.dtype)
660
hidden_states = self.patch_embed(hidden_states)
661
650
- # compute position embedding
651
- rotary_pos_emb = self.rot_pos_emb(grid_thw)
662
+ window_index_id = 0
663
+ cu_window_seqlens_last = 0
664
+ for t, h, w in grid_thw:
665
+ t, h, w = int(t), int(h), int(w)
666
+ llm_h = h // self.spatial_merge_size
667
+ llm_w = w // self.spatial_merge_size
668
+
669
+ (
670
+ rotary_pos_emb_thw,
671
+ window_index_thw,
672
+ cu_seqlens_window_thw,
673
+ cu_seqlens_thw,
674
+ ) = self.get_rope_by_thw(t, h, w)
675
+
676
+ window_index.append(window_index_thw + window_index_id)
677
+ window_index_id += (t * llm_h * llm_w)
678
+
679
+ cu_seqlens_window_thw = (cu_seqlens_window_thw +
680
+ cu_window_seqlens_last)
681
+ cu_window_seqlens_last = cu_seqlens_window_thw[-1]
682
+ cu_window_seqlens.append(cu_seqlens_window_thw)
683
653
- # windows attention
654
- window_index, cu_window_seqlens = self.get_window_index(grid_thw)
655
- cu_window_seqlens = torch.tensor(
656
- cu_window_seqlens,
657
- device=hidden_states.device,
658
- dtype=grid_thw.dtype if torch.jit.is_tracing() else torch.int32)
684
+ rotary_pos_emb.append(rotary_pos_emb_thw)
685
+
686
+ cu_seqlens.append(cu_seqlens_thw)
687
+
688
+ rotary_pos_emb = torch.cat(rotary_pos_emb)
689
+ window_index = torch.cat(window_index)
690
+ cu_window_seqlens = torch.cat(cu_window_seqlens)
691
cu_window_seqlens = torch.unique_consecutive(cu_window_seqlens)
660
- seq_len, _ = hidden_states.size()
661
- hidden_states = hidden_states.reshape(
662
- seq_len // self.spatial_merge_unit, self.spatial_merge_unit, -1)
663
- hidden_states = hidden_states[window_index, :, :]
664
- hidden_states = hidden_states.reshape(seq_len, -1)
665
- rotary_pos_emb = rotary_pos_emb.reshape(
666
- seq_len // self.spatial_merge_unit, self.spatial_merge_unit, -1)
667
- rotary_pos_emb = rotary_pos_emb[window_index, :, :]
668
- rotary_pos_emb = rotary_pos_emb.reshape(seq_len, -1)
669
- # compute cu_seqlens
670
- cu_seqlens = torch.repeat_interleave(grid_thw[:, 1] * grid_thw[:, 2],
671
- grid_thw[:, 0]).cumsum(
672
- dim=0, dtype=torch.int32)
692
+ cu_seqlens = torch.cat(cu_seqlens)
693
+ cu_seqlens = torch.cumsum(cu_seqlens, dim=0, dtype=torch.int32)
694
cu_seqlens = F.pad(cu_seqlens, (1, 0), "constant", 0)
695
696
# transformers
676
- hidden_states = hidden_states.unsqueeze(1)
677
-
697
# pre-compute seqlens for window/full attn to reduce cuMemcpy operations
698
max_seqlen_full, seqlens_full = self.compute_attn_mask_seqlen(
699
cu_seqlens)
700
max_seqlen_window, seqlens_window = self.compute_attn_mask_seqlen(
701
cu_window_seqlens)
702
+
703
+ cu_seqlens = cu_seqlens.to(device=self.device, non_blocking=True)
704
+ cu_window_seqlens = cu_window_seqlens.to(device=self.device,
705
+ non_blocking=True)
706
+ rotary_pos_emb = rotary_pos_emb.to(device=self.device,
707
+ non_blocking=True)
708
+ window_index = window_index.to(device=hidden_states.device,
709
+ non_blocking=True)
710
+
711
+ hidden_states = hidden_states.reshape(
712
+ seq_len // self.spatial_merge_unit, self.spatial_merge_unit, -1)
713
+ hidden_states = hidden_states[window_index, :, :]
714
+ hidden_states = hidden_states.reshape(seq_len, -1)
715
+
716
+ hidden_states = hidden_states.unsqueeze(1)
717
+
718
for layer_num, blk in enumerate(self.blocks):
719
if layer_num in self.fullatt_block_indexes:
720
cu_seqlens_now = cu_seqlens
@@ -932,12 +967,13 @@ class Qwen2_5_VLForConditionalGeneration(nn.Module, SupportsMultiModal,
967
968
grid_thw = image_input["image_grid_thw"]
969
assert grid_thw.ndim == 2
970
+ grid_thw_list = grid_thw.tolist()
971
972
if image_input["type"] == "image_embeds":
973
image_embeds = image_input["image_embeds"].type(self.visual.dtype)
974
else:
975
pixel_values = image_input["pixel_values"].type(self.visual.dtype)
940
- image_embeds = self.visual(pixel_values, grid_thw=grid_thw)
976
+ image_embeds = self.visual(pixel_values, grid_thw=grid_thw_list)
977
978
# Split concatenated embeddings for each image item.
979
merge_size = self.visual.spatial_merge_size
@@ -951,13 +987,15 @@ class Qwen2_5_VLForConditionalGeneration(nn.Module, SupportsMultiModal,
987
988
grid_thw = video_input["video_grid_thw"]
989
assert grid_thw.ndim == 2
990
+ grid_thw_list = grid_thw.tolist()
991
992
if video_input["type"] == "video_embeds":
993
video_embeds = video_input["video_embeds"].type(self.visual.dtype)
994
else:
995
pixel_values_videos = video_input["pixel_values_videos"].type(
996
self.visual.dtype)
960
- video_embeds = self.visual(pixel_values_videos, grid_thw=grid_thw)
997
+ video_embeds = self.visual(pixel_values_videos,
998
+ grid_thw=grid_thw_list)
999
1000
# Split concatenated embeddings for each video item.
1001
merge_size = self.visual.spatial_merge_size