GPUModelRunner.execute_model: der Forward eines Steps betritt hier die Bühne
Verantwortung
GPUModelRunner(gpu_model_runner.py:440-442) ist das Objekt im Worker-Prozess, das den Forward tatsächlich ausführt. Worker.execute_model übergibt ihm den SchedulerOutput, der in diesem Schritt alles erledigt: _update_states synchronisiert den Anfragezustand, _prepare_inputs berechnet logits_indices, _determine_batch_execution_and_padding wählt die cudagraph-Variante, maybe_create_ubatch_slices teilt Mikrobatches (ubatch) auf, _build_attention_metadata baut die Attention-Metadaten, _preprocess fügt die Modelleingaben zusammen, _model_forward führt das Modell aus — und am Ende steht entweder ein ModelRunnerOutput (letzter PP-Rank) oder IntermediateTensors (mittlerer PP-Rank).
Das Verhältnis zum Worker ist eine Komposition: Worker.__init__ erzeugt den Runner nicht direkt, sondern ruft in load_model die Funktion model_runner.load_model auf(gpu_worker.py:406-413); der Konstruktor selbst speichert nur die Konfiguration, berechnet max_num_tokens/max_num_reqs, baut den Sampler und entscheidet anhand der Konfiguration, ob das Modell mit einem UBatchWrapper umhüllt wird(gpu_model_runner.py:5350-5379).
execute_model und sample_tokens bilden ein Paar: Wenn asynchrones Scheduling oder eine Ray-Sampler-Aufspaltung aktiv sind, führt execute_model zwar den Forward aus, führt aber kein Sampling durch; stattdessen verpackt es Logits und Zwischenzustände in ExecuteModelState(gpu_model_runner.py:424-437), gibt None zurück und wartet auf den Aufruf von sample_tokens, um dann _sample auszuführen(gpu_model_runner.py:4456-4492). Das ist der Hebel, mit dem v1 den nächsten Scheduling-Schritt parallel zum Sampling ausführen kann.
Entwurfsmotivation
- Zweiphasige Zustandsmaschine:
ExecuteModelState(ein NamedTuple) bündelt alle Werte zwischen Forward und Sampling (logits, hidden_states, spec_decode_metadata, cudagraph_stats, slot_mappings usw.) explizit(gpu_model_runner.py:424-437); am Ende vonexecute_modelfolgtself.execute_model_state = ExecuteModelState(...), und insample_tokenswird wieder entpackt(gpu_model_runner.py:4470-4483), um verstreute Felder zu vermeiden. - cudagraph-Modus wird nach Batch-Form dispatcht:
_determine_batch_execution_and_padding(gpu_model_runner.py:3836-3948) berechnetuniform_decode,has_lora,has_encoder_outputund ruft danncudagraph_dispatcher.dispatch(num_tokens, has_lora, uniform_decode, ...)auf, umNONE/PIECEWISE/FULLauszuwählen(gpu_model_runner.py:3881-3893) — gleiche Form, unterschiedliche Semantik nimmt unterschiedliche Graphen. - DP-Abstimmung vor dem Aufteilen:
coordinate_batch_across_dp(gpu_model_runner.py:3907-3918) stimmtshould_ubatchundnum_tokens_across_dpüber alle DP-Ranks ab, damit alle Ranks identisch geformte Mikrobatches erhalten — sonst geraten die cudagraph-Replays des ubatch aus dem Tritt. - Modell kann gewrappt werden:Am Ende von
load_modelwirdself.modelabhängig voncudagraph_modeunduse_ubatchingdurchBreakableCUDAGraphWrapper/CUDAGraphWrapper/UBatchWrapperersetzt(gpu_model_runner.py:5353-5379);_model_forwardruft nurself.model(...)auf und macht sich die äußere Hülle nicht zunutze. - Input-Vorbereitung als eigene Methode:
_prepare_inputs(gpu_model_runner.py:1914) berechnet logits_indices und spec_decode_metadata;_preprocess(gpu_model_runner.py:3449) bautinput_ids/positions/inputs_embeds/intermediate_tensors/model_kwargszusammen — getrennt vom Forward, sodass auch profile und dummy_run es wiederverwenden können. - KV-Scales in erster Runde eager:Wenn
calculate_kv_scales=True, wirdcudagraph_modehart aufNONEzurückgesetzt(gpu_model_runner.py:4311-4314), weil die dynamischen Operationen zur Berechnung der KV-Scale nicht in den Graphen passen; erst nach der ersten Forward-Runde ist cudagraph wieder erlaubt.
Schlüsseldateien
GPUModelRunner.__init__:440-563— speichert Konfiguration, berechnetmax_num_tokens/max_num_reqs, bautSampler,MULTIMODAL_REGISTRY,cudagraph_batch_sizes,cudagraph_dispatcher = CudagraphDispatcher(vllm_config)auf.ExecuteModelState:424-437— NamedTuple zwischen Forward und Sampling, erfasst logits, hidden_states, spec_decode_metadata, cudagraph_stats usw._update_states:1152— synchronisiert Unterschiede ausscheduler_output(neue/fortgesetzte Anfragen) in den persistenten Zustand desinput_batchund liefertdeferred_state_corrections_fnzurück._prepare_inputs:1914— berechnetlogits_indicesundspec_decode_metadataund entscheidet, wie die Token in die logits einfließen._determine_batch_execution_and_padding:3836-3948— berechnetuniform_decode/has_lora/has_encoder_output, ruftcudagraph_dispatcher.dispatchauf, um denCUDAGraphModezu wählen, und führt bei mehreren DP-Rankscoordinate_batch_across_dpaus.execute_model entry:4070-4105— Entry-Point: leert routed_experts-Buffer, optionaler ngram_gpu-Copy des scheduler_output, Vorab-Behandlung des KV-Transfers, Profile-Einstieg.execute_model batch prep:4147-4203—num_scheduled_tokens_np,_prepare_inputs,_determine_batch_execution_and_padding,maybe_create_ubatch_slices.build attn metadata:4270-4295—_get_slot_mappings+_build_attention_metadata, unterstützt ubatch-Slices (ubatch_slices=ubatch_slices_attn)._model_forward:4335-4359— umschließtself.model(input_ids=..., positions=..., intermediate_tensors=..., inputs_embeds=..., **model_kwargs)mitset_forward_context(...)— die eine Zeile, in der das Modell wirklich läuft.sample_tokens entry:4456-4492— entpacktexecute_model_state, wendet optionalapply_grammar_bitmaskan und ruftself._sample(logits, spec_decode_metadata)auf._sample:3596-3624— ohne spec decode direktself.sampler(...), andernfalls überrejection_sampler.capture_model:6647-6712— nimmt beim Start einmal alle cudagraph-Graphen auf: große Formen zuerst,set_cudagraph_capturing_enabled(True)+graph_capture(device=self.device), danachlock_workspace().initialize_kv_cache:7405-7462— baut anhand vonKVCacheConfigdieattn_groups, Metadata-Builder undinitialize_kv_cache_tensorsauf und registriert die KV-Transfer-Gruppe.
Datenfluss
Beim Eintreffen eines Steps aktualisiert execute_model zuerst den persistenten Zustand, entscheidet dann über Batch-Form und cudagraph-Modus, teilt dann den ubatch auf und führt schließlich den Forward aus. Dieser Abschnitt ist die Mitte des gesamten Steps (preprocess):
# vllm/v1/worker/gpu_model_runner.py L4169-L4182
(
cudagraph_mode,
batch_desc,
should_ubatch,
num_tokens_across_dp,
cudagraph_stats,
) = self._determine_batch_execution_and_padding(
num_tokens=num_tokens_unpadded,
num_reqs=num_reqs,
num_scheduled_tokens_np=num_scheduled_tokens_np,
max_num_scheduled_tokens=max_num_scheduled_tokens,
use_cascade_attn=cascade_attn_prefix_lens is not None,
num_encoder_reqs=len(scheduler_output.scheduled_encoder_inputs),
)Anschließend werden die ubatch-Slices aufgeteilt; set_forward_context umschließt den Modell-Forward, und alle Attention-Metadaten, der cudagraph-Runtime-Modus und die ubatch-Slices stammen aus dem Forward-Kontext:
# vllm/v1/worker/gpu_model_runner.py L4335-L4359
with (
set_forward_context(
attn_metadata,
self.vllm_config,
num_tokens=num_tokens_padded,
num_tokens_across_dp=num_tokens_across_dp,
cudagraph_runtime_mode=cudagraph_mode,
batch_descriptor=batch_desc,
ubatch_slices=ubatch_slices_padded,
slot_mapping=slot_mappings,
skip_compiled=has_encoder_input,
),
record_function_or_nullcontext("gpu_model_runner: forward"),
self.maybe_get_kv_connector_output(
scheduler_output,
defer_finalize=defer_kv_connector_finalize,
) as kv_connector_output,
):
model_output = self._model_forward(
input_ids=input_ids,
positions=positions,
intermediate_tensors=intermediate_tensors,
inputs_embeds=inputs_embeds,
**model_kwargs,
)_model_forward besteht aus der einzigen Zeile self.model(...)(gpu_model_runner.py:3807-3813); das cudagraph-Replay bzw. die ubatch-Parallelität spielen sich im Wrapper von self.model ab. Ein mittlerer PP-Rank liefert die empfangenen IntermediateTensors direkt zurück; der letzte Rank parkt die hidden_states in ExecuteModelState und wartet auf sample_tokens, wo das Sampling und propose_draft_token_ids stattfinden.
Grenzen und Fehler
- Leerer Batch: früher Return:Wenn
num_scheduled_tokens == 0, wird direktreturn EMPTY_MODEL_RUNNER_OUTPUTausgeführt(gpu_model_runner.py:4122-4138) — ohne Forward; beiexternal_launcher+ DP>1 erfolgt zuvor ein_dummy_run(1)zur Synchronisation, um ein Aufeinanderzulaufen der DP-Ranks zu vermeiden. - Re-Entry-Prüfung der Zustandsmaschine:Wenn
execute_modelbeim Eintritt feststellt, dassself.execute_model_state is not None, wird sofort „sample_tokens() must be called after execute_model() returns None“ geworfen(gpu_model_runner.py:4075-4079). - KV-Scales in erster Runde erzwungen eager:Siehe Entwurfsmotivation oben(
gpu_model_runner.py:4311-4314); nach der ersten Runde giltcalculate_kv_scales = False. - Encoder-Decoder: erster Schritt eager:
has_encoder_input = is_encoder_decoder and num_encoder_reqs > 0setztskip_compiled=True, sodass der Forward-Kontext die Kompilierung überspringt(gpu_model_runner.py:4318-4321). - ngram_gpu verändert scheduler_output:Wenn
use_ngram_gpu()aktiv ist, werden sowohlnum_scheduled_tokensals auchscheduled_spec_decode_tokenszunächstcopy()und dannreplace(...)(gpu_model_runner.py:4087-4099) — um eine in-place-Modifikation des scheduler_output auf Engine-Seite zu vermeiden. - Während der cudagraph-Aufnahme gilt
is_graph_capturing:_dummy_run(..., is_graph_capturing=True)aktiviert in_prepare_inputsden Zweigfor_cudagraph_capture=True(gpu_model_runner.py:5936) und befülltmax_seqlen_kmit der echten Encoder-Länge. - cudagraph: große Formen zuerst:
capture_modeliteriert übercudagraph_dispatcher.get_capture_descs()in absteigender Reihenfolge(gpu_model_runner.py:6671-6679), sodass große Graphen zuerst aufgenommen werden und kleine Graphen den Speicher-Pool wiederverwenden können. - Shutdown löscht Graphen:
CUDAGraphWrapper.clear_all_graphs()+BreakableCUDAGraphWrapper.clear_all_graphs()(gpu_model_runner.py:6413-6415) entfernen beim erneuten Profiling historische Graphen, da sonst wiederholte Captures zusätzlichen VRAM beanspruchen würden.
Zusammenfassung
GPUModelRunner ist der Kern, mit dem v1 einen einzelnen Schritt auf der GPU ausführt. Er verknüpft die Kette „Batch-Form festlegen → cudagraph-Dispatch → ubatch-Aufteilung → Attention-Metadaten → Modell-Forward → Zustand verpacken“ zu einer Pipeline und macht über die Zweiteilung execute_model + sample_tokens Scheduling und Sampling versetzt zueinander. Das Modell selbst ist von CUDAGraphWrapper oder UBatchWrapper umhüllt; der eigentliche Lauf besteht aus der einen Zeile self.model(...). Wie der Worker ihn aufruft, steht in /worker/worker; die Aufnahme- und Replay-Details von cudagraph und ubatch in /worker/ubatch-cudagraph; die Executor-Ebene in /executor/executor.