Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

テキスト推論データフロー

深度: [MEDIUM] 確信度: [VERIFIED] 最終更新: 2026-02-11 (Phase 2bでマルチモーダル差分追加)

概要

テキスト推論リクエストは、APIエントリポイントからエンジン層を経てGPUで実行され、生成されたトークンがデトークナイズされてユーザーに返却される。フロー全体は5つの境界データ構造(EngineCoreRequest → SchedulerOutput → ModelRunnerOutput → EngineCoreOutput → RequestOutput)で区切られ、ZMQ IPCによるプロセス分離とasyncioによる非同期パイプラインで高スループットを実現する。

フロー全体図

graph TD
    subgraph フロントエンドプロセス
        API["API Server / LLM"]
        AsyncLLM["AsyncLLM<br>generate() / add_request()"]
        IP["InputProcessor<br>process_inputs()"]
        OP["OutputProcessor<br>process_outputs()"]
        Client["EngineCoreClient<br>AsyncMPClient"]
    end

    subgraph バックエンドプロセス ["EngineCore プロセス"]
        EC["EngineCore<br>step()"]
        Sched["Scheduler<br>schedule()"]
        KV["KVCacheManager<br>allocate_slots()"]
        Exec["Executor<br>execute_model()"]
        Worker["Worker"]
        MR["GPUModelRunner<br>execute_model()"]
    end

    API -->|"prompt, params"| AsyncLLM
    AsyncLLM -->|"prompt, params"| IP
    IP -->|"EngineCoreRequest"| AsyncLLM
    AsyncLLM -->|"EngineCoreRequest"| Client

    Client -->|"ZMQ ROUTER\nmsgpack"| EC
    EC --> Sched
    Sched -->|"allocate_slots()"| KV
    Sched -->|"SchedulerOutput"| EC
    EC -->|"SchedulerOutput"| Exec
    Exec -->|"MessageQueue\n共有メモリ"| Worker
    Worker --> MR
    MR -->|"ModelRunnerOutput"| Worker
    Worker -->|"ModelRunnerOutput"| Exec
    Exec -->|"ModelRunnerOutput"| EC
    EC -->|"update_from_output()"| Sched
    Sched -->|"EngineCoreOutputs"| EC

    EC -->|"ZMQ PUSH\nmsgpack"| Client
    Client -->|"EngineCoreOutputs"| OP
    OP -->|"RequestOutput"| AsyncLLM
    AsyncLLM -->|"RequestOutput"| API

境界データ構造

フローは以下の5つのデータ構造で区切られる。各構造はプロセス間またはコンポーネント間の境界を定義する。

EngineCoreRequest

フロントエンド → バックエンドの境界。ユーザー入力を正規化した内部表現。

参照: target/vllm/vllm/v1/engine/__init__.py:55 (EngineCoreRequest)

フィールド説明
request_idstr内部リクエストID(外部IDに8文字ランダムサフィックス付与)
prompt_token_idslist[int] | Noneトークナイズ済みプロンプト
mm_featureslist[MultiModalFeatureSpec] | Noneマルチモーダル入力(テキスト推論ではNone)
sampling_paramsSamplingParams | Noneサンプリングパラメータ(clone済み)
eos_token_idint | None終了トークンID
arrival_timefloatリクエスト到着時刻
lora_requestLoRARequest | NoneLoRAアダプタ情報
priorityint優先度(デフォルト0)
data_parallel_rankint | Noneデータ並列ランク指定

msgspec.Struct を継承し、array_like=True + omit_defaults=True で効率的にmsgpackシリアライズされる。

SchedulerOutput

Scheduler → Executor の境界。各ステップのスケジュール結果を含む。

参照: target/vllm/vllm/v1/core/sched/output.py:184 (SchedulerOutput)

