Compare commits

..
Author SHA1 Message Date
Zhou Taichang 181eab754e Merge branch 'main' into dev/rebase-v0170 2026-03-10 09:50:31 +08:00
Taichang Zhou e942aa2640 revert changes for the docker file which will be updated after dockers are available
Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-03-09 23:52:36 +08:00
Taichang Zhou c406622577 update installation doc
Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-03-09 23:44:02 +08:00
Taichang Zhou b1073a6e7f Merge branch 'dev/rebase-v0170' of https://github.com/vllm-project/vllm-omni into dev/rebase-v0170 2026-03-09 23:40:54 +08:00
Taichang Zhou 0940c5a05e Merge remote-tracking branch 'remotes/origin/main' into dev/rebase-v0170
Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-03-09 23:40:32 +08:00
tzhouam a9b4367808 Update GPU memory utilization and max number of batched tokens in qwen3_omni.yaml to optimize resource allocation during model execution.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-03-09 08:22:26 +00:00
tzhouam 79626586e1 Update GPU memory utilization in qwen3_omni.yaml from 0.6 to 0.45 to prevent out-of-memory errors during model execution.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-03-09 08:02:25 +00:00
tzhouam e6d4cdd71a Refactor model mode initialization and enhance error handling in AsyncOmni. Update worker type retrieval in omni_stage and add audio token handling in Qwen3OmniMoe classes for improved robustness.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-03-09 06:17:08 +00:00
Hongsheng Liu 9cdbcec44b Merge branch 'main' into dev/rebase-v0170 2026-03-08 09:18:57 +08:00
Taichang Zhou c8b695f181 smaller talker gpu util to avoid oom
Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-03-08 01:48:42 +08:00
Taichang Zhou f405240513 Change pytest fixture scope from "module" to "class" for omni_server and omni_server_no_async_chunk in test_qwen3_tts.py to improve test isolation and resource management.
Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-03-07 14:50:59 +08:00
Taichang Zhou c630cf39c1 Enhance Dockerfile.ci by adding system dependencies installation and copying project files into the container. This improves the build process for the CI environment.
Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-03-07 14:02:48 +08:00
Taichang Zhou 3facc89dd1 Update base image in Dockerfile.rocm to v0.17.0 for improved compatibility and features.
Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-03-07 13:59:23 +08:00
Taichang Zhou 68bad871bc Fix model detection logic in is_diffusion_model function to ensure config_dict is checked for None before accessing its fields. This improves robustness against missing model_index.json files.
Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-03-07 13:57:44 +08:00
Taichang Zhou f945d2a30d Refactor Dockerfile.ci to streamline installation process and update vllm-omni installation method. Removed commented-out installation commands and ensured Python symlink is created.
Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-03-07 13:57:23 +08:00
Taichang Zhou 315f9401db Refactor imports in metrics.py and patch.py to use TokenizerLike from vllm.tokenizers instead of PreTrainedTokenizerBase from transformers.
Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-03-07 13:33:10 +08:00
Taichang Zhou fd4607f215 rebase 2026/03/07
Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-03-07 13:30:58 +08:00
Taichang Zhou 0ecfff0694 comment out extra installation
Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-03-07 13:29:12 +08:00
Taichang Zhou 4a43c6b017 Merge branch 'dev/rebase-v0170' of https://github.com/vllm-project/vllm-omni into dev/rebase-v0170 2026-03-07 13:26:46 +08:00
Taichang Zhou 0c2464db20 revert unnecessary changes
Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-03-07 13:26:32 +08:00
Zhou TaichangandCyrus Leung 5f1e41970c Update vllm_omni/benchmarks/patch/patch.py
Co-authored-by: Cyrus Leung <cyrus.tl.leung@gmail.com>
Signed-off-by: Zhou Taichang <tzhouam@connect.ust.hk>
2026-03-07 13:23:47 +08:00
Zhou TaichangandCyrus Leung 4ead1c8913 Update vllm_omni/benchmarks/metrics/metrics.py
Co-authored-by: Cyrus Leung <cyrus.tl.leung@gmail.com>
Signed-off-by: Zhou Taichang <tzhouam@connect.ust.hk>
2026-03-07 13:23:38 +08:00
Taichang Zhou 2187db0e36 Remove obsolete RFC documents for automated rebase agent, output processor refactor, model runner clearance, long function decomposition, and model runner V2 integration. These files are no longer relevant to the current project direction.
Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-03-07 13:22:39 +08:00
Taichang Zhou 5511bc14c7 Merge origin/main into dev/rebase-v0170
- Resolved conflict in vllm_omni/engine/output_processor.py: use tracing_enabled for vLLM v0.17.0 API
- Resolved conflict in vllm_omni/model_executor/models/qwen3_tts/qwen3_tts_code_predictor_vllm.py: keep correct _CodePredictorDecoderLayer.forward()
- Update Dockerfile.ci vLLM wheel commit to b31e9326a (releases/v0.17.0)

Made-with: Cursor

Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-03-07 13:18:33 +08:00
Taichang Zhou 3a6b2e27be [Update] Bump VLLM base tag to v0.17.0 in CI Dockerfile
Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-03-07 13:14:10 +08:00
tzhouam 8c41492307 Merge remote-tracking branch 'remotes/origin/main' into dev/rebase-v0169 2026-03-06 03:02:50 +00:00
tzhouam 44ac28c386 trigger RTD rebuild
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-03-06 03:02:07 +00:00
tzhouam 1ccffce2bc rebase to updated commit
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-03-06 02:32:26 +00:00
tzhouam 5be08baed3 Merge remote-tracking branch 'remotes/origin/main' into dev/rebase-v0170
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-03-05 08:53:26 +00:00
tzhouam 395eab8984 Merge remote-tracking branch 'remotes/origin/main' into dev/rebase-v0169 2026-03-05 02:29:14 +00:00
tzhouam 36a507e071 Merge remote-tracking branch 'remotes/origin/main' into dev/rebase-v0170
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-03-04 08:26:56 +00:00
tzhouam ae818f4b45 Fix Qwen3Omni VisionTransformer compatibility with updated vLLM Qwen2_5_VisionAttention
The upstream vLLM Qwen2_5_VisionAttention.forward() now requires a
`sequence_lengths` argument, but Qwen3_VisionBlock.forward() does not
pass it, causing a TypeError at init time.

