OpenBMB logo

VoxCPM

VoxCPMv2Current
byOpenBMBOpenBMB(open source)
Released April 1, 2026
Context4.1K tokens
Training CutoffVoxCPM2 trained on over 2 million hours of multilingual speech
CategoryAudio Model

About VoxCPM

VoxCPM is OpenBMB’s tokenizer-free text-to-speech model family. VoxCPM2 is a 2B-parameter release for multilingual speech generation, voice design, and controllable voice cloning.

Capabilities

text to speechmultilingualvoice cloningvoice designstreaming inference

Input Modalities

textaudio

Output Modalities

audio

Technical Details

API Identifier
openbmb/VoxCPM2
Category
Audio Model
Context Window
4,096 tokens

Tags

text-to-speechvoice-cloningmultilingualspeech-generationopen-source

Run this model yourself

Local and cloud setup for VoxCPM, with exact configurations, sources and clear evidence limits.

Find a model for your hardware
Local hardwareDocumented setupSources checked Oct 16, 2026

Synthesize a fixed sentence with VoxCPM2 on a research CPU desktop

Use exact VoxCPM2 and its same-revision AudioVAE for private, fixed-sentence speech synthesis. Native CPU compute stays BF16 and the AudioVAE uses FP32. The 8,192-position cache is shared by text and acoustic patches; it is not an 8,192-text-token allowance. Verify native early stopping and WAV shape, then listen for the complete original English sentence. Fit, speed and voice quality remain unmeasured.

Hardware
Intel Core i5-12400 desktop, 4 inference threads, 32 GiB RAM
Runtime
Native VoxCPM2 fixed-sentence private CPU TTS harness VoxCPM cce58a5b59303c9bd63d12f23afe6a49a4a80c59; torch2.6.0+cpu; torchaudio2.6.0+cpu; Python3.12; transformers4.57.6 · CPU
Model format
safetensors · Native unquantized model; runtime BF16 base/residual/diffusion, FP32 AudioVAE · Unquantized native checkpoint; CPU BF16 compute with FP32 AudioVAE
Workload
8,192 token context · 1 concurrent

Based on source documentation; this setup has not been executed by OpenTools. Memory: unknown. No compatible measurement available.

Read the complete setup guide

Before you start

  • Use an owned Intel i5-12400 Ubuntu 24.04 x86_64 desktop with 32 GiB RAM,40 GiB free disk and four threads. These allocations are not measured requirements.
  • Read native model/runtime Apache-2.0 terms and retain applicable notices. This walkthrough generates only the original supplied sentence, with no voice reference, cloning, uploaded audio or external URL. Do not infer voice, likeness or consent rights from model licensing.
  • This is a source-documented private educational fixed-input service. CPU BF16 model compute and FP32 AudioVAE are native choices, not tested by the author; full fit, latency, quality and exact environment compatibility remain unknown.
  • Use the exact VoxCPM2 release and both same-pin weight files. Do not substitute VoxCPM1/1.5, an external codec, an unpinned normalizer/denoiser or a provider TTS API.

Exact model and requirements

Artifact:
OpenBMB / VoxCPM2 native model with AudioVAE V2 auxiliary checkpoint · checkpoint openbmb/VoxCPM2
Publisher revision:
32279effe8c19989596f05d353d1447f51d9e915
Artifact digest:
sha256:f7f964cfa9da23653baec6e6f7750719977ad944ed9f95fe52fe3a620506891d
Download size:
4.27 GiB
Memory requirement (unknown):
Unknown — No full process peak measured for exact native BF16 CPU, FP32 AudioVAE, static caches and output buffers.
System RAM / disk:
Unknown / Unknown
Placement:
single device. One CPU host; no tensor or pipeline sharding. Whole native speech model, text-token preparation, acoustic patch generation and AudioVAE decoding run on CPU; no GPU or disk offload.
KV cache:
Native BF16 static base/residual LM caches, each capacity8192; AudioVAE/state and full process memory unmeasured.
Workload:
8,192 token context; Unknown; Not applicable; concurrency 1; batch 1; cache cold; repetitions Unknown.
Tokenizer:
Native LlamaTokenizerFast plus mask_multichar_chinese_tokens; add audio-start control101 · 32279effe8c19989596f05d353d1447f51d9e915; runtime cce58a5b59303c9bd63d12f23afe6a49a4a80c59
Sampling / reasoning:
Native diffusion guidance cfg_value 2.0 and 10 inference steps, min_len 2, max_len 256 acoustic patches, seed 1234, retry_badcase False with one attempt. Native effective cap is min(text_token_count*6+10,256). No text temperature/top-p sampler applies. No chat template or reasoning output. contextTokens 8,192 labels native shared text/acoustic autoregressive positions. Reserve actual textcount+one audio-start+effective acoustic cap within 8,192; native lm_config 32,768 position setting is distinct from allocated cache. Generated acoustic feature count strictly below the effective cap establishes native early stop only for zero-shot context_len 0, not intelligibility/quality.
Runtime prerequisites:
Ubuntu 24.04 x86_64; See prerequisites above.

Official artifact · License

The exact native model card explicitly releases VoxCPM2 under Apache-2.0; the native runtime includes the actual Apache-2.0 license text. Retain relevant license/copyright/NOTICE and modification notices; patent and trademark conditions apply. The AudioVAE is part of the same published model repository. This fixed original-text recipe supplies no reference voice or external audio and grants no rights to impersonate anyone. No legal clearance or publisher endorsement is claimed.

  • Main checkpoint 4,580,080,592 bytes plus AudioVAE 376,951,122 bytes = 4,957,031,714 published weight bytes, excluding all runtime/cache/activation/OS overhead.
  • 32 GiB RAM, 40 GiB disk and four threads are author planning allocations, not measured minima or guaranteed fit. BF16 CPU support does not imply fast execution.

One-time setup and hardware

hardware
Unknown — Existing desktop purchase price and incremental hardware cost depend on the reader; unknown.
Existing desktop purchase price and incremental hardware cost depend on the reader; unknown. Tax: unknown.
setup
Unknown — Initial provisioning/build time and labor cost are unknown.
Initial provisioning/build time and labor cost are unknown. Tax: unknown.

Recurring costs

electricity
Unknown — Actual wall power, electricity rate and powered hours are unknown; no TDP-to-wall-power conversion.
Actual wall power, electricity rate and powered hours are unknown; no TDP-to-wall-power conversion. Tax: unknown.
storage
Unknown — Incremental cost of local disk capacity is unknown. The 40 GiB free-disk walkthrough allocation is an author choice, not a measured requirement.
Incremental cost of local disk capacity is unknown. The 40 GiB free-disk walkthrough allocation is an author choice, not a measured requirement. Tax: unknown.
egress
Unknown — Data transfer price, allowances and traffic volume are unknown.
Data transfer price, allowances and traffic volume are unknown. Tax: unknown.
administration
Unknown — Maintenance labor rate and hours are unknown.
Maintenance labor rate and hours are unknown. Tax: unknown.

