a710128/nanovllm-voxcpm

303

stars

203

commits

Python

primary language

Sep 2, 2026

updated

README

Nano-vLLM-VoxCPM

An inference engine for VoxCPM based on Nano-vLLM.

Features:

  • Faster than the pytorch implementation
  • Support concurrent requests
  • Friendly async API (can be wrapped by an HTTP server; see deployment/README.md)

This repository contains a Python package (nanovllm_voxcpm/) plus an optional FastAPI demo.

Coverage: ~71% combined (core + deployment) - see CI coverage job for the latest.

Installation

Install from PyPI

Core package:

pip install nano-vllm-voxcpm

Or with uv:

uv pip install nano-vllm-voxcpm

Note: the optional FastAPI demo service (deployment/) is not published on PyPI.

Prerequisites

  • Linux / Windows + NVIDIA GPU (CUDA)
  • Python >= 3.10
  • flash-attn is required (the package imports it at runtime)

⚠️ Important Note for Windows Users: Automated installation and compilation of flash-attn is bypassed on Windows during the package setup phase to prevent build isolation and compiler errors.

Please note that a standard pip install nano-vllm-voxcpm on Windows is NOT enough by itself to run the engine. The package will fail immediately at runtime with a ModuleNotFoundError unless you install flash-attn separately in your active Python environment.

To resolve this, you must manually install a precompiled community wheel (highly recommended to avoid local MSVC/NVCC compilation headaches) matching your exact Python/PyTorch/CUDA version, or compile it locally from source.

The runtime is GPU-centric (Triton + FlashAttention). CPU-only execution is not supported.

Windows support notes:

  • Tensor parallelism (tensor_parallel_size > 1) is not supported on Windows. This path requires CUDA tensor collectives through NCCL, which is not available on Windows; use single-GPU workers on Windows or a Linux environment for tensor parallelism.
  • Advanced users can manually override automatic KV-cache sizing with NANOVLLM_SERVERPOOL_NUM_KVCACHE_BLOCKS. Leave it unset for the normal safe memory calculation. Setting it bypasses that calculation and may cause CUDA OOM if the value is too high for the GPU.

Install from source (dev)

This repo uses uv and includes a lockfile (uv.lock).

uv sync --frozen

Dev deps (tests):

uv sync --frozen --dev

Note: compiling flash-attn from source on Linux may require the native NVIDIA CUDA Toolkit (with nvcc and CUDA headers) to be present in your system PATH.

Basic Usage

See example.py for an end-to-end async example.

Quickstart:

uv run python example.py

Load a model

VoxCPM.from_pretrained(...) accepts either:

  • a local model directory path, or
  • a HuggingFace repo id (it will download via huggingface_hub.snapshot_download).

The model directory is expected to contain:

  • config.json
  • one or more *.safetensors weight files
  • audiovae.pth (VAE weights)

Generate (async)

If you call from_pretrained() inside an async event loop, it returns an AsyncVoxCPMServerPool.

import asyncio
import numpy as np

from nanovllm_voxcpm import VoxCPM


async def main() -> None:
    server = VoxCPM.from_pretrained(
        model="/path/to/VoxCPM",
        devices=[0],
        max_num_batched_tokens=8192,
        max_num_seqs=16,
        gpu_memory_utilization=0.95,
    )
    await server.wait_for_ready()

    chunks = []
    async for chunk in server.generate(target_text="Hello world"):
        chunks.append(chunk)  # each chunk is a float32 numpy array

    wav = np.concatenate(chunks, axis=0)
    # Write with the model's sample rate (see your model's AudioVAE config; often 16000)
    # import soundfile as sf; sf.write("out.wav", wav, sample_rate)

    await server.stop()


if __name__ == "__main__":
    asyncio.run(main())

Generate (sync)

If you call from_pretrained() outside an event loop, it returns a SyncVoxCPMServerPool.

import numpy as np

from nanovllm_voxcpm import VoxCPM


