QwenLM/Qwen3-TTS ncnn 移植 #6797
crafcat7
started this conversation in
Show and tell
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
作者: Lune
日期: 2026 年 7 月 3 日
版本: v0.1
适配范围: Qwen3-TTS-12Hz-0.6B-Base + Qwen3-TTS-Tokenizer-12Hz
作品链接:https://github.com/crafcat7/Qwen3-TTS-ncnn
摘要
本报告记录了将 Qwen/Qwen3-TTS 的 12Hz 0.6B Base voice clone 推理链路适配到 ncnn 的实现过程。当前完成的是一个可复现的第一阶段闭环:使用 PNNX 将 PyTorch 模型拆分导出为多组 ncnn param/bin,使用 C++ runtime 负责 tokenizer、参考音频前处理、profile 选择、Mimi split-RVQ 量化、greedy 生成调度和 wav 输出,并在相同输入、fp32、greedy、确定性配置下验证 ncnn 最终音频与 PyTorch 原版一致。
当前稳定验证用例为 96000-sample reference audio、
max_new_tokens=59、top_k=1、top_p=1、temperature=1。该用例中 ncnn 自动选择speaker_encoder_375bucket,生成 code token 与 PyTorch 完全一致,最终 wav 可听且 waveform diff 通过阈值检查:这次适配的方案是按静态窗口拆成可验证子图,把 Python 控制流、prompt 拼装、RVQ 最近邻搜索、profile/bucket 选择和音频 IO 下沉到 C++ runtime。这样既保留 ncnn 作为主要张量计算后端,也能在 PNNX 尚不完全覆盖动态控制流和复杂容器语义时得到可验证的端到端结果。
1. 项目背景
1.1 模型与任务
本次实现目标模型是:
Qwen3-TTS-12Hz-0.6B-BaseQwen3-TTS-Tokenizer-12Hz目标任务是 deterministic voice clone:
1.2 设计目标
本次设计目标如下:
2. 总体架构
2.1 子图拆分策略
Qwen3-TTS 的 PyTorch 链路包含大量 Python 控制流、容器结构、动态 mask、RoPE/mRoPE 处理、codec 量化与自回归调度。为降低 PNNX trace 难度并让每个子模块可独立验证,本工程采用 split graph:
当前模型包入口为
model.json。它记录 profile、capabilities、各 ncnn 子图路径、tokenizer 文件、embedding/RVQ sidecar、窗口大小、采样率和 profile coverage。2.2 运行时调用流
3. 为什么不是单个完整 ncnn graph
3.1 PyTorch 全图 trace 的问题
直接导出完整 Qwen3-TTS 推理链路会遇到几个结构性问题:
cdist/argmin类语义。虽然后续 PNNX/ncnn 已补充相关能力方向,当前 release runtime 仍保留经过验证的 C++ RVQ。因此当前工程选择“PNNX + ncnn 子图 + C++ runtime”的工程化拆分。这个方案降低了每个 graph 的输入输出复杂度,也让 parity 可以逐层、逐子链路、最终音频逐级定位。
3.2 当前保留的 C++ native 部分
4. 关键难点与解决方案
4.1 难点 1:静态窗口与任意输入之间的边界
Qwen3-TTS 的自然输入长度是动态的,但 ncnn graph 更适合固定 shape。最初如果只导出单一窗口,短输入、长 reference、长输出都会混在一起失败,错误也不容易定位。
解决方案是引入 profile:
runtime 根据 reference sample count、speaker mel frame count 和生成预算进行校验或选择。超出 profile coverage 的输入会被明确拒绝,而不是静默生成错误音频。
4.2 难点 2:Mimi tokenizer encoder 的完整 graph 与 RVQ
tokenizer encoder 的 latent 前半段可以稳定导出为 ncnn graph,但完整 encoder 包含 RVQ 最近邻量化和 codebook 选择。为了先完成 raw E2E parity,本工程把路径拆成:
这样做的好处是:
ref_code,当前 stable 用例为800 / 800完全一致。后续如果 full tokenizer encoder graph 重新导出并通过 native-op scan 与 raw E2E parity,可以再把 C++ RVQ 替换掉。
4.3 难点 3:talker 与 code predictor 的生成调度
Qwen3-TTS 并不是只生成单 token。每一帧需要主 codebook token,并由 code predictor 生成剩余 15 个 codebook。为了让 ncnn 与 PyTorch 完全一致,runtime 必须复刻:
工程中为此加入了多级 replay 验证,包括 talker layer、talker stack、code predictor、prompt-level generation replay 和最终 wav compare。最终 stable 96k 用例达到
928 / 928generated code token 完全一致。5. 代码与产物结构
6. 一键复现流程
在 Windows PowerShell 中:
脚本会执行:
v38_raw_general模型包。speaker_encoder_375。成功时应看到:
7. 验证结果
flowchart LR A["PyTorch baseline trace\n(seed fixed, greedy)"] --> B["qwen3_tts_generation_trace/*.npz"] B --> C["qwen3_tts_ncnn run (same input)"] C --> D["ncnn_report.json + ncnn.wav"] C --> E["model.json + profile metadata"] D --> F["qwen3_tts_compare.py"] B --> F E --> F D --> G["qwen3_tts_wav_sanity.py"] A --> G F --> H["token parity + waveform"] G --> I["audio sanity"] H --> J{"all gates pass"} I --> J J --> K["stable parity pass"] J --> L["expected-fail sentinels / capability block"]7.1 最终 stable 96k raw E2E parity
speaker_encoder_375928 / 928800 / 800111360 / 111360~1.69e-65e-49. 结论
本次 Qwen3-TTS-ncnn 适配已经完成 0.6B Base deterministic voice clone 的核心闭环:PNNX split export、ncnn 子图加载、C++ raw input runtime、参考音频处理、生成调度、wav 输出和 PyTorch raw E2E parity。
当前方案不是“完整 PyTorch 全图一键转 ncnn”,而是更适合这个模型复杂度的工程化落地方式:把张量密集部分交给 ncnn,把动态控制流和音频/codec glue 放在 C++ runtime。
参考项目
Beta Was this translation helpful? Give feedback.
All reactions