Install the runtime

  1. 1. Create the exact native CPU research environment

    Install exact CPU wheels and pinned direct dependencies, then use the pinned native source checkout via an explicit PYTHONPATH without installing its broad UI/ASR package bundle. The archived runtime import closure supports this fixed zero-shot path. No UI, denoiser, text normalizer, VAD, prompt-audio loading or external model is enabled. Record transitive/system resolution and stop on pip check or imports/model-load errors; this stack is not executed by the author.

    Command
    set -e
    sudo apt-get update
    sudo apt-get install -y python3.12 python3.12-venv git curl ca-certificates openssl libsndfile1
    mkdir -p "$HOME/voxcpm2-tts/models"
    cd "$HOME/voxcpm2-tts"
    python3.12 -m venv .venv
    .venv/bin/python -m pip install 'https://download.pytorch.org/whl/cpu/torch-2.6.0%2Bcpu-cp312-cp312-linux_x86_64.whl#sha256=59e78aa0c690f70734e42670036d6b541930b8eabbaa18d94e090abf14cc4d91' 'https://download-r2.pytorch.org/whl/cpu/torchaudio-2.6.0%2Bcpu-cp312-cp312-linux_x86_64.whl#sha256=dfb1ae1d7da1e869a6a6a315cc2b2652c43e3aabb5184da4d363d1b4bb2c86a4'
    .venv/bin/python -m pip install 'numpy==2.2.3' 'transformers==4.57.6' 'einops==0.8.1' 'pydantic==2.10.6' 'safetensors==0.5.3' 'huggingface-hub==0.36.0' 'librosa==0.11.0' 'soundfile==0.13.1' 'tqdm==4.67.1'
    git clone https://github.com/OpenBMB/VoxCPM.git native-voxcpm
    git -C native-voxcpm checkout --detach cce58a5b59303c9bd63d12f23afe6a49a4a80c59
    .venv/bin/python -m pip check
    .venv/bin/python -m pip freeze > resolved-environment.txt

    Expected result: Pinned checkout/hashed CPU wheels and direct dependencies resolve; pip check passes for the selected installed packages and resolution is saved. The native source is imported only at reader start, through explicit PYTHONPATH. No actual model load is preclaimed.

Download the model

  1. 1. Download and hash all native model and AudioVAE assets

    Reader downloads only; the author archived metadata/source without weight bodies. Use exact native revision for both weights and tokenizer/config files. The manifest pins file bytes rather than mutable API counters. Stop on any failure; do not accept an alternate codec or revision.

    Command
    set -e
    cd "$HOME/voxcpm2-tts"
    curl --fail --location 'https://huggingface.co/openbmb/VoxCPM2/resolve/32279effe8c19989596f05d353d1447f51d9e915/model.safetensors' -o 'models/model.safetensors'
    curl --fail --location 'https://huggingface.co/openbmb/VoxCPM2/resolve/32279effe8c19989596f05d353d1447f51d9e915/audiovae.pth' -o 'models/audiovae.pth'
    curl --fail --location 'https://huggingface.co/openbmb/VoxCPM2/resolve/32279effe8c19989596f05d353d1447f51d9e915/config.json' -o 'models/config.json'
    curl --fail --location 'https://huggingface.co/openbmb/VoxCPM2/resolve/32279effe8c19989596f05d353d1447f51d9e915/tokenizer.json' -o 'models/tokenizer.json'
    curl --fail --location 'https://huggingface.co/openbmb/VoxCPM2/resolve/32279effe8c19989596f05d353d1447f51d9e915/tokenizer_config.json' -o 'models/tokenizer_config.json'
    curl --fail --location 'https://huggingface.co/openbmb/VoxCPM2/resolve/32279effe8c19989596f05d353d1447f51d9e915/special_tokens_map.json' -o 'models/special_tokens_map.json'
    cat > asset-manifest.json <<'PYMANIFEST'
    {
      "model.safetensors": {
        "bytes": 4580080592,
        "sha256": "f7f964cfa9da23653baec6e6f7750719977ad944ed9f95fe52fe3a620506891d"
      },
      "audiovae.pth": {
        "bytes": 376951122,
        "sha256": "94b5d51e107e0507d4acc976cfdadb64edd6fd06d1f751dadbf2fd1594274bf1"
      },
      "config.json": {
        "bytes": 4336,
        "sha256": "405f0dcd92f7feba6011ed4eac5c8d4f74cba9712f07fd5cfa3063bbdd95402c"
      },
      "tokenizer.json": {
        "bytes": 3676772,
        "sha256": "f8984687e4a92a3503d521396d454b7d68e9fdaab2a0288eb3536c7c1aa4bc20"
      },
      "tokenizer_config.json": {
        "bytes": 5059,
        "sha256": "e78a3ebb48a0b9437efd1823b6b726c823da89e49dd8bcc90c02419d9baa772b"
      },
      "special_tokens_map.json": {
        "bytes": 1632,
        "sha256": "068594063e37662c02b21acf42ebb334ef6a74fb810e68a2368f88f08351de76"
      }
    }
    PYMANIFEST
    .venv/bin/python - <<'PYHASH'
    import hashlib,json,pathlib
    for name,expected in json.load(open('asset-manifest.json')).items():
     file=pathlib.Path('models')/name;digest=hashlib.sha256()
     with file.open('rb') as stream:
      for chunk in iter(lambda:stream.read(1024*1024),b''):digest.update(chunk)
     assert file.stat().st_size==expected['bytes'] and digest.hexdigest()==expected['sha256'],name
    print('All six native asset length/hash checks passed.')
    PYHASH

    Expected result: All six native asset length/hash checks pass. The main model and AudioVAE are independently checked; no weight-body validation was performed by the author.

