314
aux_hidden_states: list[torch.Tensor] | None
315
ec_connector_output: ECConnectorOutput | None
316
cudagraph_stats: CUDAGraphStat | None
317
+ slot_mappings: dict[str, torch.Tensor] | list[dict[str, torch.Tensor]] | None
318
319
320
class GPUModelRunner(
1596
for_cudagraph_capture: bool = False,
1597
num_scheduled_tokens: dict[str, int] | None = None,
1598
cascade_attn_prefix_lens: list[list[int]] | None = None,
1599
+ slot_mappings: dict[int, torch.Tensor] | None = None,
1600
) -> tuple[PerLayerAttnMetadata, CommonAttentionMetadata | None]:
1601
"""
1602
:return: tuple[attn_metadata, spec_decode_common_attn_metadata]
1630
1631
kv_cache_groups = self.kv_cache_config.kv_cache_groups
1632
1631
- def _get_block_table_and_slot_mapping(kv_cache_gid: int):
1633
+ def _get_block_table(kv_cache_gid: int):
1634
assert num_reqs_padded is not None and num_tokens_padded is not None
1635
kv_cache_spec = kv_cache_groups[kv_cache_gid].kv_cache_spec
1636
if isinstance(kv_cache_spec, EncoderOnlyAttentionSpec):
1639
dtype=torch.int32,
1640
device=self.device,
1641
)
1640
- slot_mapping = torch.zeros(
1641
- (num_tokens_padded,),
1642
- dtype=torch.int64,
1643
- device=self.device,
1644
- )
1642
else:
1643
blk_table = self.input_batch.block_table[kv_cache_gid]
1644
blk_table_tensor = blk_table.get_device_tensor(num_reqs_padded)
1648
- slot_mapping = blk_table.slot_mapping.gpu[:num_tokens_padded]
1645
1646
# Fill unused with -1. Needed for reshape_and_cache in full cuda
1647
# graph mode. `blk_table_tensor` -1 to match mamba PAD_SLOT_ID
1652
- slot_mapping[num_tokens:num_tokens_padded].fill_(-1)
1648
blk_table_tensor[num_reqs:num_reqs_padded].fill_(-1)
1649
+ return blk_table_tensor
1650
1655
- return blk_table_tensor, slot_mapping
1651
+ assert slot_mappings is not None
1652
+ block_table_gid_0 = _get_block_table(0)
1653
+ slot_mapping_gid_0 = slot_mappings[0]
1654
1657
- block_table_gid_0, slot_mapping_gid_0 = _get_block_table_and_slot_mapping(0)
1655
if self.model_config.enable_return_routed_experts:
1656
self.slot_mapping = slot_mapping_gid_0[:num_tokens].cpu().numpy()
1657
cm_base = CommonAttentionMetadata(
1776
num_reqs_padded,
1777
)
1778
if kv_cache_gid > 0:
1782
- cm.block_table_tensor, cm.slot_mapping = (
1783
- _get_block_table_and_slot_mapping(kv_cache_gid)
1784
- )
1779
+ cm.block_table_tensor = _get_block_table(kv_cache_gid)
1780
+ cm.slot_mapping = slot_mappings[kv_cache_gid]
1781
1782
if self.speculative_config and spec_decode_common_attn_metadata is None:
1783
if isinstance(self.drafter, EagleProposer):
3115
pyt_hooks.register_hooks(self.model, self.model.__class__.__name__)
3116
self.layerwise_nvtx_hooks_registered = True
3117
3118
+ def _get_slot_mappings(
3119
+ self,
3120
+ num_tokens_padded: int,
3121
+ num_reqs_padded: int,
3122
+ num_tokens_unpadded: int,
3123
+ ubatch_slices: "UBatchSlices | None" = None,
3124
+ ) -> tuple[
3125
+ dict[int, torch.Tensor] | None,
3126
+ dict[str, torch.Tensor] | list[dict[str, torch.Tensor]] | None,
3127
+ ]:
3128
+ """
3129
+ Build slot mappings in both formats needed by the system.
3130
+
3131
+ Args:
3132
+ num_tokens_padded: Total number of tokens (padded)
3133
+ num_reqs_padded: Total number of requests (padded)
3134
+ num_tokens_unpadded: Actual number of tokens (unpadded)
3135
+ ubatch_slices: Optional ubatch slicing info for DBO
3136
+
3137
+ Returns:
3138
+ A tuple of:
3139
+ - slot_mappings_by_gid: dict[int, torch.Tensor] for attention metadata
3140
+ - slot_mappings_by_layer: dict[str, torch.Tensor] or list for ForwardContext
3141
+ """
3142
+ if not (
3143
+ hasattr(self, "kv_cache_config")
3144
+ and self.kv_cache_config is not None
3145
+ and len(self.kv_cache_config.kv_cache_groups) > 0
3146
+ ):
3147
+ return None, None
3148
+
3149
+ def _get_slot_mapping(kv_cache_gid: int):
3150
+ assert num_reqs_padded is not None and num_tokens_padded is not None
3151
+ kv_cache_spec = self.kv_cache_config.kv_cache_groups[
3152
+ kv_cache_gid
3153
+ ].kv_cache_spec
3154
+ if isinstance(kv_cache_spec, EncoderOnlyAttentionSpec):
3155
+ slot_mapping = torch.zeros(
3156
+ (num_tokens_padded,),
3157
+ dtype=torch.int64,
3158
+ device=self.device,
3159
+ )
3160
+ else:
3161
+ blk_table = self.input_batch.block_table[kv_cache_gid]
3162
+ slot_mapping = blk_table.slot_mapping.gpu[:num_tokens_padded]
3163
+
3164
+ # Fill unused with -1. Needed for reshape_and_cache in full cuda
3165
+ # graph mode. `blk_table_tensor` -1 to match mamba PAD_SLOT_ID
3166
+ slot_mapping[num_tokens_unpadded:num_tokens_padded].fill_(-1)
3167
+
3168
+ return slot_mapping
3169
+
3170
+ slot_mappings_by_gid = {
3171
+ gid: _get_slot_mapping(gid)
3172
+ for gid, _ in enumerate(self.kv_cache_config.kv_cache_groups)
3173
+ }
3174
+
3175
+ slot_mappings_by_layer: dict[str, torch.Tensor] = {}
3176
+ for gid, kv_cache_group in enumerate(self.kv_cache_config.kv_cache_groups):
3177
+ slot_mapping = slot_mappings_by_gid[gid]
3178
+ for layer_name in kv_cache_group.layer_names:
3179
+ slot_mappings_by_layer[layer_name] = slot_mapping
3180
+
3181
+ if ubatch_slices is not None:
3182
+ result: list[dict[str, torch.Tensor]] = []
3183
+ for ubatch in ubatch_slices:
3184
+ sliced_mappings: dict[str, torch.Tensor] = {}
3185
+ for layer_name, slot_mapping in slot_mappings_by_layer.items():
3186
+ sliced_mappings[layer_name] = slot_mapping[ubatch.token_slice]
3187
+ result.append(sliced_mappings)
3188
+ return slot_mappings_by_gid, result
3189
+
3190
+ return slot_mappings_by_gid, slot_mappings_by_layer
3191
+
3192
@torch.inference_mode()
3193
def execute_model(
3194
self,
3318
ubatch_slices_padded,
3319
)
3320
3321
+ # True if any attention backend handles KV cache update separately
3322
+ # from forward() (i.e., forward_includes_kv_cache_update=False). When true,
3323
+ # slot_mappings must use padded dimensions to match the key/value tensors.
3324
+ has_separate_kv_update = not all(
3325
+ all(
3326
+ g.backend.forward_includes_kv_cache_update
3327
+ for g in self.attn_groups[id]
3328
+ )
3329
+ for id, spec in enumerate(self.kv_cache_config.kv_cache_groups)
3330
+ if not isinstance(spec.kv_cache_spec, EncoderOnlyAttentionSpec)
3331
+ )
3332
pad_attn = cudagraph_mode == CUDAGraphMode.FULL
3333
3334
if self.cache_config.mamba_cache_mode == "align":
3346
use_spec_decode = len(scheduler_output.scheduled_spec_decode_tokens) > 0
3347
ubatch_slices_attn = ubatch_slices_padded if pad_attn else ubatch_slices
3348
3349
+ slot_mappings_by_group, slot_mappings = self._get_slot_mappings(
3350
+ num_tokens_padded=num_tokens_padded
3351
+ if pad_attn or has_separate_kv_update
3352
+ else num_tokens_unpadded,
3353
+ num_reqs_padded=(
3354
+ num_reqs_padded if pad_attn or has_separate_kv_update else num_reqs
3355
+ ),
3356
+ num_tokens_unpadded=num_tokens_unpadded,
3357
+ ubatch_slices=ubatch_slices_padded,
3358
+ )
3359
+
3360
attn_metadata, spec_decode_common_attn_metadata = (
3361
self._build_attention_metadata(
3362
num_tokens=num_tokens_unpadded,
3369
use_spec_decode=use_spec_decode,
3370
num_scheduled_tokens=scheduler_output.num_scheduled_tokens,
3371
cascade_attn_prefix_lens=cascade_attn_prefix_lens,
3372
+ slot_mappings=slot_mappings_by_group,
3373
)
3374
)
3375
3410
cudagraph_runtime_mode=cudagraph_mode,
3411
batch_descriptor=batch_desc,
3412
ubatch_slices=ubatch_slices_padded,
3413
+ slot_mapping=slot_mappings,
3414
skip_compiled=has_encoder_input,
3415
),
3416
record_function_or_nullcontext("gpu_model_runner: forward"),
3493
aux_hidden_states,
3494
ec_connector_output,
3495
cudagraph_stats,
3496
+ slot_mappings,
3497
)
3498
self.kv_connector_output = kv_connector_output
3499
return None
3530
aux_hidden_states,
3531
ec_connector_output,
3532
cudagraph_stats,
3533
+ slot_mappings,
3534
) = self.execute_model_state
3535
# Clear ephemeral state.
3536
self.execute_model_state = None
3564
aux_hidden_states,
3565
spec_decode_metadata,
3566
spec_decode_common_attn_metadata,
3567
+ slot_mappings,
3568
)
3569
self._copy_draft_token_ids_to_cpu(scheduler_output)
3570
3773
aux_hidden_states: list[torch.Tensor] | None,
3774
spec_decode_metadata: SpecDecodeMetadata | None,
3775
common_attn_metadata: CommonAttentionMetadata,
3776
+ slot_mappings: dict[str, torch.Tensor] | list[dict[str, torch.Tensor]] | None,
3777
) -> list[list[int]] | torch.Tensor:
3778
num_scheduled_tokens = scheduler_output.total_num_scheduled_tokens
3779
spec_config = self.speculative_config
3785
sampled_token_ids,
3786
self.input_batch.num_tokens_no_spec,
3787
self.input_batch.token_ids_cpu,
3788
+ slot_mappings=slot_mappings,
3789
)
3790
elif spec_config.method == "suffix":
3791
assert isinstance(sampled_token_ids, list)
3792
assert isinstance(self.drafter, SuffixDecodingProposer)
3694
- draft_token_ids = self.drafter.propose(self.input_batch, sampled_token_ids)
3793
+ draft_token_ids = self.drafter.propose(
3794
+ self.input_batch, sampled_token_ids, slot_mappings=slot_mappings
3795
+ )
3796
elif spec_config.method == "medusa":
3797
assert isinstance(sampled_token_ids, list)
3798
assert isinstance(self.drafter, MedusaProposer)
3817
draft_token_ids = self.drafter.propose(
3818
target_hidden_states=hidden_states,
3819
sampling_metadata=sampling_metadata,
3820
+ slot_mappings=slot_mappings,
3821
)
3822
elif spec_config.use_eagle() or spec_config.uses_draft_model():
3823
assert isinstance(self.drafter, EagleProposer | DraftModelProposer)
3928
common_attn_metadata=common_attn_metadata,
3929
mm_embed_inputs=mm_embed_inputs,
3930
num_rejected_tokens_gpu=num_rejected_tokens_gpu,
3931
+ slot_mappings=slot_mappings,
3932
)
3933
3934
return draft_token_ids
4509
4510
attn_metadata: PerLayerAttnMetadata | None = None
4511
4512
+ slot_mappings_by_group, slot_mappings = self._get_slot_mappings(
4513
+ num_tokens_padded=num_tokens,
4514
+ num_reqs_padded=num_reqs_padded,
4515
+ num_tokens_unpadded=num_tokens_unpadded,
4516
+ ubatch_slices=ubatch_slices_padded,
4517
+ )
4518
+
4519
# If force_attention is True, we always capture attention. Otherwise,
4520
# it only happens for cudagraph_runtime_mode=FULL.
4521
if force_attention or cudagraph_runtime_mode == CUDAGraphMode.FULL:
4541
max_query_len=max_query_len,
4542
ubatch_slices=ubatch_slices_padded if pad_attn else ubatch_slices,
4543
for_cudagraph_capture=is_graph_capturing,
4544
+ slot_mappings=slot_mappings_by_group,
4545
)
4546
4547
with self.maybe_dummy_run_with_lora(
4610
cudagraph_runtime_mode=cudagraph_runtime_mode,
4611
batch_descriptor=batch_desc,
4612
ubatch_slices=ubatch_slices_padded,
4613
+ slot_mapping=slot_mappings,
4614
),
4615
):
4616
outputs = self.model(
4657
num_tokens,
4658
use_cudagraphs=use_cudagraphs,
4659
is_graph_capturing=is_graph_capturing,
4660
+ slot_mappings=slot_mappings,
4661
)
4662
4663
# We register layerwise NVTX hooks here after the first dynamo tracing is