尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

MeloTTS QNN ONNX更新记录

MeloTTS QNN ONNX更新记录 概述本文档记录了将 MeloTTS 从原始 ONNX 动态模型适配为Qualcomm QNN HTP 静态模型所做的所有源码改动以及每项改动的原理和收益。背景原始 MeloTTS ONNX 模型是动态 shapevariable sequence length在 CPU/CUDA 上运行正常。但要部署到 Qualcomm QCS8550 的 Hexagon HTP (v73) 上运行时遇到了动态 shape 不兼容、fp16 精度崩塌、DSP 内存不足等一系列问题。重磅更新啦MeloTTS-QNN基于QCS8550 V73架构qnn onnx模型部署问题解决# 模型下载modelscope download--modelKeanuX/MeloTTS-ZH-MIXED-EN-ONNX--local_dir./# 源码Clonegitclone https://gitee.com/jackroing/melo-tts-onnx.git使用说明解压job_jp3xz8lmp_optimized_onnx_mn7p4wz8n.onnx.zip到precompiled_qnn_onnx文件夹下并提取model.onnx以及model.bin到precompiled_qnn_onnx文件夹下在QCS8550设备上需要安装Qairt SDK 2.46及以上版本Python3.10.12并安装了onnxruntime-qnn基于Qairt SDK 2.46版本的Python依赖。一、修改的文件清单文件改动类型说明melo/models.py核心逻辑添加静态导出路径、QNN 保底模式、y_lengths 输出melo/attentions.py核心逻辑添加导出模式相对位置编码切片替代动态 padmelo_extra/melo_tts.py封装层支持多路导出动态/静态/QNN、y_lengths 联合输出export_melo.py导出工具新增--seq_len、--max_mel_frames、--qnn、--precision参数run_onnx.py推理引擎y_lengths 精确截断、RMS 静音检测、自动 dtype 适配、名称映射输入compile.py编译工具一键导出 qai-hub 编译 profilecompile_configs/cfg.ini编译配置静态 512 序列长度的输入 spec二、核心问题与解决方案问题 1动态 shape → QNN 不兼容现象动态 ONNX 模型sequence_length可变编译为 QNN context binary 后推理时报QNN error 1100。根因QNN context binary generator 在编译时把图优化固化为特定 shape运行时 shape 不匹配即报错。解决方案导出静态 ONNX 模型所有维度冻结为固定值。改动位置export_melo.pymelo/models.py文件改动export_melo.pybuild_melo_dummy_input()新增target_seq_len512参数将文本 token 序列统一 pad/truncate 到 512export_melo.py不传-id时dynamic_axes{}导出纯静态模型melo/models.pyforward_for_export_static()中y_mask的arange改为可配置的max_mel_frames参数原硬编码 2048melo/attentions.pyMultiHeadAttention新增_build_export_embeddings()export_mode预构建固定尺寸的 relative position 缓冲区用纯 slice 替代运行时F.pad避免动态 op收益QNN 编译器可以完整静态优化整个计算图消除 shape 不匹配导致的 QNN error 1100问题 2DSP 内存不足QNN_COMMON_ERROR_MEM_ALLOC现象模型加载时 DSP 尝试 mmap ~191MB 的缓冲区失败报QNN_COMMON_ERROR_MEM_ALLOC。根因forward_for_export_static中y_mask硬编码2048帧导致输出 buffer(1, 1, 2048×512) 1,048,576 samples中间 tensor 呈指数级放大DDR spill 高达 12.4GB累积峰值内存超 Hexagon v73 可用空间解决方案将max_mel_frames从 2048 降低到 1024可配置。改动位置melo/models.pyexport_melo.py文件改动melo/models.pyforward_for_export_static()的static_range torch.arange(max_mel_frames, ...)参数默认 1024melo/attentions.pyMultiHeadAttention._max_length同步减小_build_export_embeddings()buffer 缩小一半melo_extra/melo_tts.pyMeloTTSWrapper.__init__()新增max_mel_frames属性export_melo.py新增-mmf/--max_mel_framesCLI 参数收益指标修改前 (2048)修改后 (1024)输出 audio shape(1, 1, 1,048,576)(1, 1, 524,288)Attention buffer per MHA 层4095 × channels2047 × channelsDSP peak memory (profile)309 MB (OOM)~247 MB (正常)问题 3fp16 精度崩塌 → 音频全空现象模型在 fp32 (CPU/CUDA) 上输出正常但在 QNN HTP fp16 上y_lengths847 token 只预测 8 帧 mel即 0.09 秒音频几乎为空。根因链Text Encoder (12 层 Transformer) ↓ fp16 累积误差 Encoder 输出 x 失真 ↓ Duration Predictor (DP/SDP) ↓ logw 极度负值 → exp(logw) fp16 underflow → 0 w_ceil 0 for most tokens ↓ y_lengths ≈ 8 (几乎为零) ↓ Decoder 输出全是 bias 漂移噪声逐步排查过程尝试 1 — 仅 clamp logwclamp(logw, min-11.5)→ 无效因为exp(-11.5) ≈ 1e-5在 fp16 中是 subnormal被 flush to zero尝试 2 — 提高 clamp 到 fp16 normal 下界clamp(logw, min-9.7)→ 无效问题不在 exp 而在 encoder 输出本身已损坏尝试 3 — 绕过 SDP 只用 DP clamp仍无效DP 同样依赖 encoder 输出fp16 下也产出垃圾尝试 4最终— 彻底绕过整个 Duration Predictor固定每 token 时长不走任何可学习的 duration 模块最终解决方案QNN 模式下 duration 完全绕过 SDP/DP使用固定时长。改动位置melo/models.py第 1060-1061 行ifgetattr(self,_qnn_mode,False):# QNN HTP fp16: SDP/DP 全崩固定每 token 4 帧w_ceiltorch.ones_like(x_mask.squeeze(1))*4# [b, t_text]w_ceilw_ceil.unsqueeze(1)*x_mask# [b, 1, t_text]else:# fp32 / CPU: 原始 SDP DP正常质量logwself.sdp(...)*sdp_ratioself.dp(...)*(1-sdp_ratio)wtorch.exp(logw)*x_mask*(1.0/speed[0])w_ceiltorch.ceil(w)导出模式Duration 方案适用场景本地 ONNX (--qnn不传)原始 SDP DPCPU/CUDAfp32语音自然QNN HTP (--qnn)固定 4 帧/tokenQCS8550 HTPfp16零精度风险收益彻底消除 fp16 下 encoder → duration predictor 链路的精度崩塌每 token 固定 4 帧47 token → 188 帧 → ~2.2 秒覆盖常规语速纯常量乘法ones * 4 * x_mask无任何 fp16 敏感 op问题 4静态模型输出超长 → 尾部杂音/空音频现象静态模型输出固定长度 buffer如 524,288 samples实际有效音频只占前面一小段尾部是 decoder 对零 mel 输入产生的空闲输出。需要精确截断。根因ONNX 静态模型只有audio_data一个输出推理端无法知道有效音频从哪结束。排查过程尝试 1 — 文本比例线性估算valid_audio total_audio × (text_len / total_len)→ 不准确duration 是非线性的尝试 2 — 静音检测峰值尾部空闲噪音振幅 ~0.06语音振幅 ~0.32ratio 仅 5.5x → 门限难以选取尝试 3 — RMS 能量检测CPU 上区分度好ratio 10x但在 QNN fp16 上完全失效idle noise RMS ≈ speech RMS尝试 4最终— 直接导出y_lengths模型内部已知有效 mel 帧数直接作为第二个 ONNX 输出解决方案在 ONNX 模型中添加y_lengths有效 mel 帧数作为第二个输出。改动位置melo/models.pymelo_extra/melo_tts.pyexport_melo.pyrun_onnx.py文件改动melo/models.pyforward_for_export_static()返回第 5 个元素为y_lengthsmelo_extra/melo_tts.py静态路径return audio_valid, y_lengths.to(torch.int32)export_melo.py静态导出output_names [audio_data, y_lengths]run_onnx.pyvalid_samples y_len * hop_size精确截取收益推理端直接取y_lengths × hop_size得到精确有效音频长度任何 EPCPU/CUDA/QNN都适用零误判兼容旧模型检测到单输出时自动 fallback 到 RMS 检测问题 5输入索引硬编码 → 多版本模型不兼容现象不同导出模式下的模型输入数量不同动态 11 输入、静态 ONNX 10 输入、QNN 8-9 输入硬编码self.input_names[8]导致IndexError。解决方案改为按名称映射输入。改动位置run_onnx.py# 旧硬编码索引input_spec{self.input_names[0]:x_tst,self.input_names[7]:np_sdp_ratio,...}# 新按名字映射兼容所有输入变体named_inputs{x_tst:x_tst,sdp_ratio:np_sdp_ratio,speed:np_speed,...}input_spec{k:vfork,vinnamed_inputs.items()ifkinself.input_names}收益一套推理代码兼容 8/9/10/11 输入的所有模型版本。三、导出命令速查# 本地 ONNXfp32SDPDP完整质量python export_melo.py-sl512-mmf1024--opset16# QNN HTPfp32 IO固定 durationQNN 优化python export_melo.py-sl512-mmf1024--opset16--qnn# 一键导出 编译 profilebashexport_and_compile.sh四、架构总览┌─────────────────────────┐ │ export_melo.py │ │ -sl 512 -mmf 1024 │ │ --qnn --opset 16 │ └───────────┬───────────────┘ │ ┌──────────────────┼──────────────────┐ │ │ │ ┌─────────▼────────┐ ┌──────▼──────┐ ┌────────▼─────────┐ │ 本地 ONNX (fp32) │ │ QNN ONNX │ │ 推理端 │ │ SDP DP │ │ 固定 4 帧 │ │ y_lengths 精确 │ │ 自然语音质量 │ │ fp16 安全 │ │ 截断音频 │ └──────────────────┘ └─────────────┘ └──────────────────┘
返回列表