Views
No views yet
[!WARNING] Only extracted from the*-Basemodel variant. The*-CustomVoiceand*-VoiceDesignvariants do NOT support speaker embeddings and will not work. Do not attempt to use them for speaker encoding.
1import librosa
2import torch
3from transformers import AutoModel, AutoProcessor
4
5processor = AutoProcessor.from_pretrained(
6 "marksverdhei/Qwen3-Voice-Embedding-12Hz-0.6B", trust_remote_code=True,
7)
8model = AutoModel.from_pretrained(
9 "marksverdhei/Qwen3-Voice-Embedding-12Hz-0.6B", trust_remote_code=True,
10)
11model.eval()
12
13audio, sr = librosa.load("audio.wav", sr=None, mono=True)
14inputs = processor(audio, sampling_rate=sr)
15
16with torch.no_grad():
17 embedding = model(**inputs).last_hidden_state # (1, 1024)1from transformers import pipeline
2
3pipe = pipeline(
4 "feature-extraction",
5 model="marksverdhei/Qwen3-Voice-Embedding-12Hz-0.6B",
6 trust_remote_code=True,
7)
8
9# From file path
10embedding = pipe("audio.wav") # list, shape (1, 1024)
11
12# From numpy array
13import librosa
14audio, sr = librosa.load("audio.wav", sr=None, mono=True)
15embedding = pipe(audio, sampling_rate=sr)1import torch
2from safetensors.torch import save_file
3
4# embedding: torch.Tensor of shape (1024,) or (1, 1024)
5embedding = embedding.squeeze() # ensure 1D
6save_file({"speaker_embedding": embedding}, "my_voice.safetensors")1from safetensors.torch import load_file
2
3tensors = load_file("my_voice.safetensors")
4embedding = tensors["speaker_embedding"] # (1024,)[!TIP] Use the key"speaker_embedding"by convention — this matches the field name used by Qwen3-TTS and vLLM-Omni.
qwen_tts Python package and the
vLLM-Omni serving API.qwen_tts package wraps the TTS
model and exposes generate_voice_clone. To inject a pre-computed
embedding without needing the original reference audio on disk, construct a
VoiceClonePromptItem directly:1import torch
2import soundfile as sf
3from dataclasses import dataclass
4from typing import Optional
5from safetensors.torch import load_file
6
7# The prompt item dataclass (mirrors qwen_tts.inference.qwen3_tts_model)
8@dataclass
9class VoiceClonePromptItem:
10 ref_code: Optional[torch.Tensor] # None when using x-vector only
11 ref_spk_embedding: torch.Tensor # (1024,)
12 x_vector_only_mode: bool
13 icl_mode: bool
14 ref_text: Optional[str] = None
15
16# 1. Load a saved embedding
17embedding = load_file("my_voice.safetensors")["speaker_embedding"] # (1024,)
18
19# 2. Build the prompt item — no reference audio needed
20prompt = VoiceClonePromptItem(
21 ref_code=None,
22 ref_spk_embedding=embedding,
23 x_vector_only_mode=True,
24 icl_mode=False,
25)
26
27# 3. Load the TTS model
28from qwen_tts import Qwen3TTSModel
29
30tts = Qwen3TTSModel.from_pretrained(
31 "Qwen/Qwen3-TTS-12Hz-0.6B-Base", device_map="cuda:0",
32)
33
34# 4. Generate speech — reusable across any text
35wavs, sr = tts.generate_voice_clone(
36 text="Hello from a stored embedding!",
37 language="English",
38 voice_clone_prompt=prompt,
39)
40sf.write("output.wav", wavs[0], sr)[!NOTE]x_vector_only_mode=Trueskips the text encoder and uses only the speaker embedding. Quality may be slightly reduced compared to full voice cloning with a reference transcript, but it lets you synthesize from stored embeddings without any audio files.
speaker_embedding field in the API request.[!NOTE] Thespeaker_embeddingfield requires thehtbranch of our vLLM-Omni fork. There is an upstream PR pending — use the fork until it is merged.
1import httpx
2from safetensors.torch import load_file
3
4embedding = load_file("my_voice.safetensors")["speaker_embedding"]
5
6response = httpx.post(
7 "http://localhost:8000/v1/audio/speech",
8 json={
9 "model": "qwen3-tts-0.6b-base",
10 "input": "Hello from a stored voice embedding.",
11 "task_type": "Base",
12 "speaker_embedding": embedding.tolist(), # flat list of 1024 floats
13 "response_format": "wav",
14 "language": "Auto",
15 },
16 headers={"Authorization": "Bearer EMPTY"},
17)
18
19with open("output.wav", "wb") as f:
20 f.write(response.content)1import numpy as np
2
3def slerp(v0, v1, t):
4 """Spherical linear interpolation between two embeddings."""
5 v0_n = v0 / (np.linalg.norm(v0) + 1e-8)
6 v1_n = v1 / (np.linalg.norm(v1) + 1e-8)
7 omega = np.arccos(np.clip(np.dot(v0_n, v1_n), -1, 1))
8 if omega < 1e-6:
9 return (1 - t) * v0 + t * v1
10 return (np.sin((1 - t) * omega) / np.sin(omega)) * v0 + \
11 (np.sin(t * omega) / np.sin(omega)) * v1
12
13blended = slerp(embedding_a.numpy(), embedding_b.numpy(), t=0.5)| Property | Value |
|---|---|
| Architecture | ECAPA-TDNN |
| Embedding dimension | 1024 |
| Input | 128-bin log-mel spectrogram |
| Sample rate | 24000 Hz |
| Parameters | ~6.3M |
| Source model | Qwen/Qwen3-TTS-12Hz-0.6B-Base |
| License | Apache 2.0 |
Input mel (batch, time, 128)
→ TDNN (128 → 512, k=5, d=1)
→ SE-Res2Net (512 → 512, k=3, d=2)
→ SE-Res2Net (512 → 512, k=3, d=3)
→ SE-Res2Net (512 → 512, k=3, d=4)
→ Multi-layer Feature Aggregation (1536 → 1536, k=1, d=1)
→ Attentive Statistics Pooling
→ Linear (3072 → 1024)
→ Output embedding (batch, 1024)| Parameter | Value |
|---|---|
| Sample rate | 24000 Hz |
| FFT size | 1024 |
| Hop length | 256 |
| Window length | 1024 |
| Mel bins | 128 |
| Frequency range | 0–12000 Hz |
| Mel scale | Slaney |
| Compression | log(clamp(x, min=1e-5)) |
torchtransformerslibrosa (for audio loading and mel filterbank computation)numpy[!IMPORTANT] The 0.6B and 1.7B encoders produce embeddings of different dimensions (1024 vs 2048). They are not interchangeable — do not mix embeddings from different model sizes.
1import torch
2import librosa
3from librosa.filters import mel as librosa_mel_fn
4from transformers import AutoModel
5
6model = AutoModel.from_pretrained(
7 "marksverdhei/Qwen3-Voice-Embedding-12Hz-0.6B", trust_remote_code=True,
8)
9model.eval()
10
11audio, sr = librosa.load("audio.wav", sr=None, mono=True)
12if sr != 24000:
13 audio = librosa.resample(audio, orig_sr=sr, target_sr=24000)
14
15y = torch.from_numpy(audio).unsqueeze(0).float()
16mel_basis = torch.from_numpy(
17 librosa_mel_fn(sr=24000, n_fft=1024, n_mels=128, fmin=0, fmax=12000)
18).float()
19padding = (1024 - 256) // 2
20y = torch.nn.functional.pad(y.unsqueeze(1), (padding, padding), mode="reflect").squeeze(1)
21spec = torch.stft(
22 y, 1024, hop_length=256, win_length=1024,
23 window=torch.hann_window(1024), center=False, return_complex=True,
24)
25mel = torch.log(torch.clamp(torch.matmul(mel_basis, torch.abs(spec)), min=1e-5))
26mel = mel.transpose(1, 2) # (1, time, 128)
27
28with torch.no_grad():
29 embedding = model(input_values=mel).last_hidden_state # (1, 1024)1@article{Qwen3-TTS,
2 title={Qwen3-TTS Technical Report},
3 author={Hangrui Hu and Xinfa Zhu and Ting He and Dake Guo and Bin Zhang and Xiong Wang and Zhifang Guo and Ziyue Jiang and Hongkun Hao and Zishan Guo and Xinyu Zhang and Pei Zhang and Baosong Yang and Jin Xu and Jingren Zhou and Junyang Lin},
4 journal={arXiv preprint arXiv:2601.15621},
5 year={2026}
6}1@article{ecapa-tdnn,
2 title={ECAPA-TDNN: Emphasized Channel Attention, Propagation and Aggregation in TDNN Based Speaker Verification},
3 author={Desplanques, Brecht and Thienpondt, Jenthe and Demuynck, Kris},
4 journal={Proc. Interspeech},
5 year={2020}
6}