Configure

  1. 1. Create a stable private local key

    Generate only when absent. Never print/share the key; preserve an existing key on repeated setup.

    Command
    cd "$HOME/voxcpm2-tts"
    umask 077
    if [ ! -e .api-key ]; then openssl rand -hex 32 > .api-key; fi
    chmod 600 .api-key
    test -s .api-key || { echo "Restore empty service key before continuing"; exit 1; }

    Expected result: A private nonempty key exists; the server requires exactly 64 lowercase hexadecimal characters.

  2. 2. Create the bounded native speech harness

    This reader-authored serial loopback server accepts only its fixed sentence fixture ID. It loads local pinned assets offline, disables compilation/warm-up and adds a base-keyset check because the native loader uses strict=False for LoRA compatibility. No reference voice, denoiser, normalizer or voice clone is used. Native generator returns patch features for an exact early-stop cap check.

    Command
    cd "$HOME/voxcpm2-tts"
    cat > voxcpm2_tts.py <<'PYSERVER1'
    """Private fixed-sentence native VoxCPM2 research harness; no model imports in boundary tests."""
    import base64,hashlib,hmac,io,json,math,pathlib,struct,wave
    from http.server import BaseHTTPRequestHandler,HTTPServer
    ROOT=pathlib.Path(__file__).resolve().parent
    PORT=8994
    TEXT='The library opens at nine in the morning. Please return your books by Friday.'
    CAPACITY=8192
    MAX_TEXT=128
    MAX_PATCHES=256
    
    def validate_audio(result):
        samples,text_count,patches,cap=result
        if type(text_count) is not int or not 1<=text_count<=MAX_TEXT:raise ValueError('text_budget')
        if type(cap) is not int or cap!=min(text_count*6+10,MAX_PATCHES):raise ValueError('effective_cap')
        if type(patches) is not int or not 4<=patches<cap:raise ValueError('not_native_early_stop')
        if text_count+1+cap>CAPACITY:raise ValueError('shared_budget')
        if not isinstance(samples,list) or not 1<=len(samples)<=MAX_PATCHES*7680:raise ValueError('audio_length')
        if any(type(x) not in (float,int) or not math.isfinite(x) or abs(x)>1 for x in samples):raise ValueError('invalid_or_clipping_audio')
        rms=math.sqrt(sum(x*x for x in samples)/len(samples))
        if rms==0:raise ValueError('silent_audio')
        buf=io.BytesIO()
        with wave.open(buf,'wb') as out:
            out.setnchannels(1);out.setsampwidth(2);out.setframerate(48000)
            out.writeframes(b''.join(struct.pack('<h',round(x*32767)) for x in samples))
        return {'fixture':'author-sentence-v1','text':TEXT,'text_tokens':text_count,'audio_start_positions':1,
                'native_shared_sequence_capacity':CAPACITY,'acoustic_patches':patches,'effective_patch_cap':cap,
                'native_early_stop':True,'sample_rate':48000,'channels':1,'sample_count':len(samples),
                'duration_seconds':len(samples)/48000,'peak':max(abs(x) for x in samples),'rms':rms,
                'wav_base64':base64.b64encode(buf.getvalue()).decode('ascii'),'generated_text_tokens':None}
    
    def make_encoder():
        import os
        os.environ['HF_HUB_OFFLINE']='1';os.environ['TRANSFORMERS_OFFLINE']='1'
        import torch
        from safetensors import safe_open
        from voxcpm.model.voxcpm2 import VoxCPM2Model
        torch.set_num_threads(4)
        path=ROOT/'models'
        expected=json.loads((ROOT/'asset-manifest.json').read_text())
        for name,info in expected.items():
            file=path/name; digest=hashlib.sha256()
            with file.open('rb') as stream:
                for chunk in iter(lambda:stream.read(1024*1024),b''):digest.update(chunk)
            if file.stat().st_size!=info['bytes'] or digest.hexdigest()!=info['sha256']:raise RuntimeError('asset_mismatch:'+name)
        model=VoxCPM2Model.from_local(str(path),optimize=False,device='cpu')
        # Native loader uses strict=False; this independent base-keyset guard is explicit.
        with safe_open(str(path/'model.safetensors'),framework='pt',device='cpu') as f:keys=set(f.keys())
        vae=torch.load(path/'audiovae.pth',map_location='cpu',weights_only=True)
        vae=vae.get('state_dict',vae);keys|={'audio_vae.'+k for k in vae}
        if keys!=set(model.state_dict()):raise RuntimeError('base_state_keyset_mismatch')
        del vae
        if model.device!='cpu' or model.config.dtype!='bfloat16' or model.config.max_length!=8192:raise RuntimeError('runtime_configuration')
        if model.sample_rate!=48000 or model.patch_size!=4 or model._decode_chunk_size!=1920:raise RuntimeError('audio_configuration')
        def encode():
            ids=model.text_tokenizer(TEXT);n=len(ids);cap=min(n*6+10,MAX_PATCHES)
            if not 1<=n<=MAX_TEXT or n+1+cap>CAPACITY:raise ValueError('input_budget')
            with torch.inference_mode():
                audio,target_ids,features=model.generate_with_prompt_cache(target_text=TEXT,prompt_cache=None,
                    min_len=2,max_len=MAX_PATCHES,inference_timesteps=10,cfg_value=2.0,
                    retry_badcase=False,retry_badcase_max_times=1,seed=1234)
            if target_ids.tolist()!=ids or list(features.shape[1:])!=[4,64]:raise RuntimeError('native_output_shape')
            if audio.ndim!=2 or audio.shape[0]!=1:raise RuntimeError('mono_shape')
            return audio[0].float().tolist(),n,int(features.shape[0]),cap
        return encode
    
    PYSERVER1

    Expected result: Dedicated wrapper is saved. Actual asset/keyset/model/dtype/shape checks run only when the reader starts the service; errors stop startup.

  3. 3. Append the bounded HTTP boundary

    Append the serial authenticated fixed-input HTTP server and entry point to the same dedicated file.

    Command
    cd "$HOME/voxcpm2-tts"
    cat >> voxcpm2_tts.py <<'PYSERVER2'
    def create_server(encoder, key, port=PORT):
        if not isinstance(key, str) or len(key) != 64 or any(c not in '0123456789abcdef' for c in key):
            raise ValueError('key_format')
    
        class Handler(BaseHTTPRequestHandler):
            def setup(self):
                super().setup()
                self.connection.settimeout(5)
    
            def log_message(self, *_):
                pass  # Do not log Bearer keys or request bodies.
    
            def reply(self, status, obj):
                data = json.dumps(obj, allow_nan=False, separators=(',', ':')).encode()
                self.send_response(status)
                self.send_header('Content-Type', 'application/json')
                self.send_header('Content-Length', str(len(data)))
                self.send_header('Connection', 'close')
                self.end_headers()
                self.wfile.write(data)
                self.close_connection = True
    
            def authorized(self):
                values = self.headers.get_all('Authorization', [])
                return len(values) == 1 and hmac.compare_digest(values[0].encode(), ('Bearer ' + key).encode())
    
            def do_GET(self):
                if not self.authorized():
                    return self.reply(401, {'error': 'unauthorized'})
                if self.path != '/health':
                    return self.reply(404, {'error': 'not_found'})
                return self.reply(200, {'ready': True, 'native_shared_sequence_capacity': 8192,
                                        'generated_text_tokens': None})
    
            def do_POST(self):
                if not self.authorized():
                    return self.reply(401, {'error': 'unauthorized'})
                if self.path != '/v1/speech':
                    return self.reply(404, {'error': 'not_found'})
                if self.headers.get('Transfer-Encoding') is not None:
                    return self.reply(400, {'error': 'transfer_encoding_unsupported'})
                lengths = self.headers.get_all('Content-Length', [])
                if len(lengths) != 1:
                    return self.reply(411, {'error': 'content_length_required'})
                raw = lengths[0]
                if not raw or len(raw) > 4 or any(c not in '0123456789' for c in raw):
                    return self.reply(413, {'error': 'invalid_or_excessive_length'})
                n = int(raw)
                if n < 1 or n > 256:
                    return self.reply(413, {'error': 'body_limit'})
                if self.headers.get_content_type() != 'application/json':
                    return self.reply(415, {'error': 'json_required'})
                try:
                    body = self.rfile.read(n)
                except (TimeoutError, OSError):
                    return self.reply(408, {'error': 'body_timeout'})
                if len(body) != n:
                    return self.reply(400, {'error': 'incomplete_body'})
                try:
                    def unique(pairs):
                        obj = {}
                        for k, v in pairs:
                            if k in obj:
                                raise ValueError('duplicate_key')
                            obj[k] = v
                        return obj
                    request = json.loads(body.decode('utf-8'), object_pairs_hook=unique,
                                         parse_constant=lambda _: (_ for _ in ()).throw(ValueError('constant')))
                except (ValueError, UnicodeError, RecursionError):
                    return self.reply(400, {'error': 'invalid_json'})
                if type(request) is not dict or request != {'fixture': 'author-sentence-v1'}:
                    return self.reply(400, {'error': 'fixed_fixture_required'})
                try:
                    result = validate_audio(encoder())
                except Exception:
                    return self.reply(500, {'error': 'synthesis_or_audio_failure'})
                return self.reply(200, result)
    
        return HTTPServer(('127.0.0.1', port), Handler)
    
    
    if __name__=='__main__':
        encoder=make_encoder()
        with create_server(encoder,(ROOT/'.api-key').read_text().strip()) as server:server.serve_forever()
    PYSERVER2

    Expected result: Complete native wrapper saved; no external model imports occur until reader start.