server = VoxCPM.from_pretrained(model="/path/to/VoxCPM", devices=[0])
chunks = []
for chunk in server.generate(target_text="Hello world"):
    chunks.append(chunk)
wav = np.concatenate(chunks, axis=0)
server.stop()

Prompting and reference audio (optional)

The VoxCPM2 server supports these conditioning inputs:

  • zero-shot: no prompt or reference audio
  • prompt continuation: provide prompt_latents + prompt_text
  • stored prompt: provide a prompt_id (via add_prompt) and then generate with that id
  • reference audio: provide ref_audio_latents to add a separate reference-audio condition

ref_audio_latents is independent from prompt_latents:

  • use prompt_latents when you want to continue from an existing audio prefix
  • use ref_audio_latents when you want to provide extra reference audio without treating it as the decode prefix

See the public API in nanovllm_voxcpm/models/voxcpm2/server.py for details.

FastAPI demo

The HTTP server demo is documented separately to keep this README focused:

  • deployment/README.md

If you want the deployment server dependencies too, use:

uv sync --all-packages --frozen

Benchmark

The benchmark/ directory contains an end-to-end inference benchmark that drives the public server API and reports throughput/latency metrics.

Quick run:

uv run python benchmark/bench_inference.py --model ~/VoxCPM1.5 --devices 0 --concurrency 1 --warmup 1 --iters 5

Use a longer English prompt (~100 words) for more stable results:

uv run python benchmark/bench_inference.py --model ~/VoxCPM1.5 --devices 0 --concurrency 1 --warmup 1 --iters 5 \
  --target-text-file benchmark/target_text_100w_en.txt

See benchmark/README.md for more flags.

Manual GPU Smoke Suite

Use scripts/gpu_smoke.sh for manual CUDA validation on an idle Linux GPU host. It checks CUDA, FlashAttention, Triton, device visibility, and then runs the curated single-GPU or two-rank TP tests. This suite requires real CUDA hardware and does not run in CI.

# Single-device smoke
CUDA_VISIBLE_DEVICES=0 bash scripts/gpu_smoke.sh --single

# Two-device tensor-parallel smoke
CUDA_VISIBLE_DEVICES=0,1 bash scripts/gpu_smoke.sh --tp

# Intentional hidden-device diagnostic
CUDA_VISIBLE_DEVICES="" bash scripts/gpu_smoke.sh --single

# Intentional insufficient-GPU failure for TP
CUDA_VISIBLE_DEVICES=0 bash scripts/gpu_smoke.sh --tp

Reference Results (RTX 4090)

All reference numbers in this section are measured on NVIDIA GeForce RTX 4090 with openbmb/VoxCPM2. The benchmark defines RTF_per_req_mean as the mean over requests of ((request_wall_time - TTFB) / request_audio_duration) under the given concurrency.

Unless noted, runs use the default gpu_memory_utilization=0.8. Two high-concurrency LoRA points (short prompt @ 128, long prompt @ 64) are measured at gpu_memory_utilization=0.7 (marked with ); at the default 0.8 they can OOM on a 24 GB card. See "Memory note" below.

Short prompt, no LoRA:

concurrencyTTFB p50 (s)TTFB p90 (s)RTF_per_req_mean
10.0672 ± 0.00180.0672 ± 0.00180.1027 ± 0.0012
80.0789 ± 0.00330.0790 ± 0.00330.1307 ± 0.0006
160.0860 ± 0.00080.0864 ± 0.00090.1764 ± 0.0005
320.1142 ± 0.00230.1148 ± 0.00240.2842 ± 0.0026
640.1885 ± 0.00240.1907 ± 0.00250.6054 ± 0.0989

Long prompt, no LoRA:

concurrencyTTFB p50 (s)TTFB p90 (s)RTF_per_req_mean
10.0768 ± 0.00220.0768 ± 0.00220.1163 ± 0.0006
80.0865 ± 0.00300.0867 ± 0.00310.1492 ± 0.0007
160.1346 ± 0.00170.1349 ± 0.00170.2017 ± 0.0011
320.2677 ± 0.00100.2684 ± 0.00090.3334 ± 0.0071
640.5510 ± 0.01820.5544 ± 0.02110.6724 ± 0.0134

