[Core] Ensure memory is pinned prior to async h2d copy (#45424)

Signed-off-by: Nick Hill <nickhill123@gmail.com>

Nick Hill committed Jun 20, 2026 at 20:02 UTC 7df3d7dada840c68b85b26b79de7f59f676d58e3
49 files changed +254 -264
tests/v1/logits_processors/test_correctness.py
-2
@@ -145,7 +145,6 @@ def _generate_fake_sampling_metadata(
145 vllm_config.scheduler_config.max_num_seqs,
146 num_spec,
147 device,
148 - PIN_MEMORY_AVAILABLE,
148 )
149 fake_sampling_metadata = SamplingMetadata(
150 temperature=torch.full((batch_size,), 0.0),
@@ -880,7 +879,6 @@ def test_maybe_create_thinking_budget_holder_without_reasoning():
879 cfg.scheduler_config.max_num_seqs,
880 0,
881 torch.device("cpu"),
883 - False,
882 )
883 is None
884 )
tests/v1/streaming_input/test_gpu_model_runner_streaming.py
-1
@@ -35,7 +35,6 @@ def mock_model_runner_with_input_batch():
35 max_model_len=1024,
36 max_num_batched_tokens=1024,
37 device="cpu",
38 - pin_memory=False,
38 vocab_size=32000,
39 block_sizes=[16],
40 kernel_block_sizes=[16],
tests/v1/worker/test_gpu_input_batch.py
-6
@@ -10,7 +10,6 @@ import torch
10
11 from vllm.platforms import current_platform
12 from vllm.sampling_params import SamplingParams
13 -from vllm.utils.platform_utils import is_pin_memory_available
13 from vllm.utils.torch_utils import make_tensor_with_pad
14 from vllm.v1.pool.metadata import PoolingMetadata
15 from vllm.v1.sample.logits_processor import LogitsProcessors
@@ -236,7 +235,6 @@ def test_sampling_metadata_in_input_batch(device: str, batch_size: int):
235 max_model_len=1024,
236 max_num_batched_tokens=1024,
237 device=torch.device(device),
239 - pin_memory=is_pin_memory_available(),
238 vocab_size=1024,
239 block_sizes=[1],
240 kernel_block_sizes=[1],
@@ -331,7 +329,6 @@ def test_swap_states_in_input_batch(device: str, batch_size: int, swap_list: lis
329 max_model_len=1024,
330 max_num_batched_tokens=1024,
331 device=torch.device(device),
334 - pin_memory=is_pin_memory_available(),
332 vocab_size=1024,
333 block_sizes=[1],
334 kernel_block_sizes=[1],
@@ -341,7 +338,6 @@ def test_swap_states_in_input_batch(device: str, batch_size: int, swap_list: lis
338 max_model_len=1024,
339 max_num_batched_tokens=1024,
340 device=torch.device(device),
344 - pin_memory=is_pin_memory_available(),
341 vocab_size=1024,
342 block_sizes=[1],
343 kernel_block_sizes=[1],
@@ -410,7 +406,6 @@ def test_pooling_prompt_lens_not_aliased(device: str):
406 max_model_len=MAX_PROMPT_SIZE + NUM_OUTPUT_TOKENS,
407 max_num_batched_tokens=batch_size * (MAX_PROMPT_SIZE + NUM_OUTPUT_TOKENS),
408 device=torch.device(device),
413 - pin_memory=is_pin_memory_available(),
409 vocab_size=VOCAB_SIZE,
410 block_sizes=[16],
411 kernel_block_sizes=[16],
@@ -459,7 +454,6 @@ def test_pooling_metadata_token_id_buffers(
454 max_model_len=MAX_PROMPT_SIZE + NUM_OUTPUT_TOKENS,
455 max_num_batched_tokens=MAX_PROMPT_SIZE + NUM_OUTPUT_TOKENS,
456 device=torch.device("cpu"),
462 - pin_memory=False,
457 vocab_size=VOCAB_SIZE,
458 block_sizes=[16],
459 kernel_block_sizes=[16],
tests/v1/worker/test_gpu_model_runner.py
-4
@@ -85,7 +85,6 @@ def initialize_kv_cache(runner: GPUModelRunner):
85 max_model_len=runner.max_model_len,
86 max_num_batched_tokens=runner.max_num_tokens,
87 device=runner.device,
88 - pin_memory=runner.pin_memory,
88 vocab_size=runner.model_config.get_vocab_size(),
89 block_sizes=[kv_cache_config.kv_cache_groups[0].kv_cache_spec.block_size],
90 kernel_block_sizes=[
@@ -1405,7 +1404,6 @@ def test_input_batch_with_kernel_block_sizes():
1404 max_model_len = 512
1405 max_num_batched_tokens = 512
1406 device = torch.device(DEVICE_TYPE)
1408 - pin_memory = False
1407 vocab_size = 50272
1408
1409 # Test with different kernel block sizes
@@ -1417,7 +1415,6 @@ def test_input_batch_with_kernel_block_sizes():
1415 max_model_len=max_model_len,
1416 max_num_batched_tokens=max_num_batched_tokens,
1417 device=device,
1420 - pin_memory=pin_memory,
1418 vocab_size=vocab_size,
1419 block_sizes=block_sizes,
1420 kernel_block_sizes=kernel_block_sizes,
@@ -1478,7 +1475,6 @@ def test_hybrid_cache_integration(default_vllm_config, dist_init):
1475 max_model_len=runner.max_model_len,
1476 max_num_batched_tokens=runner.max_num_tokens,
1477 device=runner.device,
1481 - pin_memory=runner.pin_memory,
1478 vocab_size=runner.model_config.get_vocab_size(),
1479 block_sizes=[kv_cache_config.kv_cache_groups[0].kv_cache_spec.block_size],
1480 kernel_block_sizes=[16],
vllm/device_allocator/cumem.py
+2 -2
@@ -18,8 +18,8 @@ import torch
18
19 from vllm.device_allocator import AllocationData, HandleType
20 from vllm.logger import init_logger
21 -from vllm.utils.platform_utils import is_pin_memory_available
21 from vllm.utils.system_utils import find_loaded_library
22 +from vllm.utils.torch_utils import PIN_MEMORY
23
24 logger = init_logger(__name__)
25
@@ -196,7 +196,7 @@ class CuMemAllocator:
196 size_in_bytes,
197 dtype=torch.uint8,
198 device="cpu",
199 - pin_memory=is_pin_memory_available(),
199 + pin_memory=PIN_MEMORY,
200 )
201 cpu_ptr = cpu_backup_tensor.data_ptr()
202 libcudart.cudaMemcpy(cpu_ptr, ptr, size_in_bytes)
vllm/device_allocator/xpumem.py
+2 -2
@@ -11,7 +11,7 @@ import torch
11
12 from vllm.device_allocator import AllocationData, HandleType
13 from vllm.logger import init_logger
14 -from vllm.utils.platform_utils import is_pin_memory_available
14 +from vllm.utils.torch_utils import PIN_MEMORY
15
16 logger = init_logger(__name__)
17
@@ -188,7 +188,7 @@ class XpuMemAllocator:
188 size_in_bytes,
189 dtype=torch.uint8,
190 device="cpu",
191 - pin_memory=is_pin_memory_available(),
191 + pin_memory=PIN_MEMORY,
192 )
193 cpu_ptr = cpu_backup_tensor.data_ptr()
194 _xpu_memcpy_sync(
vllm/lora/lora_model.py
+2 -2
@@ -17,7 +17,7 @@ from vllm.lora.utils import (
17 )
18 from vllm.model_executor.model_loader.tensorizer import TensorizerConfig
19 from vllm.model_executor.models.utils import WeightsMapper
20 -from vllm.utils.platform_utils import is_pin_memory_available
20 +from vllm.utils.torch_utils import PIN_MEMORY
21
22 logger = init_logger(__name__)
23
@@ -126,7 +126,7 @@ class LoRAModel:
126 skip_prefixes: list[str] | None = None,
127 ) -> "LoRAModel":
128 """Create a LoRAModel from a dictionary of tensors."""
129 - pin_memory = str(device) == "cpu" and is_pin_memory_available()
129 + pin_memory = str(device) == "cpu" and PIN_MEMORY
130 loras: dict[str, LoRALayerWeights] = {}
131 for tensor_name, tensor in tensors.items():
132 if is_base_embedding_weights(tensor_name):
vllm/lora/lora_weights.py
+2 -2
@@ -7,7 +7,7 @@ import torch
7 import torch.types
8
9 from vllm.lora.peft_helper import PEFTHelper
10 -from vllm.utils.platform_utils import is_pin_memory_available
10 +from vllm.utils.torch_utils import PIN_MEMORY
11
12
13 class LoRALayerWeights:
@@ -79,7 +79,7 @@ class LoRALayerWeights:
79 dtype: torch.dtype,
80 device: torch.types.Device,
81 ) -> "LoRALayerWeights":
82 - pin_memory = str(device) == "cpu" and is_pin_memory_available()
82 + pin_memory = str(device) == "cpu" and PIN_MEMORY
83 lora_a = torch.zeros(
84 [rank, input_dim], dtype=dtype, device=device, pin_memory=pin_memory
85 )
vllm/lora/model_manager.py
+2 -2
@@ -42,7 +42,7 @@ from vllm.model_executor.models.utils import PPMissingLayer
42 from vllm.multimodal import MULTIMODAL_REGISTRY
43 from vllm.multimodal.encoder_budget import MultiModalBudget
44 from vllm.utils.cache import LRUCache
45 -from vllm.utils.platform_utils import is_pin_memory_available
45 +from vllm.utils.torch_utils import PIN_MEMORY
46
47 logger = init_logger(__name__)
48
@@ -801,7 +801,7 @@ class LoRAModelManager:
801 # 2. The weight packing above (e.g., pack_moe) may invalidate the
802 # pin_memory allocation, so we execute it after packing.
803
804 - pin_memory = str(lora_device) == "cpu" and is_pin_memory_available()
804 + pin_memory = str(lora_device) == "cpu" and PIN_MEMORY
805 if pin_memory:
806 for lora in lora_model.loras.values():
807 if isinstance(lora.lora_a, list):
vllm/model_executor/layers/attention/mla_attention.py
+10 -8
@@ -1684,12 +1684,13 @@ class MLACommonMetadataBuilder(AttentionMetadataBuilder[M]):
1684 # [[0, 0, 0, 0], [256, 256, 256, 256], [512, 512, 512, 512]]
1685 # Note(simon): this is done in CPU because of downstream's
1686 # of `to_list`.
1687 - chunk_starts = (
1687 + chunk_starts = torch.empty(
1688 + num_chunks, num_prefills, dtype=torch.int32, pin_memory=True
1689 + ).copy_(
1690 torch.arange(num_chunks, dtype=torch.int32)
1691 + .multiply_(max_context_chunk)
1692 .unsqueeze(1)
1690 - .expand(-1, num_prefills)
1691 - * max_context_chunk
1692 - ).pin_memory()
1693 + )
1694 chunk_ends = torch.min(
1695 context_lens_cpu.unsqueeze(0), chunk_starts + max_context_chunk
1696 )
@@ -1746,12 +1747,13 @@ class MLACommonMetadataBuilder(AttentionMetadataBuilder[M]):
1747 )
1748 * self.dcp_local_block_size
1749 )
1749 - local_chunk_starts = (
1750 + local_chunk_starts = torch.empty(
1751 + num_chunks, num_prefills, dtype=torch.int32, pin_memory=True
1752 + ).copy_(
1753 torch.arange(num_chunks, dtype=torch.int32)
1754 + .multiply_(padded_local_max_context_chunk_across_ranks)
1755 .unsqueeze(1)
1752 - .expand(-1, num_prefills)
1753 - * padded_local_max_context_chunk_across_ranks
1754 - ).pin_memory()
1756 + )
1757 local_chunk_ends = torch.min(
1758 padded_local_context_lens_cpu.unsqueeze(0),
1759 local_chunk_starts
vllm/model_executor/layers/attention/mm_encoder_attention.py
+2 -1
@@ -28,6 +28,7 @@ from vllm.utils.flashinfer import (
28 is_flashinfer_cudnn_fp8_prefill_attn_supported,
29 )
30 from vllm.utils.math_utils import round_up
31 +from vllm.utils.torch_utils import async_tensor_h2d
32 from vllm.v1.attention.backends.fa_utils import get_flash_attn_version
33 from vllm.v1.attention.backends.registry import AttentionBackendEnum
34 from vllm.v1.attention.ops.vit_attn_wrappers import (
@@ -311,7 +312,7 @@ class MMEncoderAttention(CustomOp):
312 )
313 cu_seqlens = np.concatenate([cu_seqlens_qko, cu_seqlens_v])
314
314 - cu_seqlens = torch.from_numpy(cu_seqlens).to(device, non_blocking=True)
315 + cu_seqlens = async_tensor_h2d(cu_seqlens, device=device)
316 return cu_seqlens
317
318 def __init__(
vllm/model_executor/layers/pooler/seqwise/methods.py
+8 -8
@@ -10,6 +10,7 @@ import torch.nn as nn
10 from vllm.config.pooler import SequencePoolingType
11 from vllm.model_executor.layers.pooler import PoolingParamsUpdate
12 from vllm.tasks import PoolingTask
13 +from vllm.utils.torch_utils import async_tensor_h2d
14 from vllm.v1.pool.metadata import PoolingMetadata
15
16 SequencePoolingMethodOutput: TypeAlias = torch.Tensor | list[torch.Tensor]
@@ -74,15 +75,14 @@ class MeanPool(SequencePoolingMethod):
75 # early return for empty batch
76 return hidden_states.new_empty((0, hidden_size), dtype=torch.float32)
77
77 - # Build segment_ids on CPU so repeat_interleave doesn't need to sync
78 - # GPU->CPU to learn its data-dependent output length, then upload
79 - # non-blocking. eg. [2, 1, 3] -> [0, 0, 1, 2, 2, 2]
78 + prompt_lens = async_tensor_h2d(
79 + prompt_lens_cpu, device=hidden_states.device, dtype=torch.int64
80 + )
81 + # eg. [2, 1, 3] -> [0, 0, 1, 2, 2, 2]
82 segment_ids = torch.repeat_interleave(
81 - torch.arange(num_seqs, dtype=torch.long),
82 - prompt_lens_cpu,
83 - ).to(hidden_states.device, non_blocking=True)
84 - prompt_lens = prompt_lens_cpu.to(
85 - hidden_states.device, dtype=torch.int64, non_blocking=True
83 + torch.arange(num_seqs, device=hidden_states.device, dtype=torch.long),
84 + prompt_lens,
85 + output_size=int(prompt_lens_cpu.sum()),
86 )
87 segment_sums = torch.zeros(
88 (num_seqs, hidden_size),
vllm/model_executor/models/moonvit.py
+3 -2
@@ -66,6 +66,7 @@ from vllm.model_executor.models.utils import maybe_prefix
66 from vllm.model_executor.models.vision import is_vit_use_data_parallel
67 from vllm.platforms import current_platform
68 from vllm.transformers_utils.configs.moonvit import MoonViTConfig
69 +from vllm.utils.torch_utils import async_tensor_h2d
70
71
72 def _apply_rope_input_validation(x, freqs_cis):
@@ -758,7 +759,7 @@ class MoonVitPretrainedModel(PreTrainedModel):
759 ),
760 ]
761 )
761 - metadata["cu_seqlens"] = torch.from_numpy(cu_seqlens_np).to(device)
762 + metadata["cu_seqlens"] = async_tensor_h2d(cu_seqlens_np, device=device)
763
764 if max_seqlen_override is not None:
765 max_seqlen_val = int(max_seqlen_override)
@@ -770,7 +771,7 @@ class MoonVitPretrainedModel(PreTrainedModel):
771 metadata["max_seqlen"] = torch.tensor(max_seqlen_val, dtype=torch.int32)
772
773 gather_idx_np = _build_merge_gather_idx(grid_pairs, self.merge_kernel_size)
773 - metadata["merge_gather_idx"] = torch.from_numpy(gather_idx_np).to(device)
774 + metadata["merge_gather_idx"] = async_tensor_h2d(gather_idx_np, device=device)
775
776 return metadata
777
vllm/model_executor/models/qwen2_5_vl.py
+2 -3
@@ -83,9 +83,8 @@ from vllm.multimodal.parse import MultiModalDataItems
83 from vllm.multimodal.processing import PromptReplacement, PromptUpdate
84 from vllm.platforms import current_platform
85 from vllm.sequence import IntermediateTensors
86 -from vllm.utils.platform_utils import is_pin_memory_available
86 from vllm.utils.tensor_schema import TensorSchema, TensorShape
88 -from vllm.utils.torch_utils import async_tensor_h2d
87 +from vllm.utils.torch_utils import PIN_MEMORY, async_tensor_h2d
88 from vllm.v1.attention.backends.registry import AttentionBackendEnum
89 from vllm.v1.worker.encoder_cudagraph_defs import EncoderCudaGraphReplayBuffers
90
@@ -825,7 +824,7 @@ class Qwen2_5_VisionTransformer(nn.Module):
824 @staticmethod
825 def invert_permutation(perm: torch.Tensor) -> torch.Tensor:
826 # building the inverse permutation in O(n) time
828 - inv = torch.empty_like(perm, pin_memory=is_pin_memory_available())
827 + inv = torch.empty_like(perm, pin_memory=PIN_MEMORY)
828 inv[perm] = torch.arange(perm.numel(), device=perm.device, dtype=perm.dtype)
829 return inv
830
vllm/models/deepseek_v4/sparse_mla.py
+2 -1
@@ -13,6 +13,7 @@ from vllm.config.cache import CacheDType
13 from vllm.platforms.interface import DeviceCapability
14 from vllm.triton_utils import tl, triton
15 from vllm.utils.math_utils import cdiv
16 +from vllm.utils.torch_utils import np_to_pinned_tensor
17 from vllm.v1.attention.backend import (
18 AttentionBackend,
19 AttentionCGSupport,
@@ -207,7 +208,7 @@ class DeepseekV4FlashMLAMetadataBuilder(
208 # Zero-fill for cudagraphs
209 self.req_id_per_token_buffer.fill_(0)
210 self.req_id_per_token_buffer[: req_id_per_token.shape[0]].copy_(
210 - torch.from_numpy(req_id_per_token), non_blocking=True
211 + np_to_pinned_tensor(req_id_per_token), non_blocking=True
212 )
213 req_id_per_token = self.req_id_per_token_buffer[:num_tokens]
214
vllm/multimodal/inputs.py
+14 -2
@@ -488,7 +488,13 @@ class MultiModalBatchedField(BaseMultiModalField):
488 # An optimization when `batch` contains only one tensor:
489 # - produce exactly same result as `torch.stack(batch)`
490 # - will achieve zero-copy if the tensor is contiguous
491 - return batch[0].unsqueeze(0).contiguous()
491 + out = batch[0].unsqueeze(0)
492 + if not pin_memory:
493 + return out.contiguous()
494 + # Avoid extra copy - pinning unpinned memory will make it contiguous
495 + if not out.is_contiguous() and out.is_pinned():
496 + out = out.contiguous()
497 + return out.pin_memory()
498 first_shape = batch[0].shape
499 if all(elem.shape == first_shape for elem in batch):
500 out = torch.empty(
@@ -538,7 +544,13 @@ class MultiModalFlatField(BaseMultiModalField):
544 # An optimization when `batch` contains only one tensor:
545 # - produce exactly same result as `torch.concat(batch)`
546 # - will achieve zero-copy if the tensor is contiguous
541 - return batch[0].contiguous()
547 + out = batch[0]
548 + if not pin_memory:
549 + return out.contiguous()
550 + # Avoid extra copy - pinning unpinned memory will make it contiguous
551 + if not out.is_contiguous() and out.is_pinned():
552 + out = out.contiguous()
553 + return out.pin_memory()
554
555 dim = self.dim + (self.dim < 0) * len(batch[0].shape)
556
vllm/platforms/__init__.py
+1 -2
@@ -9,7 +9,6 @@ from typing import TYPE_CHECKING
9 from vllm import envs
10 from vllm.plugins import PLATFORM_PLUGINS_GROUP, load_plugins_by_group
11 from vllm.utils.import_utils import resolve_obj_by_qualname
12 -from vllm.utils.torch_utils import supports_xccl
12
13 from .interface import CpuArchEnum, Platform, PlatformEnum
14
@@ -135,7 +134,7 @@ def xpu_platform_plugin() -> str | None:
134 try:
135 import torch
136
138 - if supports_xccl():
137 + if torch.distributed.is_xccl_available():
138 dist_backend = "xccl"
139 from vllm.platforms.xpu import XPUPlatform
140
vllm/platforms/cuda.py
+2 -1
@@ -23,7 +23,6 @@ import vllm._C_stable_libtorch # noqa
23 import vllm.envs as envs
24 from vllm.logger import init_logger
25 from vllm.utils.import_utils import import_pynvml
26 -from vllm.utils.torch_utils import is_quantized_kv_cache
26 from vllm.v1.attention.backends.registry import AttentionBackendEnum
27
28 from .interface import DeviceCapability, Platform, PlatformEnum, in_wsl
@@ -88,6 +87,8 @@ def _get_backend_priorities(
87 kv_cache_dtype: CacheDType | None = None,
88 ) -> list[AttentionBackendEnum]:
89 """Get backend priorities with lazy import to avoid circular dependency."""
90 + from vllm.utils.torch_utils import is_quantized_kv_cache
91 +
92 if use_mla:
93 if device_capability.major == 10:
94 # Sparse MLA backend priorities
vllm/platforms/xpu.py
+6 -6
@@ -14,7 +14,6 @@ import vllm_xpu_kernels._xpu_C # noqa
14
15 import vllm.envs as envs
16 from vllm.logger import init_logger
17 -from vllm.utils.torch_utils import supports_xpu_graph
17 from vllm.v1.attention.backends.registry import AttentionBackendEnum
18
19 from .interface import DeviceCapability, Platform, PlatformEnum
@@ -178,8 +177,6 @@ class XPUPlatform(Platform):
177
178 @classmethod
179 def check_and_update_config(cls, vllm_config: VllmConfig) -> None:
181 - parallel_config = vllm_config.parallel_config
182 -
180 # lazy import to avoid circular import
181 from vllm.config import CUDAGraphMode
182
@@ -190,6 +187,10 @@ class XPUPlatform(Platform):
187 attention_config = vllm_config.attention_config
188 if attention_config.backend is None:
189 attention_config.backend = AttentionBackendEnum.FLASH_ATTN
190 +
191 + # lazy import to avoid circular import
192 + from vllm.utils.torch_utils import supports_xpu_graph
193 +
194 if not supports_xpu_graph():
195 compilation_config.cudagraph_mode = CUDAGraphMode.NONE
196 logger.warning(
@@ -324,9 +325,8 @@ class XPUPlatform(Platform):
325
326 @classmethod
327 def get_device_communicator_cls(cls) -> str:
327 - from vllm.utils.torch_utils import supports_xccl
328 -
329 - if not supports_xccl():
328 + if not torch.distributed.is_xccl_available():
329 + # Supports xccl with PyTorch versions >= 2.8.0.dev for XPU platform
330 logger.warning(
331 "xccl is not enabled in this torch build, communication"
332 " is not available."
vllm/utils/torch_utils.py
+18 -15
@@ -3,7 +3,6 @@
3 import contextlib
4 import importlib.metadata
5 import os
6 -import platform
6 import random
7 import threading
8 from collections.abc import Callable, Collection
@@ -18,6 +17,7 @@ from torch.library import Library, infer_schema
17
18 import vllm.envs as envs
19 from vllm.logger import init_logger
20 +from vllm.utils.platform_utils import is_pin_memory_available
21
22 if TYPE_CHECKING:
23 from vllm.config import ModelConfig
@@ -68,9 +68,7 @@ MODELOPT_TO_VLLM_KV_CACHE_DTYPE_MAP = {
68 T = TypeVar("T")
69
70
71 -# Pin memory in non-WSL case.
72 -# Logic duplicated here for now to avoid circular import.
73 -PIN_MEMORY = "microsoft" not in " ".join(platform.uname()).lower()
71 +PIN_MEMORY = is_pin_memory_available()
72
73
74 def is_quantized_kv_cache(kv_cache_dtype: str) -> bool:
@@ -606,14 +604,24 @@ def create_kv_caches_with_random(
604
605
606 def async_tensor_h2d(
609 - data: list,
610 - dtype: torch.dtype,
607 + data: list | np.ndarray | torch.Tensor,
608 device: str | torch.device,
612 - pin_memory: bool = PIN_MEMORY,
609 + dtype: torch.dtype | None = None,
610 ) -> torch.Tensor:
614 - """Asynchronously create a tensor and copy it from host to device."""
615 - t = torch.tensor(data, dtype=dtype, pin_memory=pin_memory, device="cpu")
616 - return t.to(device=device, non_blocking=True)
611 + """Copy list/numpy array/tensor async from host to device."""
612 + if isinstance(data, np.ndarray):
613 + data = torch.from_numpy(data)
614 + if isinstance(data, torch.Tensor):
615 + t = data.pin_memory() if PIN_MEMORY else data
616 + else:
617 + t = torch.tensor(data, dtype=dtype, pin_memory=PIN_MEMORY, device="cpu")
618 + assert t.is_cpu
619 + return t.to(device=device, dtype=dtype, non_blocking=True)
620 +
621 +
622 +def np_to_pinned_tensor(array: np.ndarray) -> torch.Tensor:
623 + t = torch.from_numpy(array)
624 + return t.pin_memory() if PIN_MEMORY else t
625
626
627 def make_ndarray_with_pad(
@@ -914,11 +922,6 @@ def _encode_layer_name(layer_name: str) -> str | LayerName:
922 return LayerName(layer_name) if _USE_LAYERNAME else layer_name
923
924
917 -# Supports xccl with PyTorch versions >= 2.8.0.dev for XPU platform
918 -def supports_xccl() -> bool:
919 - return torch.distributed.is_xccl_available()
920 -
921 -
925 # Supports XPU Graph with PyTorch versions >= 2.11.0.dev for XPU platform
926 def supports_xpu_graph() -> bool:
927 return is_torch_equal_or_newer("2.11.0.dev")
vllm/v1/attention/backends/flashinfer.py
+2 -4
@@ -41,8 +41,8 @@ from vllm.utils.flashinfer import (
41 use_trtllm_attention,
42 )
43 from vllm.utils.math_utils import cdiv
44 -from vllm.utils.platform_utils import is_pin_memory_available
44 from vllm.utils.torch_utils import (
45 + PIN_MEMORY,
46 canonicalize_singleton_dim_strides,
47 is_quantized_kv_cache,
48 is_strictly_contiguous,
@@ -708,9 +708,7 @@ class FlashInferMetadataBuilder(AttentionMetadataBuilder[FlashInferMetadata]):
708 # Since we do not have explicit synchronization in ModelRunnerV2, we do not pin
709 # reused CPU buffers to avoid a race condition between step N async copies to
710 # GPU and step N+1 buffer updates.
711 - self.pin_memory = (
712 - not vllm_config.use_v2_model_runner and is_pin_memory_available()
713 - )
711 + self.pin_memory = not vllm_config.use_v2_model_runner and PIN_MEMORY
712 self.paged_kv_indptr = self._make_buffer(max_num_reqs + 1)
713 self.paged_kv_indptr_cpu_buffer = torch.zeros_like(
714 self.paged_kv_indptr.cpu, pin_memory=self.pin_memory
vllm/v1/attention/backends/flex_attention.py
+6 -2
@@ -28,7 +28,11 @@ from vllm.logger import init_logger
28 from vllm.model_executor.layers.attention import Attention
29 from vllm.platforms import current_platform
30 from vllm.utils.math_utils import cdiv
31 -from vllm.utils.torch_utils import is_quantized_kv_cache, is_torch_equal_or_newer
31 +from vllm.utils.torch_utils import (
32 + async_tensor_h2d,
33 + is_quantized_kv_cache,
34 + is_torch_equal_or_newer,
35 +)
36 from vllm.v1.attention.backend import (
37 AttentionBackend,
38 AttentionCGSupport,
@@ -58,7 +62,7 @@ def _offsets_to_doc_ids_tensor(
62 doc_ids = torch.repeat_interleave(
63 torch.arange(len(counts), dtype=torch.int32), counts
64 )
61 - return doc_ids.to(device, non_blocking=True)
65 + return async_tensor_h2d(doc_ids, device=device)
66
67
68 def pad_to_multiple(x: torch.Tensor, multiple: int, dim: int):
vllm/v1/attention/backends/gdn_attn.py
+11 -8
@@ -8,6 +8,7 @@ from typing import Literal
8 import torch
9
10 from vllm.config import VllmConfig
11 +from vllm.utils.torch_utils import async_tensor_h2d
12 from vllm.v1.attention.backend import (
13 AttentionBackend,
14 AttentionCGSupport,
@@ -203,8 +204,8 @@ class GDNAttentionMetadataBuilder(AttentionMetadataBuilder[GDNAttentionMetadata]
204 spec_sequence_masks = None
205 spec_sequence_masks_cpu = None
206 else:
206 - spec_sequence_masks = spec_sequence_masks_cpu.to(
207 - query_start_loc.device, non_blocking=True
207 + spec_sequence_masks = async_tensor_h2d(
208 + spec_sequence_masks_cpu, device=query_start_loc.device
209 )
210
211 if spec_sequence_masks is None:
@@ -376,12 +377,14 @@ class GDNAttentionMetadataBuilder(AttentionMetadataBuilder[GDNAttentionMetadata]
377 )
378
379 assert prefill_query_start_loc_cpu is not None
379 - chunk_indices = prepare_chunk_indices(
380 - prefill_query_start_loc_cpu, FLA_CHUNK_SIZE
381 - ).to(device=gpu_device, non_blocking=True)
382 - chunk_offsets = prepare_chunk_offsets(
383 - prefill_query_start_loc_cpu, FLA_CHUNK_SIZE
384 - ).to(device=gpu_device, non_blocking=True)
380 + chunk_indices = async_tensor_h2d(
381 + prepare_chunk_indices(prefill_query_start_loc_cpu, FLA_CHUNK_SIZE),
382 + device=gpu_device,
383 + )
384 + chunk_offsets = async_tensor_h2d(
385 + prepare_chunk_offsets(prefill_query_start_loc_cpu, FLA_CHUNK_SIZE),
386 + device=gpu_device,
387 + )
388
389 if num_prefills > 0:
390 has_initial_state = context_lens_tensor > 0
vllm/v1/attention/backends/mamba2_attn.py
+14 -13
@@ -7,6 +7,7 @@ from typing import Any
7 import torch
8
9 from vllm.config import VllmConfig
10 +from vllm.utils.torch_utils import async_tensor_h2d
11 from vllm.v1.attention.backend import (
12 AttentionBackend,
13 CommonAttentionMetadata,
@@ -68,22 +69,22 @@ def compute_varlen_chunk_metadata(
69
70 # Exclusive prefix sum over logical-chunk lengths
71 if chunk_lens:
71 - cu_chunk_seqlens = torch.tensor(
72 - [0] + list(itertools.accumulate(chunk_lens)),
73 - device=device,
74 - dtype=torch.int32,
75 - )
76 - # Final boundary must equal total tokens
77 - assert int(cu_chunk_seqlens[-1].item()) == total
72 + cu_chunk_seqlens_list = [0] + list(itertools.accumulate(chunk_lens))
73 + # Final boundary must equal total tokens (check on host to avoid a sync)
74 + assert cu_chunk_seqlens_list[-1] == total
75 else:
79 - cu_chunk_seqlens = torch.tensor([0], device=device, dtype=torch.int32)
76 + cu_chunk_seqlens_list = [0]
77 + cu_chunk_seqlens = async_tensor_h2d(
78 + cu_chunk_seqlens_list, dtype=torch.int32, device=device
79 + )
80
81 - last_chunk_indices_t = (
82 - torch.tensor(last_chunk_indices, device=device, dtype=torch.int32)
83 - if len(starts) > 0
84 - else torch.empty((0,), device=device, dtype=torch.int32)
81 + # last_chunk_indices is empty when there are no sequences (len(starts) == 0).
82 + last_chunk_indices_t = async_tensor_h2d(
83 + last_chunk_indices, dtype=torch.int32, device=device
84 + )
85 + seq_idx_chunks_t = async_tensor_h2d(
86 + seq_idx_chunks, dtype=torch.int32, device=device
87 )
86 - seq_idx_chunks_t = torch.tensor(seq_idx_chunks, device=device, dtype=torch.int32)
88 return cu_chunk_seqlens, last_chunk_indices_t, seq_idx_chunks_t
89
90
vllm/v1/attention/backends/mla/flashinfer_mla_sparse.py
+2 -2
@@ -26,7 +26,7 @@ from vllm.model_executor.layers.attention.mla_attention import (
26 get_mla_dims,
27 )
28 from vllm.platforms.interface import DeviceCapability
29 -from vllm.utils.torch_utils import is_quantized_kv_cache
29 +from vllm.utils.torch_utils import is_quantized_kv_cache, np_to_pinned_tensor
30 from vllm.v1.attention.backend import (
31 AttentionBackend,
32 AttentionCGSupport,
@@ -217,7 +217,7 @@ class FlashInferMLASparseMetadataBuilder(
217 # Zero-fill for cudagraphs
218 self.req_id_per_token_buffer.fill_(0)
219 self.req_id_per_token_buffer[: req_id_per_token.shape[0]].copy_(
220 - torch.from_numpy(req_id_per_token), non_blocking=True
220 + np_to_pinned_tensor(req_id_per_token), non_blocking=True
221 )
222 req_id_per_token_tensor = self.req_id_per_token_buffer[:num_tokens]
223
vllm/v1/attention/backends/mla/flashmla_sparse.py
+2 -2
@@ -16,7 +16,7 @@ from vllm.model_executor.layers.attention.mla_attention import (
16 from vllm.platforms import current_platform
17 from vllm.platforms.interface import DeviceCapability
18 from vllm.utils.platform_utils import num_compute_units
19 -from vllm.utils.torch_utils import is_quantized_kv_cache
19 +from vllm.utils.torch_utils import is_quantized_kv_cache, np_to_pinned_tensor
20 from vllm.v1.attention.backend import (
21 AttentionBackend,
22 AttentionCGSupport,
@@ -503,7 +503,7 @@ class FlashMLASparseMetadataBuilder(AttentionMetadataBuilder[FlashMLASparseMetad
503 # Zero-fill for cudagraphs
504 self.req_id_per_token_buffer.fill_(0)
505 self.req_id_per_token_buffer[: req_id_per_token.shape[0]].copy_(
506 - torch.from_numpy(req_id_per_token), non_blocking=True
506 + np_to_pinned_tensor(req_id_per_token), non_blocking=True
507 )
508 req_id_per_token = self.req_id_per_token_buffer[:num_tokens]
509
vllm/v1/attention/backends/utils.py
+16 -19
@@ -17,7 +17,7 @@ from typing_extensions import runtime_checkable
17
18 from vllm.config import VllmConfig, get_layers_from_vllm_config
19 from vllm.utils.math_utils import cdiv
20 -from vllm.utils.torch_utils import async_tensor_h2d
20 +from vllm.utils.torch_utils import PIN_MEMORY, async_tensor_h2d, np_to_pinned_tensor
21 from vllm.v1.kv_cache_interface import KVCacheSpec, MambaSpec
22
23 if TYPE_CHECKING:
@@ -364,8 +364,8 @@ def make_local_attention_virtual_batches(
364 # tensor first, which recovers perf.
365 # Upload the index tensors to the block_table's device up-front so that the
366 # fancy indexing below doesn't implicitly force a synchronous H2D copy.
367 - batch_indices_torch = torch.from_numpy(batch_indices).to(device, non_blocking=True)
368 - block_indices_torch = torch.from_numpy(block_indices).to(device, non_blocking=True)
367 + batch_indices_torch = async_tensor_h2d(batch_indices, device=device)
368 + block_indices_torch = async_tensor_h2d(block_indices, device=device)
369
370 # Save as a lambda so we can return this for update_block_table
371 make_block_table = lambda block_table: block_table[
@@ -379,8 +379,8 @@ def make_local_attention_virtual_batches(
379
380 return CommonAttentionMetadata(
381 query_start_loc_cpu=query_start_loc_cpu,
382 - query_start_loc=query_start_loc_cpu.to(device=device, non_blocking=True),
383 - seq_lens=seq_lens_cpu.to(device=device, non_blocking=True),
382 + query_start_loc=async_tensor_h2d(query_start_loc_cpu, device=device),
383 + seq_lens=async_tensor_h2d(seq_lens_cpu, device=device),
384 num_reqs=len(seq_lens_cpu),
385 num_actual_tokens=common_attn_metadata.num_actual_tokens,
386 max_query_len=seqlens_q_local.max(),
@@ -808,14 +808,12 @@ def create_fast_prefill_custom_backend(
808
809
810 def compute_causal_conv1d_metadata(
811 - query_start_loc_p_cpu: torch.Tensor,
812 - *,
813 - device: torch.device,
814 -):
811 + query_start_loc_p_cpu: torch.Tensor, *, device: torch.device
812 +) -> tuple[dict[int, dict[str, Any]], torch.Tensor, torch.Tensor]:
813 # Needed for causal_conv1d. Use the CPU query_start_loc to avoid DtoH sync.
814 assert query_start_loc_p_cpu.device.type == "cpu"
815 seqlens = query_start_loc_p_cpu.diff()
818 - nums_dict = {} # type: ignore
816 + nums_dict: dict[int, dict[str, Any]] = {}
817 batch_ptr = None
818 token_chunk_offset_ptr = None
819 for BLOCK_M in [8]: # cover all BLOCK_M values
@@ -823,7 +821,7 @@ def compute_causal_conv1d_metadata(
821 nums_dict[BLOCK_M] = {}
822 nums_dict[BLOCK_M]["nums"] = nums
823 nums_dict[BLOCK_M]["tot"] = nums.sum().item()
826 - mlist = torch.from_numpy(np.repeat(np.arange(len(nums)), nums))
824 + mlist = np_to_pinned_tensor(np.repeat(np.arange(len(nums)), nums))
825 nums_dict[BLOCK_M]["mlist"] = mlist
826 mlist_len = len(nums_dict[BLOCK_M]["mlist"])
827 nums_dict[BLOCK_M]["mlist_len"] = mlist_len
@@ -831,7 +829,7 @@ def compute_causal_conv1d_metadata(
829 offsetlist = [] # type: ignore
830 for idx, num in enumerate(nums):
831 offsetlist.extend(range(num))
834 - offsetlist = torch.tensor(offsetlist, dtype=torch.int32)
832 + offsetlist = torch.tensor(offsetlist, dtype=torch.int32, pin_memory=PIN_MEMORY)
833 nums_dict[BLOCK_M]["offsetlist"] = offsetlist
834
835 if batch_ptr is None:
@@ -845,16 +843,15 @@ def compute_causal_conv1d_metadata(
843 else:
844 if batch_ptr.nelement() < MAX_NUM_PROGRAMS:
845 batch_ptr.resize_(MAX_NUM_PROGRAMS).fill_(PAD_SLOT_ID)
848 - token_chunk_offset_ptr.resize_( # type: ignore
849 - MAX_NUM_PROGRAMS
850 - ).fill_(PAD_SLOT_ID)
846 + assert token_chunk_offset_ptr is not None
847 + token_chunk_offset_ptr.resize_(MAX_NUM_PROGRAMS).fill_(PAD_SLOT_ID)
848
849 + assert batch_ptr is not None
850 batch_ptr[0:mlist_len].copy_(mlist, non_blocking=True)
853 - token_chunk_offset_ptr[ # type: ignore
854 - 0:mlist_len
855 - ].copy_(offsetlist, non_blocking=True)
851 + assert token_chunk_offset_ptr is not None
852 + token_chunk_offset_ptr[0:mlist_len].copy_(offsetlist, non_blocking=True)
853 nums_dict[BLOCK_M]["batch_ptr"] = batch_ptr
857 - nums_dict[BLOCK_M]["token_chunk_offset_ptr"] = token_chunk_offset_ptr # type: ignore
854 + nums_dict[BLOCK_M]["token_chunk_offset_ptr"] = token_chunk_offset_ptr
855
856 return nums_dict, batch_ptr, token_chunk_offset_ptr
857
vllm/v1/kv_offload/cpu/gpu_worker.py
+3 -3
@@ -14,7 +14,7 @@ from vllm.logger import init_logger
14 from vllm.platforms import current_platform
15 from vllm.triton_utils import HAS_TRITON, triton
16 from vllm.utils.math_utils import cdiv
17 -from vllm.utils.platform_utils import is_pin_memory_available
17 +from vllm.utils.torch_utils import PIN_MEMORY
18 from vllm.v1.kv_offload.base import (
19 BlockIDsLoadStoreSpec,
20 CanonicalKVCacheRef,
@@ -156,7 +156,7 @@ def pin_mmap_region(region: SharedOffloadRegion) -> None:
156 def _new_descriptor_buffers(
157 num_copy_ops: int,
158 ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
159 - pin = is_pin_memory_available()
159 + pin = PIN_MEMORY
160 # CUDA cache_kernels.cu requires int64; XPU DMA engine requires uint64.
161 ptr_dtype = torch.uint64 if current_platform.is_xpu() else torch.int64
162 return (
@@ -482,7 +482,7 @@ class CpuGpuOffloadingHandlers:
482 num_cpu_blocks: int,
483 mmap_region: SharedOffloadRegion | None = None,
484 ):
485 - pin_memory = is_pin_memory_available()
485 + pin_memory = PIN_MEMORY
486 logger.info("Allocating %d CPU tensors...", len(kv_caches.tensors))
487 self._mmap_region = mmap_region
488 if mmap_region is not None and pin_memory:
vllm/v1/pool/metadata.py
+2 -4
@@ -7,9 +7,7 @@ import torch
7
8 from vllm.pooling_params import PoolingParams
9 from vllm.tasks import PoolingTask
10 -from vllm.utils.platform_utils import is_pin_memory_available
11 -
12 -pin_memory = is_pin_memory_available()
10 +from vllm.utils.torch_utils import PIN_MEMORY
11
12
13 @dataclass
@@ -134,7 +132,7 @@ class PoolingMetadata:
132 num_scheduled_tokens_cpu = torch.from_numpy(num_scheduled_tokens_np)
133 if query_start_loc_gpu is None:
134 cumsum = torch.zeros(
137 - n_seq + 1, dtype=torch.int64, pin_memory=pin_memory, device="cpu"
135 + n_seq + 1, dtype=torch.int64, pin_memory=PIN_MEMORY, device="cpu"
136 )
137 torch.cumsum(num_scheduled_tokens_cpu, dim=0, out=cumsum[1:])
138 cumsum = cumsum.to(device, non_blocking=True)
vllm/v1/sample/logits_processor/builtin.py
+5 -10
@@ -7,6 +7,7 @@ import numpy as np
7 import torch
8
9 from vllm import SamplingParams
10 +from vllm.utils.torch_utils import async_tensor_h2d
11 from vllm.v1.sample.logits_processor.interface import (
12 BatchUpdate,
13 LogitsProcessor,
@@ -118,7 +119,6 @@ class MinPLogitsProcessor(LogitsProcessor):
119 class LogitBiasLogitsProcessor(LogitsProcessor):
120 def __init__(self, _, device: torch.device, is_pin_memory: bool):
121 self.device = device
121 - self.pin_memory = is_pin_memory
122 self.biases: dict[int, dict[int, float]] = {}
123
124 self.bias_tensor: torch.Tensor = torch.tensor(())
@@ -154,9 +154,7 @@ class LogitBiasLogitsProcessor(LogitsProcessor):
154 )
155
156 def _device_tensor(self, data: list, dtype: torch.dtype) -> torch.Tensor:
157 - return torch.tensor(
158 - data, device="cpu", dtype=dtype, pin_memory=self.pin_memory
159 - ).to(device=self.device, non_blocking=True)
157 + return async_tensor_h2d(data, device=self.device, dtype=dtype)
158
159 def apply(self, logits: torch.Tensor) -> torch.Tensor:
160 if self.biases:
@@ -170,7 +168,6 @@ class MinTokensLogitsProcessor(LogitsProcessor):
168 ):
169 # index -> (min_toks, output_token_ids, stop_token_ids)
170 self.device = device
173 - self.pin_memory = is_pin_memory
171 self.min_toks: dict[int, tuple[int, Sequence[int], set[int]]] = {}
172
173 # (req_idx_tensor,eos_tok_id_tensor)
@@ -227,9 +224,7 @@ class MinTokensLogitsProcessor(LogitsProcessor):
224 )
225
226 def _device_tensor(self, data: list, dtype: torch.dtype) -> torch.Tensor:
230 - return torch.tensor(
231 - data, device="cpu", dtype=dtype, pin_memory=self.pin_memory
232 - ).to(device=self.device, non_blocking=True)
227 + return async_tensor_h2d(data, device=self.device, dtype=dtype)
228
229 def apply(self, logits: torch.Tensor) -> torch.Tensor:
230 if self.min_toks:
@@ -283,8 +278,8 @@ class MinTokensLogitsProcessor(LogitsProcessor):
278 toks_arr = np.concatenate(all_toks)
279 # (row_indices, token_indices) for index_put_ to set -inf.
280 logits_slice = (
286 - torch.from_numpy(rows_arr).to(self.device, non_blocking=True),
287 - torch.from_numpy(toks_arr).to(self.device, non_blocking=True),
281 + async_tensor_h2d(rows_arr, device=self.device),
282 + async_tensor_h2d(toks_arr, device=self.device),
283 )
284 logits.index_put_(logits_slice, self.neg_inf_tensor)
285
vllm/v1/sample/ops/penalties.py
+2 -3
@@ -4,8 +4,7 @@
4 import torch
5
6 from vllm.model_executor.layers.utils import apply_penalties
7 -from vllm.utils.platform_utils import is_pin_memory_available
8 -from vllm.utils.torch_utils import make_tensor_with_pad
7 +from vllm.utils.torch_utils import PIN_MEMORY, make_tensor_with_pad
8
9
10 def apply_all_penalties(
@@ -52,6 +51,6 @@ def _convert_to_tensors(
51 pad=vocab_size,
52 device="cpu",
53 dtype=torch.int64,
55 - pin_memory=is_pin_memory_available(),
54 + pin_memory=PIN_MEMORY,
55 )
56 return output_tokens_tensor.to(device, non_blocking=True)
vllm/v1/sample/sampler.py
+2 -2
@@ -6,7 +6,7 @@ import torch
6 import torch.nn as nn
7
8 from vllm.config.model import LogprobsMode
9 -from vllm.utils.platform_utils import is_pin_memory_available
9 +from vllm.utils.torch_utils import PIN_MEMORY
10 from vllm.v1.outputs import LogprobsTensors, SamplerOutput
11 from vllm.v1.sample.metadata import SamplingMetadata
12 from vllm.v1.sample.ops.bad_words import apply_bad_words
@@ -65,7 +65,7 @@ class Sampler(nn.Module):
65 ):
66 super().__init__()
67 self.topk_topp_sampler = TopKTopPSampler(logprobs_mode, use_fp64_gumbel)
68 - self.pin_memory = is_pin_memory_available()
68 + self.pin_memory = PIN_MEMORY
69 self.logprobs_mode = logprobs_mode
70 self.use_fp64_gumbel = use_fp64_gumbel
71
vllm/v1/sample/thinking_budget_state.py
+2 -3
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, Any
7 import torch
8
9 from vllm.platforms import current_platform
10 -from vllm.utils.torch_utils import async_tensor_h2d
10 +from vllm.utils.torch_utils import PIN_MEMORY, async_tensor_h2d
11 from vllm.v1.sample.logits_processor.interface import (
12 BatchUpdate,
13 MoveDirectionality,
@@ -22,12 +22,11 @@ def maybe_create_thinking_budget_state_holder(
22 max_num_seqs: int,
23 num_spec_tokens: int,
24 device: torch.device,
25 - is_pin_memory: bool,
25 ) -> "ThinkingBudgetStateHolder | None":
26 if reasoning_config is None:
27 return None
28 return ThinkingBudgetStateHolder(
30 - reasoning_config, max_num_seqs, num_spec_tokens, device, is_pin_memory
29 + reasoning_config, max_num_seqs, num_spec_tokens, device, PIN_MEMORY
30 )
31
32
vllm/v1/serial_utils.py
+2 -2
@@ -33,7 +33,7 @@ from vllm.multimodal.inputs import (
33 MultiModalSharedField,
34 NestedTensors,
35 )
36 -from vllm.utils.platform_utils import is_pin_memory_available
36 +from vllm.utils.torch_utils import PIN_MEMORY
37 from vllm.v1.utils import tensor_data
38
39 logger = init_logger(__name__)
@@ -327,7 +327,7 @@ class MsgpackDecoder:
327 oob_tensor_provider: OOBTensorProvider | None = None,
328 ):
329 self.share_mem = share_mem
330 - self.pin_tensors = is_pin_memory_available()
330 + self.pin_tensors = PIN_MEMORY
331 args = () if t is None else (t,)
332 self.decoder = msgpack.Decoder(
333 *args, ext_hook=self.ext_hook, dec_hook=self.dec_hook
vllm/v1/simple_kv_offload/worker.py
+2 -2
@@ -8,7 +8,7 @@ import torch
8
9 from vllm.config import VllmConfig
10 from vllm.logger import init_logger
11 -from vllm.utils.platform_utils import is_pin_memory_available
11 +from vllm.utils.torch_utils import PIN_MEMORY
12 from vllm.v1.simple_kv_offload.copy_backend import DmaCopyBackend
13 from vllm.v1.simple_kv_offload.cuda_mem_ops import pin_tensor
14 from vllm.v1.simple_kv_offload.metadata import (
@@ -149,7 +149,7 @@ class SimpleCPUOffloadWorker:
149 (self.num_cpu_blocks * total_bytes_per_block) / (1024**3),
150 )
151
152 - pin_memory = is_pin_memory_available()
152 + pin_memory = PIN_MEMORY
153 if not pin_memory:
154 logger.warning(
155 "Pinned memory not available. CPU offload performance may be degraded."
vllm/v1/spec_decode/extract_hidden_states.py
+2 -3
@@ -12,7 +12,7 @@ from vllm.config import CUDAGraphMode, VllmConfig, get_layers_from_vllm_config
12 from vllm.forward_context import set_forward_context
13 from vllm.model_executor.layers.attention_layer_base import AttentionLayerBase
14 from vllm.model_executor.model_loader import get_model
15 -from vllm.utils.platform_utils import is_pin_memory_available
15 +from vllm.utils.torch_utils import PIN_MEMORY
16 from vllm.v1.attention.backend import AttentionMetadataBuilder, CommonAttentionMetadata
17 from vllm.v1.cudagraph_dispatcher import CudagraphDispatcher
18 from vllm.v1.utils import CpuGpuBuffer
@@ -58,7 +58,7 @@ class ExtractHiddenStatesProposer:
58 self.backup_next_token_ids = CpuGpuBuffer(
59 max_batch_size,
60 dtype=torch.int32,
61 - pin_memory=is_pin_memory_available(),
61 + pin_memory=PIN_MEMORY,
62 device=device,
63 with_numpy=True,
64 )
@@ -317,7 +317,6 @@ class ExtractHiddenStatesProposer:
317 (batch_size, 1). For each request we either use the sampled token
318 (if valid and not discarded) or a backup token from the request state.
319 """
320 - num_reqs = gpu_input_batch.num_reqs
320
321 # Precompute backup token IDs for discarded requests.
322 num_reqs = gpu_input_batch.num_reqs
vllm/v1/spec_decode/llm_base_proposer.py
+7 -9
@@ -26,7 +26,7 @@ from vllm.model_executor.models.llama_eagle3 import Eagle3LlamaForCausalLM
26 from vllm.model_executor.models.qwen3_dflash import DFlashQwen3ForCausalLM
27 from vllm.multimodal import MULTIMODAL_REGISTRY
28 from vllm.platforms import current_platform
29 -from vllm.utils.platform_utils import is_pin_memory_available
29 +from vllm.utils.torch_utils import PIN_MEMORY, async_tensor_h2d
30 from vllm.v1.attention.backend import CommonAttentionMetadata
31 from vllm.v1.attention.backends.registry import AttentionBackendEnum
32 from vllm.v1.attention.backends.triton_attn import TritonAttentionMetadata
@@ -228,7 +228,7 @@ class SpecDecodeBaseProposer:
228 self.backup_next_token_ids = CpuGpuBuffer(
229 self.max_batch_size,
230 dtype=torch.int32,
231 - pin_memory=is_pin_memory_available(),
231 + pin_memory=PIN_MEMORY,
232 device=device,
233 with_numpy=True,
234 )
@@ -239,9 +239,7 @@ class SpecDecodeBaseProposer:
239 self._last_draft_probs: torch.Tensor | None = None
240
241 self._slot_mapping_buffer = torch.zeros(
242 - self.max_positions,
243 - dtype=torch.int64,
244 - device=device,
242 + self.max_positions, dtype=torch.int64, device=device
243 )
244
245 # Determine allowed attention backends once during initialization.
@@ -1127,7 +1125,7 @@ class SpecDecodeBaseProposer:
1125 new_query_start_loc_cpu = torch.zeros(
1126 query_start_loc_cpu.shape,
1127 dtype=torch.int32,
1130 - pin_memory=is_pin_memory_available(),
1128 + pin_memory=PIN_MEMORY,
1129 )
1130 new_query_start_loc_np = new_query_start_loc_cpu.numpy()
1131 np.cumsum(new_num_tokens_per_req_np, out=new_query_start_loc_np[1:])
@@ -1160,11 +1158,11 @@ class SpecDecodeBaseProposer:
1158 # q1 + 0, q1 + 1, q1 + 2, q1 + 3, // req 2
1159 # q1 + q2 + 0, q1 + q2 + 1, q1 + q2 + 2] // req 3
1160 token_indices_np = token_offsets + old_query_start_locs_expanded
1163 - token_indices = torch.from_numpy(token_indices_np).to(device, non_blocking=True)
1161 + token_indices = async_tensor_h2d(token_indices_np, device=device)
1162
1163 spec_common_attn_metadata = CommonAttentionMetadata(
1166 - query_start_loc=new_query_start_loc_cpu.to(device, non_blocking=True),
1167 - seq_lens=new_seq_lens_cpu.to(device, non_blocking=True),
1164 + query_start_loc=async_tensor_h2d(new_query_start_loc_cpu, device=device),
1165 + seq_lens=async_tensor_h2d(new_seq_lens_cpu, device=device),
1166 query_start_loc_cpu=new_query_start_loc_cpu,
1167 _seq_lens_cpu=new_seq_lens_cpu,
1168 _num_computed_tokens_cpu=common_attn_metadata._num_computed_tokens_cpu,
vllm/v1/spec_decode/ngram_proposer_gpu.py
+2 -2
@@ -545,7 +545,7 @@ def update_ngram_gpu_tensors_incremental(
545 num_tokens = input_batch.num_tokens_no_spec[idx]
546 if num_tokens > 0:
547 token_ids_gpu_tensor[idx, :num_tokens].copy_(
548 - input_batch.token_ids_cpu_tensor[idx, :num_tokens],
548 + input_batch.token_ids_cpu_tensor[idx, :num_tokens].pin_memory(),
549 non_blocking=True,
550 )
551
@@ -591,7 +591,7 @@ def update_ngram_gpu_tensors_incremental(
591 num_tokens = input_batch.num_tokens_no_spec[new_req_idx]
592 if num_tokens > 0:
593 token_ids_gpu_tensor[new_req_idx, :num_tokens].copy_(
594 - input_batch.token_ids_cpu_tensor[new_req_idx, :num_tokens],
594 + input_batch.token_ids_cpu_tensor[new_req_idx, :num_tokens].pin_memory(),
595 non_blocking=True,
596 )
597
vllm/v1/structured_output/backend_lm_format_enforcer.py
+2 -2
@@ -11,7 +11,7 @@ from transformers import PreTrainedTokenizerBase
11
12 from vllm.sampling_params import SamplingParams
13 from vllm.utils.import_utils import LazyLoader
14 -from vllm.utils.platform_utils import is_pin_memory_available
14 +from vllm.utils.torch_utils import PIN_MEMORY
15 from vllm.v1.structured_output.backend_types import (
16 StructuredOutputBackend,
17 StructuredOutputGrammar,
@@ -139,7 +139,7 @@ class LMFormatEnforcerBackend(StructuredOutputBackend):
139 (max_num_seqs, (self.vocab_size + 31) // 32),
140 -1,
141 dtype=torch.int32,
142 - pin_memory=is_pin_memory_available(),
142 + pin_memory=PIN_MEMORY,
143 )
144
145 def destroy(self):
vllm/v1/structured_output/backend_outlines.py
+2 -2
@@ -15,7 +15,7 @@ from regex import escape as regex_escape
15
16 from vllm.sampling_params import SamplingParams
17 from vllm.utils.import_utils import LazyLoader
18 -from vllm.utils.platform_utils import is_pin_memory_available
18 +from vllm.utils.torch_utils import PIN_MEMORY
19 from vllm.v1.structured_output.backend_types import (
20 StructuredOutputBackend,
21 StructuredOutputGrammar,
@@ -101,7 +101,7 @@ class OutlinesBackend(StructuredOutputBackend):
101 (max_num_seqs, (self.vocab_size + 31) // 32),
102 -1,
103 dtype=torch.int32,
104 - pin_memory=is_pin_memory_available(),
104 + pin_memory=PIN_MEMORY,
105 )
106
107 def destroy(self):
vllm/v1/structured_output/utils.py
+11 -14
@@ -10,7 +10,6 @@ from collections.abc import Callable
10 from concurrent.futures import ThreadPoolExecutor, TimeoutError
11 from typing import TYPE_CHECKING, TypeVar
12
13 -import numpy as np
13 import regex as re
14 import torch
15 from cachetools import LRUCache
@@ -18,7 +17,7 @@ from cachetools import LRUCache
17 import vllm.envs as envs
18 from vllm.logger import init_logger
19 from vllm.utils.import_utils import LazyLoader
21 -from vllm.utils.platform_utils import is_pin_memory_available
20 +from vllm.utils.torch_utils import PIN_MEMORY, async_tensor_h2d
21 from vllm.v1.core.sched.output import GrammarOutput, SchedulerOutput
22
23 if TYPE_CHECKING:
@@ -123,11 +122,13 @@ def apply_grammar_bitmask(
122 out_indices = []
123
124 # Reorder the bitmask to match the order of the requests in the batch.
126 - sorted_bitmask = np.full(
127 - shape=(logits.shape[0], grammar_bitmask.shape[1]),
128 - fill_value=-1,
129 - dtype=grammar_bitmask.dtype,
125 + sorted_bitmask_tensor = torch.full(
126 + (logits.shape[0], grammar_bitmask.shape[1]),
127 + -1,
128 + dtype=torch.from_numpy(grammar_bitmask[:0]).dtype,
129 + pin_memory=PIN_MEMORY,
130 )
131 + sorted_bitmask = sorted_bitmask_tensor.numpy()
132 cumulative_index = 0
133 for req_id in grammar_output.structured_output_request_ids:
134 num_spec_tokens = len(spec_tokens.get(req_id, ()))
@@ -138,10 +139,8 @@ def apply_grammar_bitmask(
139 out_indices.append(bitmask_index)
140 cumulative_index += 1 + num_spec_tokens
141
141 - # Copy async to device as tensor.
142 - grammar_bitmask = torch.from_numpy(sorted_bitmask).to(
143 - logits.device, non_blocking=True
144 - )
142 + # Copy async to device.
143 + grammar_bitmask = sorted_bitmask_tensor.to(logits.device, non_blocking=True)
144
145 # If the length of out indices and the logits have the same shape
146 # we don't need to pass indices to the kernel,
@@ -154,11 +153,9 @@ def apply_grammar_bitmask(
153 # xgrammar expects a python list of indices but it will actually work with
154 # a tensor. If we copy the tensor ourselves here we can do it in a
155 # non_blocking manner and there should be no cpu sync within xgrammar.
157 - pin_memory = is_pin_memory_available()
158 - index_tensor = torch.tensor(
159 - out_indices, dtype=torch.int32, device="cpu", pin_memory=pin_memory
156 + index_tensor = async_tensor_h2d(
157 + out_indices, dtype=torch.int32, device=logits.device
158 )
161 - index_tensor = index_tensor.to(logits.device, non_blocking=True)
159
160 xgr.apply_token_bitmask_inplace(logits, grammar_bitmask, indices=index_tensor)
161 return
vllm/v1/utils.py
+2 -1
@@ -31,6 +31,7 @@ from vllm.logger import init_logger
31 from vllm.usage.usage_lib import UsageContext, is_usage_stats_enabled, usage_message
32 from vllm.utils.network_utils import get_open_zmq_ipc_path, get_tcp_uri
33 from vllm.utils.system_utils import decorate_logs, kill_process_tree, set_process_title
34 +from vllm.utils.torch_utils import PIN_MEMORY
35 from vllm.v1.core.sched.output import SchedulerOutput
36
37 if TYPE_CHECKING:
@@ -114,7 +115,7 @@ class CpuGpuBuffer:
115 *size: int | torch.SymInt,
116 dtype: torch.dtype,
117 device: torch.device,
117 - pin_memory: bool,
118 + pin_memory: bool = PIN_MEMORY,
119 with_numpy: bool = True,
120 ) -> None:
121 # these buffers are mutable runtime state, so allocate them as normal
vllm/v1/worker/cpu/shm.py
+8 -3
@@ -7,6 +7,8 @@
7
8 from typing import Any
9
10 +import numpy as np
11 +
12 # Patch torch APIs
13 import torch
14
@@ -45,11 +47,14 @@ import vllm.utils.torch_utils as torch_utils
47
48
49 def async_tensor_h2d(
48 - data: list,
49 - dtype: torch.dtype,
50 + data: list | np.ndarray | torch.Tensor,
51 device: str | torch.device,
51 - pin_memory: bool = False,
52 + dtype: torch.dtype | None = None,
53 ) -> torch.Tensor:
54 + if isinstance(data, np.ndarray):
55 + data = torch.from_numpy(data)
56 + if isinstance(data, torch.Tensor):
57 + return data.to(dtype=dtype)
58 return torch.tensor(data, dtype=dtype, device="cpu")
59
60
vllm/v1/worker/gpu/buffer_utils.py
+5 -6
@@ -36,10 +36,9 @@ def async_copy_to_gpu(
36 assert device is not None
37 out = torch.empty_like(x, device=device)
38
39 - # Copy directly to GPU — explicit pin_memory() causes sporadic stalls
40 - # under high concurrency due to CUDA driver contention. The driver
41 - # handles the transfer efficiently without manual pinning.
42 - return out.copy_(x, non_blocking=True)
39 + # pin_memory() is no-op if the memory is already pinned.
40 + pinned = x.pin_memory()
41 + return out.copy_(pinned, non_blocking=True)
42
43
44 class UvaBuffer:
@@ -183,7 +182,7 @@ class StagedWriteTensor:
182
183 # Special handling for write_contents
184 write_contents = async_tensor_h2d(
186 - self._staged_write_contents, self.dtype, self.device
185 + self._staged_write_contents, device=self.device, dtype=self.dtype
186 )
187
188 # Write diffs to the GPU buffer
@@ -255,7 +254,7 @@ class FusedStagedWriter:
254 indices_uva = self.indices.copy_to_uva(indices)
255 starts_uva = self.starts.copy_to_uva(starts)
256 cu_lens_uva = self.cu_lens.copy_to_uva(cu_lens)
258 - contents_gpu = async_tensor_h2d(contents, torch.int32, self.device)
257 + contents_gpu = async_tensor_h2d(contents, device=self.device, dtype=torch.int32)
258
259 _apply_write_kernel[(len(group_ids),)](
260 output_ptrs,
vllm/v1/worker/gpu/mm/encoder_runner.py
+2 -3
@@ -49,12 +49,11 @@ class EncoderRunner:
49
50 @torch.inference_mode()
51 def execute_mm_encoder(
52 - self,
53 - mm_kwargs: list[tuple[str, MultiModalKwargsItem]],
52 + self, mm_kwargs: list[tuple[str, MultiModalKwargsItem]]
53 ) -> list[torch.Tensor]:
54 encoder_outputs: list[torch.Tensor] = []
55 for modality, num_items, mm_kwargs_batch in group_and_batch_mm_kwargs(
57 - mm_kwargs, device=self.device, pin_memory=False
56 + mm_kwargs, device=self.device, pin_memory=True
57 ):
58 batch_outputs = self.model.embed_multimodal(**mm_kwargs_batch)
59 sanity_check_mm_encoder_outputs(batch_outputs, expected_num_items=num_items)
vllm/v1/worker/gpu/model_runner.py
+2 -3
@@ -46,8 +46,7 @@ from vllm.sequence import IntermediateTensors
46 from vllm.tasks import SupportedTask
47 from vllm.utils.math_utils import cdiv
48 from vllm.utils.mem_utils import DeviceMemoryProfiler, format_gib
49 -from vllm.utils.platform_utils import is_pin_memory_available
50 -from vllm.utils.torch_utils import STR_DTYPE_TO_TORCH_DTYPE
49 +from vllm.utils.torch_utils import PIN_MEMORY, STR_DTYPE_TO_TORCH_DTYPE
50 from vllm.v1.core.sched.output import GrammarOutput, SchedulerOutput
51 from vllm.v1.kv_cache_interface import KVCacheConfig, MambaSpec
52 from vllm.v1.outputs import DraftTokenIds, ModelRunnerOutput
@@ -498,7 +497,7 @@ class GPUModelRunner(LoRAModelRunnerMixin):
497 """Build KV-block zeroing metadata; invoked from gpu_worker."""
498 self.kv_block_zeroer = KVBlockZeroer(
499 self.device,
501 - is_pin_memory_available(),
500 + pin_memory=PIN_MEMORY,
501 attn_groups_iter=(g for groups in self.attn_groups for g in groups),
502 kernel_block_sizes=self.kernel_block_sizes,
503 cache_dtype=self.cache_config.cache_dtype,
vllm/v1/worker/gpu/model_states/whisper.py
+3 -4
@@ -170,7 +170,8 @@ class WhisperModelState(ModelState):
170 for_capture: bool,
171 num_reqs: int,
172 ) -> dict[int, tuple[torch.Tensor, np.ndarray]]:
173 - encoder_seq_lens_np = np.zeros(num_reqs, dtype=np.int32)
173 + encoder_seq_lens = torch.zeros(num_reqs, dtype=torch.int32, pin_memory=True)
174 + encoder_seq_lens_np = encoder_seq_lens.numpy()
175 if not for_capture:
176 # During normal execution, use actual encoder lengths.
177 for i, req_id in enumerate(req_ids):
@@ -183,9 +184,7 @@ class WhisperModelState(ModelState):
184 # is captured with the correct value for cross-attention.
185 encoder_seq_lens_np[:] = self.max_encoder_len
186
186 - self.encoder_seq_lens_gpu[:num_reqs].copy_(
187 - torch.from_numpy(encoder_seq_lens_np), non_blocking=True
188 - )
187 + self.encoder_seq_lens_gpu[:num_reqs].copy_(encoder_seq_lens, non_blocking=True)
188 self.encoder_seq_lens_gpu[num_reqs:].fill_(0)
189 encoder_seq_lens_gpu = self.encoder_seq_lens_gpu[:num_reqs]
190
vllm/v1/worker/gpu_input_batch.py
+17 -16
@@ -15,6 +15,7 @@ from vllm.pooling_params import PoolingParams
15 from vllm.sampling_params import SamplingParams, SamplingType
16 from vllm.utils import length_from_prompt_token_ids_or_embeds
17 from vllm.utils.collection_utils import swap_dict_values
18 +from vllm.utils.torch_utils import PIN_MEMORY
19 from vllm.v1.outputs import LogprobsTensors
20 from vllm.v1.pool.metadata import PoolingMetadata, PoolingStates
21 from vllm.v1.sample.logits_processor import (
@@ -95,7 +96,6 @@ class InputBatch:
96 max_model_len: int,
97 max_num_batched_tokens: int,
98 device: torch.device,
98 - pin_memory: bool,
99 vocab_size: int,
100 block_sizes: list[int], # The block_size of each kv cache group
101 kernel_block_sizes: list[int],
@@ -112,7 +112,6 @@ class InputBatch:
112 max_num_reqs,
113 num_spec_tokens,
114 device,
115 - pin_memory,
115 )
116 self.thinking_token_budget_reqs: set[str] = set()
117 self.is_pooling_model = is_pooling_model
@@ -120,7 +119,6 @@ class InputBatch:
119 self.max_model_len = max_model_len
120 self.max_num_batched_tokens = max_num_batched_tokens
121 self.device = device
123 - self.pin_memory = pin_memory
122 self.vocab_size = vocab_size
123
124 self._req_ids: list[str | None] = []
@@ -138,7 +136,10 @@ class InputBatch:
136 )
137 self.token_ids_cpu = self.token_ids_cpu_tensor.numpy()
138 self.is_token_ids_tensor = torch.zeros(
141 - (max_num_reqs, max_model_len), device="cpu", dtype=bool, pin_memory=False
139 + (max_num_reqs, max_model_len),
140 + device="cpu",
141 + dtype=bool,
142 + pin_memory=False,
143 )
144 self.is_token_ids = self.is_token_ids_tensor.numpy()
145 # Store prompt embeddings per request to avoid OOM from large upfront
@@ -149,21 +150,21 @@ class InputBatch:
150 (max_num_reqs,),
151 device="cpu",
152 dtype=torch.int32,
152 - pin_memory=pin_memory,
153 + pin_memory=PIN_MEMORY,
154 )
155 self.num_tokens_no_spec = self.num_tokens_no_spec_cpu_tensor.numpy()
156 self.num_prompt_tokens_cpu_tensor = torch.zeros(
157 (max_num_reqs,),
158 device="cpu",
159 dtype=torch.int32,
159 - pin_memory=pin_memory,
160 + pin_memory=PIN_MEMORY,
161 )
162 self.num_prompt_tokens = self.num_prompt_tokens_cpu_tensor.numpy()
163 self.num_computed_tokens_cpu_tensor = torch.zeros(
164 (max_num_reqs,),
165 device="cpu",
166 dtype=torch.int32,
166 - pin_memory=pin_memory,
167 + pin_memory=PIN_MEMORY,
168 )
169 self.num_computed_tokens_cpu = self.num_computed_tokens_cpu_tensor.numpy()
170
@@ -172,7 +173,7 @@ class InputBatch:
173 max_num_reqs=max_num_reqs,
174 max_model_len=max_model_len,
175 max_num_batched_tokens=max_num_batched_tokens,
175 - pin_memory=pin_memory,
176 + pin_memory=PIN_MEMORY,
177 device=device,
178 block_sizes=block_sizes,
179 kernel_block_sizes=kernel_block_sizes,
@@ -185,7 +186,7 @@ class InputBatch:
186 (max_num_reqs,), dtype=torch.float32, device=device
187 )
188 self.temperature_cpu_tensor = torch.empty(
188 - (max_num_reqs,), dtype=torch.float32, device="cpu", pin_memory=pin_memory
189 + (max_num_reqs,), dtype=torch.float32, device="cpu", pin_memory=PIN_MEMORY
190 )
191 self.temperature_cpu = self.temperature_cpu_tensor.numpy()
192 self.greedy_reqs: set[str] = set()
@@ -193,14 +194,14 @@ class InputBatch:
194
195 self.top_p = torch.empty((max_num_reqs,), dtype=torch.float32, device=device)
196 self.top_p_cpu_tensor = torch.empty(
196 - (max_num_reqs,), dtype=torch.float32, device="cpu", pin_memory=pin_memory
197 + (max_num_reqs,), dtype=torch.float32, device="cpu", pin_memory=PIN_MEMORY
198 )
199 self.top_p_cpu = self.top_p_cpu_tensor.numpy()
200 self.top_p_reqs: set[str] = set()
201
202 self.top_k = torch.empty((max_num_reqs,), dtype=torch.int32, device=device)
203 self.top_k_cpu_tensor = torch.empty(
203 - (max_num_reqs,), dtype=torch.int32, device="cpu", pin_memory=pin_memory
204 + (max_num_reqs,), dtype=torch.int32, device="cpu", pin_memory=PIN_MEMORY
205 )
206 self.top_k_cpu = self.top_k_cpu_tensor.numpy()
207 self.top_k_reqs: set[str] = set()
@@ -210,7 +211,7 @@ class InputBatch:
211 (max_num_reqs,), dtype=torch.float, device=device
212 )
213 self.frequency_penalties_cpu_tensor = torch.empty(
213 - (max_num_reqs,), dtype=torch.float, device="cpu", pin_memory=pin_memory
214 + (max_num_reqs,), dtype=torch.float, device="cpu", pin_memory=PIN_MEMORY
215 )
216 self.frequency_penalties_cpu = self.frequency_penalties_cpu_tensor.numpy()
217 self.frequency_penalties_reqs: set[str] = set()
@@ -220,7 +221,7 @@ class InputBatch:
221 (max_num_reqs,), dtype=torch.float, device=device
222 )
223 self.presence_penalties_cpu_tensor = torch.empty(
223 - (max_num_reqs,), dtype=torch.float, device="cpu", pin_memory=pin_memory
224 + (max_num_reqs,), dtype=torch.float, device="cpu", pin_memory=PIN_MEMORY
225 )
226 self.presence_penalties_cpu = self.presence_penalties_cpu_tensor.numpy()
227 self.presence_penalties_reqs: set[str] = set()
@@ -230,14 +231,14 @@ class InputBatch:
231 (max_num_reqs,), dtype=torch.float, device=device
232 )
233 self.repetition_penalties_cpu_tensor = torch.empty(
233 - (max_num_reqs,), dtype=torch.float, device="cpu", pin_memory=pin_memory
234 + (max_num_reqs,), dtype=torch.float, device="cpu", pin_memory=PIN_MEMORY
235 )
236 self.repetition_penalties_cpu = self.repetition_penalties_cpu_tensor.numpy()
237 self.repetition_penalties_reqs: set[str] = set()
238
239 # Speculative decoding
240 self.num_accepted_tokens_cpu_tensor = torch.ones(
240 - (max_num_reqs,), dtype=torch.int32, device="cpu", pin_memory=pin_memory
241 + (max_num_reqs,), dtype=torch.int32, device="cpu", pin_memory=PIN_MEMORY
242 )
243 self.num_accepted_tokens_cpu = self.num_accepted_tokens_cpu_tensor.numpy()
244
@@ -963,7 +964,7 @@ class InputBatch:
964 (self.num_reqs, max_prompt_len),
965 device="cpu",
966 dtype=torch.int64,
966 - pin_memory=self.pin_memory,
967 + pin_memory=PIN_MEMORY,
968 )
969 prompt_token_ids = prompt_token_ids_cpu_tensor.numpy()
970 prompt_token_ids[:] = self.token_ids_cpu[:num_reqs, :max_prompt_len]
vllm/v1/worker/gpu_model_runner.py
+38 -45
@@ -116,8 +116,10 @@ from vllm.utils import length_from_prompt_token_ids_or_embeds
116 from vllm.utils.math_utils import cdiv, round_up
117 from vllm.utils.mem_utils import DeviceMemoryProfiler, format_gib
118 from vllm.utils.nvtx_pytorch_hooks import PytHooks
119 -from vllm.utils.platform_utils import is_pin_memory_available, num_compute_units
119 +from vllm.utils.platform_utils import num_compute_units
120 from vllm.utils.torch_utils import (
121 + PIN_MEMORY,
122 + async_tensor_h2d,
123 get_dtype_size,
124 is_quantized_kv_cache,
125 kv_cache_dtype_str_to_dtype,
@@ -441,7 +443,6 @@ class GPUModelRunner(
443 scheduler_config = self.scheduler_config
444 parallel_config = self.parallel_config
445 self.device = device
444 - self.pin_memory = is_pin_memory_available()
446 self.dtype = self.model_config.dtype
447
448 self.kv_cache_dtype = kv_cache_dtype_str_to_dtype(
@@ -666,7 +667,6 @@ class GPUModelRunner(
667 max_model_len=max(self.max_model_len, self.max_encoder_len),
668 max_num_batched_tokens=self.max_num_tokens,
669 device=self.device,
669 - pin_memory=self.pin_memory,
670 vocab_size=self.model_config.get_vocab_size(),
671 block_sizes=[placeholder_block_size],
672 kernel_block_sizes=[placeholder_block_size],
@@ -674,7 +674,7 @@ class GPUModelRunner(
674 logitsprocs=build_logitsprocs(
675 self.vllm_config,
676 self.device,
677 - self.pin_memory,
677 + PIN_MEMORY,
678 self.is_pooling_model,
679 custom_logitsprocs,
680 ),
@@ -729,7 +729,7 @@ class GPUModelRunner(
729 self.max_num_reqs, dtype=torch.int32, device=self.device
730 )
731 self.optimistic_seq_lens_cpu = torch.zeros(
732 - self.max_num_reqs, dtype=torch.int32, pin_memory=self.pin_memory
732 + self.max_num_reqs, dtype=torch.int32, pin_memory=PIN_MEMORY
733 )
734 self.num_computed_tokens = torch.zeros(
735 self.max_num_reqs, dtype=torch.int32, device=self.device
@@ -846,7 +846,7 @@ class GPUModelRunner(
846 and self.speculative_config.use_ngram_gpu()
847 ):
848 self._num_valid_draft_tokens_cpu = torch.empty(
849 - self.max_num_reqs, dtype=torch.int32, pin_memory=self.pin_memory
849 + self.max_num_reqs, dtype=torch.int32, pin_memory=PIN_MEMORY
850 )
851 self._num_valid_draft_tokens_event = torch.cuda.Event()
852 self._num_valid_draft_tokens_copy_stream = torch.cuda.Stream()
@@ -857,7 +857,7 @@ class GPUModelRunner(
857 (self.max_num_reqs, 1),
858 dtype=torch.int64,
859 device="cpu",
860 - pin_memory=self.pin_memory,
860 + pin_memory=PIN_MEMORY,
861 )
862
863 # Pre-allocated tensor for copying valid sampled token counts to CPU,
@@ -879,7 +879,7 @@ class GPUModelRunner(
879 (self.max_num_reqs, self.num_spec_tokens),
880 dtype=torch.int64,
881 device="cpu",
882 - pin_memory=self.pin_memory,
882 + pin_memory=PIN_MEMORY,
883 )
884 if self.use_async_scheduling:
885 self.valid_sampled_token_count_event = torch.Event()
@@ -888,7 +888,7 @@ class GPUModelRunner(
888 self.max_num_reqs,
889 dtype=torch.int32,
890 device="cpu",
891 - pin_memory=self.pin_memory,
891 + pin_memory=PIN_MEMORY,
892 )
893
894 # Model weight offloader
@@ -1000,7 +1000,6 @@ class GPUModelRunner(
1000 *size,
1001 dtype=dtype,
1002 device=self.device,
1003 - pin_memory=self.pin_memory,
1003 with_numpy=numpy,
1004 )
1005
@@ -1055,7 +1054,7 @@ class GPUModelRunner(
1054 token_type_ids.append(ids)
1055
1056 token_type_ids_cpu = torch.empty(
1058 - sum(seq_lens_cpu), dtype=torch.int32, pin_memory=self.pin_memory
1057 + sum(seq_lens_cpu), dtype=torch.int32, pin_memory=PIN_MEMORY
1058 )
1059 torch.cat(token_type_ids, out=token_type_ids_cpu)
1060 model_kwargs["token_type_ids"] = token_type_ids_cpu.to(
@@ -1095,12 +1094,12 @@ class GPUModelRunner(
1094 """
1095 self._kv_block_zeroer = KVBlockZeroer(
1096 self.device,
1098 - self.pin_memory,
1097 + pin_memory=PIN_MEMORY,
1098 attn_groups_iter=self._kv_cache_spec_attn_group_iterator(),
1099 kernel_block_sizes=self._kernel_block_sizes,
1100 cache_dtype=self.cache_config.cache_dtype,
1101 runner_only_attn_layers=self.runner_only_attn_layers,
1103 - static_forward_context=(self.compilation_config.static_forward_context),
1102 + static_forward_context=self.compilation_config.static_forward_context,
1103 )
1104
1105 def _zero_block_ids(self, block_ids: list[int]) -> None:
@@ -1651,7 +1650,7 @@ class GPUModelRunner(
1650 for _, _, mm_kwargs_batch in group_and_batch_mm_kwargs(
1651 mm_kwargs,
1652 device=self.device,
1654 - pin_memory=self.pin_memory,
1653 + pin_memory=PIN_MEMORY,
1654 ):
1655 mm_kwargs_combined.update(mm_kwargs_batch)
1656
@@ -1807,10 +1806,10 @@ class GPUModelRunner(
1806 return
1807 # Upload the index tensors asynchronously so the scatter can be non-blocking.
1808 sampled_tokens_index_tensor = torch.tensor(
1810 - sample_flattened_indices, dtype=torch.int64, pin_memory=self.pin_memory
1809 + sample_flattened_indices, dtype=torch.int64, pin_memory=PIN_MEMORY
1810 ).to(self.device, non_blocking=True)
1811 prev_common_req_indices_tensor = torch.tensor(
1813 - prev_indices, dtype=torch.int64, pin_memory=self.pin_memory
1812 + prev_indices, dtype=torch.int64, pin_memory=PIN_MEMORY
1813 ).to(self.device, non_blocking=True)
1814 self.input_ids.gpu.scatter_(
1815 dim=0,
@@ -1826,10 +1825,10 @@ class GPUModelRunner(
1825
1826 assert isinstance(self._draft_token_ids, torch.Tensor)
1827 draft_tokens_index_tensor = torch.tensor(
1829 - spec_flattened_indices, dtype=torch.int64, pin_memory=self.pin_memory
1828 + spec_flattened_indices, dtype=torch.int64, pin_memory=PIN_MEMORY
1829 ).to(self.device, non_blocking=True)
1830 prev_draft_token_indices_tensor = torch.tensor(
1832 - prev_draft_token_indices, dtype=torch.int64, pin_memory=self.pin_memory
1831 + prev_draft_token_indices, dtype=torch.int64, pin_memory=PIN_MEMORY
1832 ).to(self.device, non_blocking=True)
1833
1834 # because input_ids dtype is torch.int32,
@@ -2788,21 +2787,16 @@ class GPUModelRunner(
2787 # [0, 1, 2, 5, 6, 9]
2788 target_logits_indices += self._arange_scratch[: cu_num_draft_tokens[-1]]
2789
2791 - # TODO: Optimize the CPU -> GPU copy.
2792 - cu_num_draft_tokens = torch.from_numpy(cu_num_draft_tokens).to(
2793 - self.device, non_blocking=True
2794 - )
2795 - cu_num_sampled_tokens = torch.from_numpy(cu_num_sampled_tokens).to(
2796 - self.device, non_blocking=True
2790 + cu_num_draft_tokens = async_tensor_h2d(cu_num_draft_tokens, device=self.device)
2791 + cu_num_sampled_tokens = async_tensor_h2d(
2792 + cu_num_sampled_tokens, device=self.device
2793 )
2798 - logits_indices = torch.from_numpy(logits_indices).to(
2799 - self.device, non_blocking=True
2794 + logits_indices = async_tensor_h2d(logits_indices, device=self.device)
2795 + target_logits_indices = async_tensor_h2d(
2796 + target_logits_indices, device=self.device
2797 )
2801 - target_logits_indices = torch.from_numpy(target_logits_indices).to(
2802 - self.device, non_blocking=True
2803 - )
2804 - bonus_logits_indices = torch.from_numpy(bonus_logits_indices).to(
2805 - self.device, non_blocking=True
2798 + bonus_logits_indices = async_tensor_h2d(
2799 + bonus_logits_indices, device=self.device
2800 )
2801
2802 # Compute the draft token ids.
@@ -3012,9 +3006,7 @@ class GPUModelRunner(
3006 # Track the current index in mm_kwargs/mm_lora_refs to map groups to request IDs
3007 current_item_idx = 0
3008 for modality, num_items, mm_kwargs_batch in group_and_batch_mm_kwargs(
3015 - mm_kwargs,
3016 - device=self.device,
3017 - pin_memory=self.pin_memory,
3009 + mm_kwargs, device=self.device, pin_memory=PIN_MEMORY
3010 ):
3011 batch_outputs: MultiModalEmbeddings
3012
@@ -3048,7 +3040,7 @@ class GPUModelRunner(
3040 group_and_batch_mm_kwargs(
3041 [video_mm_kwargs_item],
3042 device=self.device,
3051 - pin_memory=self.pin_memory,
3043 + pin_memory=PIN_MEMORY,
3044 )
3045 )
3046
@@ -3107,7 +3099,10 @@ class GPUModelRunner(
3099
3100 mm_embeds = list[torch.Tensor]()
3101 is_mm_embed = torch.zeros(
3110 - total_num_scheduled_tokens, dtype=torch.bool, device="cpu"
3102 + total_num_scheduled_tokens,
3103 + dtype=torch.bool,
3104 + device="cpu",
3105 + pin_memory=PIN_MEMORY,
3106 )
3107
3108 req_start_idx = 0
@@ -3515,8 +3510,7 @@ class GPUModelRunner(
3510 token_ids_idx_np = np.nonzero(is_token_ids)[0]
3511 # Some tokens ids may need to become embeds
3512 if token_ids_idx_np.size > 0:
3518 - token_ids_idx = torch.from_numpy(token_ids_idx_np)
3519 - token_ids_idx = token_ids_idx.to(self.device, non_blocking=True)
3513 + token_ids_idx = async_tensor_h2d(token_ids_idx_np, device=self.device)
3514 token_ids = self.input_ids.gpu[token_ids_idx]
3515 tokens_to_embeds = self.model.embed_input_ids(input_ids=token_ids)
3516 self.inputs_embeds.gpu[token_ids_idx] = tokens_to_embeds
@@ -4953,7 +4947,7 @@ class GPUModelRunner(
4947 ):
4948 indices.append(offset + len(tokens) - 1)
4949 offset += num_draft + 1
4956 - indices = torch.tensor(indices, device=self.device)
4950 + indices = async_tensor_h2d(indices, device=self.device)
4951 hidden_states = sample_hidden_states[indices]
4952
4953 draft_token_ids = self.drafter.propose(
@@ -5483,8 +5477,8 @@ class GPUModelRunner(
5477 continue
5478
5479 num_prompt_tokens = len(request.prompt_token_ids)
5486 - prompt_token_ids = torch.tensor(request.prompt_token_ids).to(
5487 - self.device, non_blocking=True
5480 + prompt_token_ids = async_tensor_h2d(
5481 + request.prompt_token_ids, device=self.device
5482 )
5483
5484 # Set up target LogprobsTensors object.
@@ -5651,7 +5645,7 @@ class GPUModelRunner(
5645 for _, _, mm_kwargs_batch in group_and_batch_mm_kwargs(
5646 [(modality, dummy_mm_item)] * max_items_per_batch,
5647 device=self.device,
5654 - pin_memory=self.pin_memory,
5648 + pin_memory=PIN_MEMORY,
5649 )
5650 )
5651
@@ -6995,7 +6989,6 @@ class GPUModelRunner(
6989 max_model_len=max_model_len,
6990 max_num_batched_tokens=self.max_num_tokens,
6991 device=self.device,
6998 - pin_memory=self.pin_memory,
6992 vocab_size=self.model_config.get_vocab_size(),
6993 block_sizes=block_sizes,
6994 kernel_block_sizes=kernel_block_sizes,
@@ -7430,7 +7423,7 @@ class GPUModelRunner(
7423 self.routed_experts_capturer.device_buffer.shape,
7424 dtype=self.routed_experts_capturer.device_buffer.dtype,
7425 device="cpu",
7433 - pin_memory=self.pin_memory,
7426 + pin_memory=PIN_MEMORY,
7427 )
7428 # ``slot_mapping`` dtype is fixed to int64 by
7429 # ``block_table.slot_mapping``; we mirror that here.
@@ -7439,7 +7432,7 @@ class GPUModelRunner(
7432 (max_tokens,),
7433 dtype=torch.int64,
7434 device="cpu",
7442 - pin_memory=self.pin_memory,
7435 + pin_memory=PIN_MEMORY,
7436 )
7437 # Private device buffer so the shared ``block_table.slot_mapping``
7438 # can be overwritten by the next ``_prepare_inputs`` while the