Start the model

  1. 1. Start the private native TTS experiment

    Run in foreground with exact source path. The native local loader uses BF16 on CPU and FP32 AudioVAE, optimizeFalse, no reference or auxiliary denoiser. One request runs at a time; native static caches are refilled from the fixed text each time. No inference deadline is implemented.

    Command
    cd "$HOME/voxcpm2-tts"
    PYTHONPATH="$HOME/voxcpm2-tts/native-voxcpm/src" HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1 .venv/bin/python voxcpm2_tts.py

    Expected result: After actual reader execution and successful asset/native checks, loopback 8994 listens. No successful load, audio or CPU fit is preclaimed.

Connect to your endpoint

http://127.0.0.1:8994/v1/speech

Same trusted desktop loopback only; listen to the generated local WAV with your usual audio player.

Access: loopback. Private Bearer key required on health and speech routes. Only fixture author-sentence-v1 accepted; no uploaded audio/text/URLs/paths/voices. JSON response contains native counts and base64 PCM16 WAV.

Verify the setup

  1. 1. Check the waveform and listen to the complete original sentence

    Use a second terminal on the server. Request only the fixed original sentence. Protocol checks validate native text/control/acoustic budgeting, strict early stop below its effective cap and finite non-silent unclipped PCM16 output. Listen to the generated WAV yourself: the sentence should be intelligible and complete, with no cut-off words or unwanted noise. Native early termination is not proof of spoken content or quality; record a failed listening result honestly.

    Command
    set -e
    cd "$HOME/voxcpm2-tts"
    TTS_KEY="$(cat .api-key)"
    curl --fail http://127.0.0.1:8994/health -H "Authorization: Bearer $TTS_KEY"
    curl --fail http://127.0.0.1:8994/v1/speech -H "Authorization: Bearer $TTS_KEY" -H 'Content-Type: application/json' -d '{"fixture":"author-sentence-v1"}' -o speech.json
    curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8994/health
    unset TTS_KEY
    .venv/bin/python - <<'PYVERIFY'
    import base64,io,json,wave
    d=json.load(open("speech.json"));assert d["native_shared_sequence_capacity"]==8192 and d["generated_text_tokens"] is None
    assert d["native_early_stop"] is True and 4<=d["acoustic_patches"]<d["effective_patch_cap"]
    assert d["effective_patch_cap"]==min(d["text_tokens"]*6+10,256) and d["text_tokens"]+1+d["effective_patch_cap"]<=8192
    b=base64.b64decode(d["wav_base64"],validate=True)
    with wave.open(io.BytesIO(b),"rb") as w:
     assert w.getframerate()==48000 and w.getnchannels()==1 and w.getsampwidth()==2 and w.getnframes()==d["sample_count"]
    open("speech.wav","wb").write(b)
    print("Listen to speech.wav against:",d["text"])
    print("Native early stop is not a guarantee of complete intelligible speech.")
    PYVERIFY

    Expected result: Authenticated health/audio return documented JSON; missing key returns401. Waveform checks pass only after actual synthesis. Save speech.wav, listen and record whether the complete original sentence is intelligible. No exact voice, duration or quality result is preclaimed.

Performance evidence

No compatible measurement available. Documented support and estimated memory do not establish response speed.

Limits and troubleshooting

  • Source-documented setup only; no model weights, installation, build, model imports, speech inference, memory fit, latency or quality observations.
  • Exact frozen API is VoxCPM2. Publisher describes 2B diffusion-autoregressive TTS with 30 languages, but this bounded original English sentence does not establish multilingual quality or family completeness.
  • The model uses text tokenizer IDs and continuous acoustic features. 8,192 is configured shared base/residual cache capacity, not 8,192 text tokens; lm_config 32,768 positions is distinct. Actualtext+audio-start+effectivepatchcap are checked. Output texttokens are N/A.
  • The native CPU path preserves BF16 model compute; AudioVAE is FP32. No claim of CPU BF16 hardware acceleration, FP32-equivalent behavior, fit or speed. Native internal scripted functions remain; optimizeFalse disables model compilation/warm-up.
  • Both main safetensors and same-revision AudioVAE PyTorch checkpoint are pinned. Native AudioVAE loader uses weights_only=True. Native model load uses strict=False; the custom startup keyset guard separately rejects missing/unexpected base keys. This has not been executed with weights.
  • Native zero-shot generate_with_prompt_cache(None) returns generated feature patches. With no reference/continuation context_len 0, fewer patches than min(textlength*6+10,256) proves the native early-break branch, not intelligibility or utterance correctness. Cap exhaustion is rejected.
  • Ten diffusion steps, guidance 2, minimum length 2 and seed 1234 are explicit native-compatible settings. No text sampler or chat/thinking template applies. Stochastic reproducibility and speaker consistency are not benchmarked.
  • Fixed-input HTTPServer has private auth, serial execution, request-body/socket limits and strict response checks. It has no model-execution deadline, public/TLS/multiuser suitability or voice safety certification. Privileged local users remain trusted.
  • The direct dependency/source path avoids broad native UI/ASR/normalizer/denoiser integrations. Native code imports its originalVoxCPM compatibility module and torchaudio, so CPU torchaudio is still installed. Exact runtime compatibility is source-qualified, not installed/model-tested; pip freeze records resolved transitive dependencies.
  • 32 GiB RAM, 40 GiB disk and four threads are planning allocations. Published weight sizes exclude caches/activations/audio buffers/build/OS. Dated AWS compute/storage source facts and unknown setup, egress, tax, local power and administration costs remain distinct.
  • Reference voice/cloning requires separate consent/rights/input validation. Streaming, multilingual/voice-design/continuation, GPU and longer texts remain useful unqualified scope; this pair does not exhaust supported workflows.
Asset/keyset/model load error
Stop and verify exact model and AudioVAE assets, native source revision and dependency versions. Do not relax startup checks or substitute a different release/codec.
Audio cap reached, silence, clipping or failed listening
Record the task failure. Native early stopping, finite shape and successful HTTP do not prove intelligibility or sentence completion. Do not retry indefinitely or claim success from an invalid audio result.
Missing package, unsupported dtype or slow CPU
Save the exact traceback and resolved environment. BF16 CPU is native source support, not guaranteed fast execution or full compatibility. Changing dtype/device/dependencies requires requalification.
401 or rejected input
Use the exact fixed fixture JSON and matching private key, with loopback/SSH access. Do not expose the port or add arbitrary text/voice uploads without a new boundary assessment.

Stop the model

  1. 1. Stop the native speech endpoint

    Press Ctrl+C in its foreground terminal and unset keys. Confirm loopback 8994 closes.

    Command
    unset TTS_KEY

    Expected result: Python speech service stops; no active endpoint remains.

Uninstall

  1. 1. Remove dedicated experiment files

    Back up desired outputs/licensing records, confirm the dedicated path and remove only this workspace. Shared Ubuntu packages remain.

    Command
    rm -r -- "$HOME/voxcpm2-tts"

    Expected result: Dedicated weights, source checkout, virtualenv, manifest, wrapper, audio and private key are removed.

Sources and verification

Sources checked Oct 16, 2026. Verification source

Compare this configuration
Cloud serverDocumented setupSources checked Oct 16, 2026

Synthesize a fixed sentence with VoxCPM2 on a private CPU research VM

Use exact VoxCPM2 and its same-revision AudioVAE for private, fixed-sentence speech synthesis. Native CPU compute stays BF16 and the AudioVAE uses FP32. The 8,192-position cache is shared by text and acoustic patches; it is not an 8,192-text-token allowance. Verify native early stopping and WAV shape, then listen for the complete original English sentence. Fit, speed and voice quality remain unmeasured.