Short prompt, LoRA enabled with 32 runtime slots:

concurrencyTTFB p50 (s)TTFB p90 (s)RTF_per_req_mean
10.1375 ± 0.00380.1375 ± 0.00380.1284 ± 0.0003
80.2442 ± 0.06750.2444 ± 0.06750.1639 ± 0.0024
160.3771 ± 0.32790.3774 ± 0.32780.2168 ± 0.0021
320.2358 ± 0.05600.2366 ± 0.05600.3419 ± 0.0040
640.3287 ± 0.08250.3312 ± 0.08220.6400 ± 0.0192
128 †0.4712 ± 0.05130.4749 ± 0.05331.3215 ± 0.0421

Long prompt, LoRA enabled with 32 runtime slots:

concurrencyTTFB p50 (s)TTFB p90 (s)RTF_per_req_mean
10.1444 ± 0.00130.1444 ± 0.00130.1495 ± 0.0004
80.2559 ± 0.08170.2561 ± 0.08170.1894 ± 0.0004
160.3636 ± 0.31420.3653 ± 0.31370.2541 ± 0.0028
320.4441 ± 0.14440.4451 ± 0.14420.4028 ± 0.0025
64 †0.5850 ± 0.04380.5865 ± 0.04360.7403 ± 0.0045

measured at gpu_memory_utilization=0.7.

Closed-loop results:

modeusersregistered LoRAsstartedachieved rpsokerr
no LoRA6001803.001800
LoRA30321031.721030
LoRA30128901.50900
LoRA30256601.00600

Closed-loop TTFB (seconds, ok requests):

modeusersregistered LoRAsp50p90p95p99meanstdev
no LoRA6000.51350.55720.55810.55840.52630.0213
LoRA30320.17880.30380.65350.65440.22080.1448
LoRA301280.39601.03221.93442.00030.57180.5049
LoRA302560.45761.31771.31841.31920.59690.3841

Closed-loop RTF ((wall - TTFB)/audio, ok requests):

modeusersregistered LoRAsp50p90p95p99meanstdev
no LoRA6000.67370.69420.69430.69430.67850.0114
LoRA30320.44400.45890.46260.46840.42370.0570
LoRA301280.50670.53720.54790.57260.50050.0350
LoRA302560.63700.70820.71230.72350.63310.0621

Memory note: this release adds a prefill diffusion CUDA graph that improves latency/throughput but increases steady-state VRAM by roughly 2.5 GB (the extra graph pool is not yet accounted for in the automatic KV-cache budget). On a 24 GB card at high concurrency with LoRA (e.g. short prompt @ 128, long prompt @ 64), the default gpu_memory_utilization=0.9 can OOM; lower it (e.g. 0.7) or reduce max_num_seqs to run those configurations.

Acknowledgments

License

MIT License

Known Issue

If you see the errors below:

ValueError: Missing parameters: ['base_lm.embed_tokens.weight', 'base_lm.layers.0.self_attn.qkv_proj.weight', ... , 'stop_proj.weight', 'stop_proj.bias', 'stop_head.weight']
[rank0]:[W1106 07:26:04.469150505 ProcessGroupNCCL.cpp:1538] Warning: WARNING: destroy_process_group() was not called before program exit, which can leak resources. For more info, please see https://pytorch.org/docs/stable/distributed.html#shutdown (function operator())

It's because nanovllm loads model parameters from *.safetensors, but some VoxCPM releases ship weights as .pt.

Fix:

  • use a safetensors-converted checkpoint (or convert the checkpoint yourself)
  • ensure the *.safetensors files live next to config.json in the model directory

Contributors

a710128

173 commits

elismasilva

16 commits

MayDomine

7 commits

UmutAlihan

2 commits

