ChatTTS Windows打包实战:从依赖管理到一键部署的完整解决方案
把 2.3 GB 的 PyTorch + 700 MB 语音模型塞进一个 60 MB 安装包,还要让 Windows Defender 不报警——这篇文章记录了我踩坑两周后沉淀下来的“能跑、能发、能上线”的打包套路。
一、Windows 部署 ChatTTS 的三座大山
PyTorch 全家桶体积爆炸
官方轮子 1.9 GB,语音合成其实只用得到 CPU 后端,却连带 CUDA DLL,体积直接起飞。模型懒加载路径“玄学”
ChatTTS 在第一次推理时才把*.pth解压到%TEMP%,打包后临时目录被重定向到C:\Users\<User>\AppData\Local\Temp\_MEIxxxxx,权限不足或被杀软拦截就崩溃。VC++ 运行时版本地狱
语音后端依赖libtorchaudio.dll,它依赖MSVCP140.dll,用户机器上版本不对直接 0xc000007b。
二、工具横评:PyInstaller vs Nuitka vs Cx_Freeze
| 指标 | PyInstaller | Nuitka | Cx_Freeze |
|---|---|---|---|
| 打包体积 | 60 MB(UPX 后) | 110 MB | 180 MB |
| 启动速度 | 1.8 s | 0.9 s | 2.5 s |
| 反编译难度 | 低(源码在 PYZ) | 高(C 编译) | 低 |
| 语音模型资源打包 | 支持--add-data | 需手动写插件 | 支持 |
| 调试符号 | 有 | 有 | 无 |
| 社区活跃度 | 高 | 中 | 低 |
结论:要“当天发版”选 PyInstaller;要“代码加密”选 Nuitka;Cx_Freeze 在语音场景无优势。
三、核心实现:一条命令出包
3.1 先建“干净房子”——VirtualEnv
# 在 PowerShell 里 python -m venv chatts_env chatts_env\Scripts\activate # 只装 CPU 版 PyTorch,体积立减 1.5 GB pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu pip install ChatTTS3.2 写 spec:让 PyInstaller 认识“语音模型”
# chatts.spec (行号方便下文引用) # -*- mode: python ; coding: utf-8 -*- import os from PyInstaller.utils.hooks import collect_data_files a = Analysis( ['run.py'], pathex=[], binaries=[], datas=collect_data_files('ChatTTS'), # ① 自动收集模型 hiddenimports=['scipy.special.cython_special'], # ② 常见缺漏 hookspath=[], hooksconfig={}, runtime_tmpdir=None, excludes=['matplotlib', 'pandas'], # ③ 用不到就砍掉 win_no_prefer_redirects=False, win_private_assemblies=False, noarchive=False, ) pyz = PYZ(a.pure, a.zipped_data, cipher=0) exe = EXE( pyz, a.scripts, a.binaries, a.zipfiles, a.datas, [], name='ChatTTS-CLI', debug=False, bootloader_ignore_signals=False, strip=False, upx=True, # ④ 开启 UPX upx_exclude=['vcruntime140.dll'], # ⑤ 防止签名被破坏 runtime_tmpdir=None, console=True, disable_windowed_traceback=False, target_arch=None, codesign_identity=None, # ⑥ 稍后手动签名 entitlements_file=None, )高亮:① 行用官方 hook 省得手写;④ 行 UPX=True 是体积减半的灵魂;⑤ 行一定把 VC 运行库排除,否则签名后运行会报“DLL 已损坏”。
3.3 代码层:把“懒加载”改成“预加载”并加异常兜底
# run.py import os, sys, tempfile, shutil import ChatTTS def resource_path(relative): """ PyInstaller 解包后路径 """ base = getattr(sys, '_MEIPASS', os.path.abspath(".")) return os.path.join(base, relative) def preload_model(): try: # 把模型提前拷到可写目录,避免运行时解压失败 tmpdir = tempfile.mkdtemp(prefix="chatts_model_") src = resource_path("ChatTTS/models") dst = os.path.join(tmpdir, "models") shutil.copytree(src, dst) os.environ["CHATTTS_MODEL_DIR"] = dst # 自定义环境变量 except Exception as e: print("[WARN] 模型预加载失败,回退到默认路径:", e) if __name__ == "__main__": preload_model() chat = ChatTTS.Chat() chat.load(compile=False) # 关键:compile=False 省 JIT 时间 text = "恭喜,打包成功!" wavs = chat.infer(text) ChatTTS.save_wav(wavs[0], "demo.wav")四、生产级优化:让安装包“像样子”
4.1 UPX 压缩与签名二合一
# 安装 UPX 并加到 PATH choco install upx # 打包完先压缩 upx --best --lzma dist\ChatTTS-CLI.exe # 签名(需企业证书) signtool sign /fd sha256 /f my.pfx /p <pwd> /t http://timestamp.digicert.com dist\ChatTTS-CLI.exe注意:UPX 后再签名,顺序反了会被 Windows Defender 当成加壳病毒。
4.2 解决 Defender 误报
- 把 exe 提交到 https://www.microsoft.com/en-us/wdsi/filesubmission 走白名单。
- 在 Inno Setup 脚本里写
SignTool,安装包也一起签名,降低误杀概率。 - 打包时把
console=True改成console=False并加版本信息资源,减少“无描述可执行文件”特征。
五、避坑指南:Top3 运行时错误
| 报错截图 | 根因 | 排查 checklist |
|---|---|---|
| ImportError: DLL load failed | libtorchaudio.dll依赖链缺 VC++ 14.34 | ① 装最新 VC_redist.x64.exe ② 用 Dependencies 工具看缺哪个 API |
| RuntimeError: Model file not found | 临时目录权限不足 | ① 检查是否被杀软隔离 ② 用procmon.exe看真实访问路径 |
| PermissionError: [Errno 13] | 写入%TEMP%\_MEIxxx失败 | ① 以管理员运行 ② 在 spec 里把runtime_tmpdir='.'改到安装目录 |
六、一键脚本:10 分钟出包
把下面保存为build.bat,双击即可:
@echo off call chatts_env\Scripts\activate python -m PyInstaller chatts.spec --noconfirm --clean upx --best dist\ChatTTS-CLI.exe signtool sign /fd sha256 /f my.pfx /p %CERT_PWD% /tr http://timestamp.digicert.com dist\ChatTTS-CLI.exe echo ===== 打包完成,大小: dir dist\ChatTTS-CLI.exe七、延伸思考:增量模型热更新怎么做?
当前方案把模型封死在 exe 里,发新版就得重新走完整打包 + 签名流程。
能否把*.pth单独放到云端,客户端首次运行时按需下载,并通过哈希校验?
如果模型分片更新,只有 20 MB 差异,如何设计二进制补丁让老用户秒级热更?
欢迎留言聊聊你的思路,也许下一篇就一起把它落地。
全文代码与 spec 模板已放在 GitHub 示例仓库,克隆即可跑通。祝你打包顺利,Defender 不杀,老板夸你神速。