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 3, 08:00 UTC
[Perf][4/n] Eliminate various GPU<->CPU syncs (#42347)
Signed-off-by: Nick Hill <nickhill123@gmail.com>
Nick Hill committed
May 19, 2026 at 07:35 UTC
b82e908b4c65a1f162e2d35a8106f09d95d8aa02
23 files changed
+129
-108
tests/v1/logits_processors/test_correctness.py
+1
-1
@@ -1092,7 +1092,7 @@ def test_thinking_budget_holder_spec_mode_tensor_layout():
1092
False,
1093
)
1094
assert h.in_spec_mode
1095
- assert h.mask.shape[0] == 8 * (2 + 1)
1095
+ assert h._mask_capacity == 8 * (2 + 1)
1096
1097
1098
def test_thinking_budget_holder_empty_end_tokens_disables_row():
vllm/lora/ops/triton_ops/utils.py
+21
-9
@@ -13,6 +13,7 @@ from vllm import envs
13
from vllm.logger import init_logger
14
from vllm.platforms import current_platform
15
from vllm.utils.math_utils import next_power_of_2
16
+from vllm.utils.torch_utils import async_tensor_h2d
17
18
logger = init_logger(__name__)
19
is_batch_invariant = envs.VLLM_BATCH_INVARIANT
@@ -49,7 +50,9 @@ def _get_lora_a_ptr(lora_a_weights: list[torch.Tensor], device: torch.device):
50
lora_strides_d1.append(lora_a_weight.stride(1))
51
lora_strides_d2.append(lora_a_weight.stride(2))
52
if len(lora_a_weights) > 1:
52
- lora_ptr_tensor = torch.tensor(tensor_ptrs, device=device, dtype=torch.uint64)
53
+ lora_ptr_tensor = async_tensor_h2d(
54
+ tensor_ptrs, dtype=torch.uint64, device=device
55
+ )
56
else:
57
lora_ptr_tensor = lora_a_weights[0]
58
@@ -106,10 +109,11 @@ def _get_lora_b_ptr(
109
hidden_sizes.append(lora_b_weight.size(1))
110
111
if len(lora_weights) > 1:
109
- # note these are device tensors
110
- lora_ptr_tensor = torch.tensor(tensor_ptrs, device=device, dtype=torch.uint64)
111
- slice_start_tensor = torch.tensor(
112
- slice_offset_lst, device=device, dtype=torch.uint64
112
+ lora_ptr_tensor = async_tensor_h2d(
113
+ tensor_ptrs, dtype=torch.uint64, device=device
114
+ )
115
+ slice_start_tensor = async_tensor_h2d(
116
+ slice_offset_lst, dtype=torch.uint64, device=device
117
)
118
else:
119
slice_start_tensor = slice_offset_lst[0]
@@ -129,10 +133,18 @@ def _get_lora_b_ptr(
133
same_stride = True
134
135
else:
132
- lora_strides_d0_tensor = torch.tensor(lora_strides_d0, device=device)
133
- lora_strides_d1_tensor = torch.tensor(lora_strides_d1, device=device)
134
- lora_strides_d2_tensor = torch.tensor(lora_strides_d2, device=device)
135
- hidden_sizes_tensor = torch.tensor(hidden_sizes, device=device)
136
+ lora_strides_d0_tensor = async_tensor_h2d(
137
+ lora_strides_d0, dtype=torch.int64, device=device
138
+ )
139
+ lora_strides_d1_tensor = async_tensor_h2d(
140
+ lora_strides_d1, dtype=torch.int64, device=device
141
+ )
142
+ lora_strides_d2_tensor = async_tensor_h2d(
143
+ lora_strides_d2, dtype=torch.int64, device=device
144
+ )
145
+ hidden_sizes_tensor = async_tensor_h2d(
146
+ hidden_sizes, dtype=torch.int64, device=device
147
+ )
148
same_stride = False
149
# MAX_N is the maximum hidden size among all the lora_b weights
150
MAX_N = max(hidden_sizes)
vllm/lora/punica_wrapper/utils.py
+4
-2
@@ -5,6 +5,8 @@ from typing import TYPE_CHECKING
5
6
import torch
7
8
+from vllm.utils.torch_utils import async_tensor_h2d
9
+
10
if TYPE_CHECKING:
11
# avoid circuit import
12
from vllm.lora.layers import LoRAMapping
@@ -110,8 +112,8 @@ def convert_mapping(
112
embedding_indices,
113
]
114
113
- indices = torch.tensor(indices_list, dtype=torch.long, device=device)
114
- prompt_mapping_tensor = torch.tensor(
115
+ indices = async_tensor_h2d(indices_list, dtype=torch.long, device=device)
116
+ prompt_mapping_tensor = async_tensor_h2d(
117
prompt_mapping, dtype=torch.long, device=device
118
)
119
embeddings_indices = torch.stack(
vllm/model_executor/models/bert.py
+3
-6
@@ -559,13 +559,10 @@ def _encode_token_type_ids(
559
560
561
def _decode_token_type_ids(input_ids: torch.Tensor) -> torch.Tensor:
562
- ids_mask = (
563
- torch.ones_like(input_ids, dtype=torch.int32, device=input_ids.device)
564
- << TOKEN_TYPE_SHIFT
565
- )
566
- tokens_mask = ids_mask.bitwise_not()
562
+ ids_mask = 1 << TOKEN_TYPE_SHIFT
563
+ tokens_mask = ~ids_mask
564
568
- token_type_ids = input_ids.bitwise_and(ids_mask) >> TOKEN_TYPE_SHIFT
565
+ token_type_ids = (input_ids & ids_mask) >> TOKEN_TYPE_SHIFT
566
567
input_ids.bitwise_and_(tokens_mask)
568
vllm/model_executor/models/gemma3_mm.py
+1
-1
@@ -305,7 +305,7 @@ class Gemma3MultiModalProcessor(BaseMultiModalProcessor[Gemma3ProcessingInfo]):
305
306
return dict(
307
pixel_values=MultiModalFieldConfig.flat_from_sizes("image", num_patches),
308
- num_patches=MultiModalFieldConfig.batched("image"),
308
+ num_patches=MultiModalFieldConfig.batched("image", keep_on_cpu=True),
309
)
310
311
def _get_prompt_updates(
vllm/model_executor/models/glm4_1v.py
+1
@@ -723,6 +723,7 @@ class Glm4vVisionTransformer(nn.Module):
723
# Use pre-computed cos_sin_cache from RotaryEmbedding
724
cos, sin = self.rotary_pos_emb.get_cos_sin(max_grid_size)
725
726
+ pos_ids = pos_ids.to(cos.device, non_blocking=True)
727
cos_combined = cos[pos_ids].flatten(1)
728
sin_combined = sin[pos_ids].flatten(1)
729
return cos_combined, sin_combined, pos_ids
vllm/model_executor/models/granite_speech.py
+7
-7
@@ -143,7 +143,7 @@ class GraniteSpeechMultiModalProcessor(
143
) -> Mapping[str, MultiModalFieldConfig]:
144
return dict(
145
input_features=MultiModalFieldConfig.batched("audio"),
146
- audio_embed_sizes=MultiModalFieldConfig.batched("audio"),
146
+ audio_embed_sizes=MultiModalFieldConfig.batched("audio", keep_on_cpu=True),
147
)
148
149
def _get_prompt_updates(
@@ -717,13 +717,13 @@ class GraniteSpeechForConditionalGeneration(
717
torch.Tensor: Mask of shape (bsz, num_features) to be applied to
718
the audio features prior to splitting the audio embeddings.
719
"""
720
- most_audio_features = torch.max(audio_embed_sizes).item()
721
- mask_indices = torch.arange(
722
- most_audio_features,
723
- device=audio_embed_sizes.device,
724
- ).view(1, -1)
720
+ most_audio_features = int(torch.max(audio_embed_sizes))
721
+ mask_indices = torch.arange(most_audio_features).view(1, -1)
722
input_features_mask = mask_indices < audio_embed_sizes.view(-1, 1)
726
- return input_features_mask
723
+ target_device = self.encoder.input_linear.weight.device
724
+ if target_device == input_features_mask.device:
725
+ return input_features_mask
726
+ return input_features_mask.pin_memory().to(target_device, non_blocking=True)
727
728
def _pad_and_stack_input_features(
729
self,
vllm/model_executor/models/idefics3.py
+1
-1
@@ -355,7 +355,7 @@ class Idefics3MultiModalProcessor(BaseMultiModalProcessor[Idefics3ProcessingInfo
355
"image", num_patches
356
),
357
image_embeds=MultiModalFieldConfig.batched("image"),
358
- num_patches=MultiModalFieldConfig.batched("image"),
358
+ num_patches=MultiModalFieldConfig.batched("image", keep_on_cpu=True),
359
)
360
361
def _get_prompt_updates(
vllm/model_executor/models/internvl.py
+5
-3
@@ -247,9 +247,11 @@ class BaseInternVLMultiModalProcessor(BaseMultiModalProcessor[_I]):
247
pixel_values_flat=MultiModalFieldConfig.flat_from_sizes(
248
"image", image_num_patches
249
),
250
- image_num_patches=MultiModalFieldConfig.batched("image"),
250
+ image_num_patches=MultiModalFieldConfig.batched("image", keep_on_cpu=True),
251
image_embeds=MultiModalFieldConfig.batched("image"),
252
- image_token_id=MultiModalFieldConfig.shared("image", num_images),
252
+ image_token_id=MultiModalFieldConfig.shared(
253
+ "image", num_images, keep_on_cpu=True
254
+ ),
255
)
256
257
def _get_mm_fields_config(
@@ -475,7 +477,7 @@ class InternVLMultiModalProcessor(
477
pixel_values_flat_video=MultiModalFieldConfig.flat_from_sizes(
478
"video", video_num_patches
479
),
478
- video_num_patches=MultiModalFieldConfig.batched("video"),
480
+ video_num_patches=MultiModalFieldConfig.batched("video", keep_on_cpu=True),
481
video_token_id=MultiModalFieldConfig.shared("video", num_videos),
482
)
483
vllm/model_executor/models/llava_next.py
+1
-1
@@ -214,7 +214,7 @@ class LlavaNextMultiModalProcessor(
214
) -> Mapping[str, MultiModalFieldConfig]:
215
return dict(
216
pixel_values=MultiModalFieldConfig.batched("image"),
217
- image_sizes=MultiModalFieldConfig.batched("image"),
217
+ image_sizes=MultiModalFieldConfig.batched("image", keep_on_cpu=True),
218
image_embeds=MultiModalFieldConfig.batched("image"),
219
)
220
vllm/model_executor/models/llava_onevision.py
+1
-1
@@ -316,7 +316,7 @@ class LlavaOnevisionMultiModalProcessor(
316
) -> Mapping[str, MultiModalFieldConfig]:
317
return dict(
318
pixel_values=MultiModalFieldConfig.batched("image"),
319
- image_sizes=MultiModalFieldConfig.batched("image"),
319
+ image_sizes=MultiModalFieldConfig.batched("image", keep_on_cpu=True),
320
image_embeds=MultiModalFieldConfig.batched("image"),
321
pixel_values_videos=MultiModalFieldConfig.batched("video"),
322
)
vllm/model_executor/models/phi4mm_audio.py
+9
-3
@@ -586,7 +586,9 @@ class TransformerEncoderBase(abc.ABC, nn.Module):
586
seq_len, batch_size, self.chunk_size, self.left_chunk
587
)
588
device = xs_pad.device
589
- enc_streaming_mask = enc_streaming_mask.to(device)
589
+ enc_streaming_mask = enc_streaming_mask.contiguous().to(
590
+ device, non_blocking=True
591
+ )
592
xs_pad = xs_pad.to(device)
593
594
input_tensor = xs_pad
@@ -605,7 +607,9 @@ class TransformerEncoderBase(abc.ABC, nn.Module):
607
seq_len, batch_size, chunk_size_nc, left_chunk_nc
608
)
609
if device.type != "cpu":
608
- enc_streaming_mask_nc = enc_streaming_mask_nc.to(device)
610
+ enc_streaming_mask_nc = enc_streaming_mask_nc.contiguous().to(
611
+ device, non_blocking=True
612
+ )
613
if masks is not None:
614
hs_mask_nc = masks & enc_streaming_mask_nc
615
else:
@@ -917,7 +921,9 @@ class ConformerEncoder(TransformerEncoderBase):
921
enc_streaming_mask = self._streaming_mask(
922
max_audio_length, batch_size, self.chunk_size, self.left_chunk
923
)
920
- enc_streaming_mask = enc_streaming_mask.to(device)
924
+ enc_streaming_mask = enc_streaming_mask.contiguous().to(
925
+ device, non_blocking=True
926
+ )
927
if mask is None:
928
return enc_streaming_mask
929
vllm/model_executor/models/qwen2_5_omni_thinker.py
+3
-3
@@ -278,15 +278,15 @@ def create_qwen2_5_omni_thinker_field_factory(
278
image_embeds=MultiModalFieldConfig.flat_from_sizes(
279
"image", image_embed_grid_sizes
280
),
281
- image_grid_thw=MultiModalFieldConfig.batched("image"),
281
+ image_grid_thw=MultiModalFieldConfig.batched("image", keep_on_cpu=True),
282
pixel_values_videos=MultiModalFieldConfig.flat_from_sizes(
283
"video", video_grid_sizes
284
),
285
video_embeds=MultiModalFieldConfig.flat_from_sizes(
286
"video", video_embed_grid_sizes
287
),
288
- video_grid_thw=MultiModalFieldConfig.batched("video"),
289
- second_per_grid_ts=MultiModalFieldConfig.batched("video"),
288
+ video_grid_thw=MultiModalFieldConfig.batched("video", keep_on_cpu=True),
289
+ second_per_grid_ts=MultiModalFieldConfig.batched("video", keep_on_cpu=True),
290
use_audio_in_video=MultiModalFieldConfig.shared("video", num_videos),
291
)
292
vllm/model_executor/models/qwen2_5_vl.py
+12
-7
@@ -84,6 +84,7 @@ from vllm.multimodal.processing import PromptReplacement, PromptUpdate
84
from vllm.sequence import IntermediateTensors
85
from vllm.utils.platform_utils import is_pin_memory_available
86
from vllm.utils.tensor_schema import TensorSchema, TensorShape
87
+from vllm.utils.torch_utils import async_tensor_h2d
88
from vllm.v1.attention.backends.registry import AttentionBackendEnum
89
from vllm.v1.worker.encoder_cudagraph_defs import EncoderCudaGraphReplayBuffers
90
@@ -679,6 +680,7 @@ class Qwen2_5_VisionTransformer(nn.Module):
680
# Use pre-computed cos_sin_cache from RotaryEmbedding
681
cos, sin = self.rotary_pos_emb.get_cos_sin(max_size)
682
683
+ pos_ids = pos_ids.to(cos.device, non_blocking=True)
684
cos_combined = cos[pos_ids].flatten(1)
685
sin_combined = sin[pos_ids].flatten(1)
686
@@ -737,9 +739,10 @@ class Qwen2_5_VisionTransformer(nn.Module):
739
window_index_thw, cu_seqlens_window_thw = self.get_window_index_thw(t, h, w)
740
cos_thw, sin_thw = self.rotary_pos_emb_thw(t, h, w)
741
740
- cos_thw = cos_thw[window_index_thw, :, :]
742
+ window_index_thw_dev = window_index_thw.to(cos_thw.device, non_blocking=True)
743
+ cos_thw = cos_thw[window_index_thw_dev, :, :]
744
cos_thw = cos_thw.flatten(start_dim=0, end_dim=1)
742
- sin_thw = sin_thw[window_index_thw, :, :]
745
+ sin_thw = sin_thw[window_index_thw_dev, :, :]
746
sin_thw = sin_thw.flatten(start_dim=0, end_dim=1)
747
748
cu_seqlens_thw = torch.repeat_interleave(
@@ -1036,7 +1039,7 @@ class Qwen2_5_VLMultiModalProcessor(Qwen2VLMultiModalProcessor):
1039
) -> Mapping[str, MultiModalFieldConfig]:
1040
return dict(
1041
**super()._get_mm_fields_config(hf_inputs, hf_processor_mm_kwargs),
1039
- second_per_grid_ts=MultiModalFieldConfig.batched("video"),
1042
+ second_per_grid_ts=MultiModalFieldConfig.batched("video", keep_on_cpu=True),
1043
)
1044
1045
def _call_hf_processor(
@@ -1361,7 +1364,9 @@ class Qwen2_5_VLForConditionalGeneration(
1364
grid_thw_list = grid_thw.tolist()
1365
image_embeds_out = []
1366
for emb, size in zip(image_embeds_split, grid_thw_list):
1364
- positions = compute_mrope_for_media(size, merge_size).to(emb.device)
1367
+ positions = compute_mrope_for_media(size, merge_size).to(
1368
+ emb.device, non_blocking=True
1369
+ )
1370
emb = torch.cat([emb, positions], dim=1)
1371
image_embeds_out.append(emb)
1372
image_embeds_split = image_embeds_out
@@ -1443,7 +1448,7 @@ class Qwen2_5_VLForConditionalGeneration(
1448
merge_size,
1449
tokens_per_second=tokens_per_second,
1450
video_second_per_grid=video_second_per_grid_t.item(),
1446
- ).to(emb.device)
1451
+ ).to(emb.device, non_blocking=True)
1452
1453
emb = emb[retention_mask]
1454
positions = positions[retention_mask]
@@ -1488,8 +1493,8 @@ class Qwen2_5_VLForConditionalGeneration(
1493
else mrope_positions.device
1494
)
1495
1491
- # Tensors
1492
- input_ids_t = torch.as_tensor(input_ids, device=device, dtype=torch.long)
1496
+ # Tensors.
1497
+ input_ids_t = async_tensor_h2d(input_ids, dtype=torch.long, device=device)
1498
1499
mm_embeddings_out = [mm[:, :-4] for mm in multimodal_embeddings]
1500
mm_embeddings_pos = [
vllm/model_executor/models/qwen2_audio.py
+3
-3
@@ -439,9 +439,9 @@ class Qwen2AudioForConditionalGeneration(nn.Module, SupportsMultiModal, Supports
439
num_audios, max_audio_tokens, embed_dim = audio_features.shape
440
audio_output_lengths = audio_output_lengths.unsqueeze(1)
441
audio_features_mask = (
442
- torch.arange(max_audio_tokens)
443
- .expand(num_audios, max_audio_tokens)
444
- .to(audio_output_lengths.device)
442
+ torch.arange(max_audio_tokens, device=audio_output_lengths.device).expand(
443
+ num_audios, max_audio_tokens
444
+ )
445
< audio_output_lengths
446
)
447
masked_audio_features = audio_features[audio_features_mask].view(-1, embed_dim)
vllm/model_executor/models/qwen2_vl.py
+1
@@ -634,6 +634,7 @@ class Qwen2VisionTransformer(nn.Module):
634
# Use pre-computed cos_sin_cache from RotaryEmbedding
635
cos, sin = self.rotary_pos_emb.get_cos_sin(max_grid_size)
636
637
+ pos_ids = pos_ids.to(cos.device, non_blocking=True)
638
cos_combined = cos[pos_ids].flatten(1)
639
sin_combined = sin[pos_ids].flatten(1)
640
return cos_combined, sin_combined
vllm/model_executor/models/qwen3_omni_moe_thinker.py
+5
-3
@@ -80,6 +80,7 @@ from vllm.multimodal.processing.processor import (
80
)
81
from vllm.sequence import IntermediateTensors
82
from vllm.transformers_utils.processor import cached_processor_from_config
83
+from vllm.utils.torch_utils import async_tensor_h2d
84
from vllm.v1.attention.backends.registry import AttentionBackendEnum
85
86
from .interfaces import (
@@ -502,9 +503,9 @@ class Qwen3OmniMoeAudioEncoder(nn.Module):
503
cu_chunk_lens.extend([window_aftercnn] * num_full_chunks)
504
if remainder:
505
cu_chunk_lens.append(remainder)
505
- cu_seqlens = torch.tensor(cu_chunk_lens, device=aftercnn_lens.device).cumsum(
506
- -1, dtype=torch.int32
507
- )
506
+ cu_seqlens = async_tensor_h2d(
507
+ cu_chunk_lens, dtype=torch.int32, device=aftercnn_lens.device
508
+ ).cumsum(-1, dtype=torch.int32)
509
510
max_seqlen = self.compute_attn_mask_seqlen(cu_seqlens)
511
@@ -861,6 +862,7 @@ class Qwen3Omni_VisionTransformer(nn.Module):
862
# Use pre-computed cos_sin_cache from RotaryEmbedding
863
cos, sin = self.rotary_pos_emb.get_cos_sin(max_grid_size)
864
865
+ pos_ids = pos_ids.to(cos.device, non_blocking=True)
866
cos_combined = cos[pos_ids].flatten(1)
867
sin_combined = sin[pos_ids].flatten(1)
868
vllm/model_executor/models/qwen3_vl.py
+6
-3
@@ -101,6 +101,7 @@ from vllm.utils.collection_utils import is_list_of
101
from vllm.utils.math_utils import round_up
102
from vllm.v1.worker.encoder_cudagraph_defs import EncoderCudaGraphReplayBuffers
103
104
+from ...utils.torch_utils import async_tensor_h2d
105
from .interfaces import (
106
MultiModalEmbeddings,
107
SupportsEagle,
@@ -2157,7 +2158,9 @@ class Qwen3VLForConditionalGeneration(
2158
grid_thw_list = grid_thw.tolist()
2159
image_embeds_out = []
2160
for emb, size in zip(image_embeds_split, grid_thw_list):
2160
- positions = compute_mrope_for_media(size, merge_size).to(emb.device)
2161
+ positions = compute_mrope_for_media(size, merge_size).to(
2162
+ emb.device, non_blocking=True
2163
+ )
2164
positions = torch.cat(
2165
[
2166
positions,
@@ -2388,7 +2391,7 @@ class Qwen3VLForConditionalGeneration(
2391
input_tokens=unpruned_token_ids,
2392
mm_features=[mm_feature],
2393
)[0]
2391
- .to(device)
2394
+ .to(device, non_blocking=True)
2395
.permute(1, 0)
2396
)
2397
full_is_video_embed = unpruned_token_ids_tensor == embed_token_id
@@ -2636,7 +2639,7 @@ class Qwen3VLForConditionalGeneration(
2639
)
2640
2641
# Tensors
2639
- input_ids_t = torch.as_tensor(input_ids, device=device, dtype=torch.long)
2642
+ input_ids_t = async_tensor_h2d(input_ids, device=device, dtype=torch.long)
2643
2644
mm_embeddings_out = []
2645
mm_embeddings_pos = []
vllm/model_executor/models/transformers/multimodal.py
+3
-1
@@ -152,7 +152,9 @@ class MultiModalProcessor(BaseMultiModalProcessor[MultiModalProcessingInfo]):
152
# Keep these as batched, as they always have batch size as first dim
153
mm_fields["image_grid_thw"] = MultiModalFieldConfig.batched("image")
154
mm_fields["video_grid_thw"] = MultiModalFieldConfig.batched("image")
155
- mm_fields["num_image_patches"] = MultiModalFieldConfig.batched("image")
155
+ mm_fields["num_image_patches"] = MultiModalFieldConfig.batched(
156
+ "image", keep_on_cpu=True
157
+ )
158
return mm_fields
159
160
def _get_hf_mm_data(
vllm/model_executor/models/utils.py
+7
-15
@@ -30,10 +30,8 @@ from vllm.model_executor.models.interfaces import supports_any_eagle
30
from vllm.multimodal import NestedTensors
31
from vllm.sequence import IntermediateTensors
32
from vllm.utils.math_utils import cdiv
33
-from vllm.utils.platform_utils import (
34
- is_pin_memory_available,
35
-)
33
from vllm.utils.torch_utils import (
34
+ async_tensor_h2d,
35
direct_register_custom_op,
36
)
37
@@ -498,10 +496,9 @@ def isin_list(
496
elements: torch.Tensor,
497
test_elements_list: list[int],
498
) -> torch.Tensor:
501
- test_elements = torch.tensor(
502
- test_elements_list,
503
- pin_memory=is_pin_memory_available(),
504
- ).to(device=elements.device, non_blocking=True)
499
+ test_elements = async_tensor_h2d(
500
+ test_elements_list, dtype=torch.int64, device=elements.device
501
+ )
502
503
return torch.isin(elements, test_elements)
504
@@ -772,14 +769,9 @@ def extract_layer_index(layer_name: str, num_attn_module: int = 1) -> int:
769
return layer_index
770
771
775
-def cast_overflow_tensors(
776
- tensors: torch.Tensor,
777
- offset: float = 1000,
778
-) -> torch.Tensor:
779
- if tensors.isinf().any() or tensors.isnan().any():
780
- clamp_value = torch.finfo(tensors.dtype).max - offset
781
- tensors = torch.clamp(tensors, min=-clamp_value, max=clamp_value)
782
- return tensors
772
+def cast_overflow_tensors(tensors: torch.Tensor, offset: float = 1000) -> torch.Tensor:
773
+ clamp_value = torch.finfo(tensors.dtype).max - offset
774
+ return torch.clamp(tensors, min=-clamp_value, max=clamp_value)
775
776
777
def fast_topk(
vllm/multimodal/evs.py
+1
-1
@@ -85,7 +85,7 @@ def compute_retention_mask(
85
topk_indices = order[:retain_num_tokens]
86
87
retention_mask = torch.zeros_like(dissimilarity_flat, dtype=torch.bool)
88
- retention_mask[topk_indices] = True
88
+ retention_mask.index_fill_(0, topk_indices, True)
89
retention_mask = retention_mask.reshape(dissimilarity.size())
90
91
mask = retention_mask.view(-1) # "T H W -> (T H W)"
vllm/v1/sample/thinking_budget_state.py
+25
-29
@@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Any
6
7
import torch
8
9
+from vllm.utils.torch_utils import async_tensor_h2d
10
from vllm.v1.sample.logits_processor.interface import (
11
BatchUpdate,
12
MoveDirectionality,
@@ -65,22 +66,9 @@ class ThinkingBudgetStateHolder:
66
self.cu_num_tokens: dict[int, int] = {}
67
68
if self.num_spec_tokens > 0:
68
- self.mask = torch.zeros(
69
- max_num_reqs * (self.num_spec_tokens + 1),
70
- dtype=torch.bool,
71
- device=device,
72
- )
73
- self.force_token_ids = torch.full(
74
- (max_num_reqs * (self.num_spec_tokens + 1),),
75
- -1,
76
- dtype=torch.long,
77
- device=device,
78
- )
69
+ self._mask_capacity = max_num_reqs * (self.num_spec_tokens + 1)
70
else:
80
- self.mask = torch.zeros(max_num_reqs, dtype=torch.bool, device=device)
81
- self.force_token_ids = torch.full(
82
- (max_num_reqs,), -1, dtype=torch.long, device=device
83
- )
71
+ self._mask_capacity = max_num_reqs
72
73
def has_tracked_requests(self) -> bool:
74
"""True when ``sync_batch`` has state for a ``thinking_token_budget`` row.
@@ -454,7 +442,6 @@ class ThinkingBudgetStateHolder:
442
predict_bonus_token: bool,
443
spec_token_ids_for_layout: list[list[int]],
444
) -> torch.Tensor:
457
- self.mask[:] = False
445
cumulative_total = 0
446
self.cu_num_tokens.clear()
447
@@ -474,6 +461,11 @@ class ThinkingBudgetStateHolder:
461
else:
462
cumulative_total += 1
463
464
+ # Build the active index / forced-token lists entirely on CPU so we
465
+ # avoid per-iteration scalar sync writes to GPU tensors.
466
+ active_indices_cpu: list[int] = []
467
+ force_tokens_cpu: list[int] = []
468
+
469
for seq_idx in sorted(self._state.keys()):
470
if seq_idx not in self.cu_num_tokens:
471
continue
@@ -502,9 +494,12 @@ class ThinkingBudgetStateHolder:
494
for force_idx in force_index:
495
if end_count < len(self.think_end_token_ids):
496
mask_idx = self.cu_num_tokens[seq_idx] + force_idx
505
- if mask_idx < len(self.mask) and mask_idx < logits.shape[0]:
506
- self.mask[mask_idx] = True
507
- self.force_token_ids[mask_idx] = (
497
+ if (
498
+ mask_idx < self._mask_capacity
499
+ and mask_idx < logits.shape[0]
500
+ ):
501
+ active_indices_cpu.append(mask_idx)
502
+ force_tokens_cpu.append(
503
self.think_end_token_ids[end_count]
504
)
505
if predict_bonus_token:
@@ -514,15 +509,16 @@ class ThinkingBudgetStateHolder:
509
else:
510
state["bonus_token_forced"] = True
511
517
- has_active_thinking = any(
518
- state.get("in_end", False) for state in self._state.values()
519
- )
520
-
521
- if has_active_thinking:
522
- active_indices = self.mask.nonzero(as_tuple=False).view(-1)
523
-
524
- if len(active_indices) > 0:
525
- force_tokens = self.force_token_ids[active_indices]
526
- logits[active_indices, force_tokens] = 1e9
512
+ if active_indices_cpu:
513
+ device = logits.device
514
+ active_indices = async_tensor_h2d(
515
+ active_indices_cpu, dtype=torch.long, device=device
516
+ )
517
+ force_tokens = async_tensor_h2d(
518
+ force_tokens_cpu, dtype=torch.long, device=device
519
+ )
520
+ # Avoid CPU->GPU sync.
521
+ fill = logits.new_full((len(active_indices_cpu),), 1e9)
522
+ logits.index_put_((active_indices, force_tokens), fill)
523
524
return logits
vllm/v1/worker/gpu_model_runner.py
+8
-8
@@ -1765,8 +1765,6 @@ class GPUModelRunner(
1765
self.input_batch.prev_sampled_token_ids[:num_common_tokens, 0],
1766
non_blocking=True,
1767
)
1768
- if self.enable_prompt_embeds:
1769
- self.is_token_ids.gpu[:num_common_tokens] = True
1768
return
1769
# Upload the index tensors asynchronously so the scatter can be non-blocking.
1770
sampled_tokens_index_tensor = torch.tensor(
@@ -3408,13 +3406,12 @@ class GPUModelRunner(
3406
# If a batch only has token ids, then including the embedding layer
3407
# in the CUDA graph will be more performant (like in the else case
3408
# below).
3411
- token_ids_idx = (
3412
- self.is_token_ids.gpu[:num_scheduled_tokens]
3413
- .nonzero(as_tuple=False)
3414
- .squeeze(1)
3415
- )
3409
+ is_token_ids = self.is_token_ids.np[:num_scheduled_tokens]
3410
+ token_ids_idx_np = np.nonzero(is_token_ids)[0]
3411
# Some tokens ids may need to become embeds
3417
- if token_ids_idx.numel() > 0:
3412
+ if token_ids_idx_np.size > 0:
3413
+ token_ids_idx = torch.from_numpy(token_ids_idx_np)
3414
+ token_ids_idx = token_ids_idx.to(self.device, non_blocking=True)
3415
token_ids = self.input_ids.gpu[token_ids_idx]
3416
tokens_to_embeds = self.model.embed_input_ids(input_ids=token_ids)
3417
self.inputs_embeds.gpu[token_ids_idx] = tokens_to_embeds
@@ -4601,6 +4598,9 @@ class GPUModelRunner(
4598
# appending a placeholder (-1) token id.
4599
if (req_state := self.requests.get(req_id)) is not None:
4600
req_state.output_token_ids.append(-1)
4601
+ pos = self.input_batch.num_tokens_no_spec[i]
4602
+ self.input_batch.is_token_ids[i, pos] = True
4603
+ self.input_batch.num_tokens_no_spec[i] = pos + 1
4604
self.input_batch.prev_req_id_to_index = prev_req_id_to_index
4605
4606
def take_draft_token_ids(self) -> DraftTokenIds | None: