mlx-audio 中 OmniVoice 零样本多语言 TTS 与音色克隆实战指南
【免费下载链接】mlx-audioA text-to-speech (TTS), speech-to-text (STT) and speech-to-speech (STS) library built on Apple's MLX framework, providing efficient speech analysis on Apple Silicon.项目地址: https://gitcode.com/GitHub_Trending/ml/mlx-audio
本篇指南围绕 OmniVoice 模型文档 展开,系统讲解该模型在 mlx-audio 仓库中的架构原理、语音合成与音色克隆(voice cloning)工作流、批量生成与行内发音控制等实战要点。读完本文,你将掌握如何在 Apple Silicon 上加载 OmniVoice 权重,用任意 STT 模型为参考音频生成转录文本,并通过generate()/generate_batch()高质量合成 646+ 种语言的语音。
模型概览:双向 Qwen 骨干 + HiggsAudioV2 声学分词器
OmniVoice 是一个零样本(zero-shot)多语言 TTS 模型,支持音色克隆,其实现建立在双向 Qwen 骨干网络与 HiggsAudioV2 声学分词器之上。从模型目录 README 与模型主实现可以确认其核心特征:
- 646+ 语言:通过语言标签(BCP-47,如
"en"、"zh"、"ru")触发多语言合成,其中en、zh、ru、ja、de、fr、es、ko、ar、pt、it、pl、nl、tr质量最佳; - 零样本音色克隆:只需一段干净参考语音(建议 5–15 秒)及其转录文本,即可克隆说话人音色;
- 批量生成:通过
generate_batch()一次生成多条话语; - 非语言标签:支持
[laughter]、[sigh]等表情化控制标签; - 发音覆盖:英文支持 CMU 词典音标覆盖,中文支持带声调拼音覆盖。
架构与生成原理
从模型配置文件可以看到关键设计参数:
| 配置项 | 默认值 | 说明 |
|---|---|---|
audio_vocab_size | 1025 | 1024 个真实声学 token + 1 个 MASK token(id 为 1024) |
num_audio_codebook | 8 | 8 个并行声学码本 |
audio_codebook_weights | [8,8,6,6,4,4,2,2] | 各码本的层惩罚权重 |
sample_rate | 24000 | 输出采样率(HiggsAudioV2,25 tokens/秒) |
在骨干网络实现中,Attention.__call__调用mx.fast.scaled_dot_product_attention(q, k, v, scale=self.scale, mask=None),显式关闭了因果掩码,形成全双向注意力——这是 OmniVoice 采用迭代式掩码扩散(iterative masked diffusion)进行非自回归解码的关键:文本 token、参考 token 与初始全 MASK 的声学 token 序列一起送入双向骨干,通过多轮"部分揭晓"逐步填满声学 token。
解码过程由迭代去掩码实现完成,要点如下:
- 时间步调度:
_get_time_steps采用 cosine-shifted 调度(默认t_shift=0.1),每步按total_mask * dt决定揭晓 token 数量,最后一步全部揭晓; - CFG 引导:
guidance_scale默认2.0,通过无条件分支uncond_input_ids与条件分支的 log-prob 差值放大置信度(c_lp + guidance_scale * (c_lp - u_lp)),0时关闭; - 位置采样:
position_temperature(默认5.0)向位置置信度注入 Gumbel 噪声,class_temperature(默认0.0,即贪婪)控制 token 类别的采样随机性,配合layer_penalty_factor(默认5.0)对浅层码本施加置信度惩罚; - 权重加载:
sanitize()负责把 k2-fsa/OmniVoice 的 PyTorch 权重键重映射为 mlx-audio 命名(llm.*→backbone.*,8 个合并 embedding/head 拆分为 8 个独立表)。
环境准备与模型加载
推荐使用mlx-community/OmniVoice-bf16仓库的 MLX 权重(模型文档与克隆演示脚本均以此为准;模型目录 README 中也记录了mlx-community/OmniVoice-bfloat16的写法,两种写法指向同一权重)。最便捷的加载方式是通过 TTS 统一入口:
from mlx_audio.tts.utils import load_model as load_tts tts = load_tts("mlx-community/OmniVoice-bf16") tokenizer = tts.audio_tokenizer # HiggsAudioTokenizer,克隆时必需加载过程中,post_load_hook会自动从模型目录加载两类分词器并挂到模型上:AutoTokenizer(文本分词器,存于text_tokenizer)与HiggsAudioTokenizer(声学分词器,存于audio_tokenizer)。若自动加载失败会打印警告并置为None,此时调用generate()需要手动传入tokenizer/text_tokenizer。模型注册映射位于 TTS 加载工具("omnivoice": "omnivoice"),因此也支持通过python -m mlx_audio.tts.generate --model mlx-community/OmniVoice-bf16 --text "..."的命令行方式直接合成。
最低合成示例(不克隆音色):
from mlx_audio.audio_io import write as audio_write import numpy as np results = list(tts.generate( text="Hello, this is OmniVoice running on Apple Silicon.", language="english", )) audio_write("output.wav", np.array(results[0].audio), results[0].sample_rate)零样本音色克隆
克隆所需的两类输入
OmniVoice 克隆需要两个输入:
ref_audio:一段干净的参考语音片段(推荐 5–15 秒);ref_text:参考片段的转录文本。
为什么ref_text至关重要
如果没有ref_text,模型无法将参考音色与目标文本正确对齐,生成的音频开头会出现乱码或语言错误等伪影。原始 Python 版 OmniVoice 通过内置 Whisper 模型自动转写参考音频来解决这个问题;MLX 移植版刻意不捆绑任何 ASR 模型以避免强耦合,因此你可以在调用generate()之前,用 mlx-audio 中任意 STT 模型(Whisper、Qwen3-ASR、SenseVoice 等)自行获取转录。
用 Qwen3 ASR 获取ref_text的完整工作流
关键前提:转录文本必须与预处理后的参考音频(已去除静音)匹配,而不是原始录音。如果直接转写原始文件,ASR 可能返回比预处理后片段更多的文本,多余内容会泄漏进生成语音。正确流程与原始 k2-fsa/OmniVoice 一致:先预处理,再转写。
from mlx_audio.stt.utils import load_model as load_stt from mlx_audio.tts.utils import load_model as load_tts from mlx_audio.tts.models.omnivoice.utils import create_voice_clone_prompt from mlx_audio.audio_io import write as audio_write import mlx.core as mx import numpy as np import tempfile, os tts = load_tts("mlx-community/OmniVoice-bf16") tokenizer = tts.audio_tokenizer # Step 1: 预处理参考音频(静音移除、RMS 归一化),编码为声学 token ref_tokens = create_voice_clone_prompt("reference.wav", tokenizer=tokenizer) mx.eval(ref_tokens) # Step 2: 把预处理后的 token 解码回音频,再交给 STT 转写 preprocessed = np.array(tokenizer.decode(ref_tokens).astype(mx.float32)) tmp = tempfile.NamedTemporaryFile(suffix=".wav", delete=False) audio_write(tmp.name, preprocessed, 24000) tmp.close() stt = load_stt("mlx-community/Qwen3-ASR-0.6B-8bit") ref_text = stt.generate(tmp.name).text os.unlink(tmp.name) # Step 3: 用同一来源的 ref_tokens + ref_text 生成 results = list(tts.generate( text="Hello from OmniVoice.", language="english", ref_tokens=ref_tokens, ref_text=ref_text, )) audio_write("output.wav", np.array(results[0].audio), results[0].sample_rate)generate()内部会把ref_text与目标文本拼接(见omnivoice.py中的_combine_text:去除换行、压缩空白、并移除中日韩字符周围的空格),随后以<|denoise|>、<|lang_start|>、<|instruct_start|>等风格 token 组装输入序列,再把参考 token 段拼在文本段之后、目标 MASK 段之前。当你直接传ref_audio路径而非ref_tokens时,generate()会内部调用create_voice_clone_prompt完成编码(参考音频会被裁剪到ref_audio_max_duration_s=10.0秒以内)。
参考音频预处理细节
MLX 移植版与原始 Python 管线保持一致的参考音频预处理流程,全部实现在 create_voice_clone_prompt 中:
- torchaudio 兼容的 Hann 窗 sinc 重采样:使用
mlx_audio.codec.models.higgs_audio.higgs_audio._sinc_resample将任意采样率重采样到 24 kHz,保证与上游精度一致(而非 scipy 实现); - RMS 归一化:当整段音频 RMS 小于
0.1时,按0.1 / rms增益放大,提升安静参考音频的音量; - 固定阈值静音移除:内置
_remove_silence(pydub 兼容的无依赖移植),移除大于 300 ms 的中间静音与大于 100 ms 的边缘静音; - 长音频在静音处裁剪:当音频超过 20 秒且未提供
ref_text时,_trim_long_audio会在最大静音间隙处截断到约 15 秒内;若已提供ref_text则跳过自动裁剪(仅告警),以保证转录文本与音频内容一致。
最佳效果来自静音裁剪后 5–15 秒的干净语音。
一键 CLI 演示脚本
仓库提供了完整的端到端脚本 examples/omnivoice_clone_demo.py,封装了上述"预处理 → 解码 → 转写 → 生成"全流程:
python examples/omnivoice_clone_demo.py \ --ref_audio reference.wav \ --text "Hello world." \ --language english \ --output clone_output.wav脚本支持的参数包括--ref_text(手动提供转录,跳过自动转写)、--stt_model(默认mlx-community/Qwen3-ASR-0.6B-8bit,可换成 Whisper、SenseVoice 等任意 mlx-audio STT 模型)、--tts_model(默认mlx-community/OmniVoice-bf16)、--num_steps(去掩码步数)与--guidance_scale(CFG 强度)。任何 STT 模型均可用于自动转写,核心原则不变:转写的是预处理后的音频,而不是原始文件。
批量生成
generate_batch()支持一次调用生成多条话语,且逐条指定语言、参考音频、参考文本与时长:
from mlx_audio.audio_io import write as audio_write import numpy as np model = load_tts("mlx-community/OmniVoice-bf16") results = model.generate_batch( text=["Hello world.", "Bonjour le monde."], language=["english", "french"], num_steps=32, ) for i, r in enumerate(results): audio_write(f"output_{i}.wav", np.array(r.audio), r.sample_rate)从批量生成实现可以看到其内部机制:
- 标量参数(如
language、duration_s)会自动广播到每条样本(_ensure_list),也支持传入与text等长的列表; - 默认
max_batch_size=8,超过该数量时会自动按max_batch_size分块,逐块调用iterative_unmask_batch迭代去掩码,再合并返回GenerationResult列表; - 每个
GenerationResult都携带audio、sample_rate、token_count、audio_duration、real_time_factor、processing_time_seconds与peak_memory_usage等元信息,便于后续统计与调优。
行内发音控制
英文:CMU 词典
在文本中直接嵌入 CMU 音素(带重音数字)即可覆盖默认发音,多个词可同时使用:
results = list(model.generate( text="He plays the [B EY1 S] guitar while catching a [B AE1 S] fish.", language="english", ))中文:带声调拼音
中文场景下使用带声调数字的拼音覆盖发音:
results = list(model.generate( text="今天天气很好,我想去打ZHE2出售后的商店买东西。", language="chinese", ))非语言标签
支持的非语言标签(定义于模型实现的正则_NONVERBAL_PATTERN)包括:[laughter]、[sigh]、[confirmation-en]、[question-en]、[question-ah]、[question-oh]、[question-ei]、[question-yi]、[surprise-ah]、[surprise-oh]、[surprise-wa]、[surprise-yo]、[dissatisfaction-hnn]。
results = list(model.generate( text="I just heard the funniest joke [laughter] that was incredible.", language="english", ))在 tokenize 阶段,_tokenize_with_nonverbal_tags会把这类标签当作原子 token 切分并原样送入分词器,避免被普通分词拆散。
生成参数详解
综合模型文档、模型目录 README 与源码签名,generate()/generate_batch()的核心参数如下:
| 参数 | 默认值 | 说明 |
|---|---|---|
text | — | 待合成文本 |
language | "None" | BCP-47 语言标签:"en"、"zh"、"ru"、"ja"等;"None"表示自动检测,也可通过lang_code传入 |
instruct | "None" | 风格指令标签(模型特定) |
duration_s | 5.0 | 最大输出时长(秒);不传时由内置规则时长估计器推算 |
ref_audio | None | 用于音色克隆的参考 WAV 路径(任意采样率)或波形数组 |
ref_text | None | 参考音频的转录文本,强烈建议提供 |
ref_audio_max_duration_s | 10.0 | 通过ref_audio克隆时参考音频的裁剪上限 |
num_steps | 32 | 迭代去掩码步数(4–64;越少越快、质量越低,16 步即可获得合理的速度/质量平衡) |
guidance_scale | 2.0 | CFG 引导强度(0关闭) |
class_temperature | 0.0 | token 类别采样温度(0为贪婪) |
position_temperature | 5.0 | 位置分数上的 Gumbel 噪声 |
layer_penalty_factor | 5.0 | 浅层码本的置信度惩罚系数 |
t_shift | 0.1 | 时间步余弦偏移调度参数 |
关于时长估计:当未指定duration_s时,规则时长估计器(RuleDurationEstimator)会以 "Nice to meet you." 约 25 个 token 为参考,按字符音素权重表(CJK 每个字符权重 3.0、拉丁字母 1.0、标点 0.5、空格 0.2、数字 3.5 等,覆盖 600+ 语系)推算目标 token 数,再乘以1.15安全系数并保证至少 10 个 token;generate_batch中则按sample_rate / 960(即 25 tokens/秒)将duration_s换算为目标 token 数。
注意事项与工程细节
ref_text强烈建议提供:没有它,音色克隆的输出质量会显著下降;- 编码与解码路径分离:MLX 的 HiggsAudio 编码路径与 Python/CUDA 参考实现达到完整 token 级对齐;克隆所需的编码走 PyTorch CPU(约 0.5 s 处理 1 s 音频),解码走 MLX GPU,因此克隆流程需要
torchaudio与transformers(pip install torchaudio transformers); - 编码精度:编码管线使用 torchaudio 兼容的 sinc 重采样器(而非 scipy),以精确匹配上游精度;
- 双向骨干限制:由于
OmniVoiceBackbone不支持填充掩码(attention_mask必须为None),同一批次内不同长度的样本通过 MASK token 补齐(见_pack_batch),补齐位置仍参与迭代解码; - 自定义加载方式:也可以完全手动加载权重(
OmniVoiceConfig.from_dict+Model(config)+sanitize+load_weights)并显式传入两个分词器,详见 模型目录 README 的 Python API 示例。
综上,mlx-audio 中的 OmniVoice 移植版在保留原始模型 646+ 语言零样本合成与克隆能力的同时,通过"先预处理再转写"的 STT+TTS 组合工作流解耦了 ASR 依赖,配合批量生成、发音覆盖与丰富的解码参数,为 Apple Silicon 上的多语言语音合成提供了完整、可复现的工程化方案。
【免费下载链接】mlx-audioA text-to-speech (TTS), speech-to-text (STT) and speech-to-speech (STS) library built on Apple's MLX framework, providing efficient speech analysis on Apple Silicon.项目地址: https://gitcode.com/GitHub_Trending/ml/mlx-audio
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考