mlx-audio 中的 Confucius4-TTS:基于 MLX 的多语言零样本语音克隆全解

原创2026-09-15 09:13:58709 阅读
文章标签:语音音频人工智能本地部署模型推理服务

mlx-audio 中的 Confucius4-TTS:基于 MLX 的多语言零样本语音克隆全解

Confucius4-TTS 是网易有道开源的多语言、跨语言零样本语音克隆(zero-shot voice cloning)TTS 模型,mlx-audio 将其完整移植到 Apple 的 MLX 框架上,推理路径完全去除 torch/transformers 依赖。读完本文,你将掌握 Confucius4 在 mlx-audio 中的四阶段管线结构(w2v-bert 语义特征 + CAMPPlus 说话人嵌入 → T2S GPT-2 语义建模 → S2A 条件流匹配 → BigVGAN v2 声码器)、generate() 全部采样参数、fp32/int8 两种精度模型的用法,以及如何用仓库自带脚本把原始 PyTorch 权重量化转换为 MLX 格式。

一、整体架构:四阶段级联管线

Confucius4 的推理链路在 confucius4.py 的模块 docstring 中被明确概括为:

w2v-bert semantic features + CAMPPlus speaker embedding → T2S (GPT-2) → S2A conditional flow-matching (DiT + WaveNet) → BigVGAN v2 vocoder

对应到源码实现,四个组件分别在独立文件中:

阶段 作用 实现文件 权重文件
w2v-bert-2.0 Conformer 编码器 从 160 维 fbank 提取第 17 层隐状态作为语义条件 w2vbert.py w2vbert_mlx.safetensors
CAMPPlus 说话人编码器 从参考音频提取 192 维说话人向量 复用 xvector.py 的 CAMPPlus campplus.safetensors
T2S(GPT-2) 文本 + 语义条件自回归生成语义 token 序列 t2s.py t2s_model.safetensors
T2S 前缀编码器 文本投影 + ECAPA-TDNN 说话人编码器 prefix.py 同 t2s_model.safetensors
S2A 流匹配估计器 DiT(13 层、U-Net 跳跃连接)+ WaveNet,条件流匹配求解 mel s2a.py s2a_mlx.safetensors
BigVGAN v2 声码器 80 维 mel → 22050 Hz 波形,256× 上采样 vocoder.py bigvgan_mlx.safetensors

