Feature Extraction
Transformers
Safetensors
VibeVoice
English
multilingual
vibevoice_embed
audio
speaker-embedding
voice-embedding
custom_code
Instructions to use lemuriandezapada/VibeVoice-Embed with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use lemuriandezapada/VibeVoice-Embed with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("feature-extraction", model="lemuriandezapada/VibeVoice-Embed", trust_remote_code=True)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("lemuriandezapada/VibeVoice-Embed", trust_remote_code=True, device_map="auto") - VibeVoice
How to use lemuriandezapada/VibeVoice-Embed with VibeVoice:
import torch, soundfile as sf, librosa, numpy as np from vibevoice.processor.vibevoice_processor import VibeVoiceProcessor from vibevoice.modular.modeling_vibevoice_inference import VibeVoiceForConditionalGenerationInference # Load voice sample (should be 24kHz mono) voice, sr = sf.read("path/to/voice_sample.wav") if voice.ndim > 1: voice = voice.mean(axis=1) if sr != 24000: voice = librosa.resample(voice, sr, 24000) processor = VibeVoiceProcessor.from_pretrained("lemuriandezapada/VibeVoice-Embed") model = VibeVoiceForConditionalGenerationInference.from_pretrained( "lemuriandezapada/VibeVoice-Embed", torch_dtype=torch.bfloat16 ).to("cuda").eval() model.set_ddpm_inference_steps(5) inputs = processor(text=["Speaker 0: Hello!\nSpeaker 1: Hi there!"], voice_samples=[[voice]], return_tensors="pt") audio = model.generate(**inputs, cfg_scale=1.3, tokenizer=processor.tokenizer).speech_outputs[0] sf.write("output.wav", audio.cpu().numpy().squeeze(), 24000) - Notebooks
- Google Colab
- Kaggle
VibeVoice-Embed: standalone acoustic-encoder voice-embedding export of microsoft/VibeVoice-1.5B
27b6b94 verified | #!/usr/bin/env python3 | |
| """OpenAI-compatible /v1/embeddings server for VibeVoice-Embed. | |
| Reference implementation, deliberately small: one model, one lock, no batching tricks. | |
| ``input`` carries AUDIO rather than text — a data: URI, bare base64, or a server-side | |
| file path (or a list of them). Vectors come back unnormalised, matching the model's | |
| contract; pass ``"normalize": true`` to get unit vectors. | |
| pip install torch torchaudio transformers fastapi uvicorn | |
| python openai_embeddings_server.py --model lemuriandezapada/VibeVoice-Embed --port 8080 | |
| curl -s localhost:8080/v1/embeddings \ | |
| -H 'Content-Type: application/json' \ | |
| -d '{"input": "'"$(base64 -w0 clip.wav)"'", "normalize": true}' | |
| Why no dynamic batching: the encoder runs two orders of magnitude faster than realtime | |
| on a modest GPU, so request transport dominates end-to-end latency long before the | |
| forward pass does. Batched clips of unequal length would also need mask-aware pooling | |
| (the model supports it via ``padding_mask``) — correct, but pointless complexity at | |
| this model size. Scale out with replicas instead. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import asyncio | |
| import base64 | |
| import io | |
| import logging | |
| import os | |
| import time | |
| from typing import Optional, Union | |
| logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") | |
| logger = logging.getLogger("vibevoice-embed") | |
| MAX_AUDIO_BYTES = 32 * 1024 * 1024 | |
| MAX_BATCH = 64 | |
| def load_model(model_id: str, dtype: str, device: str): | |
| import torch | |
| from transformers import AutoModel | |
| torch_dtype = {"float32": torch.float32, "bfloat16": torch.bfloat16}[dtype] | |
| model = AutoModel.from_pretrained(model_id, torch_dtype=torch_dtype, trust_remote_code=True) | |
| model.to(device) | |
| model.eval() | |
| params = sum(p.numel() for p in model.parameters()) | |
| logger.info("model ready: %.1fM params, %s, %s, dim=%d", | |
| params / 1e6, torch_dtype, device, model.config.vae_dim) | |
| return model | |
| def decode_audio(value: str, sampling_rate: int): | |
| """data: URI, bare base64, or path -> (mono float32 waveform (1, T), seconds).""" | |
| text = (value or "").strip() | |
| if not text: | |
| raise ValueError("empty audio input") | |
| import torch | |
| import torchaudio | |
| if text.startswith("data:"): | |
| raw = base64.b64decode(text.split(",", 1)[-1], validate=False) | |
| source = io.BytesIO(raw) | |
| elif os.path.exists(text): | |
| if os.path.getsize(text) > MAX_AUDIO_BYTES: | |
| raise ValueError("audio input is too large") | |
| source = text | |
| else: | |
| raw = base64.b64decode(text, validate=False) | |
| if len(raw) > MAX_AUDIO_BYTES: | |
| raise ValueError("audio input is too large") | |
| source = io.BytesIO(raw) | |
| wav, sr = torchaudio.load(source) | |
| if wav.numel() == 0: | |
| raise ValueError("audio input decoded to zero samples") | |
| if wav.shape[0] > 1: | |
| wav = wav.mean(0, keepdim=True) | |
| if sr != sampling_rate: | |
| wav = torchaudio.functional.resample(wav, sr, sampling_rate) | |
| return wav.to(torch.float32), wav.shape[-1] / sampling_rate | |
| def build_app(model, served_name: str, device: str): | |
| import torch | |
| from fastapi import FastAPI, HTTPException | |
| from pydantic import BaseModel, Field | |
| sampling_rate = int(model.config.sampling_rate) | |
| frame_rate = sampling_rate / model.config.hop_length | |
| class EmbeddingRequest(BaseModel): | |
| model: Optional[str] = None | |
| input: Union[str, list[str]] | |
| normalize: bool = False | |
| encoding_format: str = Field(default="float") | |
| app = FastAPI(title="VibeVoice-Embed") | |
| # One request at a time on the one model; the GPU work never touches the event loop. | |
| lock = asyncio.Lock() | |
| async def health(): | |
| return {"status": "ok"} | |
| async def models(): | |
| return {"object": "list", "data": [{ | |
| "id": served_name, | |
| "object": "model", | |
| "owned_by": "vibevoice", | |
| "dimensions": int(model.config.vae_dim), | |
| "sample_rate": sampling_rate, | |
| "frame_rate": frame_rate, | |
| }]} | |
| def embed_all(waveforms): | |
| with torch.no_grad(): | |
| return [ | |
| model(wav.unsqueeze(0).to(device)).pooler_output.squeeze(0) | |
| for wav in waveforms | |
| ] | |
| async def embeddings(body: EmbeddingRequest): | |
| items = [body.input] if isinstance(body.input, str) else list(body.input) | |
| if not items: | |
| raise HTTPException(400, "input is required") | |
| if len(items) > MAX_BATCH: | |
| raise HTTPException(400, f"at most {MAX_BATCH} clips per request, got {len(items)}") | |
| try: | |
| decoded = [decode_audio(item, sampling_rate) for item in items] | |
| except ValueError as exc: | |
| raise HTTPException(400, str(exc)) from exc | |
| except Exception as exc: | |
| raise HTTPException(400, f"could not decode audio: {exc}") from exc | |
| t0 = time.time() | |
| async with lock: | |
| vectors = await asyncio.to_thread(embed_all, [wav for wav, _ in decoded]) | |
| if body.normalize: | |
| vectors = [torch.nn.functional.normalize(v, dim=0) for v in vectors] | |
| seconds = sum(secs for _, secs in decoded) | |
| logger.info("%d clip(s), %.2fs audio in %.2fs", len(items), seconds, time.time() - t0) | |
| return { | |
| "object": "list", | |
| "data": [ | |
| {"object": "embedding", "index": i, "embedding": v.cpu().tolist()} | |
| for i, v in enumerate(vectors) | |
| ], | |
| "model": served_name, | |
| # Audio has no tokens; encoder frames are the honest cost analogue. | |
| "usage": { | |
| "prompt_tokens": int(round(seconds * frame_rate)), | |
| "total_tokens": int(round(seconds * frame_rate)), | |
| "audio_seconds": round(seconds, 3), | |
| }, | |
| } | |
| return app | |
| def main() -> None: | |
| parser = argparse.ArgumentParser(description="Serve VibeVoice-Embed voice embeddings") | |
| parser.add_argument("--model", default="lemuriandezapada/VibeVoice-Embed") | |
| parser.add_argument("--host", default="127.0.0.1") | |
| parser.add_argument("--port", type=int, default=8080) | |
| parser.add_argument("--device", default="cuda") | |
| parser.add_argument("--dtype", default="float32", choices=["float32", "bfloat16"], | |
| help="float32 by default: the encoder is small and the pooled " | |
| "vector is compared at tolerances bfloat16 cannot hold") | |
| args = parser.parse_args() | |
| model = load_model(args.model, args.dtype, args.device) | |
| import uvicorn | |
| uvicorn.run(build_app(model, args.model, args.device), | |
| host=args.host, port=args.port, log_level="info") | |
| if __name__ == "__main__": | |
| main() | |