Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
f64a519
refactor(stage_input_processors): RFC #4872 P3/P8a shared helpers + P…
RyanYun09 Aug 25, 2026
f757802
feat(stage_input_processors): RFC #4872 P2 processor registry + start…
RyanYun09 Aug 25, 2026
1ba009b
feat(stage_input_processors): RFC #4872 P8b unify async-chunk placeho…
RyanYun09 Aug 25, 2026
cad699d
refactor(stage_input_processors): RFC #4872 P6 centralize codec/stop_…
RyanYun09 Aug 25, 2026
51cfc7d
fix(stage_input_processors): resolve precheck-pr blockers (ruff/spdx/…
RyanYun09 Aug 30, 2026
7037100
docs(design): document stage input processor contract (RFC #4872)
RyanYun09 Aug 30, 2026
ac30134
docs(contributing): align model guides with stage input processor con…
RyanYun09 Aug 30, 2026
356007a
style(docs): apply ruff format to markdown code blocks (RFC #4872)
RyanYun09 Aug 30, 2026
c42c6c5
fix(stage_input_processors): restore diffusion output_formatter wrap-…
RyanYun09 Aug 31, 2026
11a2738
fix(stage_input_processors): make P8b stage0_only prewarm the chat-te…
RyanYun09 Aug 31, 2026
ec668a6
fix(engine): extract_legacy_stage_metadata fails fast on processor re…
RyanYun09 Aug 31, 2026
edab992
refactor(stage_input_processors): wrap orchestrator processors once p…
RyanYun09 Aug 31, 2026
8f7c25c
refactor(stage_input_processors): remove dead length helper and move …
RyanYun09 Aug 31, 2026
ccfb0d8
feat(engine): wire dead_processor_hint at orchestrator startup (RFC #…
RyanYun09 Aug 31, 2026
f33009c
docs(contributing): align adding_omni_model snippet with the real *_t…
RyanYun09 Aug 31, 2026
18e27d6
test(stage_input_processors): normalize docstrings for upstream contr…
RyanYun09 Aug 31, 2026
657f0b6
refactor(stage_input_processors): normalize module comments and docst…
RyanYun09 Aug 31, 2026
6096bb5
fix(stage_input_processors): silence mypy attr-defined on *_token_onl…
RyanYun09 Aug 31, 2026
22d7816
fix(stage_input_processors): preserve keyword-only sampling_params an…
RyanYun09 Sep 2, 2026
d0bf725
fix(stage_input_processors): restore recursive per-item to_token_id_l…
RyanYun09 Sep 2, 2026
8e5e030
docs(contributing): wire ar2decoder_full_payload producer in adding_t…
RyanYun09 Sep 2, 2026
f766fa0
fix(stage_input_processors): validate producer keyword call with Sign…
RyanYun09 Sep 2, 2026
344cab4
fix(engine): do not report the selected sync hook as dead (RFC #4872 P2)
RyanYun09 Sep 2, 2026
ace8190
fix(engine): cache per-stage prewarm placeholder builder resolution (…
RyanYun09 Sep 2, 2026
b804311
fix(merge): apply RFC resolve_processor to omni_connector_runtime and…
RyanYun09 Sep 15, 2026
f7b01ef
refactor(stage_input_processors): 消除 _ensure_list/_to_cpu_tensor/_ext…
RyanYun09 Sep 19, 2026
6319bcf
fix(stage_init_utils): restore rebase-lost prompt_transform_func and …
RyanYun09 Sep 19, 2026
1de423f
fix(engine): remove dead _call_custom_process_input (Ruff F821 in CI …
RyanYun09 Sep 19, 2026
96cf213
fix(merge): remove leftover unused imports from thin-wrapper mixin (R…
RyanYun09 Sep 19, 2026
f3ca0e9
test: add regression tests for async_chunk_prewarm_prompt_len=37 (amy…
RyanYun09 Sep 19, 2026
6f870af
fix: resolve P1 regressions from amy-why-3459 review and restore auk …
RyanYun09 Sep 19, 2026
6385b60
fix: apply prewarm prompt_len override on builder success and validat…
RyanYun09 Oct 8, 2026
554d5b5
fix(merge): resolve post-rebase F821 undefined names (importlib, _ens…
RyanYun09 Oct 8, 2026
23f651f
fix: accept structurally compatible connector payload hooks and resto…
RyanYun09 Oct 9, 2026
3ec3a3d
docs: remove internal phase markers and PR-tag residue from tests and…
RyanYun09 Oct 9, 2026
679cfcd
refactor(stage_input_processors): RFC #4872 Tier 1/2/2.5/2.75/2.8 — t…
RyanYun09 Oct 10, 2026
d06aa9b
fix(engine): read sync_process_input_func and prewarm_placeholder_bui…
RyanYun09 Oct 13, 2026
729d593
fix(stage_input_processors): accept 3-D per-step codec frames in extr…
RyanYun09 Oct 14, 2026
4518e23
Merge branch 'main' into rfc4872-stage-input-processors-refactor
amy-why-3459 Oct 14, 2026
83199a2
[Core] fix scheduler async-chunk hook resolution and prewarm snapshot…
RyanYun09 Oct 15, 2026
71af62a
Merge branch 'main' of https://github.com/vllm-project/vllm-omni
RyanYun09 Oct 15, 2026
5856af4
[Core] address linyueqian's optional follow-ups on connector hook res…
RyanYun09 Oct 16, 2026
946570a
[Core] fix pre-commit failures in stage_input_processors tests
RyanYun09 Oct 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 73 additions & 32 deletions docs/contributing/model/adding_omni_model.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,8 @@ The thinker stage handles multimodal understanding. Key features:
from vllm.model_executor.models.interfaces import SupportsMultiModal, SupportsPP
from vllm.model_executor.models.qwen3_moe import Qwen3MoeForCausalLM

class Qwen3OmniMoeThinkerForConditionalGeneration(
Qwen3MoeForCausalLM,
SupportsMultiModal,
SupportsPP
):

class Qwen3OmniMoeThinkerForConditionalGeneration(Qwen3MoeForCausalLM, SupportsMultiModal, SupportsPP):
"""Thinker stage: multimodal understanding → text generation."""

def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
Expand All @@ -93,10 +90,7 @@ class Qwen3OmniMoeThinkerForConditionalGeneration(
The talker stage converts text embeddings to codec codes:

```python
class Qwen3OmniMoeTalkerForConditionalGeneration(
Qwen3MoeForCausalLM,
SupportsPP
):
class Qwen3OmniMoeTalkerForConditionalGeneration(Qwen3MoeForCausalLM, SupportsPP):
"""Talker stage: text embeddings → RVQ codec codes."""

def __init__(self, vllm_config, talker_config, prefix):
Expand Down Expand Up @@ -156,8 +150,7 @@ class Qwen3OmniMoeForConditionalGeneration(
if self.model_stage == "thinker":
# Initialize thinker model
thinker_vllm_config = vllm_config.with_hf_config(
config.thinker_config,
architectures=["Qwen3OmniMoeThinkerForConditionalGeneration"]
config.thinker_config, architectures=["Qwen3OmniMoeThinkerForConditionalGeneration"]
)
self.thinker = init_vllm_registered_model(
vllm_config=thinker_vllm_config,
Expand All @@ -170,8 +163,7 @@ class Qwen3OmniMoeForConditionalGeneration(
elif self.model_stage == "talker":
# Initialize talker model
talker_vllm_config = vllm_config.with_hf_config(
config.talker_config,
architectures=["Qwen3OmniMoeTalkerForConditionalGeneration"]
config.talker_config, architectures=["Qwen3OmniMoeTalkerForConditionalGeneration"]
)
self.talker = init_vllm_registered_model(
vllm_config=talker_vllm_config,
Expand All @@ -184,8 +176,7 @@ class Qwen3OmniMoeForConditionalGeneration(
elif self.model_stage == "code2wav":
# Initialize code2wav model
code2wav_vllm_config = vllm_config.with_hf_config(
config.code2wav_config,
architectures=["Qwen3OmniMoeCode2Wav"]
config.code2wav_config, architectures=["Qwen3OmniMoeCode2Wav"]
)
self.code2wav = init_vllm_registered_model(
vllm_config=code2wav_vllm_config,
Expand All @@ -196,8 +187,7 @@ class Qwen3OmniMoeForConditionalGeneration(
self.model = self.code2wav
else:
raise ValueError(
f"Invalid model_stage: {self.model_stage}. "
f"Must be one of: 'thinker', 'talker', 'code2wav'"
f"Invalid model_stage: {self.model_stage}. Must be one of: 'thinker', 'talker', 'code2wav'"
)
```

Expand Down Expand Up @@ -308,11 +298,10 @@ Register your model in `vllm_omni/model_executor/models/registry.py`:
```python
_OMNI_MODELS = {
# ... existing models ...

# Your new model
"YourModelForConditionalGeneration": (
"your_model_name", # Module folder name
"your_model", # Module file name (without .py)
"your_model_name", # Module folder name
"your_model", # Module file name (without .py)
"YourModelForConditionalGeneration", # Class name
),
"YourModelThinkerForConditionalGeneration": (
Expand Down Expand Up @@ -410,10 +399,7 @@ The stage transition process follows these steps:
else:
# Custom transition function (YOUR CODE HERE)
return self.custom_process_input_func(
stage_list,
self.engine_input_source,
prompt,
self.requires_multimodal_data
stage_list, self.engine_input_source, prompt, self.requires_multimodal_data
)
```
- If `custom_process_input_func` is configured, it calls that function
Expand Down Expand Up @@ -506,35 +492,62 @@ thinker_hidden_states = output.multimodal_output["24"]

Create stage transition processors in `vllm_omni/model_executor/stage_input_processors/your_model_name.py`. Each inter-stage edge should provide a **coherent processor set** rather than a single monolithic function:

| Suffix | Role | Runs when |
| -------- | ------ | ----------- |
| `*_full_payload` | Worker-side payload producer | `async_chunk=false`; accumulates tensors and ships via connector |
| `*_async_chunk` | Scheduler-side streaming producer | `async_chunk=true`; emits per-chunk payloads |
| `*_token_only` | Orchestrator placeholder builder | `async_chunk=false`; allocates downstream prompt slots only |
| Suffix | Registry kind | Runs where | Role |
| -------- | --------------- | ------------ | ------ |
| `*_full_payload` | `producer_full_payload` | Worker (producer-side) | `async_chunk=false`; accumulates tensors and ships via connector |
| `*_async_chunk` | `producer_async_chunk` | Scheduler (producer-side) | `async_chunk=true`; emits per-chunk payloads |
| `*_token_only` | `placeholder_prompt_builder` | Orchestrator (consumer-side) | Allocates downstream prompt slots only (both modes) |

A model that supports **both** `async_chunk=false` and `async_chunk=true` must
implement all three processors for every inter-stage edge — `*_full_payload`,
`*_async_chunk` and `*_token_only` (the `*_token_only` placeholder is required
in both modes). See the
[Stage Input Processor Contract](../../design/feature/async_chunk.md#stage-input-processor-contract)
for the full contract, naming convention, and registry rules.

Orchestrator-facing builders are normalized to the C1 contract
`(source_outputs, ctx: OrchestratorInputContext)` by
`wrap_orchestrator_processor`. The legacy `*_token_only` shell keeps its
positional signature for call-site compatibility; the actual C1 entry point is
the `build_forward_placeholder` / `build_prewarm_placeholder` pair attached to
the shell (the dual-entry design):

```python
# qwen3_omni.py (Thinker → Talker, non-async path)
# qwen3_omni.py (Thinker → Talker)

def thinker2talker_token_only(
source_outputs: list[Any],
prompt: OmniTokensPrompt | TextPrompt | None = None,
requires_multimodal_data: bool = False,
streaming_context: Any | None = None,
) -> list[OmniTokensPrompt]:
"""Allocate talker prefill slots; bulk tensors arrive via the connector."""
"""Allocate talker prefill slots; bulk tensors arrive via the connector.

Legacy positional shell (C2). The orchestrator wraps it once via
``wrap_orchestrator_processor``, which forwards the C1
``OrchestratorInputContext`` fields back into the positional arguments.
"""
...


# Attached C1 dual-entry builders:
# thinker2talker_token_only.build_forward_placeholder(source_outputs, ctx)
# thinker2talker_token_only.build_prewarm_placeholder(*, stage0_prompt, ctx, ...)


def thinker2talker_full_payload(
*,
transfer_manager: Any,
pooling_output: dict[str, Any],
request: OmniEngineCoreRequest,
is_finished: bool = ..., # optional: the worker retries without it
) -> dict[str, Any] | None:
"""Pack accumulated thinker hidden states into OmniPayload for stage-1."""
...


def thinker2talker_async_chunk(
*,
transfer_manager: Any,
multimodal_output: OmniPayload | dict[str, Any],
request: OmniEngineCoreRequest,
Expand All @@ -544,6 +557,11 @@ def thinker2talker_async_chunk(
...
```

The producer-side builders (`*_full_payload`, `*_async_chunk`) are
keyword-only and never receive an `OrchestratorInputContext`;
`pooling_output` / `multimodal_output` are load-bearing keyword names of the
connector data plane.

Wire these in `pipeline.py`:

```python
Expand All @@ -560,7 +578,30 @@ StagePipelineConfig(
),
```

Do **not** add a no-suffix `thinker2talker` when a `sync_process_input_func` is already declared — `_select_processor_funcs()` always prefers the `*_token_only` hook in non-async mode, so the bare function would never run. See `docs/design/rfc_stage_input_processors_refactor.md` for the full contract.
### Registry registration and validation

The processor registry infers a processor's kind from its name suffix and
structurally validates the signature at startup (it never executes processor
logic). Names that do not follow the suffix convention can register an explicit
kind override once, e.g. at module import time:

```python
from vllm_omni.model_executor.stage_input_processors import register_processor

register_processor(f"{_PROC}.thinker2talker_token_only", "placeholder_prompt_builder")
```

At startup the runtime resolves each processor through
`resolve_processor(path, expected_kind=...)` — import -> `infer_kind` ->
`validate_processor` -> expected-kind check, returning a `ProcessorSpec` whose
`fn` matches the legacy lookup. Hard violations raise
`ProcessorValidationError`; soft mismatches emit a `RuntimeWarning`. New model
processors should therefore match the suffix convention and the corresponding
signature exactly.

Do **not** add a no-suffix `thinker2talker` when a `sync_process_input_func` is
already declared — `_select_processor_funcs()` always prefers the `*_token_only`
hook in non-async mode, so the bare function would never run.

## Testing

Expand Down
Loading
Loading