Hardware
AWS r7i.xlarge, 32 GiB RAM, Ubuntu x86_64
Runtime
Native VoxCPM2 fixed-sentence private CPU TTS harness VoxCPM cce58a5b59303c9bd63d12f23afe6a49a4a80c59; torch2.6.0+cpu; torchaudio2.6.0+cpu; Python3.12; transformers4.57.6 · CPU
Model format
safetensors · Native unquantized model; runtime BF16 base/residual/diffusion, FP32 AudioVAE · Unquantized native checkpoint; CPU BF16 compute with FP32 AudioVAE
Workload
8,192 token context · 1 concurrent

Based on source documentation; this setup has not been executed by OpenTools. Memory: unknown. No compatible measurement available.

Read the complete setup guide

Before you start

  • Use an owned AWS r7i.xlarge Ubuntu 24.04 x86_64 VM in us-east-1, 32 GiB RAM, four vCPUs and 40 GiB gp3. Existing SSH must be limited to your client; keep service port 8994 closed. Provisioning/billing are reader actions.
  • Read native model/runtime Apache-2.0 terms and retain applicable notices. This walkthrough generates only the original supplied sentence, with no voice reference, cloning, uploaded audio or external URL. Do not infer voice, likeness or consent rights from model licensing.
  • This is a source-documented private educational fixed-input service. CPU BF16 model compute and FP32 AudioVAE are native choices, not tested by the author; full fit, latency, quality and exact environment compatibility remain unknown.
  • Use the exact VoxCPM2 release and both same-pin weight files. Do not substitute VoxCPM1/1.5, an external codec, an unpinned normalizer/denoiser or a provider TTS API.

Exact model and requirements

Artifact:
OpenBMB / VoxCPM2 native model with AudioVAE V2 auxiliary checkpoint · checkpoint openbmb/VoxCPM2
Publisher revision:
32279effe8c19989596f05d353d1447f51d9e915
Artifact digest:
sha256:f7f964cfa9da23653baec6e6f7750719977ad944ed9f95fe52fe3a620506891d
Download size:
4.27 GiB
Memory requirement (unknown):
Unknown — No full process peak measured for exact native BF16 CPU, FP32 AudioVAE, static caches and output buffers.
System RAM / disk:
Unknown / Unknown
Placement:
single device. One CPU host; no tensor or pipeline sharding. Whole native speech model, text-token preparation, acoustic patch generation and AudioVAE decoding run on CPU; no GPU or disk offload.
KV cache:
Native BF16 static base/residual LM caches, each capacity8192; AudioVAE/state and full process memory unmeasured.
Workload:
8,192 token context; Unknown; Not applicable; concurrency 1; batch 1; cache cold; repetitions Unknown.
Tokenizer:
Native LlamaTokenizerFast plus mask_multichar_chinese_tokens; add audio-start control101 · 32279effe8c19989596f05d353d1447f51d9e915; runtime cce58a5b59303c9bd63d12f23afe6a49a4a80c59
Sampling / reasoning:
Native diffusion guidance cfg_value 2.0 and 10 inference steps, min_len 2, max_len 256 acoustic patches, seed 1234, retry_badcase False with one attempt. Native effective cap is min(text_token_count*6+10,256). No text temperature/top-p sampler applies. No chat template or reasoning output. contextTokens 8,192 labels native shared text/acoustic autoregressive positions. Reserve actual textcount+one audio-start+effective acoustic cap within 8,192; native lm_config 32,768 position setting is distinct from allocated cache. Generated acoustic feature count strictly below the effective cap establishes native early stop only for zero-shot context_len 0, not intelligibility/quality.
Runtime prerequisites:
Ubuntu 24.04 x86_64; See prerequisites above.

Official artifact · License

The exact native model card explicitly releases VoxCPM2 under Apache-2.0; the native runtime includes the actual Apache-2.0 license text. Retain relevant license/copyright/NOTICE and modification notices; patent and trademark conditions apply. The AudioVAE is part of the same published model repository. This fixed original-text recipe supplies no reference voice or external audio and grants no rights to impersonate anyone. No legal clearance or publisher endorsement is claimed.

  • Main checkpoint 4,580,080,592 bytes plus AudioVAE 376,951,122 bytes = 4,957,031,714 published weight bytes, excluding all runtime/cache/activation/OS overhead.
  • 32 GiB RAM, 40 GiB disk and four threads are author planning allocations, not measured minima or guaranteed fit. BF16 CPU support does not imply fast execution.

One-time setup and hardware

hardware
Not applicable — AWS hardware acquisition is included in compute rental; no purchased GPU required. Any separate hardware cost is not applicable.
AWS hardware acquisition is included in compute rental; no purchased GPU required. Any separate hardware cost is not applicable. Tax: unknown.
setup
Unknown — Initial provisioning/build time and labor cost are unknown.
Initial provisioning/build time and labor cost are unknown. Tax: unknown.

Recurring costs

compute
$0.26 / hour
AWS public us-east-1 Linux On-Demand rate USD 0.2646 per billed instance-hour, no paid preinstalled software. Eight hours USD 2.1168; 24 hours USD 6.3504 compute. Idle running hours bill. Storage, egress, tax and administration separate; no Spot/reservation/discount assumed. Capacity/quota unverified. Region: us-east-1. SKU: r7i.xlarge Linux On-Demand. Tax: excluded. Price dated Oct 15, 2026. Price source
storage
$0.08 / GB month
US East (N. Virginia) gp3 USD 0.08 per GB-month; AWS defines GB as 1024^3 bytes. Selected 40 GiB root volume is USD 3.20 per full month, prorated by provisioned duration. Baseline 3000 IOPS / 125 MB/s; no extras assumed. Retained snapshots/resources separate; storage persists after instance stop. Tax excluded. Region: us-east-1. SKU: EBS gp3 baseline 3000 IOPS / 125 MB/s. Tax: excluded. Price dated Oct 16, 2026. Price source
egress
Unknown — Data transfer price, allowances and traffic volume are unknown.
Data transfer price, allowances and traffic volume are unknown. Tax: unknown.
administration
Unknown — Maintenance labor rate and hours are unknown.
Maintenance labor rate and hours are unknown. Tax: unknown.

Install the runtime

  1. 1. Create the exact native CPU research environment

    Install exact CPU wheels and pinned direct dependencies, then use the pinned native source checkout via an explicit PYTHONPATH without installing its broad UI/ASR package bundle. The archived runtime import closure supports this fixed zero-shot path. No UI, denoiser, text normalizer, VAD, prompt-audio loading or external model is enabled. Record transitive/system resolution and stop on pip check or imports/model-load errors; this stack is not executed by the author.

    Command
    set -e
    sudo apt-get update
    sudo apt-get install -y python3.12 python3.12-venv git curl ca-certificates openssl libsndfile1
    mkdir -p "$HOME/voxcpm2-tts/models"
    cd "$HOME/voxcpm2-tts"
    python3.12 -m venv .venv
    .venv/bin/python -m pip install 'https://download.pytorch.org/whl/cpu/torch-2.6.0%2Bcpu-cp312-cp312-linux_x86_64.whl#sha256=59e78aa0c690f70734e42670036d6b541930b8eabbaa18d94e090abf14cc4d91' 'https://download-r2.pytorch.org/whl/cpu/torchaudio-2.6.0%2Bcpu-cp312-cp312-linux_x86_64.whl#sha256=dfb1ae1d7da1e869a6a6a315cc2b2652c43e3aabb5184da4d363d1b4bb2c86a4'
    .venv/bin/python -m pip install 'numpy==2.2.3' 'transformers==4.57.6' 'einops==0.8.1' 'pydantic==2.10.6' 'safetensors==0.5.3' 'huggingface-hub==0.36.0' 'librosa==0.11.0' 'soundfile==0.13.1' 'tqdm==4.67.1'
    git clone https://github.com/OpenBMB/VoxCPM.git native-voxcpm
    git -C native-voxcpm checkout --detach cce58a5b59303c9bd63d12f23afe6a49a4a80c59
    .venv/bin/python -m pip check
    .venv/bin/python -m pip freeze > resolved-environment.txt

    Expected result: Pinned checkout/hashed CPU wheels and direct dependencies resolve; pip check passes for the selected installed packages and resolution is saved. The native source is imported only at reader start, through explicit PYTHONPATH. No actual model load is preclaimed.

