三代同堂怎么选:轻量版、1.5 与 2B 的速度音质显存对照
【免费下载链接】VoxCPMVoxCPM: Tokenizer-Free TTS for Context-Aware Speech Generation and True-to-Life Voice Cloning项目地址: https://gitcode.com/OpenBMB/VoxCPM
VoxCPM 现在有三代可以同时选择:640M 的 VoxCPM-0.5B、800M 的 VoxCPM1.5,以及把参数量拉到 2B 的 VoxCPM2。它们共用同一套 API、同一份微调脚本,甚至同一套 CLI 参数,看起来“换个模型 ID 就行”。但真正落到生产环境,三者在采样率、token 率、显存占用、首包延迟上的差别,会直接决定你应当按单卡部署还是按集群部署、单条生成还是批处理,以及是否需要更换整条音频管线。
本文不按“参数越大越强”的顺序来写,而是从当前仓库快照(配置目录里只有conf/voxcpm_v1/与conf/voxcpm_v1.5/,README 中 VoxCPM2 标注为coming soon)出发,把源码里的 hop_length、patch_size、静态 KV cache 都算一遍,再结合社区部署的实测口径,给出可以改进为决策表的结论。
一、先把三代摆到同一张表上
三代的能力边界,官方在 README.md 的 Model Versions 里已经写清了两代,第三代则通过社区发布补齐:
| 维度 | VoxCPM-0.5B(轻量版) | VoxCPM1.5 | VoxCPM2(2B) |
|---|---|---|---|
| 参数量 | 640M | 800M | 2B |
| AudioVAE 采样率 | 16 kHz | 44.1 kHz | 48 kHz |
| LM token 率 | 12.5 Hz(patch=2) | 6.25 Hz(patch=4) | 未在仓库快照中体现 |
| 语言 | 中英 | 中英 | 30 种语言 + 中文方言(社区报道在 8~9 种间有出入) |
| RTF(单卡 RTX 4090) | 0.17 | ~0.15 | 0.13(推理引擎)~0.30(裸 PyTorch) |
| 社区实测显存口径 | ~5 GB | ~6 GB | ~8 GB |
| 特色能力 | 零样本克隆、上下文感知韵律 | + 44.1kHz 高保真克隆、SFT/LoRA | + 音色设计(文字造声)、多语种 |
需要先说明:表中 0.5B 与 1.5 的 RTF、参数、采样率、token 率来自 README.md 与 docs/release_note.md;2B 一行来自社区发布与部署教程,当前仓库代码并未包含 VoxCPM2 的实现,src/voxcpm/model/下只有一套VoxCPMConfig(默认patch_size: int = 2、dtype: str = "bfloat16")。这意味着本文讨论的“迁移成本”对 1.5 是有源码兜底的,对 2B 则要留出接口变更的余量。
二、音质:采样率只是表面,patch 与 hop 的整数关系才是内核
很多人比较音质时只看“16k / 44.1k / 48k”,但在 VoxCPM 里,采样率从来不是孤立参数,它必须和 hop_length、patch_size 一起满足一个整数约束。
看 src/voxcpm/modules/audiovae/audio_vae.py 的定义:
class AudioVAEConfig(BaseModel): encoder_dim: int = 128 encoder_rates: List[int] = [2, 5, 8, 8] latent_dim: int = 64 ... sample_rate: int = 16000而 hop_length 是各级下采样率的连乘:
self.hop_length = np.prod(encoder_rates) # 2*5*8*8 = 640 self.sample_rate = sample_rate self.chunk_size = math.prod(encoder_rates) # 640这组默认值(16000 Hz / 640)恰好等于 0.5B 的几何:640 个采样点一帧 latent,16000/640 = 25 帧/秒;再乘 patch=2,正好得到 12.5 token/秒。反推 1.5 的配置:44100 Hz 要维持同样的 25 帧/秒 latent 速率,hop_length 必须是 1764(44100/25),再乘 patch=4,才能落在 6.25 token/秒。
两个直接推论:
- 1.5 的“6.25Hz”属于把 latent 帧率保持不变、只把 patch 拉长一倍的做法,并非把模型“做快”的工程技巧。这解释了 docs/release_note.md 里那句容易被误读的话:LocEnc/LocDiT 的 patch size 从 2 改成 4,LM 处理速率减半,但由于局部模块要处理更长上下文,网络深度被扩了,所以参数从 640M 涨到 800M。参数量上涨而速度反而更快,原因在这里。
- AudioVAE 的默认配置是给 0.5B 用的。
VoxCPMModel.from_local的加载逻辑是:
audio_vae_config = getattr(config, 'audio_vae_config', None) audio_vae = AudioVAE(config=audio_vae_config) if audio_vae_config else AudioVAE()如果权重目录里的config.json没有携带audio_vae_config,代码会静默回落到 16 kHz 的默认 VAE。这是从 0.5B 迁移到 1.5 时最隐蔽的一个问题:模型照样能推理,不报错,但声音会“糊”掉一层,44.1kHz 的高频细节全被丢掉。排查时应该先打印model.tts_model.sample_rate和model.tts_model.audio_vae.hop_length确认,而不要只凭听感判断。
至于客观指标,仓库里 docs/performance.md 的表格标注的是VoxCPM(0.5B)本身,1.5 的官方说法是“在评测集上表现相近”,2B 的评测数据不在本次快照中。0.5B 的成绩是:Seed-TTS-eval 英文 WER 1.85 / SIM 72.9,中文 CER 0.93 / SIM 77.2;CV3-eval 中文 CER 3.40、英文 WER 4.04。但它并非全面领先:在test-Hard上 CER 8.87,明显落后于 CosyVoice2 的 6.83。困难集才是大参数真正能拉开差距的地方,这一点在后面选型会用到。
音质还有一个容易被忽视的回退:denoise=True会先走 ZipEnhancer,而 docs/usage_guide.md 明确写了“启用提示语音增强会把采样率限制到 16kHz”。也就是说,如果你在 1.5 上开着降噪做克隆,44.1kHz 的优势会被这个开关本身抵消。想拿到高保真克隆,正确做法是关掉降噪,并在素材侧解决噪声问题。
三、速度:RTF 只差 0.02,但每秒音频的计算结构完全不同
官方给出的 RTF 是 0.17 与 ~0.15,看起来几乎一样。但 RTF 是“每个音频秒”的口径,它把两件方向相反的事混在一起了。
先看生成循环。在 src/voxcpm/model/voxcpm.py 的_inference里,每个 token 都要跑一次扩散采样:
pred_feat = self.feat_decoder( mu=dit_hidden, patch_size=self.patch_size, cond=prefix_feat_cond.transpose(1, 2).contiguous(), n_timesteps=inference_timesteps, cfg_value=cfg_value, )inference_timesteps默认 10,也就是每个 token 要跑 10 步去噪。于是每秒音频的成本可以拆成:
- 1.5:6.25 个 token × 10 步去噪 = 62.5 次扩散前向;解码端每 token 出
4 × 1764 = 7056个采样点(160 ms)。 - 0.5B:12.5 个 token × 10 步去噪 = 125 次扩散前向;解码端每 token 出
2 × 640 = 1280个采样点(80 ms)。
LM 与 DiT 的调用次数少一半,是 1.5 更快的主因;代价是 AudioVAE 解码端每秒要输出 44100 个采样点、而 0.5B 只有 16000 个,解码负载约为前者的 2.76 倍。两相抵消后,RTF 落在 0.15 对 0.17——这就是数字接近却结构不同的原因。
同一套计算规则也能解释 2B 的口径分歧。社区一边有“4090 上 RTF 0.13、比实时快 7.7 倍”的说法,另一边有教程给出裸 PyTorch 下 ~0.30 的表格。两者并不矛盾:0.13 是接入高吞吐推理引擎(Nano-vLLM 一类)之后的结果,0.30 是不做服务化、直接跑仓库原生的torch循环。同代模型在不同后端上的差距,比三代模型之间的差距还大。所以比较 RTF 时必须先对齐后端,否则表格是没有意义的。
还有一个工程细节决定了“能不能真的跑到标称 RTF”:src/voxcpm/model/voxcpm.py 的optimize()只在 CUDA + triton 下生效:
if self.device != "cuda": raise ValueError("VoxCPMModel can only be optimized on CUDA device") self.base_lm.forward_step = torch.compile(..., mode="reduce-overhead", fullgraph=True) self.residual_lm.forward_step = torch.compile(..., mode="reduce-overhead", fullgraph=True)而在__init__里optimize=True会顺带做一次预热生成(Warm up VoxCPMModel...)。也就是说,如果你在 MPS 或 CPU 上跑(__init__会自动回落到mps/cpu),既拿不到torch.compile的收益,还要付出一次预热时间。CPU/端侧的实时性,请按社区 ONNX、ANE 那条路线的实测值来估,不要直接套 4090 的 RTF。
流式场景还要算首包。src/voxcpm/core.py 把streaming_prefix_len固定在 3,即每次解码都带上最近 3 个 patch 的上下文来保证拼接平滑。换算成音频,1.5 的首包大约对应3 × 160 ms = 480 ms的内容,0.5B 是3 × 80 ms = 240 ms。轻量版在“第一句话什么时候出声”这项上天然占优,这也是它在语音助手、IVR 里仍有位置的原因。
四、显存:权重只是入场券,静态 KV cache 才是固定成本
按 bf16 计算,三代的权重下限大概是:640M → 1.28 GB,800M → 1.6 GB,2B → 4 GB。但社区给出的实测是 5 / 6 / 8 GB,多出来的部分主要来自三块。
第一块是两张静态 KV cache。__init__里对 base_lm 和 residual_lm 各做了一次:
self.base_lm.setup_cache(1, config.max_length, self.device, get_dtype(self.config.dtype)) self.residual_lm.setup_cache(1, config.max_length, self.device, get_dtype(self.config.dtype))而 src/voxcpm/modules/minicpm4/cache.py 是按max_length一次性分配的:
self.kv_cache = torch.zeros( 2, num_layers, batch_size, num_kv_heads, max_length, dim_kv_head, device=device, dtype=dtype, )config.max_length默认 4096,batch_size写死为 1。所以显存里长期躺着一块“按最长序列预留”的缓存,而且这个 batch 维度是 1——原生路径下想提高并发只能多进程/多实例,这也是社区要另做 Nano-vLLM、vLLM-Omni 集成的根本原因,而不是单纯为了“更快的 kernel”。
第二块是AudioVAE 解码器。1.5 的解码端每秒要重建 44100 个采样点,2B 是 48000,解码器本身的显存与中间激活都随采样率上抬。
第三块是降噪器。from_pretrained(load_denoiser=True)是默认行为,会额外拉起 ZipEnhancer 的 pipeline。显存吃紧时这是第一个该关的开关(load_denoiser=False或 CLI 的--no-denoiser),代价只是失去提示音频降噪能力。
顺带一个被忽略的容量上限:max_length=4096个 token,按各自 token 率换算,0.5B 单次理论上限约4096/12.5 ≈ 327 秒,1.5 约4096/6.25 ≈ 655 秒。当然提示音频的 token 也要占额度,实际可用会更短。做有声书这类长文本时,1.5 的“同等显存下能装更长的音频”是实打实的收益,不只是音质升级。
五、什么场景其实不需要 2B
把上面三节合起来,可以给出一个不依赖“谁更强”的判断顺序。
第一类,下游链路本身就是窄带的,用 2B 纯属浪费。电话客服、IVR、语音回环(TTS 出来再喂 ASR)、车载提示音,终端采样率往往就是 16k。这种场景 0.5B 的 16k 输出正好对齐,还能省下 2.76 倍的存储与带宽:16k 单声道 16bit 约 32 KB/s,44.1k 约 88 KB/s,48k 约 96 KB/s。一小时的音频,前者约 115 MB,后者 317 MB 起。批量生产时这是直接的账单差异。
第二类,任务本身是“短句 + 高并发”,瓶颈在调度而不是模型能力。由于原生 KV cache 的 batch 维度是 1,2B 想要靠单卡扛并发,必须上推理引擎;而 0.5B/1.5 更容易用多实例把卡填满。社区教程里那句“先拿 1.5 跑一条 20 秒样音,满意了再切 2B”是对的:并发还没解决之前,更大的模型只会让排队更长。
第三类,只做中英且音色固定。2B 的核心增量是多语种、音色设计、48kHz。如果业务是中英双语、音色来自固定的几条参考音频,那 2B 的能力用不上。此时正确做法是 1.5 + LoRA:用 5~10 分钟音频把音色固化下来,比每次靠参考音频做零样本克隆更稳,也省掉长 prompt 占用的 token 额度。仓库已经支持全参与 LoRA 两条路(见 docs/finetune.md),LoRA 配置里enable_dit: true是必须的——文档明确写了它“required for voice cloning”。
反过来,哪些场景确实该上 2B?两类:一是多语种/方言覆盖(30 语言 + 方言,这是 0.x/1.5 完全没有的能力,硬做只能靠外部拼接);二是前面test-Hard暴露的短板——困难文本、强表现力长句、需要文字描述造声的角色配音。在这些地方,参数量的边际收益才真正兑现。
六、旧版本迁移:配置只改两行,成本在别处
如果只看训练配置,“迁移成本”几乎为零。把两个版本的 YAML 逐行 diff,差异只有两行:
# conf/voxcpm_v1/voxcpm_finetune_lora.yaml → conf/voxcpm_v1.5/voxcpm_finetune_lora.yaml -pretrained_path: /path/to/VoxCPM-0.5B/ -sample_rate: 16000 +pretrained_path: /path/to/VoxCPM1.5/ +sample_rate: 44100推理侧同理:VoxCPM.from_pretrained()的签名没变,generate()的参数没变,CLI 的--cfg-value、--inference-timesteps也没变,只有默认模型 ID 指向了openbmb/VoxCPM1.5(见 src/voxcpm/core.py 与 src/voxcpm/cli.py)。换模型真的就是换一个字符串。
真正的成本有四处,且都不在配置文件里:
其一,采样率变了,下游全链路要复核。44.1kHz 输出意味着:存储格式(是否还按 16k 归档)、播放器重采样策略、与字幕/口型对齐的时间戳、以及任何假设“采样率=16000”的硬编码。build_prompt_cache内部会把参考音频torchaudio.functional.resample到模型采样率,所以参考素材不必手动转码;但输出侧的假设没人帮你兜底。同样,训练数据也必须按 44.1kHz 准备,docs/finetune.md写得很清楚:0.5B 用 16kHz,1.5 用 44.1kHz。
其二,LoRA 权重不能跨代复用。LoRA 的r/alpha虽可沿用,但注入位置依赖隐藏维度和 patch 结构(target_modules_lm: ["q_proj","v_proj","k_proj","o_proj"]),而 1.5 的局部模块深度与 0.5B 不同,权重形状对不上。仓库的加载逻辑是“能加载的加载、其余跳过并打印数量”(Loaded N LoRA parameters, skipped M),所以它不会崩,只会静默地少加载一部分——这种“不报错的降级”比直接失败更难查。跨代迁移必须重训 LoRA,并核对 skipped 是否为 0。
其三,默认值会悄悄漂移。from_pretrained的默认hf_model_id已经是openbmb/VoxCPM1.5。如果你的代码习惯性省略这个参数,某次升级依赖之后,模型就换了代;而app.py里还留了HF_REPO_ID这个环境变量出口(默认openbmb/VoxCPM1.5)。生产环境应该显式固定模型 ID 与本地路径,而不是依赖默认值。
其四,流式路径的隐含限制。_generate_with_prompt_cache里有一句:
if retry_badcase and streaming: warnings.warn("Retry on bad cases is not supported in streaming mode, setting retry_badcase=False.")retry_badcase=True是 README 推荐开启的兜底(默认 3 次、阈值 6.0),但在流式模式下它会被静默关闭。也就是说,你从“非流式 + 重试”切到“流式”时,稳定性兜底同时消失了。做实时业务时,这一条要单独设计超时与重生成策略,不能指望框架层。
七、给一个可以直接执行的选型顺序
- 先定采样率,而不是先定参数量。下游是 16k 窄带链路 → 0.5B;需要高保真、且能接受 2.76 倍的存储与带宽 → 1.5;需要 48kHz 与多语种 → 2B。采样率定错,后面所有优化都是白做。
- 再定并发模型。单请求、离线批处理,裸
torch就够;高并发服务必须上推理引擎,此时才谈 2B 的 RTF 0.13 是否可信,否则它的真实成本是 0.30 量级。 - 最后定音色策略。靠参考音频做零样本克隆,就用原生
prompt_wav_path + prompt_text;音色要长期固定,就走 LoRA,并接受“换代会重训”的成本。 - 调参只调两个旋钮:
inference_timesteps(默认 10)决定质量与速度的斜率,cfg_value(默认 2.0)决定对提示的贴合度。长文本降 cfg、短句升 cfg,这是 docs/usage_guide.md 给出的经验规则,比盲目加大模型更有效。
一个最小可用的加载与流式片段,三代通用,只需换模型 ID:
import numpy as np, soundfile as sf from voxcpm import VoxCPM model = VoxCPM.from_pretrained( "openbmb/VoxCPM1.5", # 或 openbmb/VoxCPM-0.5B load_denoiser=False, # 显存吃紧时先关这个;但开启降噪会把提示限制到 16kHz ) print(model.tts_model.sample_rate, model.tts_model.audio_vae.hop_length) chunks = [] for chunk in model.generate_streaming( text="VoxCPM 的三代模型共用同一套推理接口,差别在采样率与 token 率。", cfg_value=2.0, inference_timesteps=10, ): chunks.append(chunk) sf.write("out.wav", np.concatenate(chunks), model.tts_model.sample_rate)选型的本质不是选出“最强的一代”,而是找到一个采样率、并发模型、音色策略三者同时成立的组合。VoxCPM 把这三代做成同一套接口,好处是迁移几乎零代码改动;坏处也正是这一点——那些不会报错、只会让音质或延迟悄悄退化的参数,才是真正需要盯住的地方。
【免费下载链接】VoxCPMVoxCPM: Tokenizer-Free TTS for Context-Aware Speech Generation and True-to-Life Voice Cloning项目地址: https://gitcode.com/OpenBMB/VoxCPM
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考