フィールド説明
scheduled_new_reqslist[NewRequestData]初回スケジュールされたリクエスト(フルデータ)
scheduled_cached_reqsCachedRequestData既スケジュール済みリクエスト(差分のみ)
num_scheduled_tokensdict[str, int]リクエストごとのスケジュールトークン数
total_num_scheduled_tokensint合計スケジュールトークン数
scheduled_spec_decode_tokensdict[str, list[int]]Speculative Decoding用トークン
scheduled_encoder_inputsdict[str, list[int]]エンコーダ入力インデックス(マルチモーダル)
num_common_prefix_blockslist[int]共通プレフィックスブロック数(Cascade Attention用)
finished_req_idsset[str]このステップで完了したリクエストID
free_encoder_mm_hasheslist[str]解放するエンコーダキャッシュのmm_hash
preempted_req_idsset[str] | Noneプリエンプションされたリクエスト
has_structured_output_requestsbool構造化出力リクエストの有無
pending_structured_output_tokensboolGrammar bitmask準備状態
num_invalid_spec_tokensdict[str, int] | None無効スペキュレーショントークン数
kv_connector_metadataKVConnectorMetadata | NoneKV Transfer メタデータ
ec_connector_metadataECConnectorMetadata | NoneEC Transfer メタデータ

NewRequestData は初回スケジュール時のフルデータ(プロンプトトークン、サンプリングパラメータ、ブロックID等)を含む。CachedRequestData は既スケジュール済みリクエストの差分(新規ブロックID、新トークンID、計算済みトークン数の更新)のみを含み、プロセス間通信コストを最小化する。

ModelRunnerOutput

GPUModelRunner → EngineCore の境界。モデル推論結果を含む。

参照: target/vllm/vllm/v1/outputs.py:160 (ModelRunnerOutput)

フィールド説明
req_idslist[str]バッチ内のリクエストID一覧
req_id_to_indexdict[str, int]リクエストID → バッチインデックス
sampled_token_idslist[list[int]]サンプリング済みトークンID [num_reqs, num_generated]
logprobsLogprobsLists | None生成トークンの対数確率
prompt_logprobs_dictdict[str, LogprobsTensors | None]プロンプトトークンの対数確率
pooler_outputlist[Tensor | None] | Noneプーリング出力(埋め込みモデル用)
kv_connector_outputKVConnectorOutput | NoneKV Transfer出力
ec_connector_outputECConnectorOutput | NoneEC Transfer出力
num_nans_in_logitsdict[str, int] | Nonelogits内のNaN数
cudagraph_statsCUDAGraphStat | NoneCUDAGraph実行統計

Worker→Executorへの転送ではPythonリスト形式を使用し、torch.Tensorの高コストなシリアライゼーションを回避する。

EngineCoreOutput

バックエンド → フロントエンドの境界。リクエスト単位の推論結果。

参照: target/vllm/vllm/v1/engine/__init__.py:130 (EngineCoreOutput)

フィールド説明
request_idstr対応するリクエストID
new_token_idslist[int]新たに生成されたトークンID
finish_reasonFinishReason | None完了理由(stop/length/abort/error)
new_logprobsLogprobsLists | None生成トークンのlogprobs
num_cached_tokensintプレフィックスキャッシュヒット数

EngineCoreOutputs(複数形)がこれをlist[EngineCoreOutput]としてバッチ化し、scheduler_statsやタイムスタンプと共にZMQ経由で送信される。

参照: target/vllm/vllm/v1/engine/__init__.py:176 (EngineCoreOutputs)

RequestOutput

OutputProcessor → API の境界。ユーザーに返却される最終出力。

参照: target/vllm/vllm/outputs.py:86 (RequestOutput)

フィールド説明
request_idstr外部リクエストID(クライアントが指定したID)
promptstr | None元のプロンプト文字列
prompt_token_idslist[int] | Noneトークナイズ済みプロンプト
prompt_logprobsPromptLogprobs | Noneプロンプトトークンの対数確率
outputslist[CompletionOutput]サンプルごとの出力(n>1で複数)
finishedboolリクエスト完了フラグ
metricsRequestStateStats | Noneレイテンシ等の統計情報
num_cached_tokensint | Noneプレフィックスキャッシュヒット数
kv_transfer_paramsdict[str, Any] | NoneKV Transfer情報(完了時)

CompletionOutput (target/vllm/vllm/outputs.py:23) は各サンプルの出力を表す:

フィールド説明
indexintサンプルインデックス
textstrデトークナイズ済みテキスト
token_idsGenericSequence[int]生成トークンID列
cumulative_logprobfloat | None累積対数確率
logprobsSampleLogprobs | None各トークンのlogprobs
finish_reasonstr | None完了理由(“stop” / “length”)
stop_reasonint | str | None停止トークン/文字列

出力モードRequestOutputKindtarget/vllm/vllm/sampling_params.py:108):

  • CUMULATIVE: 毎回全出力を返す(デフォルト)
  • DELTA: 差分のみ返す(ストリーミング向け)
  • FINAL_ONLY: 完了時のみ返す

