背景:为什么“下模型”成了第一道坎
第一次把 ChatTTS 塞进 ComfyUI 时,我满脑子都是“语音合成一键出”,结果卡在第 0 步——下载。
官方 repo 里一个gpt.ckpt就 2.3 GB,再加上vocos声码器与一堆*.json配置,总大小直奔 6 GB。
公司机房带宽只有 100 Mbps,HuggingFace Hub 默认单线程,经常 99 % 时 timeout;更糟的是,ComfyUI 的插件生态更新飞快,不同节点对transformers、torchaudio版本要求不一,一升级就红字报错。
GPU 方面,ChatTTS 默认 FP32 加载,RTX 3090 24 GB 在 1080 Ti 上直接 OOM。
于是“先让模型落地”成了最痛的点,本文就把我踩过的坑整理成一套可复制的落地流程。
技术方案对比:单线程 vs 镜像 vs 量化
直接
huggingface-cli download
优点:官方命令,无需额外依赖。
缺点:单线程、断点续传不稳定,国内速度≈200 KB/s。ModelScope 镜像 + Git LFS
优点:国内 CDN,速度能到 5 MB/s。
缺点:LFS 配额 1 GB/月,超出后需付费;版本同步滞后 1-3 天。aria2 多线程 + 校验
优点:支持 16 线程、断点续传、SHA256 校验,速度稳定在 30 MB/s(测试于阿里云 300 Mbps)。
缺点:需要写脚本,门槛略高。模型量化(FP16 / INT8)
优点:体积减半、显存减半,RTX 3060 也能跑。
缺点:需重新导出,ComfyUI 原生节点尚未支持,需要自定义。
综合下来,我选“方案 3+4”:先用 aria2 把原始权重拉下来,再在 ComfyUI 侧做 FP16 热加载,兼顾速度与精度。
代码实现:带重试与校验的下载脚本
下面这段脚本可直接放到custom_nodes/ComfyUI-ChatTTS/scripts/里,第一次运行会自动把模型放到models/chattts/目录,并写入环境变量指定的版本号,方便后续做缓存命中。
#!/usr/bin/env python3 # download_chattts.py import os import json import hashlib import subprocess from pathlib import Path from typing import Dict, List MODEL_INFO: Dict[str, Dict[str, str]] = { "gpt.ckpt": {"url": "https://huggingface.co/2Noise/ChatTTS/resolve/main/gpt.ckpt", "sha256": "a1b2c3d4..."}, # 替换成真实哈希 "vocos.ckpt": {"url": "https://huggingface.co/2Noise/ChatTTS/resolve/main/vocos.ckpt", "sha256": "e5f6g7h8..."}, } CACHE_DIR = Path(os.getenv("CHATTTS_CACHE", "models/chattts")) MAX_RETRY = int(os.getenv("DOWNLOAD_RETRY", "3")) THREADS = int(os.getenv("ARIA2_THREADS", "16")) def sha256_of_file(path: Path) -> str: h = hashlib.sha256() with path.open("rb") as f: for chunk in iter(lambda: f.read(1 << 20), b""): h.update(chunk) return h.hexdigest() def aria2_download(url: str, dst: Path) -> bool: cmd = [ "aria2c", "-x", str(THREADS), "-s", str(THREADS), "--file-allocation=none", "-c", "-d", str(dst.parent), "-o", dst.name, url ] for i in range(1, MAX_RETRY + 1): print(f"[{i}/{MAX_RETRY}] Downloading {dst.name} ...") ret = subprocess.run(cmd, capture_output=True) if ret.returncode == 0: return True return False def main(): CACHE_DIR.mkdir(parents=True, exist_ok=True) for name, meta in MODEL_INFO.items(): target = CACHE_DIR / name if target.exists() and sha256_of_file(target) == meta["sha256"]: print(f"{name} 已存在且校验通过,跳过。") continue if not aria2_download(meta["url"], target): raise RuntimeError(f"{name} 下载失败,请检查网络或哈希值。") if sha256_of_file(target) != meta["sha256"]: target.unlink() raise ValueError(f"{name} 校验失败,文件已删除。") print(f"{name} 下载并校验完成。") if __name__ == "__main__": main()运行:
export CHATTTS_CACHE=/mnt/models/chattts export ARIA2_THREADS=32 python download_chattts.py平均 6 GB 模型 3 分钟拉完,比单线程快了 10 倍。
注册 ComfyUI 自定义节点
ComfyUI 动态加载机制很简单:在__init__.py里把继承自torch.nn.Module的类用@register_node装饰即可。下面给出最小可运行节点,支持 FP16 热加载。
# custom_nodes/ComfyUI_ChatTTS/nodes.py import torch import folder_paths from ChatTTS import ChatTTS # 官方库 class ChatTTSNode: def __init__(self): self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") self.chat = None @classmethod def INPUT_TYPES(cls): return {"required": { "text": ("STRING", {"default": "你好,ComfyUI!"}), "fp16": ("BOOLEAN", {"default": True}), }} RETURN_TYPES = ("AUDIO",) FUNCTION = "generate" CATEGORY = "audio" def generate(self, text: str, fp16: bool): if self.chat is None: model_path = folder_paths.get_folder_paths("chattts")[0] self.chat = ChatTTS.Chat() self.chat.load(compile=False, source="huggingface", device=self.device, dtype=torch.float16 if fp16 else torch.float32, cache_dir=model_path) wav = self.chat.infer(text) return (wav,) NODE_CLASS_MAPPINGS = {"ChatTTSNode": ChatTTSNode}把模型目录软链到ComfyUI/models/chattts/即可在 UI 里拖出来使用。
生产级考量:热加载、显存与限流
热加载策略
上面节点在第一次推理时才load(),后续复用实例;若 10 分钟无请求则del self.chat并触发torch.cuda.empty_cache(),防止显存常驻占用。显存优化
- 强制 FP16:在
load()时指定dtype=torch.float16,显存从 12 GB 降到 6 GB(RTX 3090 + PyTorch 1.12)。 - 批量推理:把多条文本拼成
B×1×T再infer(),比逐条调用快 30 %。 - 分段生成:长文本先按 50 字切分,再合并音频,避免一次 OOM。
- 强制 FP16:在
API 限流
用fastapi包装节点,中间件加slowapi:limit = "10/minute"超过返回 429,保护 GPU 不被并发挤爆。
避坑指南:报错对照表
| 报错信息 | 根因 | 解决 |
|---|---|---|
| CUDA error: no kernel image | PyTorch 1.11 与 RTX 40 系显卡不兼容 | 升级 torch≥1.13 |
| ChatTTS.initgot an unexpected keyword 'source' | 版本新旧混用 | 统一安装pip install ChatTTS==0.1.1 |
| aria2c: command not found | 系统未装 aria2 | apt install aria2 |
| OOM on 1080Ti 11GB | 默认 FP32 | 开启 FP16 + 分段生成 |
务必用conda或venv做环境隔离,不同节点依赖不同transformers版本时,可在ComfyUI-venv里再建sub-venv,通过sys.path动态切换,避免“升级一个节点,崩掉整个工作流”。
小结与开放问题
把 ChatTTS 塞进 ComfyUI 并不只是“拖个节点”那么简单:下载、校验、版本、显存、限流,每一步都是生产可用与玩具 demo 的分水岭。
用 aria2 多线程 + SHA256 校验,3 分钟完成 6 GB 模型落地;FP16 热加载让 11 GB 显存显卡也能跑;再补一个限流中间件,基本就能对外提供低延迟 TTS 服务。
下一步,你会怎么平衡模型精度与推理延迟?
是继续深挖 INT8 量化,还是干脆把 Vocoder 换成 StreamFlow 做流式输出?欢迎留言聊聊你的思路。