ChatTTS 模型下载位置修改实战指南:从配置到避坑
背景痛点:默认路径带来的“三座大山”
第一次跑通 ChatTTS 的 demo 时,我兴冲冲地敲下:
from ChatTTS import ChatTTS chat = ChatTTS.ChatTTS() chat.load_models()结果模型哗啦啦往~/.cache/chattts/里灌,不到 3 分钟,系统盘直接爆红。
更尴尬的是,公司这台 Linux 服务器把/home挂载成只读,权限拒绝弹窗比弹幕还多。
总结下来,新手最容易被三座大山绊住:
- 系统盘空间不足,模型 4 GB+,Docker 镜像再一叠加,直接原地爆炸。
- 权限限制,默认缓存目录在只读分区或无写权限路径,导致下载失败。
- 多用户混用,A 同学下载的模型 B 同学无法复用,重复拉取浪费带宽。
一句话:不改路径,ChatTTS 就是“磁盘杀手”。
技术方案对比:环境变量 vs 代码硬改
| 维度 | 环境变量法 | 代码硬改法 |
|---|---|---|
| 侵入性 | 0,不动源码 | 中,需重写 loader |
| 升级维护 | 无痛升级,官方更新无感知 | 每次升级需手动 merge |
| 灵活度 | 启动时一次设定,运行期不可变 | 可运行时动态切换 |
| 适合场景 | 个人电脑、一次性脚本 | 服务化部署、多租户隔离 |
结论:
- 本地笔记本、教学 demo → 环境变量一把梭。
- 线上服务、多模型热切换 → 代码硬改更稳。
核心实现
1. 环境变量方式:三行命令搞定
ChatTTS 在0.2.0+之后内置了对CHATTTS_MODEL_DIR的读取,优先级高于默认缓存。
Linux / macOS:
export CHATTTS_MODEL_DIR=/data/ai/models/chattts python app.pyWindows PowerShell:
$env:CHATTTS_MODEL_DIR="D:\ai\models\chattts" python app.py一行代码验证是否生效:
import os, ChatTTS print(ChatTTS.utils.get_model_dir()) # 应该打印 /data/ai/models/chattts小提示:把
export写进~/.bashrc或/etc/profile.d/ai.sh可实现永久生效。
2. 代码修改方式:写一个“路径劫持”类
当环境变量不能满足动态切换时,直接在 Python 层拦截模型加载。
核心思路:继承ChatTTS并重写_get_model_path。
# custom_chattts.py import os from pathlib import Path import ChatTTS class CustomChatTTS(ChatTTS.ChatTTS): def _get_model_path(self, model_name: str) -> Path: """ 统一模型路径入口,任何模型下载前都会走到这里 """ root = Path(os.getenv("CHATTTS_MODEL_DIR", "/opt/chattts")) root.mkdir(parents=True, exist_ok=True) return root / f"{model_name}.pt" def load_models(self, device='cuda', compile=False): # 先劫持路径,再调父类逻辑 self._model_path_resolver = self._get_model_path super().load_models(device=device, compile=compile)使用侧零感知:
from custom_chattts import CustomChatTTS chat = CustomChatTTS() chat.load_models() # 模型已落到 /opt/chattts好处:
- 可运行时动态换盘:
os.environ['CHATTTS_MODEL_DIR'] = '/mnt/ssd2/chattts'后重新实例化即可。 - 兼容旧代码,无需改业务侧一行。
完整可运行示例:带进度条与异常处理
把上面片段揉成一个脚本,可直接python tts_cli.py跑通。
# tts_cli.py import os import sys from pathlib import Path import ChatTTS from ChatTTS.logger import logger MODEL_DIR = Path(os.getenv("CHATTTS_MODEL_DIR", "./my_models")) MODEL_DIR.mkdir(parents=True, exist_ok=True) def download_with_resume(url: str, dst: Path, chunk_size=1*1021024): """带断点续传的简易下载器,防止公司网络抖动""" import requests headers = {} if dst.exists(): headers['Range'] = f'bytes={dst.stat().st_size}-' with requests.get(url, headers=headers, stream=True) as r: r.raise_for_status() with open(dst, 'ab') as f: for chunk in r.iter_content(chunk_size): f.write(chunk) sys.stdout.write('.') sys.stdout.flush() print("\ndone.") class MyChatTTS(ChatTTS.ChatTTS): def _download_model(self, model_name: str): url = f"https://huggingface.co/2Noise/ChatTTS/resolve/main/{model_name}.pt" dst = MODEL_DIR / f"{model_name}.pt" if dst.exists() and dst.stat().st_size > 0: logger.info(f"skip {model_name}, already exists.") return logger.info(f"downloading {model_name} -> {dst}") download_with_resume(url, dst) def load_models(self, device='cuda', compile=False): for m in ['gpt', 'vocos', 'dvae']: self._download_model(m) # 劫持本地路径 self._model_path_resolver = lambda name: MODEL_DIR / f"{name}.pt" super().load_models(device=device, compile=compile) if __name__ == "__main__": chat = MyChatTTS() chat.load_models() # 推理示例 wavs = chat.infer("你好,ChatTTS 模型路径已自由!") ChatTTS.save(wavs[0], "./demo.wav")跑完后目录结构:
my_models/ ├── g逐.pt ├── vocos.pt └── dvae.pt生产环境考量
路径权限
建议把模型目录挂到独立磁盘,并创建专用用户:sudo mkdir -p /data/ai/models sudo groupadd aimodel sudo usermod -a -G aimodel www-data sudo chown root:aimodel /data/ai/models sudo chmod 2775 /data/ai/models # 之后新建文件自动继承组多用户隔离
若同一台机器跑多个服务,可用CHATTTS_MODEL_DIR=/data/ai/models/serviceA做软链隔离,避免重复下载。磁盘 IO 优化
- 把模型放 SSD,推理时
num_workers>0可显著降低延迟。 - 定期
fstrim防止 SSD 写放大。 - 若用 NFS,开启
local_lock=all减少重复拉取。
- 把模型放 SSD,推理时
避坑指南:报错对照表
| 报错信息 | 根因 | 解决 |
|---|---|---|
Permission denied: ~/.cache/chattts/gpt.pt | 默认路径无写权限 | 设置CHATTTS_MODEL_DIR到可写目录 |
FileNotFoundError: [Errno 2] No such file or directory: 'models/gpt.pt' | 路径拼写少了/ | 用Path对象拼接,避免裸字符串 |
RuntimeError: Model size mismatch | 旧版本缓存残留 | 清空旧目录后重拉 |
huggingface_hub.utils.LocalEntryNotFound | 代理没配 | export HF_ENDPOINT=https://hf-mirror.com |
| 下载到 99% 断网 | 没做断点续传 | 参考上方download_with_resume |
延伸思考题
- 如何基于
filelock实现多进程同时下载时的单例锁,防止重复写入? - 若模型需要热更新,怎样在不停服务的情况下把新权重切换进去?
- 当磁盘剩余空间低于 5% 时,如何自动清理最久未使用的模型文件?
把这三个问题想透,你的 ChatTTS 就真正从“能跑”进化到“好养”了。
写完这篇笔记,我把公司服务器系统盘从爆满 98% 降到 42%,运维小哥终于肯在周报里夸我“降本增效”。
如果你也踩过默认缓存的坑,不妨把CHATTTS_MODEL_DIR写进 CI 变量,再顺手点个 star,下次升级就能无痛起飞。