从源码结构看几个值得注意的设计细节:

  • w2v-bert 只保留前 17 层:w2vbert.py 中 NLAYERS = 17,hidden17() 方法只跑 feature_projection 加 17 个 Conformer 层(ffn1 → 带 relative-key 的自注意力 → 因果 depthwise conv_module → ffn2 → final LayerNorm),与 confucius4.py 中调用 self.w2v.hidden17(feats) 的接口一一对应。每层注意力的相对位置键实现为 mx.clip(r - l, -64, 8) + 64 的距离嵌入查表(w2vbert.py)。
  • T2S 是标准 GPT-2 结构:24 层、20 头、hidden 1280(t2s.py),词表用 BOS=8192 / EOS=8193 作为语义 token 流的首尾标记,语义 embedding 之上叠加独立学习的 semantic position embedding。
  • S2A 使用欧拉法求解流匹配 ODE:solve_euler() 中条件/无条件各拼一条 batch,按 d = (1+cfg)*cond_d - cfg*uncond_d 做 CFG(s2a.py),每步之后会把参考区间 T_ref 的 mel 重新置零,保证只有目标段被生成;mel<a href="https://link.gitcode.com/i/93955a1d39f20f15fbb9f34ad6f4ce1d" target="_blank">:, :, T_ref:] 的切片在 [confucius4.py 中完成,即参考前缀只作条件、不输出。
  • 长度由 T2S 决定:S2A 的 build_mu() 内部长度调节器按 out_len = T * 1.72 把语义 token 序列插值展开为 mel 帧序列(s2a.py),所以整段音频的时长在语义生成阶段就已确定。

二、快速上手:零样本语音克隆

使用入口是 mlx_audio.tts.utils.load_model。按模块 README 的标准用法:

from mlx_audio.tts.utils import load_model

model = load_model("mlx-community/Confucius4-TTS-mlx")
results = list(model.generate(
    text="Xin chào, đây là giọng nói được nhân bản.",
    ref_audio="reference.wav",   # 任意采样率;内部解码并重采样到 16 kHz
    lang="vi",
))

audio = results[0].audio              # mx.array
sample_rate = results[0].sample_rate  # 22050

生成结果是 GenerationResult 的生成器(generate 是 yield 单条结果),用仓库内置的音频 I/O 工具保存:

import numpy as np
from mlx_audio.audio_io import write

write("output.wav", np.array(results[0].audio), results[0].sample_rate)

GenerationResult 除了 audio/sample_rate,还携带 token_count(语义 token 数)、audio_duration、real_time_factor、processing_time_seconds 等字段,可直接用于推理性能统计(见 confucius4.py)。

generate() 参数详解

对照 confucius4.py 的函数签名,全部参数与默认值如下:

参数 默认值 说明
text 必填 待合成的文本
ref_audio 必填 参考音频路径,任意采样率,内部解码 + 重采样为 16 kHz 单声道
lang "vi" 目标语言代码,决定语言指令 token
temperature 0.8 采样温度,传入 T2S 的 _sample 中做 logits / temperature
top_k 30 先截断到 top-k 再做 top-p
top_p 0.8 核采样阈值
repetition_penalty 10.0 重复惩罚(注意默认值远大于 1,防止语义 token 循环)
seed 0 随机种子;同时作用于 numpy 采样 RNG 与 S2A 的初始噪声 z

采样逻辑在 t2s.py 的 _sample 中,顺序是:先对已生成过的 token 施加重复惩罚(正值除以惩罚、负值乘以惩罚),再除以温度,然后 top-k 截断、top-p 截尾、softmax 后用 np.random.default_rng(seed) 抽样。

语言支持

lang 接受六个代码:zh、en、vi、ja、ko、th(中、英、越南、日、韩、泰)。从源码看,语言代码的作用是查 confucius4.py 中的 LANGUAGE_TOKEN 表,得到一句中文语言指令(如 "请用越南语朗读接下来的文字"),与 "You are a helpful assistant. " 前缀和待合成文本拼接后经 tokenizer 编码,再进入 T2S 的文本投影。传入未知代码时回退为英文(LANGUAGE_TOKEN.get(lang, LANGUAGE_TOKEN<a href="https://link.gitcode.com/i/22253bbde8759e6cefdac32185425027" target="_blank">"en"]),[confucius4.py)。

三、可选模型与精度

模块 README 列出两个模型:

模型 精度 说明
mlx-community/Confucius4-TTS-mlx fp32 全精度权重
mlx-community/Confucius4-TTS-mlx-int8 int8 体积约缩小 60%,在 Apple Silicon 上更快

从源码结构看,int8 模型与 fp32 模型共用同一套加载/推理代码:ModelConfig 中的 quant_bits(默认 8)与 quant_group_size(默认 64)控制量化状态(confucius4.py)。T2S 的 _cw 与 w2v-bert 的 _lin 在检测到权重旁存在 .scales 伴生张量时自动走 mx.quantized_matmul,否则退回普通矩阵乘(t2s.py),因此精度差异对上层 generate() 完全透明。

四、权重转换:从 PyTorch 到 MLX

仓库自带转换脚本,一条命令完成:

python -m mlx_audio.tts.models.confucius4.convert --out ./confucius4-model

加 --quantize int8(或 int4)可在转换时量化 T2S 主干的 matmul。脚本实现在 convert.py,要点如下:

  1. torch 只在转换阶段出现。脚本 docstring 明确写着 "torch is used ONLY here (conversion), never at inference";推理路径只用 mlx + numpy + tokenizers(Rust 实现的 BPE 分词器)。
  2. 量化范围有讲究。--quantize 只量化 T2S 24 层中的 attn.c_attn / attn.c_proj / mlp.c_fc / mlp.c_proj 四类主干 matmul(GPT-2 的 Conv1D 权重会先转置成 <a href="https://link.gitcode.com/i/c29529775029ef11979a4015083d296a" target="_blank">out,in] 布局再按 group 64 量化),而 semantic_head、norm、embedding 保持 fp32——代码注释指出 8-bit 量化 semantic_head 会明显劣化 token 选择的保真度([convert.py)。w2v-bert 则量化每层的 ffn 与自注意力线性层,feature_projection 因输入维度 160 不是 group 64 的倍数而保持 fp32(convert.py)。
  3. 转换产物是一个完整模型目录,共 8 个条目(与 confucius4.py __init__ 的加载清单一一对应):
文件 来源 说明
t2s_model.safetensors netease-youdao/Confucius4-TTS 同名文件 T2S 主干 + 前缀编码器权重
s2a_mlx.safetensors s2a_model.pt 转换时折叠 weight_norm(_fold_weight_norm)
bigvgan_mlx.safetensors nvidia/bigvgan_v2_22khz_80band_256x 同样折叠 weight_norm
w2vbert_mlx.safetensors facebook/w2v-bert-2.0 只取 feature_projection + 0~16 层
w2v_stats.npz wav2vec2bert_stats.pt 第 17 层隐状态的 mean/std,用于归一化条件向量
campplus.safetensors funasr/campplus 的 campplus_cn_common.bin 经 mlx-audio 自带 CAMPPlus 清洗后保存
fbank_filters.npz 转换期预计算 kaldi-mel 80 维滤波矩阵 + povey 窗
checkpoints/tokenizer.json netease-youdao/Confucius4-TTS 运行时用 Tokenizer.from_file 无 torch 加载

另外还会写出一份 config.json(model_type: confucius4、sample_rate: 22050、quant_bits、quant_group_size),供 load_model 构建 ModelConfig。

五、推理路径实现细节(torch-free)

理解"参考音频到底怎么被用掉",是理解这个模型的关键。generate() 的前半段(confucius4.py)一次参考音频输入产出三种条件:

  1. 16 kHz 单声道波形:load_audio(ref_audio, sample_rate=16000) 解码并重采样。源码注释解释了为什么必须重采样——下游 fbank、CAMPPlus、参考 mel 全部假设 16 kHz,若不重采样,44.1/48 kHz 参考音频会被当作 16 kHz 误读,mel 频率轴偏移 sr/16000 倍,输出乱音。
  2. 语义条件向量:160 维 SeamlessM4T 风格 fbank(features.py,povey 窗、逐帧去直流 + 0.97 预加重、80 kaldi-mel、逐 mel 箱 CMVN、stride-2 叠帧)→ w2v-bert 第 17 层隐状态 → 用 w2v_stats.npz 的 mean/std 归一化 → 经 prefix.cond_emb 的 ECAPA-TDNN 压缩成 (1,1,1280) 条件嵌入。
  3. 风格与音色条件:CAMPPlus 192 维说话人向量(style)用于 S2A 的流匹配条件;_ref_mel()(confucius4.py)用 numpy 在 22050 Hz 上重算 80 维 slaney-norm mel(n_fft=1024, hop=256,滑窗用 numpy 纯实现),作为 S2A 的前缀参考 mel。

随后文本经 tokenizer 编码、prefix.text_emb 投影(4096 维 embedding → SiLU → 1280 维,叠加文本位置嵌入),T2S 以 KV cache 做 prefill + 自回归采样(max_new 默认 512),得到语义 codes 与 LM 隐状态 latent;S2A build_mu 构造 (T_ref + target) 长度的条件序列;26 步欧拉积分(mx.linspace(0, 1, 26)、cfg=0.7,confucius4.py)从噪声 z 积分到 mel;最后 BigVGAN v2(上采样率 [4,4,2,2,2,2]、anti-aliased snakeβ 激活)还原 22050 Hz 波形并裁剪到 ±1。

六、使用前提与限制

  • 运行环境:MLX 框架,即 Apple Silicon(macOS)。模型输出采样率为 22050 Hz(ModelConfig.sample_rate = 22050)。
  • 模型来源:fp32 与 int8 模型分别对应 mlx-community/Confucius4-TTS-mlx 与 mlx-community/Confucius4-TTS-mlx-int8;若需自定义转换,运行上文 convert 命令即可在本地生成等价目录。
  • 参考音频:任意采样率均可,但内部统一按 16 kHz 处理特征;参考片段长度决定 T_ref,过长时 S2A 的 RoPE 频率表会自动线性外推延长(s2a.py 的 _extend_freqs),无需担心超过 4096 帧的限制。
  • 依赖边界:推理路径不含 torch/transformers;torch 仅在 convert.py 中用于一次性权重转换。

小结

mlx-audio 的 Confucius4 移植把"多语言零样本语音克隆"完整搬进了纯 MLX 生态:load_model 一行加载,generate(text, ref_audio, lang) 三参即得 22050 Hz 波形;采样参数(temperature/top_k/top_p/repetition_penalty/seed)全部暴露且默认值经过调校;convert.py 支持 fp32/int8/int4 三档产物,量化只作用于主干 matmul 以保护语义 token 选择质量。相关实现集中在 mlx_audio/tts/models/confucius4/ 目录,配合模块内 README 可作为后续深入源码的入口。

登录后查看全文
mlx-audio