上流パス: リクエスト受信 → EngineCore

エントリポイント (LLM / AsyncLLM)

vLLMには同期パス(LLM)と非同期パス(AsyncLLM)の2つのエントリポイントがある。内部的にはどちらもInputProcessorEngineCoreClientを使用する。

非同期パス(主パス): AsyncLLM

APIサーバー(OpenAI互換API等)が使用する主要パス。

参照: target/vllm/vllm/v1/engine/async_llm.py:71 (AsyncLLM)

AsyncLLM.generate(prompt, sampling_params, request_id)    # L537
  │
  ├─ add_request(request_id, prompt, params)               # L286
  │   ├─ input_processor.process_inputs(prompt, params)    # L364
  │   │   → EngineCoreRequest を生成
  │   ├─ input_processor.assign_request_id(request)        # L378
  │   │   → 内部IDを付与(外部ID + 8文字ランダムサフィックス)
  │   ├─ output_processor.add_request(request, ...)        # L423
  │   │   → フロントエンド側でリクエストを登録
  │   └─ engine_core.add_request_async(request)            # L426
  │       → ZMQ経由でバックエンドへ送信
  │
  └─ while not finished:                                    # L586
      out = q.get_nowait() or await q.get()                # L589
      yield out                                             # L596

generate()はAsyncGeneratorで、バックグラウンドのoutput_handlerタスクがEngineCoreからの出力をRequestOutputCollectorキューにpushし、generate()がそれをyieldする。

output_handler(バックグラウンドタスク):

参照: target/vllm/vllm/v1/engine/async_llm.py:647 (_run_output_handler)

output_handler():                                           # L662
  while True:
    outputs = await engine_core.get_output_async()          # L666
    for chunk in outputs.outputs:                           # L677
      output_processor.process_outputs(chunk, ...)          # L681
      → RequestOutputをキューにpush
    if reqs_to_abort:
      await engine_core.abort_requests_async(...)           # L693

同期パス: LLM

オフライン推論(バッチ処理)で使用される。

参照: target/vllm/vllm/entrypoints/llm.py:396 (generate)

LLM.generate(prompts, sampling_params)                      # L396
  → _run_completion(prompts, params)                        # L449
    → _add_request(prompt, params) × N                      # L1850
    │  ├─ input_processor.process_inputs(prompt, params)    # L1879
    │  └─ llm_engine.add_request(request_id, request, ...)  # L1889
    → _run_engine()                                         # L1900
       while has_unfinished_requests():                     # L1918
         step_outputs = llm_engine.step()                   # L1919

同期パスとの主な違い:

  • LLM_run_engine()でポーリングループを回す(AsyncGeneratorではない)
  • llm_engine(=AsyncLLMのラッパー)のstep()を直接呼ぶ
  • プログレスバー(tqdm)でバッチ処理の進捗を表示

入力処理 (InputProcessor)

InputProcessorはユーザー入力(テキストプロンプト、パラメータ)をEngineCoreRequestに変換する。

参照: target/vllm/vllm/v1/engine/input_processor.py:56 (InputProcessor)

InputProcessor.process_inputs(request_id, prompt, params)   # L521
  ├─ _validate_lora(lora_request)                           # L535
  ├─ _validate_params(params)                               # L536
  ├─ data_parallel_rank の範囲チェック                       # L542
  ├─ arrival_time 設定(未指定なら time.time())             # L548
  │
  ├─ input_preprocessor.preprocess(prompt, ...)             # L581
  │   → テキストをトークナイズ(tokenizer.encode())
  │   → ProcessorInputs を返す
  │
  ├─ split_enc_dec_inputs(processed_inputs)                 # L597
  │   → エンコーダ/デコーダ入力を分離
  │
  ├─ SamplingParams の正規化                                # L608-623
  │   ├─ params.clone()                                     # L612
  │   ├─ max_tokens 未設定時: max_model_len - seq_len       # L614-618
  │   ├─ update_from_generation_config()                    # L619
  │   └─ update_from_tokenizer()                            # L623
  │
  └─ EngineCoreRequest を構築して返す                        # L656-671

テキスト推論の場合、マルチモーダル関連処理(L630-654)はスキップされる(mm_featuresはNone)。

プロセス間通信 (EngineCoreClient / ZMQ IPC)

