Core AI model zoo

VoxCPM-0.5B on Core AI — diffusion TTS, on-device (iPhone + Mac)

OpenBMB VoxCPM-0.5B ported to Apple’s Core AI. It is the zoo’s second TTS (after Kokoro) and the first diffusion one. Apache-2.0. Conversion in conversion/voxcpm/; Swift host in CoreAIKit (VoxCPMTTS); demo = apps/coreai-audio “Voice” tab. Weights: 🤗 mlboydaisuke/VoxCPM-0.5B-CoreAI.

Architecture

VoxCPM is not a vocoder TTS. It generates 16 kHz audio through a continuous, token-rate (12.5 Hz) loop:

Per frame: dit = lm_to_dit(lm_h) + res_to_dit(res_h)feat_decoder(dit, prev_patch, z)feat_encoder → stop-head check → base_lm.decodeFSQresidual_lm.decode. Latents → AudioVAE → wav.

Port lessons

On-device

Plain TTS today; VoxCPM’s voice-cloning branch (prompt VAE-encode + prompt prefill) is a follow-on.

Latency: streaming + batched-prefill bundle

The “too slow to generate” complaint is about time-to-first-audio, not throughput. Two changes fix it with zero quality change (same int8 model):

iPhone 17 Pro, int8, same model — OLD (no prefill bundle, non-streaming) vs NEW: synthesis emits its first chunk at ~0.43 s (vs ~4.2 s for the whole clip) and runs at RTF ~0.9 (just below real-time). Because RTF sits close to 1.0, the app holds a small pre-roll jitter buffer (~2 chunks ≈ 1 s) before starting playback, so a momentary generation slowdown can’t starve the player (audible crackle/underrun). Net perceived time-to-first-audio is ~0.9 s — still ~5× better than the ~4 s whole-clip wait — with smooth, gapless playback.

Throughput note: on the bandwidth-bound A19 the backbone q=1 GEMV is the floor, so int8 and fp16 tie on RTF — the win is first-audio + streaming, not raw tokens/s. (On M-series the picture differs — fp16 and an fp16-on-ANE backbone are faster — but those gains don’t transfer to iOS: the ANE backbone path is uncompilable for h18p, and a custom Metal matvec lost to the engine’s own GEMV.) A larger real-throughput win needs few-step / CFG distillation of the diffusion, which is a quality trade-off.

VoxCPM2 (2B, 48 kHz) on Core AI

OpenBMB VoxCPM2 — the 2B, 48 kHz successor — is the zoo’s first 2B on-device TTS. Same five-bundle + host-glue shape as the 0.5B, scaled up and re-architected in a few places. Swift host = VoxCPM2TTS; demo = apps/coreai-audio “Voice 2B” tab. Weights: 🤗 mlboydaisuke/VoxCPM2-CoreAI. Conversion = conversion/voxcpm/*_v2.py (+ pipeline_v2.py, a self-contained generator that loads only from the raw checkpoint = the Swift-host reference).

What changed vs 0.5B

Verification (the oracle)

Reassemble the official VoxCPM2 source into its expected package layout (_ref_v2/voxref/, with a no-op LoRA stub) so the full VoxCPM2Model instantiates on CPU = a true oracle. Then: per-component gates (backbone / feat_decoder / feat_encoder) cos 1.0 vs the official modules loaded from the real checkpoint; an e2e gate replays the official CFM noise through my overlays and matches latents (cos 0.997) + full-chain 48 kHz magspec 0.996; every exported bundle engine-gated cos ≥ 0.9999.

On-device (iPhone 17 Pro)

Gotcha

Uploading the large Core AI bundles to the Hub: the xet transfer backend panics mid-file on the multi-GB .mlirb/.aimodelc blobs (assertion left == right ... not fully completed). Set HF_HUB_DISABLE_XET=1 (classic LFS path); upload_folder is idempotent, so a re-run skips completed files and finishes clean.