VibeVoice is Microsoft's open-source TTS + ASR research stack. This guide covers system architecture, full configuration reference, deployment + scaling guidance, security posture, performance tuning, developer internals, and integration points. It is written for operators running VibeVoice in a service, developers extending the codebase, and architects evaluating the design.
For end-user usage, scripts, and recipes, see the VibeVoice User Guide.
1. Architecture Overview
VibeVoice is a two-track speech model packaged as a single Python distribution. One track produces speech (TTS), the other consumes it (ASR). Both tracks share an identity — a Qwen2.5 decoder-only LLM backbone wrapped by VibeVoice's own acoustic and semantic tokenizers, plus a diffusion head — but expose different generation modes through different model classes and processors.
High-level dataflow
+-----------------------------------------------+
| TTS Path |
| |
text -> Streaming -> | VibeVoiceStreamingProcessor |
Processor | - apply_chat_template (Qwen2 tokenizer) |
| - cached prompt (.pt voice preset) |
| |
| VibeVoiceStreamingForConditionalGeneration- |
| Inference |
| - language_model (Qwen2) |
| - tts_language_model (parallel head) |
| - acoustic / semantic tokenizers |
| - diffusion head + DPMSolverMultistep |
| |
| AudioStreamer (per-batch queue) |
+-----------------------------------------------+
|
v
PCM bytes (24 kHz s16le mono)
^
|
+-----------------------------------------------+
| ASR Path |
| |
audio -> FFmpeg -> 24kHz | VibeVoiceASRProcessor |
stdin pipe | - load + normalize audio |
| - VAE-tokenize speech (semantic + acoustic)|
| - context_info -> system prompt |
| |
| VibeVoiceASRForConditionalGeneration |
| - LLM-only generate over multimodal seq |
| - speech tokens injected via |
| acoustic_input_mask |
| |
| post_process_transcription (JSON parse) |
+-----------------------------------------------+
|
v
segments [{Start, End, Speaker, Content}]
Both paths share vibevoice/processor/audio_utils.py, vibevoice/modular/configuration_*.py, and the underlying vibevoice/modular/modular_vibevoice_tokenizer.py (acoustic + semantic VAEs). They diverge at the model class — TTS uses streaming inference with a diffusion head; ASR uses LLM generation with multimodal speech embeddings.
Note
The unified
VibeVoiceStreamingModel.forward()is intentionally disabled (raisesRuntimeError). All inference goes throughmodel.language_model(...),model.tts_language_model(...), ormodel.generate(...). This is a design invariant that prevents callers from bypassing the streaming machinery accidentally.
Component inventory
| Component | Path | Role |
|---|---|---|
| Top-level package | vibevoice/__init__.py |
Re-exports streaming TTS classes for from vibevoice import .... |
| Modular models | vibevoice/modular/ |
Configurations, model classes, streamer, diffusion head, tokenizer (acoustic + semantic VAEs), text tokenizer. 11 files, ~199KB. |
| Processors | vibevoice/processor/ |
Audio I/O (FFmpeg), audio normalizer, ASR processor, TTS processor, streaming processor, tokenizer processor. 6 files, ~95KB. |
| Schedulers | vibevoice/schedule/ |
DPMSolverMultistepScheduler, timestep sampler. 2 files, ~51KB. |
| Default configs | vibevoice/configs/ |
qwen2.5_1.5b_64k.json, qwen2.5_7b_32k.json. 2 files, ~5KB. |
| vLLM plugin | vllm_plugin/ |
vLLM model registration, audio input mapper, multimodal model class. 3 files, ~59KB. |
| Streaming demo + web | demo/web/ |
FastAPI app (app.py), HTML player (index.html). |
| Gradio + CLI demos | demo/ |
realtime_model_inference_from_file.py, vibevoice_asr_gradio_demo.py, vibevoice_asr_inference_from_file.py, vibevoice_realtime_demo.py. |
| LoRA finetune | finetuning-asr/ |
lora_finetune.py, inference_lora.py, toy dataset. |
| Voice presets | demo/voices/streaming_model/ |
23 .pt files (Git LFS) covering 11 languages. |
| Repo metadata | pyproject.toml, LICENSE, SECURITY.md, CONTRIBUTING.md |
Standard OSS scaffolding. |
Technology stack
| Layer | Choice | Why |
|---|---|---|
| Language | Python 3.10+ | Hugging Face / PyTorch ecosystem. |
| Deep-learning framework | PyTorch | Native MPS / XPU / CUDA support, mature ecosystem. |
| Backbone LLM | Qwen2 (1.5B and 7B variants) | Strong multilingual, permissive license, supported by transformers. |
| Attention | flash-attention-2 (CUDA), SDPA (MPS / CPU / XPU), eager (debug) | Auto-selected per device. |
| Speech tokenizer | Custom acoustic + semantic VAEs (RMSNorm, depthwise conv mixer) | Continuous-token speech for both ASR ingest and TTS synthesis. |
| Diffusion head | 4-layer (default), v-prediction, cosine beta schedule | Conditional speech latent generation in TTS. |
| Audio I/O | FFmpeg (subprocess) | Universal format coverage; stdin-pipe for byte input. |
| Web framework | FastAPI + Starlette + uvicorn | Streaming TTS WebSocket endpoint. |
| UI | Gradio 6.0+ for ASR demo; static HTML + WebSocket for TTS player | Lightweight; both demos minimize external state. |
| Multi-tenant serving | vLLM (via vllm.general_plugins entry point) |
High-throughput batched ASR. |
| Fine-tuning | PEFT LoRA + transformers Trainer | Memory-efficient adaptation. |
| Acceleration (optional) | Liger Kernel for Qwen2 (RoPE / RMSNorm / SwiGLU) | Faster attention/MLP layers in the ASR Gradio demo. |
Data flow — streaming TTS
- HTTP/WS request lands at
demo/web/app.py:websocket_stream. Query params:text,cfg,steps,voice. - The endpoint acquires the per-app
asyncio.Lock. Concurrent clients are rejected with WS code 1013. StreamingTTSService.stream()runs the full pipeline:- Look up the voice preset (
_get_voice_resources). - Run
VibeVoiceStreamingProcessor.process_input_with_cached_prompt(...)— produces token IDs and attention mask. - Spawn a background
threading.Threadthat callsmodel.generate(...)with anAudioStreamer(batch_size=1)sink. - The generator yields decoded float32 chunks; the WS handler converts to int16 PCM via
chunk_to_pcm16and writes binary frames.
- Look up the voice preset (
- JSON log frames are enqueued into a
queue.Queuefrom the worker thread and flushed to the WS by the main coroutine between binary writes. - Stop is cooperative: a
threading.Eventis passed in viastop_check_fnso generation aborts on disconnect.
Data flow — long-form ASR
- Client supplies an audio file path, bytes, ndarray, or HF-dataset name.
audio_utils.load_audio_use_ffmpeg()shells out toffmpeg -loglevel error -nostdin ... -f s16le -ac 1 -ar 24000 -to produce 24 kHz mono float32. Concurrency is throttled by_FFMPEG_SEM(athreading.Semaphoregated onVIBEVOICE_FFMPEG_MAX_CONCURRENCY).AudioNormalizerapplies a target dB-FS scaling.VibeVoiceASRProcessorbuilds the prompt:- System prompt (with optional
context_infohotword block). - Speech token region delimited by
<|object_ref_start|>...<|object_ref_end|>(or by the corresponding IDs in the fast tokenizer). - Generation prompt (
<|im_start|>assistant\n).
- System prompt (with optional
VibeVoiceASRForConditionalGeneration.generate()runs greedy / sampled / beam-search decoding.- Output text contains a JSON array of
{Start, End, Speaker, Content}objects;processor.post_process_transcription()parses it.
Note
Both tokenizers (acoustic and semantic) are frozen during LoRA fine-tuning. Only the Qwen2 attention and MLP projections receive adapter updates. This keeps the speech-token vocabulary stable across fine-tunes — important for swapping LoRA adapters at inference time.
2. System Requirements
Hardware
| Tier | TTS-Realtime-0.5B | ASR-7B |
|---|---|---|
| Dev box | 8 GB VRAM (any CUDA-capable card or M1/M2 Mac) | 16 GB VRAM (RTX 3090 / 4080 / A100 40GB) |
| Production single-tenant | 16 GB VRAM, latency optimized | 24 GB VRAM, throughput optimized |
| Multi-tenant (vLLM) | Not the typical workload | 80 GB VRAM (A100 80GB or H100) for high concurrency |
| CPU fallback | Workable for short prompts | Not recommended; 5-10x slower than CUDA |
| MPS (Apple) | Yes (float32, SDPA) | Marginally workable for short audio |
| XPU (Intel) | Yes (float32, SDPA) | Yes (float32, SDPA) |
Software
| Required | Version |
|---|---|
| Python | 3.10+ |
| PyTorch | 2.1+ recommended (2.4+ for the most recent attention APIs) |
| transformers | 4.40+ (Qwen2 baseline; some demos require 4.45+ for Liger Kernel) |
| FFmpeg | 4.x or newer; must be on PATH |
| Git LFS | For voice preset checkout |
| Optional | Purpose |
|---|---|
| flash-attn | CUDA-only attention acceleration |
| liger-kernel | Qwen2 RoPE/RMSNorm/SwiGLU fast paths in the ASR Gradio demo |
| pydub | MP3 re-encoding of audio segments in the ASR Gradio demo |
| peft | LoRA fine-tuning |
| diffusers | Pulls DPMSolverMultistepScheduler base classes |
| vllm | High-throughput ASR serving |
Network
The repo's only outbound dependencies are:
- Hugging Face Hub (model + processor downloads).
- GitHub release attachments (experimental voice packs in
download_experimental_voices.sh).
When deploying behind a firewall, mirror both into your registry and pass local paths to --model_path.
3. Configuration Reference
3.1 CLI flags — every argument across every entry point
| Script | Flag | Type | Default | Purpose |
|---|---|---|---|---|
finetuning-asr/inference_lora.py |
--base_model |
str | microsoft/VibeVoice-ASR |
Path to base pretrained model. |
finetuning-asr/inference_lora.py |
--lora_path |
str | (required) | Path to LoRA adapter weights. |
finetuning-asr/inference_lora.py |
--audio_file |
str | (required) | Path to audio file to transcribe. |
finetuning-asr/inference_lora.py |
--context_info |
str | None |
Hotword block, e.g. Hotwords: Tea Brew, Aiden Host. |
finetuning-asr/inference_lora.py |
--max_new_tokens |
int | 4096 |
Generation cap. |
finetuning-asr/inference_lora.py |
--temperature |
float | 0.0 |
Sampling temperature; 0 = greedy. |
finetuning-asr/inference_lora.py |
--device |
str | auto | Device override. |
demo/realtime_model_inference_from_file.py |
--model_path |
str | microsoft/VibeVoice-Realtime-0.5B |
Streaming TTS checkpoint. |
demo/realtime_model_inference_from_file.py |
--txt_path |
str | demo/text_examples/1p_vibevoice.txt |
Script file. |
demo/realtime_model_inference_from_file.py |
--speaker_name |
str | Wayne |
Voice preset substring match. |
demo/realtime_model_inference_from_file.py |
--output_dir |
str | ./outputs |
WAV output dir. |
demo/realtime_model_inference_from_file.py |
--device |
str | auto | cuda / mps / cpu. |
demo/realtime_model_inference_from_file.py |
--cfg_scale |
float | 1.5 |
Classifier-Free Guidance. |
demo/vibevoice_asr_gradio_demo.py |
--model_path |
str | microsoft/VibeVoice-ASR |
ASR checkpoint. |
demo/vibevoice_asr_gradio_demo.py |
--attn_implementation |
str | flash_attention_2 |
Attention backend. |
demo/vibevoice_asr_gradio_demo.py |
--max_new_tokens |
int | 32768 |
Default UI slider value. |
demo/vibevoice_asr_gradio_demo.py |
--host |
str | 0.0.0.0 |
Bind address. |
demo/vibevoice_asr_gradio_demo.py |
--port |
int | 7860 |
Bind port. |
demo/vibevoice_asr_gradio_demo.py |
--share |
bool | False | Enable Gradio public tunnel. |
demo/vibevoice_asr_inference_from_file.py |
--model_path |
str | (required) | ASR checkpoint. |
demo/vibevoice_asr_inference_from_file.py |
--audio_files |
str+ | none | One or more file paths. |
demo/vibevoice_asr_inference_from_file.py |
--audio_dir |
str | none | Directory walk. |
demo/vibevoice_asr_inference_from_file.py |
--dataset |
str | none | HF dataset name. |
demo/vibevoice_asr_inference_from_file.py |
--split |
str | test |
Dataset split. |
demo/vibevoice_asr_inference_from_file.py |
--max_duration |
float | 3600.0 |
Max seconds per concatenated audio. |
demo/vibevoice_asr_inference_from_file.py |
--batch_size |
int | 2 |
Files per batch. |
demo/vibevoice_asr_inference_from_file.py |
--device |
str | auto | cuda/cpu/mps/xpu/auto. |
demo/vibevoice_asr_inference_from_file.py |
--max_new_tokens |
int | 32768 |
Generation cap. |
demo/vibevoice_asr_inference_from_file.py |
--temperature |
float | 0.0 |
0 = greedy. |
demo/vibevoice_asr_inference_from_file.py |
--top_p |
float | 1.0 |
Nucleus sampling. |
demo/vibevoice_asr_inference_from_file.py |
--num_beams |
int | 1 |
Beam-search width. |
demo/vibevoice_asr_inference_from_file.py |
--attn_implementation |
str | auto |
Attention backend. |
demo/vibevoice_realtime_demo.py |
--port |
int | 3000 |
uvicorn port. |
demo/vibevoice_realtime_demo.py |
--model_path |
str | microsoft/VibeVoice-Realtime-0.5B |
Sets MODEL_PATH. |
demo/vibevoice_realtime_demo.py |
--device |
str | cuda |
Sets MODEL_DEVICE. |
demo/vibevoice_realtime_demo.py |
--reload |
bool | False | Enable uvicorn auto-reload. |
3.2 Environment variables
| Variable | Type | Default | Source | Purpose |
|---|---|---|---|---|
MODEL_PATH |
str | (none) | demo/web/app.py:340 |
Streaming TTS checkpoint. Required. |
MODEL_DEVICE |
str | cuda |
demo/web/app.py:344 |
Streaming TTS device. |
VOICE_PRESET |
str | (none, falls back to en-Carter_man) |
demo/web/app.py:122 |
Default voice for the streaming server. |
VIBEVOICE_MAX_AUDIO_DURATION |
float (sec) | 3660 |
vllm_plugin/inputs.py:20 |
Hard cap on input duration. |
VIBEVOICE_FFMPEG_MAX_CONCURRENCY |
int | 0 (no limit) |
vibevoice/processor/audio_utils.py:82 |
Cap on concurrent FFmpeg subprocesses. |
VIBEVOICE_USE_MEAN |
bool (truthy) | unset | vllm_plugin/model.py:264 |
Use mean pooling over speech tokens (issue #256). |
OPTIMIZE_FOR_SPEED |
int (0/1) | 0 |
vibevoice/modular/modular_vibevoice_tokenizer.py:30 |
Drop normalization in tokenizer for speed. |
USERNAME |
str | user |
demo/vibevoice_asr_gradio_demo.py:26 |
Windows-only USER shim. |
TEMP |
str | . |
demo/vibevoice_asr_gradio_demo.py:27 |
Windows-only base for TORCHINDUCTOR_CACHE_DIR. |
Important
Two of these variables silently change runtime behavior with no log output:
OPTIMIZE_FOR_SPEED=1andVIBEVOICE_USE_MEAN. Set them only after benchmarking — a default deployment should leave both unset.
3.3 Model JSON configs (read by from_pretrained)
Two reference configs ship in vibevoice/configs/. They define the architecture; the model.safetensors carries the weights.
qwen2.5_1.5b_64k.json — used by streaming TTS
| Group | Key | Value | Notes |
|---|---|---|---|
| top | model_type |
vibepod |
Composition wrapper. |
| top | _attn_implementation_autoset |
true |
Attention is decided at load time. |
| top | acoustic_vae_dim |
64 |
Latent dim of acoustic tokenizer. |
| top | semantic_vae_dim |
128 |
Latent dim of semantic tokenizer. |
| top | torch_dtype |
bfloat16 |
Default load dtype. |
| acoustic_tokenizer | causal |
true |
Streaming-friendly. |
| acoustic_tokenizer | vae_dim |
64 |
|
| acoustic_tokenizer | encoder_n_filters |
32 |
|
| acoustic_tokenizer | encoder_ratios |
[8, 5, 5, 4, 2, 2] |
Total 6400× downsample. |
| acoustic_tokenizer | encoder_depths |
3-3-3-3-3-3-8 |
Per-stage block counts. |
| acoustic_tokenizer | mixer_layer |
depthwise_conv |
|
| acoustic_tokenizer | layernorm |
RMSNorm |
|
| acoustic_tokenizer | disable_last_norm |
true |
|
| acoustic_tokenizer | fix_std |
0.5 |
Gaussian latent std. |
| acoustic_tokenizer | std_dist_type |
gaussian |
|
| acoustic_tokenizer | model_type |
vibepod_acoustic_tokenizer |
|
| semantic_tokenizer | vae_dim |
128 |
|
| semantic_tokenizer | fix_std |
0 |
|
| semantic_tokenizer | std_dist_type |
none |
|
| semantic_tokenizer | model_type |
vibepod_semantic_tokenizer |
|
| decoder (Qwen2) | hidden_size |
1536 |
|
| decoder (Qwen2) | intermediate_size |
8960 |
|
| decoder (Qwen2) | max_position_embeddings |
65536 |
64K context. |
| decoder (Qwen2) | num_attention_heads |
12 |
|
| decoder (Qwen2) | num_hidden_layers |
28 |
|
| decoder (Qwen2) | num_key_value_heads |
2 |
GQA ratio 6. |
| decoder (Qwen2) | rope_theta |
1000000.0 |
|
| decoder (Qwen2) | tie_word_embeddings |
true |
|
| decoder (Qwen2) | vocab_size |
151936 |
|
| diffusion_head | head_layers |
4 |
|
| diffusion_head | head_ffn_ratio |
3.0 |
|
| diffusion_head | hidden_size |
1536 |
Matches decoder. |
| diffusion_head | latent_size |
64 |
|
| diffusion_head | prediction_type |
v_prediction |
|
| diffusion_head | diffusion_type |
ddpm |
|
| diffusion_head | ddpm_num_steps |
1000 |
Training. |
| diffusion_head | ddpm_num_inference_steps |
20 |
Default; the streaming server overrides to 5 for latency. |
| diffusion_head | ddpm_beta_schedule |
cosine |
|
| diffusion_head | ddpm_batch_mul |
4 |
|
| diffusion_head | model_type |
vibepod_diffusion_head |
qwen2.5_7b_32k.json — used by long-form ASR
Identical structure; key deltas:
| Key | 1.5B value | 7B value |
|---|---|---|
| decoder.hidden_size | 1536 | 3584 |
| decoder.intermediate_size | 8960 | 18944 |
| decoder.num_attention_heads | 12 | 28 |
| decoder.num_hidden_layers | 28 | 28 |
| decoder.num_key_value_heads | 2 | 4 |
| decoder.max_position_embeddings | 65536 | 32768 |
| decoder.tie_word_embeddings | true | false |
| decoder.vocab_size | 151936 | 152064 |
| decoder.transformers_version | (absent) | 4.40.1 |
| diffusion_head.hidden_size | 1536 | 3584 |
The 7B config also sets use_mrope: false on the decoder.
3.4 LoRA configuration (finetuning-asr/lora_finetune.py)
| Argument class | Field | Default |
|---|---|---|
ModelArguments |
model_path |
microsoft/VibeVoice-ASR |
DataArguments |
data_dir |
./toy_dataset |
DataArguments |
max_audio_length |
None (no cap) |
DataArguments |
use_customized_context |
True |
LoraArguments |
lora_r |
16 |
LoraArguments |
lora_alpha |
32 |
LoraArguments |
lora_dropout |
0.05 |
| LoRA target modules | (hardcoded) | q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj |
LoraConfig |
bias |
"none" |
LoraConfig |
task_type |
TaskType.CAUSAL_LM |
The script forces dataloader_num_workers = 0 and remove_unused_columns = False regardless of TrainingArguments user input, because audio loading is not multiprocessing-safe in this code path.
3.5 HTTP API surface (streaming TTS, demo/web/app.py)
| Method | Path | Function | Auth | Returns |
|---|---|---|---|---|
| GET | / |
index |
none | index.html (FileResponse). |
| GET | /config |
get_config |
none | {"voices": [...], "default_voice": "..."}. |
| WebSocket | /stream |
websocket_stream |
none | Binary PCM frames + JSON log frames; closes with code 1013 if busy. |
There is no auth, no rate limit, and no CORS configuration baked in — the demo is intended to run behind your own gateway.
Warning
Do not expose
demo/web/app.pydirectly to the public internet. It has zero authentication and a single global lock, so anyone can both (a) tie up your GPU with a long script and (b) trivially DoS the service.
4. Deployment & Scaling
4.1 Single-host streaming TTS
The simplest deployment is a single CUDA host running:
MODEL_PATH=microsoft/VibeVoice-Realtime-0.5B \
MODEL_DEVICE=cuda \
VOICE_PRESET=en-Carter_man \
python demo/vibevoice_realtime_demo.py --port 3000
Front this with nginx for TLS termination + WebSocket proxying. Example nginx block:
upstream vibevoice {
server 127.0.0.1:3000;
}
server {
listen 443 ssl http2;
location / {
proxy_pass http://vibevoice;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
}
Important
The default WebSocket idle timeout in many proxies is 60s — too short for long generations. Bump
proxy_read_timeoutandproxy_send_timeoutto at least the longest TTS request you expect (5 minutes is a safe default for read-aloud workloads).
4.2 Horizontal scaling — TTS
Each FastAPI process holds a single global asyncio.Lock. Concurrency therefore equals number of processes (not threads). Two patterns:
- Worker pool: launch
--workers Nuvicorn processes against the same socket, each with its own model copy. Memory cost: N × ~6 GB on bfloat16. Best when you have a single large GPU and want N concurrent streams. - Multi-host: deploy several boxes behind a sticky load balancer (Layer 7 with cookie or path-based routing). The streaming WebSocket holds state across messages, so sticky routing is required.
4.3 vLLM serving for ASR
For high-throughput ASR, register the plugin and launch vLLM:
pip install vllm
python -m vllm.entrypoints.openai.api_server \
--model microsoft/VibeVoice-ASR \
--max-model-len 32768 \
--gpu-memory-utilization 0.85 \
--max-num-seqs 8
The plugin auto-loads via pyproject.toml's [project.entry-points."vllm.general_plugins"] table and registers:
VibeVoiceConfigwithAutoConfig.VibeVoiceASRTextTokenizerFast(fast) andQwen2Tokenizer(slow) withAutoTokenizer.Qwen2AudioProcessorwithAutoProcessor.VibeVoiceForCausalLMwith vLLM'sModelRegistryunder namesVibeVoiceandVibeVoiceForASRTraining.
Audio inputs come in via the multi_modal_data parameter on the OpenAI-compatible request (audio/file/bytes/array, see vllm_plugin/inputs.py).
Note
Throughput at 8 concurrent requests on an A100-80GB approaches ~3-4× single-stream PyTorch baseline, mostly from vLLM's continuous batching.
4.4 Sizing the GPU
| Workload | Concurrency | Suggested GPU |
|---|---|---|
| Single-user dev / demo (TTS) | 1 | RTX 3060 12GB / RTX 4070 |
| Production read-aloud (TTS) | 4-8 | RTX 4090 24GB or A10 24GB |
| Single-user ASR (1-hour audio) | 1 | RTX 3090/4090 24GB |
| Multi-tenant ASR (vLLM) | 8-16 | A100 80GB / H100 80GB |
4.5 Container deployment
There is no first-party Dockerfile in the repo. A minimal recipe:
FROM nvidia/cuda:12.1.0-cudnn8-devel-ubuntu22.04
RUN apt-get update && apt-get install -y python3.10 python3-pip ffmpeg git git-lfs
WORKDIR /app
COPY . /app
RUN pip install -e . && \
pip install transformers peft accelerate diffusers \
soundfile pydub fastapi uvicorn[standard] websockets gradio && \
pip install flash-attn --no-build-isolation
RUN git lfs install && git lfs pull
ENV MODEL_PATH=microsoft/VibeVoice-Realtime-0.5B \
MODEL_DEVICE=cuda \
VIBEVOICE_FFMPEG_MAX_CONCURRENCY=4
EXPOSE 3000
CMD ["python", "demo/vibevoice_realtime_demo.py", "--port", "3000"]
Caution
Issue #339 reports the Gradio ASR demo crashing under HTTP-only Docker setups due to URL scheme rewriting. Always front the Gradio demo with TLS in container environments.
5. Operations
5.1 Health checks
# Streaming TTS
curl -fsS http://localhost:3000/config
# Expected
# {"voices": ["de-Spk0_man", ..., "en-Carter_man", ...], "default_voice": "en-Carter_man"}
The /config endpoint dereferences service.voice_presets, which is only populated after service.load() completes. A 500/disconnect therefore indicates the model has not finished loading.
Tip
For Kubernetes, use
httpGetwith/configfor bothreadinessProbeandlivenessProbe. Allow at least 120 seconds for the initial model load before the readiness probe starts.
5.2 Logs
The streaming TTS server emits structured stdout logs:
[startup] Loading processor from <path>— early init.[startup] Found N voice presets— voice scan complete.[startup] Loading prefilled prompt from <path>— voice cached.[startup] Model ready.— service is accepting traffic.- Per-request:
Client connected, text='...'and JSON event frames over WebSocket (backend_request_received,model_progress,backend_first_chunk_sent,backend_stream_complete,client_disconnected,backend_error).
Pipe to a log aggregator that handles structured JSON; the WebSocket events are valuable telemetry.
5.3 Metrics worth tracking
| Metric | Source | Why it matters |
|---|---|---|
| Time to first chunk (sec) | Diff between backend_request_received and backend_first_chunk_sent |
Most user-visible latency. |
| Generation throughput (chunk_sec / wall_sec) | model_progress events |
Inverse of RTF; tracks GPU health. |
| Lock-contended rejections | Count of WS code 1013 closes | Indicates need to scale workers. |
| FFmpeg subprocess count | _FFMPEG_SEM permits taken |
Set VIBEVOICE_FFMPEG_MAX_CONCURRENCY to a known cap. |
| GPU memory headroom | nvidia-smi |
Spikes during long generations; OOM risk. |
5.4 Backup & state
VibeVoice is stateless. Voice presets and model weights are content-addressable (Hugging Face hashes); nothing per-tenant lives on disk. The only state worth checkpointing is:
- LoRA adapter weights (after fine-tuning) → push to HF Hub or copy to S3.
- Custom voice presets (
.ptfiles) → copy to S3.
5.5 Runbook checklist for a new deployment
- [ ] Provision a GPU host with CUDA 12.1+ and FFmpeg installed.
- [ ] Clone the repo and
git lfs pullto fetch voice presets. - [ ] Install the package + flash-attn.
- [ ] Pre-warm
microsoft/VibeVoice-Realtime-0.5B(or your local copy) viahuggingface-cli download. - [ ] Set
MODEL_PATH,MODEL_DEVICE,VOICE_PRESET,VIBEVOICE_FFMPEG_MAX_CONCURRENCY. - [ ] Launch through
demo/vibevoice_realtime_demo.py(or an equivalent supervisord / systemd unit). - [ ] Wait for
[startup] Model ready.before flipping the load balancer. - [ ] Verify
GET /configreturns 200 with the expected voice list. - [ ] Verify a short WebSocket TTS round-trip (10-second script) under target latency.
- [ ] Configure Prometheus / Grafana alerts on per-request latency p95.
Tip
Pre-warm the voice cache by hitting
/streamwithtext=Hi&voice=<preset>for every preset you expect to use. The first request per voice triggers atorch.load(.pt, weights_only=False)from disk; subsequent requests reuse the cached object.
5.6 Upgrade procedure
- Snapshot any LoRA adapters and custom voices.
git fetch && git checkout <new-tag>in a parallel directory.- Run a smoke test:
python demo/realtime_model_inference_from_file.py ...against the new code. - Cut traffic over (DNS or load-balancer flip).
- Keep the old install for 24h as a rollback.
6. Performance
6.1 TTS latency budget (CUDA + flash-attention-2, RTX 4090)
| Stage | Time |
|---|---|
| FastAPI accept + lock | <5 ms |
| Tokenize text | ~10 ms |
| Voice preset lookup (cache hit) | <1 ms |
| First diffusion step (5 steps total) | ~150 ms |
| First audio chunk emitted | ~200 ms |
| Subsequent chunks (per ~1 sec audio) | ~250 ms wall |
Time-to-first-byte target: ≤ 250 ms. Real Time Factor at this configuration is ~0.3 — generation runs ~3× faster than playback.
6.2 ASR throughput
| Audio length | Single-stream wall time (RTX 4090, bfloat16, flash-attention-2) |
|---|---|
| 1 minute | ~3-5 s |
| 10 minutes | ~25-40 s |
| 1 hour | ~3-5 minutes |
vLLM raises throughput sublinearly with batch size. Past ~8 concurrent requests, FFmpeg becomes the bottleneck — set VIBEVOICE_FFMPEG_MAX_CONCURRENCY to your CPU core count.
6.3 Memory profile
| Component | bfloat16 | float32 |
|---|---|---|
| TTS-Realtime-0.5B weights | ~1 GB | ~2 GB |
| TTS KV cache (64K context) | ~1 GB | ~2 GB |
| ASR-7B weights | ~14 GB | ~28 GB |
| ASR KV cache (32K context, batch 1) | ~1.5 GB | ~3 GB |
6.4 Tuning levers
Tip
Production tuning, in order of impact: (1) attention implementation (flash-attention-2 vs SDPA — measure both), (2)
cfg_scale(lower = faster, less expressive), (3)inference_steps(5 default, raise for fidelity), (4) Liger Kernel in the ASR path, (5)OPTIMIZE_FOR_SPEED=1for tokenizer speedup.
| Lever | Effect | Risk |
|---|---|---|
inference_steps 5 → 10 |
~30% better fidelity | ~2× latency |
cfg_scale 1.5 → 1.0 |
~10% lower latency | More monotone delivery |
attn_implementation SDPA → flash-attention-2 |
~25-40% faster | Requires CUDA + nvcc |
OPTIMIZE_FOR_SPEED=1 |
~5-10% faster tokenizer | Slight quality drop |
| Liger Kernel (ASR Gradio) | ~15-25% faster Qwen2 layers | Requires liger-kernel package |
VIBEVOICE_FFMPEG_MAX_CONCURRENCY=4 |
Caps subprocess pressure | None (cap to ≤ CPU cores) |
do_sample=false, num_beams=1 (ASR) |
Deterministic, fastest | Less diverse; usually preferred |
num_beams=4 (ASR) |
Best WER on hard audio | ~3-4× slower |
6.5 Profiling
import torch
with torch.profiler.profile(
activities=[torch.profiler.ProfilerActivity.CPU, torch.profiler.ProfilerActivity.CUDA],
record_shapes=True,
) as prof:
out = model.generate(**inputs, max_new_tokens=256)
print(prof.key_averages().table(sort_by="cuda_time_total", row_limit=20))
Hot spots in our profiling: Qwen2 attention (~50%), diffusion head (TTS only, ~25%), tokenizer encode (~10%), audio normalization (~5%). Memory peaks during the prefill pass — tune --max_new_tokens if you see OOM mid-generation.
7. Security
7.1 Threat model
VibeVoice is a research model release. Three primary threat surfaces apply if you deploy it:
- Model misuse — voice cloning + TTS produces realistic speech in the trained voices. Mitigation: deploy behind authenticated APIs, retain server-side logs of
(text, voice)tuples. - Data exfiltration via context_info — the ASR system prompt accepts free-form text; in a multi-tenant deployment, this could be used to inject system-prompt content. Mitigation: sanitize / quote-escape user-supplied hotwords before passing through.
- Resource exhaustion — the streaming server has no rate limit and runs unbounded scripts on the GPU. Mitigation: max-text-length guard, per-IP rate limit, max-concurrency cap, queue rejections.
7.2 Network exposure
The streaming server (demo/web/app.py):
- Exposes WebSocket
/streamwith no authentication. - Exposes
/configwith no authentication (returns voice list, useful for fingerprinting). - Has no CORS configuration; FastAPI defaults to no CORS allowance.
Warning
Never expose
demo/web/app.pydirectly to the internet. Wrap with an authenticated reverse proxy (oauth2-proxy, Cloudflare Access, your own API gateway). Even the demoindex.htmlpage is intended for local-network use.
7.3 Secret management
The repo currently consumes no secrets at runtime:
- No API keys.
- No database connection strings.
- No Hugging Face token requirement (public models).
The only sensitive environment input is MODEL_PATH if it points to a local directory containing weights you do not want to leak. Bind-mount that directory read-only.
7.4 Input validation
| Surface | Validation enforced | Validation not enforced |
|---|---|---|
/stream?text= |
None — raw text passed to model. | Text length, language, content. |
/stream?voice= |
Only existing preset keys are honored; unknown defaults silently to en-Carter_man. |
None |
/stream?cfg= |
Coerced to float; ≤ 0 falls back to 1.5. | Upper bound (extreme values produce artefacts but no crash). |
/stream?steps= |
Coerced to int > 0; otherwise None. | Upper bound. |
| ASR audio bytes | VIBEVOICE_MAX_AUDIO_DURATION enforced. |
No file-format whitelist beyond FFmpeg's parser. |
ASR context_info |
Free text. | None — tenant separation must come from the deployer. |
Caution
Untrusted audio bytes are decoded by FFmpeg via
subprocess.run(...). While this is a sandboxed binary, FFmpeg has had its share of CVEs. Keep FFmpeg patched and consider running the API container with seccomp /--read-onlyfilesystem and a dropped capability set (noCAP_NET_RAW, noCAP_SYS_ADMIN).
7.5 Supply chain
pyproject.toml declares the project. The repo's external dependencies (PyTorch, transformers, diffusers, FFmpeg binary, optional flash-attn / vllm / liger-kernel) all come from public registries. Pin exact versions in your deployment lockfile and mirror to a private index.
The vLLM plugin's register_vibevoice() runs at import time for every vLLM process. Treat this as a code-execution surface — only add the plugin to vLLM environments you trust.
7.6 Reporting vulnerabilities
SECURITY.md is the canonical channel. Issue #341 (closed) noted earlier guidance was unclear; check the latest copy on main for the reporting email address and PGP key.
8. Developer Internals
8.1 Repository structure
VibeVoice/
├── pyproject.toml # Package metadata + entry points (vLLM)
├── LICENSE # MIT
├── SECURITY.md # Vulnerability reporting
├── CONTRIBUTING.md # PR guide
├── README.md # (intentionally minimal in this commit)
├── vibevoice/
│ ├── __init__.py # Re-exports streaming TTS classes
│ ├── modular/ # Model code
│ │ ├── configuration_vibevoice.py # VibeVoiceConfig + sub-configs
│ │ ├── configuration_vibevoice_streaming.py # Streaming variant
│ │ ├── modeling_vibevoice.py # Base TTS model
│ │ ├── modeling_vibevoice_asr.py # ASR model (separate path)
│ │ ├── modeling_vibevoice_streaming.py # Streaming TTS submodel (forward disabled)
│ │ ├── modeling_vibevoice_streaming_inference.py # Inference wrapper with .generate()
│ │ ├── modular_vibevoice_diffusion_head.py # Diffusion head MLP
│ │ ├── modular_vibevoice_text_tokenizer.py # Text tokenizer (Qwen2-derived) + ASR variant
│ │ ├── modular_vibevoice_tokenizer.py # Acoustic + semantic VAEs
│ │ └── streamer.py # AudioStreamer + AsyncAudioStreamer
│ ├── processor/ # Audio + tokenization
│ │ ├── audio_utils.py # FFmpeg shell-out + AudioNormalizer
│ │ ├── vibevoice_processor.py # Long-form TTS processor (multi-speaker)
│ │ ├── vibevoice_streaming_processor.py # Streaming TTS processor
│ │ ├── vibevoice_asr_processor.py # ASR processor (system prompt + speech tokens)
│ │ └── vibevoice_tokenizer_processor.py # File I/O for .wav/.mp3/.pt/.npy/.npz
│ ├── schedule/ # Diffusion scheduler
│ │ ├── dpm_solver.py # DPMSolverMultistepScheduler
│ │ └── timestep_sampler.py # Training-time sampling
│ ├── configs/
│ │ ├── qwen2.5_1.5b_64k.json
│ │ └── qwen2.5_7b_32k.json
│ └── scripts/ # (placeholder)
├── vllm_plugin/
│ ├── __init__.py # register_vibevoice() — entry point
│ ├── inputs.py # vibevoice_audio_input_mapper
│ └── model.py # VibeVoiceForCausalLM (vLLM model class)
├── demo/
│ ├── realtime_model_inference_from_file.py
│ ├── vibevoice_asr_gradio_demo.py
│ ├── vibevoice_asr_inference_from_file.py
│ ├── vibevoice_realtime_demo.py
│ ├── vibevoice_realtime_colab.ipynb
│ ├── download_experimental_voices.sh
│ ├── web/
│ │ ├── app.py # FastAPI + WebSocket
│ │ └── index.html
│ ├── voices/streaming_model/*.pt # 23 voice presets (Git LFS)
│ ├── text_examples/*.txt
│ └── asr_demo/*.{mp3,mp4,wav}
├── finetuning-asr/
│ ├── lora_finetune.py
│ ├── inference_lora.py
│ ├── README.md
│ └── toy_dataset/{0.json, 0.mp3, 1.json, 1.mp3}
└── docs/ # Reserved (currently no markdown — pull from upstream branches)
8.2 Module reference
vibevoice/modular/configuration_vibevoice.py
Defines four PretrainedConfig subclasses:
VibeVoiceAcousticTokenizerConfig(model_type = "vibevoice_acoustic_tokenizer").VibeVoiceSemanticTokenizerConfig(model_type = "vibevoice_semantic_tokenizer").VibeVoiceDiffusionHeadConfig(model_type = "vibevoice_diffusion_head").VibeVoiceConfig(model_type = "vibevoice", composition of the four above plusQwen2Config).
Implements _convert_dtype_to_string() to coerce torch.dtype to its string form during to_dict() — fixes "Object of type dtype is not JSON serializable" reported in issue #199.
VibeVoiceConfig.get_text_config() returns the inner Qwen2Config so vLLM can read hidden_size, num_attention_heads, etc. for memory profiling.
vibevoice/modular/modeling_vibevoice.py
Base TTS model. Raises NotImplementedError(f'Speech type {speech_type} not implemented') for unknown speech-type tokens in the prefix; NotImplementedError(f'Prediction type {prediction_type} not implemented') for unknown diffusion targets.
vibevoice/modular/modeling_vibevoice_asr.py
VibeVoiceASRForConditionalGeneration — wraps the Qwen2 decoder for ASR. Validates segment_length must be positive (raises ValueError otherwise).
vibevoice/modular/modeling_vibevoice_streaming.py
VibeVoiceStreamingModel.forward() is intentionally disabled — raises RuntimeError('VibeVoiceStreamingModel.forward is intentionally disabled. Use model.language_model(...) or model.tts_language_model(...) instead.').
vibevoice/modular/modeling_vibevoice_streaming_inference.py
Inference wrapper. set_output_embeddings() raises if lm_head is undefined; the unified forward() is disabled (use forward_lm, forward_tts_lm, or generate); loss computation is NotImplementedError.
vibevoice/modular/modular_vibevoice_tokenizer.py
The acoustic and semantic VAEs. OPTIMIZE_FOR_SPEED env var read at module load time (line 30) skips selected normalization/scale steps. Validates several knobs:
mixer_layer∈ a hardcoded set (else "Unsupported mixer layer").layernorm∈ {RMSNorm,LayerNorm} (else "Unsupported norm type").dist_type∈ {fix,gaussian} (else "Unsupported dist_type").- GroupNorm + causal evaluation incompatible (rejected).
vibevoice/modular/streamer.py
AudioStreamer(batch_size, stop_signal, timeout) and AsyncAudioStreamer. Both validate sample_idx < self.batch_size; the streaming TTS path always uses batch_size=1.
vibevoice/processor/vibevoice_processor.py
Long-form TTS processor (multi-speaker dialog, not used by the realtime demo). Validates JSON speaker scripts: a list of speaker entries, with at least one valid speaker line, content non-empty.
vibevoice/processor/vibevoice_asr_processor.py
ASR processor. Builds the prompt:
<|im_start|>system
You are a speech recognition model.
<context_info, if provided>
<|im_end|>
<|im_start|>user
<|object_ref_start|><speech_pad_id × N><|object_ref_end|>
<|im_end|>
<|im_start|>assistant
Speech tokens are mapped to specific Qwen2 special-token IDs:
speech_start_id→<|object_ref_start|>speech_pad_id→<|box_start|>speech_end_id→<|object_ref_end|>
The fast tokenizer (VibeVoiceASRTextTokenizerFast from modular_vibevoice_text_tokenizer.py) MUST respect these mappings or ASR quality silently degrades — this is called out in vllm_plugin/__init__.py:register_vibevoice().
Supported language_model_pretrained_name: Qwen, Llama, Gemma. Other backbones raise ValueError.
vibevoice/processor/vibevoice_streaming_processor.py
Streaming TTS processor. __call__ is not implemented — callers must use process_input_with_cached_prompt(text, cached_prompt, ...) which combines the script with a pre-encoded voice preset.
vibevoice/processor/vibevoice_tokenizer_processor.py
File I/O. Accepts .wav, .mp3, .flac, .m4a, .ogg, .pt, .npy, .npz. Validates audio shape (1D or 2D, otherwise raises). save_audio() requires soundfile; if missing it raises ImportError('soundfile is required to save audio files. Install it with: pip install soundfile').
vibevoice/schedule/dpm_solver.py
Adapted from https://github.com/LuChengTHU/dpm-solver. Implements multi-step DPM solvers (1st through 3rd order) with several algorithm_type and final_sigmas_type options. Heavy validation:
algorithm_typenot implemented →NotImplementedError.solver_typenot implemented →NotImplementedError.final_sigmas_type ∈ {zero, sigma_min}(else ValueError).prediction_type ∈ {epsilon, sample, v_prediction}.timestep_spacing ∈ {linspace, leading, trailing}.beta_schedulenot implemented →NotImplementedError.alpha_transform_type∈ {cosine, exp, cauchy, laplace}.- Cannot pass both
num_inference_stepsandtimesteps(orcustom_timesteps). karras_sigmasandlu_lambdasmutually exclusive with explicittimesteps.
vllm_plugin/inputs.py
vibevoice_audio_input_mapper(ctx, data) — accepts str, bytes, np.ndarray, or List[str]; rejects everything else with ValueError(f'Unsupported audio data type: {type(data)}'). Enforces VIBEVOICE_MAX_AUDIO_DURATION (raises with a specific guidance message instructing the user to set the env var).
vllm_plugin/model.py
VibeVoiceForCausalLM — the vLLM-side model class. Validates: tokenizer config presence (Missing acoustic/semantic tokenizer config in model config), tokenizer config types (acoustic_tokenizer_config has unexpected type, semantic_tokenizer_config has unexpected type), audio item length (Audio at index N is too short to be represented), modality strings (Unsupported modality: {modality}), and embed_tokens discoverability (Cannot find embed_tokens layer).
demo/web/app.py
Entry point for the FastAPI streaming TTS server. Composition:
StreamingTTSService— owns the model, processor, voice presets, default voice key, voice cache.StreamingTTSService.load()— runs at FastAPIstartupevent; selects dtype + attn implementation per device; reconfigures the noise scheduler tosde-dpmsolver+++squaredcos_cap_v2; sets DDPM inference steps to 5.StreamingTTSService.stream()— generator that yields normalized float32 numpy chunks.chunk_to_pcm16— clips, scales by 32767, casts to int16, encodes to bytes.streaming_tts(text, **kwargs)— module-level convenience wrapper.websocket_stream— WebSocket handler, owns theasyncio.Lockbusy-check.index— servesindex.html.get_config— JSON list of voices and default voice key.
8.3 Code patterns
Config composition. VibeVoiceConfig is a Hugging Face composition config (is_composition = True) with four sub_configs. Each child can be passed as a dict (auto-instantiated) or a config instance. This is how the same code path serves both the 1.5B-64K and 7B-32K checkpoints — only the JSON values differ.
Attention selection. Every model loader in this repo follows the same pattern: try flash_attention_2 first; if it raises (commonly ImportError), fall back to sdpa and print a warning. The fall-through is hardcoded — there is no env-var override.
Device handling. Each script normalizes mpx → mps, then validates MPS availability and falls back to CPU. CUDA gets bfloat16 + device_map='cuda', MPS gets float32 + manual model.to('mps') (because MPS does not yet honor device_map cleanly).
Lazy loading. _voice_cache in StreamingTTSService caches torch.load(.pt) results per voice key. Other parts of the codebase use Hugging Face from_pretrained caching (~/.cache/huggingface/hub/). No explicit cache eviction — restart the server to free voice memory.
Streaming via threads. TTS generation runs in a daemon threading.Thread because the underlying model.generate() is a long blocking call. The main async event loop polls a queue.Queue for log frames between binary writes — await asyncio.to_thread(next, iterator, sentinel) is used to advance the audio iterator without blocking the loop.
Right-padding for training, left-padding for inference. The LoRA finetune collator (VibeVoiceASRDataCollator) right-pads input_ids/labels/speech for training. The processor's default behavior at inference time is left-padding (so causal attention treats the unpadded tokens as the most recent). This split is intentional and load-bearing — see lora_finetune.py:VibeVoiceASRDataCollator for the comment.
Frozen tokenizers. setup_model_for_training walks model.named_parameters() and freezes everything matching acoustic_tokenizer or semantic_tokenizer. Only the Qwen2 attention/MLP weights receive LoRA adapters.
8.4 Testing strategy
The vllm_plugin/tests/ directory contains test_api.py and test_api_auto_recover.py — these exercise the vLLM serving path end-to-end. There is no unit-test infrastructure committed for the rest of the repo; integration testing is performed via the demo scripts (which double as smoke tests). The toy dataset under finetuning-asr/toy_dataset/ (two MP3 + JSON pairs) supports a single-step finetune sanity check.
Note
Test coverage is the only confidence-floor "gap" recorded in this briefing's metadata. Adding pytest coverage for
VibeVoiceASRProcessor,audio_utils.load_audio_use_ffmpeg, andDPMSolverMultistepScheduler.set_timestepswould close the loop without major architectural surgery.
8.5 Potential improvements
| Topic | Source | Note |
|---|---|---|
| GGUF quantization | issue #297 | Community feature request; not first-party. |
| OpenAI API parity for TTS | issue #336 | "Does VibeVoice support open API" — currently only WebSocket. |
| NVIDIA DGX Spark support | issue #340 | New hardware target. |
| Arabic / Vietnamese / Traditional Chinese voices | #108, #253, #271, #283 | Voice preset gaps. |
| Low-resource ASR training | #311, #115 | Documentation + recipes for non-English finetune. |
| Saved-files location clarity | #328 | The Gradio demo embeds segments inline; users expect filesystem outputs. |
| Windows 11 stability | #327 | CUDA/VC++ runtime alignment. |
| Token-length runaway | #332 | Input validation pre-tokenize. |
| Package-name disambiguation | #184 | "vibevoice" name collides with unrelated PyPI projects. |
| AliMeeting DER methodology | #322 | Eval pipeline question, deserves a doc page. |
8.6 Contributing
CONTRIBUTING.md is checked in but minimal. Expect the standard Microsoft OSS flow: fork → branch → PR; sign-off via CLA bot if it triggers; tests + lint required for non-trivial changes. The repo has only a main branch and follows trunk-based development.
9. Integration Points
9.1 Hugging Face Hub
All model and processor classes implement the standard from_pretrained / save_pretrained contracts. The repo references three published Hugging Face IDs:
| ID | Used by |
|---|---|
microsoft/VibeVoice-Realtime-0.5B |
Streaming TTS demos, default MODEL_PATH. |
microsoft/VibeVoice-ASR |
ASR demos, LoRA finetune base, vLLM plugin. |
Qwen/Qwen2.5-7B |
ASR processor pulls the Qwen tokenizer via language_model_pretrained_name="Qwen/Qwen2.5-7B". |
To deploy without network access, mirror these to your private HF instance and pass local paths to every script's --model_path / --base_model.
9.2 vLLM
pyproject.toml declares an entry point under vllm.general_plugins that maps to vllm_plugin.register_vibevoice. When vLLM starts, it discovers and runs this plugin, which registers the config, tokenizers, processor, and model classes. After registration, vLLM treats microsoft/VibeVoice-ASR as a first-class model — vllm serve microsoft/VibeVoice-ASR works end-to-end.
9.3 PEFT / LoRA
finetuning-asr/lora_finetune.py uses peft.LoraConfig + peft.get_peft_model. inference_lora.py uses peft.PeftModel.from_pretrained(base_model, lora_path). Adapter weights can be merged into the base model for faster inference (model = model.merge_and_unload()), at the cost of swappability.
9.4 FFmpeg
The audio loader requires ffmpeg and ffprobe on PATH. Both are invoked via subprocess.run(..., check=True). The semaphore in audio_utils.py allows operators to cap concurrent subprocesses (set VIBEVOICE_FFMPEG_MAX_CONCURRENCY to ~CPU-core count under heavy load).
9.5 Gradio
demo/vibevoice_asr_gradio_demo.py builds a gr.Blocks interface. Custom CSS styles the Stop button. demo.queue(default_concurrency_limit=3) allows up to three concurrent ASR requests through the Gradio runtime, but the ASR model itself is shared — back-pressure builds on the GPU, not the queue. demo.launch(share=args.share, show_error=True, theme=gr.themes.Soft(), css=custom_css) is the standard Gradio launch call; share=True publishes a Gradio public tunnel.
9.6 FastAPI / Starlette
demo/web/app.py uses FastAPI lifespan via @app.on_event("startup") (the older event-based API). For long-lived production, port to the lifespan async context manager pattern; behavior is equivalent.
9.7 Liger Kernel (optional)
The ASR Gradio demo wraps Qwen2 RoPE / RMSNorm / SwiGLU layers with Liger Kernel if importable:
from liger_kernel.transformers import apply_liger_kernel_to_qwen2
apply_liger_kernel_to_qwen2(rope=True, rms_norm=True, swiglu=True, cross_entropy=False)
cross_entropy=False is intentional — the Liger CE replacement is not yet validated against VibeVoice's loss path. Leave it disabled.
9.8 Pydub (optional)
Used by the ASR Gradio demo to re-encode segment audio as 32 kbps MP3 (smaller than WAV for inline data URIs). If pydub is missing, the demo falls back to WAV embedding. No effect on transcription quality — display-time only.
9.9 Diffusers
The DPM solver inherits from diffusers.schedulers.scheduling_utils.SchedulerMixin + ConfigMixin. This makes it usable in any diffusers pipeline that expects a KarrasDiffusionSchedulers-compatible scheduler. The streaming server reconfigures the scheduler to sde-dpmsolver++ + squaredcos_cap_v2 at startup — those values are not the diffusers defaults; if you build a pipeline outside the streaming server you must reconfigure manually.