Magpie-TTS Multilingual
Apple Silicon 上的 NVIDIA Magpie-TTS Multilingual 357M Swift 实现 — 一个基于 NeMo 22.05 kHz Nano-Codec 的自回归多码本 TTS 模型。9 种语言(英语、西班牙语、德语、法语、意大利语、越南语、印地语、汉语普通话、日语),5 个预设说话人。量化为 INT4(约 247 MB)或 INT8(约 411 MB)。支持流式合成,首包延迟约 120 ms。
当你需要一个小型 bundle 用同一种声音流利地说 9 种语言时,Magpie 是首选。5 个预设说话人在所有语言间保持身份一致 — 适合多语言助手、教育应用,或需要语言切换的有声书叙述。需要零样本声音克隆请使用 CosyVoice3、Qwen3-TTS Base 或 VoxCPM2。
架构
Magpie 是 4-bundle MLX 流水线:文本编码器 → 带交叉注意力的解码器 → LocalTransformer 码本头 → 因果 HiFi-GAN 音频编解码器。bundle 在 prefill 与 step 入口之间共享解码器权重,与上游 FluidInference 的 CoreML 布局保持兼容。
| 阶段 | 模块 | 说明 |
|---|---|---|
| 1. 分词 | MagpieTokenizer | 逐语言 G2P(IPA 词典 / byT5 字节 / 拼音 / 片假名),共享 2360-token 词表 + 逐 tokenizer 偏移,始终追加 EOS |
| 2. 文本编码器 | MagpieTextEncoder | 6 层因果 Transformer,d=768,k=3 卷积 FFN |
| 3. Decoder prefill | MagpieDecoder | 12 层因果 + 交叉注意力。将 110-frame 预设说话人上下文 + BOS 写入 KV cache。 |
| 4. LocalTransformer | MagpieLocalTransformer | 1 层码本 AR 头,d=256。基于解码器 hidden 顺序采样每帧 8 个码本。 |
| 5. Decoder step | MagpieDecoder | 每帧一次 AR 推进,直到 EOS 或 500-frame 上限(约 23 秒)。 |
| 6. NanoCodec | MagpieNanoCodec | FSQ 逆变换 → 因果 HiFi-GAN → 22.05 kHz 单声道波形。 |
语言与 G2P
SDK 的 testMultilingualRoundTrip 验证全部 9 种语言均能通过 Qwen3-ASR 进行往返。每种语言都有专属的 G2P 流水线:
| 语言 | 代码 | G2P 流水线 |
|---|---|---|
| 英语 | en | CMU IPA 词典(12.5 万条,已内置) |
| 西班牙语 | es | 西班牙语 IPA 词典(已内置) |
| 德语 | de | 德语 IPA 词典(已内置) |
| 法语 | fr | byT5 UTF-8 字节编码器 |
| 意大利语 | it | byT5 UTF-8 字节编码器 |
| 越南语 | vi | byT5 UTF-8 字节编码器 |
| 印地语 | hi | 梵文码点查表 + last-wins 子词表 |
| 汉语普通话 | zh | NLTokenizer(.simplifiedChinese) 分词 + Apple .mandarinToLatin + 内置拼音→IPA 词典 + #N 声调标记 |
| 日语 | ja | CFStringTokenizer 汉字读音 + NFC 保留浊音 + 平板调标记 + 助词/问候语覆盖 |
共享 2360-token 词表把各语言的子 tokenizer 按偏移量拼接(记录在 MagpieSubVocab)。文本嵌入在词表之后多出两行用作 BOS / EOS;eos_id = 2361 会被追加到每个输入序列末尾。
预设说话人
权重内嵌 5 个说话人上下文(每个 110 帧 × 768 维),作为每次 AR 解码的前缀。说话人身份在 9 种语言间保持一致。
| 序号 | CLI 名 | 身份 |
|---|---|---|
| 0 | sofia | Sofia(默认) |
| 1 | aria | Aria |
| 2 | jason | Jason |
| 3 | leo | Leo |
| 4 | john | John Van Stan |
模型版本
| 版本 | 磁盘 | 内存(加载+解码) | HuggingFace |
|---|---|---|---|
| INT4(默认) | 约 247 MB | 约 1.3 GB | aufklarer/Magpie-TTS-Multilingual-357M-MLX-4bit |
| INT8 | 约 411 MB | 约 1.6 GB | aufklarer/Magpie-TTS-Multilingual-357M-MLX-8bit |
两个 bundle 都使用 MLX 平坦仿射量化(mlx_affine_flat,group size 64),加载时反量化为 FP32 — 运行时激活为全精度。本模型 INT4 与 INT8 在听感上无差别;除非有存储空间余量,否则建议 INT4。
CLI 用法
# 英语,贪心解码
speech speak "Hello, world." --engine magpie --magpie-speaker aria \
--magpie-temperature 0 -o out.wav
# 西班牙语(9 种语言任一,用 --language 选择)
speech speak "Hola, mundo." --engine magpie --language es \
--magpie-speaker aria -o out.wav
# 日语 — 需要随机采样(贪心会卡在第一段)
speech speak "こんにちは世界、これは音声合成システムです。" \
--engine magpie --language ja --magpie-temperature 0.6 \
--magpie-top-k 80 --seed 42 -o out.wav
# 流式合成 + 播放
speech speak "Streaming test" --engine magpie --stream --play
# 列出 5 个预设说话人
speech speak --engine magpie --list-speakers
# 预音素化 IPA 跳过逐语言 G2P
speech speak "həˈloʊ" --engine magpie --magpie-prephonemized -o out.wav
选项
| 选项 | 默认 | 说明 |
|---|---|---|
--magpie-variant | int4 | 量化版本:int4 或 int8 |
--magpie-speaker | sofia | 预设说话人:sofia、aria、jason、leo、john |
--magpie-temperature | 0.6 | 采样温度(0 = 贪心) |
--magpie-top-k | 80 | Top-k 采样过滤 |
--magpie-max-frames | 500 | 码本帧数硬上限(约 23 秒) |
--magpie-min-frames | 4 | 允许 EOS 之前的最少帧数 |
--magpie-prephonemized | 关 | 把输入视作 IPA / 音素流,跳过逐语言 G2P |
--language | english | 选择逐语言 tokenizer |
--stream | 关 | 输出 AsyncStream<AudioChunk> 而不是单个 WAV |
--seed | — | 可复现 Gumbel 采样 |
日语输入超过一个词时需要随机解码(--magpie-temperature 0.6 --magpie-top-k 80 --seed 42 复刻 NeMo 参考测试)。贪心会卡在第一段,因为平板调启发式与逐词真值有偏差。
不支持声音克隆
Magpie 模型本身没有零样本说话人条件;bundle 只内置 5 个预设身份。CLI 会拒绝通用的 --voice-sample、--speaker、--instruct 标志并给出可执行的错误信息,提示改用 --magpie-speaker 或换用支持克隆的引擎(Qwen3-TTS Base、CosyVoice3、VoxCPM2)。
性能(M4 Pro)
| 设置 | 音频 | 耗时 | RTF |
|---|---|---|---|
| 批处理 INT4 贪心,短提示 | 2.8 s | 0.88 s | 0.32 |
| 批处理 INT4 贪心,整句 | 5.8 s | 1.35 s | 0.23 |
| 批处理 INT4 采样,23 s 输出 | 23 s | 5.6 s | 0.24 |
| 流式 INT4 采样 | 23 s | 21.6 s | 0.93 |
流式模式下加载模型后首包延迟约 120 ms。流式 RTF 较高是因为每次 chunk 输出时编解码器会对完整 code buffer 重跑(后续可加状态缓存优化)。
Swift API
import MagpieTTS
let model = try await MagpieTTS.fromPretrained(variant: .int4)
// 批处理合成(en/es/de/fr/it/vi/hi/zh — 贪心可用)
let audio = try model.synthesize(
text: "Hello, world.",
speaker: .aria,
language: .english,
params: MagpieTTSParams(temperature: 0, topK: 1, maxSteps: 500))
// 日语 — 用随机采样
let audioJA = try model.synthesize(
text: "こんにちは世界、これは音声合成システムです。",
speaker: .aria,
language: .japanese,
params: MagpieTTSParams(temperature: 0.6, topK: 80,
maxSteps: 300, seed: 42))
// 流式(AsyncStream<AudioChunk>)
let stream = model.synthesizeStream(
text: "Streaming text",
speaker: .aria,
language: .english,
firstChunkFrames: 8,
framesPerChunk: 25)
for try await chunk in stream {
// chunk.samples 是 22.05 kHz 单声道 Float32
}
CoreML 后端(--engine magpie-coreml)
除了 MLX 套件,Magpie 还提供 CoreML 套件(aufklarer/Magpie-TTS-Multilingual-357M-CoreML-8bit,约 342 MB INT8)。四个 .mlmodelc 包 — text_encoder、decoder_prefill、decoder_step、nanocodec_decoder — 在 ANE / GPU 上运行;Swift 端的 FSQ 逆映射将采样的代码转换为编解码器使用的 32 维潜变量。
# 8 种语言(无日语),5 位预设说话人
speech speak "Hello world." --engine magpie-coreml --magpie-speaker aria -o hi.wav
speech speak "Hola mundo." --engine magpie-coreml --language es --magpie-speaker leo -o es.wav
# --language ja 自动路由到 MLX 后端(stderr 提示)
speech speak "こんにちは" --engine magpie-coreml --language ja -o ja.wav
相对于 --engine magpie 的注意事项:
- 当前为混合管道。1 层 LocalTransformer(NeMo 训练的真正代码本采样头)和 8 张音频嵌入表未随 CoreML 套件一起发布。在首次合成时,CoreML 引擎会延迟加载 MLX INT4 套件来驱动这两部分。ASR 双向对比与 MLX 后端按位一致;区别在于此引擎也会下载 MLX 套件。仅 ANE 的 iOS 部署纯 CoreML 路径需要套件提供
local_transformer/*.npy+audio_embedding_*.npy以及 Swift Accelerate LT(后续跟进)。 - 不支持流式。
nanocodec_decoder.mlmodelc在固定 64 帧窗口下被追踪。更长的序列在内部分块,但如果在分块边界发出,首包延迟将为 ~3 秒。--stream会被拒绝并返回可操作的错误。 - 无日语分词器。CoreML 套件尚未提供 JA 分词器 JSON。使用此引擎时,
--language ja会自动回退到 MLX 后端。
说话人排序与 CoreML 套件的 speaker_info.json 一致(0=John, 1=Sofia, 2=Aria, 3=Jason, 4=Leo — 与 MLX 不同);说话人枚举在内部进行映射,因此 CLI 名称在两个引擎中均可使用。
实现要点
移植 NeMo 风格多语言 TTS 时三个值得注意的坑:
- FSQ 整除 — MLX-swift 的
/是真除(mlx_divide);NeMo 的 FSQ 反变换用 Python//。要用MLX.floorDivide(...),否则每个 FSQ slot 会解码到小数偏移,编解码器输出会糊。 - 子词表偏移 — NeMo 的
AggregatedTTSTokenizer按偏移拼接各语言词表。简单的全局 first-occurrence 映射总是落到英语区域,会让其他语言输出乱码。 - 印地语 last-wins 去重 —
HindiCharsTokenizer在自身词表内有重复梵文条目(CHARSET 与 PUNCT_LIST 重叠)。Python 的{l: i for i, l in enumerate(tokens)}字典推导保留最后一个赋值;要保持一致,不要用 first-occurrence。
三个修复都内联记录在 Swift 模块中。
来源
- 上游权重:nvidia/magpie_tts_multilingual_357m(NVIDIA Open Model License)
- 编解码器:nvidia/nemo-nano-codec-22khz-1.89kbps-21.5fps
- 论文:NanoCodec: Towards High-Quality Ultra Fast Speech LLM Inference(2025)
- 参考 CoreML 移植:FluidInference/mobius
- Swift 模块:MagpieTTS(MLX)+ MagpieTTSCoreML(CoreML)
- CoreML 套件:aufklarer/Magpie-TTS-Multilingual-357M-CoreML-8bit
许可证
- 模型权重:NVIDIA Open Model License(允许商用;详见 HuggingFace 页面)
- Swift 移植 + 内置 IPA / 拼音词典:沿用上游 NeMo 的许可(词典 Apache 2.0,模型 NVIDIA OML)