EngineCoreClientはフロントエンドプロセスとバックエンドプロセス(EngineCore)間のZMQ IPC通信を担当する。

参照: target/vllm/vllm/v1/engine/core_client.py:63 (EngineCoreClient)

クライアント階層

クラス用途トランスポート
EngineCoreClient (ABC)抽象インターフェース
InprocClientインプロセス(デバッグ用)直接呼び出し
SyncMPClient同期マルチプロセス(LLM用)ZMQ同期
AsyncMPClient非同期マルチプロセス(AsyncLLM用)ZMQ非同期
DPAsyncMPClientデータ並列(外部LB)複数ZMQ
DPLBAsyncMPClientデータ並列(内部LB)複数ZMQ

参照: target/vllm/vllm/v1/engine/core_client.py:442 (MPClient)

ZMQソケット構成

フロントエンド                 バックエンド
┌──────────────┐              ┌──────────────┐
│ AsyncMPClient│              │ EngineCore   │
│              │              │              │
│ input_socket ├─── ROUTER ──→│ (受信)       │
│ (zmq.ROUTER) │    msgpack   │              │
│              │              │              │
│ output_socket│←── PULL ─────┤ (送信)       │
│ (zmq.PULL)   │    msgpack   │              │
└──────────────┘              └──────────────┘
  • シリアライゼーション: MsgpackEncoder / MsgpackDecodermsgspecライブラリ)
    • EngineCoreRequestのシリアライズ → 入力ソケット経由で送信
    • EngineCoreOutputsのデシリアライズ ← 出力ソケット経由で受信
  • 非同期出力受信: process_outputs_socket()タスクがZMQソケットをポーリングし、受信したOutputsをasyncio.Queueにpush

参照: target/vllm/vllm/v1/engine/core_client.py:822 (AsyncMPClient)

EngineCore側のリクエスト受信

EngineCore.add_request()はリクエストをバリデーションしてSchedulerに登録する。

参照: target/vllm/vllm/v1/engine/core.py:288 (add_request)

EngineCore.add_request(request)                             # L288
  ├─ request_id の型チェック                                 # L295
  ├─ pooling_params のバリデーション                          # L300
  ├─ kv_transfer_params の互換性チェック                      # L311
  └─ scheduler.add_request(request)                          # L319

EngineCore.step() (コアループ概要)

参照: target/vllm/vllm/v1/engine/core.py:389 (step)

EngineCore.step()                                            # L389
  ├─ scheduler.schedule()        → SchedulerOutput           # L404
  ├─ executor.execute_model()    → Future[ModelRunnerOutput]  # L405
  ├─ grammar_output 取得                                      # L406
  ├─ future.result()             → ModelRunnerOutput          # L411
  ├─ sample_tokens()(非同期スケジューリング時)              # L413
  └─ scheduler.update_from_output() → EngineCoreOutputs      # L418

コアループ: EngineCore.step()

EngineCoreのstep()メソッドは、各ステップで schedule → execute → update のサイクルを実行し、待機中のリクエストから生成トークンを生産する。

step() 実行フロー

参照: target/vllm/vllm/v1/engine/core.py:389 (step)

EngineCore.step()                                            # L389
  │
  ├─ if _scheduler_paused: return {}, False                  # L397
  ├─ if not scheduler.has_requests(): return {}, False       # L402
  │
  ├─ 1. scheduler_output = scheduler.schedule()              # L404
  │      → SchedulerOutput
  │      (RUNNINGリクエストの予算割当 → WAITINGリクエストの受け入れ
  │        → KVキャッシュブロック確保 → SchedulerOutput構築)
  │
  ├─ 2. future = executor.execute_model(                     # L405
  │         scheduler_output, non_block=True)
  │      → Future[ModelRunnerOutput | None]
  │      (非ブロッキング。ワーカープロセスで並行実行)
  │
  ├─ 3. grammar_output = scheduler.get_grammar_bitmask(      # L406
  │         scheduler_output)
  │      (構造化出力有効時のみ使用)
  │
  ├─ 4. model_output = future.result()                       # L411
  │      → ModelRunnerOutput(ブロッキング待機)
  │
  ├─ 5. if model_output is None:                             # L413
  │        model_output = executor.sample_tokens(grammar_output)
  │      (非同期スケジューリング時: execute_modelとsamplingが分離)
  │
  ├─ 6. _process_aborts_queue()                              # L417
  │
  └─ 7. engine_core_outputs = scheduler.update_from_output(  # L418
  │         scheduler_output, model_output)
  │      → dict[int, EngineCoreOutputs]
  │      (生成トークンの追加、完了判定、出力構築)
  │
  └─ return (engine_core_outputs,                            # L422
             total_num_scheduled_tokens > 0)