a710128/nanovllm-voxcpm

303

stars

203

commits

Python

primary language

Sep 2, 2026

updated

README

Nano-vLLM-VoxCPM

An inference engine for VoxCPM based on Nano-vLLM.

Features:

  • Faster than the pytorch implementation
  • Support concurrent requests
  • Friendly async API (can be wrapped by an HTTP server; see deployment/README.md)

This repository contains a Python package (nanovllm_voxcpm/) plus an optional FastAPI demo.

Coverage: ~71% combined (core + deployment) - see CI coverage job for the latest.

Installation

Install from PyPI

Core package:

pip install nano-vllm-voxcpm

Or with uv:

uv pip install nano-vllm-voxcpm

Note: the optional FastAPI demo service (deployment/) is not published on PyPI.

Prerequisites

  • Linux / Windows + NVIDIA GPU (CUDA)
  • Python >= 3.10
  • flash-attn is required (the package imports it at runtime)

⚠️ Important Note for Windows Users: Automated installation and compilation of flash-attn is bypassed on Windows during the package setup phase to prevent build isolation and compiler errors.

Please note that a standard pip install nano-vllm-voxcpm on Windows is NOT enough by itself to run the engine. The package will fail immediately at runtime with a ModuleNotFoundError unless you install flash-attn separately in your active Python environment.

To resolve this, you must manually install a precompiled community wheel (highly recommended to avoid local MSVC/NVCC compilation headaches) matching your exact Python/PyTorch/CUDA version, or compile it locally from source.

The runtime is GPU-centric (Triton + FlashAttention). CPU-only execution is not supported.

Windows support notes:

  • Tensor parallelism (tensor_parallel_size > 1) is not supported on Windows. This path requires CUDA tensor collectives through NCCL, which is not available on Windows; use single-GPU workers on Windows or a Linux environment for tensor parallelism.
  • Advanced users can manually override automatic KV-cache sizing with NANOVLLM_SERVERPOOL_NUM_KVCACHE_BLOCKS. Leave it unset for the normal safe memory calculation. Setting it bypasses that calculation and may cause CUDA OOM if the value is too high for the GPU.

Install from source (dev)

This repo uses uv and includes a lockfile (uv.lock).

uv sync --frozen

Dev deps (tests):

uv sync --frozen --dev

Note: compiling flash-attn from source on Linux may require the native NVIDIA CUDA Toolkit (with nvcc and CUDA headers) to be present in your system PATH.

Basic Usage

See example.py for an end-to-end async example.

Quickstart:

uv run python example.py

Load a model

VoxCPM.from_pretrained(...) accepts either:

  • a local model directory path, or
  • a HuggingFace repo id (it will download via huggingface_hub.snapshot_download).

The model directory is expected to contain:

  • config.json
  • one or more *.safetensors weight files
  • audiovae.pth (VAE weights)

Generate (async)

If you call from_pretrained() inside an async event loop, it returns an AsyncVoxCPMServerPool.

import asyncio
import numpy as np

from nanovllm_voxcpm import VoxCPM


async def main() -> None:
    server = VoxCPM.from_pretrained(
        model="/path/to/VoxCPM",
        devices=[0],
        max_num_batched_tokens=8192,
        max_num_seqs=16,
        gpu_memory_utilization=0.95,
    )
    await server.wait_for_ready()

    chunks = []
    async for chunk in server.generate(target_text="Hello world"):
        chunks.append(chunk)  # each chunk is a float32 numpy array

    wav = np.concatenate(chunks, axis=0)
    # Write with the model's sample rate (see your model's AudioVAE config; often 16000)
    # import soundfile as sf; sf.write("out.wav", wav, sample_rate)

    await server.stop()


if __name__ == "__main__":
    asyncio.run(main())

Generate (sync)

If you call from_pretrained() outside an event loop, it returns a SyncVoxCPMServerPool.

import numpy as np

from nanovllm_voxcpm import VoxCPM


