refactor: reduce copied vLLM 0.29 compatibility code
Reuse upstream weight mapping and multimodal processing, consolidate Omni processor bridges, and trim unused Hunyuan internals. Route no-media kwargs through the active renderer and preserve GLM generation prompt formatting. Validation: 453 CPU tests passed, 7 skipped; processor imports, Ruff, and diff checks passed. GPU and end-to-end CI not rerun. Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
This commit is contained in:
1 parent
eef8e2bb94
commit
efda9369a8
37 files changed
+448
-1506
No files matched your search
@@ -1593,73 +1593,42 @@ def test_model_path_resolver_is_generic_and_model_owned(tmp_path):
|
||||
assert "model_path_resolver" not in engine_args
|
||||
|
||||
|
||||
def test_build_stage0_input_processor_uses_omni_input_preprocessor(monkeypatch):
|
||||
def test_build_stage0_input_processor_wraps_active_renderer(monkeypatch):
|
||||
import vllm_omni.engine.stage_init_utils as init_mod
|
||||
from vllm_omni.inputs.preprocess import OmniRenderer
|
||||
|
||||
original = object()
|
||||
|
||||
class DummyInputProcessor:
|
||||
def __init__(self, vllm_config, renderer=None):
|
||||
self.vllm_config = vllm_config
|
||||
self.renderer = renderer or object()
|
||||
self.input_preprocessor = None
|
||||
|
||||
class DummyOmniInputPreprocessor:
|
||||
def __init__(self, vllm_config, renderer=None):
|
||||
self.vllm_config = vllm_config
|
||||
self.renderer = renderer
|
||||
self.renderer = renderer or original
|
||||
|
||||
monkeypatch.setattr(init_mod, "InputProcessor", DummyInputProcessor)
|
||||
monkeypatch.setattr(init_mod, "OmniInputPreprocessor", DummyOmniInputPreprocessor)
|
||||
|
||||
input_processor = build_stage0_input_processor(
|
||||
processor = build_stage0_input_processor(
|
||||
types.SimpleNamespace(model_config=types.SimpleNamespace(try_get_generation_config=lambda: {}))
|
||||
)
|
||||
|
||||
assert isinstance(input_processor.input_preprocessor, DummyOmniInputPreprocessor)
|
||||
assert input_processor.input_preprocessor.renderer is input_processor.renderer
|
||||
assert isinstance(processor.renderer, OmniRenderer)
|
||||
assert processor.renderer._renderer is original
|
||||
assert not hasattr(processor, "input_preprocessor")
|
||||
|
||||
|
||||
def test_build_stage0_input_processor_does_not_resolve_tokenizer_when_skipped(
|
||||
monkeypatch,
|
||||
):
|
||||
def test_build_stage0_input_processor_does_not_resolve_tokenizer_when_skipped(monkeypatch):
|
||||
import vllm_omni.engine.stage_init_utils as init_mod
|
||||
|
||||
token_only_renderer = object()
|
||||
seen = {}
|
||||
original = object()
|
||||
|
||||
class DummyInputProcessor:
|
||||
def __init__(self, vllm_config, renderer=None):
|
||||
if renderer is None:
|
||||
raise AssertionError("skip_tokenizer_init must supply a renderer")
|
||||
seen["renderer"] = renderer
|
||||
assert renderer is original
|
||||
self.renderer = renderer
|
||||
self.input_preprocessor = None
|
||||
|
||||
class DummyOmniInputPreprocessor:
|
||||
def __init__(self, vllm_config, renderer=None):
|
||||
seen["preprocessor_renderer"] = renderer
|
||||
|
||||
monkeypatch.setattr(init_mod, "InputProcessor", DummyInputProcessor)
|
||||
monkeypatch.setattr(
|
||||
init_mod,
|
||||
"_build_token_only_renderer",
|
||||
lambda _config: token_only_renderer,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
init_mod,
|
||||
"OmniInputPreprocessor",
|
||||
DummyOmniInputPreprocessor,
|
||||
)
|
||||
|
||||
monkeypatch.setattr(init_mod, "_build_token_only_renderer", lambda _: original)
|
||||
config = types.SimpleNamespace(
|
||||
model_config=types.SimpleNamespace(
|
||||
skip_tokenizer_init=True,
|
||||
try_get_generation_config=lambda: {},
|
||||
)
|
||||
model_config=types.SimpleNamespace(skip_tokenizer_init=True, try_get_generation_config=lambda: {})
|
||||
)
|
||||
build_stage0_input_processor(config)
|
||||
|
||||
assert seen["renderer"] is token_only_renderer
|
||||
assert seen["preprocessor_renderer"] is token_only_renderer
|
||||
processor = build_stage0_input_processor(config)
|
||||
assert processor.renderer._renderer is original
|
||||
|
||||
|
||||
def test_inject_kv_stage_info_infers_sender_tp_topology():
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
from transformers import BatchFeature
|
||||
|
||||
from vllm_omni.inputs.mm_processor import OmniMultiModalProcessor
|
||||
|
||||
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("counts", [{}, {"audio": 1, "image": 0}])
|
||||
@pytest.mark.parametrize("original_text", [None, "", "voice cloning text"])
|
||||
def test_custom_processor_bridge_preserves_prompt_kwargs_and_passthrough(counts, original_text):
|
||||
class Processor(OmniMultiModalProcessor):
|
||||
def _get_mm_fields_config(self, *args):
|
||||
return {}
|
||||
|
||||
def _get_prompt_updates(self, *args):
|
||||
return []
|
||||
|
||||
processor = object.__new__(Processor)
|
||||
processor.dummy_inputs = SimpleNamespace(get_dummy_text=Mock(return_value="dummy"))
|
||||
processor._get_hf_mm_data = Mock(return_value=({"audios": ["wave"]}, {"embedding": "preserve"}))
|
||||
processor._call_hf_processor = Mock(return_value=BatchFeature({"features": "processed"}))
|
||||
items = Mock()
|
||||
items.get_all_counts.return_value = counts
|
||||
kwargs = {"sampling_rate": 16000}
|
||||
if original_text is not None:
|
||||
kwargs[processor._OMNI_PROMPT_TEXT_KEY] = original_text
|
||||
before = dict(kwargs)
|
||||
result = processor._apply_hf_processor_main(items, kwargs)
|
||||
items.select.assert_called_once_with({k for k, v in counts.items() if v > 0})
|
||||
processor._call_hf_processor.assert_called_once_with(
|
||||
"dummy" if original_text is None else original_text,
|
||||
{"audios": ["wave"]},
|
||||
{"sampling_rate": 16000},
|
||||
{},
|
||||
)
|
||||
assert dict(result) == {"features": "processed", "embedding": "preserve"}
|
||||
assert kwargs == before
|
||||
@@ -1,35 +1,99 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
"""Unit tests for OmniInputPreprocessor._process_text routing."""
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||
"""Exercise Omni routing through the active upstream rendering pipeline."""
|
||||
|
||||
import asyncio
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, Mock
|
||||
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
import torch
|
||||
from vllm.inputs import tokens_input
|
||||
from vllm.pooling_params import PoolingParams
|
||||
from vllm.renderers import BaseRenderer
|
||||
from vllm.renderers.params import TokenizeParams
|
||||
from vllm.v1.engine.input_processor import InputProcessor
|
||||
|
||||
from vllm_omni.inputs.preprocess import OmniRenderer
|
||||
|
||||
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
||||
|
||||
|
||||
class TestProcessTextMmProcessorKwargsRouting:
|
||||
"""Presence-based routing: an explicit empty ``mm_processor_kwargs`` dict
|
||||
on the prompt still routes through ``_process_multimodal``. Required for
|
||||
AR-based image generation (e.g. GLM-Image t2i) where the HF processor
|
||||
supplies the image-generation scaffold from its own defaults and no extra
|
||||
kwargs are needed from the caller.
|
||||
"""
|
||||
class _Renderer(BaseRenderer):
|
||||
def __init__(self):
|
||||
self.model_config = SimpleNamespace(is_encoder_decoder=False, enable_prompt_embeds=True)
|
||||
self.tokenizer = None
|
||||
self.default_cmpl_tok_params = TokenizeParams(max_total_tokens=128)
|
||||
self._tokenize_prompt = lambda prompt, params: {**prompt, "prompt_token_ids": [1, 2, 3]}
|
||||
self._tokenize_prompt_async = AsyncMock(side_effect=self._tokenize_prompt)
|
||||
self._process_multimodal = Mock(return_value=tokens_input([1, 2, 3, 99]))
|
||||
self._process_multimodal_async = AsyncMock(side_effect=self._process_multimodal)
|
||||
|
||||
@pytest.fixture
|
||||
def preprocessor(self, mocker: MockerFixture):
|
||||
from vllm_omni.inputs.preprocess import OmniInputPreprocessor
|
||||
def render_messages(self, messages, params):
|
||||
return messages, {"prompt": "hello"}
|
||||
|
||||
instance = object.__new__(OmniInputPreprocessor)
|
||||
instance._process_multimodal = mocker.MagicMock(return_value={})
|
||||
instance._tokenize_prompt = mocker.MagicMock(return_value=[1, 2, 3])
|
||||
return instance
|
||||
|
||||
def test_empty_mm_processor_kwargs_routes_to_multimodal(self, preprocessor):
|
||||
preprocessor._process_text({"prompt": "hello", "mm_processor_kwargs": {}})
|
||||
assert preprocessor._process_multimodal.called
|
||||
assert not preprocessor._tokenize_prompt.called
|
||||
@pytest.fixture
|
||||
def renderer():
|
||||
return OmniRenderer(_Renderer())
|
||||
|
||||
def test_missing_mm_processor_kwargs_routes_to_tokenize(self, preprocessor):
|
||||
preprocessor._process_text({"prompt": "hello"})
|
||||
assert preprocessor._tokenize_prompt.called
|
||||
assert not preprocessor._process_multimodal.called
|
||||
|
||||
@pytest.mark.parametrize("kwargs", [{}, {"target_h": 512, "target_w": 768}])
|
||||
@pytest.mark.parametrize("tokenized", [False, True])
|
||||
def test_process_inputs_routes_no_media_processor_kwargs(renderer, kwargs, tokenized):
|
||||
# Keep real process_inputs/render_cmpl; stub unrelated config validation.
|
||||
processor = object.__new__(InputProcessor)
|
||||
processor.renderer = renderer
|
||||
processor.model_config = renderer.model_config
|
||||
processor.vllm_config = SimpleNamespace(
|
||||
parallel_config=SimpleNamespace(
|
||||
data_parallel_size=1,
|
||||
data_parallel_size_local=1,
|
||||
local_engines_only=False,
|
||||
)
|
||||
)
|
||||
processor._validate_params = Mock()
|
||||
processor._validate_lora = Mock()
|
||||
processor._validate_model_inputs = Mock()
|
||||
prompt = {"prompt_token_ids": [1, 2, 3]} if tokenized else {"prompt": "hello"}
|
||||
prompt.update(mm_processor_kwargs=kwargs, cache_salt="salt")
|
||||
request = processor.process_inputs("image-request", prompt, PoolingParams(), ("embed",))
|
||||
assert request.prompt_token_ids == [1, 2, 3, 99]
|
||||
assert request.cache_salt == "salt"
|
||||
renderer._renderer._process_multimodal.assert_called_once_with(
|
||||
[1, 2, 3],
|
||||
{},
|
||||
mm_processor_kwargs=kwargs,
|
||||
mm_uuids=None,
|
||||
skip_mm_cache=False,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("async_mode", [False, True])
|
||||
@pytest.mark.parametrize("kind", ["text", "tokens", "media", "kwargs", "embeds"])
|
||||
def test_renderer_preserves_routes_extras_and_cache_policy(renderer, async_mode, kind):
|
||||
prompt = {"prompt": "hello", "additional_information": {"speaker": 1}, "model_intermediate_buffer": {"x": 2}}
|
||||
if kind == "tokens":
|
||||
prompt["prompt_token_ids"] = [1, 2, 3]
|
||||
elif kind == "media":
|
||||
prompt["multi_modal_data"] = {"image": "image"}
|
||||
elif kind == "kwargs":
|
||||
prompt["mm_processor_kwargs"] = {}
|
||||
elif kind == "embeds":
|
||||
prompt["prompt_embeds"] = torch.ones(3, 4)
|
||||
if async_mode:
|
||||
(result,) = asyncio.run(renderer.render_cmpl_async([prompt], skip_mm_cache=True))
|
||||
else:
|
||||
(result,) = renderer.render_cmpl([prompt], skip_mm_cache=True)
|
||||
assert result["additional_information"] == {"speaker": 1}
|
||||
assert result["model_intermediate_buffer"] == {"x": 2}
|
||||
assert result["prompt"] == "hello"
|
||||
if kind in ("media", "kwargs"):
|
||||
assert result["prompt_token_ids"] == [1, 2, 3, 99]
|
||||
assert renderer._renderer._process_multimodal.call_args.kwargs["skip_mm_cache"] is True
|
||||
else:
|
||||
renderer._renderer._process_multimodal.assert_not_called()
|
||||
if kind == "embeds":
|
||||
torch.testing.assert_close(result["prompt_embeds"], prompt["prompt_embeds"])
|
||||
else:
|
||||
assert result["prompt_token_ids"] == [1, 2, 3]
|
||||
@@ -91,6 +91,44 @@ GlmImageRotaryEmbedding = _ar_mod.GlmImageRotaryEmbedding
|
||||
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("num_images", [0, 1, 2])
|
||||
@pytest.mark.parametrize("preformatted", [False, True])
|
||||
def test_generation_prompt_ids_preserve_hf_target_scaffold(num_images, preformatted):
|
||||
from functools import partial
|
||||
from types import SimpleNamespace
|
||||
|
||||
from transformers.models.glm_image.processing_glm_image import GlmImageProcessor
|
||||
from vllm.multimodal.parse import MultiModalDataItems
|
||||
from vllm.multimodal.processing import BaseMultiModalProcessor, ProcessorInputs
|
||||
|
||||
processor = object.__new__(GlmImageMultiModalProcessor)
|
||||
symbols = SimpleNamespace(grid_bos_token="<sop>", grid_eos_token="<eop>", bos_token="<image_start>")
|
||||
hf_processor = SimpleNamespace(
|
||||
_build_prompt_with_target_shape=partial(GlmImageProcessor._build_prompt_with_target_shape, symbols)
|
||||
)
|
||||
tokenizer = SimpleNamespace(encode=lambda text, **kwargs: list(text.encode()))
|
||||
processor.info = SimpleNamespace(
|
||||
get_tokenizer=lambda: tokenizer,
|
||||
get_hf_processor=lambda: hf_processor,
|
||||
get_hf_config=lambda: _make_hf_config(),
|
||||
)
|
||||
items = MagicMock(spec=MultiModalDataItems)
|
||||
items.get_all_counts.return_value = {"image": num_images}
|
||||
suffix = "<sop>16 24<eop>" + ("<sop>13 19<eop>" if not num_images else "") + "<image_start>"
|
||||
text_ids = list(b"a red bird")
|
||||
expected = [167855] * num_images + text_ids + list(suffix.encode())
|
||||
inputs = ProcessorInputs(
|
||||
prompt=expected if preformatted else text_ids,
|
||||
mm_data_items=items,
|
||||
hf_processor_mm_kwargs={"target_h": 512, "target_w": 768},
|
||||
)
|
||||
with patch.object(BaseMultiModalProcessor, "apply", side_effect=lambda inputs, timing: inputs) as upstream:
|
||||
result = processor.apply(inputs, MagicMock())
|
||||
assert result.prompt == expected
|
||||
assert inputs.prompt == (expected if preformatted else text_ids)
|
||||
upstream.assert_called_once()
|
||||
|
||||
|
||||
def test_glm_image_ar_omits_hidden_pooler_payload():
|
||||
assert GlmImageForConditionalGeneration.omni_pooler_payload_include_hidden is False
|
||||
|
||||
|
||||
@@ -43,6 +43,34 @@ _FEAT_DIM = 128
|
||||
_CHUNK_FRAMES = 3000
|
||||
|
||||
|
||||
@pytest.mark.parametrize("version", [(2, 5), (4, 5)])
|
||||
def test_prompt_batching_delegates_upstream_without_tts_kwargs(version):
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
info = SimpleNamespace(
|
||||
get_model_version=lambda: version,
|
||||
get_hf_processor=MagicMock(return_value=object()),
|
||||
ctx=SimpleNamespace(call_hf_processor=MagicMock()),
|
||||
)
|
||||
info.ctx.call_hf_processor.side_effect = (
|
||||
[{"features": [1]}, {"features": [2]}] if version == (2, 5) else [{"features": [1, 2]}]
|
||||
)
|
||||
kwargs = {"use_tts": True, "sampling_rate": 16000}
|
||||
result = MiniCPMO45OmniLLMMultiModalProcessor._call_hf_processor_on_prompts(
|
||||
SimpleNamespace(info=info), ["one", "two"], {"audios": [10, 20]}, kwargs, out_keys={"features"}
|
||||
)
|
||||
assert result == {"features": [1, 2]}
|
||||
assert kwargs == {"use_tts": True, "sampling_rate": 16000}
|
||||
for call in info.ctx.call_hf_processor.call_args_list:
|
||||
assert call.args[2] == {"sampling_rate": 16000}
|
||||
payloads = [call.args[1] for call in info.ctx.call_hf_processor.call_args_list]
|
||||
assert payloads == (
|
||||
[{"text": "one", "audios": 10}, {"text": "two", "audios": 20}]
|
||||
if version == (2, 5)
|
||||
else [{"text": ["one", "two"], "audios": [10, 20]}]
|
||||
)
|
||||
|
||||
|
||||
class TestMiniCPMOFieldConfig:
|
||||
def test_short_audios_stay_batched(self) -> None:
|
||||
# two <=30s audios: one feature entry + one 1-element lens per audio
|
||||
|
||||
@@ -620,10 +620,17 @@ def test_derive_audio_from_video_placeholders_only_pairs_true_videos():
|
||||
|
||||
|
||||
def test_qwen3_processor_inherits_vllm_omni_per_video_helpers():
|
||||
from vllm.model_executor.models.qwen3_omni_moe_thinker import (
|
||||
Qwen3OmniMoeThinkerMultiModalProcessor as UpstreamQwen3Processor,
|
||||
)
|
||||
|
||||
assert issubclass(
|
||||
Qwen3OmniMoeThinkerMultiModalProcessor,
|
||||
Qwen2_5OmniThinkerMultiModalProcessor,
|
||||
)
|
||||
assert Qwen3OmniMoeThinkerMultiModalProcessor._get_hf_mm_data is UpstreamQwen3Processor._get_hf_mm_data
|
||||
mro = Qwen3OmniMoeThinkerMultiModalProcessor.__mro__
|
||||
assert mro.index(Qwen2_5OmniThinkerMultiModalProcessor) < mro.index(UpstreamQwen3Processor)
|
||||
|
||||
|
||||
def test_qwen3_prompt_updates_prefer_hf_second_per_grid_ts_from_outputs():
|
||||
|
||||
@@ -113,7 +113,9 @@ def _save_peft_adapter(tmp_path: Path) -> Path:
|
||||
return adapter_dir
|
||||
|
||||
|
||||
def test_peft_expert_lora_loads_into_thinker(tmp_path: Path):
|
||||
def test_peft_expert_lora_loads_into_thinker(tmp_path: Path, monkeypatch):
|
||||
# This CPU harness tests weight mapping, not CUDA pinned-memory allocation.
|
||||
monkeypatch.setattr("vllm.lora.lora_model.PIN_MEMORY", False)
|
||||
adapter_dir = _save_peft_adapter(tmp_path)
|
||||
peft_helper = PEFTHelper.from_local_dir(str(adapter_dir), max_position_embeddings=128)
|
||||
lora_model = LoRAModel.from_local_checkpoint(
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
import pytest
|
||||
import torch
|
||||
from torch import nn
|
||||
from vllm.model_executor.models.utils import WeightsMapper
|
||||
|
||||
from vllm_omni.model_executor.models.weight_loader import AutoWeightsLoader
|
||||
from vllm.model_executor.models.utils import AutoWeightsLoader, WeightsMapper
|
||||
|
||||
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
||||
|
||||
@@ -20,21 +18,18 @@ class _TinyModel(nn.Module):
|
||||
self.rotary_embed.register_parameter("inv_freq", nn.Parameter(torch.zeros(1)))
|
||||
|
||||
|
||||
def test_auto_weights_loader_preserves_removed_skip_arguments() -> None:
|
||||
def test_upstream_mapper_preserves_stage_exclusions_after_renaming() -> None:
|
||||
model = _TinyModel()
|
||||
model.skipped.weight.data.zero_()
|
||||
|
||||
loaded = AutoWeightsLoader(
|
||||
model,
|
||||
skip_prefixes=["skipped."],
|
||||
skip_substrs=["rotary_embed.inv_freq"],
|
||||
).load_weights(
|
||||
loaded = AutoWeightsLoader(model).load_weights(
|
||||
[
|
||||
("checkpoint.kept.weight", torch.ones(1, 1)),
|
||||
("skipped.weight", torch.full((1, 1), 2.0)),
|
||||
("checkpoint.skipped.weight", torch.full((1, 1), 2.0)),
|
||||
("rotary_embed.inv_freq", torch.ones(1)),
|
||||
],
|
||||
mapper=WeightsMapper(orig_to_new_prefix={"checkpoint.": ""}),
|
||||
mapper=WeightsMapper(orig_to_new_prefix={"checkpoint.": ""})
|
||||
| WeightsMapper(orig_to_new_prefix={"skipped.": None}, orig_to_new_substr={"rotary_embed.inv_freq": None}),
|
||||
)
|
||||
|
||||
assert loaded == {"kept.weight"}
|
||||
|
||||
@@ -17,7 +17,7 @@ from transformers.generation.configuration_utils import GenerationConfig
|
||||
from transformers.generation.utils import ALL_CACHE_NAMES, GenerationMixin
|
||||
from transformers.utils.generic import ModelOutput
|
||||
from vllm.config.vllm import get_current_vllm_config
|
||||
from vllm.model_executor.models.utils import WeightsMapper
|
||||
from vllm.model_executor.models.utils import AutoWeightsLoader, WeightsMapper
|
||||
from vllm.transformers_utils.config import get_config
|
||||
|
||||
from vllm_omni.diffusion.data import DiffusionOutput, OmniDiffusionConfig
|
||||
@@ -33,7 +33,6 @@ from vllm_omni.diffusion.request import OmniDiffusionRequest
|
||||
from vllm_omni.diffusion.worker.request_batch import DiffusionRequestBatch
|
||||
from vllm_omni.inputs.data import OmniTextPrompt
|
||||
from vllm_omni.model_executor.models.hunyuan_image3.siglip2 import Siglip2VisionTransformer
|
||||
from vllm_omni.model_executor.models.weight_loader import AutoWeightsLoader
|
||||
|
||||
from . import request_layout as request_layout_utils
|
||||
from .hunyuan_image3_tokenizer import TokenizerEncodeOutput, TokenizerWrapper
|
||||
@@ -455,11 +454,10 @@ class HunyuanImage3Pipeline(
|
||||
|
||||
# Note: guidance_emb and timestep_r_emb are no longer skipped
|
||||
# to support HunyuanImage-3.0-Distil and MeanFlow distilled models
|
||||
loader = AutoWeightsLoader(
|
||||
self,
|
||||
skip_prefixes=skip_prefixes,
|
||||
loader = AutoWeightsLoader(self)
|
||||
return loader.load_weights(
|
||||
weights, mapper=WeightsMapper(orig_to_new_prefix={name: None for name in (skip_prefixes or ())})
|
||||
)
|
||||
return loader.load_weights(weights)
|
||||
|
||||
def prepare_seed(self, seed=None, batch_size=1):
|
||||
# random seed
|
||||
|
||||
@@ -54,7 +54,7 @@ from vllm_omni.engine.arg_utils import OmniEngineArgs
|
||||
from vllm_omni.entrypoints.stage_utils import _to_dict, set_stage_devices
|
||||
from vllm_omni.entrypoints.utils import filter_dataclass_kwargs
|
||||
from vllm_omni.inputs.data import OmniDiffusionSamplingParams, OmniSamplingParams
|
||||
from vllm_omni.inputs.preprocess import OmniInputPreprocessor
|
||||
from vllm_omni.inputs.preprocess import OmniRenderer
|
||||
from vllm_omni.outputs.output_processor import MultimodalOutputProcessor
|
||||
from vllm_omni.platforms import current_omni_platform
|
||||
from vllm_omni.quantization.inc_config import OmniINCConfig
|
||||
@@ -1563,10 +1563,7 @@ def build_stage0_input_processor(stage_vllm_config: Any) -> InputProcessor:
|
||||
)
|
||||
else:
|
||||
input_processor = InputProcessor(vllm_config=stage_vllm_config)
|
||||
input_processor.input_preprocessor = OmniInputPreprocessor(
|
||||
vllm_config=stage_vllm_config,
|
||||
renderer=input_processor.renderer,
|
||||
)
|
||||
input_processor.renderer = OmniRenderer(input_processor.renderer)
|
||||
return input_processor
|
||||
|
||||
|
||||
|
||||
@@ -702,7 +702,7 @@ class OmniOpenAIServingChat(OpenAIServingChat, AudioMixin):
|
||||
if negative_prompt is not None:
|
||||
tprompt["negative_prompt"] = negative_prompt
|
||||
# Always attach mm_processor_kwargs (possibly empty) so
|
||||
# OmniInputPreprocessor._process_text routes through the
|
||||
# OmniRenderer routes through the
|
||||
# multimodal processor path. Without it, the preprocessor
|
||||
# falls back to plain _tokenize_prompt and AR-based image-gen
|
||||
# models like GLM-Image never see their image-generation
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||
|
||||
"""Multimodal data plumbing for Omni's custom, non-HF processors."""
|
||||
|
||||
from collections.abc import Mapping
|
||||
from typing import TypeVar
|
||||
|
||||
from transformers import BatchFeature
|
||||
from vllm.multimodal.parse import MultiModalDataItems
|
||||
from vllm.multimodal.processing import BaseMultiModalProcessor, BaseProcessingInfo
|
||||
|
||||
_I = TypeVar("_I", bound=BaseProcessingInfo)
|
||||
|
||||
|
||||
class OmniMultiModalProcessor(BaseMultiModalProcessor[_I]):
|
||||
"""Share data/passthrough handling, leaving model processing in its owner.
|
||||
|
||||
These processors implement ``_call_hf_processor`` locally, sometimes
|
||||
without any HF processor object. Prompt tokens are prepared separately
|
||||
in ``apply``; the upstream base still owns caching and prompt updates.
|
||||
"""
|
||||
|
||||
_OMNI_PROMPT_TEXT_KEY = "_vllm_omni_original_prompt_text"
|
||||
|
||||
def _apply_hf_processor_main(
|
||||
self,
|
||||
mm_items: MultiModalDataItems,
|
||||
hf_processor_mm_kwargs: Mapping[str, object],
|
||||
) -> BatchFeature:
|
||||
kwargs = dict(hf_processor_mm_kwargs)
|
||||
prompt = kwargs.pop(self._OMNI_PROMPT_TEXT_KEY, None)
|
||||
if prompt is None:
|
||||
prompt = self.dummy_inputs.get_dummy_text(mm_items.get_all_counts())
|
||||
valid_items = mm_items.select({key for key, count in mm_items.get_all_counts().items() if count > 0})
|
||||
mm_data, passthrough = self._get_hf_mm_data(valid_items)
|
||||
result = self._call_hf_processor(str(prompt), dict(mm_data), kwargs, {})
|
||||
result.update(passthrough)
|
||||
return result
|
||||
+43
-361
@@ -3,379 +3,61 @@
|
||||
|
||||
from typing import Any
|
||||
|
||||
from typing_extensions import assert_never
|
||||
from vllm.inputs import (
|
||||
EmbedsInput,
|
||||
MultiModalDataDict,
|
||||
MultiModalInput,
|
||||
MultiModalUUIDDict,
|
||||
SingletonInput,
|
||||
TextPrompt,
|
||||
TokensInput,
|
||||
TokensPrompt,
|
||||
tokens_input,
|
||||
)
|
||||
from vllm.logger import init_logger
|
||||
from vllm.renderers import BaseRenderer, renderer_from_config
|
||||
from vllm.renderers.inputs import SingletonDictPrompt
|
||||
|
||||
from vllm_omni.inputs.data import (
|
||||
OmniEmbedsPrompt,
|
||||
OmniTextPrompt,
|
||||
OmniTokenInputs,
|
||||
OmniTokensPrompt,
|
||||
token_inputs_omni,
|
||||
)
|
||||
|
||||
logger = init_logger(__name__)
|
||||
from vllm.renderers import BaseRenderer
|
||||
|
||||
|
||||
class _InputPreprocessor:
|
||||
"""Compatibility shim for the upstream-removed ``InputPreprocessor``.
|
||||
class OmniRenderer:
|
||||
"""Delegate tokenization/chat rendering while extending engine-input routing.
|
||||
|
||||
Upstream vLLM moved raw-prompt preprocessing into
|
||||
:class:`vllm.renderers.BaseRenderer` and removed
|
||||
``vllm.inputs.preprocess.InputPreprocessor``. vLLM-Omni still exposes an
|
||||
omni-specific preprocessor object for stage-0 input processing, so keep the
|
||||
same thin renderer-delegating surface locally and adapt the few calls whose
|
||||
renderer signature changed upstream.
|
||||
Keep the original renderer's tokenizer, executors, and multimodal caches.
|
||||
The upstream pipeline methods run on this adapter so both sync and async
|
||||
entrypoints reach the Omni singleton processing below.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vllm_config: Any,
|
||||
renderer: BaseRenderer | None = None,
|
||||
) -> None:
|
||||
self.model_config = vllm_config.model_config
|
||||
self.renderer = renderer or renderer_from_config(vllm_config)
|
||||
render_cmpl = BaseRenderer.render_cmpl
|
||||
render_cmpl_async = BaseRenderer.render_cmpl_async
|
||||
render_chat = BaseRenderer.render_chat
|
||||
render_chat_async = BaseRenderer.render_chat_async
|
||||
process_for_engine = BaseRenderer.process_for_engine
|
||||
process_for_engine_async = BaseRenderer.process_for_engine_async
|
||||
_process_enc_dec = BaseRenderer._process_enc_dec
|
||||
_process_enc_dec_async = BaseRenderer._process_enc_dec_async
|
||||
|
||||
@property
|
||||
def tokenizer(self) -> Any | None:
|
||||
return self.renderer.tokenizer
|
||||
def __init__(self, renderer: BaseRenderer) -> None:
|
||||
self._renderer = renderer
|
||||
|
||||
def _tokenize_prompt(
|
||||
self,
|
||||
prompt: str,
|
||||
tokenization_kwargs: dict[str, Any] | None = None,
|
||||
) -> list[int]:
|
||||
"""Apply the model's tokenizer to a text prompt."""
|
||||
tok_params = self.renderer.default_cmpl_tok_params.with_kwargs(**(tokenization_kwargs or {}))
|
||||
|
||||
tok_prompt = self.renderer._tokenize_singleton_prompt(
|
||||
TextPrompt(prompt=prompt),
|
||||
tok_params,
|
||||
)
|
||||
|
||||
return tok_prompt["prompt_token_ids"]
|
||||
|
||||
def _process_multimodal(
|
||||
self,
|
||||
prompt: str | list[int],
|
||||
mm_data: MultiModalDataDict,
|
||||
mm_processor_kwargs: dict[str, Any] | None = None,
|
||||
tokenization_kwargs: dict[str, Any] | None = None,
|
||||
*,
|
||||
mm_uuids: MultiModalUUIDDict | None = None,
|
||||
) -> MultiModalInput:
|
||||
"""Apply the model's multi-modal processor to a multi-modal prompt.
|
||||
|
||||
The upstream renderer no longer accepts raw ``str`` prompts or
|
||||
``tokenization_kwargs`` in ``_process_multimodal``; tokenize text
|
||||
prompts here first to preserve the legacy ``InputPreprocessor`` API.
|
||||
"""
|
||||
if isinstance(prompt, str):
|
||||
prompt = self._tokenize_prompt(prompt, tokenization_kwargs)
|
||||
|
||||
return self.renderer._process_multimodal(
|
||||
prompt,
|
||||
mm_data,
|
||||
mm_uuids=mm_uuids,
|
||||
mm_processor_kwargs=mm_processor_kwargs,
|
||||
)
|
||||
|
||||
def _process_embeds(
|
||||
self,
|
||||
parsed_content: OmniEmbedsPrompt,
|
||||
) -> EmbedsInput:
|
||||
return self.renderer._process_embeds(parsed_content)
|
||||
|
||||
def _truncate_inputs(
|
||||
self,
|
||||
inputs: list[int],
|
||||
tokenization_kwargs: dict[str, Any] | None = None,
|
||||
) -> list[int]:
|
||||
tok_params = self.renderer.default_cmpl_tok_params.with_kwargs(**(tokenization_kwargs or {}))
|
||||
|
||||
tok_prompt = self.renderer._tokenize_singleton_prompt(
|
||||
TokensPrompt(prompt_token_ids=inputs),
|
||||
tok_params,
|
||||
)
|
||||
|
||||
return tok_prompt["prompt_token_ids"]
|
||||
|
||||
def _process_tokens(
|
||||
self,
|
||||
parsed_content: OmniTokensPrompt,
|
||||
tokenization_kwargs: dict[str, Any] | None = None,
|
||||
) -> TokensInput | MultiModalInput:
|
||||
prompt_token_ids = self._truncate_inputs(parsed_content["prompt_token_ids"], tokenization_kwargs)
|
||||
|
||||
inputs: TokensInput | MultiModalInput
|
||||
if multi_modal_data := parsed_content.get("multi_modal_data"):
|
||||
inputs = self._process_multimodal(
|
||||
prompt_token_ids,
|
||||
multi_modal_data,
|
||||
parsed_content.get("mm_processor_kwargs"),
|
||||
tokenization_kwargs=tokenization_kwargs,
|
||||
mm_uuids=parsed_content.get("multi_modal_uuids"),
|
||||
)
|
||||
else:
|
||||
inputs = tokens_input(prompt_token_ids)
|
||||
|
||||
if prompt_text := parsed_content.get("prompt"):
|
||||
inputs["prompt"] = prompt_text
|
||||
if cache_salt := parsed_content.get("cache_salt"):
|
||||
inputs["cache_salt"] = cache_salt
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
return getattr(self._renderer, name)
|
||||
|
||||
@staticmethod
|
||||
def _with_omni_extras(inputs, prompt):
|
||||
for key in ("prompt", "cache_salt", "additional_information", "model_intermediate_buffer"):
|
||||
if key in prompt:
|
||||
inputs[key] = prompt[key]
|
||||
return inputs
|
||||
|
||||
def _process_text(
|
||||
self,
|
||||
parsed_content: OmniTextPrompt,
|
||||
tokenization_kwargs: dict[str, Any] | None = None,
|
||||
*,
|
||||
mm_uuids: MultiModalUUIDDict | None = None,
|
||||
) -> TokensInput | MultiModalInput:
|
||||
prompt_text = parsed_content["prompt"]
|
||||
|
||||
inputs: TokensInput | MultiModalInput
|
||||
if multi_modal_data := parsed_content.get("multi_modal_data"):
|
||||
inputs = self._process_multimodal(
|
||||
prompt_text,
|
||||
multi_modal_data,
|
||||
parsed_content.get("mm_processor_kwargs") or {},
|
||||
tokenization_kwargs=tokenization_kwargs,
|
||||
mm_uuids=mm_uuids,
|
||||
)
|
||||
else:
|
||||
prompt_token_ids = self._tokenize_prompt(
|
||||
prompt_text,
|
||||
tokenization_kwargs=tokenization_kwargs,
|
||||
)
|
||||
inputs = tokens_input(prompt_token_ids)
|
||||
|
||||
inputs["prompt"] = prompt_text
|
||||
|
||||
if cache_salt := parsed_content.get("cache_salt"):
|
||||
inputs["cache_salt"] = cache_salt
|
||||
|
||||
return inputs
|
||||
|
||||
def _prompt_to_llm_inputs(
|
||||
self,
|
||||
prompt: SingletonDictPrompt,
|
||||
tokenization_kwargs: dict[str, Any] | None = None,
|
||||
*,
|
||||
mm_uuids: MultiModalUUIDDict | None = None,
|
||||
) -> SingletonInput:
|
||||
"""Extract the singleton inputs from a prompt."""
|
||||
if "prompt_embeds" in prompt:
|
||||
return self._process_embeds(prompt) # type: ignore[arg-type]
|
||||
|
||||
if "prompt_token_ids" in prompt:
|
||||
return self._process_tokens(
|
||||
prompt, # type: ignore[arg-type]
|
||||
tokenization_kwargs=tokenization_kwargs,
|
||||
)
|
||||
|
||||
if "prompt" in prompt:
|
||||
return self._process_text(
|
||||
prompt, # type: ignore[arg-type]
|
||||
tokenization_kwargs=tokenization_kwargs,
|
||||
mm_uuids=mm_uuids,
|
||||
)
|
||||
|
||||
assert_never(prompt) # type: ignore[arg-type]
|
||||
|
||||
|
||||
class OmniInputPreprocessor(_InputPreprocessor):
|
||||
"""Input preprocessor for omni models.
|
||||
|
||||
Extends the base InputPreprocessor to handle omni-specific input
|
||||
types including prompt embeddings and additional information payloads.
|
||||
Supports processing tokens, embeddings, text, and multimodal inputs.
|
||||
"""
|
||||
|
||||
def _process_text(
|
||||
self,
|
||||
parsed_content: OmniTextPrompt,
|
||||
tokenization_kwargs: dict[str, Any] | None = None,
|
||||
*,
|
||||
mm_uuids: Any | None = None,
|
||||
) -> OmniTokenInputs | MultiModalInput:
|
||||
"""Process text prompts with support for mm_processor_kwargs.
|
||||
|
||||
Extends base class to support mm_processor_kwargs without multi_modal_data.
|
||||
This is needed for models like GLM-Image where text-to-image generation
|
||||
requires processor kwargs (target_h, target_w) to format the prompt.
|
||||
"""
|
||||
prompt_text = parsed_content["prompt"]
|
||||
mm_processor_kwargs = parsed_content.get("mm_processor_kwargs") or {}
|
||||
# When the deprecated raw-prompt path is used, process_inputs does
|
||||
# not pass mm_uuids to preprocess(). Fall back to reading it from
|
||||
# the prompt dict so the Renderer's _validate_mm_uuids can see it.
|
||||
effective_mm_uuids = mm_uuids or parsed_content.get("multi_modal_uuids")
|
||||
|
||||
inputs: OmniTokenInputs | MultiModalInput
|
||||
if multi_modal_data := parsed_content.get("multi_modal_data"):
|
||||
inputs = self._process_multimodal(
|
||||
prompt_text,
|
||||
multi_modal_data,
|
||||
mm_processor_kwargs,
|
||||
tokenization_kwargs=tokenization_kwargs,
|
||||
mm_uuids=effective_mm_uuids,
|
||||
)
|
||||
prompt_embeds = parsed_content.get("prompt_embeds")
|
||||
if prompt_embeds is not None:
|
||||
inputs["prompt_embeds"] = prompt_embeds
|
||||
additional_information = parsed_content.get("additional_information")
|
||||
if additional_information is not None:
|
||||
inputs["additional_information"] = additional_information
|
||||
model_intermediate_buffer = parsed_content.get("model_intermediate_buffer")
|
||||
if model_intermediate_buffer is not None:
|
||||
inputs["model_intermediate_buffer"] = model_intermediate_buffer
|
||||
elif "mm_processor_kwargs" in parsed_content:
|
||||
# Presence — not truthiness. An explicitly-set empty dict still
|
||||
# signals "route through the multimodal processor" (needed for
|
||||
# AR-based image-gen where the HF processor supplies its own
|
||||
# defaults and scaffold).
|
||||
inputs = self._process_multimodal(
|
||||
prompt_text,
|
||||
def _process_singleton(self, prompt, *, skip_mm_cache: bool = False):
|
||||
if "prompt_embeds" not in prompt and "mm_processor_kwargs" in prompt and not prompt.get("multi_modal_data"):
|
||||
inputs = self._renderer._process_multimodal(
|
||||
prompt["prompt_token_ids"],
|
||||
{},
|
||||
mm_processor_kwargs,
|
||||
tokenization_kwargs=tokenization_kwargs,
|
||||
mm_uuids=effective_mm_uuids,
|
||||
mm_processor_kwargs=prompt["mm_processor_kwargs"],
|
||||
mm_uuids=prompt.get("multi_modal_uuids"),
|
||||
skip_mm_cache=skip_mm_cache,
|
||||
)
|
||||
else:
|
||||
prompt_token_ids = self._tokenize_prompt(
|
||||
prompt_text,
|
||||
tokenization_kwargs=tokenization_kwargs,
|
||||
inputs = self._renderer._process_singleton(prompt, skip_mm_cache=skip_mm_cache)
|
||||
return self._with_omni_extras(inputs, prompt)
|
||||
|
||||
async def _process_singleton_async(self, prompt, *, skip_mm_cache: bool = False):
|
||||
if "prompt_embeds" not in prompt and "mm_processor_kwargs" in prompt and not prompt.get("multi_modal_data"):
|
||||
inputs = await self._renderer._process_multimodal_async(
|
||||
prompt["prompt_token_ids"],
|
||||
{},
|
||||
mm_processor_kwargs=prompt["mm_processor_kwargs"],
|
||||
mm_uuids=prompt.get("multi_modal_uuids"),
|
||||
skip_mm_cache=skip_mm_cache,
|
||||
)
|
||||
inputs = token_inputs_omni(
|
||||
prompt_token_ids,
|
||||
prompt_embeds=parsed_content.get("prompt_embeds"),
|
||||
additional_information=parsed_content.get("additional_information"),
|
||||
model_intermediate_buffer=parsed_content.get("model_intermediate_buffer"),
|
||||
)
|
||||
prompt_embeds = parsed_content.get("prompt_embeds")
|
||||
if prompt_embeds is not None:
|
||||
inputs["prompt_embeds"] = prompt_embeds
|
||||
additional_information = parsed_content.get("additional_information")
|
||||
if additional_information is not None:
|
||||
inputs["additional_information"] = additional_information
|
||||
model_intermediate_buffer = parsed_content.get("model_intermediate_buffer")
|
||||
if model_intermediate_buffer is not None:
|
||||
inputs["model_intermediate_buffer"] = model_intermediate_buffer
|
||||
if cache_salt := parsed_content.get("cache_salt"):
|
||||
inputs["cache_salt"] = cache_salt
|
||||
|
||||
return inputs
|
||||
|
||||
def _process_tokens(
|
||||
self,
|
||||
parsed_content: OmniTokensPrompt,
|
||||
tokenization_kwargs: dict[str, Any] | None = None,
|
||||
) -> OmniTokenInputs | MultiModalInput:
|
||||
prompt_token_ids = self._truncate_inputs(parsed_content["prompt_token_ids"], tokenization_kwargs)
|
||||
prompt_embeds = parsed_content.get("prompt_embeds")
|
||||
additional_information = parsed_content.get("additional_information")
|
||||
model_intermediate_buffer = parsed_content.get("model_intermediate_buffer")
|
||||
|
||||
multi_modal_data = parsed_content.get("multi_modal_data")
|
||||
|
||||
inputs: OmniTokenInputs | MultiModalInput
|
||||
if multi_modal_data:
|
||||
inputs = self._process_multimodal(
|
||||
prompt_token_ids,
|
||||
multi_modal_data,
|
||||
parsed_content.get("mm_processor_kwargs"),
|
||||
tokenization_kwargs=tokenization_kwargs,
|
||||
mm_uuids=parsed_content.get("multi_modal_uuids"),
|
||||
)
|
||||
|
||||
else:
|
||||
inputs = token_inputs_omni(
|
||||
prompt_token_ids=prompt_token_ids,
|
||||
prompt_embeds=prompt_embeds,
|
||||
additional_information=additional_information,
|
||||
model_intermediate_buffer=model_intermediate_buffer,
|
||||
)
|
||||
if prompt_embeds is not None:
|
||||
inputs["prompt_embeds"] = prompt_embeds
|
||||
if additional_information is not None:
|
||||
inputs["additional_information"] = additional_information
|
||||
if model_intermediate_buffer is not None:
|
||||
inputs["model_intermediate_buffer"] = model_intermediate_buffer
|
||||
if prompt_text := parsed_content.get("prompt"):
|
||||
inputs["prompt"] = prompt_text
|
||||
if cache_salt := parsed_content.get("cache_salt"):
|
||||
inputs["cache_salt"] = cache_salt
|
||||
|
||||
return inputs
|
||||
|
||||
def _process_embeds(
|
||||
self,
|
||||
parsed_content: OmniEmbedsPrompt,
|
||||
) -> EmbedsInput:
|
||||
"""Process embeddings prompt with omni-specific extensions.
|
||||
|
||||
Extends base _process_embeds to handle additional_information payload
|
||||
for direct transfer between pipeline stages.
|
||||
"""
|
||||
# Call parent implementation for base embeds processing
|
||||
inputs = super()._process_embeds(parsed_content)
|
||||
|
||||
# Add omni-specific additional_information if present
|
||||
additional_information = parsed_content.get("additional_information")
|
||||
if additional_information is not None:
|
||||
inputs["additional_information"] = additional_information # type: ignore[typeddict-unknown-key]
|
||||
model_intermediate_buffer = parsed_content.get("model_intermediate_buffer")
|
||||
if model_intermediate_buffer is not None:
|
||||
inputs["model_intermediate_buffer"] = model_intermediate_buffer # type: ignore[typeddict-unknown-key]
|
||||
|
||||
return inputs
|
||||
|
||||
def _prompt_to_llm_inputs(
|
||||
self,
|
||||
prompt: SingletonDictPrompt,
|
||||
tokenization_kwargs: dict[str, Any] | None = None,
|
||||
*,
|
||||
mm_uuids: Any | None = None,
|
||||
) -> SingletonInput:
|
||||
"""
|
||||
Extract the singleton inputs from a prompt.
|
||||
|
||||
Arguments:
|
||||
|
||||
* prompt: single encoder or decoder input prompt
|
||||
|
||||
Returns:
|
||||
|
||||
* [`SingletonInput`][vllm.inputs.engine.SingletonInput] instance
|
||||
"""
|
||||
if "prompt_embeds" in prompt:
|
||||
return self._process_embeds(prompt) # type: ignore[arg-type]
|
||||
|
||||
if "prompt_token_ids" in prompt:
|
||||
return self._process_tokens(
|
||||
prompt, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
if "prompt" in prompt:
|
||||
return self._process_text(
|
||||
prompt, # type: ignore[arg-type]
|
||||
tokenization_kwargs=tokenization_kwargs,
|
||||
mm_uuids=mm_uuids,
|
||||
)
|
||||
|
||||
assert_never(prompt) # type: ignore[arg-type]
|
||||
inputs = await self._renderer._process_singleton_async(prompt, skip_mm_cache=skip_mm_cache)
|
||||
return self._with_omni_extras(inputs, prompt)
|
||||
@@ -33,13 +33,14 @@ from vllm.multimodal.inputs import MultiModalFieldConfig, MultiModalKwargsItems
|
||||
from vllm.multimodal.parse import MultiModalDataItems, MultiModalDataParser
|
||||
from vllm.multimodal.processing import (
|
||||
BaseDummyInputsBuilder,
|
||||
BaseMultiModalProcessor,
|
||||
BaseProcessingInfo,
|
||||
PromptReplacement,
|
||||
PromptUpdate,
|
||||
PromptUpdateDetails,
|
||||
)
|
||||
|
||||
from vllm_omni.inputs.mm_processor import OmniMultiModalProcessor
|
||||
|
||||
SOUND_TOKEN = "<so_embedding>"
|
||||
SOUND_START_TOKEN = "<so_start>"
|
||||
SOUND_END_TOKEN = "<so_end>"
|
||||
@@ -176,7 +177,7 @@ class AudexDummyInputsBuilder(BaseDummyInputsBuilder[AudexProcessingInfo]):
|
||||
return {"audio": self._get_dummy_audios(length=audio_len, num_audios=num_audios, overrides=overrides)}
|
||||
|
||||
|
||||
class AudexMultiModalProcessor(BaseMultiModalProcessor[AudexProcessingInfo]):
|
||||
class AudexMultiModalProcessor(OmniMultiModalProcessor[AudexProcessingInfo]):
|
||||
def _call_hf_processor(
|
||||
self,
|
||||
prompt: str,
|
||||
@@ -222,22 +223,6 @@ class AudexMultiModalProcessor(BaseMultiModalProcessor[AudexProcessingInfo]):
|
||||
tensor_type="pt",
|
||||
)
|
||||
|
||||
def _apply_hf_processor_main(
|
||||
self,
|
||||
mm_items: MultiModalDataItems,
|
||||
hf_processor_mm_kwargs: Mapping[str, object],
|
||||
) -> BatchFeature:
|
||||
valid_mm_items = mm_items.select({key for key, count in mm_items.get_all_counts().items() if count > 0})
|
||||
mm_data, passthrough_data = self._get_hf_mm_data(valid_mm_items)
|
||||
processed_data = self._call_hf_processor(
|
||||
self.dummy_inputs.get_dummy_text(mm_items.get_all_counts()),
|
||||
mm_data,
|
||||
hf_processor_mm_kwargs,
|
||||
{},
|
||||
)
|
||||
processed_data.update(passthrough_data)
|
||||
return processed_data
|
||||
|
||||
def _get_mm_fields_config(
|
||||
self,
|
||||
hf_inputs: BatchFeature,
|
||||
|
||||
@@ -21,6 +21,7 @@ from vllm.model_executor.model_loader.weight_utils import default_weight_loader
|
||||
from vllm.model_executor.models.bagel import BagelForConditionalGeneration
|
||||
from vllm.model_executor.models.interfaces import MultiModalEmbeddings
|
||||
from vllm.model_executor.models.qwen2 import Qwen2DecoderLayer, Qwen2MLP
|
||||
from vllm.model_executor.models.utils import AutoWeightsLoader, WeightsMapper
|
||||
from vllm.multimodal import MULTIMODAL_REGISTRY
|
||||
from vllm.multimodal.inputs import (
|
||||
MultiModalFieldConfig,
|
||||
@@ -53,7 +54,6 @@ from vllm_omni.diffusion.models.bagel.bagel_transformer import (
|
||||
TimestepEmbedder,
|
||||
)
|
||||
from vllm_omni.diffusion.models.bagel.pipeline_bagel import default_ae_params
|
||||
from vllm_omni.model_executor.models.weight_loader import AutoWeightsLoader
|
||||
|
||||
|
||||
class OmniBagelProcessor(BagelProcessor):
|
||||
@@ -1164,11 +1164,12 @@ class OmniBagelForConditionalGeneration(BagelForConditionalGeneration):
|
||||
filtered_weights.append((mapped_name, tensor))
|
||||
|
||||
loader = AutoWeightsLoader(
|
||||
self,
|
||||
skip_prefixes=["vit_pos_embed.pos_embed"],
|
||||
ignore_unexpected_prefixes=["vae.", "latent_pos_embed.", "time_embedder.", "vae2llm."],
|
||||
self, ignore_unexpected_prefixes=["vae.", "latent_pos_embed.", "time_embedder.", "vae2llm."]
|
||||
)
|
||||
loaded = loader.load_weights(
|
||||
filtered_weights,
|
||||
mapper=(self.hf_to_vllm_mapper) | WeightsMapper(orig_to_new_prefix={"vit_pos_embed.pos_embed": None}),
|
||||
)
|
||||
loaded = loader.load_weights(filtered_weights, mapper=self.hf_to_vllm_mapper)
|
||||
|
||||
loaded |= self._load_moe_gen_weights(moe_gen_weights)
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ from vllm.multimodal.inputs import MultiModalFieldConfig, MultiModalKwargsItems
|
||||
from vllm.multimodal.parse import MultiModalDataItems, MultiModalDataParser
|
||||
from vllm.multimodal.processing import (
|
||||
BaseDummyInputsBuilder,
|
||||
BaseMultiModalProcessor,
|
||||
BaseProcessingInfo,
|
||||
ProcessorInputs,
|
||||
PromptIndexTargets,
|
||||
@@ -39,6 +38,7 @@ from vllm.v1.sample.ops.topk_topp_sampler import random_sample
|
||||
from vllm.v1.sample.sampler import Sampler
|
||||
|
||||
from vllm_omni.data_entry_keys import EmbeddingsStruct, OmniPayloadStruct, to_dict, to_struct
|
||||
from vllm_omni.inputs.mm_processor import OmniMultiModalProcessor
|
||||
from vllm_omni.model_executor.models.cosyvoice3.tokenizer import get_qwen_tokenizer
|
||||
from vllm_omni.model_executor.models.cosyvoice3.utils import (
|
||||
concat_text_with_prompt_ids,
|
||||
@@ -109,9 +109,7 @@ class CosyVoice3MultiModalProcessingInfo(BaseProcessingInfo):
|
||||
)
|
||||
|
||||
|
||||
class CosyVoice3MultiModalProcessor(BaseMultiModalProcessor[CosyVoice3MultiModalProcessingInfo]):
|
||||
_OMNI_PROMPT_TEXT_KEY = "_vllm_omni_original_prompt_text"
|
||||
|
||||
class CosyVoice3MultiModalProcessor(OmniMultiModalProcessor[CosyVoice3MultiModalProcessingInfo]):
|
||||
def apply(self, inputs: ProcessorInputs, timing_ctx):
|
||||
tokenizer = self.info.get_tokenizer()
|
||||
prompt_text = tokenizer.decode(inputs.prompt, skip_special_tokens=False)
|
||||
@@ -405,24 +403,6 @@ class CosyVoice3MultiModalProcessor(BaseMultiModalProcessor[CosyVoice3MultiModal
|
||||
|
||||
return ft
|
||||
|
||||
def _apply_hf_processor_main(
|
||||
self,
|
||||
mm_items: MultiModalDataItems,
|
||||
hf_processor_mm_kwargs: Mapping[str, object],
|
||||
) -> BatchFeature:
|
||||
mm_kwargs = dict(hf_processor_mm_kwargs)
|
||||
prompt_text = str(mm_kwargs.pop(self._OMNI_PROMPT_TEXT_KEY, ""))
|
||||
valid_mm_items = mm_items.select({key for key, count in mm_items.get_all_counts().items() if count > 0})
|
||||
mm_data, passthrough_data = self._get_hf_mm_data(valid_mm_items)
|
||||
processed_data = self._call_hf_processor(
|
||||
prompt_text,
|
||||
mm_data,
|
||||
mm_kwargs,
|
||||
{},
|
||||
)
|
||||
processed_data.update(passthrough_data)
|
||||
return processed_data
|
||||
|
||||
def _get_mm_fields_config(
|
||||
self,
|
||||
hf_inputs: "BatchFeature",
|
||||
|
||||
@@ -20,7 +20,6 @@ from vllm.multimodal.inputs import MultiModalFieldConfig
|
||||
from vllm.multimodal.parse import MultiModalDataItems, MultiModalDataParser
|
||||
from vllm.multimodal.processing import (
|
||||
BaseDummyInputsBuilder,
|
||||
BaseMultiModalProcessor,
|
||||
BaseProcessingInfo,
|
||||
ProcessorInputs,
|
||||
PromptReplacement,
|
||||
@@ -29,6 +28,7 @@ from vllm.multimodal.processing import (
|
||||
from vllm.sequence import IntermediateTensors
|
||||
from vllm.v1.sample.metadata import SamplingMetadata
|
||||
|
||||
from vllm_omni.inputs.mm_processor import OmniMultiModalProcessor
|
||||
from vllm_omni.model_executor.custom_process_mixin import CustomProcessMixin
|
||||
from vllm_omni.model_executor.models.output_templates import OmniOutput
|
||||
from vllm_omni.model_executor.models.utils import add_prefix_to_loaded_weights
|
||||
@@ -109,7 +109,7 @@ class CovoAudioDummyInputsBuilder(BaseDummyInputsBuilder[CovoAudioProcessingInfo
|
||||
)
|
||||
|
||||
|
||||
class CovoAudioMultiModalProcessor(BaseMultiModalProcessor[CovoAudioProcessingInfo]):
|
||||
class CovoAudioMultiModalProcessor(OmniMultiModalProcessor[CovoAudioProcessingInfo]):
|
||||
def _hf_processor_applies_updates(self, prompt_text, mm_items, hf_processor_mm_kwargs, tokenization_kwargs) -> bool:
|
||||
return False
|
||||
|
||||
@@ -155,22 +155,6 @@ class CovoAudioMultiModalProcessor(BaseMultiModalProcessor[CovoAudioProcessingIn
|
||||
tensor_type=None,
|
||||
)
|
||||
|
||||
def _apply_hf_processor_main(
|
||||
self,
|
||||
mm_items: MultiModalDataItems,
|
||||
hf_processor_mm_kwargs: Mapping[str, object],
|
||||
) -> BatchFeature:
|
||||
valid_mm_items = mm_items.select({key for key, count in mm_items.get_all_counts().items() if count > 0})
|
||||
mm_data, passthrough_data = self._get_hf_mm_data(valid_mm_items)
|
||||
processed_data = self._call_hf_processor(
|
||||
self.dummy_inputs.get_dummy_text(mm_items.get_all_counts()),
|
||||
mm_data,
|
||||
hf_processor_mm_kwargs,
|
||||
{},
|
||||
)
|
||||
processed_data.update(passthrough_data)
|
||||
return processed_data
|
||||
|
||||
def _get_mm_fields_config(
|
||||
self,
|
||||
hf_inputs: BatchFeature,
|
||||
|
||||
@@ -22,13 +22,12 @@ import torch.nn as nn
|
||||
from vllm.config import VllmConfig
|
||||
from vllm.logger import init_logger
|
||||
from vllm.model_executor.models.qwen3_5 import Qwen3_5ForCausalLM
|
||||
from vllm.model_executor.models.utils import maybe_prefix
|
||||
from vllm.model_executor.models.utils import AutoWeightsLoader, WeightsMapper, maybe_prefix
|
||||
from vllm.sequence import IntermediateTensors
|
||||
|
||||
from vllm_omni.model_executor.models.gepard.configuration_gepard import GepardConfig
|
||||
from vllm_omni.model_executor.models.gepard.nanocodec import NanoCodec
|
||||
from vllm_omni.model_executor.models.output_templates import OmniOutput
|
||||
from vllm_omni.model_executor.models.weight_loader import AutoWeightsLoader
|
||||
from vllm_omni.platforms import current_omni_platform
|
||||
|
||||
logger = init_logger(__name__)
|
||||
@@ -200,8 +199,10 @@ class GepardTalkerForConditionalGeneration(nn.Module):
|
||||
|
||||
# NanoCodec loads lazily on first decode, so load_format=dummy and
|
||||
# NeMo-less environments never pay for it.
|
||||
loader = AutoWeightsLoader(self, skip_prefixes=["mtp.", "ref_compressor."])
|
||||
return loader.load_weights(iter(rest))
|
||||
loader = AutoWeightsLoader(self)
|
||||
return loader.load_weights(
|
||||
iter(rest), mapper=WeightsMapper(orig_to_new_prefix={"mtp.": None, "ref_compressor.": None})
|
||||
)
|
||||
|
||||
def _get_or_create_state(self, request_id: str) -> _GepardState:
|
||||
st = self._active_states.get(request_id)
|
||||
|
||||
@@ -82,7 +82,6 @@ from vllm.multimodal.inputs import (
|
||||
MultiModalKwargsItems,
|
||||
)
|
||||
from vllm.multimodal.parse import (
|
||||
ImageProcessorItems,
|
||||
MultiModalDataItems,
|
||||
MultiModalDataParser,
|
||||
)
|
||||
@@ -360,282 +359,36 @@ class GlmImageMultiModalProcessor(BaseMultiModalProcessor[GlmImageProcessingInfo
|
||||
return super()._cached_apply_hf_processor(inputs, timing_ctx)
|
||||
|
||||
def apply(self, inputs, timing_ctx):
|
||||
"""Ensure token prompts contain one source-image placeholder each."""
|
||||
"""Add the generation scaffold before upstream expands source images."""
|
||||
num_images = inputs.mm_data_items.get_all_counts().get("image", 0)
|
||||
prompt_ids = list(inputs.prompt)
|
||||
if num_images:
|
||||
image_token_id = getattr(
|
||||
self.info.get_hf_config(),
|
||||
"image_token_id",
|
||||
167855,
|
||||
)
|
||||
prompt_ids = list(inputs.prompt)
|
||||
missing = num_images - prompt_ids.count(image_token_id)
|
||||
if missing > 0:
|
||||
inputs = replace(
|
||||
inputs,
|
||||
prompt=[image_token_id] * missing + prompt_ids,
|
||||
)
|
||||
return super().apply(inputs, timing_ctx)
|
||||
prompt_ids = [image_token_id] * missing + prompt_ids
|
||||
|
||||
def _call_hf_processor(
|
||||
self,
|
||||
prompt: str,
|
||||
mm_data: Mapping[str, object],
|
||||
mm_kwargs: Mapping[str, object],
|
||||
tok_kwargs: Mapping[str, object],
|
||||
) -> BatchFeature:
|
||||
"""
|
||||
Call the HuggingFace processor.
|
||||
|
||||
For text-to-image mode (no images), we need to:
|
||||
1. Build the prompt with target grid dimensions
|
||||
2. Build the image_grid_thw tensor for M-RoPE position encoding
|
||||
|
||||
For image-to-image mode:
|
||||
1. Process source images through the image processor
|
||||
2. Build prompt with image placeholders expanded
|
||||
3. Build image_grid_thw including source and target grids
|
||||
"""
|
||||
processor = self.info.get_hf_processor()
|
||||
|
||||
# Get target dimensions from mm_kwargs or use defaults
|
||||
target_h = mm_kwargs.get("target_h", 1024) if mm_kwargs else 1024
|
||||
target_w = mm_kwargs.get("target_w", 1024) if mm_kwargs else 1024
|
||||
|
||||
logger.debug(
|
||||
f"_call_hf_processor: target dimensions for generation: {target_h}x{target_w}, mm_kwargs={mm_kwargs}"
|
||||
)
|
||||
|
||||
if not mm_data or not mm_data.get("images"):
|
||||
# Text-to-image mode
|
||||
if processor is not None:
|
||||
# Build messages format expected by processor
|
||||
messages = [{"role": "user", "content": [{"type": "text", "text": prompt}]}]
|
||||
|
||||
# Use apply_chat_template which handles target dimensions
|
||||
hf_inputs = processor.apply_chat_template(
|
||||
messages,
|
||||
tokenize=True,
|
||||
target_h=target_h,
|
||||
target_w=target_w,
|
||||
return_dict=True,
|
||||
return_tensors="pt",
|
||||
)
|
||||
|
||||
return hf_inputs
|
||||
else:
|
||||
# Fallback: just tokenize (this won't work properly for generation)
|
||||
tokenizer = self.info.get_tokenizer()
|
||||
prompt_ids = tokenizer.encode(prompt)
|
||||
return BatchFeature(dict(input_ids=[prompt_ids]), tensor_type="pt")
|
||||
|
||||
# Image-to-image mode
|
||||
# NOTE: Use "images" (plural) - this is what vLLM's ImageProcessorItems.get_processor_data() returns
|
||||
images = mm_data.get("images")
|
||||
if not isinstance(images, list):
|
||||
images = [images]
|
||||
|
||||
logger.debug(
|
||||
f"_call_hf_processor i2i: num_images={len(images)}, image_types={[type(img).__name__ for img in images]}"
|
||||
)
|
||||
|
||||
if processor is not None:
|
||||
# Build messages with image objects directly in content
|
||||
# This is how GlmImageProcessor expects images - embedded in the content dict
|
||||
# NOT as a separate images= parameter
|
||||
#
|
||||
# IMPORTANT: Remove <|image|> placeholders from prompt since apply_chat_template
|
||||
# will automatically insert them for each image in content. Having both leads to
|
||||
# index out of bounds when processing image_grid_thw.
|
||||
clean_prompt = prompt.replace("<|image|>", "")
|
||||
content = []
|
||||
for img in images:
|
||||
content.append({"type": "image", "image": img})
|
||||
content.append({"type": "text", "text": clean_prompt})
|
||||
messages = [{"role": "user", "content": content}]
|
||||
|
||||
logger.debug(f"_call_hf_processor: calling apply_chat_template with {len(images)} images in content")
|
||||
|
||||
# Use apply_chat_template - processor will process images when they're in content
|
||||
hf_inputs = processor.apply_chat_template(
|
||||
messages,
|
||||
tokenize=True,
|
||||
target_h=target_h,
|
||||
target_w=target_w,
|
||||
return_dict=True,
|
||||
return_tensors="pt",
|
||||
)
|
||||
|
||||
logger.debug(f"_call_hf_processor: apply_chat_template returned keys: {list(hf_inputs.keys())}")
|
||||
|
||||
# IMPORTANT (i2i): vLLM multimodal encoder must see source-only grids
|
||||
# (matching pixel_values and number of images), but M-RoPE needs full
|
||||
# grids (source + target) to compute correct decode positions.
|
||||
image_grid_thw = hf_inputs.get("image_grid_thw")
|
||||
if image_grid_thw is not None:
|
||||
# Preserve full grids for M-RoPE.
|
||||
hf_inputs["mrope_image_grid_thw"] = image_grid_thw
|
||||
|
||||
# Expose source-only grids for MM.
|
||||
# In most i2i requests, we process one prompt at a time here,
|
||||
# so `len(images)` is the number of source images.
|
||||
num_source_images = len(images)
|
||||
if image_grid_thw.shape[0] != num_source_images:
|
||||
source_grids = image_grid_thw[:num_source_images]
|
||||
hf_inputs["image_grid_thw"] = source_grids
|
||||
logger.debug(
|
||||
"_call_hf_processor: adjusted image_grid_thw for MM from %s to %s (num_source_images=%d)",
|
||||
tuple(image_grid_thw.shape),
|
||||
tuple(source_grids.shape),
|
||||
num_source_images,
|
||||
)
|
||||
|
||||
# Debug: Analyze input_ids for image tokens
|
||||
input_ids = hf_inputs.get("input_ids")
|
||||
if input_ids is not None:
|
||||
if hasattr(input_ids, "tolist"):
|
||||
ids_list = input_ids.tolist()
|
||||
if isinstance(ids_list[0], list):
|
||||
ids_list = ids_list[0] # Unbatch
|
||||
else:
|
||||
ids_list = list(input_ids)
|
||||
|
||||
# Get image token ID from config
|
||||
hf_config = self.info.get_hf_config()
|
||||
image_token_id = getattr(hf_config, "image_token_id", 167855)
|
||||
|
||||
# Count image tokens
|
||||
image_token_count = ids_list.count(image_token_id)
|
||||
logger.debug(
|
||||
f"_call_hf_processor: input_ids length={len(ids_list)}, "
|
||||
f"image_token_id={image_token_id}, "
|
||||
f"image_token_count={image_token_count}"
|
||||
)
|
||||
|
||||
# Log first/last few tokens to understand structure
|
||||
logger.debug(f"_call_hf_processor: first 20 tokens: {ids_list[:20]}")
|
||||
logger.debug(f"_call_hf_processor: last 20 tokens: {ids_list[-20:]}")
|
||||
|
||||
# Find positions of image tokens
|
||||
image_positions = [i for i, t in enumerate(ids_list) if t == image_token_id]
|
||||
if image_positions:
|
||||
logger.debug(f"_call_hf_processor: image token positions (first 10): {image_positions[:10]}")
|
||||
|
||||
return hf_inputs
|
||||
else:
|
||||
# Fallback without processor - this is not ideal but prevents crashes
|
||||
logger.warning("GlmImageProcessor not available, using fallback for i2i")
|
||||
tokenizer = self.info.get_tokenizer()
|
||||
hf_config = self.info.get_hf_config()
|
||||
|
||||
# Get image token
|
||||
image_token_id = getattr(hf_config, "image_token_id", 167855)
|
||||
try:
|
||||
image_token = tokenizer.convert_ids_to_tokens(image_token_id)
|
||||
except Exception:
|
||||
image_token = "<|image|>"
|
||||
|
||||
# Build prompt with image placeholders
|
||||
image_placeholders = image_token * len(images)
|
||||
full_prompt = f"{image_placeholders}{prompt}"
|
||||
prompt_ids = tokenizer.encode(full_prompt)
|
||||
return BatchFeature(dict(input_ids=[prompt_ids]), tensor_type="pt")
|
||||
|
||||
def _apply_hf_processor_mm_only(
|
||||
self,
|
||||
mm_items: MultiModalDataItems,
|
||||
hf_processor_mm_kwargs: Mapping[str, object],
|
||||
tokenization_kwargs: Mapping[str, object],
|
||||
) -> BatchFeature:
|
||||
"""
|
||||
Apply the HF processor on the multi-modal data only.
|
||||
|
||||
GLM-Image requires special handling because apply_chat_template always
|
||||
adds a target <|image|> placeholder in addition to source image placeholders.
|
||||
This causes an IndexError when the HF processor tries to find grid info
|
||||
for the target placeholder (which doesn't exist for source-only processing).
|
||||
|
||||
Solution: Call the image processor directly to get pixel_values and
|
||||
image_grid_thw, bypassing apply_chat_template's target handling.
|
||||
"""
|
||||
mm_counts = mm_items.get_all_counts()
|
||||
num_images = mm_counts.get("image", 0)
|
||||
|
||||
if num_images == 0:
|
||||
# No images - call parent implementation
|
||||
return super()._apply_hf_processor_mm_only(
|
||||
mm_items=mm_items,
|
||||
hf_processor_mm_kwargs=hf_processor_mm_kwargs,
|
||||
tokenization_kwargs=tokenization_kwargs,
|
||||
)
|
||||
|
||||
# For i2i mode, we need to process images directly with the image processor
|
||||
# to avoid the apply_chat_template target placeholder issue
|
||||
processor = self.info.get_hf_processor()
|
||||
image_processor = processor.image_processor
|
||||
|
||||
# Get images from mm_items
|
||||
images = mm_items.get_items("image", ImageProcessorItems)
|
||||
image_list = [images.get(i) for i in range(images.get_count())]
|
||||
|
||||
logger.debug(f"_apply_hf_processor_mm_only: processing {len(image_list)} images directly")
|
||||
|
||||
# Process images directly with image processor
|
||||
image_inputs = image_processor(
|
||||
images=image_list,
|
||||
return_tensors="pt",
|
||||
)
|
||||
|
||||
# Get grid info for source images only (no target)
|
||||
pixel_values = image_inputs.get("pixel_values")
|
||||
image_grid_thw = image_inputs.get("image_grid_thw")
|
||||
if image_grid_thw is not None and image_grid_thw.shape[0] != num_images:
|
||||
# Be defensive: some processors may include extra target grids.
|
||||
image_grid_thw = image_grid_thw[:num_images]
|
||||
image_inputs["image_grid_thw"] = image_grid_thw
|
||||
|
||||
logger.debug(
|
||||
f"_apply_hf_processor_mm_only: pixel_values shape=\
|
||||
{pixel_values.shape if pixel_values is not None else None}, "
|
||||
f"image_grid_thw shape={image_grid_thw.shape if image_grid_thw is not None else None}"
|
||||
)
|
||||
|
||||
# Build input_ids with image token placeholders
|
||||
# The _get_prompt_updates returns PromptReplacement(target=[image_token_id], ...)
|
||||
# which needs to find image tokens in input_ids to replace them.
|
||||
# We need to include one image_token_id per image so the replacement can work.
|
||||
tokenizer = self.info.get_tokenizer()
|
||||
image_token_id = tokenizer.convert_tokens_to_ids("<|image|>")
|
||||
|
||||
# Build input_ids: [image_token] * num_images + tokenized text
|
||||
# This way _apply_prompt_updates can find the image tokens and replace them
|
||||
dummy_text = self.dummy_inputs.get_dummy_text(mm_counts)
|
||||
text_ids = tokenizer.encode(dummy_text, add_special_tokens=False)
|
||||
input_ids = [image_token_id] * num_images + text_ids
|
||||
|
||||
logger.debug(
|
||||
f"_apply_hf_processor_mm_only: built input_ids with {num_images} image tokens + {len(text_ids)} text tokens"
|
||||
# Reuse HF's shape formatter without decoding/re-tokenizing the user's
|
||||
# tokens. Text-to-image needs both target and preview grids; i2i needs
|
||||
# only the target. Grid metadata alone does not supply these AR tokens.
|
||||
target_grid = self._build_generation_grids(inputs.hf_processor_mm_kwargs)[0]
|
||||
suffix, *_ = processor._build_prompt_with_target_shape(
|
||||
"",
|
||||
height=int(target_grid[1]) * 32,
|
||||
width=int(target_grid[2]) * 32,
|
||||
is_text_to_image=num_images == 0,
|
||||
)
|
||||
|
||||
return BatchFeature(
|
||||
dict(
|
||||
input_ids=[input_ids],
|
||||
pixel_values=pixel_values,
|
||||
image_grid_thw=image_grid_thw,
|
||||
),
|
||||
tensor_type="pt",
|
||||
)
|
||||
|
||||
def _apply_hf_processor_text_only(
|
||||
self, prompt_text: str, hf_processor_mm_kwargs: Mapping[str, object], tokenization_kwargs: Mapping[str, object]
|
||||
) -> list[int]:
|
||||
prompt_ids, _, _ = super()._apply_hf_processor_text_mm(
|
||||
prompt_text=prompt_text,
|
||||
mm_items=MultiModalDataItems({}),
|
||||
hf_processor_mm_kwargs=hf_processor_mm_kwargs,
|
||||
tokenization_kwargs=tokenization_kwargs,
|
||||
)
|
||||
return prompt_ids
|
||||
suffix_ids = tokenizer.encode(suffix, add_special_tokens=False)
|
||||
if prompt_ids[-len(suffix_ids) :] != suffix_ids:
|
||||
prompt_ids.extend(suffix_ids)
|
||||
inputs = replace(inputs, prompt=prompt_ids)
|
||||
return super().apply(inputs, timing_ctx)
|
||||
|
||||
def _build_generation_grids(self, hf_processor_mm_kwargs: Mapping[str, object]) -> torch.Tensor:
|
||||
"""Build generation grids for M-RoPE decode positions.
|
||||
@@ -678,174 +431,6 @@ class GlmImageMultiModalProcessor(BaseMultiModalProcessor[GlmImageProcessingInfo
|
||||
dtype=torch.long,
|
||||
)
|
||||
|
||||
def _apply_hf_processor_main(
|
||||
self,
|
||||
prompt: str | list[int],
|
||||
mm_items: MultiModalDataItems,
|
||||
hf_processor_mm_kwargs: Mapping[str, object],
|
||||
tokenization_kwargs: Mapping[str, object],
|
||||
*,
|
||||
enable_hf_prompt_update: bool,
|
||||
) -> tuple[list[int], BatchFeature, bool]:
|
||||
"""
|
||||
Override to handle GLM-Image i2i mode correctly.
|
||||
|
||||
Problem: When vLLM processes cached mm items (enable_hf_prompt_update=False),
|
||||
the base implementation:
|
||||
1. Gets prompt_ids from _apply_hf_processor_text_only (no image tokens)
|
||||
2. Gets mm_data from _apply_hf_processor_mm_only
|
||||
3. Returns is_update_applied=False
|
||||
|
||||
This causes _apply_prompt_updates to fail because prompt_ids has no image tokens.
|
||||
|
||||
Solution: For i2i mode, we build prompt_ids that include image placeholders,
|
||||
and return is_update_applied=False so _apply_prompt_updates can expand them.
|
||||
"""
|
||||
mm_counts = mm_items.get_all_counts()
|
||||
num_images = mm_counts.get("image", 0)
|
||||
|
||||
logger.debug(f"_apply_hf_processor_main: mm_counts={mm_counts}, num_images={num_images}")
|
||||
|
||||
if num_images == 0 and isinstance(prompt, str):
|
||||
# t2i mode or normal flow - use parent implementation
|
||||
prompt_ids = self._apply_hf_processor_text_only(
|
||||
prompt_text=prompt,
|
||||
hf_processor_mm_kwargs=hf_processor_mm_kwargs,
|
||||
tokenization_kwargs=tokenization_kwargs,
|
||||
)
|
||||
mm_processed_data = self._apply_hf_processor_mm_only(
|
||||
mm_items=mm_items,
|
||||
hf_processor_mm_kwargs=hf_processor_mm_kwargs,
|
||||
tokenization_kwargs=tokenization_kwargs,
|
||||
)
|
||||
|
||||
# t2i has no source images, so mm features cannot provide image_grid_thw.
|
||||
# Provide explicit generation grids for M-RoPE to avoid fallback token parsing
|
||||
# (which can degrade high-resolution spatial positions, e.g. 1920x1920).
|
||||
try:
|
||||
mrope_grid_thw = self._build_generation_grids(hf_processor_mm_kwargs)
|
||||
mm_processed_data["mrope_image_grid_thw"] = mrope_grid_thw
|
||||
logger.debug(
|
||||
"_apply_hf_processor_main t2i: mrope_image_grid_thw=%s",
|
||||
mrope_grid_thw.tolist(),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning("_apply_hf_processor_main t2i: failed to set mrope_image_grid_thw: %s", e)
|
||||
|
||||
return prompt_ids, mm_processed_data, False
|
||||
|
||||
# i2i mode: use unified HF processor path only.
|
||||
# This avoids drift between duplicated manual/HF i2i implementations.
|
||||
logger.debug(
|
||||
"_apply_hf_processor_main: i2i mode (enable_hf_prompt_update=%s), num_images=%s",
|
||||
enable_hf_prompt_update,
|
||||
num_images,
|
||||
)
|
||||
|
||||
if not isinstance(prompt, str):
|
||||
# Online OpenAI chat preprocessing can arrive here with tokenized
|
||||
# prompts (list[int]) before serving_chat replaces engine prompt
|
||||
# with the clean text prompt. Do not fail the whole request.
|
||||
logger.warning(
|
||||
"_apply_hf_processor_main i2i: got tokenized prompt type=%s; "
|
||||
"using compatibility path for preprocessing",
|
||||
type(prompt).__name__,
|
||||
)
|
||||
|
||||
prompt_ids = list(prompt)
|
||||
mm_processed_data = self._apply_hf_processor_mm_only(
|
||||
mm_items=mm_items,
|
||||
hf_processor_mm_kwargs=hf_processor_mm_kwargs,
|
||||
tokenization_kwargs=tokenization_kwargs,
|
||||
)
|
||||
|
||||
# Preserve full grids for M-RoPE decode (source + target), while
|
||||
# keeping image_grid_thw source-only for MM batching.
|
||||
try:
|
||||
source_grid_thw = mm_processed_data.get("image_grid_thw")
|
||||
if source_grid_thw is not None and isinstance(source_grid_thw, torch.Tensor):
|
||||
target_h = (
|
||||
hf_processor_mm_kwargs.get("target_h")
|
||||
if isinstance(hf_processor_mm_kwargs.get("target_h"), int)
|
||||
else None
|
||||
)
|
||||
target_w = (
|
||||
hf_processor_mm_kwargs.get("target_w")
|
||||
if isinstance(hf_processor_mm_kwargs.get("target_w"), int)
|
||||
else None
|
||||
)
|
||||
if target_h is None or target_w is None:
|
||||
target_h = (
|
||||
hf_processor_mm_kwargs.get("height")
|
||||
if isinstance(hf_processor_mm_kwargs.get("height"), int)
|
||||
else 1024
|
||||
)
|
||||
target_w = (
|
||||
hf_processor_mm_kwargs.get("width")
|
||||
if isinstance(hf_processor_mm_kwargs.get("width"), int)
|
||||
else 1024
|
||||
)
|
||||
|
||||
factor = 32
|
||||
token_h = max(1, target_h // factor)
|
||||
token_w = max(1, target_w // factor)
|
||||
target_grid = torch.tensor([[1, token_h, token_w]], dtype=source_grid_thw.dtype)
|
||||
mm_processed_data["mrope_image_grid_thw"] = torch.cat([source_grid_thw, target_grid], dim=0)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Prompt updates will expand image placeholders in this compatibility path.
|
||||
return prompt_ids, mm_processed_data, False
|
||||
|
||||
images = mm_items.get_items("image", ImageProcessorItems)
|
||||
image_list = [images.get(i) for i in range(images.get_count())]
|
||||
if not image_list:
|
||||
raise ValueError("GLM-Image i2i requires at least one source image in mm_items")
|
||||
|
||||
hf_inputs = self._call_hf_processor(
|
||||
prompt=prompt,
|
||||
mm_data={"images": image_list},
|
||||
mm_kwargs=hf_processor_mm_kwargs,
|
||||
tok_kwargs=tokenization_kwargs,
|
||||
)
|
||||
|
||||
input_ids = hf_inputs.get("input_ids")
|
||||
if input_ids is None:
|
||||
raise ValueError("HF i2i processor returned no input_ids")
|
||||
|
||||
if isinstance(input_ids, torch.Tensor):
|
||||
prompt_ids = input_ids[0].tolist() if input_ids.dim() > 1 else input_ids.tolist()
|
||||
else:
|
||||
prompt_ids = (
|
||||
input_ids[0]
|
||||
if isinstance(input_ids, list) and input_ids and isinstance(input_ids[0], list)
|
||||
else list(input_ids)
|
||||
)
|
||||
|
||||
mm_processed_data = BatchFeature(dict(), tensor_type="pt")
|
||||
for key in ("pixel_values", "image_grid_thw", "mrope_image_grid_thw"):
|
||||
value = hf_inputs.get(key)
|
||||
if value is not None:
|
||||
mm_processed_data[key] = value
|
||||
|
||||
image_grid_thw = mm_processed_data.get("image_grid_thw")
|
||||
mrope_grid_thw = mm_processed_data.get("mrope_image_grid_thw")
|
||||
hf_config = self.info.get_hf_config()
|
||||
image_token_id = getattr(hf_config, "image_token_id", 167855)
|
||||
image_token_count = prompt_ids.count(image_token_id)
|
||||
logger.debug(
|
||||
"_apply_hf_processor_main i2i(HF): num_images=%s, prompt_len=%s, image_token_count=%s, "
|
||||
"source_grid_shape=%s, mrope_grid_shape=%s",
|
||||
num_images,
|
||||
len(prompt_ids),
|
||||
image_token_count,
|
||||
tuple(image_grid_thw.shape) if image_grid_thw is not None else None,
|
||||
tuple(mrope_grid_thw.shape) if mrope_grid_thw is not None else None,
|
||||
)
|
||||
|
||||
# HF processor already expanded image placeholders in input_ids.
|
||||
return prompt_ids, mm_processed_data, True
|
||||
|
||||
def _apply_hf_processor_main(
|
||||
self,
|
||||
mm_items: MultiModalDataItems,
|
||||
@@ -891,7 +476,7 @@ class GlmImageMultiModalProcessor(BaseMultiModalProcessor[GlmImageProcessingInfo
|
||||
Get the multimodal field configuration.
|
||||
|
||||
For GLM-Image i2i mode:
|
||||
- image_grid_thw has been sliced in _call_hf_processor to only include source images
|
||||
- image_grid_thw contains only source grids from _apply_hf_processor_main
|
||||
- pixel_values has shape [total_patches, C, H, W] - only for source images
|
||||
|
||||
For t2i mode:
|
||||
@@ -899,7 +484,7 @@ class GlmImageMultiModalProcessor(BaseMultiModalProcessor[GlmImageProcessingInfo
|
||||
"""
|
||||
result = {}
|
||||
|
||||
# Get image_grid_thw if present (already sliced in _call_hf_processor)
|
||||
# Get the source-only image_grid_thw if present.
|
||||
image_grid_thw = hf_inputs.get("image_grid_thw")
|
||||
|
||||
if "pixel_values" in hf_inputs and image_grid_thw is not None:
|
||||
@@ -916,7 +501,7 @@ class GlmImageMultiModalProcessor(BaseMultiModalProcessor[GlmImageProcessingInfo
|
||||
|
||||
result["pixel_values"] = MultiModalFieldConfig.flat_from_sizes("image", image_grid_sizes)
|
||||
|
||||
# Register image_grid_thw - it's been sliced in _call_hf_processor
|
||||
# Register image_grid_thw - it's been sliced in _apply_hf_processor_main
|
||||
# to only include source image grids, so batching will work correctly
|
||||
result["image_grid_thw"] = MultiModalFieldConfig.batched("image")
|
||||
|
||||
@@ -931,37 +516,6 @@ class GlmImageMultiModalProcessor(BaseMultiModalProcessor[GlmImageProcessingInfo
|
||||
|
||||
return result
|
||||
|
||||
def _hf_processor_applies_updates(
|
||||
self,
|
||||
prompt_text: str,
|
||||
mm_items: MultiModalDataItems,
|
||||
hf_processor_mm_kwargs: Mapping[str, object],
|
||||
tokenization_kwargs: Mapping[str, object],
|
||||
) -> bool:
|
||||
"""
|
||||
Return whether the HF processor applies prompt updates.
|
||||
|
||||
For GLM-Image i2i mode, the HF processor's apply_chat_template already
|
||||
expands <|image|> to N tokens (e.g., 4096 for 64x64 grid).
|
||||
|
||||
By returning True, we tell vLLM that HF processor DID apply prompt updates,
|
||||
so vLLM will use _find_mm_placeholders to locate the expanded tokens
|
||||
instead of trying to apply replacements.
|
||||
|
||||
For t2i mode (no images), there are no image placeholders to expand.
|
||||
"""
|
||||
# Check if we have images (i2i mode)
|
||||
num_images = mm_items.get_all_counts().get("image", 0)
|
||||
if num_images > 0:
|
||||
logger.debug(
|
||||
f"_hf_processor_applies_updates: returning True for i2i mode "
|
||||
f"(num_images={num_images}) - HF processor already expanded tokens"
|
||||
)
|
||||
return True
|
||||
|
||||
# For t2i mode (no images), use default behavior
|
||||
return True
|
||||
|
||||
def _get_prompt_updates(
|
||||
self,
|
||||
mm_items: MultiModalDataItems,
|
||||
@@ -971,17 +525,8 @@ class GlmImageMultiModalProcessor(BaseMultiModalProcessor[GlmImageProcessingInfo
|
||||
"""
|
||||
Get prompt updates for image tokens.
|
||||
|
||||
For GLM-Image image-to-image mode, the HF processor's apply_chat_template
|
||||
already expands each <|image|> placeholder to the correct number of
|
||||
image tokens (grid_h * grid_w tokens per source image).
|
||||
|
||||
The HF processor does:
|
||||
1. Replace each <|image|> with num_image_tokens copies of <|placeholder|>
|
||||
2. Replace all <|placeholder|> back to <|image|>
|
||||
|
||||
So the tokenized input already has the expanded tokens. We use
|
||||
target=[image_token_id] to match each occurrence of the image token,
|
||||
similar to how Qwen2VL handles this pattern.
|
||||
Expand each source-image placeholder into grid_h * grid_w image tokens
|
||||
through vLLM's token-based prompt updates.
|
||||
|
||||
We use image_grid_thw from out_mm_kwargs to get the actual processed grid
|
||||
size, following the Qwen2VL pattern. This is critical because the HF processor
|
||||
@@ -992,8 +537,7 @@ class GlmImageMultiModalProcessor(BaseMultiModalProcessor[GlmImageProcessingInfo
|
||||
"""
|
||||
hf_config = self.info.get_hf_config()
|
||||
|
||||
# Get image token ID - this is the token that appears multiple times
|
||||
# in the tokenized input after HF processor expansion
|
||||
# Match the source-image placeholder in the tokenized input.
|
||||
image_token_id = getattr(hf_config, "image_token_id", 167855)
|
||||
|
||||
# Debug: log mm_items info
|
||||
|
||||
@@ -39,7 +39,6 @@ from vllm.multimodal.inputs import MultiModalFieldConfig, MultiModalKwargsItems
|
||||
from vllm.multimodal.parse import MultiModalDataItems, MultiModalDataParser
|
||||
from vllm.multimodal.processing import (
|
||||
BaseDummyInputsBuilder,
|
||||
BaseMultiModalProcessor,
|
||||
BaseProcessingInfo,
|
||||
ProcessorInputs,
|
||||
PromptIndexTargets,
|
||||
@@ -51,6 +50,7 @@ from vllm.v1.outputs import SamplerOutput
|
||||
from vllm.v1.sample.metadata import SamplingMetadata
|
||||
from vllm.v1.sample.sampler import Sampler
|
||||
|
||||
from vllm_omni.inputs.mm_processor import OmniMultiModalProcessor
|
||||
from vllm_omni.model_executor.models.common.nucleus_ras_sampling import ras_sample_one as _ras_sample_one
|
||||
from vllm_omni.model_executor.models.output_templates import OmniOutput
|
||||
from vllm_omni.platforms import current_omni_platform
|
||||
@@ -364,7 +364,7 @@ class GLMTTSMultiModalProcessingInfo(BaseProcessingInfo):
|
||||
)
|
||||
|
||||
|
||||
class GLMTTSMultiModalProcessor(BaseMultiModalProcessor[GLMTTSMultiModalProcessingInfo]):
|
||||
class GLMTTSMultiModalProcessor(OmniMultiModalProcessor[GLMTTSMultiModalProcessingInfo]):
|
||||
"""GLM-TTS voice-clone processor.
|
||||
|
||||
Unlike CosyVoice3, GLM-TTS prompt speech tokens are normal Llama vocab IDs
|
||||
@@ -373,8 +373,6 @@ class GLMTTSMultiModalProcessor(BaseMultiModalProcessor[GLMTTSMultiModalProcessi
|
||||
handoff.
|
||||
"""
|
||||
|
||||
_OMNI_PROMPT_TEXT_KEY = "_vllm_omni_original_prompt_text"
|
||||
|
||||
def apply(self, inputs: ProcessorInputs, timing_ctx):
|
||||
source_tokenizer = self.info.get_tokenizer()
|
||||
prompt_text = source_tokenizer.decode(
|
||||
@@ -630,24 +628,6 @@ class GLMTTSMultiModalProcessor(BaseMultiModalProcessor[GLMTTSMultiModalProcessi
|
||||
}
|
||||
)
|
||||
|
||||
def _apply_hf_processor_main(
|
||||
self,
|
||||
mm_items: MultiModalDataItems,
|
||||
hf_processor_mm_kwargs: Mapping[str, object],
|
||||
) -> BatchFeature:
|
||||
mm_kwargs = dict(hf_processor_mm_kwargs)
|
||||
prompt_text = str(mm_kwargs.pop(self._OMNI_PROMPT_TEXT_KEY, ""))
|
||||
valid_mm_items = mm_items.select({key for key, count in mm_items.get_all_counts().items() if count > 0})
|
||||
mm_data, passthrough_data = self._get_hf_mm_data(valid_mm_items)
|
||||
processed_data = self._call_hf_processor(
|
||||
prompt_text,
|
||||
mm_data,
|
||||
mm_kwargs,
|
||||
{},
|
||||
)
|
||||
processed_data.update(passthrough_data)
|
||||
return processed_data
|
||||
|
||||
def _get_mm_fields_config(
|
||||
self,
|
||||
hf_inputs: BatchFeature,
|
||||
|
||||
@@ -1669,8 +1669,10 @@ class _BackboneWrapper(nn.Module):
|
||||
self.config = config
|
||||
|
||||
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
|
||||
from vllm_omni.model_executor.models.weight_loader import AutoWeightsLoader
|
||||
from vllm.model_executor.models.utils import AutoWeightsLoader, WeightsMapper
|
||||
|
||||
skip = ["lm_head."] if getattr(self.config, "tie_word_embeddings", False) else None
|
||||
loader = AutoWeightsLoader(self, skip_prefixes=skip)
|
||||
return loader.load_weights(weights)
|
||||
loader = AutoWeightsLoader(self)
|
||||
return loader.load_weights(
|
||||
weights, mapper=WeightsMapper(orig_to_new_prefix={name: None for name in (skip or ())})
|
||||
)
|
||||
@@ -8,13 +8,12 @@
|
||||
# subclasses the removed HunYuanModel / HunYuanSparseMoeBlock / HunYuanMLP,
|
||||
# so those internals are preserved here to keep the omni diffusion model
|
||||
# working against current upstream.
|
||||
# Weight loading, expert mapping, and QKV splitting are owned by Omni's
|
||||
# HunyuanModel subclass; their unused upstream implementations are omitted.
|
||||
"""Inference-only HunYuan V1 internals vendored for vllm-omni HunyuanImage3."""
|
||||
|
||||
import typing
|
||||
from collections.abc import Callable, Iterable
|
||||
from itertools import islice
|
||||
|
||||
import regex as re
|
||||
import torch
|
||||
from torch import nn
|
||||
from transformers import PretrainedConfig
|
||||
@@ -29,7 +28,6 @@ from vllm.model_executor.layers.activation import SiluAndMul
|
||||
from vllm.model_executor.layers.attention import Attention
|
||||
from vllm.model_executor.layers.fused_moe import (
|
||||
FusedMoEFactory,
|
||||
fused_moe_make_expert_params_mapping,
|
||||
)
|
||||
from vllm.model_executor.layers.layernorm import RMSNorm
|
||||
from vllm.model_executor.layers.linear import (
|
||||
@@ -44,14 +42,9 @@ from vllm.model_executor.layers.rotary_embedding import get_rope
|
||||
from vllm.model_executor.layers.vocab_parallel_embedding import (
|
||||
VocabParallelEmbedding,
|
||||
)
|
||||
from vllm.model_executor.model_loader.weight_utils import (
|
||||
default_weight_loader,
|
||||
maybe_remap_kv_scale_name,
|
||||
)
|
||||
from vllm.model_executor.models.interfaces import EagleModelMixin
|
||||
from vllm.model_executor.models.utils import (
|
||||
PPMissingLayer,
|
||||
is_pp_missing_parameter,
|
||||
make_layers,
|
||||
)
|
||||
from vllm.sequence import IntermediateTensors
|
||||
@@ -630,201 +623,3 @@ class HunYuanModel(nn.Module, EagleModelMixin):
|
||||
if len(aux_hidden_states) > 0:
|
||||
return hidden_states, aux_hidden_states
|
||||
return hidden_states
|
||||
|
||||
def _split_qkv_weight(self, qkv: torch.Tensor):
|
||||
num_attention_heads = self.config.num_attention_heads
|
||||
num_kv_heads = getattr(self.config, "num_key_value_heads", self.config.num_attention_heads)
|
||||
num_key_value_groups = num_attention_heads // num_kv_heads
|
||||
hidden_size = self.config.hidden_size
|
||||
|
||||
if hasattr(self.config, "head_dim"):
|
||||
attention_head_dim = self.config.head_dim
|
||||
elif hasattr(self.config, "attention_head_dim"):
|
||||
attention_head_dim = self.config.attention_head_dim
|
||||
else:
|
||||
attention_head_dim = self.config.hidden_size // num_attention_heads
|
||||
|
||||
qkv = qkv.reshape(num_kv_heads, num_key_value_groups + 2, attention_head_dim, hidden_size)
|
||||
q, k, v = torch.split(qkv, (num_key_value_groups, 1, 1), dim=1)
|
||||
q = q.reshape(-1, hidden_size)
|
||||
k = k.reshape(-1, hidden_size)
|
||||
v = v.reshape(-1, hidden_size)
|
||||
return torch.concat((q, k, v))
|
||||
|
||||
def get_expert_mapping(self) -> list[tuple[str, str, int, str]]:
|
||||
if _is_moe(self.config):
|
||||
# Params for weights, fp8 weight scales, fp8 activation scales
|
||||
# (param_name, weight_name, expert_id, shard_id)
|
||||
return fused_moe_make_expert_params_mapping(
|
||||
self,
|
||||
ckpt_gate_proj_name="gate_proj",
|
||||
ckpt_down_proj_name="down_proj",
|
||||
ckpt_up_proj_name="up_proj",
|
||||
num_experts=self.config.num_experts,
|
||||
num_redundant_experts=self.num_redundant_experts,
|
||||
)
|
||||
else:
|
||||
return []
|
||||
|
||||
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]):
|
||||
cla_factor = _get_cla_factor(self.config)
|
||||
stacked_params_mapping = [
|
||||
# (param_name, shard_name, shard_id)
|
||||
(".qkv_proj", ".q_proj", "q"),
|
||||
(".qkv_proj", ".k_proj", "k"),
|
||||
(".qkv_proj", ".v_proj", "v"),
|
||||
(".gate_up_proj", ".gate_proj", 0),
|
||||
(".gate_up_proj", ".up_proj", 1),
|
||||
]
|
||||
|
||||
num_attention_heads = self.config.num_attention_heads
|
||||
num_kv_heads = getattr(self.config, "num_key_value_heads", self.config.num_attention_heads)
|
||||
split_params_mapping = [
|
||||
(".gate_up_proj", ".gate_and_up_proj", 2, [(1, 1), (0, 1)], None),
|
||||
(
|
||||
".qkv_proj",
|
||||
".qkv_proj",
|
||||
num_attention_heads + num_kv_heads * 2,
|
||||
[("q", num_attention_heads), ("k", num_kv_heads), ("v", num_kv_heads)],
|
||||
self._split_qkv_weight,
|
||||
),
|
||||
]
|
||||
|
||||
params_dict = dict(self.named_parameters())
|
||||
loaded_params: set[str] = set()
|
||||
expert_params_mapping = self.get_expert_mapping()
|
||||
for name, loaded_weight in weights:
|
||||
if "rotary_emb.inv_freq" in name:
|
||||
continue
|
||||
if "gate_proj_bias" in name:
|
||||
name = name.replace("gate_proj_bias", "gate_proj.bias")
|
||||
if "up_proj_bias" in name:
|
||||
name = name.replace("up_proj_bias", "up_proj.bias")
|
||||
if "rotary_emb.cos_cached" in name or "rotary_emb.sin_cached" in name:
|
||||
# Models trained using ColossalAI may include these tensors in
|
||||
# the checkpoint. Skip them.
|
||||
continue
|
||||
# With tie_word_embeddings, we can skip lm_head.weight
|
||||
# The weight might appear unnecessarily in the files if the model is
|
||||
# processed with quantization, LoRA, fine-tuning, etc.
|
||||
if self.config.tie_word_embeddings and "lm_head.weight" in name:
|
||||
continue
|
||||
|
||||
is_found = False
|
||||
for param_name, weight_name, shard_id in stacked_params_mapping:
|
||||
if weight_name not in name:
|
||||
continue
|
||||
if "mlp.experts" in name:
|
||||
continue
|
||||
# cross layer only have q_proj, skip qkv pack
|
||||
if weight_name == ".q_proj":
|
||||
match = re.search(r"layers\.\d+", name)
|
||||
if match:
|
||||
layer_id = int(match.group(0).split(".")[-1])
|
||||
if cla_factor > 1 and layer_id % cla_factor != 0:
|
||||
continue
|
||||
name = name.replace(weight_name, param_name)
|
||||
# Skip loading extra bias for GPTQ models.
|
||||
if name.endswith(".bias") and name not in params_dict:
|
||||
continue
|
||||
|
||||
if is_pp_missing_parameter(name, self):
|
||||
continue
|
||||
|
||||
param = params_dict[name]
|
||||
weight_loader = param.weight_loader
|
||||
weight_loader(param, loaded_weight, shard_id)
|
||||
loaded_params.add(name)
|
||||
is_found = True
|
||||
break
|
||||
if is_found:
|
||||
continue
|
||||
|
||||
for (
|
||||
param_name,
|
||||
weight_name,
|
||||
den,
|
||||
split_param,
|
||||
func,
|
||||
) in split_params_mapping:
|
||||
if weight_name not in name:
|
||||
continue
|
||||
name = name.replace(weight_name, param_name)
|
||||
# Skip loading extra bias for GPTQ models.
|
||||
if name.endswith(".bias") and name not in params_dict:
|
||||
continue
|
||||
|
||||
if is_pp_missing_parameter(name, self):
|
||||
continue
|
||||
|
||||
assert loaded_weight.shape[0] % den == 0
|
||||
units = loaded_weight.shape[0] // den
|
||||
|
||||
param = params_dict[name]
|
||||
weight_loader = param.weight_loader
|
||||
offset = 0
|
||||
for shard_id, num in split_param:
|
||||
new_offset = offset + num * units
|
||||
if func:
|
||||
weight_loader(param, func(loaded_weight)[offset:new_offset], shard_id)
|
||||
else:
|
||||
weight_loader(param, loaded_weight[offset:new_offset], shard_id)
|
||||
offset = new_offset
|
||||
|
||||
break
|
||||
else:
|
||||
# Skip loading extra bias for GPTQ models.
|
||||
if name.endswith(".bias") and name not in params_dict:
|
||||
continue
|
||||
is_expert_weight = False
|
||||
for mapping in expert_params_mapping:
|
||||
param_name, weight_name, expert_id, shard_id = mapping
|
||||
if weight_name not in name:
|
||||
continue
|
||||
# this is an expert weight and should not be
|
||||
# attempted to load as other weights later
|
||||
is_expert_weight = True
|
||||
|
||||
# Do not modify `name` since the loop may continue here
|
||||
# Instead, create a new variable
|
||||
name_mapped = name.replace(weight_name, param_name)
|
||||
if is_pp_missing_parameter(name_mapped, self):
|
||||
continue
|
||||
param = params_dict[name_mapped]
|
||||
# We should ask the weight loader to return success or not
|
||||
# here since otherwise we may skip experts with other
|
||||
# available replicas.
|
||||
weight_loader = typing.cast(Callable[..., bool], param.weight_loader)
|
||||
success = weight_loader(
|
||||
param,
|
||||
loaded_weight,
|
||||
name_mapped,
|
||||
shard_id=shard_id,
|
||||
expert_id=expert_id,
|
||||
return_success=True,
|
||||
)
|
||||
if success:
|
||||
name = name_mapped
|
||||
break
|
||||
else:
|
||||
if is_expert_weight:
|
||||
# We've checked that this is an expert weight
|
||||
# However it's not mapped locally to this rank
|
||||
# So we simply skip it
|
||||
continue
|
||||
# Remapping the name of FP8 kv-scale.
|
||||
name = maybe_remap_kv_scale_name(name, params_dict)
|
||||
if name is None:
|
||||
continue
|
||||
|
||||
if is_pp_missing_parameter(name, self):
|
||||
continue
|
||||
|
||||
if "mlp.gate.wg." in name:
|
||||
name = name.replace("wg.", "")
|
||||
|
||||
param = params_dict[name]
|
||||
weight_loader = getattr(param, "weight_loader", default_weight_loader)
|
||||
weight_loader(param, loaded_weight)
|
||||
loaded_params.add(name)
|
||||
return loaded_params
|
||||
@@ -54,7 +54,9 @@ from vllm.model_executor.models.interfaces import (
|
||||
_require_is_multimodal,
|
||||
)
|
||||
from vllm.model_executor.models.utils import (
|
||||
AutoWeightsLoader,
|
||||
PPMissingLayer,
|
||||
WeightsMapper,
|
||||
_merge_multimodal_embeddings,
|
||||
is_pp_missing_parameter,
|
||||
maybe_prefix,
|
||||
@@ -70,7 +72,6 @@ from vllm.multimodal.parse import (
|
||||
)
|
||||
from vllm.multimodal.processing import (
|
||||
BaseDummyInputsBuilder,
|
||||
BaseMultiModalProcessor,
|
||||
BaseProcessingInfo,
|
||||
PromptReplacement,
|
||||
PromptUpdate,
|
||||
@@ -83,6 +84,7 @@ from vllm.v1.outputs import SamplerOutput
|
||||
from vllm.v1.sample.metadata import SamplingMetadata
|
||||
from vllm.v1.sample.sampler import Sampler
|
||||
|
||||
from vllm_omni.inputs.mm_processor import OmniMultiModalProcessor
|
||||
from vllm_omni.model_executor.models.hunyuan_image3._hunyuan_v1_vendored import (
|
||||
HunYuanMLP,
|
||||
HunYuanModel,
|
||||
@@ -92,7 +94,6 @@ from vllm_omni.model_executor.models.hunyuan_image3._hunyuan_v1_vendored import
|
||||
)
|
||||
from vllm_omni.model_executor.models.hunyuan_image3.autoencoder_kl_3d import AutoencoderKLConv3D
|
||||
from vllm_omni.model_executor.models.hunyuan_image3.siglip2 import LightProjector, Siglip2VisionTransformer
|
||||
from vllm_omni.model_executor.models.weight_loader import AutoWeightsLoader
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
@@ -1027,7 +1028,7 @@ class HunyuanImage3DummyInputsBuilder(BaseDummyInputsBuilder[HunyuanImage3Proces
|
||||
}
|
||||
|
||||
|
||||
class HunyuanImage3MultiModalProcessor(BaseMultiModalProcessor[HunyuanImage3ProcessingInfo]):
|
||||
class HunyuanImage3MultiModalProcessor(OmniMultiModalProcessor[HunyuanImage3ProcessingInfo]):
|
||||
"""Multimodal processor for HunyuanImage3 model."""
|
||||
|
||||
def _call_hf_processor(
|
||||
@@ -1046,22 +1047,6 @@ class HunyuanImage3MultiModalProcessor(BaseMultiModalProcessor[HunyuanImage3Proc
|
||||
batch_feature["vae_generator_seed"] = torch.full((len(images),), int(vae_generator_seed), dtype=torch.long)
|
||||
return batch_feature
|
||||
|
||||
def _apply_hf_processor_main(
|
||||
self,
|
||||
mm_items: MultiModalDataItems,
|
||||
hf_processor_mm_kwargs: Mapping[str, object],
|
||||
) -> BatchFeature:
|
||||
valid_mm_items = mm_items.select({key for key, count in mm_items.get_all_counts().items() if count > 0})
|
||||
mm_data, passthrough_data = self._get_hf_mm_data(valid_mm_items)
|
||||
processed_data = self._call_hf_processor(
|
||||
self.dummy_inputs.get_dummy_text(mm_items.get_all_counts()),
|
||||
mm_data,
|
||||
hf_processor_mm_kwargs,
|
||||
{},
|
||||
)
|
||||
processed_data.update(passthrough_data)
|
||||
return processed_data
|
||||
|
||||
def _hf_processor_applies_updates(
|
||||
self,
|
||||
prompt_text: str,
|
||||
@@ -2196,12 +2181,11 @@ class HunyuanImage3ForConditionalGeneration(nn.Module, SupportsMultiModal, Suppo
|
||||
"timestep_r_emb",
|
||||
]
|
||||
skip_prefixes.extend(unexpected_keywords)
|
||||
loader = AutoWeightsLoader(
|
||||
self,
|
||||
skip_prefixes=skip_prefixes,
|
||||
)
|
||||
loader = AutoWeightsLoader(self)
|
||||
|
||||
loaded_params = loader.load_weights(weights)
|
||||
loaded_params = loader.load_weights(
|
||||
weights, mapper=WeightsMapper(orig_to_new_prefix={name: None for name in (skip_prefixes or ())})
|
||||
)
|
||||
return loaded_params
|
||||
|
||||
def get_language_model(self) -> torch.nn.Module:
|
||||
|
||||
@@ -30,7 +30,6 @@ from vllm.multimodal.inputs import (
|
||||
from vllm.multimodal.parse import AudioProcessorItems, MultiModalDataItems, MultiModalDataParser
|
||||
from vllm.multimodal.processing import (
|
||||
BaseDummyInputsBuilder,
|
||||
BaseMultiModalProcessor,
|
||||
BaseProcessingInfo,
|
||||
ProcessorInputs,
|
||||
PromptReplacement,
|
||||
@@ -45,6 +44,7 @@ from vllm.utils.platform_utils import is_pin_memory_available
|
||||
from vllm.v1.sample.metadata import SamplingMetadata
|
||||
from vllm.v1.sample.sampler import Sampler
|
||||
|
||||
from vllm_omni.inputs.mm_processor import OmniMultiModalProcessor
|
||||
from vllm_omni.model_executor.custom_process_mixin import CustomProcessMixin
|
||||
from vllm_omni.model_executor.models.mimo_audio.config_mimo_audio import (
|
||||
NO_INTERLEAVE_NEXT_TOKEN_ID,
|
||||
@@ -351,7 +351,7 @@ class MiMoAudioDataParser(MultiModalDataParser):
|
||||
return AudioProcessorItems(new_audios)
|
||||
|
||||
|
||||
class MiMoAudioLLMMultiModalProcessor(BaseMultiModalProcessor[MiMoAudioLLMProcessingInfo]):
|
||||
class MiMoAudioLLMMultiModalProcessor(OmniMultiModalProcessor[MiMoAudioLLMProcessingInfo]):
|
||||
def _call_hf_processor(
|
||||
self,
|
||||
prompt: str,
|
||||
@@ -398,22 +398,6 @@ class MiMoAudioLLMMultiModalProcessor(BaseMultiModalProcessor[MiMoAudioLLMProces
|
||||
tensor_type=None,
|
||||
)
|
||||
|
||||
def _apply_hf_processor_main(
|
||||
self,
|
||||
mm_items: MultiModalDataItems,
|
||||
hf_processor_mm_kwargs: Mapping[str, object],
|
||||
) -> BatchFeature:
|
||||
valid_mm_items = mm_items.select({key for key, count in mm_items.get_all_counts().items() if count > 0})
|
||||
mm_data, passthrough_data = self._get_hf_mm_data(valid_mm_items)
|
||||
processed_data = self._call_hf_processor(
|
||||
self.dummy_inputs.get_dummy_text(mm_items.get_all_counts()),
|
||||
dict(mm_data),
|
||||
hf_processor_mm_kwargs,
|
||||
{},
|
||||
)
|
||||
processed_data.update(passthrough_data)
|
||||
return processed_data
|
||||
|
||||
def _get_mm_fields_config(
|
||||
self,
|
||||
hf_inputs: BatchFeature,
|
||||
|
||||
@@ -22,6 +22,7 @@ from transformers import AutoTokenizer, Qwen2Config, Qwen2Model
|
||||
from transformers.utils.hub import cached_file
|
||||
from vllm.config import VllmConfig
|
||||
from vllm.logger import init_logger
|
||||
from vllm.model_executor.models.utils import AutoWeightsLoader, WeightsMapper
|
||||
from vllm.sequence import IntermediateTensors
|
||||
|
||||
from vllm_omni.model_executor.custom_process_mixin import CustomProcessMixin
|
||||
@@ -30,7 +31,6 @@ from vllm_omni.model_executor.models.common.ming.aggregator import Aggregator
|
||||
from vllm_omni.model_executor.models.common.ming.audio_vae import AudioVAE, AudioVAEConfig
|
||||
from vllm_omni.model_executor.models.ming_tts.constants import SPEAKER_EMBEDDING_DIM
|
||||
from vllm_omni.model_executor.models.output_templates import OmniOutput
|
||||
from vllm_omni.model_executor.models.weight_loader import AutoWeightsLoader
|
||||
from vllm_omni.transformers_utils.configs.ming_flash_omni import MingFlashOmniTalkerConfig
|
||||
|
||||
from .prompt_utils import DEFAULT_PROMPT as MING_DEFAULT_PROMPT
|
||||
@@ -510,12 +510,13 @@ class MingFlashOmniTalkerForConditionalGeneration(nn.Module, CustomProcessMixin)
|
||||
if self._standalone:
|
||||
weights = self._iter_talker_safetensors()
|
||||
|
||||
loader = AutoWeightsLoader(
|
||||
self,
|
||||
skip_prefixes=["audio_vae."], # loaded separately
|
||||
skip_substrs=["rotary_embed.inv_freq"], # non-persistent buffer
|
||||
loader = AutoWeightsLoader(self)
|
||||
loaded = loader.load_weights(
|
||||
weights,
|
||||
mapper=WeightsMapper(
|
||||
orig_to_new_prefix={"audio_vae.": None}, orig_to_new_substr={"rotary_embed.inv_freq": None}
|
||||
),
|
||||
)
|
||||
loaded = loader.load_weights(weights)
|
||||
logger.info("Loaded %d talker weights from checkpoint", len(loaded))
|
||||
|
||||
if self.audio_vae is not None and self._vae_weight_source is not None:
|
||||
|
||||
@@ -21,7 +21,6 @@ import inspect
|
||||
import math
|
||||
import os
|
||||
import warnings
|
||||
from collections import defaultdict
|
||||
from collections.abc import Callable, Iterable, Mapping, Sequence
|
||||
from dataclasses import dataclass, replace
|
||||
from functools import partial
|
||||
@@ -3596,29 +3595,15 @@ class MiniCPMO45OmniLLMMultiModalProcessor(BaseMultiModalProcessor[MiniCPMO45Omn
|
||||
*,
|
||||
out_keys: set[str],
|
||||
) -> dict[str, NestedTensors]:
|
||||
mm_kwargs = {k: v for k, v in mm_kwargs.items() if k != "use_tts"}
|
||||
# This processor supports zipping prompt and mm_data together
|
||||
if self.info.get_model_version() in {(2, 6), (4, 0), (4, 5)}:
|
||||
inputs = self.info.ctx.call_hf_processor(
|
||||
self.info.get_hf_processor(**mm_kwargs),
|
||||
dict(text=prompts, **mm_data),
|
||||
mm_kwargs,
|
||||
)
|
||||
else:
|
||||
inputs = defaultdict[str, list[torch.Tensor]](list)
|
||||
from vllm.model_executor.models.minicpmv import MiniCPMVMultiModalProcessor
|
||||
|
||||
for i, prompt in enumerate(prompts):
|
||||
inputs_one = self.info.ctx.call_hf_processor(
|
||||
self.info.get_hf_processor(**mm_kwargs),
|
||||
dict(text=prompt, **{key: value[i] for key, value in mm_data.items()}),
|
||||
mm_kwargs,
|
||||
)
|
||||
|
||||
for k, v in inputs_one.items():
|
||||
assert len(v) == 1, (k, len(v))
|
||||
inputs[k].append(v[0])
|
||||
|
||||
return {k: inputs[k] for k in out_keys}
|
||||
return MiniCPMVMultiModalProcessor._call_hf_processor_on_prompts(
|
||||
self,
|
||||
prompts,
|
||||
mm_data,
|
||||
{key: value for key, value in mm_kwargs.items() if key != "use_tts"},
|
||||
out_keys=out_keys,
|
||||
)
|
||||
|
||||
def _get_prompt_updates(
|
||||
self,
|
||||
|
||||
@@ -37,10 +37,10 @@ from vllm.logger import init_logger
|
||||
from vllm.model_executor.layers.logits_processor import LogitsProcessor
|
||||
from vllm.model_executor.layers.vocab_parallel_embedding import ParallelLMHead
|
||||
from vllm.model_executor.models.qwen3 import Qwen3Model
|
||||
from vllm.model_executor.models.utils import AutoWeightsLoader, WeightsMapper
|
||||
from vllm.v1.outputs import SamplerOutput
|
||||
|
||||
from vllm_omni.model_executor.models.output_templates import OmniOutput
|
||||
from vllm_omni.model_executor.models.weight_loader import AutoWeightsLoader
|
||||
|
||||
from .chunking import chunk_windows
|
||||
from .constants import (
|
||||
@@ -980,8 +980,10 @@ class MiniMaxMusic3TalkerForConditionalGeneration(nn.Module):
|
||||
through vLLM. The audio embedding table and the depth decoder live in
|
||||
a separate component of the repo and are loaded explicitly.
|
||||
"""
|
||||
loader = AutoWeightsLoader(self, skip_prefixes=["audio_embeddings.", "rvq_decoder."])
|
||||
loaded = loader.load_weights(weights)
|
||||
loader = AutoWeightsLoader(self)
|
||||
loaded = loader.load_weights(
|
||||
weights, mapper=WeightsMapper(orig_to_new_prefix={"audio_embeddings.": None, "rvq_decoder.": None})
|
||||
)
|
||||
loaded |= load_depth_decoder_weights(
|
||||
self.vllm_config,
|
||||
audio_embeddings=self.audio_embeddings,
|
||||
|
||||
@@ -29,7 +29,6 @@ from vllm.multimodal.inputs import (
|
||||
from vllm.multimodal.parse import MultiModalDataItems, MultiModalDataParser
|
||||
from vllm.multimodal.processing import (
|
||||
BaseDummyInputsBuilder,
|
||||
BaseMultiModalProcessor,
|
||||
BaseProcessingInfo,
|
||||
ProcessorInputs,
|
||||
PromptIndexTargets,
|
||||
@@ -38,6 +37,7 @@ from vllm.multimodal.processing import (
|
||||
)
|
||||
from vllm.sequence import IntermediateTensors
|
||||
|
||||
from vllm_omni.inputs.mm_processor import OmniMultiModalProcessor
|
||||
from vllm_omni.model_executor.models.output_templates import OmniOutput
|
||||
from vllm_omni.platforms import current_omni_platform
|
||||
from vllm_omni.transformers_utils.configs.omnivoice import OmniVoiceConfig
|
||||
@@ -64,15 +64,13 @@ class OmniVoiceMultiModalProcessingInfo(BaseProcessingInfo):
|
||||
)
|
||||
|
||||
|
||||
class OmniVoiceMultiModalProcessor(BaseMultiModalProcessor[OmniVoiceMultiModalProcessingInfo]):
|
||||
class OmniVoiceMultiModalProcessor(OmniMultiModalProcessor[OmniVoiceMultiModalProcessingInfo]):
|
||||
"""Processes text + optional reference audio for OmniVoice.
|
||||
|
||||
For voice cloning: text + reference audio → tokenized reference
|
||||
For auto voice: text only
|
||||
"""
|
||||
|
||||
_OMNI_PROMPT_TEXT_KEY = "_vllm_omni_original_prompt_text"
|
||||
|
||||
def apply(self, inputs: ProcessorInputs, timing_ctx):
|
||||
tokenizer = self.info.get_tokenizer()
|
||||
prompt_text = tokenizer.decode(inputs.prompt, skip_special_tokens=False)
|
||||
@@ -205,24 +203,6 @@ class OmniVoiceMultiModalProcessor(BaseMultiModalProcessor[OmniVoiceMultiModalPr
|
||||
)
|
||||
return ft
|
||||
|
||||
def _apply_hf_processor_main(
|
||||
self,
|
||||
mm_items: MultiModalDataItems,
|
||||
hf_processor_mm_kwargs: Mapping[str, object],
|
||||
) -> BatchFeature:
|
||||
mm_kwargs = dict(hf_processor_mm_kwargs)
|
||||
prompt_text = str(mm_kwargs.pop(self._OMNI_PROMPT_TEXT_KEY, ""))
|
||||
valid_mm_items = mm_items.select({key for key, count in mm_items.get_all_counts().items() if count > 0})
|
||||
mm_data, passthrough_data = self._get_hf_mm_data(valid_mm_items)
|
||||
processed_data = self._call_hf_processor(
|
||||
prompt_text,
|
||||
mm_data,
|
||||
mm_kwargs,
|
||||
{},
|
||||
)
|
||||
processed_data.update(passthrough_data)
|
||||
return processed_data
|
||||
|
||||
def _get_mm_fields_config(
|
||||
self,
|
||||
hf_inputs: BatchFeature,
|
||||
|
||||
@@ -11,6 +11,7 @@ from vllm.config import VllmConfig
|
||||
from vllm.logger import init_logger
|
||||
from vllm.model_executor.models.interfaces import SupportsPP
|
||||
from vllm.model_executor.models.utils import (
|
||||
AutoWeightsLoader,
|
||||
WeightsMapper,
|
||||
init_vllm_registered_model,
|
||||
maybe_prefix,
|
||||
@@ -20,8 +21,6 @@ from vllm.v1.outputs import SamplerOutput
|
||||
from vllm.v1.sample.metadata import SamplingMetadata
|
||||
from vllm.v1.sample.sampler import Sampler
|
||||
|
||||
from vllm_omni.model_executor.models.weight_loader import AutoWeightsLoader
|
||||
|
||||
|
||||
class Qwen2_5OmniTalkerForConditionalGeneration(
|
||||
nn.Module,
|
||||
@@ -143,11 +142,11 @@ class Qwen2_5OmniTalkerForConditionalGeneration(
|
||||
return self.language_model.sample(logits, sampling_metadata)
|
||||
|
||||
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
|
||||
loader = AutoWeightsLoader(
|
||||
self,
|
||||
skip_prefixes=["thinker.", "token2wav."],
|
||||
loader = AutoWeightsLoader(self)
|
||||
loaded = loader.load_weights(
|
||||
weights,
|
||||
mapper=(self.hf_to_vllm_mapper) | WeightsMapper(orig_to_new_prefix={"thinker.": None, "token2wav.": None}),
|
||||
)
|
||||
loaded = loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
|
||||
# Log load summary
|
||||
try:
|
||||
total_bytes = 0
|
||||
|
||||
@@ -63,6 +63,7 @@ from vllm.model_executor.models.qwen2_audio import (
|
||||
_get_feat_extract_output_lengths,
|
||||
)
|
||||
from vllm.model_executor.models.utils import (
|
||||
AutoWeightsLoader,
|
||||
WeightsMapper,
|
||||
init_vllm_registered_model,
|
||||
maybe_prefix,
|
||||
@@ -87,7 +88,6 @@ from vllm.multimodal.processing.processor import (
|
||||
from vllm.sequence import IntermediateTensors
|
||||
from vllm.utils.collection_utils import is_list_of
|
||||
|
||||
from vllm_omni.model_executor.models.weight_loader import AutoWeightsLoader
|
||||
from vllm_omni.quantization.component_config import (
|
||||
resolve_encoder_quant_config,
|
||||
)
|
||||
@@ -1398,11 +1398,12 @@ class Qwen2_5OmniThinkerForConditionalGeneration(
|
||||
if self.visual is None:
|
||||
skip_prefixes.extend(["visual."])
|
||||
|
||||
loader = AutoWeightsLoader(
|
||||
self,
|
||||
skip_prefixes=skip_prefixes,
|
||||
loader = AutoWeightsLoader(self)
|
||||
loaded_weights = loader.load_weights(
|
||||
weights,
|
||||
mapper=(self.hf_to_vllm_mapper)
|
||||
| WeightsMapper(orig_to_new_prefix={name: None for name in (skip_prefixes or ())}),
|
||||
)
|
||||
loaded_weights = loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
|
||||
|
||||
return loaded_weights
|
||||
|
||||
|
||||
@@ -21,7 +21,9 @@ from vllm.model_executor.layers.vocab_parallel_embedding import ParallelLMHead,
|
||||
from vllm.model_executor.model_loader.weight_utils import default_weight_loader, maybe_remap_kv_scale_name
|
||||
from vllm.model_executor.models.interfaces import SupportsLoRA, SupportsPP
|
||||
from vllm.model_executor.models.utils import (
|
||||
AutoWeightsLoader,
|
||||
PPMissingLayer,
|
||||
WeightsMapper,
|
||||
is_pp_missing_parameter,
|
||||
make_empty_intermediate_tensors_factory,
|
||||
make_layers,
|
||||
@@ -33,8 +35,6 @@ from vllm.v1.outputs import SamplerOutput
|
||||
from vllm.v1.sample.metadata import SamplingMetadata
|
||||
from vllm.v1.sample.sampler import Sampler
|
||||
|
||||
from vllm_omni.model_executor.models.weight_loader import AutoWeightsLoader
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
|
||||
@@ -445,8 +445,8 @@ class Qwen2ForCausalLM(nn.Module, SupportsLoRA, SupportsPP):
|
||||
return next_tokens
|
||||
|
||||
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
|
||||
loader = AutoWeightsLoader(
|
||||
self,
|
||||
skip_prefixes=(["lm_head."] if self.config.tie_word_embeddings else None),
|
||||
loader = AutoWeightsLoader(self)
|
||||
return loader.load_weights(
|
||||
weights,
|
||||
mapper=WeightsMapper(orig_to_new_prefix={"lm_head.": None} if self.config.tie_word_embeddings else {}),
|
||||
)
|
||||
return loader.load_weights(weights)
|
||||
@@ -23,6 +23,7 @@ from transformers.models.qwen3_omni_moe.modeling_qwen3_omni_moe import (
|
||||
from vllm.config import VllmConfig # type: ignore
|
||||
from vllm.logger import init_logger # type: ignore
|
||||
from vllm.model_executor.models.utils import ( # type: ignore
|
||||
AutoWeightsLoader,
|
||||
WeightsMapper,
|
||||
)
|
||||
|
||||
@@ -30,7 +31,6 @@ from vllm_omni.model_executor.models.common.snake_activation import SnakeBeta
|
||||
from vllm_omni.model_executor.models.qwen3_omni.quantization import (
|
||||
Qwen3OmniNestedSupportsQuant,
|
||||
)
|
||||
from vllm_omni.model_executor.models.weight_loader import AutoWeightsLoader
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
@@ -336,11 +336,11 @@ class Qwen3OmniMoeCode2Wav(nn.Module, Qwen3OmniNestedSupportsQuant):
|
||||
|
||||
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
|
||||
"""Load weights from HuggingFace checkpoint."""
|
||||
loader = AutoWeightsLoader(
|
||||
self,
|
||||
skip_prefixes=["thinker.", "talker."], # Already loaded above
|
||||
loader = AutoWeightsLoader(self)
|
||||
loaded = loader.load_weights(
|
||||
weights,
|
||||
mapper=(self.hf_to_vllm_mapper) | WeightsMapper(orig_to_new_prefix={"thinker.": None, "talker.": None}),
|
||||
)
|
||||
loaded = loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
|
||||
|
||||
# Log load summary
|
||||
try:
|
||||
|
||||
@@ -15,6 +15,7 @@ from vllm.model_executor.models.interfaces import (
|
||||
SupportsPP,
|
||||
)
|
||||
from vllm.model_executor.models.utils import (
|
||||
AutoWeightsLoader,
|
||||
WeightsMapper,
|
||||
maybe_prefix,
|
||||
)
|
||||
@@ -29,7 +30,6 @@ from vllm_omni.model_executor.models.qwen3_omni.qwen3_omni_moe_code_predictor_mt
|
||||
from vllm_omni.model_executor.models.qwen3_omni.qwen3_omni_moe_thinker import (
|
||||
Qwen3MoeLLMForCausalLM,
|
||||
)
|
||||
from vllm_omni.model_executor.models.weight_loader import AutoWeightsLoader
|
||||
from vllm_omni.quantization.component_config import ComponentQuantizationConfig
|
||||
|
||||
logger = init_logger(__name__)
|
||||
@@ -302,13 +302,12 @@ class Qwen3OmniMoeTalkerForConditionalGeneration(
|
||||
to vLLM's internal structure. Code predictor weights are routed
|
||||
to its custom loader for vocab extension support.
|
||||
"""
|
||||
loader = AutoWeightsLoader(
|
||||
self,
|
||||
skip_prefixes=["thinker.", "code2wav."],
|
||||
# "code_predictor."],
|
||||
)
|
||||
loader = AutoWeightsLoader(self)
|
||||
# Don't apply mapper again since we already did it
|
||||
loaded = loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
|
||||
loaded = loader.load_weights(
|
||||
weights,
|
||||
mapper=(self.hf_to_vllm_mapper) | WeightsMapper(orig_to_new_prefix={"thinker.": None, "code2wav.": None}),
|
||||
)
|
||||
|
||||
# Log load summary
|
||||
try:
|
||||
|
||||
@@ -32,7 +32,6 @@ import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from transformers import PretrainedConfig
|
||||
from transformers.feature_extraction_utils import BatchFeature
|
||||
from transformers.models.qwen3_omni_moe.configuration_qwen3_omni_moe import (
|
||||
Qwen3OmniMoeConfig,
|
||||
Qwen3OmniMoeThinkerConfig,
|
||||
@@ -93,10 +92,14 @@ from vllm.model_executor.models.qwen3_omni_moe_thinker import (
|
||||
from vllm.model_executor.models.qwen3_omni_moe_thinker import (
|
||||
Qwen3OmniMoeAudioEncoderLayer as _Qwen3OmniMoeAudioEncoderLayer,
|
||||
)
|
||||
from vllm.model_executor.models.qwen3_omni_moe_thinker import (
|
||||
Qwen3OmniMoeThinkerMultiModalProcessor as _UpstreamQwen3Processor,
|
||||
)
|
||||
from vllm.model_executor.models.qwen3_omni_moe_thinker import (
|
||||
_get_feat_extract_output_lengths,
|
||||
)
|
||||
from vllm.model_executor.models.utils import (
|
||||
AutoWeightsLoader,
|
||||
WeightsMapper,
|
||||
_merge_multimodal_embeddings,
|
||||
maybe_prefix,
|
||||
@@ -125,7 +128,6 @@ from vllm_omni.model_executor.models.qwen2_5_omni.qwen2_5_omni_thinker import (
|
||||
from vllm_omni.model_executor.models.qwen3_omni.quantization import (
|
||||
Qwen3OmniNestedSupportsQuant,
|
||||
)
|
||||
from vllm_omni.model_executor.models.weight_loader import AutoWeightsLoader
|
||||
from vllm_omni.quantization.component_config import (
|
||||
PRE_QUANTIZED_METHODS,
|
||||
ComponentQuantizationConfig,
|
||||
@@ -698,83 +700,10 @@ Qwen3OmniMoeThinkerDummyInputsBuilder = Qwen2_5OmniThinkerDummyInputsBuilder
|
||||
|
||||
class Qwen3OmniMoeThinkerMultiModalProcessor(
|
||||
Qwen2_5OmniThinkerMultiModalProcessor,
|
||||
_UpstreamQwen3Processor,
|
||||
):
|
||||
def _get_hf_mm_data(
|
||||
self,
|
||||
mm_items: MultiModalDataItems,
|
||||
) -> tuple[Mapping[str, object], Mapping[str, object]]:
|
||||
processor_data, passthrough_data = super()._get_hf_mm_data(mm_items)
|
||||
|
||||
audios = processor_data.get("audios")
|
||||
if audios:
|
||||
feature_extractor = self.info.get_feature_extractor()
|
||||
hop_length = feature_extractor.hop_length
|
||||
|
||||
def pad_to_hop_length(x: np.ndarray) -> np.ndarray:
|
||||
length = x.shape[-1]
|
||||
if length % hop_length != 0:
|
||||
pad_length = hop_length - (length % hop_length)
|
||||
x = np.pad(
|
||||
x,
|
||||
(0, pad_length),
|
||||
mode="constant",
|
||||
constant_values=0,
|
||||
)
|
||||
return x
|
||||
|
||||
# To make sure the cache works with padding=True, pre-pad audio
|
||||
# to a multiple of the feature extractor's hop length.
|
||||
processor_data = dict(processor_data)
|
||||
processor_data["audios"] = [
|
||||
pad_to_hop_length(audio) if isinstance(audio, np.ndarray) else (pad_to_hop_length(audio[0]), audio[1])
|
||||
for audio in audios
|
||||
]
|
||||
|
||||
return processor_data, passthrough_data
|
||||
|
||||
def _apply_hf_processor_main(
|
||||
self,
|
||||
mm_items: MultiModalDataItems,
|
||||
hf_processor_mm_kwargs: Mapping[str, object],
|
||||
) -> BatchFeature:
|
||||
if mm_items.get_all_counts().get("audio", 0):
|
||||
# TODO(Isotr0py): Remove this patch after upstream fix PR
|
||||
# released and Transformers version update:
|
||||
# https://github.com/huggingface/transformers/pull/41473
|
||||
hf_processor_mm_kwargs = dict(hf_processor_mm_kwargs)
|
||||
hf_processor_mm_kwargs["audio_kwargs"] = dict(hf_processor_mm_kwargs.get("audio_kwargs") or {})
|
||||
hf_processor_mm_kwargs["text_kwargs"] = dict(hf_processor_mm_kwargs.get("text_kwargs") or {})
|
||||
|
||||
processed_data = super()._apply_hf_processor_main(
|
||||
mm_items,
|
||||
hf_processor_mm_kwargs,
|
||||
)
|
||||
|
||||
if "audio_feature_lengths" in processed_data and "feature_attention_mask" in processed_data:
|
||||
valid_mm_items = mm_items.select({k for k, c in mm_items.get_all_counts().items() if c > 0})
|
||||
processor_data, _ = self._get_hf_mm_data(valid_mm_items)
|
||||
audios = processor_data.get("audios", [])
|
||||
if audios:
|
||||
feature_extractor = self.info.get_feature_extractor(**hf_processor_mm_kwargs)
|
||||
hop_length = feature_extractor.hop_length
|
||||
|
||||
audio_num_frames = []
|
||||
for audio in audios:
|
||||
audio_length = len(audio[0]) if isinstance(audio, tuple) else len(audio)
|
||||
num_frame = (
|
||||
audio_length // hop_length if audio_length % hop_length == 0 else audio_length // hop_length - 1
|
||||
)
|
||||
if hf_processor_mm_kwargs.get("truncation", False):
|
||||
num_frame = min(
|
||||
num_frame,
|
||||
feature_extractor.n_samples // hop_length,
|
||||
)
|
||||
audio_num_frames.append(num_frame)
|
||||
|
||||
processed_data["feature_attention_mask"] = [torch.ones(num_frame) for num_frame in audio_num_frames]
|
||||
processed_data["audio_feature_lengths"] = torch.tensor(audio_num_frames)
|
||||
|
||||
return processed_data
|
||||
# Preserve Omni's per-video overrides while inheriting upstream Qwen3's
|
||||
# audio processing through the shared, cooperative Qwen2.5 base.
|
||||
|
||||
def _maybe_apply_prompt_updates(
|
||||
self,
|
||||
@@ -1518,11 +1447,11 @@ class Qwen3OmniMoeThinkerForConditionalGeneration(
|
||||
return self.language_model.compute_logits(hidden_states)
|
||||
|
||||
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
|
||||
loader = AutoWeightsLoader(
|
||||
self,
|
||||
skip_prefixes=["talker.", "code2wav."],
|
||||
loader = AutoWeightsLoader(self)
|
||||
loaded_weights = loader.load_weights(
|
||||
weights,
|
||||
mapper=(self.hf_to_vllm_mapper) | WeightsMapper(orig_to_new_prefix={"talker.": None, "code2wav.": None}),
|
||||
)
|
||||
loaded_weights = loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
|
||||
|
||||
return loaded_weights
|
||||
|
||||
|
||||
@@ -14,9 +14,9 @@ from vllm.config import VllmConfig
|
||||
from vllm.forward_context import get_forward_context, is_forward_context_available
|
||||
from vllm.logger import init_logger
|
||||
from vllm.model_executor.model_loader import DefaultModelLoader
|
||||
from vllm.model_executor.models.utils import AutoWeightsLoader, WeightsMapper
|
||||
|
||||
from vllm_omni.model_executor.models.output_templates import OmniOutput
|
||||
from vllm_omni.model_executor.models.weight_loader import AutoWeightsLoader
|
||||
from vllm_omni.model_executor.stage_input_processors.chunk_size_utils import parse_chunk_ramp
|
||||
|
||||
from .tokenizer_12hz.configuration_qwen3_tts_tokenizer_v2 import (
|
||||
@@ -555,10 +555,9 @@ class Qwen3TTSCode2Wav(nn.Module):
|
||||
subfolder="speech_tokenizer",
|
||||
)
|
||||
subfolder_weights = model_loader._get_weights_iterator(source)
|
||||
loaded = AutoWeightsLoader(
|
||||
self,
|
||||
skip_prefixes=["encoder."],
|
||||
).load_weights(subfolder_weights)
|
||||
loaded = AutoWeightsLoader(self).load_weights(
|
||||
subfolder_weights, mapper=WeightsMapper(orig_to_new_prefix={"encoder.": None})
|
||||
)
|
||||
|
||||
device = self.vllm_config.device_config.device
|
||||
self.decoder.to(device=device, dtype=torch.float32)
|
||||
|
||||
@@ -20,13 +20,12 @@ from vllm.model_executor.layers.logits_processor import LogitsProcessor
|
||||
from vllm.model_executor.layers.vocab_parallel_embedding import ParallelLMHead
|
||||
from vllm.model_executor.model_loader.default_loader import DefaultModelLoader
|
||||
from vllm.model_executor.models.qwen3 import Qwen3Model
|
||||
from vllm.model_executor.models.utils import PPMissingLayer, WeightsMapper, maybe_prefix
|
||||
from vllm.model_executor.models.utils import AutoWeightsLoader, PPMissingLayer, WeightsMapper, maybe_prefix
|
||||
from vllm.multimodal.audio import AudioResampler
|
||||
from vllm.sequence import IntermediateTensors
|
||||
|
||||
from vllm_omni.data_entry_keys import OmniPayload
|
||||
from vllm_omni.model_executor.models.output_templates import OmniOutput
|
||||
from vllm_omni.model_executor.models.weight_loader import AutoWeightsLoader
|
||||
from vllm_omni.utils.speaker_cache import (
|
||||
get_speaker_cache,
|
||||
iter_custom_voice_profiles,
|
||||
@@ -1104,10 +1103,9 @@ class Qwen3TTSTalkerForConditionalGeneration(nn.Module):
|
||||
subfolder="speech_tokenizer",
|
||||
)
|
||||
subfolder_weights = model_loader._get_weights_iterator(source)
|
||||
enc_loaded = AutoWeightsLoader(
|
||||
self,
|
||||
skip_prefixes=["decoder."],
|
||||
).load_weights(subfolder_weights)
|
||||
enc_loaded = AutoWeightsLoader(self).load_weights(
|
||||
subfolder_weights, mapper=WeightsMapper(orig_to_new_prefix={"decoder.": None})
|
||||
)
|
||||
loaded |= enc_loaded
|
||||
|
||||
# AutoWeightsLoader only loads parameters; the encoder's VQ
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||
|
||||
"""Compatibility helpers for vLLM model weight loading."""
|
||||
|
||||
from collections.abc import Iterable
|
||||
|
||||
import torch
|
||||
from torch import nn
|
||||
from vllm.model_executor.models.utils import (
|
||||
AutoWeightsLoader as _VllmAutoWeightsLoader,
|
||||
)
|
||||
from vllm.model_executor.models.utils import (
|
||||
WeightsMapper,
|
||||
)
|
||||
|
||||
|
||||
class AutoWeightsLoader(_VllmAutoWeightsLoader):
|
||||
"""Preserve vLLM's pre-0.29 weight-filtering arguments.
|
||||
|
||||
vLLM removed ``skip_prefixes`` and ``skip_substrs`` from
|
||||
:class:`AutoWeightsLoader` in favor of mapper-based filtering. Omni models
|
||||
still use those arguments to exclude checkpoint components owned by a
|
||||
different stage. Convert the filters to a ``WeightsMapper`` so the old
|
||||
behavior is retained without bypassing the current loader implementation.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
module: nn.Module,
|
||||
*,
|
||||
skip_prefixes: list[str] | None = None,
|
||||
skip_substrs: list[str] | None = None,
|
||||
ignore_unexpected_prefixes: list[str] | None = None,
|
||||
ignore_unexpected_suffixes: list[str] | None = None,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
module,
|
||||
ignore_unexpected_prefixes=ignore_unexpected_prefixes,
|
||||
ignore_unexpected_suffixes=ignore_unexpected_suffixes,
|
||||
)
|
||||
self._skip_mapper = WeightsMapper(
|
||||
orig_to_new_prefix={prefix: None for prefix in skip_prefixes or ()},
|
||||
orig_to_new_substr={substring: None for substring in skip_substrs or ()},
|
||||
)
|
||||
|
||||
def load_weights(
|
||||
self,
|
||||
weights: Iterable[tuple[str, torch.Tensor]],
|
||||
*,
|
||||
mapper: WeightsMapper | None = None,
|
||||
) -> set[str]:
|
||||
mapper = (mapper or WeightsMapper()) | self._skip_mapper
|
||||
return super().load_weights(weights, mapper=mapper)
|
||||
Reference in new issue
Block a user