Download the model

  1. 1. Download and hash all native model and AudioVAE assets

    Reader downloads only; the author archived metadata/source without weight bodies. Use exact native revision for both weights and tokenizer/config files. The manifest pins file bytes rather than mutable API counters. Stop on any failure; do not accept an alternate codec or revision.

    Command
    set -e
    cd "$HOME/voxcpm2-tts"
    curl --fail --location 'https://huggingface.co/openbmb/VoxCPM2/resolve/32279effe8c19989596f05d353d1447f51d9e915/model.safetensors' -o 'models/model.safetensors'
    curl --fail --location 'https://huggingface.co/openbmb/VoxCPM2/resolve/32279effe8c19989596f05d353d1447f51d9e915/audiovae.pth' -o 'models/audiovae.pth'
    curl --fail --location 'https://huggingface.co/openbmb/VoxCPM2/resolve/32279effe8c19989596f05d353d1447f51d9e915/config.json' -o 'models/config.json'
    curl --fail --location 'https://huggingface.co/openbmb/VoxCPM2/resolve/32279effe8c19989596f05d353d1447f51d9e915/tokenizer.json' -o 'models/tokenizer.json'
    curl --fail --location 'https://huggingface.co/openbmb/VoxCPM2/resolve/32279effe8c19989596f05d353d1447f51d9e915/tokenizer_config.json' -o 'models/tokenizer_config.json'
    curl --fail --location 'https://huggingface.co/openbmb/VoxCPM2/resolve/32279effe8c19989596f05d353d1447f51d9e915/special_tokens_map.json' -o 'models/special_tokens_map.json'
    cat > asset-manifest.json <<'PYMANIFEST'
    {
      "model.safetensors": {
        "bytes": 4580080592,
        "sha256": "f7f964cfa9da23653baec6e6f7750719977ad944ed9f95fe52fe3a620506891d"
      },
      "audiovae.pth": {
        "bytes": 376951122,
        "sha256": "94b5d51e107e0507d4acc976cfdadb64edd6fd06d1f751dadbf2fd1594274bf1"
      },
      "config.json": {
        "bytes": 4336,
        "sha256": "405f0dcd92f7feba6011ed4eac5c8d4f74cba9712f07fd5cfa3063bbdd95402c"
      },
      "tokenizer.json": {
        "bytes": 3676772,
        "sha256": "f8984687e4a92a3503d521396d454b7d68e9fdaab2a0288eb3536c7c1aa4bc20"
      },
      "tokenizer_config.json": {
        "bytes": 5059,
        "sha256": "e78a3ebb48a0b9437efd1823b6b726c823da89e49dd8bcc90c02419d9baa772b"
      },
      "special_tokens_map.json": {
        "bytes": 1632,
        "sha256": "068594063e37662c02b21acf42ebb334ef6a74fb810e68a2368f88f08351de76"
      }
    }
    PYMANIFEST
    .venv/bin/python - <<'PYHASH'
    import hashlib,json,pathlib
    for name,expected in json.load(open('asset-manifest.json')).items():
     file=pathlib.Path('models')/name;digest=hashlib.sha256()
     with file.open('rb') as stream:
      for chunk in iter(lambda:stream.read(1024*1024),b''):digest.update(chunk)
     assert file.stat().st_size==expected['bytes'] and digest.hexdigest()==expected['sha256'],name
    print('All six native asset length/hash checks passed.')
    PYHASH

    Expected result: All six native asset length/hash checks pass. The main model and AudioVAE are independently checked; no weight-body validation was performed by the author.

