From 0576264dd1a063ec0c8601b45e2926f039ef1752 Mon Sep 17 00:00:00 2001 From: Hongshaorou Date: Tue, 21 Jul 2026 15:44:05 +0800 Subject: [PATCH] [Bugfix] Fix MiniCPM-o 4.5 audio placeholder pool step (#5116) Signed-off-by: a-kaa Co-authored-by: amy-why-3459 --- .../minicpmo_4_5/test_audio_pool_step.py | 71 +++++++++++++++++++ .../minicpmo_4_5/minicpmo_4_5_omni_llm.py | 7 +- 2 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 tests/model_executor/models/minicpmo_4_5/test_audio_pool_step.py diff --git a/tests/model_executor/models/minicpmo_4_5/test_audio_pool_step.py b/tests/model_executor/models/minicpmo_4_5/test_audio_pool_step.py new file mode 100644 index 000000000..9fe2f144a --- /dev/null +++ b/tests/model_executor/models/minicpmo_4_5/test_audio_pool_step.py @@ -0,0 +1,71 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project +"""Regression tests for MiniCPM-o 4.5 audio placeholder pooling.""" + +from __future__ import annotations + +import math +from types import SimpleNamespace + +import pytest + +from vllm_omni.model_executor.models.minicpmo_4_5.minicpmo_4_5_omni_llm import ( + MiniCPMO45OmniLLMProcessingInfo, + MiniCPMOConfig, +) + +pytestmark = [pytest.mark.core_model, pytest.mark.cpu] + + +class _FakeProcessor: + """Minimal processor surface used by ``get_audio_placeholder``.""" + + def __init__(self) -> None: + # Reproduce the cached vLLM processor's legacy initial state. + self.pool_step = 2 + self.audio_processor = SimpleNamespace(hop_length=160) + self.image_processor = SimpleNamespace(mean=[0.0], std=[1.0]) + + def get_audio_placeholder( + self, + audio_lens: int, + chunk_input: bool = True, + chunk_length: int = 1, + ) -> str: + del chunk_input, chunk_length + feature_lens = math.ceil(audio_lens / self.audio_processor.hop_length) + cnn_feature_lens = (feature_lens - 1) // 2 + 1 + output_lens = (cnn_feature_lens - self.pool_step) // self.pool_step + 1 + return "" * output_lens + + +def _processing_info(pool_step: int, processor: _FakeProcessor) -> MiniCPMO45OmniLLMProcessingInfo: + info = object.__new__(MiniCPMO45OmniLLMProcessingInfo) + info.ctx = SimpleNamespace( + tokenizer=object(), + get_hf_config=lambda: SimpleNamespace(audio_pool_step=pool_step), + get_hf_processor=lambda **kwargs: processor, + ) + return info + + +@pytest.mark.parametrize( + ("configured_pool_step", "expected_placeholders"), + [(5, 50), (2, 125)], +) +def test_audio_placeholders_follow_checkpoint_pool_step( + configured_pool_step: int, + expected_placeholders: int, +) -> None: + """The prompt path must use the same pool step as the audio encoder.""" + processor = _FakeProcessor() + info = _processing_info(configured_pool_step, processor) + + placeholder = info.get_audio_placeholder(5 * 16_000) + + assert processor.pool_step == configured_pool_step + assert placeholder.count("") == expected_placeholders + + +def test_minicpmo_4_5_pool_step_defaults_to_checkpoint_value() -> None: + assert MiniCPMOConfig().audio_pool_step == 5 diff --git a/vllm_omni/model_executor/models/minicpmo_4_5/minicpmo_4_5_omni_llm.py b/vllm_omni/model_executor/models/minicpmo_4_5/minicpmo_4_5_omni_llm.py index 3019a5b3d..921095e75 100644 --- a/vllm_omni/model_executor/models/minicpmo_4_5/minicpmo_4_5_omni_llm.py +++ b/vllm_omni/model_executor/models/minicpmo_4_5/minicpmo_4_5_omni_llm.py @@ -328,7 +328,7 @@ class MiniCPMOConfig(Qwen2Config): tts_config=None, use_image_id=True, vision_batch_size=16, - audio_pool_step=2, + audio_pool_step=5, audio_chunk_length=1.0, stream_input=False, init_vision=True, @@ -2839,6 +2839,9 @@ class MiniCPMO45OmniLLMProcessingInfo(BaseProcessingInfo): ) -> str: hf_processor = self.get_hf_processor() + # Keep prompt placeholders aligned with the encoder's checkpoint config. + hf_processor.pool_step = self.get_default_audio_pool_step() + return hf_processor.get_audio_placeholder( audio_lens, chunk_input=chunk_input, @@ -2846,7 +2849,7 @@ class MiniCPMO45OmniLLMProcessingInfo(BaseProcessingInfo): ) def get_default_audio_pool_step(self) -> int: - return getattr(self.get_hf_config(), "audio_pool_step", 2) + return getattr(self.get_hf_config(), "audio_pool_step", 5) def get_default_audio_sampling_rate(self) -> int: return 16000