Signed-off-by: Jungle430 <junglece430@gmail.com> Co-authored-by: Codex <noreply@openai.com>
179 lines
6.5 KiB
Python
179 lines
6.5 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
|
|
|
"""Output modality types for vLLM-Omni.
|
|
|
|
This module defines the OutputModality enum and TensorAccumulationStrategy
|
|
for type-safe multimodal output routing and tensor merging.
|
|
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from enum import Enum, Flag, auto
|
|
|
|
|
|
class OutputModalityNames(str, Enum):
|
|
"""Keys for output modalities.
|
|
|
|
TODO: (Alex) Integrate this with the big-flag enum below + throughout the code
|
|
for better type safety (currently only used for output processor).
|
|
"""
|
|
|
|
# Keep the string conversion provided by ``enum.StrEnum`` while using the
|
|
# Python 3.10-compatible ``str, Enum`` form.
|
|
__str__ = str.__str__
|
|
|
|
TEXT = "text"
|
|
IMAGE = "image"
|
|
AUDIO = "audio"
|
|
LATENT = "latent"
|
|
TOKEN_IDS = "token_ids"
|
|
|
|
|
|
# Specify which output modalities may be drained when handling delta messages.
|
|
# Retain intermediate outputs such as latents and token IDs across emissions
|
|
# so downstream stages can receive full context when async chunk isn't enabled.
|
|
NON_DRAINABLE_MODALITIES = {
|
|
OutputModalityNames.TEXT,
|
|
OutputModalityNames.LATENT,
|
|
OutputModalityNames.TOKEN_IDS,
|
|
}
|
|
DRAINABLE_MODALITIES = {mod for mod in OutputModalityNames if mod not in NON_DRAINABLE_MODALITIES}
|
|
|
|
|
|
class OutputModality(Flag):
|
|
"""Bit-flag enum for output modalities.
|
|
|
|
Compose freely with ``|`` — no need to enumerate every combination.
|
|
|
|
Single: ``OutputModality.TEXT``, ``OutputModality.IMAGE``, ...
|
|
Compound: ``OutputModality.TEXT | OutputModality.IMAGE`` (text+image)
|
|
|
|
TODO: Describe modality, representation, and update/retention rules per
|
|
named output field instead of sharing one stage-level type. Audit legacy
|
|
LATENT uses when introducing that contract.
|
|
|
|
Note: POOLING is intentionally excluded. Pooling/embedding is vLLM's
|
|
native path (pooling_output → PoolingRequestOutput), handled entirely
|
|
by the base OutputProcessor. vLLM-Omni's layer does not participate.
|
|
"""
|
|
|
|
TEXT = auto()
|
|
IMAGE = auto()
|
|
AUDIO = auto()
|
|
LATENT = auto()
|
|
"""Continuous latent tensors used for further computation or decoding.
|
|
|
|
Existing LATENT producers may carry other representations; their current
|
|
payload contracts remain unchanged.
|
|
"""
|
|
|
|
TOKEN_IDS = auto()
|
|
"""Discrete vocabulary/codebook indices, e.g. GLM-Image's AR image tokens.
|
|
|
|
Unlike continuous latents, these values identify discrete symbols.
|
|
The distinction is semantic, not inferred from the tensor dtype.
|
|
"""
|
|
|
|
@classmethod
|
|
def from_string(cls, s: str | None) -> OutputModality:
|
|
"""Parse canonical modality names into an OutputModality flag.
|
|
|
|
Names must be lowercase and contain no whitespace. Compound names
|
|
may be separated by + or ,. None preserves the default TEXT modality.
|
|
|
|
Examples::
|
|
|
|
OutputModality.from_string("text+image")
|
|
# → OutputModality.TEXT | OutputModality.IMAGE
|
|
"""
|
|
if s is None:
|
|
return cls.TEXT
|
|
|
|
result = cls(0)
|
|
for part in s.replace(",", "+").split("+"):
|
|
try:
|
|
name = OutputModalityNames(part)
|
|
except ValueError:
|
|
raise ValueError(
|
|
f"Unknown modality: {part!r}. Supported: {[m.value for m in OutputModalityNames]}"
|
|
) from None
|
|
result |= cls[name.name]
|
|
return result
|
|
|
|
@property
|
|
def has_text(self) -> bool:
|
|
return OutputModality.TEXT in self
|
|
|
|
@property
|
|
def has_multimodal(self) -> bool:
|
|
return bool(self & ~OutputModality.TEXT)
|
|
|
|
|
|
class TensorAccumulationStrategy(Enum):
|
|
"""Strategy for merging incremental multimodal tensors."""
|
|
|
|
CONCAT_DIM0 = "concat_dim0"
|
|
"""Concatenate along dimension 0. Used for image/latent/token-ID tensors."""
|
|
|
|
CONCAT_LAST = "concat_last"
|
|
"""Concatenate along the last dimension. Used for audio waveforms."""
|
|
|
|
APPEND_LIST = "append_list"
|
|
"""Append to a list (no tensor concatenation)."""
|
|
|
|
REPLACE = "replace"
|
|
"""Replace previous tensor entirely with the latest one."""
|
|
|
|
|
|
# Per-tensor-key overrides for the modality-wide default below. A modality
|
|
# is a coarse hint (e.g. AUDIO tensors are usually waveform chunks that grow
|
|
# along the last dimension), but some models emit tensors under that same
|
|
# modality whose accumulation semantics differ -- e.g. discrete codec-frame
|
|
# matrices that grow along dim 0, or a constant reference/context tensor
|
|
# re-emitted unchanged at every step. Pipelines that produce such keys
|
|
# register the correct strategy here via ``register_key_accumulation_strategy``
|
|
# instead of forcing every tensor of the modality through one default.
|
|
# Schema-defined prompt conditioning is a complete reference snapshot. Keep
|
|
# these defaults in the output process too, which may never import the model's
|
|
# worker-side stage input processor. Feature lengths can change between
|
|
# snapshots and cannot be concatenated as generated content deltas.
|
|
_KEY_ACCUMULATION_OVERRIDES: dict[str, TensorAccumulationStrategy] = {
|
|
key: TensorAccumulationStrategy.REPLACE
|
|
for key in ("embed.speech_token", "embed.speech_feat", "embed.embedding", "embed.speech_token_len")
|
|
}
|
|
|
|
|
|
def register_key_accumulation_strategy(key: str, strategy: TensorAccumulationStrategy) -> None:
|
|
"""Register a per-key override for ``get_accumulation_strategy``.
|
|
|
|
Args:
|
|
key: The flattened multimodal output key this override applies to,
|
|
e.g. ``"codes.audio"`` as produced by
|
|
``vllm_omni.data_entry_keys.flatten_payload`` from a nested
|
|
``{"codes": {"audio": tensor}}`` payload.
|
|
strategy: The accumulation strategy to use for this key, regardless
|
|
of the modality its request is otherwise associated with.
|
|
"""
|
|
_KEY_ACCUMULATION_OVERRIDES[key] = strategy
|
|
|
|
|
|
def get_accumulation_strategy(
|
|
modality: OutputModality,
|
|
key: str | None = None,
|
|
) -> TensorAccumulationStrategy:
|
|
"""Determine the tensor merge strategy for one output key.
|
|
|
|
A registered per-key override (see ``register_key_accumulation_strategy``)
|
|
always wins; otherwise the strategy falls back to the modality-wide
|
|
default.
|
|
"""
|
|
if key is not None and key in _KEY_ACCUMULATION_OVERRIDES:
|
|
return _KEY_ACCUMULATION_OVERRIDES[key]
|
|
if OutputModality.AUDIO in modality:
|
|
return TensorAccumulationStrategy.CONCAT_LAST
|
|
if OutputModality.IMAGE in modality or OutputModality.LATENT in modality:
|
|
return TensorAccumulationStrategy.CONCAT_DIM0
|
|
return TensorAccumulationStrategy.CONCAT_DIM0 # TEXT / TOKEN_IDS default
|