Configure

  1. 1. Create a stable private local key

    Generate only when absent. Never print/share the key; preserve an existing key on repeated setup.

    Command
    cd "$HOME/voxcpm2-tts"
    umask 077
    if [ ! -e .api-key ]; then openssl rand -hex 32 > .api-key; fi
    chmod 600 .api-key
    test -s .api-key || { echo "Restore empty service key before continuing"; exit 1; }

    Expected result: A private nonempty key exists; the server requires exactly 64 lowercase hexadecimal characters.

  2. 2. Create the bounded native speech harness

    This reader-authored serial loopback server accepts only its fixed sentence fixture ID. It loads local pinned assets offline, disables compilation/warm-up and adds a base-keyset check because the native loader uses strict=False for LoRA compatibility. No reference voice, denoiser, normalizer or voice clone is used. Native generator returns patch features for an exact early-stop cap check.

    Command
    cd "$HOME/voxcpm2-tts"
    cat > voxcpm2_tts.py <<'PYSERVER1'
    """Private fixed-sentence native VoxCPM2 research harness; no model imports in boundary tests."""
    import base64,hashlib,hmac,io,json,math,pathlib,struct,wave
    from http.server import BaseHTTPRequestHandler,HTTPServer
    ROOT=pathlib.Path(__file__).resolve().parent
    PORT=8994
    TEXT='The library opens at nine in the morning. Please return your books by Friday.'
    CAPACITY=8192
    MAX_TEXT=128
    MAX_PATCHES=256
    
    def validate_audio(result):
        samples,text_count,patches,cap=result
        if type(text_count) is not int or not 1<=text_count<=MAX_TEXT:raise ValueError('text_budget')
        if type(cap) is not int or cap!=min(text_count*6+10,MAX_PATCHES):raise ValueError('effective_cap')
        if type(patches) is not int or not 4<=patches<cap:raise ValueError('not_native_early_stop')
        if text_count+1+cap>CAPACITY:raise ValueError('shared_budget')
        if not isinstance(samples,list) or not 1<=len(samples)<=MAX_PATCHES*7680:raise ValueError('audio_length')
        if any(type(x) not in (float,int) or not math.isfinite(x) or abs(x)>1 for x in samples):raise ValueError('invalid_or_clipping_audio')
        rms=math.sqrt(sum(x*x for x in samples)/len(samples))
        if rms==0:raise ValueError('silent_audio')
        buf=io.BytesIO()
        with wave.open(buf,'wb') as out:
            out.setnchannels(1);out.setsampwidth(2);out.setframerate(48000)
            out.writeframes(b''.join(struct.pack('<h',round(x*32767)) for x in samples))
        return {'fixture':'author-sentence-v1','text':TEXT,'text_tokens':text_count,'audio_start_positions':1,
                'native_shared_sequence_capacity':CAPACITY,'acoustic_patches':patches,'effective_patch_cap':cap,
                'native_early_stop':True,'sample_rate':48000,'channels':1,'sample_count':len(samples),
                'duration_seconds':len(samples)/48000,'peak':max(abs(x) for x in samples),'rms':rms,
                'wav_base64':base64.b64encode(buf.getvalue()).decode('ascii'),'generated_text_tokens':None}
    
    def make_encoder():
        import os
        os.environ['HF_HUB_OFFLINE']='1';os.environ['TRANSFORMERS_OFFLINE']='1'
        import torch
        from safetensors import safe_open
        from voxcpm.model.voxcpm2 import VoxCPM2Model
        torch.set_num_threads(4)
        path=ROOT/'models'
        expected=json.loads((ROOT/'asset-manifest.json').read_text())
        for name,info in expected.items():
            file=path/name; digest=hashlib.sha256()
            with file.open('rb') as stream:
                for chunk in iter(lambda:stream.read(1024*1024),b''):digest.update(chunk)
            if file.stat().st_size!=info['bytes'] or digest.hexdigest()!=info['sha256']:raise RuntimeError('asset_mismatch:'+name)
        model=VoxCPM2Model.from_local(str(path),optimize=False,device='cpu')
        # Native loader uses strict=False; this independent base-keyset guard is explicit.
        with safe_open(str(path/'model.safetensors'),framework='pt',device='cpu') as f:keys=set(f.keys())
        vae=torch.load(path/'audiovae.pth',map_location='cpu',weights_only=True)
        vae=vae.get('state_dict',vae);keys|={'audio_vae.'+k for k in vae}
        if keys!=set(model.state_dict()):raise RuntimeError('base_state_keyset_mismatch')
        del vae
        if model.device!='cpu' or model.config.dtype!='bfloat16' or model.config.max_length!=8192:raise RuntimeError('runtime_configuration')
        if model.sample_rate!=48000 or model.patch_size!=4 or model._decode_chunk_size!=1920:raise RuntimeError('audio_configuration')
        def encode():
            ids=model.text_tokenizer(TEXT);n=len(ids);cap=min(n*6+10,MAX_PATCHES)
            if not 1<=n<=MAX_TEXT or n+1+cap>CAPACITY:raise ValueError('input_budget')
            with torch.inference_mode():
                audio,target_ids,features=model.generate_with_prompt_cache(target_text=TEXT,prompt_cache=None,
                    min_len=2,max_len=MAX_PATCHES,inference_timesteps=10,cfg_value=2.0,
                    retry_badcase=False,retry_badcase_max_times=1,seed=1234)
            if target_ids.tolist()!=ids or list(features.shape[1:])!=[4,64]:raise RuntimeError('native_output_shape')
            if audio.ndim!=2 or audio.shape[0]!=1:raise RuntimeError('mono_shape')
            return audio[0].float().tolist(),n,int(features.shape[0]),cap
        return encode
    
    PYSERVER1

    Expected result: Dedicated wrapper is saved. Actual asset/keyset/model/dtype/shape checks run only when the reader starts the service; errors stop startup.

  3. 3. Append the bounded HTTP boundary

    Append the serial authenticated fixed-input HTTP server and entry point to the same dedicated file.

    Command
    cd "$HOME/voxcpm2-tts"
    cat >> voxcpm2_tts.py <<'PYSERVER2'
    def create_server(encoder, key, port=PORT):
        if not isinstance(key, str) or len(key) != 64 or any(c not in '0123456789abcdef' for c in key):
            raise ValueError('key_format')
    
        class Handler(BaseHTTPRequestHandler):
            def setup(self):
                super().setup()
                self.connection.settimeout(5)
    
            def log_message(self, *_):
                pass  # Do not log Bearer keys or request bodies.
    
            def reply(self, status, obj):
                data = json.dumps(obj, allow_nan=False, separators=(',', ':')).encode()
                self.send_response(status)
                self.send_header('Content-Type', 'application/json')
                self.send_header('Content-Length', str(len(data)))
                self.send_header('Connection', 'close')
                self.end_headers()
                self.wfile.write(data)
                self.close_connection = True
    
            def authorized(self):
                values = self.headers.get_all('Authorization', [])
                return len(values) == 1 and hmac.compare_digest(values[0].encode(), ('Bearer ' + key).encode())
    
            def do_GET(self):
                if not self.authorized():
                    return self.reply(401, {'error': 'unauthorized'})
                if self.path != '/health':
                    return self.reply(404, {'error': 'not_found'})
                return self.reply(200, {'ready': True, 'native_shared_sequence_capacity': 8192,
                                        'generated_text_tokens': None})
    
            def do_POST(self):
                if not self.authorized():
                    return self.reply(401, {'error': 'unauthorized'})
                if self.path != '/v1/speech':
                    return self.reply(404, {'error': 'not_found'})
                if self.headers.get('Transfer-Encoding') is not None:
                    return self.reply(400, {'error': 'transfer_encoding_unsupported'})
                lengths = self.headers.get_all('Content-Length', [])
                if len(lengths) != 1:
                    return self.reply(411, {'error': 'content_length_required'})
                raw = lengths[0]
                if not raw or len(raw) > 4 or any(c not in '0123456789' for c in raw):
                    return self.reply(413, {'error': 'invalid_or_excessive_length'})
                n = int(raw)
                if n < 1 or n > 256:
                    return self.reply(413, {'error': 'body_limit'})
                if self.headers.get_content_type() != 'application/json':
                    return self.reply(415, {'error': 'json_required'})
                try:
                    body = self.rfile.read(n)
                except (TimeoutError, OSError):
                    return self.reply(408, {'error': 'body_timeout'})
                if len(body) != n:
                    return self.reply(400, {'error': 'incomplete_body'})
                try:
                    def unique(pairs):
                        obj = {}
                        for k, v in pairs:
                            if k in obj:
                                raise ValueError('duplicate_key')
                            obj[k] = v
                        return obj
                    request = json.loads(body.decode('utf-8'), object_pairs_hook=unique,
                                         parse_constant=lambda _: (_ for _ in ()).throw(ValueError('constant')))
                except (ValueError, UnicodeError, RecursionError):
                    return self.reply(400, {'error': 'invalid_json'})
                if type(request) is not dict or request != {'fixture': 'author-sentence-v1'}:
                    return self.reply(400, {'error': 'fixed_fixture_required'})
                try:
                    result = validate_audio(encoder())
                except Exception:
                    return self.reply(500, {'error': 'synthesis_or_audio_failure'})
                return self.reply(200, result)
    
        return HTTPServer(('127.0.0.1', port), Handler)
    
    
    if __name__=='__main__':
        encoder=make_encoder()
        with create_server(encoder,(ROOT/'.api-key').read_text().strip()) as server:server.serve_forever()
    PYSERVER2

    Expected result: Complete native wrapper saved; no external model imports occur until reader start.

Start the model

  1. 1. Start the private native TTS experiment

    Run in foreground with exact source path. The native local loader uses BF16 on CPU and FP32 AudioVAE, optimizeFalse, no reference or auxiliary denoiser. One request runs at a time; native static caches are refilled from the fixed text each time. No inference deadline is implemented.

    Command
    cd "$HOME/voxcpm2-tts"
    PYTHONPATH="$HOME/voxcpm2-tts/native-voxcpm/src" HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1 .venv/bin/python voxcpm2_tts.py

    Expected result: After actual reader execution and successful asset/native checks, loopback 8994 listens. No successful load, audio or CPU fit is preclaimed.

Connect to your endpoint

http://127.0.0.1:8994/v1/speech

