VoxCPM2 voice cloning on Mac
Run VoxCPM2 locally on Apple Silicon with MLX to clone a voice or design one from a text description. This OpenBMB model produces 48 kHz speech in 30 languages. Listen to the examples, then try the CLI with the reference recording and its transcript.
Audio samples · Clone a voice with the CLI · VoxCPM2 vs Qwen3-TTS
Voice cloning samples
These English clips come from the published FLEURS comparison. The reference and generated speech use different sentences. Each output is one representative sample; the comparison metrics average multiple clips.
Reference transcript: One can only wonder what the keyboard will become when something newer comes along.
Generated text: While he was working at the hospital Liggins began to investigate premature labor during his spare time.
Generated text: While he was working at the hospital Liggins began to investigate premature labor during his spare time.
Clone a voice with the CLI
Download Reference audio as reference.wav. The command uses its exact transcript; replace both the recording and transcript when using your own voice sample.
speech speak "Hello from the cloned voice." \
--engine voxcpm2 \
--voxcpm2-variant bf16 \
--voice-sample reference.wav \
--voxcpm2-prompt-audio reference.wav \
--voxcpm2-prompt-text "One can only wonder what the keyboard will become when something newer comes along." \
--seed 42 \
--output clone.wav
What it is
- 48 kHz output — the only on-device engine in this stack with studio sample rate. Every other TTS module maxes at 24 kHz.
- Voice design — natural-language style control:
--voxcpm2-instruct "young female voice, warm and gentle". None of the other engines expose this. - Voice cloning — single-reference cloning from a 16 kHz clip; "ultimate cloning" (reference audio + transcript) for prosody preservation.
- 30 languages — English, Chinese, Indonesian, Japanese, Korean, and more. Auto-detected from text.
- Apache 2.0 — model weights inherit upstream openbmb licence; our Swift port is the same.
Architecture
Five cooperating components produce a 48 kHz waveform:
| Component | Description |
|---|---|
| MiniCPM-4 base LM | 28-layer MiniCPM-4 with LongRoPE, GQA (16 Q / 2 KV heads, 128 head dim), and SwiGLU MLP. Conditions on text tokens + audio latents. |
| Residual LM | 8-layer MiniCPM-4 variant without rotary embeddings. Refines the base LM hidden state per generated audio patch. |
| FSQ + Local DiT estimator | Scalar-quantised hidden states drive a 12-layer Diffusion Transformer (V2) operating on 64-dim audio latents in patches of 4. CFG-zero-star Euler solver, 10 timesteps default. |
| AudioVAE V2 | Causal convolutional decoder. Reads 16 kHz reference audio + emits 48 kHz waveform (3× upsample baked in). |
| Stop head | Per-step binary classifier on the LM hidden state. Argmax = 1 ends generation after minTokens patches. |
Bundles
The current CLI supports bf16 and int8 bundles converted from the upstream openbmb/VoxCPM2 checkpoint. Quantization applies to the language-model and diffusion projections; the audio decoder stays at full precision.
| Bundle | Quantisation | Size | HuggingFace |
|---|---|---|---|
| bf16 | None (reference) | ~5.0 GB | aufklarer/VoxCPM2-MLX-bf16 |
| int8 | MLX QuantizedLinear, group size 64 | ~3.0 GB | aufklarer/VoxCPM2-MLX-int8 |
Round-trip ASR (Qwen3-ASR 0.6B, 8-sentence harness, M-series Apple Silicon):
| Variant | WER | RTF |
|---|---|---|
| bf16 | 2.04 % | 1.38 |
| int8 | 0.00 % | 1.02 |
| int4 (retired) | 4.08 % | 0.83 |
The CLI defaults to bf16; choose int8 for a smaller download. int4 is retired and appears in the table only as a historical measurement.
Quick start
import VoxCPM2TTS
let tts = try await VoxCPM2TTSModel.fromPretrained() // defaults to bf16
let audio = try await tts.generate(text: "Hello from VoxCPM2.", language: "english")
// audio: [Float] at 48 kHz mono
Select the int8 bundle explicitly:
let tts = try await VoxCPM2TTSModel.fromPretrained(
modelId: "aufklarer/VoxCPM2-MLX-int8"
)
Voice design (instruction-driven)
Pass a natural-language style description; the model conditions synthesis on it without a reference audio sample:
let audio = try await tts.generateVoxCPM2(
text: "Welcome to the show.",
instruct: "A young woman, gentle and warm voice."
)
Voice cloning
Single-reference cloning from a 16 kHz mono clip:
let ref = try AudioFileLoader.load(url: URL(fileURLWithPath: "speaker.wav"),
targetSampleRate: 16000)
let audio = try await tts.generateVoxCPM2(
text: "This is a cloned voice.",
refAudio: ref
)
Ultimate cloning — pass both reference audio and the matching transcript so the LM can also condition on the lexical context, preserving prosody and accent more faithfully:
let audio = try await tts.generateVoxCPM2(
text: "Hello from the cloned voice.",
refAudio: ref,
promptText: "this is what the reference clip actually said",
promptAudio: ref
)
CLI
speech speak "Hello there." \
--engine voxcpm2 \
--voxcpm2-variant int8 \
--output hello.wav
# Voice design
speech speak "Welcome to the show." \
--engine voxcpm2 \
--voxcpm2-instruct "A young woman, gentle and warm voice." \
--output design.wav
# Voice cloning
speech speak "This is a cloned voice." \
--engine voxcpm2 \
--voice-sample speaker.wav \
--output clone.wav
Flags: --voxcpm2-variant {bf16,int8}, --voxcpm2-instruct, --voxcpm2-ref-audio, --voxcpm2-prompt-audio + --voxcpm2-prompt-text, --voxcpm2-cfg-value, --voxcpm2-timesteps, --voxcpm2-max-tokens, --voxcpm2-min-tokens, --seed for reproducible synthesis.
VoxCPM2 vs Qwen3-TTS
Published English results for ten FLEURS reference/target pairs per model: mean WER and UTMOS, median RTF. Lower WER and RTF are better; UTMOS predicts naturalness and is not a human rating. These results do not establish a universal ranking.
| Model | WER ↓ | RTF ↓ | UTMOS ↑ |
|---|---|---|---|
| VoxCPM2 bf16 | 1.8% | 1.11 | 2.97 |
| Qwen3-TTS Base 1.7B bf16 ICL | 2.4% | 1.70 | 3.50 |
Full five-language voice-cloning comparison and methodology.
Picking among speech-swift TTS modules
| Kokoro-82M | Qwen3-TTS | CosyVoice3 | VoxCPM2 | VibeVoice 1.5B | |
|---|---|---|---|---|---|
| Params | 82M | 0.6 / 1.7 B | 0.5B | 2B | 1.5B |
| Sample rate | 24 kHz | 24 kHz | 24 kHz | 48 kHz | 24 kHz |
| Backend | CoreML (ANE) | MLX, CoreML | MLX | MLX | MLX |
| Languages | 10 | 10 | 9 | 30 | EN + ZH |
| Voice design | Fixed presets | — | — | Instruction-driven | — |
| Voice cloning | — | ICL reference | Zero-shot reference | Reference + ultimate | Raw audio + transcript |
| Long-form | Short/medium | Streaming | Streaming | Streaming patches | Up to 90 min |
…you need 48 kHz output (music / broadcast applications) or natural-language voice design without a reference clip. For maximum-quality short-form English TTS with smaller download, CosyVoice3 or Qwen3-TTS are lighter. For long-form podcast / audiobook in EN/ZH, VibeVoice 1.5B is purpose-built.
Responsible use
Voice cloning is included. Obtain consent for any voice you clone and don't use the model to impersonate individuals, generate disinformation, or commit fraud. The full safety guidance from openbmb/VoxCPM2 applies.
Local native inference
VoxCPM2 supports weightPrecision: .float16 for an unquantized checkpoint. Its main parameters use FP16; AudioVAE and some computation remain FP32. INT8 checkpoints are rejected for this mode.
Explicit tokenizerDirectory loading uses staged tokenizer files without modifying them or needing a separate cache. Incremental speech decodes bounded causal windows while retaining the AudioVAE left context.
Speech with stream: true returns NDJSON audio events at 48 kHz, followed by done or error. Cancel by requestID at /v1/inference/cancel. GET /v1/inference/capabilities and /v1/inference/provenance report model identities, parameter dtypes, asset pins, and memory counters.