Scheduler と KVCacheManager の相互作用

sequenceDiagram
    participant EC as EngineCore
    participant S as Scheduler
    participant KV as KVCacheManager
    participant Ex as Executor

    EC->>S: schedule()

    Note over S: Phase 1: RUNNINGリクエスト処理
    loop 各RUNNINGリクエスト
        S->>KV: allocate_slots(request, num_new_tokens)
        KV-->>S: KVCacheBlocks or None
        alt 割り当て失敗 (None)
            S->>KV: free(低優先度request)
            Note over S: プリエンプション → 再試行
        end
    end

    Note over S: Phase 2: WAITINGリクエスト受け入れ
    loop 各WAITINGリクエスト
        S->>KV: get_computed_blocks(request)
        KV-->>S: (cached_blocks, num_hits)
        S->>KV: allocate_slots(request, num_new_tokens, ...)
        KV-->>S: KVCacheBlocks or None
        alt 割り当て失敗 (None)
            Note over S: break(ループ終了)
        end
    end

    Note over S: Phase 3: SchedulerOutput構築
    S-->>EC: SchedulerOutput

    EC->>Ex: execute_model(scheduler_output)
    Ex-->>EC: Future[ModelRunnerOutput]
    EC->>EC: future.result()(待機)

    EC->>S: update_from_output(scheduler_output, model_output)
    Note over S: トークン追加、完了判定
    S-->>EC: dict[int, EngineCoreOutputs]

Scheduler.schedule() の3フェーズ

参照: target/vllm/vllm/v1/core/sched/scheduler.py:321 (schedule)

schedule() は Unified Compute Model を採用し、Prefill/Decodeを区別せず num_computed_tokens の進捗で統一的にトークンを割り当てる。

フェーズ対象処理
Phase 1L350-517RUNNINGリクエストトークン予算割当。ブロック不足時はプリエンプション
Phase 2L532-800WAITINGリクエスト新規受け入れ。プレフィックスキャッシュ検索 + ブロック割当
Phase 3L827-896出力構築NewRequestData + CachedRequestData → SchedulerOutput

トークン予算: token_budget = max_num_scheduled_tokens(ステップあたり上限)で、各リクエストのスケジュール時に消費される。

詳細は Scheduler サマリー を参照。

KVCacheManager のブロック割り当て

参照: target/vllm/vllm/v1/core/kv_cache_manager.py:206 (allocate_slots)

allocate_slots() は以下のブロック配置に基づいてGPUメモリブロックを確保する:

|  comp  | new_comp | ext_comp |   new   | lookahead |
|<------ 既計算トークン ------>|<-- 新規計算対象 -->|
                               |<- 割り当て対象 ->|
  • 成功時: KVCacheBlocks(割り当てたブロック情報)を返す
  • 失敗時: None を返す → Schedulerがプリエンプション(RUNNING)またはスキップ(WAITING)

プレフィックスキャッシュ検索は get_computed_blocks() で行い、過去に計算済みのブロックを再利用する。

詳細は KVCacheManager サマリー を参照。

update_from_output() → EngineCoreOutputs

参照: target/vllm/vllm/v1/core/sched/scheduler.py:1241 (update_from_output)

ModelRunnerOutputを受けてSchedulerの状態を更新し、クライアントに返すEngineCoreOutputsを構築する。

update_from_output(scheduler_output, model_runner_output)
  for each scheduled request:
    ├─ Speculative Decodingリジェクション処理
    │   → 不採用分の num_computed_tokens 巻き戻し
    ├─ 生成トークンをリクエストに追加
    ├─ 完了判定(EOS、max_tokens、stop_token)
    │   → 完了時: kv_cache_manager.free(request) でブロック解放
    └─ EngineCoreOutput 構築(request_id, new_token_ids, finish_reason, ...)
  → dict[int, EngineCoreOutputs](クライアントインデックス別)

下流パス: 実行 → ユーザー応答

実行層: Executor → Worker → GPUModelRunner

