diff --git a/tests/e2e/online_serving/stage_configs/step_audio2_ci.yaml b/tests/e2e/online_serving/stage_configs/step_audio2_ci.yaml deleted file mode 100644 index 703a7cdcc..000000000 --- a/tests/e2e/online_serving/stage_configs/step_audio2_ci.yaml +++ /dev/null @@ -1,81 +0,0 @@ -# Stage config for Step-Audio2 CI tests -# Stage 0: Thinker (audio understanding → text + audio tokens) -# Stage 1: Token2Wav (audio tokens → waveform) - -# The following config has been verified on 2x 24GB GPU (L4/RTX3090/RTX4090). -stage_args: - - stage_id: 0 - runtime: - process: true - devices: "0" - max_batch_size: 1 - engine_args: - model_stage: thinker - model_arch: StepAudio2ThinkerForConditionalGeneration - tensor_parallel_size: 1 - hf_overrides: - architectures: ["StepAudio2ThinkerForConditionalGeneration"] - - worker_cls: vllm_omni.worker.gpu_ar_worker.GPUARWorker - scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler - max_model_len: 1024 - max_num_batched_tokens: 1024 - max_num_seqs: 1 - gpu_memory_utilization: 0.7 - skip_mm_profiling: true - enforce_eager: true - trust_remote_code: true - enable_prefix_caching: false - engine_output_type: text # Output text + audio tokens - is_comprehension: true - final_output: true - final_output_type: text - default_sampling_params: - temperature: 0.7 - top_p: 0.9 - top_k: -1 - max_tokens: 256 - seed: 42 - detokenize: true - repetition_penalty: 1.05 - - - stage_id: 1 - runtime: - process: true - devices: "0" - max_batch_size: 1 - engine_args: - - model_stage: token2wav - model_arch: StepAudio2Token2WavModel - worker_cls: vllm_omni.worker.gpu_generation_worker.GPUGenerationWorker - scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler - hf_overrides: - architectures: ["StepAudio2Token2WavModel"] - gpu_memory_utilization: 0.2 - enforce_eager: true - trust_remote_code: true - enable_prefix_caching: false - max_num_batched_tokens: 1024 - engine_output_type: audio # Output waveform - engine_input_source: [0] - custom_process_input_func: vllm_omni.model_executor.stage_input_processors.step_audio2.thinker2token2wav - final_output: true - final_output_type: audio - default_sampling_params: - temperature: 0.0 - top_p: 1.0 - top_k: -1 - max_tokens: 1 - seed: 42 - detokenize: false - -runtime: - enabled: true - defaults: - window_size: -1 - max_inflight: 1 - edges: - - from: 0 - to: 1 - window_size: -1 diff --git a/tests/e2e/online_serving/test_step_audio2_expansion.py b/tests/e2e/online_serving/test_step_audio2_expansion.py index 05beb1131..9c8280b59 100644 --- a/tests/e2e/online_serving/test_step_audio2_expansion.py +++ b/tests/e2e/online_serving/test_step_audio2_expansion.py @@ -3,34 +3,32 @@ """ E2E expansion tests for Step-Audio2 online serving (weekly CI). -Tests speech-to-speech translation via /v1/chat/completions. +Tests audio-to-text generation via /v1/chat/completions. Request shape follows examples/online_serving/step_audio2/openai_chat_completion_client.py. """ import os -from pathlib import Path import pytest from tests.helpers.mark import hardware_test from tests.helpers.media import generate_synthetic_audio from tests.helpers.runtime import OmniServerParams +from tests.helpers.stage_config import get_deploy_config_path os.environ.setdefault("VLLM_WORKER_MULTIPROC_METHOD", "spawn") MODEL = "stepfun-ai/Step-Audio-2-mini" -STAGE_CONFIG = str(Path(__file__).parent / "stage_configs" / "step_audio2_ci.yaml") +DEPLOY_CONFIG = get_deploy_config_path("step_audio2_ci.yaml") SAMPLE_RATE = 16000 SEED = 42 -# pyttsx3 synthetic input phrase; Step-Audio2 text output includes codec tokens -# () so we validate audio only, not text↔audio similarity. SYNTH_PHRASE = "how are you" TEST_PARAMS = [ pytest.param( OmniServerParams( model=MODEL, - stage_config_path=STAGE_CONFIG, + server_args=["--deploy-config", DEPLOY_CONFIG, "--trust-remote-code"], env_dict={"VLLM_IMAGE_FETCH_TIMEOUT": "60"}, ), id="step_audio2", @@ -64,19 +62,11 @@ def _default_sampling_params_list() -> list[dict]: "detokenize": True, "repetition_penalty": 1.05, }, - { - "temperature": 0.0, - "top_p": 1.0, - "top_k": -1, - "max_tokens": 1, - "seed": SEED, - "detokenize": False, - }, ] -def _build_s2st_request_config(omni_server, audio_base64: str) -> dict: - """Build audio-to-audio (S2ST) chat completion request config.""" +def _build_audio_to_text_request_config(omni_server, audio_base64: str) -> dict: + """Build an audio-to-text chat completion request config.""" audio_url = f"data:audio/wav;base64,{audio_base64}" return { "model": omni_server.model, @@ -86,7 +76,7 @@ def _build_s2st_request_config(omni_server, audio_base64: str) -> dict: "content": [ { "type": "text", - "text": "You are an audio processing assistant. Listen and repeat the audio content.", + "text": "You are a speech recognition assistant. Transcribe the audio accurately.", } ], }, @@ -94,26 +84,19 @@ def _build_s2st_request_config(omni_server, audio_base64: str) -> dict: "role": "user", "content": [ {"type": "audio_url", "audio_url": {"url": audio_url}}, - {"type": "text", "text": "Please listen to this audio and repeat its content."}, + {"type": "text", "text": "Please transcribe the audio content."}, ], }, - {"role": "assistant", "content": ""}, ], - # Thinker text includes undetokenized audio codec placeholders; assert audio only. - "modalities": ["audio"], - "audio_ref_text": SYNTH_PHRASE, + "modalities": ["text"], "sampling_params_list": _default_sampling_params_list(), - "extra_body": { - "continue_final_message": True, - "add_generation_prompt": False, - }, } @hardware_test(res={"cuda": "H100"}, num_cards=1) -def test_single_s2st_request(omni_server, openai_client) -> None: - """Test single speech-to-speech request via chat completions API.""" - request_config = _build_s2st_request_config( +def test_single_audio_to_text_request(omni_server, openai_client) -> None: + """Test a single audio-to-text request via the chat completions API.""" + request_config = _build_audio_to_text_request_config( omni_server, _synthetic_audio_base64(duration_sec=2), ) @@ -122,9 +105,9 @@ def test_single_s2st_request(omni_server, openai_client) -> None: @hardware_test(res={"cuda": "H100"}, num_cards=1) @pytest.mark.parametrize("request_num", [2, 4]) -def test_concurrent_s2st_requests(omni_server, openai_client, request_num: int) -> None: - """Test concurrent speech-to-speech requests.""" - request_config = _build_s2st_request_config( +def test_concurrent_audio_to_text_requests(omni_server, openai_client, request_num: int) -> None: + """Test concurrent audio-to-text requests.""" + request_config = _build_audio_to_text_request_config( omni_server, _synthetic_audio_base64(duration_sec=2), ) diff --git a/tests/test_config_factory.py b/tests/test_config_factory.py index e08349140..c70b8c487 100644 --- a/tests/test_config_factory.py +++ b/tests/test_config_factory.py @@ -1174,6 +1174,7 @@ stages: ("step_audio_2.yaml", "step_audio_2", 2, "audio", None), ("step_audio_2_asr.yaml", "step_audio_2_asr", 1, "text", "step_audio_2_asr"), ("step_audio_2_async_chunk.yaml", "step_audio_2", 2, "audio", None), + ("step_audio2_ci.yaml", "step_audio_2_asr", 1, "text", "step_audio_2_asr"), ("hunyuan_video_15.yaml", "hunyuan_video_15", 1, "video", None), ("wan2_2_ti2v.yaml", "wan2_2_ti2v", 1, "video", None), ], @@ -1241,6 +1242,33 @@ stages: assert deploy.stages[0].tensor_parallel_size == 2 assert deploy.stages[1].devices == "1" + def test_step_audio2_online_ci_deploy_is_single_thinker(self): + deploy = load_deploy_config(Path(get_deploy_config_path("step_audio2_ci.yaml"))) + + assert deploy.pipeline == "step_audio_2_asr" + assert deploy.async_chunk is False + assert deploy.trust_remote_code is True + assert deploy.enable_prefix_caching is False + assert len(deploy.stages) == 1 + + stage = deploy.stages[0] + assert stage.devices == "0" + assert stage.tensor_parallel_size == 1 + assert stage.max_model_len == 1024 + assert stage.max_num_batched_tokens == 1024 + assert stage.max_num_seqs == 1 + assert stage.gpu_memory_utilization == 0.7 + assert stage.skip_mm_profiling is True + assert stage.enforce_eager is True + assert stage.async_scheduling is False + assert stage.engine_extras["hf_overrides"]["architectures"] == ["StepAudio2ThinkerForConditionalGeneration"] + + stages = merge_pipeline_deploy(OMNI_PIPELINES[deploy.pipeline], deploy) + assert len(stages) == 1 + assert stages[0].final_output is True + assert stages[0].final_output_type == "text" + assert stages[0].yaml_engine_args["engine_output_type"] == "text" + def test_step_audio2_dispatches_sync_and_async_chunk_processors(self): pipeline = resolve_pipeline_config("step_audio_2") assert isinstance(pipeline, PipelineConfig) diff --git a/vllm_omni/deploy/step_audio2_ci.yaml b/vllm_omni/deploy/step_audio2_ci.yaml new file mode 100644 index 000000000..4373dfaa8 --- /dev/null +++ b/vllm_omni/deploy/step_audio2_ci.yaml @@ -0,0 +1,31 @@ +# Step-Audio2 online CI deploy: thinker only (audio/text -> text). +# +# The two-stage topology can deadlock when an audio-to-text request skips the +# Token2Wav stage, so this config explicitly selects the ASR pipeline. +# Keeps the legacy 1x 24GB CI sizing; validated here on 1x H100-80GB. +pipeline: step_audio_2_asr +async_chunk: false +trust_remote_code: true +enable_prefix_caching: false + +stages: + - stage_id: 0 + devices: "0" + tensor_parallel_size: 1 + max_model_len: 1024 + max_num_batched_tokens: 1024 + max_num_seqs: 1 + gpu_memory_utilization: 0.7 + skip_mm_profiling: true + enforce_eager: true + async_scheduling: false + hf_overrides: + architectures: ["StepAudio2ThinkerForConditionalGeneration"] + default_sampling_params: + temperature: 0.7 + top_p: 0.9 + top_k: -1 + max_tokens: 256 + seed: 42 + detokenize: true + repetition_penalty: 1.05