server = VoxCPM.from_pretrained(model="/path/to/VoxCPM", devices=[0])
chunks = []
for chunk in server.generate(target_text="Hello world"):
    chunks.append(chunk)
wav = np.concatenate(chunks, axis=0)
server.stop()

Prompting and reference audio (optional)

The VoxCPM2 server supports these conditioning inputs:

  • zero-shot: no prompt or reference audio
  • prompt continuation: provide prompt_latents + prompt_text
  • stored prompt: provide a prompt_id (via add_prompt) and then generate with that id
  • reference audio: provide ref_audio_latents to add a separate reference-audio condition

ref_audio_latents is independent from prompt_latents:

  • use prompt_latents when you want to continue from an existing audio prefix
  • use ref_audio_latents when you want to provide extra reference audio without treating it as the decode prefix

See the public API in nanovllm_voxcpm/models/voxcpm2/server.py for details.

FastAPI demo

The HTTP server demo is documented separately to keep this README focused:

  • deployment/README.md

If you want the deployment server dependencies too, use:

uv sync --all-packages --frozen

Benchmark

The benchmark/ directory contains an end-to-end inference benchmark that drives the public server API and reports throughput/latency metrics.

Quick run:

uv run python benchmark/bench_inference.py --model ~/VoxCPM1.5 --devices 0 --concurrency 1 --warmup 1 --iters 5

Use a longer English prompt (~100 words) for more stable results:

uv run python benchmark/bench_inference.py --model ~/VoxCPM1.5 --devices 0 --concurrency 1 --warmup 1 --iters 5 \
  --target-text-file benchmark/target_text_100w_en.txt

See benchmark/README.md for more flags.

Manual GPU Smoke Suite

Use scripts/gpu_smoke.sh for manual CUDA validation on an idle Linux GPU host. It checks CUDA, FlashAttention, Triton, device visibility, and then runs the curated single-GPU or two-rank TP tests. This suite requires real CUDA hardware and does not run in CI.

# Single-device smoke
CUDA_VISIBLE_DEVICES=0 bash scripts/gpu_smoke.sh --single

# Two-device tensor-parallel smoke
CUDA_VISIBLE_DEVICES=0,1 bash scripts/gpu_smoke.sh --tp

# Intentional hidden-device diagnostic
CUDA_VISIBLE_DEVICES="" bash scripts/gpu_smoke.sh --single

# Intentional insufficient-GPU failure for TP
CUDA_VISIBLE_DEVICES=0 bash scripts/gpu_smoke.sh --tp

Reference Results (RTX 4090)

All reference numbers in this section are measured on NVIDIA GeForce RTX 4090 with openbmb/VoxCPM2. The benchmark defines RTF_per_req_mean as the mean over requests of ((request_wall_time - TTFB) / request_audio_duration) under the given concurrency.

Unless noted, runs use the default gpu_memory_utilization=0.8. Two high-concurrency LoRA points (short prompt @ 128, long prompt @ 64) are measured at gpu_memory_utilization=0.7 (marked with ); at the default 0.8 they can OOM on a 24 GB card. See "Memory note" below.

Short prompt, no LoRA:

concurrencyTTFB p50 (s)TTFB p90 (s)RTF_per_req_mean
10.0672 ± 0.00180.0672 ± 0.00180.1027 ± 0.0012
80.0789 ± 0.00330.0790 ± 0.00330.1307 ± 0.0006
160.0860 ± 0.00080.0864 ± 0.00090.1764 ± 0.0005
320.1142 ± 0.00230.1148 ± 0.00240.2842 ± 0.0026
640.1885 ± 0.00240.1907 ± 0.00250.6054 ± 0.0989

Long prompt, no LoRA:

concurrencyTTFB p50 (s)TTFB p90 (s)RTF_per_req_mean
10.0768 ± 0.00220.0768 ± 0.00220.1163 ± 0.0006
80.0865 ± 0.00300.0867 ± 0.00310.1492 ± 0.0007
160.1346 ± 0.00170.1349 ± 0.00170.2017 ± 0.0011
320.2677 ± 0.00100.2684 ± 0.00090.3334 ± 0.0071
640.5510 ± 0.01820.5544 ± 0.02110.6724 ± 0.0134