EngineCore.step()はexecutor.execute_model()非ブロッキングで呼び出し、GPUでの推論実行を開始する。

参照: target/vllm/vllm/v1/executor/abstract.py:202 (execute_model)

collective_rpc パターン

Executorはcollective_rpc()パターンで全Workerに同一メソッドを実行させ、出力ランクのWorkerの結果のみを返す。

EngineCore.step()
  │
  ├─ executor.execute_model(scheduler_output, non_block=True)
  │   └─ collective_rpc("execute_model", args=(scheduler_output,))
  │       └─ Worker.execute_model(scheduler_output)                # L604
  │           └─ model_runner.execute_model(scheduler_output)      # L652
  │               → ExecuteModelState を内部保存、None を返す
  │
  ├─ grammar_output = scheduler.get_grammar_bitmask(...)           # 並行処理
  │
  ├─ future.result()  → None                                       # 待機
  │
  └─ executor.sample_tokens(grammar_output)                         # L222
      └─ collective_rpc("sample_tokens", args=(grammar_output,))
          └─ Worker.sample_tokens(grammar_output)                  # L598
              └─ model_runner.sample_tokens(grammar_output)        # L3621
                  → ModelRunnerOutput を返す

参照: target/vllm/vllm/v1/worker/gpu_worker.py:604 (Worker.execute_model)

GPUModelRunner の2フェーズ実行

GPUModelRunnerは execute_model()sample_tokens() を分離する2フェーズ実行パターンを採用する。これにより、モデルフォワード中にgrammar bitmask計算を並行実行できる。

Phase 1: execute_model() (target/vllm/vllm/v1/worker/gpu_model_runner.py:3312)

execute_model(scheduler_output)
  ├─ _update_states(scheduler_output)          # バッチ状態更新
  ├─ _prepare_inputs(scheduler_output)         # 入力ID・位置計算
  ├─ _build_attention_metadata(...)            # Attention メタデータ構築
  ├─ _model_forward(...)                       # model.forward() 実行
  │   → hidden_states
  ├─ compute_logits(hidden_states)             # logits 計算
  │   → logits
  └─ ExecuteModelState に保存 → None を返す

Phase 2: sample_tokens() (target/vllm/vllm/v1/worker/gpu_model_runner.py:3621)

sample_tokens(grammar_output)
  ├─ ExecuteModelState を復元
  ├─ grammar bitmask 適用(構造化出力時)
  ├─ _sample(logits) → SamplerOutput
  ├─ バッチ状態更新(生成トークン反映)
  └─ ModelRunnerOutput を構築して返す

ExecuteModelState (target/vllm/vllm/v1/worker/gpu_model_runner.py:313) はGPUテンソル(logits, hidden_states等)を保持するNamedTupleで、2フェーズ間の一時状態転送に使用される。

出力処理: EngineCoreOutput → RequestOutput

ModelRunnerOutputはバックエンドプロセス(EngineCore)でEngineCoreOutputに変換され、ZMQ経由でフロントエンドプロセスのOutputProcessorに送られてデトークナイズされる。

sequenceDiagram
    participant MR as GPUModelRunner
    participant S as Scheduler
    participant EC as EngineCore
    participant ZMQ as ZMQ IPC
    participant OP as OutputProcessor
    participant Client as API Client

    MR->>EC: ModelRunnerOutput
    EC->>S: update_from_output()
    Note over S: トークン追加、完了判定<br>KVキャッシュ解放
    S->>EC: dict[int, EngineCoreOutputs]

    EC->>ZMQ: msgpack シリアライズ
    ZMQ->>OP: EngineCoreOutputs

    Note over OP: デトークナイズ<br>停止文字列判定<br>logprobs処理
    OP->>Client: RequestOutput (yield)

OutputProcessor.process_outputs()

参照: target/vllm/vllm/v1/engine/output_processor.py:582 (process_outputs)

OutputProcessorはフロントエンドプロセスで動作し、EngineCoreOutputをユーザー向けRequestOutputに変換する。