VM loopback only. Optional SSH tunnel: ssh -N -L 8994:127.0.0.1:8994 USER@VM_IP; keep public port 8994 closed. Shown checks run on server; copy the resulting WAV through your existing SSH access for local listening.

Access: loopback. Private Bearer key required on health and speech routes. Only fixture author-sentence-v1 accepted; no uploaded audio/text/URLs/paths/voices. JSON response contains native counts and base64 PCM16 WAV.

Verify the setup

  1. 1. Check the waveform and listen to the complete original sentence

    Use a second terminal on the server. Request only the fixed original sentence. Protocol checks validate native text/control/acoustic budgeting, strict early stop below its effective cap and finite non-silent unclipped PCM16 output. Listen to the generated WAV yourself: the sentence should be intelligible and complete, with no cut-off words or unwanted noise. Native early termination is not proof of spoken content or quality; record a failed listening result honestly.

    Command
    set -e
    cd "$HOME/voxcpm2-tts"
    TTS_KEY="$(cat .api-key)"
    curl --fail http://127.0.0.1:8994/health -H "Authorization: Bearer $TTS_KEY"
    curl --fail http://127.0.0.1:8994/v1/speech -H "Authorization: Bearer $TTS_KEY" -H 'Content-Type: application/json' -d '{"fixture":"author-sentence-v1"}' -o speech.json
    curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8994/health
    unset TTS_KEY
    .venv/bin/python - <<'PYVERIFY'
    import base64,io,json,wave
    d=json.load(open("speech.json"));assert d["native_shared_sequence_capacity"]==8192 and d["generated_text_tokens"] is None
    assert d["native_early_stop"] is True and 4<=d["acoustic_patches"]<d["effective_patch_cap"]
    assert d["effective_patch_cap"]==min(d["text_tokens"]*6+10,256) and d["text_tokens"]+1+d["effective_patch_cap"]<=8192
    b=base64.b64decode(d["wav_base64"],validate=True)
    with wave.open(io.BytesIO(b),"rb") as w:
     assert w.getframerate()==48000 and w.getnchannels()==1 and w.getsampwidth()==2 and w.getnframes()==d["sample_count"]
    open("speech.wav","wb").write(b)
    print("Listen to speech.wav against:",d["text"])
    print("Native early stop is not a guarantee of complete intelligible speech.")
    PYVERIFY

    Expected result: Authenticated health/audio return documented JSON; missing key returns401. Waveform checks pass only after actual synthesis. Save speech.wav, listen and record whether the complete original sentence is intelligible. No exact voice, duration or quality result is preclaimed.

Performance evidence

No compatible measurement available. Documented support and estimated memory do not establish response speed.

Limits and troubleshooting

  • Source-documented setup only; no model weights, installation, build, model imports, speech inference, memory fit, latency or quality observations.
  • Exact frozen API is VoxCPM2. Publisher describes 2B diffusion-autoregressive TTS with 30 languages, but this bounded original English sentence does not establish multilingual quality or family completeness.
  • The model uses text tokenizer IDs and continuous acoustic features. 8,192 is configured shared base/residual cache capacity, not 8,192 text tokens; lm_config 32,768 positions is distinct. Actualtext+audio-start+effectivepatchcap are checked. Output texttokens are N/A.
  • The native CPU path preserves BF16 model compute; AudioVAE is FP32. No claim of CPU BF16 hardware acceleration, FP32-equivalent behavior, fit or speed. Native internal scripted functions remain; optimizeFalse disables model compilation/warm-up.
  • Both main safetensors and same-revision AudioVAE PyTorch checkpoint are pinned. Native AudioVAE loader uses weights_only=True. Native model load uses strict=False; the custom startup keyset guard separately rejects missing/unexpected base keys. This has not been executed with weights.
  • Native zero-shot generate_with_prompt_cache(None) returns generated feature patches. With no reference/continuation context_len 0, fewer patches than min(textlength*6+10,256) proves the native early-break branch, not intelligibility or utterance correctness. Cap exhaustion is rejected.
  • Ten diffusion steps, guidance 2, minimum length 2 and seed 1234 are explicit native-compatible settings. No text sampler or chat/thinking template applies. Stochastic reproducibility and speaker consistency are not benchmarked.
  • Fixed-input HTTPServer has private auth, serial execution, request-body/socket limits and strict response checks. It has no model-execution deadline, public/TLS/multiuser suitability or voice safety certification. Privileged local users remain trusted.
  • The direct dependency/source path avoids broad native UI/ASR/normalizer/denoiser integrations. Native code imports its originalVoxCPM compatibility module and torchaudio, so CPU torchaudio is still installed. Exact runtime compatibility is source-qualified, not installed/model-tested; pip freeze records resolved transitive dependencies.
  • 32 GiB RAM, 40 GiB disk and four threads are planning allocations. Published weight sizes exclude caches/activations/audio buffers/build/OS. Dated AWS compute/storage source facts and unknown setup, egress, tax, local power and administration costs remain distinct.
  • Reference voice/cloning requires separate consent/rights/input validation. Streaming, multilingual/voice-design/continuation, GPU and longer texts remain useful unqualified scope; this pair does not exhaust supported workflows.
Asset/keyset/model load error
Stop and verify exact model and AudioVAE assets, native source revision and dependency versions. Do not relax startup checks or substitute a different release/codec.
Audio cap reached, silence, clipping or failed listening
Record the task failure. Native early stopping, finite shape and successful HTTP do not prove intelligibility or sentence completion. Do not retry indefinitely or claim success from an invalid audio result.
Missing package, unsupported dtype or slow CPU
Save the exact traceback and resolved environment. BF16 CPU is native source support, not guaranteed fast execution or full compatibility. Changing dtype/device/dependencies requires requalification.
401 or rejected input
Use the exact fixed fixture JSON and matching private key, with loopback/SSH access. Do not expose the port or add arbitrary text/voice uploads without a new boundary assessment.

Stop the model

  1. 1. Stop the native speech endpoint

    Press Ctrl+C in its foreground terminal and unset keys. Close SSH tunnels; stopping Python does not stop VM/storage billing.

    Command
    unset TTS_KEY

    Expected result: Python speech service stops; no active endpoint remains.

Uninstall

  1. 1. Remove dedicated experiment files

    Back up desired outputs/licensing records, confirm the dedicated path and remove only this workspace. Shared Ubuntu packages remain.

    Command
    rm -r -- "$HOME/voxcpm2-tts"

    Expected result: Dedicated weights, source checkout, virtualenv, manifest, wrapper, audio and private key are removed.

Shut down cloud resources

  1. 1. End cloud billing resources

    In EC2 us-east-1, stop for later reuse or terminate this exact VM. Review Delete on termination for its root volume. Delete unwanted detached EBS volumes, snapshots and reserved/Elastic/public IP resources. Stopped VM storage and retained resources may still bill. Check Bills/Cost Explorer later for final usage.

    Expected result: Exact instance is stopped/terminated as intended; no unwanted attached/retained billed resources remain. Instance compute stops only after its lifecycle transition, not when the Python service stops.

Sources and verification

Sources checked Oct 16, 2026. Verification source

Compare this configuration

Pricing and access

Open-source Apache-2.0 code and model weights; self-hosting compute costs apply.

OpenBMB logo
OpenBMBopen source

Open-source efficient AI models and agent infrastructure

1 ModelsFounded 2022China
View full profile

AI Models by OpenBMB

AI models from the same organization.

ModelContext WindowPrice (In / Out per M)
MiniCPMCurrent128K-- / --