Short prompt, LoRA enabled with 32 runtime slots:

concurrencyTTFB p50 (s)TTFB p90 (s)RTF_per_req_mean
10.1375 ± 0.00380.1375 ± 0.00380.1284 ± 0.0003
80.2442 ± 0.06750.2444 ± 0.06750.1639 ± 0.0024
160.3771 ± 0.32790.3774 ± 0.32780.2168 ± 0.0021
320.2358 ± 0.05600.2366 ± 0.05600.3419 ± 0.0040
640.3287 ± 0.08250.3312 ± 0.08220.6400 ± 0.0192
128 †0.4712 ± 0.05130.4749 ± 0.05331.3215 ± 0.0421

Long prompt, LoRA enabled with 32 runtime slots:

concurrencyTTFB p50 (s)TTFB p90 (s)RTF_per_req_mean
10.1444 ± 0.00130.1444 ± 0.00130.1495 ± 0.0004
80.2559 ± 0.08170.2561 ± 0.08170.1894 ± 0.0004
160.3636 ± 0.31420.3653 ± 0.31370.2541 ± 0.0028
320.4441 ± 0.14440.4451 ± 0.14420.4028 ± 0.0025
64 †0.5850 ± 0.04380.5865 ± 0.04360.7403 ± 0.0045

measured at gpu_memory_utilization=0.7.

Closed-loop results:

modeusersregistered LoRAsstartedachieved rpsokerr
no LoRA6001803.001800
LoRA30321031.721030
LoRA30128901.50900
LoRA30256601.00600

Closed-loop TTFB (seconds, ok requests):

modeusersregistered LoRAsp50p90p95p99meanstdev
no LoRA6000.51350.55720.55810.55840.52630.0213
LoRA30320.17880.30380.65350.65440.22080.1448
LoRA301280.39601.03221.93442.00030.57180.5049
LoRA302560.45761.31771.31841.31920.59690.3841

Closed-loop RTF ((wall - TTFB)/audio, ok requests):

modeusersregistered LoRAsp50p90p95p99meanstdev
no LoRA6000.67370.69420.69430.69430.67850.0114
LoRA30320.44400.45890.46260.46840.42370.0570
LoRA301280.50670.53720.54790.57260.50050.0350
LoRA302560.63700.70820.71230.72350.63310.0621

Memory note: this release adds a prefill diffusion CUDA graph that improves latency/throughput but increases steady-state VRAM by roughly 2.5 GB (the extra graph pool is not yet accounted for in the automatic KV-cache budget). On a 24 GB card at high concurrency with LoRA (e.g. short prompt @ 128, long prompt @ 64), the default gpu_memory_utilization=0.9 can OOM; lower it (e.g. 0.7) or reduce max_num_seqs to run those configurations.

Acknowledgments

License

MIT License

Known Issue

If you see the errors below:

ValueError: Missing parameters: ['base_lm.embed_tokens.weight', 'base_lm.layers.0.self_attn.qkv_proj.weight', ... , 'stop_proj.weight', 'stop_proj.bias', 'stop_head.weight']
[rank0]:[W1106 07:26:04.469150505 ProcessGroupNCCL.cpp:1538] Warning: WARNING: destroy_process_group() was not called before program exit, which can leak resources. For more info, please see https://pytorch.org/docs/stable/distributed.html#shutdown (function operator())

It's because nanovllm loads model parameters from *.safetensors, but some VoxCPM releases ship weights as .pt.

Fix:

  • use a safetensors-converted checkpoint (or convert the checkpoint yourself)
  • ensure the *.safetensors files live next to config.json in the model directory

Contributors

a710128

173 commits

elismasilva

16 commits

MayDomine

7 commits

UmutAlihan

2 commits

Languages

Python

99.6%