OutputProcessor.process_outputs(engine_core_outputs)       # L582
  for each engine_core_output:
    ├─ req_state = request_states[req_id]                  # RequestState取得
    │
    ├─ detokenizer.update(new_token_ids, stop_terminated)  # L637
    │   ├─ トークン→テキスト変換(インクリメンタル)
    │   └─ 停止文字列チェック → stop_string or None
    │
    ├─ logprobs_processor.update_from_output(output)       # L646
    │
    ├─ req_state.make_request_output(...)                   # L649
    │   ├─ _new_completion_output(token_ids, finish_reason, ...)
    │   │   ├─ detokenizer.get_next_output_text(finished, delta)
    │   │   └─ CompletionOutput(text, token_ids, logprobs, ...)
    │   └─ RequestOutput(request_id, outputs, finished, ...)
    │
    └─ req_state.queue.put(request_output)                 # L661
        → AsyncLLM.generate() が yield

Detokenizer(インクリメンタルデトークナイズ)

参照: target/vllm/vllm/v1/engine/detokenizer.py:30 (IncrementalDetokenizer)

トークンからテキストへの変換はインクリメンタルに行われ、ストリーミング出力を実現する。

クラス条件方式
FastIncrementalDetokenizerPreTrainedTokenizerFast 使用時HF tokenizersのDecodeStreamで高速変換
SlowIncrementalDetokenizerその他のトークナイザdetokenize_incrementally()でPython変換
IncrementalDetokenizerトークナイザなしNo-op(テキスト出力なし)

update()メソッドで各トークンをインクリメンタルにデコードし、同時にcheck_stop_strings()で停止文字列を検出する(target/vllm/vllm/v1/engine/detokenizer.py:316)。

Prefill vs Decode

vLLM v1はUnified Compute Modelを採用し、PrefillとDecodeを明示的に区別しない。両者はnum_computed_tokensの進捗によって暗黙的に区分される。

統一管理の仕組み

各リクエストはnum_computed_tokensフィールドで計算済みトークン数を追跡する:

プロンプト: [A, B, C, D, E]    (len=5)
num_computed_tokens: 0 → 5 → 6 → 7 → ...

Prefillフェーズ: num_computed_tokens < len(prompt_token_ids)
  → 複数トークンを一度に計算(チャンクプリフィル可能)

Decodeフェーズ: num_computed_tokens >= len(prompt_token_ids)
  → 1トークンずつ生成

Schedulerでの扱い

Scheduler.schedule()はPrefill/Decodeを区別せず、トークン予算の範囲内で各リクエストに計算トークン数を割り当てる:

  • 新規リクエスト(WAITING→RUNNING): num_tokens = len(prompt_token_ids) - num_computed_tokens(プレフィックスキャッシュヒット分を差し引き)
  • 継続リクエスト(RUNNING): num_tokens = 1(Decode 1トークン)
  • 予算不足時は部分的なPrefill(チャンクプリフィル)も可能

GPUModelRunner内での違い

GPUModelRunner.execute_model()は入力準備の段階で暗黙的にPrefill/Decodeを処理する:

  • _prepare_inputs(): num_scheduled_tokensに基づいて入力トークンと位置を計算。Prefillなら複数トークン、Decodeなら1トークン
  • _build_attention_metadata(): Prefillはフルattention、Decodeはキャッシュ済みKVに対するattentionのメタデータを構築
  • モデルフォワード: 入力テンソルのサイズが異なるだけで、同一のforward()を実行

この統一モデルにより、同一バッチ内にPrefillリクエストとDecodeリクエストを混在させるContinuous Batchingが自然に実現される。

コンポーネント優先度(確定)

Phase 2での深堀り順序。ユーザー関心領域とフロー上の重要度に基づく。

優先度コンポーネント理由現在の深度
SKVCacheManagerユーザー関心1位(メモリ管理/KVキャッシュ)。PagedAttention、ブロック管理、Eviction[MEDIUM]
ASchedulerKVCacheManagerと密連携、推論パイプライン全体を制御。Continuous Batching[MEDIUM]
AGPUModelRunner推論実行の中核。6277行の巨大クラス。将来のプラグイン開発に重要[SHALLOW]
BEngineCorestep()サイクル、batch_queueパイプライン。全体の統合ポイント[MEDIUM]
BOutputProcessorデトークナイズ、停止判定。ストリーミング出力の仕組み[SHALLOW]
CAsyncLLM, InputProcessorエントリポイント。薄いレイヤー[SHALLOW]
CExecutor, Worker委譲パターン。分散推論時のみ詳細が必要[SHALLOW]
CEngineCoreClientZMQ IPC通信層。プロトコルは把握済み[SHALLOW]

参照ファイル一覧

