@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 56539cddac
Fork 0

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 12, 12:15 UTC

[Core] Refactor padding logic and pad for CUDA graphs before attention metadata building (#28579)

Lucas Wilkinson committed Nov 26, 2025 at 14:07 UTC 56539cddac9eeab0a91941d8de689a6cae5dbe05
10 files changed +401 -283
docs/design/cuda_graphs.md
+5 -3
@@ -84,12 +84,14 @@ See the following figures for a quick comparison between the previous and curren
84 ```python
85 class BatchDescriptor(NamedTuple):
86 num_tokens: int
87 - uniform_decode: bool = False
87 + num_reqs: int
88 + uniform: bool = False
89 + has_lora: bool = False
90 ```
91
90 -where `num_tokens` can be the padded token length, and `uniform_decode` is determined by if `max_query_len` of a batch is equal to the desired `max_query_len` of a uniform_decode, and the num_scheduled_tokens is divisible by that desired `max_query_len`.
92 +where `num_tokens` can be the padded token length, and `uniform` indicates if all the requests have the same query lengths. Many attention backends only support full cudagraphs when the batches are uniform; pure decode batches are uniform but may not be query length 1 (i.e. `num_tokens == num_reqs`), this occurs in the validation pass of spec-decode where "decode" batches will have a query length of `1+num_spec_tokens`.
93
92 -The goal of this structure is to uniquely identify a (padded) batch with minimal possible items corresponding to a CUDA Graphs item. We are safe to exclude items like `uniform_query_len` because it is a constant at runtime for a certain setup currently. For example, it should be either `1` for a commonly pure decode or `1+num_spec_tokens` for a validation phase of speculative decode.
94 +The goal of this structure is to uniquely identify a (padded) batch with minimal possible items corresponding to a CUDA Graphs item.
95
96 !!! note
97 The prototype of `BatchDescriptor` may be extended for more general situations in the future, e.g., include more items, like `uniform_query_len` to support multiple different uniform decode lengths settings (<https://github.com/vllm-project/vllm/pull/23679>), or other modifications needed to support CUDA Graphs for models whose inputs are not necessarily token length aware (for example, some multi-modal inputs).
tests/v1/cudagraph/test_cudagraph_dispatch.py
+31 -12
@@ -42,12 +42,24 @@ def _create_vllm_config(
42 mock_config.compilation_config = compilation_config
43 mock_config.scheduler_config = SchedulerConfig(max_num_seqs=max_num_seqs)
44 mock_config.parallel_config = ParallelConfig()
45 + mock_config.speculative_config = None # No speculative decoding
46 if not lora_config:
47 mock_config.lora_config = None
48 # Mimic the behavior of VllmConfig.__post_init__()
49 if compilation_config.mode == CompilationMode.VLLM_COMPILE:
50 compilation_config.set_splitting_ops_for_v1()
51
52 + # mimic VllmConfig.__post_init__
53 + if compilation_config.cudagraph_capture_sizes:
54 + compilation_config.max_cudagraph_capture_size = (
55 + compilation_config.cudagraph_capture_sizes[-1]
56 + )
57 +
58 + compilation_config.post_init_cudagraph_sizes()
59 + mock_config.pad_for_cudagraph = (
60 + lambda batch_size: compilation_config.bs_to_padded_graph_size[batch_size]
61 + )
62 +
63 return mock_config
64
65
@@ -109,9 +121,11 @@ class TestCudagraphDispatcher:
121 # 1. non-uniform batch, size in cudagraph size list
122 desc_full_exact = BatchDescriptor(
123 num_tokens=8,
112 - uniform_decode=False,
124 + uniform=False,
125 + )
126 + rt_mode, key = dispatcher.dispatch(
127 + num_tokens=8, uniform_decode=False, has_lora=False
128 )
114 - rt_mode, key = dispatcher.dispatch(desc_full_exact)
129 if cudagraph_mode_str == "FULL":
130 assert rt_mode == CUDAGraphMode.FULL
131 assert key == desc_full_exact
@@ -122,32 +136,37 @@ class TestCudagraphDispatcher:
136 assert rt_mode == CUDAGraphMode.NONE
137
138 # 2. uniform decode batch, size in cudagraph size list
125 - desc_uniform_exact = BatchDescriptor(num_tokens=8, uniform_decode=True)
126 - rt_mode, key = dispatcher.dispatch(desc_uniform_exact)
139 + desc_uniform_exact = BatchDescriptor(num_tokens=8, num_reqs=8, uniform=True)
140 + rt_mode, key = dispatcher.dispatch(
141 + num_tokens=8, uniform_decode=True, has_lora=False
142 + )
143 if cudagraph_mode_str == "FULL":
144 assert rt_mode == CUDAGraphMode.FULL
129 - assert key == desc_uniform_exact.non_uniform
145 + assert key == desc_uniform_exact.relax_for_mixed_batch_cudagraphs()
146 elif cudagraph_mode_str in ["FULL_DECODE_ONLY", "FULL_AND_PIECEWISE"]:
147 assert rt_mode == CUDAGraphMode.FULL
148 assert key == desc_uniform_exact
149 elif cudagraph_mode_str == "PIECEWISE":
150 assert rt_mode == CUDAGraphMode.PIECEWISE
135 - assert key == desc_uniform_exact.non_uniform
151 + assert key == desc_uniform_exact.relax_for_mixed_batch_cudagraphs()
152 else:
153 assert rt_mode == CUDAGraphMode.NONE
154
155 # 3. No key match
140 - desc_no_match = BatchDescriptor(num_tokens=15, uniform_decode=False)
141 - rt_mode, key = dispatcher.dispatch(desc_no_match)
156 + rt_mode, key = dispatcher.dispatch(
157 + num_tokens=15, uniform_decode=False, has_lora=False
158 + )
159 assert rt_mode == CUDAGraphMode.NONE
143 - assert key is None
160 + assert key == BatchDescriptor(num_tokens=15)
161
162 # 4. Cascade attention should have a fall back mode
146 - desc_full_exact = BatchDescriptor(num_tokens=8, uniform_decode=False)
147 - rt_mode, key = dispatcher.dispatch(desc_full_exact, use_cascade_attn=True)
163 + desc_full_exact = BatchDescriptor(num_tokens=8, uniform=False)
164 + rt_mode, key = dispatcher.dispatch(
165 + num_tokens=8, uniform_decode=False, has_lora=False, use_cascade_attn=True
166 + )
167 if "PIECEWISE" in cudagraph_mode_str: # string contains check
168 assert rt_mode == CUDAGraphMode.PIECEWISE
150 - assert key == desc_full_exact.non_uniform
169 + assert key == desc_full_exact.relax_for_mixed_batch_cudagraphs()
170 else:
171 assert rt_mode == CUDAGraphMode.NONE
172
vllm/forward_context.py
+11 -7
@@ -35,23 +35,27 @@ class BatchDescriptor(NamedTuple):
35 """
36
37 num_tokens: int
38 - uniform_decode: bool = False
38 + num_reqs: int | None = None
39 """
40 - False can also be used for an uniform decode batch to dispatch to the
41 - cudagraph supporting non-uniform batches.
40 + Number of requests in the batch. Can be None for PIECEWISE cudagraphs where
41 + the cudagraphs can handle any number of requests.
42 + """
43 + uniform: bool = False
44 + """
45 + True if all the requests in the batch have the same number of tokens.
46 """
47 has_lora: bool = False
48 """
49 Whether this batch has active LoRA adapters.
50 """
51
48 - @property
49 - def non_uniform(self) -> "BatchDescriptor":
52 + def relax_for_mixed_batch_cudagraphs(self) -> "BatchDescriptor":
53 """
51 - Return a non-uniform version of current batch descriptor.
54 + Return a relaxed version of current batch descriptor that is still compatible
55 + with PIECEWISE cudagraphs (or mixed prefill-decode FA cudagraphs).
56 """
57 return BatchDescriptor(
54 - self.num_tokens, uniform_decode=False, has_lora=self.has_lora
58 + self.num_tokens, num_reqs=None, uniform=False, has_lora=self.has_lora
59 )
60
61
vllm/v1/attention/backends/flashinfer.py
+1 -20
@@ -930,31 +930,12 @@ class FlashInferMetadataBuilder(AttentionMetadataBuilder[FlashInferMetadata]):
930
931 if num_decodes > 0:
932 pure_decode = num_prefills == 0
933 - # possible required padding for cudagraph replay
933 use_cudagraph = (
934 self.enable_cuda_graph
935 and pure_decode
936 and num_decode_tokens <= self._decode_cudagraph_max_bs
937 )
939 - if use_cudagraph:
940 - num_input_tokens = self.vllm_config.pad_for_cudagraph(
941 - num_decode_tokens
942 - )
943 - # Carefully fulfill the padding region with reasonable value
944 - # on cpu.
945 - # Make sure paged_kv_indptr_cpu is not decreasing
946 - self.paged_kv_indptr_cpu[
947 - 1 + num_decodes : 1 + num_input_tokens
948 - ].fill_(paged_kv_indptr_cpu[-1])
949 - # Fill the remaining paged_kv_last_page_len_cpu with 1.
950 - # This is because flashinfer treats 0 as a full page
951 - # instead of empty.
952 - self.paged_kv_last_page_len_cpu[num_decodes:num_input_tokens].fill_(
953 - 1
954 - )
955 -
956 - else:
957 - num_input_tokens = num_decode_tokens
938 + num_input_tokens = num_decode_tokens
939
940 attn_metadata.decode_wrapper = self._get_decode_wrapper(
941 num_input_tokens, use_cudagraph
vllm/v1/attention/backends/mamba_attn.py
+2
@@ -107,6 +107,8 @@ class BaseMambaAttentionMetadataBuilder(AttentionMetadataBuilder[M], abc.ABC):
107 )
108 # -1 in case it's non-computed and causes later issues with indexing
109 block_idx_last_computed_token = block_idx_last_computed_token.clamp(min=0)
110 + # -1 in the case we have a padded request (0 seq-len)
111 + block_idx_last_scheduled_token = block_idx_last_scheduled_token.clamp(min=0)
112
113 return (
114 block_idx_last_computed_token,
vllm/v1/attention/backends/utils.py
+4 -1
@@ -72,6 +72,7 @@ class CommonAttentionMetadata:
72
73 num_reqs: int
74 """Number of requests"""
75 + # TODO(lucas): rename to num_tokens since it may be padded and this is misleading
76 num_actual_tokens: int
77 """Total number of tokens in batch"""
78 max_query_len: int
@@ -857,7 +858,9 @@ def split_decodes_and_prefills(
858 if require_uniform:
859 is_prefill = query_lens != query_lens[0]
860 else:
860 - is_prefill = query_lens > decode_threshold
861 + # 0-query len indicates a padded request; leave this at the back
862 + # of the batch with the prefills
863 + is_prefill = (query_lens > decode_threshold) | (query_lens == 0)
864
865 if not torch.any(is_prefill):
866 return num_reqs, 0, num_tokens, 0
vllm/v1/cudagraph_dispatcher.py
+66 -31
@@ -4,6 +4,9 @@ from itertools import product
4
5 from vllm.config import CUDAGraphMode, VllmConfig
6 from vllm.forward_context import BatchDescriptor
7 +from vllm.logger import init_logger
8 +
9 +logger = init_logger(__name__)
10
11
12 class CudagraphDispatcher:
@@ -28,7 +31,11 @@ class CudagraphDispatcher:
31 def __init__(self, vllm_config: VllmConfig):
32 self.vllm_config = vllm_config
33 self.compilation_config = vllm_config.compilation_config
31 - self.cudagraph_mode = self.compilation_config.cudagraph_mode
34 + self.uniform_decode_query_len = (
35 + 1
36 + if not self.vllm_config.speculative_config
37 + else 1 + self.vllm_config.speculative_config.num_speculative_tokens
38 + )
39
40 # Dict to store valid cudagraph dispatching keys.
41 self.cudagraph_keys: dict[CUDAGraphMode, set[BatchDescriptor]] = {
@@ -36,25 +43,42 @@ class CudagraphDispatcher:
43 CUDAGraphMode.FULL: set(),
44 }
45
39 - not_use_piecewise_compilation = (
40 - not self.cudagraph_mode.requires_piecewise_compilation()
41 - )
42 -
46 assert (
44 - not_use_piecewise_compilation
47 + not self.compilation_config.cudagraph_mode.requires_piecewise_compilation()
48 or self.compilation_config.is_attention_compiled_piecewise()
49 ), (
50 "Compilation mode should be CompilationMode.VLLM_COMPILE when "
51 "cudagraph_mode piecewise cudagraphs is used, "
52 "and attention should be in splitting_ops or "
53 "inductor splitting should be used. "
51 - f"cudagraph_mode={self.cudagraph_mode}, "
54 + f"cudagraph_mode={self.compilation_config.cudagraph_mode}, "
55 f"compilation_mode={self.compilation_config.mode}, "
56 f"splitting_ops={self.compilation_config.splitting_ops}"
57 )
58
59 self.keys_initialized = False
60
61 + def _create_padded_batch_descriptor(
62 + self, num_tokens: int, uniform_decode: bool, has_lora: bool
63 + ) -> BatchDescriptor:
64 + max_num_seqs = self.vllm_config.scheduler_config.max_num_seqs
65 + uniform_decode_query_len = self.uniform_decode_query_len
66 + num_tokens_padded = self.vllm_config.pad_for_cudagraph(num_tokens)
67 +
68 + if uniform_decode and self.cudagraph_mode.has_mode(CUDAGraphMode.FULL):
69 + num_reqs = num_tokens_padded // uniform_decode_query_len
70 + assert num_tokens_padded % uniform_decode_query_len == 0
71 + else:
72 + uniform_decode = False
73 + num_reqs = min(num_tokens_padded, max_num_seqs)
74 +
75 + return BatchDescriptor(
76 + num_tokens=num_tokens_padded,
77 + num_reqs=num_reqs,
78 + uniform=uniform_decode,
79 + has_lora=has_lora,
80 + )
81 +
82 def add_cudagraph_key(
83 self, runtime_mode: CUDAGraphMode, batch_descriptor: BatchDescriptor
84 ):
@@ -66,7 +90,9 @@ class CudagraphDispatcher:
90 def initialize_cudagraph_keys(
91 self, cudagraph_mode: CUDAGraphMode, uniform_decode_query_len: int
92 ):
69 - # This should be called only after attention backend is initialized.
93 + # This should be called only after attention backend is initialized. So we can
94 + # get the correct cudagraph mode after backend support is resolved.
95 + self.cudagraph_mode = cudagraph_mode
96
97 # LoRA activation cases to specialize the cuda graphs on
98 if self.vllm_config.lora_config:
@@ -86,9 +112,9 @@ class CudagraphDispatcher:
112 ):
113 self.add_cudagraph_key(
114 cudagraph_mode.mixed_mode(),
89 - BatchDescriptor(
90 - num_tokens=bs, uniform_decode=False, has_lora=has_lora
91 - ),
115 + self._create_padded_batch_descriptor(
116 + bs, False, has_lora
117 + ).relax_for_mixed_batch_cudagraphs(),
118 )
119
120 # if decode cudagraph mode is FULL, and we don't already have mixed
@@ -109,40 +135,49 @@ class CudagraphDispatcher:
135 for bs, has_lora in product(cudagraph_capture_sizes_for_decode, lora_cases):
136 self.add_cudagraph_key(
137 CUDAGraphMode.FULL,
112 - BatchDescriptor(
113 - num_tokens=bs, uniform_decode=True, has_lora=has_lora
114 - ),
138 + self._create_padded_batch_descriptor(bs, True, has_lora),
139 )
140 +
141 self.keys_initialized = True
142
143 def dispatch(
119 - self, batch_descriptor: BatchDescriptor, use_cascade_attn: bool = False
120 - ) -> tuple[CUDAGraphMode, BatchDescriptor | None]:
144 + self,
145 + num_tokens: int,
146 + uniform_decode: bool,
147 + has_lora: bool,
148 + use_cascade_attn: bool = False,
149 + ) -> tuple[CUDAGraphMode, BatchDescriptor]:
150 """
151 Given conditions(e.g.,batch descriptor and if using cascade attention),
152 dispatch to a cudagraph runtime mode and the valid batch descriptor.
153 A new batch descriptor is returned as we might dispatch a uniform batch
154 to a graph that supports a more general batch (uniform to non-uniform).
155 """
127 - # if not initialized, just skip dispatching.
128 - if not self.keys_initialized:
129 - return CUDAGraphMode.NONE, None
156 + if (
157 + not self.keys_initialized
158 + or self.cudagraph_mode == CUDAGraphMode.NONE
159 + or num_tokens > self.compilation_config.max_cudagraph_capture_size
160 + ):
161 + return CUDAGraphMode.NONE, BatchDescriptor(num_tokens)
162 +
163 + batch_desc = self._create_padded_batch_descriptor(
164 + num_tokens, uniform_decode, has_lora
165 + )
166 + relaxed_batch_desc = batch_desc.relax_for_mixed_batch_cudagraphs()
167
131 - non_uniform_key = batch_descriptor.non_uniform
132 - # if a batch use cascade attention, bypass checking full cudagraphs
168 if not use_cascade_attn:
169 # check if key exists for full cudagraph
135 - if batch_descriptor in self.cudagraph_keys[CUDAGraphMode.FULL]:
136 - return CUDAGraphMode.FULL, batch_descriptor
170 + if batch_desc in self.cudagraph_keys[CUDAGraphMode.FULL]:
171 + return CUDAGraphMode.FULL, batch_desc
172
138 - # otherwise, check if non-uniform key exists
139 - if non_uniform_key in self.cudagraph_keys[CUDAGraphMode.FULL]:
140 - return CUDAGraphMode.FULL, non_uniform_key
173 + # otherwise, check if the relaxed key exists
174 + if relaxed_batch_desc in self.cudagraph_keys[CUDAGraphMode.FULL]:
175 + return CUDAGraphMode.FULL, relaxed_batch_desc
176
142 - # also check if non-uniform key exists for more "general"
177 + # also check if the relaxed key exists for more "general"
178 # piecewise cudagraph
144 - if non_uniform_key in self.cudagraph_keys[CUDAGraphMode.PIECEWISE]:
145 - return CUDAGraphMode.PIECEWISE, non_uniform_key
179 + if relaxed_batch_desc in self.cudagraph_keys[CUDAGraphMode.PIECEWISE]:
180 + return CUDAGraphMode.PIECEWISE, relaxed_batch_desc
181
147 - # finally, just return no cudagraphs
148 - return CUDAGraphMode.NONE, None
182 + # finally, just return no cudagraphs and a trivial batch descriptor
183 + return CUDAGraphMode.NONE, BatchDescriptor(num_tokens)
vllm/v1/worker/dp_utils.py
+16 -1
@@ -9,6 +9,7 @@ from vllm.config import ParallelConfig
9 from vllm.distributed.parallel_state import get_dp_group
10 from vllm.logger import init_logger
11 from vllm.v1.worker.ubatch_utils import (
12 + UBatchSlice,
13 UBatchSlices,
14 check_ubatch_thresholds,
15 create_ubatch_slices,
@@ -88,6 +89,17 @@ def _post_process_dp_padding(tensor: torch.Tensor, should_dp_pad: bool) -> torch
89 return num_tokens_across_dp.cpu()
90
91
92 +# This just pads the second ubatch slice out to the total number of tokens
93 +# (num_tokens + padding) since we do `create_ubatch_slices` before applying DP padding.
94 +def _pad_out_ubatch_slice(ubatch_slices: UBatchSlices, num_total_tokens: int):
95 + padded_second_ubatch_slice = slice(
96 + ubatch_slices[1].token_slice.start, num_total_tokens
97 + )
98 + ubatch_slices[1] = UBatchSlice(
99 + padded_second_ubatch_slice, padded_second_ubatch_slice
100 + )
101 +
102 +
103 def _synchronize_dp_ranks(
104 num_tokens_unpadded: int,
105 num_tokens_padded: int,
@@ -220,11 +232,14 @@ def coordinate_batch_across_dp(
232 # to the second ubatch in pad_out_ubatch_slice after attention
233 # metadata creation
234 assert num_tokens_after_padding is not None
223 - token_split_point = int(num_tokens_after_padding[0].item()) // 2
235 + num_tokens_padded = int(num_tokens_after_padding[0].item())
236 + token_split_point = int(num_tokens_padded) // 2
237
238 assert num_scheduled_tokens_per_request is not None
239 ubatch_slices = create_ubatch_slices(
240 num_scheduled_tokens_per_request, token_split_point
241 )
242 + ubatch_slices = _pad_out_ubatch_slice(ubatch_slices, num_tokens_padded)
243 + assert sum(s.num_tokens for s in ubatch_slices) == num_tokens_padded
244
245 return (ubatch_slices, num_tokens_after_padding)
vllm/v1/worker/gpu_model_runner.py
+230 -202
@@ -151,7 +151,6 @@ from vllm.v1.worker.gpu_ubatch_wrapper import UBatchWrapper
151 from vllm.v1.worker.kv_connector_model_runner_mixin import KVConnectorModelRunnerMixin
152 from vllm.v1.worker.lora_model_runner_mixin import LoRAModelRunnerMixin
153 from vllm.v1.worker.ubatch_utils import (
154 - UBatchSlice,
154 UBatchSlices,
155 check_ubatch_thresholds,
156 )
@@ -1239,17 +1238,13 @@ class GPUModelRunner(
1238 self,
1239 scheduler_output: "SchedulerOutput",
1240 num_scheduled_tokens: np.ndarray,
1242 - max_num_scheduled_tokens: int,
1241 ) -> tuple[
1242 torch.Tensor,
1243 SpecDecodeMetadata | None,
1246 - UBatchSlices | None,
1247 - torch.Tensor | None,
1244 ]:
1245 """
1246 :return: tuple[
1247 logits_indices, spec_decode_metadata,
1252 - ubatch_slices, num_tokens_across_dp,
1248 ]
1249 """
1250 total_num_scheduled_tokens = scheduler_output.total_num_scheduled_tokens
@@ -1364,28 +1359,6 @@ class GPUModelRunner(
1359 self.query_start_loc.copy_to_gpu()
1360 query_start_loc = self.query_start_loc.gpu[: num_reqs + 1]
1361
1367 - num_tokens_unpadded = scheduler_output.total_num_scheduled_tokens
1368 - num_tokens_padded = self._get_num_input_tokens(num_tokens_unpadded)
1369 - uniform_decode = (
1370 - max_num_scheduled_tokens == self.uniform_decode_query_len
1371 - ) and (total_num_scheduled_tokens == num_reqs * max_num_scheduled_tokens)
1372 -
1373 - # Disable DP padding when running eager to avoid excessive padding when
1374 - # running prefills. This lets us set enforce_eager on the prefiller in
1375 - # a P/D setup and still use CUDA graphs (enabled by this padding) on the
1376 - # decoder.
1377 - allow_dp_padding = self.compilation_config.cudagraph_mode != CUDAGraphMode.NONE
1378 -
1379 - ubatch_slices, num_tokens_across_dp = coordinate_batch_across_dp(
1380 - num_tokens_unpadded=num_tokens_unpadded,
1381 - parallel_config=self.parallel_config,
1382 - allow_microbatching=True,
1383 - allow_dp_padding=allow_dp_padding,
1384 - num_tokens_padded=num_tokens_padded,
1385 - uniform_decode=uniform_decode,
1386 - num_scheduled_tokens_per_request=num_scheduled_tokens,
1387 - )
1388 -
1362 self.seq_lens.np[:num_reqs] = (
1363 self.input_batch.num_computed_tokens_cpu[:num_reqs] + num_scheduled_tokens
1364 )
@@ -1486,15 +1459,15 @@ class GPUModelRunner(
1459 return (
1460 logits_indices,
1461 spec_decode_metadata,
1489 - ubatch_slices,
1490 - num_tokens_across_dp,
1462 )
1463
1464 def _build_attention_metadata(
1465 self,
1495 - total_num_scheduled_tokens: int,
1496 - max_num_scheduled_tokens: int,
1466 + num_tokens: int,
1467 num_reqs: int,
1468 + max_query_len: int,
1469 + num_tokens_padded: int | None = None,
1470 + num_reqs_padded: int | None = None,
1471 ubatch_slices: UBatchSlices | None = None,
1472 logits_indices: torch.Tensor | None = None,
1473 use_spec_decode: bool = False,
@@ -1505,6 +1478,9 @@ class GPUModelRunner(
1478 """
1479 :return: tuple[attn_metadata, spec_decode_common_attn_metadata]
1480 """
1481 + num_tokens_padded = num_tokens_padded or num_tokens
1482 + num_reqs_padded = num_reqs_padded or num_reqs
1483 +
1484 logits_indices_padded = None
1485 num_logits_indices = None
1486 if logits_indices is not None:
@@ -1522,28 +1498,13 @@ class GPUModelRunner(
1498 self.dcp_rank,
1499 self.parallel_config.cp_kv_cache_interleave_size,
1500 )
1525 - self.dcp_local_seq_lens.copy_to_gpu(num_reqs)
1501 + self.dcp_local_seq_lens.cpu[num_reqs:].fill_(0)
1502 + self.dcp_local_seq_lens.copy_to_gpu(num_reqs_padded)
1503
1504 attn_metadata: PerLayerAttnMetadata = {}
1505 if ubatch_slices is not None:
1506 attn_metadata = [dict() for _ in range(len(ubatch_slices))]
1507
1531 - # Used in the below loop
1532 - query_start_loc = self.query_start_loc.gpu[: num_reqs + 1]
1533 - query_start_loc_cpu = self.query_start_loc.cpu[: num_reqs + 1]
1534 - seq_lens = self.seq_lens.gpu[:num_reqs]
1535 - seq_lens_cpu = self.seq_lens.cpu[:num_reqs]
1536 - num_computed_tokens_cpu = self.input_batch.num_computed_tokens_cpu_tensor[
1537 - :num_reqs
1538 - ]
1539 -
1540 - dcp_local_seq_lens, dcp_local_seq_lens_cpu = None, None
1541 - if self.dcp_world_size > 1:
1542 - dcp_local_seq_lens = self.dcp_local_seq_lens.gpu[:num_reqs]
1543 - dcp_local_seq_lens_cpu = self.dcp_local_seq_lens.cpu[:num_reqs]
1544 -
1545 - spec_decode_common_attn_metadata = None
1546 -
1508 if for_cudagraph_capture:
1509 # For some attention backends (e.g. FA) with sliding window models we need
1510 # to make sure the backend see a max_seq_len that is larger to the sliding
@@ -1559,6 +1520,22 @@ class GPUModelRunner(
1520 self.num_accepted_tokens.np[num_reqs:].fill(1)
1521 self.num_accepted_tokens.copy_to_gpu()
1522
1523 + # Used in the below loop, uses padded shapes
1524 + query_start_loc = self.query_start_loc.gpu[: num_reqs_padded + 1]
1525 + query_start_loc_cpu = self.query_start_loc.cpu[: num_reqs_padded + 1]
1526 + seq_lens = self.seq_lens.gpu[:num_reqs_padded]
1527 + seq_lens_cpu = self.seq_lens.cpu[:num_reqs_padded]
1528 + num_computed_tokens_cpu = self.input_batch.num_computed_tokens_cpu_tensor[
1529 + :num_reqs_padded
1530 + ]
1531 +
1532 + dcp_local_seq_lens, dcp_local_seq_lens_cpu = None, None
1533 + if self.dcp_world_size > 1:
1534 + dcp_local_seq_lens = self.dcp_local_seq_lens.gpu[:num_reqs_padded]
1535 + dcp_local_seq_lens_cpu = self.dcp_local_seq_lens.cpu[:num_reqs_padded]
1536 +
1537 + spec_decode_common_attn_metadata = None
1538 +
1539 # Prepare the attention metadata for each KV cache group and make layers
1540 # in the same group share the same metadata.
1541 for kv_cache_gid, kv_cache_group in enumerate(
@@ -1567,30 +1544,31 @@ class GPUModelRunner(
1544 encoder_seq_lens, encoder_seq_lens_cpu = self._get_encoder_seq_lens(
1545 num_scheduled_tokens or {},
1546 kv_cache_group.kv_cache_spec,
1570 - num_reqs,
1547 + num_reqs_padded,
1548 )
1549
1550 if isinstance(kv_cache_group.kv_cache_spec, EncoderOnlyAttentionSpec):
1551 # Encoder-only layers do not have KV cache, so we need to
1552 # create a dummy block table and slot mapping for them.
1553 blk_table_tensor = torch.zeros(
1577 - (num_reqs, 1),
1554 + (num_tokens_padded, 1),
1555 dtype=torch.int32,
1556 device=self.device,
1557 )
1558 slot_mapping = torch.zeros(
1582 - (total_num_scheduled_tokens,),
1559 + (num_tokens_padded,),
1560 dtype=torch.int64,
1561 device=self.device,
1562 )
1563 else:
1564 blk_table = self.input_batch.block_table[kv_cache_gid]
1588 - blk_table_tensor = blk_table.get_device_tensor(num_reqs)
1589 - slot_mapping = blk_table.slot_mapping.gpu[:total_num_scheduled_tokens]
1565 + blk_table_tensor = blk_table.get_device_tensor(num_reqs_padded)
1566 + slot_mapping = blk_table.slot_mapping.gpu[:num_tokens_padded]
1567
1568 # Fill unused with -1. Needed for reshape_and_cache in full cuda
1592 - # graph mode.
1593 - blk_table.slot_mapping.gpu[total_num_scheduled_tokens:].fill_(-1)
1569 + # graph mode. `blk_table_tensor` -1 to match mamba PAD_SLOT_ID
1570 + slot_mapping[num_tokens:num_tokens_padded].fill_(-1)
1571 + blk_table_tensor[num_reqs:num_reqs_padded].fill_(-1)
1572
1573 common_attn_metadata = CommonAttentionMetadata(
1574 query_start_loc=query_start_loc,
@@ -1598,9 +1576,9 @@ class GPUModelRunner(
1576 seq_lens=seq_lens,
1577 seq_lens_cpu=seq_lens_cpu,
1578 num_computed_tokens_cpu=num_computed_tokens_cpu,
1601 - num_reqs=num_reqs,
1602 - num_actual_tokens=total_num_scheduled_tokens,
1603 - max_query_len=max_num_scheduled_tokens,
1579 + num_actual_tokens=num_tokens_padded,
1580 + num_reqs=num_reqs_padded,
1581 + max_query_len=max_query_len,
1582 max_seq_len=max_seq_len,
1583 block_table_tensor=blk_table_tensor,
1584 slot_mapping=slot_mapping,
@@ -1631,9 +1609,11 @@ class GPUModelRunner(
1609 extra_attn_metadata_args = {}
1610 if use_spec_decode and isinstance(builder, GDNAttentionMetadataBuilder):
1611 extra_attn_metadata_args = dict(
1634 - num_accepted_tokens=self.num_accepted_tokens.gpu[:num_reqs],
1612 + num_accepted_tokens=self.num_accepted_tokens.gpu[
1613 + :num_reqs_padded
1614 + ],
1615 num_decode_draft_tokens_cpu=self.num_decode_draft_tokens.cpu[
1636 - :num_reqs
1616 + :num_reqs_padded
1617 ],
1618 )
1619
@@ -1677,6 +1657,7 @@ class GPUModelRunner(
1657 def _compute_cascade_attn_prefix_lens(
1658 self,
1659 num_scheduled_tokens: np.ndarray,
1660 + num_computed_tokens: np.ndarray,
1661 num_common_prefix_blocks: list[int],
1662 ) -> list[list[int]] | None:
1663 """
@@ -1699,6 +1680,7 @@ class GPUModelRunner(
1680 # 0 if cascade attention should not be used
1681 cascade_attn_prefix_len = self._compute_cascade_attn_prefix_len(
1682 num_scheduled_tokens,
1683 + num_computed_tokens,
1684 num_common_prefix_blocks[kv_cache_gid],
1685 attn_group.kv_cache_spec,
1686 attn_group.get_metadata_builder(),
@@ -1711,6 +1693,7 @@ class GPUModelRunner(
1693 def _compute_cascade_attn_prefix_len(
1694 self,
1695 num_scheduled_tokens: np.ndarray,
1696 + num_computed_tokens: np.ndarray,
1697 num_common_prefix_blocks: int,
1698 kv_cache_spec: KVCacheSpec,
1699 attn_metadata_builder: AttentionMetadataBuilder,
@@ -1777,10 +1760,7 @@ class GPUModelRunner(
1760 # and the second kernel will get an empty input. While this is not
1761 # a fundamental problem, our current implementation does not support
1762 # this case.
1780 - num_reqs = len(num_scheduled_tokens)
1781 - common_prefix_len = min(
1782 - common_prefix_len, self.input_batch.num_computed_tokens_cpu[:num_reqs].min()
1783 - )
1763 + common_prefix_len = min(common_prefix_len, num_computed_tokens.min())
1764 # common_prefix_len should be a multiple of the block size.
1765 common_prefix_len = (
1766 common_prefix_len // kv_cache_spec.block_size * kv_cache_spec.block_size
@@ -2334,19 +2314,6 @@ class GPUModelRunner(
2314 log_stats=self.parallel_config.eplb_config.log_balancedness,
2315 )
2316
2337 - # This is where the second ubatch is adjusted to account for the padding.
2338 - # Should be called after attention metadata creation. This just pads
2339 - # the second ubatch slice out to the total number of tokens
2340 - # (num_tokens + padding)
2341 - @staticmethod
2342 - def pad_out_ubatch_slice(ubatch_slices: UBatchSlices, num_total_tokens: int):
2343 - padded_second_ubatch_slice = slice(
2344 - ubatch_slices[1].token_slice.start, num_total_tokens
2345 - )
2346 - ubatch_slices[1] = UBatchSlice(
2347 - padded_second_ubatch_slice, padded_second_ubatch_slice
2348 - )
2349 -
2317 def _pool(
2318 self,
2319 hidden_states: torch.Tensor,
@@ -2391,18 +2358,7 @@ class GPUModelRunner(
2358 pooler_output=pooler_output,
2359 )
2360
2394 - def _get_num_input_tokens(self, num_scheduled_tokens: int) -> int:
2395 - if (
2396 - self.compilation_config.cudagraph_mode != CUDAGraphMode.NONE
2397 - and hasattr(self, "cudagraph_batch_sizes")
2398 - and self.cudagraph_batch_sizes
2399 - and num_scheduled_tokens <= self.cudagraph_batch_sizes[-1]
2400 - ):
2401 - # Use CUDA graphs.
2402 - # Add padding to the batch size.
2403 - return self.vllm_config.pad_for_cudagraph(num_scheduled_tokens)
2404 -
2405 - # Eager mode.
2361 + def _pad_for_sequence_parallelism(self, num_scheduled_tokens: int) -> int:
2362 # Pad tokens to multiple of tensor_parallel_size when
2363 # enabled collective fusion for SP
2364 tp_size = self.vllm_config.parallel_config.tensor_parallel_size
@@ -2738,6 +2694,87 @@ class GPUModelRunner(
2694 **model_kwargs,
2695 )
2696
2697 + def _determine_batch_execution_and_padding(
2698 + self,
2699 + num_tokens: int,
2700 + num_reqs: int,
2701 + num_scheduled_tokens_np: np.ndarray,
2702 + max_num_scheduled_tokens: int,
2703 + use_cascade_attn: bool,
2704 + allow_microbatching: bool = True,
2705 + force_eager: bool = False,
2706 + # For cudagraph capture TODO(lucas): Refactor how we capture cudagraphs (will
2707 + # be improved in model runner v2)
2708 + force_uniform_decode: bool | None = None,
2709 + force_has_lora: bool | None = None,
2710 + ) -> tuple[
2711 + CUDAGraphMode, BatchDescriptor, UBatchSlices | None, torch.Tensor | None
2712 + ]:
2713 + num_tokens_padded = self._pad_for_sequence_parallelism(num_tokens)
2714 + uniform_decode = (
2715 + (
2716 + (max_num_scheduled_tokens == self.uniform_decode_query_len)
2717 + and (num_tokens_padded == max_num_scheduled_tokens * num_reqs)
2718 + )
2719 + if force_uniform_decode is None
2720 + else force_uniform_decode
2721 + )
2722 +
2723 + has_lora = (
2724 + len(self.input_batch.lora_id_to_lora_request) > 0
2725 + if force_has_lora is None
2726 + else force_has_lora
2727 + )
2728 +
2729 + dispatch_cudagraph = (
2730 + lambda num_tokens: self.cudagraph_dispatcher.dispatch(
2731 + num_tokens=num_tokens,
2732 + has_lora=has_lora,
2733 + use_cascade_attn=use_cascade_attn,
2734 + uniform_decode=uniform_decode,
2735 + )
2736 + if not force_eager
2737 + else (CUDAGraphMode.NONE, BatchDescriptor(num_tokens_padded))
2738 + )
2739 +
2740 + cudagraph_mode, batch_descriptor = dispatch_cudagraph(num_tokens_padded)
2741 + num_tokens_padded = batch_descriptor.num_tokens
2742 +
2743 + # Extra coordination when running data-parallel since we need to coordinate
2744 + # across ranks
2745 + ubatch_slices, num_tokens_across_dp = None, None
2746 + if self.vllm_config.parallel_config.data_parallel_size > 1:
2747 + # Disable DP padding when running eager to avoid excessive padding when
2748 + # running prefills. This lets us set cudagraph_mode="NONE" on the prefiller
2749 + # in a P/D setup and still use CUDA graphs (enabled by this padding) on the
2750 + # decoder.
2751 + allow_dp_padding = (
2752 + self.compilation_config.cudagraph_mode != CUDAGraphMode.NONE
2753 + )
2754 +
2755 + ubatch_slices, num_tokens_across_dp = coordinate_batch_across_dp(
2756 + num_tokens_unpadded=num_tokens_padded,
2757 + parallel_config=self.parallel_config,
2758 + allow_microbatching=allow_microbatching,
2759 + allow_dp_padding=allow_dp_padding,
2760 + num_tokens_padded=num_tokens_padded,
2761 + uniform_decode=uniform_decode,
2762 + num_scheduled_tokens_per_request=num_scheduled_tokens_np,
2763 + )
2764 +
2765 + # Extract DP padding if there is any
2766 + if num_tokens_across_dp is not None:
2767 + dp_rank = self.parallel_config.data_parallel_rank
2768 + num_tokens_padded = int(num_tokens_across_dp[dp_rank].item())
2769 +
2770 + # Re-dispatch with DP padding
2771 + cudagraph_mode, batch_descriptor = dispatch_cudagraph(num_tokens_padded)
2772 + # Assert to make sure the agreed upon token count is correct otherwise
2773 + # num_tokens_across_dp will no-longer be valid
2774 + assert batch_descriptor.num_tokens == num_tokens_padded
2775 +
2776 + return cudagraph_mode, batch_descriptor, ubatch_slices, num_tokens_across_dp
2777 +
2778 @torch.inference_mode()
2779 def execute_model(
2780 self,
@@ -2790,7 +2827,7 @@ class GPUModelRunner(
2827 # returns True. before returning early here we call
2828 # dummy run to ensure coordinate_batch_across_dp
2829 # is called into to avoid out of sync issues.
2793 - self._dummy_run(self._get_num_input_tokens(1))
2830 + self._dummy_run(1)
2831 if not has_kv_transfer_group():
2832 # Return empty ModelRunnerOutput if no work to do.
2833 return EMPTY_MODEL_RUNNER_OUTPUT
@@ -2809,36 +2846,63 @@ class GPUModelRunner(
2846 tokens = [scheduler_output.num_scheduled_tokens[i] for i in req_ids]
2847 num_scheduled_tokens_np = np.array(tokens, dtype=np.int32)
2848 max_num_scheduled_tokens = int(num_scheduled_tokens_np.max())
2849 + num_tokens_unpadded = scheduler_output.total_num_scheduled_tokens
2850
2851 (
2852 logits_indices,
2853 spec_decode_metadata,
2816 - ubatch_slices,
2817 - num_tokens_across_dp,
2854 ) = self._prepare_inputs(
2819 - scheduler_output, num_scheduled_tokens_np, max_num_scheduled_tokens
2855 + scheduler_output,
2856 + num_scheduled_tokens_np,
2857 )
2858
2859 cascade_attn_prefix_lens = None
2860 # Disable cascade attention when using microbatching (DBO)
2824 - if self.cascade_attn_enabled and ubatch_slices is None:
2861 + if self.cascade_attn_enabled and not self.parallel_config.enable_dbo:
2862 # Pre-compute cascade attention prefix lengths
2826 - # NOTE: Must be AFTER _prepare_inputs uses self.input_batch state
2863 cascade_attn_prefix_lens = self._compute_cascade_attn_prefix_lens(
2864 num_scheduled_tokens_np,
2865 + self.input_batch.num_computed_tokens_cpu[:num_reqs],
2866 scheduler_output.num_common_prefix_blocks,
2867 )
2868
2832 - # TODO(lucas): move cudagraph dispatching here:
2833 - # https://github.com/vllm-project/vllm/issues/23789
2869 + (
2870 + cudagraph_mode,
2871 + batch_desc,
2872 + ubatch_slices,
2873 + num_tokens_across_dp,
2874 + ) = self._determine_batch_execution_and_padding(
2875 + num_tokens=num_tokens_unpadded,
2876 + num_reqs=num_reqs,
2877 + num_scheduled_tokens_np=num_scheduled_tokens_np,
2878 + max_num_scheduled_tokens=max_num_scheduled_tokens,
2879 + use_cascade_attn=cascade_attn_prefix_lens is not None,
2880 + )
2881 +
2882 + logger.debug(
2883 + "Running batch with cudagraph_mode: %s, batch_descriptor: %s, "
2884 + "ubatch_slices: %s, num_tokens_across_dp: %s",
2885 + cudagraph_mode,
2886 + batch_desc,
2887 + ubatch_slices,
2888 + num_tokens_across_dp,
2889 + )
2890 +
2891 + num_tokens_padded = batch_desc.num_tokens
2892 + num_reqs_padded = (
2893 + batch_desc.num_reqs if batch_desc.num_reqs is not None else num_reqs
2894 + )
2895
2835 - total_num_scheduled_tokens = scheduler_output.total_num_scheduled_tokens
2896 use_spec_decode = len(scheduler_output.scheduled_spec_decode_tokens) > 0
2837 - attn_metadata, spec_decode_common_attn_metadata = (
2897 + pad_attn = cudagraph_mode == CUDAGraphMode.FULL
2898 +
2899 + (attn_metadata, spec_decode_common_attn_metadata) = (
2900 self._build_attention_metadata(
2839 - total_num_scheduled_tokens=total_num_scheduled_tokens,
2840 - max_num_scheduled_tokens=max_num_scheduled_tokens,
2901 + num_tokens=num_tokens_unpadded,
2902 + num_tokens_padded=num_tokens_padded if pad_attn else None,
2903 num_reqs=num_reqs,
2904 + num_reqs_padded=num_reqs_padded if pad_attn else None,
2905 + max_query_len=max_num_scheduled_tokens,
2906 ubatch_slices=ubatch_slices,
2907 logits_indices=logits_indices,
2908 use_spec_decode=use_spec_decode,
@@ -2847,49 +2911,22 @@ class GPUModelRunner(
2911 )
2912 )
2913
2850 - dp_rank = self.parallel_config.data_parallel_rank
2851 - if ubatch_slices:
2852 - assert num_tokens_across_dp is not None
2853 - num_input_tokens = int(num_tokens_across_dp[dp_rank].item())
2854 - self.pad_out_ubatch_slice(ubatch_slices, num_input_tokens)
2855 - elif num_tokens_across_dp is not None:
2856 - num_input_tokens = int(num_tokens_across_dp[dp_rank].item())
2857 - else:
2858 - num_input_tokens = self._get_num_input_tokens(
2859 - scheduler_output.total_num_scheduled_tokens
2860 - )
2861 -
2862 - (
2863 - input_ids,
2864 - inputs_embeds,
2865 - positions,
2866 - intermediate_tensors,
2867 - model_kwargs,
2868 - ec_connector_output,
2869 - ) = self._preprocess(
2870 - scheduler_output, num_input_tokens, intermediate_tensors
2871 - )
2872 -
2873 - uniform_decode = (
2874 - max_num_scheduled_tokens == self.uniform_decode_query_len
2875 - ) and (num_scheduled_tokens == num_reqs * max_num_scheduled_tokens)
2876 - batch_desc = BatchDescriptor(
2877 - num_tokens=num_input_tokens,
2878 - uniform_decode=uniform_decode,
2879 - has_lora=len(self.input_batch.lora_id_to_lora_request) > 0,
2880 - )
2881 - cudagraph_runtime_mode, batch_descriptor = (
2882 - self.cudagraph_dispatcher.dispatch(
2883 - batch_desc,
2884 - use_cascade_attn=cascade_attn_prefix_lens is not None,
2885 - )
2914 + (
2915 + input_ids,
2916 + inputs_embeds,
2917 + positions,
2918 + intermediate_tensors,
2919 + model_kwargs,
2920 + ec_connector_output,
2921 + ) = self._preprocess(
2922 + scheduler_output, num_tokens_padded, intermediate_tensors
2923 )
2924
2925 # Set cudagraph mode to none if calc_kv_scales is true.
2926 # KV scales calculation involves dynamic operations that are incompatible
2927 # with CUDA graph capture.
2928 if self.calculate_kv_scales:
2892 - cudagraph_runtime_mode = CUDAGraphMode.NONE
2929 + cudagraph_mode = CUDAGraphMode.NONE
2930 # Mark KV scales as calculated after the first forward pass
2931 self.calculate_kv_scales = False
2932
@@ -2899,10 +2936,10 @@ class GPUModelRunner(
2936 set_forward_context(
2937 attn_metadata,
2938 self.vllm_config,
2902 - num_tokens=num_input_tokens,
2939 + num_tokens=num_tokens_padded,
2940 num_tokens_across_dp=num_tokens_across_dp,
2904 - cudagraph_runtime_mode=cudagraph_runtime_mode,
2905 - batch_descriptor=batch_descriptor,
2941 + cudagraph_runtime_mode=cudagraph_mode,
2942 + batch_descriptor=batch_desc,
2943 ubatch_slices=ubatch_slices,
2944 ),
2945 record_function_or_nullcontext("gpu_model_runner: forward"),
@@ -2952,7 +2989,7 @@ class GPUModelRunner(
2989 if not get_pp_group().is_last_rank:
2990 all_gather_tensors = {
2991 "residual": not is_residual_scattered_for_sp(
2955 - self.vllm_config, num_input_tokens
2992 + self.vllm_config, num_tokens_padded
2993 )
2994 }
2995 get_pp_group().send_tensor_dict(
@@ -3841,52 +3878,44 @@ class GPUModelRunner(
3878 assert sum(num_scheduled_tokens_list) == num_tokens
3879 assert len(num_scheduled_tokens_list) == num_reqs
3880 num_scheduled_tokens = np.array(num_scheduled_tokens_list, dtype=np.int32)
3844 - total_num_scheduled_tokens = int(num_scheduled_tokens.sum())
3881 + num_tokens_unpadded = int(num_scheduled_tokens.sum())
3882 +
3883 num_sampled_tokens = np.ones(num_reqs, dtype=np.int32)
3884
3847 - # Disable DP padding when running eager
3848 - allow_dp_padding = self.compilation_config.cudagraph_mode != CUDAGraphMode.NONE
3849 -
3850 - # We currently only microbatch if the number of tokens is
3851 - # over a certain threshold.
3852 - ubatch_slices, num_tokens_across_dp = coordinate_batch_across_dp(
3853 - num_tokens_unpadded=total_num_scheduled_tokens,
3854 - parallel_config=self.vllm_config.parallel_config,
3855 - allow_microbatching=allow_microbatching,
3856 - allow_dp_padding=allow_dp_padding,
3857 - num_tokens_padded=total_num_scheduled_tokens,
3858 - uniform_decode=uniform_decode,
3859 - num_scheduled_tokens_per_request=num_scheduled_tokens,
3860 - )
3861 - num_tokens_after_padding = num_tokens
3862 - if num_tokens_across_dp is not None:
3863 - dp_rank = self.parallel_config.data_parallel_rank
3864 - num_tokens_after_padding = int(num_tokens_across_dp[dp_rank])
3865 -
3866 - # filter out the valid batch descriptor
3867 - _cg_mode, batch_descriptor = (
3868 - self.cudagraph_dispatcher.dispatch(
3869 - BatchDescriptor(
3870 - num_tokens=num_tokens_after_padding,
3871 - uniform_decode=uniform_decode,
3872 - has_lora=activate_lora and self.lora_config is not None,
3873 - )
3885 + _cudagraph_mode, batch_desc, ubatch_slices, num_tokens_across_dp = (
3886 + self._determine_batch_execution_and_padding(
3887 + num_tokens=num_tokens_unpadded,
3888 + num_reqs=num_reqs,
3889 + num_scheduled_tokens_np=num_scheduled_tokens,
3890 + max_num_scheduled_tokens=max_query_len,
3891 + use_cascade_attn=False,
3892 + allow_microbatching=allow_microbatching,
3893 + force_eager=is_profile
3894 + or (cudagraph_runtime_mode == CUDAGraphMode.NONE),
3895 + # `force_uniform_decode` is used for cudagraph capture; because for
3896 + # capturing mixed prefill-decode batches, we sometimes use
3897 + # num_tokens == num_reqs which looks like a uniform decode batch to the
3898 + # dispatcher; but we actually want to capture a piecewise cudagraph
3899 + force_uniform_decode=uniform_decode,
3900 + # `force_has_lora` is used for cudagraph capture; because LoRA is
3901 + # activated later in the context manager, but we need to know the
3902 + # LoRA state when determining the batch descriptor for capture
3903 + force_has_lora=activate_lora,
3904 )
3875 - if not is_profile
3876 - else (CUDAGraphMode.NONE, None)
3905 )
3878 - if cudagraph_runtime_mode is not None:
3879 - # we allow forcing NONE when the dispatcher disagrees to support
3880 - # warm ups for cudagraph capture
3881 - assert (
3882 - cudagraph_runtime_mode == CUDAGraphMode.NONE
3883 - or cudagraph_runtime_mode == _cg_mode
3884 - ), (
3885 - f"Cudagraph runtime mode mismatch at dummy_run. "
3886 - f"Expected {_cg_mode}, but got {cudagraph_runtime_mode}."
3887 - )
3906 +
3907 + if cudagraph_runtime_mode is None:
3908 + cudagraph_runtime_mode = _cudagraph_mode
3909 else:
3889 - cudagraph_runtime_mode = _cg_mode
3910 + assert cudagraph_runtime_mode == _cudagraph_mode, (
3911 + f"Cudagraph runtime mode mismatch in dummy_run. "
3912 + f"Expected {_cudagraph_mode}, but got {cudagraph_runtime_mode}."
3913 + )
3914 +
3915 + num_tokens_padded = batch_desc.num_tokens
3916 + num_reqs_padded = (
3917 + batch_desc.num_reqs if batch_desc.num_reqs is not None else num_reqs
3918 + )
3919
3920 attn_metadata: PerLayerAttnMetadata | None = None
3921
@@ -3909,9 +3938,9 @@ class GPUModelRunner(
3938 self.query_start_loc.copy_to_gpu()
3939
3940 attn_metadata, _ = self._build_attention_metadata(
3912 - total_num_scheduled_tokens=num_tokens,
3913 - max_num_scheduled_tokens=max_query_len,
3914 - num_reqs=num_reqs,
3941 + num_tokens=num_tokens_unpadded,
3942 + num_reqs=num_reqs_padded,
3943 + max_query_len=max_query_len,
3944 ubatch_slices=ubatch_slices,
3945 for_cudagraph_capture=True,
3946 )
@@ -3924,29 +3953,29 @@ class GPUModelRunner(
3953 remove_lora,
3954 ):
3955 # Make sure padding doesn't exceed max_num_tokens
3927 - assert num_tokens_after_padding <= self.max_num_tokens
3928 - model_kwargs = self._init_model_kwargs(num_tokens_after_padding)
3956 + assert num_tokens_padded <= self.max_num_tokens
3957 + model_kwargs = self._init_model_kwargs(num_tokens_padded)
3958 if self.supports_mm_inputs and not self.model_config.is_encoder_decoder:
3959 input_ids = None
3931 - inputs_embeds = self.inputs_embeds.gpu[:num_tokens_after_padding]
3960 + inputs_embeds = self.inputs_embeds.gpu[:num_tokens_padded]
3961 model_kwargs = {
3962 **model_kwargs,
3963 **self._dummy_mm_kwargs(num_reqs),
3964 }
3965 elif self.enable_prompt_embeds:
3966 input_ids = None
3938 - inputs_embeds = self.inputs_embeds.gpu[:num_tokens_after_padding]
3939 - model_kwargs = self._init_model_kwargs(num_tokens_after_padding)
3967 + inputs_embeds = self.inputs_embeds.gpu[:num_tokens_padded]
3968 + model_kwargs = self._init_model_kwargs(num_tokens_padded)
3969 else:
3941 - input_ids = self.input_ids.gpu[:num_tokens_after_padding]
3970 + input_ids = self.input_ids.gpu[:num_tokens_padded]
3971 inputs_embeds = None
3972
3973 if self.uses_mrope:
3945 - positions = self.mrope_positions.gpu[:, :num_tokens_after_padding]
3974 + positions = self.mrope_positions.gpu[:, :num_tokens_padded]
3975 elif self.uses_xdrope_dim > 0:
3947 - positions = self.xdrope_positions.gpu[:, :num_tokens_after_padding]
3976 + positions = self.xdrope_positions.gpu[:, :num_tokens_padded]
3977 else:
3949 - positions = self.positions.gpu[:num_tokens_after_padding]
3978 + positions = self.positions.gpu[:num_tokens_padded]
3979
3980 if get_pp_group().is_first_rank:
3981 intermediate_tensors = None
@@ -3961,26 +3990,26 @@ class GPUModelRunner(
3990 )
3991
3992 intermediate_tensors = self.sync_and_slice_intermediate_tensors(
3964 - num_tokens_after_padding, None, False
3993 + num_tokens_padded, None, False
3994 )
3995
3996 if ubatch_slices is not None:
3997 # Adjust values to reflect a single ubatch.
3998 # TODO(sage,lucas): this is cruft that should be addressed in
3999 # the padding refactor.
3971 - num_tokens_after_padding = ubatch_slices[0].num_tokens
4000 + num_tokens_padded = ubatch_slices[0].num_tokens
4001 if num_tokens_across_dp is not None:
3973 - num_tokens_across_dp[:] = num_tokens_after_padding
4002 + num_tokens_across_dp[:] = num_tokens_padded
4003
4004 with (
4005 self.maybe_randomize_inputs(input_ids),
4006 set_forward_context(
4007 attn_metadata,
4008 self.vllm_config,
3980 - num_tokens=num_tokens_after_padding,
4009 + num_tokens=num_tokens_padded,
4010 num_tokens_across_dp=num_tokens_across_dp,
4011 cudagraph_runtime_mode=cudagraph_runtime_mode,
3983 - batch_descriptor=batch_descriptor,
4012 + batch_descriptor=batch_desc,
4013 ubatch_slices=ubatch_slices,
4014 ),
4015 ):
@@ -4706,8 +4735,7 @@ class GPUModelRunner(
4735
4736 # Trigger cudagraph dispatching keys initialization after
4737 # resolved cudagraph mode.
4709 - cudagraph_mode = self.compilation_config.cudagraph_mode
4710 - assert cudagraph_mode is not None
4738 + self.compilation_config.cudagraph_mode = cudagraph_mode
4739 self.cudagraph_dispatcher.initialize_cudagraph_keys(
4740 cudagraph_mode, self.uniform_decode_query_len
4741 )
vllm/v1/worker/gpu_worker.py
+35 -6
@@ -8,12 +8,13 @@ from contextlib import AbstractContextManager, nullcontext
8 from types import NoneType
9 from typing import TYPE_CHECKING, Any, cast
10
11 +import numpy as np
12 import torch
13 import torch.distributed
14 import torch.nn as nn
15
16 import vllm.envs as envs
16 -from vllm.config import VllmConfig
17 +from vllm.config import CUDAGraphMode, VllmConfig
18 from vllm.distributed import (
19 ensure_model_parallel_initialized,
20 init_distributed_environment,
@@ -487,6 +488,7 @@ class Worker(WorkerBase):
488 hidden_states, last_hidden_states = self.model_runner._dummy_run(
489 num_tokens=max_num_reqs,
490 skip_eplb=True,
491 + cudagraph_runtime_mode=CUDAGraphMode.NONE,
492 )
493 if self.model_runner.is_pooling_model:
494 self.model_runner._dummy_pooler_run(hidden_states)
@@ -534,12 +536,39 @@ class Worker(WorkerBase):
536 intermediate_tensors = None
537 forward_pass = scheduler_output.total_num_scheduled_tokens > 0
538 num_scheduled_tokens = scheduler_output.total_num_scheduled_tokens
537 - num_input_tokens = self.model_runner._get_num_input_tokens(num_scheduled_tokens)
538 - all_gather_tensors = {
539 - "residual": not is_residual_scattered_for_sp(
540 - self.vllm_config, num_input_tokens
539 + all_gather_tensors = {}
540 + compilation_config = self.vllm_config.compilation_config
541 + parallel_config = self.vllm_config.parallel_config
542 +
543 + if (
544 + parallel_config.pipeline_parallel_size > 1
545 + and compilation_config.pass_config.enable_sequence_parallelism
546 + and forward_pass
547 + ):
548 + # currently only supported by V1 GPUModelRunner
549 + assert isinstance(self.model_runner, GPUModelRunner)
550 + num_scheduled_tokens_np = np.array(
551 + list(scheduler_output.num_scheduled_tokens.values()),
552 + dtype=np.int32,
553 )
542 - }
554 + # TODO(lucas): This is pretty gross; ideally we should only ever call
555 + # `_determine_batch_execution_and_padding` once (will get called again
556 + # in `execute_model`) but this requires a larger refactor of PP.
557 + _, batch_desc, _, _ = (
558 + self.model_runner._determine_batch_execution_and_padding(
559 + num_tokens=num_scheduled_tokens,
560 + num_reqs=len(num_scheduled_tokens_np),
561 + num_scheduled_tokens_np=num_scheduled_tokens_np,
562 + max_num_scheduled_tokens=num_scheduled_tokens_np.max(),
563 + use_cascade_attn=False, # TODO(lucas): Handle cascade attention
564 + )
565 + )
566 + all_gather_tensors = {
567 + "residual": not is_residual_scattered_for_sp(
568 + self.vllm_config, batch_desc.num_tokens
569 + )
570 + }
571 +
572 if forward_pass and not get_pp_group().is_first_rank:
573 tensor_dict = get_pp_group().recv_tensor_dict(
574 all_gather_group=get_tp_group(),