Instead of patching the vLLM package directly, introduce a
Qwen3Omni_VisionTransformer subclass in vllm-omni that overrides
forward() to properly compute and propagate sequence_lengths via
MMEncoderAttention (following qwen3_vl.py's pattern). Both thinker
and talker now import the fixed subclass.

Also bump the vLLM wheel to e196028dbaa41b.

Made-with: Cursor

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-03-04 02:51:02 +00:00
tzhouam 4914466f0b Update Dockerfile.ci to install vLLM wheel version 3dd142adb7f7fa3b0b8345ee046ba36a12d9e530 and remove ExtractHiddenStatesProposer references from GPU model runners for cleaner code.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-03-03 15:09:26 +00:00
tzhouam 2398a7c1d7 Merge origin/main into dev/rebase-v0170
Resolve conflict in omni_generation_scheduler.py: keep both
num_cached_tokens tracking and additional_information caching.

Made-with: Cursor

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-03-03 14:31:05 +00:00
tzhouam 16824aa295 Add max_query_len parameter to _LocalPredictorKVCache for improved query handling
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-03-03 14:04:07 +00:00
tzhouam 2da9d28f1b Enhance reproducibility in Ulysses attention tests by replacing current_omni_platform.seed_everything with torch's manual_seed and adding support for CUDA devices. Introduce get_supported_tasks method in AsyncOmni class to dynamically determine available tasks based on stage capabilities.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-03-03 09:45:51 +00:00
tzhouam 4d8a76e721 Reintroduce installation of flashinfer-jit-cache in Dockerfile.ci to enhance dependency management for the CI environment, ensuring compatibility with the latest requirements.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-03-03 09:23:09 +00:00
tzhouam a17078974f Remove flashinfer-jit-cache installation from Dockerfile.ci to streamline dependency management and avoid potential conflicts.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-03-03 07:00:18 +00:00
tzhouam 8dcf1061f1 Update Dockerfile.ci to install additional dependencies including flashinfer-jit-cache, nvidia-cublas-cu12, and numpy, ensuring a more comprehensive environment setup for development.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-03-03 06:47:10 +00:00
tzhouam 98926707f5 rebase 2026/3/3
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-03-03 03:50:34 +00:00
tzhouam 3e539fcb6a Merge remote-tracking branch 'origin/main' into dev/vllm-align
Made-with: Cursor

# Conflicts:
#	vllm_omni/entrypoints/openai/serving_chat.py
#	vllm_omni/worker/gpu_model_runner.py

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-03-03 02:57:27 +00:00
tzhouam e0497550c4 Update Dockerfile.ci to install a specific version of flashinfer-cubin (0.6.4) directly, simplifying the installation process and ensuring compatibility with the current setup.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-03-03 02:51:35 +00:00
tzhouam 029bb56e3a Update Dockerfile.ci to use importlib.metadata for retrieving the current version of flashinfer, enhancing compatibility with the latest Python standards.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-03-03 02:28:24 +00:00
tzhouam 65169baa9d Update Dockerfile.ci to create a symbolic link for Python 3, ensuring compatibility with Python scripts during the build process.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-03-03 01:58:35 +00:00
tzhouam 908e3fe761 Update Dockerfile.ci to dynamically install the current version of flashinfer-cubin, ensuring compatibility with the latest changes in the flashinfer package.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-03-02 07:00:59 +00:00
tzhouam 8fb247a481 Update Dockerfile.ci to install specific version of flashinfer-cubin (0.6.4) and ensure vllm installation remains intact.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-03-02 06:48:58 +00:00
tzhouam be4226bae0 Remove unnecessary uninstallation of nvidia-cublas-cu12 and streamline vllm installation in Dockerfile.ci
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-03-02 06:38:24 +00:00
tzhouam 473d3ecd29 Update vLLM wheel version and adjust GPU memory utilization in CI configuration
- Updated the vLLM wheel commit hash in Dockerfile.ci to a60985b07eaf0fefa4c353ad2309b745707591ed.
- Modified the mock shared memory handle name in test_basic_connectors.py for clarity.
- Increased GPU memory utilization to 0.9 and adjusted max_num_batched_tokens to 4096 in qwen2_5_omni_ci.yaml to enable testing on H800.
- Refactored GPU model runner classes to remove deprecated activate_lora parameter and ensure compatibility with num_active_loras.
- Enhanced request handling in the OmniGPUModelRunner class to improve streaming request updates.

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-03-02 03:08:45 +00:00
tzhouam 26744bcb35 Merge origin/main into dev/vllm-align
Resolved conflicts:
- vllm_omni/inputs/preprocess.py: took origin/main's elif mm_processor_kwargs branch, fixed undefined mm_uuids reference
- vllm_omni/model_executor/models/qwen3_tts/qwen3_tts_code_predictor_vllm.py: took origin/main's NPU support

Made-with: Cursor

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-03-02 02:10:32 +00:00
tzhouam 8d0c46487e Merge origin/main into dev/vllm-align
Resolve conflicts:
- docker/Dockerfile.ci: Keep wheel install section, update hash to 6042e66cd5
- vllm_omni/platforms/npu/worker/npu_model_runner.py: Accept main's refactoring

Made-with: Cursor

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-26 08:19:53 +00:00
tzhouam a252918008 [Update] Upgrade vLLM wheel version and enhance metrics handling
- Updated the vLLM wheel to commit 24650715105a26b41c88f4164777b5e1a0f3200b in Dockerfile.ci.
- Modified the `calculate_metrics` function to handle `tokenizer` as optional (can be None) in metrics.py.
- Adjusted the `benchmark` function to accept `tokenizer` as optional in patch.py.
- Enhanced condition checks in OmniARScheduler to ensure valid token generation in omni_ar_scheduler.py.
- Updated input processing to include supported tasks in input_processor.py.
- Cleaned up unnecessary code in output_processor.py.
- Added support for SSL ciphers in the API server configuration in api_server.py.
- Enabled speculative decoding in GPU model runners for improved performance in gpu_generation_model_runner.py and gpu_model_runner.py.

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-25 10:02:23 +00:00
tzhouam 22e01c416b Merge remote-tracking branch 'origin/main' into dev/vllm-align 2026-02-25 08:15:55 +00:00
tzhouam 81b9e40bbf Enhance signature compatibility checks in OmniLLM tests
- Improved the test_overridden_method_signatures_compatible function to better handle missing parameters in method signatures.
- Added logic to account for VAR_KEYWORD parameters, ensuring more accurate detection of signature mismatches.

Signed-off-by: [Your Name] <your.email@example.com>

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-24 07:56:47 +00:00
tzhouam 04ccb062fc Add signature compatibility tests for OmniLLM class
- Introduced tests to ensure method signatures in OmniLLM match those of the base LLM class, preventing runtime TypeErrors due to parameter mismatches.
- Updated the _run_engine method in OmniLLM to include an optional output_type parameter.

Signed-off-by: [Your Name] <your.email@example.com>

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-24 06:58:39 +00:00
tzhouam 861698f88f Update Dockerfile for vllm wheel version and enhance SHM connector tests
- Updated Dockerfile to use a new vllm wheel version.
- Modified test_put_get_inline to clarify that it always uses SHM and updated metadata assertions.
- Added handling for speculative configuration in GPU model runners to delay clearing connector metadata.
- Refactored payload decoding in OmniGPUModelRunner for better clarity and maintainability.

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-24 05:45:48 +00:00
tzhouam e22ae62396 Merge remote-tracking branch 'origin/main' into dev/vllm-align 2026-02-24 03:23:20 +00:00
tzhouam 9070f15f52 debug qwen tts
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-24 03:17:11 +00:00
tzhouam bf69cd94bb Rebase 2026/02/23
Update Dockerfile for vllm wheel version and enhance input processing in Omni models. Added decoding for byte messages in async requests, included new metrics in benchmarks, and improved pause state handling in the scheduler. Refined input and output processing methods for better multimodal support and error handling.

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-23 03:36:01 +00:00
tzhouam 292da4b0d8 Merge remote-tracking branch 'origin/main' into dev/vllm-align 2026-02-23 02:09:09 +00:00
tzhouam 9869ea007e Update vLLM wheel version in Dockerfile and enhance input processing for omni models. Added functionality to reinject omni-specific fields into processed prompts and improved handling of multimodal audio extraction in serving_chat.py. Adjusted SharedMemoryConnector logic for payload size checks and refined input preprocessing methods for better compatibility with multimodal data.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-20 08:34:53 +00:00
tzhouam 663976b8ca Merge remote-tracking branch 'origin/main' into dev/vllm-align 2026-02-20 01:38:44 +00:00
tzhouam b867bacd24 rebase 2025/02/16
Qwen3 Omni offline/online passed

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-16 03:21:21 +00:00
tzhouam 93eeb1a4c8 Merge remote-tracking branch 'origin/main' into dev/rebase-v0.16.0 2026-02-15 01:24:23 +00:00
tzhouam 3051fb10b5 reverse change in SamplingParams
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-13 09:59:03 +00:00
tzhouam 15099119dd Merge remote-tracking branch 'remotes/origin/main' into dev/rebase-v0.16.0 2026-02-13 09:55:18 +00:00
tzhouam f24273dc03 Update vLLM version to 0.16.0 in quickstart and CUDA installation documentation, and adjust parameters in multimodal generation script for improved performance. Refactor request logging in serving_chat.py for better input tracking.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-13 09:50:11 +00:00
tzhouam f50cf07c74 Update Dockerfile.ci to allow the installation of 'pplx_kernels' to continue even if the compatible wheel is not found, enhancing build resilience.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-13 07:22:07 +00:00
tzhouam bc0a2598ed Update Dockerfile.ci to replace 'uv pip' with 'pip' for 'pplx_kernels' installation, ensuring consistency in package management across the CI environment.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-13 07:11:40 +00:00
tzhouam 963deaf5ad Refactor Dockerfile.ci to streamline package installations by using 'uv pip' consistently for both 'pplx_kernels' and VLLM dependencies, ensuring a more uniform CI environment setup.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-13 06:55:10 +00:00
tzhouam 3abec0173c Update Dockerfile.ci to force-reinstall the 'pplx_kernels' package, ensuring the latest version is used in the CI environment.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-13 06:41:41 +00:00
tzhouam 8a4eee9bed Enhance configuration and debugging: Added asyncio mode to pytest settings, increased timeout for Qwen3-TTS E2E test, and improved logging in GPU model runner for better traceability of input modalities. Updated Dockerfile.ci to streamline VLLM installation process.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-13 06:39:28 +00:00
tzhouam 31e9fdbb57 Refactor chat_utils to remove unused multi-modal classes and add async audio extraction from video. Update README to reflect new entry point for audio extraction. Enhance serving_chat to process audio from video URLs when specified.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-13 03:27:16 +00:00
tzhouam 39a045f439 debug Qwen 3 Omni cuda graph
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-13 02:57:05 +00:00
tzhouam 7e9cae56fc Refactor Dockerfile.ci to use precompiled VLLM installation and remove unused parse.py file. Update serving_chat.py to streamline rendering logic and enhance compatibility with new API. Modify OmniInputPreprocessor to directly handle SingletonDictPrompt, improving input processing efficiency.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-13 02:28:25 +00:00
tzhouam 31c5465e00 Update .gitignore to exclude .wav output files and remove audio_0.wav
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-13 01:52:06 +00:00
Hongsheng Liu 622c6f5f14 Merge branch 'main' into dev/rebase-v0.16.0 2026-02-13 06:05:57 +08:00
Taichang Zhou a6d633c319 debug
Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-02-12 23:46:20 +08:00
tzhouam ec3814f587 debug
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-12 14:20:57 +00:00
tzhouam 59a7602d6e debug
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-12 13:55:08 +00:00
Taichang Zhou 85a7b558ab rebase to v0.16.0
Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-02-12 21:30:54 +08:00
Taichang Zhou 3dc19bc305 0.16.0 docker not released yet
Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-02-12 21:25:53 +08:00
Taichang Zhou 7458ba44b7 merge main
Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-02-12 21:21:03 +08:00
Taichang ZhouandCursor e2ced80d59 Merge origin/main into dev/rebase-v0.16.0.
Resolve arg_utils merge conflict and align CI base image tag to v0.16.0 for the vLLM rebase track.

Co-authored-by: Cursor <cursoragent@cursor.com>

Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-02-12 21:18:03 +08:00
Taichang Zhou 1705edcce9 Refactor model path in test_qwen3_tts for improved clarity and maintainability
Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-02-12 21:12:53 +08:00
tzhouam f8959bcc6e Temp fix for metrics error in test_qwen3_tts
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-12 10:02:19 +00:00
tzhouam db40563b88 Update Dockerfile.ci to pin nvidia-cublas-cu12 and add numpy installation, ensuring proper dependency management for the build environment.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-12 09:52:51 +00:00
tzhouam 86e20769fd Add SSL context support in benchmark function for improved connection handling
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-12 08:52:27 +00:00
tzhouam 8c1df5c704 update flashinfer
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-12 08:52:06 +00:00
tzhouam 3e89c99001 Merge remote-tracking branch 'remotes/origin/main' into dev/vllm-align 2026-02-12 07:16:25 +00:00
tzhouam c751f0d684 Update Dockerfile.ci to install a new version of vllm from a different extra index URL. Refactor import statement in serving_chat.py to use the correct BaseRenderer module path.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-12 06:37:17 +00:00
tzhouamandCursor 50f2b1e854 Merge origin/main into dev/vllm-align
Co-authored-by: Cursor <cursoragent@cursor.com>

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-12 03:41:38 +00:00
tzhouam 0099eebb3f Refactor OmniEngineArgs and AsyncOmniEngineArgs to improve compatibility with partial payloads by using getattr for various attributes. Update GPUGenerationModelRunner and OmniGPUModelRunner to replace the activate_lora parameter with num_active_loras for better clarity and backward compatibility. This change enhances the handling of LoRA activation during model runs.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-12 03:33:50 +00:00
Taichang Zhou cd2ffbf2a0 update dockerfile.ci
Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-02-11 23:39:15 +08:00
Taichang Zhou 9fbffc6137 Update requirements/cuda.txt to clarify the pinning of nvidia-cublas-cu12 version 12.9.1.4 due to dependency resolution issues with torch. This comment aids in understanding the force-reinstall strategy in Dockerfile.ci.
Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-02-11 22:44:11 +08:00
Taichang Zhou 514bd46f2e Uninstall old nvidia-cublas-cu12 version before reinstalling the pinned version to ensure consistent dependency management in Dockerfile.ci.
Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-02-11 22:34:05 +08:00
Taichang Zhou 169d94dab9 Refactor Dockerfile.ci to use bash for Python executable resolution during vllm-omni installation and pin nvidia-cublas-cu12 version to 12.9.1.4 for consistent dependency management.
Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-02-11 21:31:09 +08:00
Taichang Zhou 268a58e4e8 Update pyproject.toml to remove redundant transformers package installation and adjust Dockerfile.ci to install the current package in editable mode. Also, update fa3-fwd package version in requirements/cuda.txt to 0.0.2 for consistency.
Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-02-11 21:18:53 +08:00
Taichang Zhou eb05967451 Pin nvidia-cublas-cu12 to version 12.9.1.4 in Dockerfile.ci to ensure consistent dependency management in the CI environment.
Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-02-11 20:31:57 +08:00
tzhouam 05784d3dd0 Update pyproject.toml to include transformers package and remove redundant installation step in Dockerfile.ci for cleaner CI setup.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-11 06:27:51 +00:00
tzhouam b7f297c8c3 Add transformers package installation in Dockerfile.ci to ensure compatibility with the CI environment.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-11 06:15:00 +00:00
tzhouam 379f61d038 Enhance Dockerfile.ci by adding a debug step to print key dependency versions for CI troubleshooting, while commenting out the flashinfer version sync logic for clarity.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-11 06:01:01 +00:00
tzhouam 31b6584373 Refine flashinfer version retrieval in Dockerfile.ci to enhance reliability and maintain consistency in package installations
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-11 04:56:50 +00:00
tzhouam 50cea0b29c Refactor flashinfer version retrieval in Dockerfile.ci for improved accuracy and consistency in package installations
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-11 04:34:52 +00:00
tzhouam e897640b43 Update Dockerfile.ci to enhance flashinfer version retrieval logic and ensure package installation consistency
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-11 04:11:11 +00:00
tzhouam 3663e803d1 rebase 2026/02/11
vllm: 4a1550d22d7058e129d0e1257e726b3bf4a77025

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-11 02:43:49 +00:00
tzhouam 279b6b5050 Merge remote-tracking branch 'origin/main' into dev/vllm-align 2026-02-11 01:44:54 +00:00
tzhouam af53491cac Update sampling parameters in openai_chat_completion_client_for_multimodal_generation.py for improved model performance, adjusting temperature, top_k, and max_tokens settings.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-10 09:54:32 +00:00
tzhouam c3d35ed5fe pass local t2i test
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-10 06:20:26 +00:00
tzhouam 3c5b53358c Update Dockerfile.ci to ensure consistent formatting of flashinfer version extraction commands for improved readability and maintainability.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-10 03:53:55 +00:00
tzhouam 58d09ed656 reabse 2026/02/10
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-10 03:36:09 +00:00
tzhouam 064595a97c Merge remote-tracking branch 'origin/main' into dev/vllm-align 2026-02-10 02:22:49 +00:00
tzhouam e0f540fe98 Enhance Dockerfile.ci to synchronize flashinfer runtime package versions with installed flashinfer-python, ensuring compatibility across components. Additionally, improve error handling in ring_globals.py for flashinfer import failures, logging warnings for fallback scenarios.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-10 02:17:00 +00:00
tzhouam 670240ccd4 Enhance Dockerfile.ci to allow pre-release vLLM installations by adding --prerelease=allow flag for compatibility with nightly/commit wheels.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-09 10:01:39 +00:00
tzhouam c9fb2dc7c7 Update vLLM commit reference in Dockerfile.ci to f97ca671766c5201404e9fc812e35bf2c4e95a01
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-09 08:09:21 +00:00
tzhouam 7e4a604d4d reabse 2025/2/9
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-09 03:07:43 +00:00
tzhouamandCursor 03c6a58f69 Merge origin/main into dev/vllm-align; resolve omni_stage.py conflicts
Co-authored-by: Cursor <cursoragent@cursor.com>

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-09 01:52:31 +00:00
tzhouam 7f54fd2f54 Merge remote-tracking branch 'origin/main' into dev/vllm-align 2026-02-06 02:26:06 +00:00
tzhouam 41e22e2770 [Refactor] Clean up code by removing unnecessary lines and adding missing imports in async_omni_llm.py, omni_stage.py, omni.py, and api_server.py
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-05 11:02:03 +00:00
tzhouam 24b04f58a0 rebase 2026/2/5 2026-02-05 10:52:28 +00:00
tzhouam 0144a424ed Merge remote-tracking branch 'origin/main' into dev/vllm-align 2026-02-05 01:44:34 +00:00
tzhouam 560a5aa57c Merge branch 'dev/vllm-align' of https://github.com/vllm-project/vllm-omni into dev/vllm-align 2026-02-03 05:59:37 +00:00
tzhouam 02c6062908 Merge branch 'main' into dev/vllm-align
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-03 05:57:38 +00:00
Taichang Zhou 87c7b7e519 Refactor comments and formatting in Qwen3 TTS configuration and modeling files for improved readability.
Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-02-02 21:52:10 +08:00
Taichang Zhou 22ca74432b Merge branch 'main' into dev/vllm-align 2026-02-02 21:35:45 +08:00
Taichang Zhou 354777d5e7 Merge branch 'main' of https://github.com/vllm-project/vllm-omni 2026-02-02 21:34:42 +08:00
汪志鹏 b6c846cc95 Add stage_init_timeout=300 (#1153)
Signed-off-by: princepride <wangzhipeng628@gmail.com>
2026-02-02 17:58:48 +08:00
tzhouam 9194c1dd84 Update base image in Dockerfile.rocm from v0.14.0 to v0.15.0
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-02 08:41:35 +00:00
tzhouam ce9105267f Enhance error handling in TTS generation and improve audio output compatibility by supporting both "audio" and "model_outputs" keys in response.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-02 08:40:56 +00:00
tzhouam 39f7cade27 Fix audio sample rate access in end2end.py for correct output retrieval
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-02 08:38:24 +00:00
tzhouam 08fa85a1ad Update audio tensor access in offline inference tests and increase image fetch timeout for CI
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-02 06:10:54 +00:00
tzhouam 6568a78a26 Remove unused tokenizer argument from OmniInputProcessor class docstring
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-02 03:12:43 +00:00
tzhouam ff8b3dbbc8 update qwen 2.5 Omni
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-02 03:01:11 +00:00
tzhouam 1297a46040 Merge remote-tracking branch 'origin/main' into dev/vllm-align
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-02-02 01:54:12 +00:00
Taichang Zhou e033498600 Merge branch 'main' of https://github.com/vllm-project/vllm-omni 2026-01-31 10:59:36 +08:00
tzhouam 7aea8e8d91 Add OmniMRotaryEmbedding for multimodal position computation
- Introduced OmniMRotaryEmbedding to extend MRotaryEmbedding with methods for computing input positions across various multimodal scenarios, including image, video, and audio inputs.
- Updated patch.py to replace the original MRotaryEmbedding with OmniMRotaryEmbedding in the module patching logic.
- Created new files for the OmniMRotaryEmbedding implementation and its associated methods.

Signed-off-by: [Your Name] <[Your Email]>

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-01-30 10:03:02 +00:00
tzhouam 3208afd82b Refactor OmniRequestOutput to encapsulate multimodal output
- Changed the multimodal_output field to a private attribute _multimodal_output for better encapsulation.
- Added a property method to access multimodal_output, allowing for a clearer interface and improved handling of request outputs.

Signed-off-by: [Your Name] <[Your Email]>

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-01-30 10:02:22 +00:00
tzhouam 2b009b3cef Fix audio tensor extraction in end2end.py files across multiple examples
- Updated the audio tensor extraction logic to access the correct output structure in Qwen2.5 Omni, Qwen3 Omni, and Qwen3 TTS implementations.
- Ensured consistency in handling audio outputs for improved functionality.

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-01-30 10:00:14 +00:00
tzhouam 4faf190e28 Merge remote-tracking branch 'origin/main' into dev/vllm-align 2026-01-30 03:49:04 +00:00
tzhouam ca57f510c7 Refactor OmniInputPreprocessor to remove unused embed processing logic
- Eliminated the _process_embeds method and related imports from preprocess.py, streamlining the input preprocessing workflow.
- Updated the main processing logic to focus solely on token inputs, enhancing clarity and maintainability.

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-01-30 03:02:38 +00:00
tzhouam 4b895459e4 Merge branch 'main' into dev/vllm-align
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-01-30 02:53:03 +00:00
tzhouam 62dbf07fb6 Refactor OpenAI serving modules for improved functionality and organization
- Reintroduced the load_chat_template import in api_server.py for better template handling.
- Updated the logic in serving_chat.py to streamline the resolution of chat template content formats, enhancing clarity and functionality.
- Adjusted the application of chat templates to ensure proper handling of tools and chat templates.

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-01-30 02:44:13 +00:00
tzhouam 104e105c93 Refactor multimodal processing imports and update configuration handling in Qwen3OmniMoeThinker
- Changed the import path for multimodal processing to improve module organization.
- Removed the unused multimodal configuration parameter from the Qwen3OmniMoeThinker initialization, streamlining the class definition.

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-01-30 02:43:41 +00:00
tzhouam ef21879c3a Refactor GPU model runner to streamline multimodal configuration handling
- Removed the unused import of supports_mm_encoder_only from interfaces.
- Updated the dummy run check to utilize the multimodal configuration directly, enhancing clarity and future extensibility.

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-01-30 02:43:26 +00:00
tzhouam 45880a30f0 Refactor chat_utils and OpenAI modules for improved functionality and organization
- Updated import statements in chat_utils.py to replace the internal content format type with the public one for better clarity.
- Refactored api_server.py to streamline the chat template loading process by replacing the previous method with a direct function call.
- Enhanced serving_chat.py by reintroducing safe_apply_chat_template functions for better template handling.

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-01-29 09:00:43 +00:00
tzhouam 2ac33489b5 Update Dockerfile.ci to install vllm package using the correct URL format
- Modified the pip install command for vllm to use the correct URL format for direct installation from the specified commit hash, ensuring a more reliable installation process.
- Removed the fallback option for pre-release installation to streamline the installation command.

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-01-29 08:25:51 +00:00
tzhouam 63fd163efb Refactor import statement in OpenAI API server for improved module organization
- Updated the import path for AnthropicServingMessages in api_server.py to enhance clarity and maintain consistency with the new module structure.

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-01-29 08:23:17 +00:00
tzhouam fea328364a Update Dockerfile.ci to install vllm package directly from wheel file
- Changed the pip install command for vllm to download the wheel file directly instead of using an extra index URL, ensuring a more reliable installation process.
- Added a fallback option to install the pre-release version of vllm if the direct installation fails.

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-01-29 08:05:57 +00:00
tzhouam 478c036a01 [Refactor] Update OpenAI API server and related modules for improved structure and functionality
- Refactored import statements in api_server.py to enhance module organization, including changes to the routing and server setup.
- Updated the method for removing routes to ensure compatibility with the new app structure.
- Adjusted the handling of chat completion requests to improve error responses and streamline processing.
- Modified serving modules to align with the new import paths and enhance code clarity.

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-01-29 15:28:51 +08:00
tzhouam 8407818368 Update simple_test.sh to force reinstall vllm package with extra index URL
- Modified the pip install command for vllm to include --upgrade, --force-reinstall flags, and an extra index URL, ensuring the latest version is installed from the specified source during testing.

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-01-28 17:34:22 +08:00
tzhouam 6e7cb01afe Update Dockerfile.ci to force reinstall vllm package during CI build
- Modified the pip install command for vllm to include --upgrade and --force-reinstall flags, ensuring the latest version is installed during the CI process.

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-01-28 17:20:29 +08:00
tzhouam acb28f3d1d Remove commented-out import and related code for MRotaryEmbedding in patch.py to clean up the codebase.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-01-28 16:52:10 +08:00
tzhouam bf51318176 debug
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-01-28 16:40:06 +08:00
tzhouam bd8c0443cf build image from commit id
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-01-28 16:21:29 +08:00
tzhouam 554d14fd28 debug docker file
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-01-28 15:57:13 +08:00
tzhouam 49b9d0d6b1 debug docker build
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-01-28 15:47:30 +08:00
tzhouam 2c2d06d920 [Enhancement] Update GPUARWorker and GPUGenerationWorker to enforce v1 model runner
- Added a warning in GPUARWorker and GPUGenerationWorker to indicate that the v2 model runner does not include omni hooks, forcing the use of the v1 model runner.
- Updated import statements in gpu_ar_model_runner.py and gpu_generation_model_runner.py to include necessary functions from parallel_state.
- Removed unused imports from both model runner files to streamline the code.

Signed-off-by: [Your Name] <your.email@example.com>

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-01-28 15:43:27 +08:00
tzhouam 31d127edcd [Refactor] Remove unused MRotaryEmbedding and vision position ID functions
- Deleted the MRotaryEmbedding class and its associated methods from mrope.py to streamline the codebase.
- Removed the get_llm_pos_ids_for_vision function from vision.py as it is no longer needed.
- Updated import statements in qwen2_5_omni.py to reflect the removal of the vision module.

Signed-off-by: [Your Name] <your.email@example.com>

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-01-28 15:42:35 +08:00
tzhouam 52d0122fb5 [Enhancement] Refactor OmniInputProcessor and related classes for improved prompt handling
- Updated OmniInputProcessor to streamline input processing, including enhanced handling of prompt embeddings and additional information.
- Introduced OmniTextPrompt and OmniEmbedsInputs to support new input structures with optional metadata.
- Modified input preprocessing methods to accommodate new prompt types and ensure compatibility with multimodal inputs.
- Improved error handling and type checking for data parallel configurations.

Signed-off-by: [Your Name] <your.email@example.com>

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-01-28 15:42:05 +08:00
tzhouam b26a9735e6 [Enhancement] Update type hint for stage_connector_spec in OmniEngineArgs and AsyncOmniEngineArgs
- Changed type hint for stage_connector_spec from 'any' to 'Any' for improved type checking and consistency.
- Ensured compatibility with typing standards in the arg_utils module.

Signed-off-by: [Your Name] <your.email@example.com>

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-01-28 15:41:20 +08:00
tzhouam d7cfe195ea [Enhancement] Refactor OmniARScheduler and OmniGenerationScheduler to include OmniSchedulerOutput
- Updated OmniARScheduler to wrap output in OmniSchedulerOutput, including transfer metadata for finished requests needing KV cache transfer.
- Enhanced OmniGenerationScheduler to handle routed experts and finish reasons more effectively, ensuring proper output structure.
- Introduced performance statistics handling in both schedulers for improved monitoring.

Signed-off-by: [Your Name] <your.email@example.com>

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-01-28 15:40:59 +08:00
tzhouam e1c6639f18 [Enhancement] Add mm_encoder_only parameter and deprecation warning for convert option
- Introduced mm_encoder_only parameter to OmniModelConfig.
- Added a deprecation warning for the `--convert mm_encoder_only` option, advising users to switch to `--mm-encoder-only`.

Signed-off-by: [Your Name] <your.email@example.com>

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-01-28 15:40:29 +08:00
tzhouam ce7430b562 [Enhancement] Update OmniRequest to support torch.Tensor for prompt_embeds and add decoding method
- Modified the constructor to accept torch.Tensor as an option for prompt_embeds.
- Introduced a new static method _maybe_decode_prompt_embeds to handle the conversion from PromptEmbedsPayload to torch.Tensor.
- Preserved the original prompt_embeds payload for reference.

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-01-28 15:40:17 +08:00
tzhouam ed8b03224b update the docker setup to vllm nightly
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-01-28 11:15:54 +08:00
Taichang Zhou af8501dcc7 debug Qwen TTS
Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
2026-01-22 22:46:06 +08:00
28 changed files with 1878 additions and 91 deletions

No files matched your search

+2 -2
View File
@@ -67,8 +67,8 @@ Through five levels (L1-L5) and common (Common) specifications, the system clari
<td>Online & Offline (multiple deployment scenarios):<br>real model, normal inference function, normal accuracy</td>
<td>&lt;30min</td>
<td>
/tests/e2e/online_serving/test_{model_name}.py<br>
/tests/e2e/offline_inference/test_{model_name}.py
/tests/e2e/online_serving/test_{model_name}_expansion.py<br>
/tests/e2e/offline_inference/test_{model_name}_expansion.py
</td>
<td>
<a href="#chapter-2-l3-level-testing-core-integration-performance-and-accuracy-verification">Chapter 2</a><br>
+338
View File
@@ -0,0 +1,338 @@
# Audio Generate API
vLLM-Omni provides an API for text-to-audio generation using diffusion-based models such as Stable Audio.
Unlike the [Speech API](speech_api.md) which targets text-to-speech synthesis, the Audio Generate API is designed for general-purpose audio generation from text descriptions (sound effects, music, ambient soundscapes, etc.).
Each server instance runs a single model (specified at startup via `vllm-omni serve <model> --omni`).
## Quick Start
### Start the Server
```bash
vllm-omni serve stabilityai/stable-audio-open-1.0 \
--host 0.0.0.0 \
--port 8000 \
--gpu-memory-utilization 0.9 \
--trust-remote-code \
--enforce-eager \
--omni
```
### Generate Audio
**Using curl:**
```bash
curl -X POST http://localhost:8000/v1/audio/generate \
-H "Content-Type: application/json" \
-d '{
"input": "The sound of a cat purring",
"audio_length": 10.0
}' --output cat.wav
```
**Using Python:**
```python
import httpx
response = httpx.post(
"http://localhost:8000/v1/audio/generate",
json={
"input": "The sound of a cat purring",
"audio_length": 10.0,
},
timeout=300.0,
)
with open("cat.wav", "wb") as f:
f.write(response.content)
```
## API Reference
### Endpoint
```
POST /v1/audio/generate
Content-Type: application/json
```
### Request Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `input` | string | **required** | Text prompt describing the audio to generate |
| `model` | string | server's model | Model to use (optional, should match server if specified) |
| `response_format` | string | "wav" | Audio format: wav, mp3, flac, pcm, aac, opus |
| `speed` | float | 1.0 | Playback speed (0.25 - 4.0) |
#### Diffusion Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `audio_length` | float | null | Audio duration in seconds (default value is the max ~47s for `stable-audio-open-1.0`) |
| `audio_start` | float | 0.0 | Audio start time in seconds |
| `negative_prompt` | string | null | Text describing what to avoid in generation |
| `guidance_scale` | float | model default | Classifier-free guidance scale (higher = more adherence to prompt) |
| `num_inference_steps` | int | model default | Number of denoising steps (higher = better quality, slower) |
| `seed` | int | null | Random seed for reproducible generation |
### Response Format
Returns binary audio data with the appropriate `Content-Type` header:
| `response_format` | Content-Type |
|--------------------|--------------|
| `wav` | `audio/wav` |
| `mp3` | `audio/mpeg` |
| `flac` | `audio/flac` |
| `pcm` | `audio/pcm` |
| `aac` | `audio/aac` |
| `opus` | `audio/opus` |
## Examples
### Basic Generation
Generate audio with only a text prompt (model defaults for all other parameters):
```bash
curl -X POST http://localhost:8000/v1/audio/generate \
-H "Content-Type: application/json" \
-d '{
"input": "The sound of ocean waves crashing on a beach"
}' --output ocean.wav
```
### Custom Duration
Specify an explicit audio length in seconds:
```bash
curl -X POST http://localhost:8000/v1/audio/generate \
-H "Content-Type: application/json" \
-d '{
"input": "A dog barking",
"audio_length": 5.0
}' --output dog_5s.wav
```
### High Quality with Negative Prompt
Use a negative prompt to steer generation away from undesired characteristics, and increase inference steps for higher quality:
```bash
curl -X POST http://localhost:8000/v1/audio/generate \
-H "Content-Type: application/json" \
-d '{
"input": "A piano playing a gentle melody",
"audio_length": 10.0,
"negative_prompt": "Low quality, distorted, noisy",
"guidance_scale": 8.0,
"num_inference_steps": 150
}' --output piano_hq.wav
```
### Reproducible Generation
Set a `seed` to get deterministic results across runs:
```bash
curl -X POST http://localhost:8000/v1/audio/generate \
-H "Content-Type: application/json" \
-d '{
"input": "Thunder and rain sounds",
"audio_length": 15.0,
"seed": 42
}' --output thunder.wav
```
### Full Control
Combine all parameters for precise control over generation:
```bash
curl -X POST http://localhost:8000/v1/audio/generate \
-H "Content-Type: application/json" \
-d '{
"input": "Thunder and rain sounds",
"audio_length": 15.0,
"negative_prompt": "Low quality",
"guidance_scale": 7.0,
"num_inference_steps": 100,
"seed": 42
}' --output thunder_rain.wav
```
### Quick Generation (Fewer Steps)
For faster generation with slightly lower quality:
```bash
curl -X POST http://localhost:8000/v1/audio/generate \
-H "Content-Type: application/json" \
-d '{
"input": "Birds chirping in a forest",
"audio_length": 8.0,
"num_inference_steps": 50
}' --output birds_quick.wav
```
### Python Client
```python
import httpx
response = httpx.post(
"http://localhost:8000/v1/audio/generate",
json={
"input": "Thunder and rain",
"audio_length": 15.0,
"negative_prompt": "Low quality",
"guidance_scale": 7.0,
"num_inference_steps": 100,
"seed": 42,
"response_format": "wav",
},
timeout=300.0,
)
with open("thunder.wav", "wb") as f:
f.write(response.content)
```
## Parameter Tuning Guide
### `guidance_scale`
Controls how closely the generated audio follows the text prompt.
| Range | Behaviour |
|-------|-----------|
| 3 - 5 | More creative / varied output |
| 7 (default) | Balanced adherence |
| 10+ | Strict adherence to the prompt |
### `num_inference_steps`
Controls the number of denoising steps in the diffusion process.
| Steps | Quality | Speed | Use Case |
|-------|---------|-------|----------|
| 50 | Good | Fast | Quick previews |
| 100 | Very Good | Medium | General purpose |
| 150+ | Excellent | Slow | Final / critical audio |
### `audio_length`
Duration of the generated audio clip. For `stable-audio-open-1.0`, the maximum is approximately 47 seconds. If omitted, the model uses its own default length.
### `negative_prompt`
Describes characteristics to avoid. Common negative prompts include:
- `"Low quality, distorted, noisy"`
- `"Silence, static"`
- `"Music"` (when generating sound effects only)
## Supported Models
| Model | Description |
|-------|-------------|
| `stabilityai/stable-audio-open-1.0` | Open-source audio generation model, up to ~47 seconds, 44.1 kHz stereo |
## Error Responses
### 400 Bad Request
Invalid or missing parameters:
```json
{
"error": {
"message": "Audio generation model did not produce audio output.",
"type": "BadRequestError",
"param": null,
"code": 400
}
}
```
### 404 Not Found
Model mismatch:
```json
{
"error": {
"message": "The model `xxx` does not exist.",
"type": "NotFoundError",
"param": "model",
"code": 404
}
}
```
### 422 Unprocessable Entity
Pydantic validation failure (e.g. invalid `response_format`, `speed` out of range):
```json
{
"detail": [
{
"type": "literal_error",
"msg": "Input should be 'wav', 'pcm', 'flac', 'mp3', 'aac' or 'opus'",
...
}
]
}
```
## Troubleshooting
### "Audio generation model did not produce audio output"
The model finished but returned no audio data. Verify the server started successfully and the model loaded without errors.
### Server Not Responding
```bash
# Check if the server is healthy
curl http://localhost:8000/health
```
### Audio Quality Issues
- Increase `num_inference_steps` (e.g. 150).
- Add a negative prompt: `"Low quality, distorted, noisy"`.
- Increase `guidance_scale` for stronger prompt adherence.
### Generation Timeout
- Reduce `num_inference_steps`.
- Reduce `audio_length`.
- Check GPU memory with `nvidia-smi`.
### Out of Memory
- Lower `--gpu-memory-utilization` (e.g. 0.8).
- Reduce `audio_length`.
## Development
Enable debug logging:
```bash
vllm-omni serve stabilityai/stable-audio-open-1.0 \
--host 0.0.0.0 \
--port 8000 \
--gpu-memory-utilization 0.9 \
--trust-remote-code \
--enforce-eager \
--omni \
--uvicorn-log-level debug
```
@@ -0,0 +1,193 @@
# Text-To-Audio
Source <https://github.com/vllm-project/vllm-omni/tree/main/examples/online_serving/stable_audio>.
This example demonstrates how to deploy Stable Audio models for online text-to-audio generation using vLLM-Omni.
## Supported Models
| Model | Description |
|-------|-------------|
| `stabilityai/stable-audio-open-1.0` | Open-source audio generation, up to ~47 seconds, 44.1 kHz stereo |
## Start Server
### Basic Start
```bash
vllm-omni serve stabilityai/stable-audio-open-1.0 \
--host 0.0.0.0 \
--port 8000 \
--gpu-memory-utilization 0.9 \
--trust-remote-code \
--enforce-eager \
--omni
```
## API Calls
### Method 1: Using curl
```bash
# Run all curl examples
bash curl_examples.sh
# Or execute directly
curl -X POST http://localhost:8000/v1/audio/generate \
-H "Content-Type: application/json" \
-d '{
"input": "The sound of a cat purring",
"audio_length": 10.0
}' --output cat.wav
```
### Method 2: Using Python Client
```bash
cd examples/online_serving/stable_audio
# Simple generation
python stable_audio_client.py \
--text "The sound of a cat purring"
# With custom duration
python stable_audio_client.py \
--text "A dog barking" \
--audio_length 5.0
# With all parameters
python stable_audio_client.py \
--text "Thunder and rain" \
--audio_length 15.0 \
--negative_prompt "Low quality" \
--guidance_scale 7.0 \
--num_inference_steps 100 \
--seed 42 \
--output thunder.wav
```
The Python client supports the following command-line arguments:
- `--api_url`: API endpoint URL (default: `http://localhost:8000/v1/audio/generate`)
- `--text`: Text prompt for audio generation (default: `"The sound of a cat purring"`)
- `--audio_length`: Audio length in seconds (default: `10.0`, max ~47s for `stable-audio-open-1.0`)
- `--audio_start`: Audio start time in seconds (default: `0.0`)
- `--negative_prompt`: Negative prompt for classifier-free guidance (default: `"Low quality"`)
- `--guidance_scale`: Guidance scale for diffusion (default: `7.0`)
- `--num_inference_steps`: Number of inference steps (default: `100`)
- `--seed`: Random seed for reproducibility (default: `None`)
- `--response_format`: Audio output format (default: `wav`). Options: `wav`, `mp3`, `flac`, `pcm`
- `--output`: Output file path (default: `stable_audio_output.wav`)
### Method 3: Using Python httpx
```python
import httpx
response = httpx.post(
"http://localhost:8000/v1/audio/generate",
json={
"input": "The sound of ocean waves crashing on a beach",
"audio_length": 10.0,
"negative_prompt": "Low quality, distorted",
"guidance_scale": 7.0,
"num_inference_steps": 100,
},
timeout=300.0,
)
with open("ocean.wav", "wb") as f:
f.write(response.content)
```
## Request Format
### Simple Generation
```json
{
"input": "The sound of ocean waves"
}
```
### Generation with Parameters
```json
{
"input": "A piano playing a gentle melody",
"audio_length": 10.0,
"negative_prompt": "Low quality, distorted, noisy",
"guidance_scale": 8.0,
"num_inference_steps": 150,
"seed": 42,
"response_format": "wav"
}
```
## API Reference
### Endpoint
```
POST /v1/audio/generate
Content-Type: application/json
```
### Generation Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `input` | string | **required** | Text prompt describing the audio to generate |
| `model` | string | server's model | Model to use (optional, should match server if specified) |
| `response_format` | string | "wav" | Audio format: wav, mp3, flac, pcm, aac, opus |
| `speed` | float | 1.0 | Playback speed (0.25 - 4.0) |
| `audio_length` | float | null | Audio duration in seconds (max ~47s for `stable-audio-open-1.0`) |
| `audio_start` | float | 0.0 | Audio start time in seconds |
| `negative_prompt` | string | null | Text describing what to avoid in generation |
| `guidance_scale` | float | model default | Classifier-free guidance scale (higher = more adherence to prompt) |
| `num_inference_steps` | int | model default | Number of denoising steps (higher = better quality, slower) |
| `seed` | int | null | Random seed for reproducible generation |
### Response Format
Returns binary audio data with appropriate `Content-Type` header (e.g., `audio/wav`).
## Tuning Tips
1. **Audio Length**: Keep under 47 seconds for `stable-audio-open-1.0`.
2. **Quality vs Speed**:
- 50 steps: Fast, decent quality (quick previews)
- 100 steps: Good balance (general purpose)
- 150+ steps: High quality, slower (final / critical audio)
3. **Guidance Scale**:
- Lower (3 - 5): More creative / varied output
- Default (7): Good balance
- Higher (10+): Strict adherence to the prompt
4. **Negative Prompts**: Use to avoid unwanted characteristics such as `"Low quality"`, `"distorted"`, `"noisy"`.
5. **Seeds**: Set a fixed seed to get deterministic, reproducible results.
## File Description
| File | Description |
|------|-------------|
| `curl_examples.sh` | Curl examples covering common use cases |
| `stable_audio_client.py` | Python client with full CLI argument support |
## Troubleshooting
1. **Audio generation model did not produce audio output**: Verify the server started successfully and the model loaded without errors.
2. **Connection refused**: Make sure the server is running on the correct port.
3. **Generation timeout**: Reduce `num_inference_steps` or `audio_length`, and check GPU memory with `nvidia-smi`.
4. **Out of memory**: Lower `--gpu-memory-utilization` or reduce `audio_length`.
5. **Audio quality issues**: Increase `num_inference_steps`, add a negative prompt, or raise `guidance_scale`.
## Example materials
??? abstract "stable_audio_client.py"
``````py
--8<-- "examples/online_serving/stable_audio/stable_audio_client.py"
``````
??? abstract "curl_examples.sh"
``````sh
--8<-- "examples/online_serving/stable_audio/curl_examples.sh"
``````
@@ -0,0 +1,234 @@
# Stable Audio Online Serving
Generate audio from text prompts using Stable Audio models via an OpenAI-compatible API endpoint.
## Features
- **OpenAI-compatible API**: Use `/v1/audio/generate` endpoint
- **Flexible control**: Adjust audio length, guidance scale, inference steps
- **Quality control**: Use negative prompts to avoid unwanted characteristics
- **Reproducible**: Set random seed for deterministic generation
## Quick Start
### 1. Start the Server
```bash
vllm-omni serve stabilityai/stable-audio-open-1.0 \
--host 0.0.0.0 \
--port 8000 \
--gpu-memory-utilization 0.9 \
--trust-remote-code \
--enforce-eager \
--omni
```
### 2. Generate Audio
#### Using curl
```bash
curl -X POST http://localhost:8000/v1/audio/generate \
-H "Content-Type: application/json" \
-d '{
"input": "The sound of a cat purring",
"audio_length": 10.0
}' --output cat.wav
```
#### Using Python Client
```bash
python stable_audio_client.py \
--text "The sound of a cat purring" \
--audio_length 10.0 \
--output cat.wav
```
#### Using Bash Script
```bash
bash curl_examples.sh
```
## API Reference
### Endpoint
```
POST /v1/audio/generate
```
### Request Body
```json
{
"input": "Text description of the audio",
"audio_length": 10.0,
"audio_start": 0.0,
"negative_prompt": "Low quality",
"guidance_scale": 7.0,
"num_inference_steps": 100,
"seed": 42,
"response_format": "wav"
}
```
### Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `input` | string | **required** | Text prompt describing the audio to generate |
| `audio_length` | float | ~47s | Audio duration in seconds (max ~47s for stable-audio-open-1.0) |
| `audio_start` | float | 0.0 | Audio start time in seconds |
| `negative_prompt` | string | null | Text describing what to avoid in generation |
| `guidance_scale` | float | 7.0 | Classifier-free guidance scale (higher = more adherence to prompt) |
| `num_inference_steps` | int | 50 | Number of denoising steps (higher = better quality, slower) |
| `seed` | int | null | Random seed for reproducibility |
| `response_format` | string | "wav" | Output format: wav, mp3, flac, pcm |
### Response
Returns audio data in the requested format (default: WAV).
## Usage Examples
### Basic Generation
```bash
curl -X POST http://localhost:8000/v1/audio/generate \
-H "Content-Type: application/json" \
-d '{
"input": "The sound of ocean waves"
}' --output ocean.wav
```
### Custom Duration
```bash
curl -X POST http://localhost:8000/v1/audio/generate \
-H "Content-Type: application/json" \
-d '{
"input": "A dog barking",
"audio_length": 5.0
}' --output dog_5s.wav
```
### High Quality with Negative Prompt
```bash
curl -X POST http://localhost:8000/v1/audio/generate \
-H "Content-Type: application/json" \
-d '{
"input": "A piano playing a gentle melody",
"audio_length": 10.0,
"negative_prompt": "Low quality, distorted, noisy",
"guidance_scale": 8.0,
"num_inference_steps": 150
}' --output piano_hq.wav
```
### Reproducible Generation
```bash
curl -X POST http://localhost:8000/v1/audio/generate \
-H "Content-Type: application/json" \
-d '{
"input": "Thunder and rain sounds",
"audio_length": 15.0,
"seed": 42
}' --output thunder.wav
```
### Quick Generation (Fewer Steps)
For faster generation with slightly lower quality:
```bash
curl -X POST http://localhost:8000/v1/audio/generate \
-H "Content-Type: application/json" \
-d '{
"input": "Birds chirping in a forest",
"audio_length": 8.0,
"num_inference_steps": 50
}' --output birds_quick.wav
```
## Python Client Examples
### Simple Generation
```bash
python stable_audio_client.py \
--text "The sound of a cat purring"
```
### Custom Parameters
```bash
python stable_audio_client.py \
--text "Thunder and rain" \
--audio_length 15.0 \
--negative_prompt "Low quality" \
--guidance_scale 7.0 \
--num_inference_steps 100 \
--seed 42 \
--output thunder.wav
```
### Different Output Format
```bash
python stable_audio_client.py \
--text "Guitar playing" \
--response_format mp3 \
--output guitar.mp3
```
## Tips
1. **Audio Length**: Keep under 47 seconds for `stable-audio-open-1.0`
2. **Quality vs Speed**:
- 50 steps: Fast, decent quality
- 100 steps: Good balance (default)
- 150+ steps: High quality, slower
3. **Guidance Scale**:
- Lower (3-5): More creative/varied
- Default (7): Good balance
- Higher (10+): More literal to prompt
4. **Negative Prompts**: Use to avoid "Low quality", "distorted", "noisy", etc.
5. **Seeds**: Use same seed for reproducible results
## Performance
| Inference Steps | Quality | Speed | Use Case |
|----------------|---------|-------|----------|
| 50 | Good | Fast | Quick previews |
| 100 (default) | Very Good | Medium | Production |
| 150+ | Excellent | Slow | Final/critical audio |
## Troubleshooting
### Server not responding
- Check if server is running: `curl http://localhost:8000/health`
- Check server logs for errors
### Audio quality issues
- Increase `num_inference_steps` (e.g., 150)
- Add negative prompts: `"Low quality, distorted, noisy"`
- Increase `guidance_scale` for more prompt adherence
### Generation timeout
- Reduce `num_inference_steps`
- Reduce `audio_length`
- Check GPU memory with `nvidia-smi`
### Wrong audio length
- Ensure `audio_length` is within model limits (~47s max)
- Adjust `audio_start` if trimming is needed
## See Also
- [Offline Inference Example](../../offline_inference/text_to_audio/README.md)
- [Stable Audio Model Card](https://huggingface.co/stabilityai/stable-audio-open-1.0)
- [vLLM-Omni Documentation](https://github.com/vllm-project/vllm-omni)
+54
View File
@@ -0,0 +1,54 @@
#!/bin/bash
# Examples for using Stable Audio with curl via /v1/audio/generate endpoint
# Example 1: Simple request with default parameters
echo "Example 1: Simple request with default parameters"
curl -X POST http://localhost:8000/v1/audio/generate \
-H "Content-Type: application/json" \
-d '{
"input": "The sound audience clapping and cheering in a stadium"
}' --output stadium.wav
# Example 2: Request with custom audio_length
echo "Example 2: Custom audio length (5 seconds)"
curl -X POST http://localhost:8000/v1/audio/generate \
-H "Content-Type: application/json" \
-d '{
"input": "The sound of a dog barking",
"audio_length": 5.0
}' --output dog_5s.wav
# Example 3: Request with negative prompt for quality control
echo "Example 3: With negative prompt"
curl -X POST http://localhost:8000/v1/audio/generate \
-H "Content-Type: application/json" \
-d '{
"input": "A piano playing a gentle melody",
"audio_length": 10.0,
"negative_prompt": "Low quality, distorted, noisy"
}' --output piano.wav
# Example 4: Full control with all parameters
echo "Example 4: Full control (custom length, guidance, steps, seed)"
curl -X POST http://localhost:8000/v1/audio/generate \
-H "Content-Type: application/json" \
-d '{
"input": "Thunder and rain sounds",
"audio_length": 15.0,
"negative_prompt": "Low quality",
"guidance_scale": 7.0,
"num_inference_steps": 100,
"seed": 42
}' --output thunder_rain.wav
# Example 5: Quick generation with fewer steps (faster but lower quality)
echo "Example 5: Quick generation (fewer steps)"
curl -X POST http://localhost:8000/v1/audio/generate \
-H "Content-Type: application/json" \
-d '{
"input": "Ocean waves crashing on a beach",
"audio_length": 8.0,
"num_inference_steps": 50
}' --output ocean.wav
echo "All examples completed!"
+170
View File
@@ -0,0 +1,170 @@
#!/usr/bin/env python3
"""
OpenAI-compatible client for Stable Audio via /v1/audio/generate endpoint.
This script demonstrates how to use the OpenAI-compatible speech API
to generate audio from text using Stable Audio models.
Examples:
# Simple generation
python stable_audio_client.py --text "The sound of a cat purring"
# With custom duration
python stable_audio_client.py --text "A dog barking" --audio_length 5.0
# With all parameters
python stable_audio_client.py --text "Thunder and rain" \
--audio_length 15.0 \
--negative_prompt "Low quality" \
--guidance_scale 7.0 \
--num_inference_steps 100 \
--seed 42 \
--output thunder.wav
"""
import argparse
import sys
import requests
def parse_args():
parser = argparse.ArgumentParser(description="Generate audio with Stable Audio via OpenAI-compatible API")
parser.add_argument(
"--api_url",
default="http://localhost:8000/v1/audio/generate",
help="API endpoint URL",
)
parser.add_argument(
"--text",
default="The sound of a cat purring",
help="Text prompt for audio generation",
)
parser.add_argument(
"--audio_length",
type=float,
default=10.0,
help="Audio length in seconds (max ~47s for stable-audio-open-1.0)",
)
parser.add_argument(
"--audio_start",
type=float,
default=0.0,
help="Audio start time in seconds",
)
parser.add_argument(
"--negative_prompt",
default="Low quality",
help="Negative prompt for classifier-free guidance",
)
parser.add_argument(
"--guidance_scale",
type=float,
default=7.0,
help="Guidance scale for diffusion (higher = more adherence to prompt)",
)
parser.add_argument(
"--num_inference_steps",
type=int,
default=100,
help="Number of inference steps (higher = better quality, slower)",
)
parser.add_argument(
"--seed",
type=int,
default=None,
help="Random seed for reproducibility",
)
parser.add_argument(
"--output",
default="stable_audio_output.wav",
help="Output file path",
)
parser.add_argument(
"--response_format",
default="wav",
choices=["wav", "mp3", "flac", "pcm"],
help="Audio output format",
)
return parser.parse_args()
def generate_audio(args):
"""Generate audio using the API."""
# Build request payload
payload = {
"input": args.text,
"audio_length": args.audio_length,
"audio_start": args.audio_start,
"response_format": args.response_format,
}
# Add optional parameters
if args.negative_prompt:
payload["negative_prompt"] = args.negative_prompt
if args.guidance_scale:
payload["guidance_scale"] = args.guidance_scale
if args.num_inference_steps:
payload["num_inference_steps"] = args.num_inference_steps
if args.seed is not None:
payload["seed"] = args.seed
print(f"\n{'=' * 60}")
print("Stable Audio - Text-to-Audio Generation")
print(f"{'=' * 60}")
print(f"API URL: {args.api_url}")
print(f"Prompt: {args.text}")
print(f"Audio length: {args.audio_length}s")
print(f"Negative prompt: {args.negative_prompt}")
print(f"Guidance scale: {args.guidance_scale}")
print(f"Inference steps: {args.num_inference_steps}")
if args.seed is not None:
print(f"Seed: {args.seed}")
print(f"Output: {args.output}")
print(f"{'=' * 60}\n")
try:
# Make the API request
print("Generating audio...")
response = requests.post(
args.api_url,
json=payload,
headers={"Content-Type": "application/json"},
timeout=300, # 5 minute timeout for long generations
)
# Check for errors
if response.status_code != 200:
print(f"Error: API returned status code {response.status_code}")
print(f"Response: {response.text}")
return False
# Save the audio
with open(args.output, "wb") as f:
f.write(response.content)
print(f"✓ Audio saved to {args.output}")
print(f" File size: {len(response.content) / 1024:.1f} KB")
return True
except requests.exceptions.Timeout:
print("Error: Request timed out. Try reducing inference steps or audio length.")
return False
except requests.exceptions.ConnectionError:
print(f"Error: Could not connect to {args.api_url}")
print("Make sure the server is running.")
return False
except Exception as e:
print(f"Error: {e}")
return False
def main():
args = parse_args()
success = generate_audio(args)
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()
+9 -10
View File
@@ -1057,16 +1057,15 @@ class OmniServer:
max_wait = 1200 # 20 minutes
start_time = time.time()
while time.time() - start_time < max_wait:
# Check for process status
ret = self.proc.poll()
if ret is not None:
raise RuntimeError(f"Server processes exited with code {ret} before becoming ready.")
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.settimeout(1)
result = sock.connect_ex((self.host, self.port))
if result == 0:
print(f"Server ready on {self.host}:{self.port}")
return
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.settimeout(1)
result = sock.connect_ex((self.host, self.port))
if result == 0:
print(f"Server ready on {self.host}:{self.port}")
return
except Exception:
pass
time.sleep(2)
raise RuntimeError(f"Server failed to start within {max_wait} seconds")
+1 -1
View File
@@ -11,7 +11,7 @@ import torch.nn as nn
from vllm_omni.diffusion.data import DiffusionParallelConfig
from vllm_omni.diffusion.distributed.hsdp import HSDPInferenceConfig
pytestmark = [pytest.mark.diffusion, pytest.mark.parallel, pytest.mark.cpu, pytest.mark.core_model]
pytestmark = [pytest.mark.diffusion, pytest.mark.parallel, pytest.mark.cpu]
class TestHSDPInferenceConfig:
@@ -11,7 +11,10 @@ import torch
from vllm_omni.diffusion.distributed.parallel_state import RankGenerator, set_seq_parallel_pg
pytestmark = [pytest.mark.diffusion, pytest.mark.parallel, pytest.mark.core_model, pytest.mark.cpu]
pytestmark = [
pytest.mark.diffusion,
pytest.mark.parallel,
]
def _fake_new_group_factory(created_groups: list[SimpleNamespace]):
@@ -47,8 +47,6 @@ requires_distributed = pytest.mark.skipif(
pytestmark = [
pytest.mark.diffusion,
pytest.mark.parallel,
pytest.mark.core_model,
pytest.mark.cpu,
]
# =============================================================================
@@ -8,8 +8,6 @@ import torch
from vllm_omni.diffusion.distributed import vae_patch_parallel as vae_patch_parallel
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
class _DummyConfig:
def __init__(self, **attrs):
@@ -8,8 +8,6 @@ import vllm_omni.diffusion.models.nextstep_1_1.pipeline_nextstep_1_1 as nextstep
from vllm_omni.diffusion.models.nextstep_1_1.modeling_nextstep_heads import FlowMatchingHead
from vllm_omni.diffusion.models.nextstep_1_1.pipeline_nextstep_1_1 import NextStep11Pipeline
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
class _DummyImageHead:
def __init__(self, token_dim: int):
@@ -2,8 +2,6 @@ import pytest
from vllm_omni.diffusion.models.z_image.z_image_transformer import validate_zimage_tp_constraints
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
def test_validate_zimage_tp_constraints_tp2_ok():
ffn_hidden_dim, final_out_dims, supported_tp = validate_zimage_tp_constraints(
@@ -12,7 +12,7 @@ from vllm_omni.diffusion.data import DiffusionOutput
from vllm_omni.diffusion.executor.multiproc_executor import MultiprocDiffusionExecutor
from vllm_omni.diffusion.scheduler import Scheduler
pytestmark = [pytest.mark.diffusion, pytest.mark.core_model, pytest.mark.cpu]
pytestmark = [pytest.mark.diffusion]
# ───────────────────────────────────────────── helpers ─────────────────────
@@ -17,7 +17,7 @@ from vllm_omni.distributed.omni_connectors.connectors.mooncake_transfer_engine_c
)
# All tests in this file are pure-CPU unit tests for the memory allocator.
pytestmark = [pytest.mark.cpu, pytest.mark.parallel, pytest.mark.core_model]
pytestmark = [pytest.mark.cpu, pytest.mark.parallel]
@pytest.mark.core_model
@@ -8,8 +8,6 @@ import pytest
# Use the new import path for initialization utilities
from vllm_omni.distributed.omni_connectors.utils.initialization import load_omni_transfer_config
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
def get_config_files():
"""Helper to find config files."""
@@ -0,0 +1,509 @@
# tests/entrypoints/openai_api/test_serving_audio_generate.py
import logging
from inspect import Signature, signature
from unittest.mock import MagicMock, patch
import pytest
import torch
from fastapi import FastAPI
from fastapi.testclient import TestClient
from vllm_omni.entrypoints.openai.protocol.audio import (
CreateAudio,
OpenAICreateAudioGenerateRequest,
)
from vllm_omni.entrypoints.openai.serving_audio_generate import (
OmniOpenAIServingAudioGenerate,
)
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
from vllm_omni.outputs import OmniRequestOutput
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
logger = logging.getLogger(__name__)
# Helper: create a mock audio output for endpoint tests
def create_mock_audio_output(
request_id: str = "audiogen-mock-123",
sample_rate: int = 44100,
num_samples: int = 44100,
audio_key: str = "audio",
) -> OmniRequestOutput:
"""Return an OmniRequestOutput mimicking diffusion audio model output."""
audio_tensor = torch.sin(torch.linspace(0, 440 * 2 * torch.pi, num_samples))
return OmniRequestOutput.from_diffusion(
request_id=request_id,
images=[],
prompt=None,
metrics={},
multimodal_output={audio_key: audio_tensor, "sr": sample_rate},
)
def _make_engine_client(*, audio_key: str = "audio", sample_rate: int = 44100):
"""Build a mock engine client producing audio output."""
mock_engine_client = MagicMock()
mock_engine_client.errored = False
mock_engine_client.model_type = "StableAudioPipeline"
mock_engine_client.default_sampling_params_list = [{}]
async def mock_generate_fn(*args, **kwargs):
yield create_mock_audio_output(
request_id=kwargs.get("request_id", "audiogen-mock"),
sample_rate=sample_rate,
audio_key=audio_key,
)
mock_engine_client.generate = MagicMock(side_effect=mock_generate_fn)
return mock_engine_client
def _make_server(engine_client=None):
"""Build an OmniOpenAIServingAudioGenerate with mocks."""
if engine_client is None:
engine_client = _make_engine_client()
mock_models = MagicMock()
mock_models.is_base_model.return_value = True
return OmniOpenAIServingAudioGenerate(
engine_client=engine_client,
models=mock_models,
request_logger=MagicMock(),
)
@pytest.fixture
def test_app():
server = _make_server()
original_fn = server.create_audio_generate
sig = signature(original_fn)
new_params = [p for name, p in sig.parameters.items() if name != "raw_request"]
new_sig = Signature(parameters=new_params, return_annotation=sig.return_annotation)
async def patched_create_audio_generate(*args, **kwargs):
return await original_fn(*args, **kwargs)
patched_create_audio_generate.__signature__ = new_sig
server.create_audio_generate = patched_create_audio_generate
app = FastAPI()
app.add_api_route(
"/v1/audio/generate",
server.create_audio_generate,
methods=["POST"],
response_model=None,
)
return app
@pytest.fixture
def client(test_app):
return TestClient(test_app)
# Request Validation (Pydantic model)
class TestRequestValidation:
"""Validate OpenAICreateAudioGenerateRequest pydantic constraints."""
def test_valid_minimal_request(self):
req = OpenAICreateAudioGenerateRequest(input="A calm piano melody")
assert req.input == "A calm piano melody"
assert req.response_format == "wav"
assert req.speed == 1.0
def test_fields_are_wired_correctly(self):
req = OpenAICreateAudioGenerateRequest(
input="rain sounds",
model="stable-audio",
response_format="flac",
speed=1.5,
audio_length=10.0,
audio_start=2.0,
negative_prompt="noise",
guidance_scale=7.5,
num_inference_steps=100,
seed=42,
)
assert req.input == "rain sounds"
assert req.model == "stable-audio"
assert req.response_format == "flac"
assert req.speed == 1.5
assert req.audio_length == 10.0
assert req.audio_start == 2.0
assert req.negative_prompt == "noise"
assert req.guidance_scale == 7.5
assert req.num_inference_steps == 100
assert req.seed == 42
def test_invalid_response_format(self):
with pytest.raises(Exception):
OpenAICreateAudioGenerateRequest(input="test", response_format="invalid_format")
def test_speed_lower_bound(self):
with pytest.raises(Exception):
OpenAICreateAudioGenerateRequest(input="test", speed=0.1)
def test_speed_upper_bound(self):
with pytest.raises(Exception):
OpenAICreateAudioGenerateRequest(input="test", speed=5.0)
def test_speed_at_boundaries(self):
req_low = OpenAICreateAudioGenerateRequest(input="test", speed=0.25)
assert req_low.speed == 0.25
req_high = OpenAICreateAudioGenerateRequest(input="test", speed=4.0)
assert req_high.speed == 4.0
def test_stream_format_sse_rejected(self):
with pytest.raises(Exception):
OpenAICreateAudioGenerateRequest(input="test", stream_format="sse")
def test_stream_format_audio_accepted(self):
req = OpenAICreateAudioGenerateRequest(input="test", stream_format="audio")
assert req.stream_format == "audio"
# Constructor & Class Methods
class TestConstructor:
def test_default_init(self):
server = _make_server()
assert server.diffusion_mode is False
def test_for_diffusion_factory(self):
engine_client = _make_engine_client()
mock_models = MagicMock()
mock_models.is_base_model.return_value = True
server = OmniOpenAIServingAudioGenerate.for_diffusion(
engine_client=engine_client,
models=mock_models,
request_logger=MagicMock(),
)
assert server.diffusion_mode is True
def test_is_stable_audio_model_true(self):
server = _make_server()
assert server._is_stable_audio_model() is True
def test_is_stable_audio_model_false(self):
engine = _make_engine_client()
engine.model_type = "SomeOtherModel"
server = _make_server(engine_client=engine)
assert server._is_stable_audio_model() is False
# Parameter Wiring — verify request params reach the engine
class TestParameterWiring:
"""Ensure request parameters are correctly forwarded to the engine."""
@pytest.fixture
def server_and_engine(self):
engine = _make_engine_client()
server = _make_server(engine_client=engine)
return server, engine
@pytest.mark.asyncio
async def test_prompt_wiring(self, server_and_engine):
server, engine = server_and_engine
req = OpenAICreateAudioGenerateRequest(input="birds chirping")
await server.create_audio_generate(req)
engine.generate.assert_called_once()
call_kwargs = engine.generate.call_args[1]
assert call_kwargs["prompt"]["prompt"] == "birds chirping"
assert call_kwargs["output_modalities"] == ["audio"]
@pytest.mark.asyncio
async def test_negative_prompt_wiring(self, server_and_engine):
server, engine = server_and_engine
req = OpenAICreateAudioGenerateRequest(input="a calm ocean", negative_prompt="noise distortion")
await server.create_audio_generate(req)
call_kwargs = engine.generate.call_args[1]
assert call_kwargs["prompt"]["negative_prompt"] == "noise distortion"
@pytest.mark.asyncio
async def test_negative_prompt_absent(self, server_and_engine):
server, engine = server_and_engine
req = OpenAICreateAudioGenerateRequest(input="a calm ocean")
await server.create_audio_generate(req)
call_kwargs = engine.generate.call_args[1]
assert "negative_prompt" not in call_kwargs["prompt"]
@pytest.mark.asyncio
async def test_guidance_scale_wiring(self, server_and_engine):
server, engine = server_and_engine
req = OpenAICreateAudioGenerateRequest(input="test", guidance_scale=12.0)
await server.create_audio_generate(req)
call_kwargs = engine.generate.call_args[1]
sp = call_kwargs["sampling_params_list"][0]
assert isinstance(sp, OmniDiffusionSamplingParams)
assert sp.guidance_scale == 12.0
@pytest.mark.asyncio
async def test_num_inference_steps_wiring(self, server_and_engine):
server, engine = server_and_engine
req = OpenAICreateAudioGenerateRequest(input="test", num_inference_steps=200)
await server.create_audio_generate(req)
sp = engine.generate.call_args[1]["sampling_params_list"][0]
assert sp.num_inference_steps == 200
@pytest.mark.asyncio
async def test_seed_creates_generator(self, server_and_engine):
server, engine = server_and_engine
req = OpenAICreateAudioGenerateRequest(input="test", seed=42)
with patch("vllm_omni.entrypoints.openai.serving_audio_generate.torch") as mock_torch:
mock_gen = MagicMock()
mock_gen.manual_seed.return_value = mock_gen
mock_torch.Generator.return_value = mock_gen
await server.create_audio_generate(req)
mock_torch.Generator.assert_called_once()
mock_gen.manual_seed.assert_called_once_with(42)
@pytest.mark.asyncio
async def test_seed_none_skips_generator(self, server_and_engine):
server, engine = server_and_engine
req = OpenAICreateAudioGenerateRequest(input="test")
await server.create_audio_generate(req)
sp = engine.generate.call_args[1]["sampling_params_list"][0]
assert sp.generator is None
@pytest.mark.asyncio
async def test_audio_length_wiring(self, server_and_engine):
server, engine = server_and_engine
req = OpenAICreateAudioGenerateRequest(input="test", audio_length=10.0, audio_start=2.0)
await server.create_audio_generate(req)
sp = engine.generate.call_args[1]["sampling_params_list"][0]
assert sp.extra_args["audio_start_in_s"] == 2.0
assert sp.extra_args["audio_end_in_s"] == 12.0 # start + length
@pytest.mark.asyncio
async def test_audio_length_default_start(self, server_and_engine):
server, engine = server_and_engine
req = OpenAICreateAudioGenerateRequest(input="test", audio_length=5.0)
await server.create_audio_generate(req)
sp = engine.generate.call_args[1]["sampling_params_list"][0]
assert sp.extra_args["audio_start_in_s"] == 0.0
assert sp.extra_args["audio_end_in_s"] == 5.0
@pytest.mark.asyncio
async def test_no_audio_length_skips_extra_args(self, server_and_engine):
server, engine = server_and_engine
req = OpenAICreateAudioGenerateRequest(input="test")
await server.create_audio_generate(req)
sp = engine.generate.call_args[1]["sampling_params_list"][0]
assert sp.extra_args == {}
@pytest.mark.asyncio
async def test_defaults_not_set_when_omitted(self, server_and_engine):
"""Guidance scale and num_inference_steps keep dataclass defaults when not in request."""
server, engine = server_and_engine
req = OpenAICreateAudioGenerateRequest(input="test")
await server.create_audio_generate(req)
sp = engine.generate.call_args[1]["sampling_params_list"][0]
defaults = OmniDiffusionSamplingParams()
assert sp.guidance_scale == defaults.guidance_scale
assert sp.num_inference_steps == defaults.num_inference_steps
# Audio Response Format
class TestAudioResponseFormat:
def test_wav_response(self, client):
payload = {"input": "a gentle rain", "response_format": "wav"}
response = client.post("/v1/audio/generate", json=payload)
assert response.status_code == 200
assert response.headers["content-type"] == "audio/wav"
assert len(response.content) > 0
def test_mp3_response(self, client):
payload = {"input": "a gentle rain", "response_format": "mp3"}
response = client.post("/v1/audio/generate", json=payload)
assert response.status_code == 200
assert response.headers["content-type"] == "audio/mpeg"
assert len(response.content) > 0
def test_flac_response(self, client):
payload = {"input": "a gentle rain", "response_format": "flac"}
response = client.post("/v1/audio/generate", json=payload)
assert response.status_code == 200
assert response.headers["content-type"] == "audio/flac"
assert len(response.content) > 0
def test_invalid_format_rejected(self, client):
payload = {"input": "test", "response_format": "banana"}
response = client.post("/v1/audio/generate", json=payload)
assert response.status_code == 422
@patch("vllm_omni.entrypoints.openai.serving_audio_generate.OmniOpenAIServingAudioGenerate.create_audio")
def test_speed_parameter_forwarded(self, mock_create_audio, test_app):
mock_audio_response = MagicMock()
mock_audio_response.audio_data = b"dummy_audio"
mock_audio_response.media_type = "audio/wav"
mock_create_audio.return_value = mock_audio_response
c = TestClient(test_app)
payload = {"input": "test", "response_format": "wav", "speed": 2.5}
c.post("/v1/audio/generate", json=payload)
mock_create_audio.assert_called_once()
audio_obj = mock_create_audio.call_args[0][0]
assert isinstance(audio_obj, CreateAudio)
assert audio_obj.speed == 2.5
@patch("vllm_omni.entrypoints.openai.serving_audio_generate.OmniOpenAIServingAudioGenerate.create_audio")
def test_sample_rate_from_output(self, mock_create_audio, test_app):
mock_audio_response = MagicMock()
mock_audio_response.audio_data = b"dummy"
mock_audio_response.media_type = "audio/wav"
mock_create_audio.return_value = mock_audio_response
c = TestClient(test_app)
payload = {"input": "test"}
c.post("/v1/audio/generate", json=payload)
audio_obj = mock_create_audio.call_args[0][0]
assert audio_obj.sample_rate == 44100 # Stable Audio default
# Error Handling
class TestErrorHandling:
@pytest.mark.asyncio
async def test_no_output_returns_error(self):
engine = _make_engine_client()
async def empty_gen(*args, **kwargs):
return
yield # noqa: unreachable – makes this an async generator
engine.generate = MagicMock(side_effect=empty_gen)
server = _make_server(engine_client=engine)
req = OpenAICreateAudioGenerateRequest(input="test")
resp = await server.create_audio_generate(req)
# create_error_response returns an ErrorResponse with .error.message
assert "No output generated" in resp.error.message
@pytest.mark.asyncio
async def test_no_audio_in_output_returns_error(self):
engine = _make_engine_client()
async def gen_without_audio(*args, **kwargs):
yield OmniRequestOutput.from_diffusion(
request_id="test",
images=[],
prompt=None,
metrics={},
multimodal_output={}, # no audio key
)
engine.generate = MagicMock(side_effect=gen_without_audio)
server = _make_server(engine_client=engine)
req = OpenAICreateAudioGenerateRequest(input="test")
resp = await server.create_audio_generate(req)
assert "did not produce audio" in resp.error.message
@pytest.mark.asyncio
async def test_engine_errored_raises(self):
engine = _make_engine_client()
engine.errored = True
engine.dead_error = RuntimeError("engine is dead")
server = _make_server(engine_client=engine)
req = OpenAICreateAudioGenerateRequest(input="test")
with pytest.raises(RuntimeError, match="engine is dead"):
await server.create_audio_generate(req)
@pytest.mark.asyncio
async def test_model_outputs_key_fallback(self):
"""Audio data under 'model_outputs' key should be accepted."""
engine = _make_engine_client(audio_key="model_outputs")
server = _make_server(engine_client=engine)
req = OpenAICreateAudioGenerateRequest(input="test")
resp = await server.create_audio_generate(req)
# Should succeed and return a Response with audio bytes
assert hasattr(resp, "body")
assert len(resp.body) > 0
@pytest.mark.asyncio
async def test_value_error_returns_error_response(self):
engine = _make_engine_client()
async def gen_value_error(*args, **kwargs):
raise ValueError("bad value")
yield # noqa: unreachable
engine.generate = MagicMock(side_effect=gen_value_error)
server = _make_server(engine_client=engine)
req = OpenAICreateAudioGenerateRequest(input="test")
resp = await server.create_audio_generate(req)
assert "bad value" in resp.error.message
@pytest.mark.asyncio
async def test_generic_exception_returns_error_response(self):
engine = _make_engine_client()
async def gen_runtime_error(*args, **kwargs):
raise RuntimeError("something went wrong")
yield # noqa: unreachable
engine.generate = MagicMock(side_effect=gen_runtime_error)
server = _make_server(engine_client=engine)
req = OpenAICreateAudioGenerateRequest(input="test")
resp = await server.create_audio_generate(req)
assert "Audio generation failed" in resp.error.message
# End-to-End via TestClient
class TestAudioGenerateAPI:
def test_basic_success(self, client):
payload = {"input": "ambient forest sounds"}
response = client.post("/v1/audio/generate", json=payload)
assert response.status_code == 200
assert len(response.content) > 0
def test_with_all_params(self, client):
payload = {
"input": "gentle piano",
"response_format": "wav",
"speed": 1.0,
"audio_length": 5.0,
"audio_start": 0.0,
"negative_prompt": "noise",
"guidance_scale": 7.0,
"num_inference_steps": 50,
"seed": 123,
}
response = client.post("/v1/audio/generate", json=payload)
assert response.status_code == 200
assert response.headers["content-type"] == "audio/wav"
def test_missing_input_rejected(self, client):
payload = {}
response = client.post("/v1/audio/generate", json=payload)
assert response.status_code == 422
def test_extra_unknown_fields_ignored(self, client):
payload = {"input": "test", "unknown_field": "value"}
response = client.post("/v1/audio/generate", json=payload)
# Pydantic v2 ignores extra fields by default
assert response.status_code == 200
+9 -9
View File
@@ -612,7 +612,7 @@ def test_initialize_stage_configs_called_when_none(
"""Test that stage configs are auto-loaded when stage_configs_path is None."""
def _fake_loader(
model: str,
config_path: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
@@ -687,7 +687,7 @@ def test_generate_raises_on_length_mismatch(monkeypatch: pytest.MonkeyPatch, moc
"""Test that generate raises ValueError when sampling_params_list length doesn't match."""
def _fake_loader(
model: str,
config_path: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
@@ -742,7 +742,7 @@ def test_generate_pipeline_and_final_outputs(monkeypatch: pytest.MonkeyPatch, mo
stage_cfg1["processed_input"] = ["processed-for-stage-1"]
def _fake_loader(
model: str,
config_path: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
@@ -848,7 +848,7 @@ def test_generate_pipeline_with_batch_input(monkeypatch: pytest.MonkeyPatch, moc
stage_cfg1["stage_id"] = 1
def _fake_loader(
model: str,
config_path: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
@@ -969,7 +969,7 @@ def test_generate_no_final_output_returns_empty(
stage_cfg1["final_output"] = False
def _fake_loader(
model: str,
config_path: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
@@ -1062,7 +1062,7 @@ def test_generate_sampling_params_none_use_default(
stage_cfg1["final_output"] = False
def _fake_loader(
model: str,
config_path: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
@@ -1140,7 +1140,7 @@ def test_wait_for_stages_ready_timeout(monkeypatch: pytest.MonkeyPatch, mocker:
"""Test that _wait_for_stages_ready handles timeout correctly."""
def _fake_loader(
model: str,
config_path: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
@@ -1202,7 +1202,7 @@ def test_generate_handles_error_messages(monkeypatch: pytest.MonkeyPatch, mocker
"""Test that generate handles error messages from stages correctly."""
def _fake_loader(
model: str,
config_path: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
@@ -1283,7 +1283,7 @@ def test_close_sends_shutdown_signal(monkeypatch: pytest.MonkeyPatch, mocker: Mo
"""Test that close() sends shutdown signal to all input queues."""
def _fake_loader(
model: str,
config_path: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
+8 -8
View File
@@ -577,7 +577,7 @@ def test_initialize_stage_configs_called_when_none(
"""Test that stage configs are auto-loaded when stage_configs_path is None."""
def _fake_loader(
model: str,
config_path: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
@@ -654,7 +654,7 @@ def test_generate_raises_on_length_mismatch(monkeypatch: pytest.MonkeyPatch, moc
"""Test that generate raises ValueError when sampling_params_list length doesn't match."""
def _fake_loader(
model: str,
config_path: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
@@ -711,7 +711,7 @@ def test_generate_pipeline_and_final_outputs(monkeypatch: pytest.MonkeyPatch, mo
stage_cfg1["processed_input"] = ["processed-for-stage-1"]
def _fake_loader(
model: str,
config_path: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
@@ -819,7 +819,7 @@ def test_generate_no_final_output_returns_empty(
stage_cfg1["final_output"] = False
def _fake_loader(
model: str,
config_path: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
@@ -911,7 +911,7 @@ def test_generate_sampling_params_none_use_default(
stage_cfg1["final_output"] = False
def _fake_loader(
model: str,
config_path: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
@@ -988,7 +988,7 @@ def test_wait_for_stages_ready_timeout(monkeypatch: pytest.MonkeyPatch, mocker:
"""Test that _wait_for_stages_ready handles timeout correctly."""
def _fake_loader(
model: str,
config_path: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
@@ -1052,7 +1052,7 @@ def test_generate_handles_error_messages(monkeypatch: pytest.MonkeyPatch, mocker
"""Test that generate handles error messages from stages correctly."""
def _fake_loader(
model: str,
config_path: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
@@ -1135,7 +1135,7 @@ def test_close_sends_shutdown_signal(monkeypatch: pytest.MonkeyPatch, mocker: Mo
"""Test that close() sends shutdown signal to all input queues."""
def _fake_loader(
model: str,
config_path: str,
stage_configs_path: str | None = None,
base_engine_args: dict | None = None,
default_stage_cfg_factory=None,
@@ -19,8 +19,6 @@ from vllm.model_executor.models.qwen2_5_omni_thinker import (
merge_interleaved_embeddings,
)
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
# Fake token IDs
AUDIO_TOKEN_ID = 1001
IMAGE_TOKEN_ID = 1002
@@ -356,7 +356,6 @@ class StableAudioPipeline(nn.Module, SupportAudioOutput):
negative_prompt: str | list[str] | None = None,
audio_end_in_s: float | None = None,
audio_start_in_s: float = 0.0,
num_inference_steps: int = 100,
guidance_scale: float = 7.0,
num_waveforms_per_prompt: int = 1,
generator: torch.Generator | list[torch.Generator] | None = None,
@@ -374,7 +373,6 @@ class StableAudioPipeline(nn.Module, SupportAudioOutput):
negative_prompt: Negative prompt for CFG
audio_end_in_s: Audio end time in seconds (max ~47s for stable-audio-open-1.0)
audio_start_in_s: Audio start time in seconds
num_inference_steps: Number of denoising steps
guidance_scale: CFG scale
num_waveforms_per_prompt: Number of audio outputs per prompt
generator: Random generator for reproducibility
@@ -395,7 +393,7 @@ class StableAudioPipeline(nn.Module, SupportAudioOutput):
elif req.prompts:
negative_prompt = ["" if isinstance(p, str) else (p.get("negative_prompt") or "") for p in req.prompts]
num_inference_steps = req.sampling_params.num_inference_steps or num_inference_steps
num_inference_steps = req.sampling_params.num_inference_steps
if req.sampling_params.guidance_scale_provided:
guidance_scale = req.sampling_params.guidance_scale
+4 -2
View File
@@ -28,6 +28,7 @@ from vllm_omni.entrypoints.utils import (
load_stage_configs_from_model,
load_stage_configs_from_yaml,
resolve_model_config_path,
resolve_model_type,
)
logger = init_logger(__name__)
@@ -84,12 +85,13 @@ class OmniLLM(LLM):
self.worker_backend = kwargs.get("worker_backend", "multi_process")
self.ray_address = kwargs.get("ray_address", None)
self.batch_timeout = batch_timeout
self.model_type = resolve_model_type(model)
self.log_stats: bool = bool(log_stats)
# Load stage configurations
if stage_configs_path is None:
self.config_path = resolve_model_config_path(model)
self.stage_configs = load_stage_configs_from_model(model)
self.config_path = resolve_model_config_path(self.model_type)
self.stage_configs = load_stage_configs_from_model(config_path=self.config_path)
else:
self.config_path = stage_configs_path
self.stage_configs = load_stage_configs_from_yaml(stage_configs_path)
+74 -4
View File
@@ -87,7 +87,10 @@ from vllm_omni.entrypoints.openai.image_api_utils import (
encode_image_base64,
parse_size,
)
from vllm_omni.entrypoints.openai.protocol.audio import OpenAICreateSpeechRequest
from vllm_omni.entrypoints.openai.protocol.audio import (
OpenAICreateAudioGenerateRequest,
OpenAICreateSpeechRequest,
)
from vllm_omni.entrypoints.openai.protocol.images import (
ImageData,
ImageGenerationRequest,
@@ -97,6 +100,7 @@ from vllm_omni.entrypoints.openai.protocol.videos import (
VideoGenerationRequest,
VideoGenerationResponse,
)
from vllm_omni.entrypoints.openai.serving_audio_generate import OmniOpenAIServingAudioGenerate
from vllm_omni.entrypoints.openai.serving_chat import OmniOpenAIServingChat
from vllm_omni.entrypoints.openai.serving_speech import OmniOpenAIServingSpeech
from vllm_omni.entrypoints.openai.serving_video import OmniOpenAIServingVideo
@@ -175,8 +179,29 @@ class _DiffusionServingModels:
provide a lightweight fallback.
"""
class _NullModelConfig:
def __getattr__(self, name):
return None
class _Unsupported:
def __init__(self, name: str):
self.name = name
def __call__(self, *args, **kwargs):
raise NotImplementedError(f"{self.name} is not supported in diffusion mode")
def __getattr__(self, attr):
raise NotImplementedError(f"{self.name}.{attr} is not supported in diffusion mode")
def __init__(self, base_model_paths: list[BaseModelPath]) -> None:
self._base_model_paths = base_model_paths
self.model_config = self._NullModelConfig()
def __getattr__(self, name):
"""Return a sentinel that raises NotImplementedError if called or
accessed, so any use of unsupported OpenAIServingModels features in
diffusion mode fails loudly with a descriptive message."""
return self._Unsupported(name)
async def show_available_models(self) -> ModelList:
return ModelList(
@@ -434,10 +459,10 @@ async def omni_init_app_state(
# For omni models
state.stage_configs = engine_client.stage_configs if hasattr(engine_client, "stage_configs") else None
model_name = served_model_names[0] if served_model_names else args.model
# Pure Diffusion mode: use simplified initialization logic
if is_pure_diffusion:
model_name = served_model_names[0] if served_model_names else args.model
state.vllm_config = None
state.diffusion_engine = engine_client
state.openai_serving_models = _DiffusionServingModels(base_model_paths)
@@ -450,6 +475,16 @@ async def omni_init_app_state(
model_name=model_name,
)
# audio related
state.openai_serving_speech = None
state.openai_serving_audio_generate = OmniOpenAIServingAudioGenerate.for_diffusion(
engine_client,
state.openai_serving_models,
request_logger=request_logger,
model_name=model_name,
)
# video related
diffusion_stage_configs = engine_client.stage_configs if hasattr(engine_client, "stage_configs") else None
state.openai_serving_video = OmniOpenAIServingVideo.for_diffusion(
diffusion_engine=engine_client, # type: ignore
@@ -457,7 +492,6 @@ async def omni_init_app_state(
stage_configs=diffusion_stage_configs,
)
state.enable_server_load_tracking = getattr(args, "enable_server_load_tracking", False)
state.server_load_metrics = 0
logger.info("Pure diffusion API server initialized for model: %s", model_name)
return
@@ -732,7 +766,11 @@ async def omni_init_app_state(
)
state.openai_serving_speech = OmniOpenAIServingSpeech(
engine_client, state.openai_serving_models, request_logger=request_logger
engine_client, state.openai_serving_models, request_logger=request_logger, model_name=model_name
)
state.openai_serving_audio_generate = OmniOpenAIServingAudioGenerate(
engine_client, state.openai_serving_models, request_logger=request_logger, model_name=model_name
)
state.openai_serving_video = OmniOpenAIServingVideo(
@@ -757,6 +795,10 @@ def Omnispeech(request: Request) -> OmniOpenAIServingSpeech | None:
return request.app.state.openai_serving_speech
def OmniAudioGenerate(request: Request) -> OmniOpenAIServingAudioGenerate | None:
return getattr(request.app.state, "openai_serving_audio_generate", None)
@router.post(
"/v1/chat/completions",
dependencies=[Depends(validate_json_request)],
@@ -867,6 +909,34 @@ async def create_speech(request: OpenAICreateSpeechRequest, raw_request: Request
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR.value, detail=str(e)) from e
@router.post(
"/v1/audio/generate",
dependencies=[Depends(validate_json_request)],
responses={
HTTPStatus.OK.value: {"content": {"audio/*": {}}},
HTTPStatus.BAD_REQUEST.value: {"model": ErrorResponse},
HTTPStatus.NOT_FOUND.value: {"model": ErrorResponse},
HTTPStatus.INTERNAL_SERVER_ERROR.value: {"model": ErrorResponse},
},
)
@with_cancellation
@load_aware_call
async def create_audio_generate(request: OpenAICreateAudioGenerateRequest, raw_request: Request):
handler = OmniAudioGenerate(raw_request)
if handler is None:
base_server = getattr(raw_request.app.state, "openai_serving_tokenization", None)
if base_server is None:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND.value,
detail="The model does not support Audio Generate API",
)
return base_server.create_error_response(message="The model does not support Audio Generate API")
try:
return await handler.create_audio_generate(request, raw_request)
except Exception as e:
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR.value, detail=str(e)) from e
@router.get(
"/v1/audio/voices",
responses={
@@ -45,6 +45,10 @@ class AudioMixin:
"Only mono (1D) and stereo (2D) are supported."
)
if audio_tensor.ndim == 2 and audio_tensor.shape[0] == 2:
# Convert from [channels, samples] to [samples, channels]
audio_tensor = audio_tensor.T
audio_tensor, sample_rate = self._apply_speed_adjustment(audio_tensor, speed, sample_rate)
supported_formats = {
@@ -85,6 +85,53 @@ class OpenAICreateSpeechRequest(BaseModel):
return self
class OpenAICreateAudioGenerateRequest(BaseModel):
"""Request model for audio generation via diffusion models (e.g. Stable Audio)."""
input: str = Field(
description="Text prompt describing the audio to generate",
)
model: str | None = None
response_format: Literal["wav", "pcm", "flac", "mp3", "aac", "opus"] = "wav"
speed: float | None = Field(
default=1.0,
ge=0.25,
le=4.0,
)
stream_format: Literal["sse", "audio"] | None = "audio"
audio_length: float | None = Field(
default=None,
description="Audio length in seconds",
)
audio_start: float | None = Field(
default=0.0,
description="Audio start time in seconds",
)
negative_prompt: str | None = Field(
default=None,
description="Negative prompt for classifier-free guidance",
)
guidance_scale: float | None = Field(
default=None,
description="Guidance scale for diffusion models",
)
num_inference_steps: int | None = Field(
default=None,
description="Number of inference steps",
)
seed: int | None = Field(
default=None,
description="Random seed for reproducibility",
)
@field_validator("stream_format")
@classmethod
def validate_stream_format(cls, v: str) -> str:
if v == "sse":
raise ValueError("'sse' is not a supported stream_format yet. Please use 'audio'.")
return v
class CreateAudio(BaseModel):
audio_tensor: np.ndarray
sample_rate: int = 24000
@@ -0,0 +1,168 @@
import asyncio
import torch
from fastapi import Request
from fastapi.responses import Response
from vllm.entrypoints.openai.engine.serving import OpenAIServing
from vllm.logger import init_logger
from vllm.utils import random_uuid
from vllm_omni.entrypoints.openai.audio_utils_mixin import AudioMixin
from vllm_omni.entrypoints.openai.protocol.audio import (
AudioResponse,
CreateAudio,
OpenAICreateAudioGenerateRequest,
)
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
from vllm_omni.outputs import OmniRequestOutput
logger = init_logger(__name__)
class OmniOpenAIServingAudioGenerate(OpenAIServing, AudioMixin):
"""Serving class for audio generation via diffusion models (e.g. Stable Audio)."""
def __init__(self, *args, **kwargs):
self.model_name = kwargs.pop("model_name", None)
super().__init__(*args, **kwargs)
self.diffusion_mode = False
@classmethod
def for_diffusion(cls, *args, **kwargs) -> "OmniOpenAIServingAudioGenerate":
"""Create an instance configured to run in diffusion mode."""
instance = cls(*args, **kwargs)
instance.diffusion_mode = True
return instance
def _is_stable_audio_model(self) -> bool:
return self.engine_client.model_type == "StableAudioPipeline"
async def create_audio_generate(
self,
request: OpenAICreateAudioGenerateRequest,
raw_request: Request | None = None,
):
"""
Generate audio using diffusion-based models (e.g. Stable Audio).
This endpoint is designed for audio generation models as
opposed to TTS models that specifically generate speech.
"""
error_check_ret = await self._check_model(request)
if error_check_ret is not None:
logger.error("Error with model %s", error_check_ret)
return error_check_ret
if self.engine_client.errored:
raise self.engine_client.dead_error
request_id = f"audiogen-{random_uuid()}"
try:
default_sr = 44100 # Default sample rate for Stable Audio
# Build prompt for diffusion audio generation
prompt = {
"prompt": request.input,
}
if request.negative_prompt:
prompt["negative_prompt"] = request.negative_prompt
# Build sampling params for diffusion
sampling_params_list = [OmniDiffusionSamplingParams(num_outputs_per_prompt=1)]
# Create generator if seed provided
if request.seed is not None:
from vllm_omni.platforms import current_omni_platform
generator = torch.Generator(device=current_omni_platform.device_type).manual_seed(request.seed)
sampling_params_list[0].generator = generator
if request.guidance_scale is not None:
sampling_params_list[0].guidance_scale = request.guidance_scale
if request.num_inference_steps is not None:
sampling_params_list[0].num_inference_steps = request.num_inference_steps
# Set up audio duration parameters
if request.audio_length is not None:
audio_length = request.audio_length
audio_start = request.audio_start if request.audio_start is not None else 0.0
audio_end_in_s = audio_start + audio_length
sampling_params_list[0].extra_args = {
"audio_start_in_s": audio_start,
"audio_end_in_s": audio_end_in_s,
}
logger.info(
"Audio generation request %s: prompt=%r",
request_id,
request.input[:50] + "..." if len(request.input) > 50 else request.input,
)
generator = self.engine_client.generate(
prompt=prompt,
request_id=request_id,
sampling_params_list=sampling_params_list,
output_modalities=["audio"],
)
final_output: OmniRequestOutput | None = None
async for res in generator:
final_output = res
if final_output is None:
return self.create_error_response("No output generated from the model.")
# Extract audio from output
audio_output = None
if hasattr(final_output, "multimodal_output") and final_output.multimodal_output:
audio_output = final_output.multimodal_output
if not audio_output and hasattr(final_output, "request_output"):
if final_output.request_output and hasattr(final_output.request_output, "multimodal_output"):
audio_output = final_output.request_output.multimodal_output
# Check for audio data using either "audio" or "model_outputs" key
audio_key = None
if audio_output:
if "audio" in audio_output:
audio_key = "audio"
elif "model_outputs" in audio_output:
audio_key = "model_outputs"
if not audio_output or audio_key is None:
return self.create_error_response("Audio generation model did not produce audio output.")
audio_tensor = audio_output[audio_key]
sample_rate = audio_output.get("sr", default_sr)
if hasattr(sample_rate, "item"):
sample_rate = sample_rate.item()
# Convert tensor to numpy
if hasattr(audio_tensor, "float"):
audio_tensor = audio_tensor.float().detach().cpu().numpy()
# Squeeze batch dimension if present, but preserve channel dimension for stereo
if audio_tensor.ndim > 1:
audio_tensor = audio_tensor.squeeze()
audio_obj = CreateAudio(
audio_tensor=audio_tensor,
sample_rate=int(sample_rate),
response_format=request.response_format or "wav",
speed=request.speed or 1.0,
stream_format=request.stream_format,
base64_encode=False,
)
audio_response: AudioResponse = self.create_audio(audio_obj)
return Response(content=audio_response.audio_data, media_type=audio_response.media_type)
except asyncio.CancelledError:
return self.create_error_response("Client disconnected")
except ValueError as e:
return self.create_error_response(e)
except Exception as e:
logger.exception("Audio generation failed: %s", e)
return self.create_error_response(f"Audio generation failed: {e}")
@@ -84,6 +84,7 @@ def _validate_path_within_directory(file_path: Path, directory: Path) -> bool:
class OmniOpenAIServingSpeech(OpenAIServing, AudioMixin):
def __init__(self, *args, **kwargs):
self.model_name = kwargs.pop("model_name", None)
super().__init__(*args, **kwargs)
# Initialize uploaded speakers storage
speech_voice_samples_dir = os.environ.get("SPEECH_VOICE_SAMPLES", "/tmp/voice_samples")
@@ -756,6 +757,8 @@ class OmniOpenAIServingSpeech(OpenAIServing, AudioMixin):
request_id = f"speech-{random_uuid()}"
try:
sampling_params_list = self.engine_client.default_sampling_params_list
default_sr = 24000 # Default sample rate for TTS models
if self._is_tts:
# Validate TTS parameters
validation_error = self._validate_tts_request(request)
@@ -781,8 +784,6 @@ class OmniOpenAIServingSpeech(OpenAIServing, AudioMixin):
tts_params.get("task_type", ["unknown"])[0],
)
sampling_params_list = self.engine_client.default_sampling_params_list
generator = self.engine_client.generate(
prompt=prompt,
request_id=request_id,
@@ -809,7 +810,7 @@ class OmniOpenAIServingSpeech(OpenAIServing, AudioMixin):
return self.create_error_response("TTS model did not produce audio output.")
audio_tensor = audio_output[audio_key]
sr_raw = audio_output.get("sr", 24000)
sr_raw = audio_output.get("sr", default_sr)
sr_val = sr_raw[-1] if isinstance(sr_raw, list) and sr_raw else sr_raw
sample_rate = sr_val.item() if hasattr(sr_val, "item") else int(sr_val)
+43 -34
View File
@@ -169,22 +169,46 @@ def _convert_dataclasses_to_dict(obj: Any) -> Any:
return obj
def resolve_model_config_path(model: str) -> str:
"""Resolve the stage config file path from the model name.
def resolve_model_config_path(model_type: str) -> str | None:
"""Resolve the stage config file path from the model type.
Resolves stage configuration path based on the model type and device type.
First tries to find a device-specific YAML file from stage_configs/{device_type}/
directory. If not found, falls back to the default config file.
Args:
model: Model name or path (used to determine model_type)
model_type: Model type string
Returns:
String path to the stage configuration file
String path to the stage configuration file if found, None otherwise
"""
default_config_path = current_omni_platform.get_default_stage_config_path()
config_file_name = f"{model_type}.yaml"
complete_config_path = PROJECT_ROOT / default_config_path / config_file_name
if os.path.exists(complete_config_path):
return str(complete_config_path)
# Fall back to default config
stage_config_file = f"vllm_omni/model_executor/stage_configs/{model_type}.yaml"
stage_config_path = PROJECT_ROOT / stage_config_file
if not os.path.exists(stage_config_path):
return None
return str(stage_config_path)
def resolve_model_type(model: str) -> str:
"""Resolve the model type from the model name.
Args:
model: Model name or path
Returns:
Model type string (e.g. ``"Qwen3TTSForConditionalGeneration"``,
``"StableAudioPipeline"``).
Raises:
ValueError: If model_type cannot be determined
FileNotFoundError: If no stage config file exists for the model type
ValueError: If the model type cannot be determined from any
available configuration file.
"""
# Try to get config from standard transformers format first
try:
@@ -217,42 +241,26 @@ def resolve_model_config_path(model: str) -> str:
f"Please ensure the model has proper configuration files with 'model_type' field"
)
default_config_path = current_omni_platform.get_default_stage_config_path()
model_type_str = f"{model_type}.yaml"
complete_config_path = PROJECT_ROOT / default_config_path / model_type_str
if os.path.exists(complete_config_path):
return str(complete_config_path)
# Fall back to default config
stage_config_file = f"vllm_omni/model_executor/stage_configs/{model_type}.yaml"
stage_config_path = PROJECT_ROOT / stage_config_file
if not os.path.exists(stage_config_path):
return None
return str(stage_config_path)
return model_type
def load_stage_configs_from_model(model: str, base_engine_args: dict | None = None) -> list:
"""Load stage configurations from model's default config file.
Loads stage configurations based on the model type and device type.
First tries to load a device-specific YAML file from stage_configs/{device_type}/
directory. If not found, falls back to the default config file.
def load_stage_configs_from_model(config_path: str | None, base_engine_args: dict | None = None) -> list:
"""Load stage configurations from a resolved config file path.
Args:
model: Model name or path (used to determine model_type)
config_path: Path to the YAML configuration file, or None.
When None, returns an empty list.
base_engine_args: Optional engine arguments to merge with stage configs.
Returns:
List of stage configuration dictionaries
Raises:
FileNotFoundError: If no stage config file exists for the model type
List of stage configuration dictionaries, or empty list if
config_path is None.
"""
if base_engine_args is None:
base_engine_args = {}
stage_config_path = resolve_model_config_path(model)
if stage_config_path is None:
if config_path is None:
return []
stage_configs = load_stage_configs_from_yaml(config_path=stage_config_path, base_engine_args=base_engine_args)
stage_configs = load_stage_configs_from_yaml(config_path=config_path, base_engine_args=base_engine_args)
return stage_configs
@@ -306,9 +314,10 @@ def load_and_resolve_stage_configs(
Returns:
Tuple of (config_path, stage_configs)
"""
model_type = resolve_model_type(model)
if stage_configs_path is None:
config_path = resolve_model_config_path(model)
stage_configs = load_stage_configs_from_model(model, base_engine_args=kwargs)
config_path = resolve_model_config_path(model_type)
stage_configs = load_stage_configs_from_model(config_path, base_engine_args=kwargs)
if not stage_configs:
if default_stage_cfg_factory is not None:
default_stage_cfg = default_stage_cfg_factory()