ファイル主要クラス/関数役割
target/vllm/vllm/entrypoints/llm.pyLLM.generate() (L396), _add_request() (L1850)同期エントリポイント
target/vllm/vllm/v1/engine/async_llm.pyAsyncLLM.generate() (L537), add_request() (L286)非同期エントリポイント
target/vllm/vllm/v1/engine/input_processor.pyInputProcessor.process_inputs() (L521)入力処理
target/vllm/vllm/v1/engine/__init__.pyEngineCoreRequest (L55), EngineCoreOutput (L130), EngineCoreOutputs (L176)境界データ構造
target/vllm/vllm/v1/engine/core_client.pyEngineCoreClient (L63), MPClient (L442), AsyncMPClient (L822)ZMQ IPC通信
target/vllm/vllm/v1/engine/core.pyEngineCore.add_request() (L288), step() (L389)推論ループ本体
target/vllm/vllm/v1/core/sched/scheduler.pyScheduler.schedule() (L321), update_from_output() (L1241)スケジューリング
target/vllm/vllm/v1/core/sched/output.pySchedulerOutput (L184), NewRequestData (L34), CachedRequestData (L114)スケジュール出力データ構造
target/vllm/vllm/v1/core/kv_cache_manager.pyKVCacheManager.allocate_slots() (L206), get_computed_blocks() (L164)KVキャッシュ管理
target/vllm/vllm/v1/core/block_pool.pyBlockPool (L128)物理ブロック管理
target/vllm/vllm/v1/request.pyRequestリクエスト内部状態
target/vllm/vllm/v1/outputs.pyModelRunnerOutput (L160)モデル推論出力
target/vllm/vllm/v1/executor/abstract.pyExecutor (ABC), execute_model() (L202), collective_rpc() (L180)実行層抽象
target/vllm/vllm/v1/executor/uniproc_executor.pyUniProcExecutor (L26)単一プロセス実行
target/vllm/vllm/v1/executor/multiproc_executor.pyMultiprocExecutor (L93)マルチプロセス実行
target/vllm/vllm/v1/worker/gpu_worker.pyWorker.execute_model() (L604), sample_tokens() (L598)GPU Worker
target/vllm/vllm/v1/worker/gpu_model_runner.pyGPUModelRunner.execute_model() (L3312), sample_tokens() (L3621), ExecuteModelState (L313)モデル実行
target/vllm/vllm/v1/engine/output_processor.pyOutputProcessor.process_outputs() (L582), RequestState.make_request_output() (L269)出力処理
target/vllm/vllm/v1/engine/detokenizer.pyIncrementalDetokenizer (L30), FastIncrementalDetokenizer (L169), check_stop_strings() (L316)デトークナイズ
target/vllm/vllm/v1/engine/logprobs.pyLogprobsProcessor (L28)logprobs処理
target/vllm/vllm/outputs.pyRequestOutput (L86), CompletionOutput (L23)最終出力データ構造

マルチモーダル推論パスの差分

テキスト推論フローに対し、画像等のマルチモーダル入力がある場合の主要な差分を以下に示す。詳細は マルチモーダル処理パイプライン を参照。

フロントエンド(P0)の差分

  1. チャットテンプレート: プレースホルダー(<start_of_image> 等)がプロンプトに挿入される
  2. HF Processor実行: 画像を pixel_values テンソルに変換(リサイズ、正規化、パッチ分割)
  3. MMハッシュ計算: MultiModalHasher でコンテンツベースのblake3ハッシュを生成
  4. ProcessorCache: HF処理結果をキャッシュ(4種類の実装: processor_only/lru/shm/none)
  5. EngineCoreRequest: mm_features: list[MultiModalFeatureSpec] にテンソルデータ・位置情報・ハッシュを格納

バックエンド(P1)の差分

  1. EncoderCacheManager: エンコーダ出力をリファレンスカウント方式で管理。キャッシュヒットでエンコーダ計算スキップ
  2. Scheduler: encoder_compute_budget でステップあたりのエンコーダ計算量を制御
  3. GPUModelRunner:
    • _execute_mm_encoder(): ビジョンエンコーダ実行(model.embed_multimodal()
    • _gather_mm_embeddings(): キャッシュからプレースホルダー位置に対応する埋め込みを取得
    • embed_input_ids(): masked_scatter_ でテキスト埋め込みとビジョン埋め込みをマージ
  4. モデルforward: input_ids ではなく inputs_embeds(マージ済み)が渡される