[CI][MiniCPM-o] Add MiniCPM-o 4.5 accuracy and performance coverage (#5524)
Signed-off-by: R2-Y <ruiruyang2@gmail.com> Signed-off-by: psv666 <2693925048@qq.com> Co-authored-by: psv666 <2693925048@qq.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: amy-why-3459 <wuhaiyan17@huawei.com>
This commit is contained in:
59 files changed
+1908
-155
No files matched your search
@@ -1,192 +0,0 @@
|
||||
"""Shared fixtures and argument builders for MiniCPM-o 4.5 duplex E2E tests."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import uuid
|
||||
import wave
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
from huggingface_hub import snapshot_download
|
||||
|
||||
from tests.helpers.runtime import OmniServerParams
|
||||
from tests.helpers.stage_config import get_deploy_config_path, modify_stage_config
|
||||
|
||||
MODEL = "openbmb/MiniCPM-o-4_5"
|
||||
DEPLOY_CONFIG = modify_stage_config(
|
||||
get_deploy_config_path("minicpmo_4_5_duplex.yaml"),
|
||||
updates={
|
||||
"base_config": get_deploy_config_path("minicpmo_4_5.yaml"),
|
||||
"stages": {
|
||||
0: {"kv_cache_memory_bytes": 6 * 1024 * 1024 * 1024},
|
||||
1: {"kv_cache_memory_bytes": 512 * 1024 * 1024},
|
||||
2: {"kv_cache_memory_bytes": 256 * 1024 * 1024},
|
||||
},
|
||||
# Platform overrides are applied after ordinary stage settings. Keep
|
||||
# the constrained duplex test's Talker budget ahead of the base CUDA
|
||||
# profile's single-GPU default.
|
||||
"platforms": {
|
||||
"cuda": {
|
||||
"stages": [
|
||||
{
|
||||
"stage_id": 1,
|
||||
"kv_cache_memory_bytes": 512 * 1024 * 1024,
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
CORE_DEPLOY_CONFIG = modify_stage_config(
|
||||
DEPLOY_CONFIG,
|
||||
updates={
|
||||
"stages": {
|
||||
0: {"enforce_eager": True},
|
||||
1: {"enforce_eager": True},
|
||||
}
|
||||
},
|
||||
)
|
||||
ASSET_DIR = Path(__file__).resolve().parents[1] / "assets" / "minicpmo_4_5"
|
||||
RESPONSE_REQUIRED_WAV = ASSET_DIR / "response_required_16k.wav"
|
||||
RESPONSE_REQUIRED_SHA256 = "2e5fd4eb3ee434ce107ee3a0591fa624a33f7683c7462f45fe651c443c9af941"
|
||||
SOFT_INTERRUPT_WAV = ASSET_DIR / "soft_interrupt_16k.wav"
|
||||
SOFT_INTERRUPT_SHA256 = "cadae6d0ddc510310f16f8775d6379f30a5195369ccb54ff10a8e3f9a1f4a2ea"
|
||||
REF_AUDIO_RELATIVE_PATH = Path("assets") / "HT_ref_audio.wav"
|
||||
|
||||
SERVER_PARAMS = [
|
||||
pytest.param(
|
||||
OmniServerParams(
|
||||
model=MODEL,
|
||||
stage_config_path=DEPLOY_CONFIG,
|
||||
use_stage_cli=True,
|
||||
server_args=["--trust-remote-code"],
|
||||
),
|
||||
id="three-stage-single-gpu",
|
||||
)
|
||||
]
|
||||
CORE_SERVER_PARAMS = [
|
||||
pytest.param(
|
||||
OmniServerParams(
|
||||
model=MODEL,
|
||||
stage_config_path=CORE_DEPLOY_CONFIG,
|
||||
use_stage_cli=True,
|
||||
server_args=["--trust-remote-code"],
|
||||
),
|
||||
id="three-stage-single-gpu",
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def validated_wav(path: Path, expected_sha256: str) -> Path:
|
||||
actual_sha256 = hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
if actual_sha256 != expected_sha256:
|
||||
raise AssertionError(
|
||||
f"MiniCPM-o duplex fixture SHA256 mismatch: expected {expected_sha256}, got {actual_sha256}"
|
||||
)
|
||||
with wave.open(str(path), "rb") as wav_file:
|
||||
actual_format = (
|
||||
wav_file.getframerate(),
|
||||
wav_file.getnchannels(),
|
||||
wav_file.getsampwidth(),
|
||||
wav_file.getcomptype(),
|
||||
)
|
||||
expected_format = (16_000, 1, 2, "NONE")
|
||||
if actual_format != expected_format:
|
||||
raise AssertionError(f"MiniCPM-o duplex fixture must be 16 kHz mono PCM16, got {actual_format}")
|
||||
return path
|
||||
|
||||
|
||||
def validated_input_wav() -> Path:
|
||||
return validated_wav(RESPONSE_REQUIRED_WAV, RESPONSE_REQUIRED_SHA256)
|
||||
|
||||
|
||||
def validated_soft_interrupt_wav() -> Path:
|
||||
return validated_wav(SOFT_INTERRUPT_WAV, SOFT_INTERRUPT_SHA256)
|
||||
|
||||
|
||||
def resolve_ref_audio(model_prefix: str) -> Path:
|
||||
if model_prefix:
|
||||
model_root = Path(model_prefix) / MODEL
|
||||
else:
|
||||
model_root = Path(snapshot_download(MODEL, local_files_only=True))
|
||||
ref_audio = model_root / REF_AUDIO_RELATIVE_PATH
|
||||
if not ref_audio.is_file():
|
||||
raise FileNotFoundError(f"MiniCPM-o checkpoint ref audio is missing: {ref_audio}")
|
||||
return ref_audio
|
||||
|
||||
|
||||
def realtime_url(omni_server) -> str:
|
||||
return f"ws://{omni_server.host}:{omni_server.port}/v1/realtime?duplex=1"
|
||||
|
||||
|
||||
def demo_args(
|
||||
*,
|
||||
omni_server,
|
||||
input_wav: Path,
|
||||
ref_audio: Path,
|
||||
output_dir: Path,
|
||||
) -> SimpleNamespace:
|
||||
return SimpleNamespace(
|
||||
url=realtime_url(omni_server),
|
||||
model=omni_server.model,
|
||||
session_id=f"duplex-ci-single-{uuid.uuid4().hex}",
|
||||
input_wav=str(input_wav),
|
||||
ref_audio=str(ref_audio),
|
||||
turn_input_wav=[],
|
||||
output_dir=str(output_dir),
|
||||
output_audio_format="pcm16",
|
||||
chunk_ms=200,
|
||||
realtime_input=True,
|
||||
first_turn_ms=1400,
|
||||
turn_duration_ms=[],
|
||||
first_turn_transcript="duplex CI speech",
|
||||
omit_transcript_hints=False,
|
||||
validation_mode="response-required",
|
||||
temperature=0.0,
|
||||
scenario="sequential",
|
||||
require_audio=True,
|
||||
require_distinct_inputs=False,
|
||||
expect_empty_turn=[],
|
||||
short_ack_ms=350,
|
||||
turns=1,
|
||||
timeout_s=120.0,
|
||||
model_policy_settle_ms=2000,
|
||||
)
|
||||
|
||||
|
||||
def multi_session_args(
|
||||
*,
|
||||
omni_server,
|
||||
input_wav: Path,
|
||||
ref_audio: Path,
|
||||
output_dir: Path,
|
||||
response_required: bool,
|
||||
) -> SimpleNamespace:
|
||||
return SimpleNamespace(
|
||||
url=realtime_url(omni_server),
|
||||
model=omni_server.model,
|
||||
sessions=2,
|
||||
input_wav=str(input_wav),
|
||||
ref_audio=str(ref_audio),
|
||||
session_input_wav=[],
|
||||
session_expected_token=[],
|
||||
turn_input_wav=[],
|
||||
output_dir=str(output_dir),
|
||||
realtime_input=True,
|
||||
chunk_ms=200,
|
||||
turns=1,
|
||||
first_turn_ms=1400,
|
||||
turn_duration_ms=[],
|
||||
response_required=response_required,
|
||||
temperature=0.0 if response_required else None,
|
||||
disconnect_session_index=0,
|
||||
takeover_session_index=1,
|
||||
resume_after_ms=100,
|
||||
expire_session_index=None,
|
||||
expire_after_s=40.0,
|
||||
verify_admission_limit=None,
|
||||
model_policy_settle_ms=2000,
|
||||
timeout_s=180.0,
|
||||
)
|
||||
@@ -3179,7 +3179,12 @@ def iter_omni_server(
|
||||
print("OmniServer stopping...")
|
||||
else:
|
||||
if stage_config_path is not None:
|
||||
server_args += ["--deploy-config", stage_config_path]
|
||||
from vllm_omni.entrypoints.utils import is_new_format_deploy_config
|
||||
|
||||
if is_new_format_deploy_config(stage_config_path):
|
||||
server_args += ["--deploy-config", stage_config_path]
|
||||
else:
|
||||
server_args += ["--stage-configs-path", stage_config_path]
|
||||
|
||||
with (
|
||||
OmniServer(
|
||||
|
||||
Reference in new issue
Block a user