@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / fbfc3ee37e
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 15, 21:00 UTC

[V1][TPU] TPU multimodal model support for ragged attention (#14158)

Signed-off-by: Michael Goin <mgoin64@gmail.com>

Michael Goin committed Mar 4, 2025 at 19:58 UTC fbfc3ee37efc09c18b63f48d2813ec17e49a73d2
2 files changed +194 -30
vllm/v1/worker/tpu_model_runner.py
+193 -29
@@ -15,14 +15,18 @@ from vllm.attention.backends.abstract import AttentionType
15 from vllm.attention.layer import Attention
16 from vllm.config import VllmConfig
17 from vllm.forward_context import get_forward_context, set_forward_context
18 +from vllm.inputs import INPUT_REGISTRY
19 from vllm.logger import init_logger
20 from vllm.model_executor.model_loader import get_model
21 +from vllm.multimodal import MULTIMODAL_REGISTRY, MultiModalKwargs
22 +from vllm.multimodal.utils import group_mm_inputs_by_modality
23 from vllm.sampling_params import SamplingType
24 from vllm.utils import LayerBlockType, cdiv, is_pin_memory_available
25 from vllm.v1.attention.backends.pallas import (NUM_KV_PAGES_PER_BLOCK,
26 NUM_QUERIES_PER_BLOCK,
27 PallasAttentionBackend,
28 PallasMetadata)
29 +from vllm.v1.core.encoder_cache_manager import compute_encoder_budget
30 from vllm.v1.kv_cache_interface import (FullAttentionSpec, KVCacheConfig,
31 KVCacheSpec)
32 from vllm.v1.outputs import LogprobsTensors, ModelRunnerOutput
@@ -72,8 +76,10 @@ class TPUModelRunner:
76 self.block_size = cache_config.block_size
77 self.max_model_len = model_config.max_model_len
78 self.max_num_blocks_per_req = cdiv(self.max_model_len, self.block_size)
75 - self.max_num_tokens = scheduler_config.max_num_batched_tokens
76 - self.max_num_reqs = scheduler_config.max_num_seqs
79 + self.max_num_tokens = _get_padded_number(
80 + scheduler_config.max_num_batched_tokens, NUM_QUERIES_PER_BLOCK)
81 + self.max_num_reqs = _get_padded_number(scheduler_config.max_num_seqs,
82 + NUM_QUERIES_PER_BLOCK)
83
84 # Model-related.
85 self.num_attn_layers = model_config.get_num_layers_by_block_type(
@@ -84,6 +90,28 @@ class TPUModelRunner:
90 self.head_size = model_config.get_head_size()
91 self.hidden_size = model_config.get_hidden_size()
92
93 + # Multi-modal data support
94 + self.input_registry = INPUT_REGISTRY
95 + self.mm_registry = MULTIMODAL_REGISTRY
96 + self.uses_mrope = model_config.uses_mrope
97 + # TODO: Support M-RoPE (e.g, Qwen2-VL)
98 + assert not self.uses_mrope, "TPU does not support M-RoPE yet."
99 +
100 + encoder_compute_budget, encoder_cache_size = compute_encoder_budget(
101 + model_config=model_config,
102 + scheduler_config=scheduler_config,
103 + )
104 + self.max_num_encoder_input_tokens = encoder_compute_budget
105 + self.encoder_cache_size = encoder_cache_size
106 +
107 + # Lazy initialization
108 + # self.model: nn.Module # Set after load_model
109 + self.kv_caches: list[torch.Tensor] = []
110 + # req_id -> (input_id -> encoder_output)
111 + self.encoder_cache: dict[str, dict[int, torch.Tensor]] = {}
112 +
113 + # Request states.
114 + self.requests: dict[str, CachedRequestState] = {}
115 # Persistent batch.
116 self.input_batch = InputBatch(
117 max_num_reqs=self.max_num_reqs,
@@ -91,18 +119,9 @@ class TPUModelRunner:
119 max_num_blocks_per_req=self.max_num_blocks_per_req,
120 device=self.device,
121 pin_memory=self.pin_memory,
94 - vocab_size=self.model_config.get_vocab_size(),
122 + vocab_size=model_config.get_vocab_size(),
123 )
124
97 - # Request states.
98 - self.requests: dict[str, CachedRequestState] = {}
99 -
100 - # req_id -> (input_id -> encoder_output)
101 - self.encoder_cache: dict[str, dict[int, torch.Tensor]] = {}
102 -
103 - # KV caches for forward pass
104 - self.kv_caches: list[tuple[torch.Tensor, torch.Tensor]] = []
105 -
125 # Cached torch/numpy tensor
126 # The pytorch tensor and numpy array share the same buffer.
127 # Sometimes the numpy op is faster so we create both.
@@ -164,6 +183,7 @@ class TPUModelRunner:
183 # Remove finished requests from the cached states.
184 for req_id in scheduler_output.finished_req_ids:
185 self.requests.pop(req_id, None)
186 + self.encoder_cache.pop(req_id, None)
187
188 # Remove the finished requests from the persistent batch.
189 # NOTE(woosuk): There could be an edge case where finished_req_ids and
@@ -177,6 +197,14 @@ class TPUModelRunner:
197 if req_index is not None:
198 removed_req_indices.append(req_index)
199
200 + # Free the cached encoder outputs.
201 + for req_id, input_id in scheduler_output.free_encoder_input_ids:
202 + encoder_outputs = self.encoder_cache.get(req_id)
203 + if encoder_outputs is not None:
204 + encoder_outputs.pop(input_id, None)
205 + if not encoder_outputs:
206 + self.encoder_cache.pop(req_id, None)
207 +
208 # Remove the unscheduled requests from the persistent batch.
209 # NOTE(woosuk): The unscheduled requests are either preempted requests
210 # or running requests that are not scheduled in this step. We remove
@@ -426,6 +454,92 @@ class TPUModelRunner:
454 logits_indices = query_start_loc[1:] - 1
455 return attn_metadata, logits_indices
456
457 + def _execute_encoder(self, scheduler_output: "SchedulerOutput"):
458 + scheduled_encoder_inputs = scheduler_output.scheduled_encoder_inputs
459 + if not scheduled_encoder_inputs:
460 + return
461 +
462 + # Batch the multi-modal inputs.
463 + mm_inputs: list[MultiModalKwargs] = []
464 + req_input_ids: list[tuple[str, int]] = []
465 + for req_id, encoder_input_ids in scheduled_encoder_inputs.items():
466 + req_state = self.requests[req_id]
467 + for input_id in encoder_input_ids:
468 + mm_inputs.append(req_state.mm_inputs[input_id])
469 + req_input_ids.append((req_id, input_id))
470 +
471 + # Batch mm inputs as much as we can: if a request in the batch has
472 + # multiple modalities or a different modality than the previous one,
473 + # we process it separately to preserve item order.
474 + # FIXME(ywang96): This is a hacky way to deal with multiple modalities
475 + # in the same batch while still being able to benefit from batching
476 + # multimodal inputs. The proper solution should be reordering the
477 + # encoder outputs.
478 + grouped_mm_inputs_list = group_mm_inputs_by_modality(mm_inputs)
479 +
480 + encoder_outputs = []
481 + for grouped_mm_inputs in grouped_mm_inputs_list:
482 + batched_mm_inputs = MultiModalKwargs.batch(grouped_mm_inputs)
483 + batched_mm_inputs = MultiModalKwargs.as_kwargs(batched_mm_inputs,
484 + device=self.device)
485 +
486 + # Run the encoder.
487 + # `curr_group_outputs` is either of the following:
488 + # 1. A tensor of shape (num_items, feature_size, hidden_size)
489 + # in case feature_size is fixed across all multimodal items.
490 + # 2. A list or tuple (length: num_items) of tensors, each of shape
491 + # (feature_size, hidden_size) in case the feature size is dynamic
492 + # depending on the input multimodal items.
493 + curr_group_outputs = self.model.get_multimodal_embeddings(
494 + **batched_mm_inputs)
495 +
496 + for output in curr_group_outputs:
497 + encoder_outputs.append(output)
498 +
499 + # Cache the encoder outputs.
500 + for (req_id, input_id), output in zip(req_input_ids, encoder_outputs):
501 + if req_id not in self.encoder_cache:
502 + self.encoder_cache[req_id] = {}
503 + self.encoder_cache[req_id][input_id] = output
504 +
505 + def _gather_encoder_outputs(
506 + self,
507 + scheduler_output: "SchedulerOutput",
508 + ) -> list[torch.Tensor]:
509 + encoder_outputs: list[torch.Tensor] = []
510 + for req_id in self.input_batch.req_ids:
511 + num_scheduled_tokens = scheduler_output.num_scheduled_tokens[
512 + req_id]
513 + req_state = self.requests[req_id]
514 + num_computed_tokens = req_state.num_computed_tokens
515 + mm_positions = req_state.mm_positions
516 + for i, pos_info in enumerate(mm_positions):
517 + start_pos = pos_info["offset"]
518 + num_encoder_tokens = pos_info["length"]
519 +
520 + # The encoder output is needed if the two ranges overlap:
521 + # [num_computed_tokens,
522 + # num_computed_tokens + num_scheduled_tokens) and
523 + # [start_pos, start_pos + num_encoder_tokens)
524 + if start_pos >= num_computed_tokens + num_scheduled_tokens:
525 + # The encoder output is not needed in this step.
526 + break
527 + if start_pos + num_encoder_tokens <= num_computed_tokens:
528 + # The encoder output is already processed and stored
529 + # in the decoder's KV cache.
530 + continue
531 +
532 + start_idx = max(num_computed_tokens - start_pos, 0)
533 + end_idx = min(
534 + num_computed_tokens - start_pos + num_scheduled_tokens,
535 + num_encoder_tokens)
536 + assert start_idx < end_idx
537 + assert req_id in self.encoder_cache
538 + assert i in self.encoder_cache[req_id]
539 + encoder_output = self.encoder_cache[req_id][i]
540 + encoder_outputs.append(encoder_output[start_idx:end_idx])
541 + return encoder_outputs
542 +
543 @torch.no_grad()
544 def execute_model(
545 self,
@@ -434,16 +548,42 @@ class TPUModelRunner:
548 # Update cached state
549 self._update_states(scheduler_output)
550
551 + if self.is_multimodal_model:
552 + # Run the multimodal encoder if any.
553 + self._execute_encoder(scheduler_output)
554 + encoder_outputs = self._gather_encoder_outputs(scheduler_output)
555 + else:
556 + encoder_outputs = []
557 +
558 # Prepare inputs
559 attn_metadata, logits_indices = self._prepare_inputs(scheduler_output)
560 total_num_scheduled_tokens = scheduler_output.total_num_scheduled_tokens
561
562 + if self.is_multimodal_model:
563 + # NOTE(woosuk): To unify token ids and soft tokens (vision
564 + # embeddings), we always use embeddings (rather than token ids)
565 + # as input to the multimodal model, even when the input is text.
566 + if encoder_outputs:
567 + inputs_embeds = self.model.get_input_embeddings(
568 + self.input_ids, encoder_outputs)
569 + else:
570 + inputs_embeds = self.model.get_input_embeddings(self.input_ids)
571 + input_ids = None
572 + else:
573 + # For text-only models, we use token ids as input.
574 + # While it is possible to use embeddings as input just like the
575 + # multimodal models, it is not desirable for performance since
576 + # then the embedding layer is not included in the CUDA graph.
577 + input_ids = self.input_ids
578 + inputs_embeds = None
579 +
580 # Run the decoder
581 with set_forward_context(attn_metadata, self.vllm_config):
582 hidden_states = self.model(
444 - token_ids=self.input_ids,
445 - position_ids=self.position_ids,
583 + input_ids=input_ids,
584 + positions=self.position_ids,
585 kv_caches=self.kv_caches,
586 + inputs_embeds=inputs_embeds,
587 )
588 hidden_states = hidden_states[:total_num_scheduled_tokens]
589 num_reqs = self.input_batch.num_reqs
@@ -538,14 +678,21 @@ class TPUModelRunner:
678 fullgraph=True,
679 dynamic=False)
680
541 - def dummy_run(
681 + def _dummy_run(
682 self,
683 kv_caches,
684 num_tokens: int,
685 ) -> None:
546 - input_ids = torch.zeros(num_tokens,
547 - dtype=torch.int32,
548 - device=self.device)
686 + if self.is_multimodal_model:
687 + input_ids = None
688 + inputs_embeds = torch.zeros((num_tokens, self.hidden_size),
689 + dtype=self.dtype,
690 + device=self.device)
691 + else:
692 + input_ids = torch.zeros((num_tokens),
693 + dtype=torch.int32,
694 + device=self.device)
695 + inputs_embeds = None
696 position_ids = torch.zeros(num_tokens,
697 dtype=torch.int32,
698 device=self.device)
@@ -571,7 +718,10 @@ class TPUModelRunner:
718 num_seqs=num_tokens,
719 )
720
574 - torch._dynamo.mark_dynamic(input_ids, 0)
721 + if self.is_multimodal_model:
722 + torch._dynamo.mark_dynamic(inputs_embeds, 0)
723 + else:
724 + torch._dynamo.mark_dynamic(input_ids, 0)
725 torch._dynamo.mark_dynamic(position_ids, 0)
726 torch._dynamo.mark_dynamic(attn_metadata.slot_mapping, 0)
727 torch._dynamo.mark_dynamic(attn_metadata.block_tables, 0)
@@ -580,7 +730,12 @@ class TPUModelRunner:
730
731 with set_forward_context(attn_metadata, self.vllm_config, 0):
732 assert self.model is not None
583 - self.model(input_ids, position_ids, kv_caches)
733 + self.model(
734 + input_ids=input_ids,
735 + positions=position_ids,
736 + kv_caches=kv_caches,
737 + inputs_embeds=inputs_embeds,
738 + )
739
740 def capture_model(self) -> None:
741 """Compile the model."""
@@ -590,11 +745,11 @@ class TPUModelRunner:
745 start = time.perf_counter()
746 num_tokens = 16
747 while True:
593 - self.dummy_run(self.kv_caches, num_tokens)
748 + self._dummy_run(self.kv_caches, num_tokens)
749 logger.info(" -- num_tokens: %d", num_tokens)
750 xm.mark_step()
751 xm.wait_device_ops()
597 - if num_tokens >= self.scheduler_config.max_num_batched_tokens:
752 + if num_tokens >= self.max_num_tokens:
753 break
754 num_tokens *= 2
755 end = time.perf_counter()
@@ -647,17 +802,20 @@ class ModelWrapperV1(nn.Module):
802
803 def forward(
804 self,
650 - token_ids: torch.Tensor,
651 - position_ids: torch.Tensor,
805 + input_ids: torch.Tensor,
806 + positions: torch.Tensor,
807 kv_caches: list[tuple[torch.Tensor, torch.Tensor]],
808 + inputs_embeds: Optional[torch.Tensor] = None,
809 ) -> torch.Tensor:
810 """Executes the forward pass of the model and samples the next token.
811
812 Args:
657 - token_ids: The input token IDs of shape [num_tokens].
658 - position_ids: The input position IDs of shape [num_tokens].
813 + input_ids: The input token IDs of shape [num_tokens].
814 + positions: The input position IDs of shape [num_tokens].
815 kv_caches: The key and value caches. They can be None during the
816 memory profiling at initialization.
817 + inputs_embeds: The input embeddings of shape [num_tokens,
818 + hidden_size]. It is used for multimodal models.
819 """
820 # Skip this in memory profiling at initialization.
821 if kv_caches[0][0].numel() > 0:
@@ -684,9 +842,9 @@ class ModelWrapperV1(nn.Module):
842
843 assert self.model is not None
844 hidden_states = self.model(
687 - token_ids,
688 - position_ids,
689 - kv_caches,
845 + input_ids=input_ids,
846 + positions=positions,
847 + inputs_embeds=inputs_embeds,
848 )
849
850 return hidden_states
@@ -699,6 +857,12 @@ class ModelWrapperV1(nn.Module):
857 logits = self.model.compute_logits(hidden_states, sampling_metadata)
858 return logits
859
860 + def get_multimodal_embeddings(self, *args, **kwargs):
861 + return self.model.get_multimodal_embeddings(*args, **kwargs)
862 +
863 + def get_input_embeddings(self, *args, **kwargs):
864 + return self.model.get_input_embeddings(*args, **kwargs)
865 +
866
867 def _get_padded_number(n: int, multiple: int) -> int:
868 return ((n + multiple - 1) // multiple) * multiple
vllm/v1/worker/tpu_worker.py
+1 -1
@@ -134,7 +134,7 @@ class TPUWorker:
134 self.vllm_config.compilation_config.static_forward_context,
135 runner_kv_caches)
136
137 - self.model_runner.dummy_run(
137 + self.model_runner._dummy_run(
138 runner_kv_caches,
139 num_tokens=self.scheduler_config.max_num_batched_tokens,
140 )