在 TTS 中使用 Tortoise:高表现力语音合成与声音克隆的模型结构与推理配置全解析
Tortoise 是 🐸TTS 仓库中一个以"表现力"和"声音克隆"见长的 TTS 系统,其核心是一条由 GPT 式自回归声学模型、扩散模型与 UnivNet 声码器串联的三级流水线。本文以 docs/source/models/tortoise.md 为主线,结合仓库内 模型实现 与 tortoise 专用层实现 的源码证据,讲解它的整体工作原理、三种调用方式(Python 模型级、🐸TTS API、命令行)、TortoiseConfig / TortoiseArgs 全部配置参数,以及预设(preset)与采样参数在推理时的真实作用。读完你既能照抄示例完成声音克隆推理,也能理解每个旋钮在源码流水线中落在哪一步。
一、Tortoise 是什么:一段"绕远路"但表现力极强的合成流水线
与 VITS 这类一步到位的并行(parallel)TTS 不同,Tortoise 采用的是从源码结构上清晰可见的三级级联设计(模型在 TTS/tts/models/tortoise.py 中按此结构初始化对应子网络):
- GPT 式自回归声学模型(UnifiedVoice):把输入文本与参考说话人音频的 condition 一起,自回归地"逐 token"预测离散化的声学 token(
TTS/tts/layers/tortoise/autoregressive.py中的UnifiedVoice,其推理封装GPT2InferenceModel基于 Hugging FaceGPT2PreTrainedModel实现,见同一文件的 GPT2InferenceModel 定义); - 扩散模型(DiffusionTts):将上一步得到的离散声学 token + 文本/说话人嵌入条件,通过迭代去噪生成梅尔频谱帧(
TTS/tts/layers/tortoise/diffusion.py与diffusion_decoder.py); - UnivNet 声码器:把梅尔频谱还原为最终波形(
TTS/tts/layers/tortoise/vocoder.py中的 UnivNet,配置默认指向VocConf.Univnet)。
除此之外,推理流水线中还有一个起"裁判"作用的 CLVP(CLIP 变体,条件潜在变量评分器)(TTS/tts/layers/tortoise/clvp.py),用于对自回归模型产生的多个候选样本按与文本的匹配程度打分排序。原文档对它的定位很直白:
Tortoise is a very expressive TTS system with impressive voice cloning capabilities... The important downside is that Tortoise is very slow compared to the parallel TTS models like VITS.
也就是说,表现力与克隆能力是它的卖点,推理速度则是其明显短板。需要澄清一点:这里"慢"是相对 VITS 等并行模型而言的架构性代价——自回归解码逐 token 生成 + 扩散模型多步去噪 + 一次合成数十个候选再筛选,天然就无法做到并行 TTS 那样的低延迟。仓库实现同样保持了这一约束,且只实现了推理路径(forward()、train_step()、eval_step() 均直接 raise NotImplementedError("Tortoise Training is not implemented"),源码见),因此本文所述均针对推理/克隆场景。
二、快速上手:三种加载 Tortoise 的方式
原文档给出了三种等价用法,下面按当前仓库源码的实际签名整理(注意:仓库当前版本的 synthesize() 参数名是 voice_dirs,与早期文档中的 extra_voice_dirs 不同,详见 TTS/tts/models/tortoise.py 的 synthesize 定义)。
1. Python 模型级调用
from TTS.tts.configs.tortoise_config import TortoiseConfig
from TTS.tts.models.tortoise import Tortoise
config = TortoiseConfig()
model = Tortoise.init_from_config(config)
model.load_checkpoint(config, checkpoint_dir="paths/to/models_dir/", eval=True)
# 随机音色合成
output_dict = model.synthesize(text, config, speaker_id="random", voice_dirs=None, **kwargs)
# 用某个说话人的参考音频克隆音色
output_dict = model.synthesize(text, config, speaker_id="speaker_n", voice_dirs="path/to/speaker_n/", **kwargs)
这里 load_checkpoint() 有它自己的特殊性:Tortoise 不是单文件权重,而是一个多模型权重集合。从 load_checkpoint 实现 可以看到它会在 checkpoint_dir 下按固定文件名查找以下文件:
| 文件名 | 加载到的子网络 | 说明 |
|---|---|---|
autoregressive.pth |
self.autoregressive |
GPT 式自回归模型(以 strict=False 加载以兼容 Transformers 版本差异) |
diffusion_decoder.pth |
self.diffusion |
扩散解码器 |
clvp2.pth |
self.clvp |
候选音频打分器 |
vocoder.pth |
self.vocoder |
UnivNet 声码器 |
mel_norms.pth |
self.mel_norm_path |
梅尔归一化统计量(format_conditioning 会用到,见 torchmel 转换 相关逻辑) |
若设置了 config.model_dir 也可以不传 checkpoint_dir;eval=True 时会额外调用 autoregressive.post_init_gpt2_config(self.args.kv_cache) 并让所有子网络进入 eval() 模式。用于"随机音色"的 RLG 权重(rlg_auto.pth、rlg_diffuser.pth)则是在 get_random_conditioning_latents 中从 models_dir 惰性加载的。
2. 通过 🐸TTS 高级 API 调用
from TTS.api import TTS
tts = TTS("tts_models/en/multi-dataset/tortoise-v2")
# 克隆 `lj` 声音(来自你本地 voices 目录里的 lj 文件夹),并用自定义推理设置覆盖默认值
tts.tts_to_file(text="Hello, my name is Manmay , how are you?",
file_path="output.wav",
voice_dir="path/to/tortoise/voices/dir/",
speaker="lj",
num_autoregressive_samples=1,
diffusion_iterations=10)
# 用预设(preset)合成同一音色
tts.tts_to_file(text="Hello, my name is Manmay , how are you?",
file_path="output.wav",
voice_dir="path/to/tortoise/voices/dir/",
speaker="lj",
preset="ultra_fast")
# 完全随机音色生成
tts.tts_to_file(text="Hello, my name is Manmay , how are you?",
file_path="output.wav")
这套高层 API 在 TTS/api.py 中定义(tts_to_file 见该文件 第 290 行),并由 TTS/utils/synthesizer.py 的 Synthesizer 负责把 voice_dir 等参数转发到模型层的 synthesize(..., voice_dirs=...) 调用链(见 synthesizer 中 voice_dir 处理 与 模型 synthesize 调用)。注意 voice_dir 指向的是存放多个音色子目录的父目录,而 speaker="lj" 会从其中找到 lj/ 子目录并读取里面的参考音频。
3. 命令行调用
# 克隆 `lj` 音色
tts --model_name tts_models/en/multi-dataset/tortoise-v2 \
--text "This is an example." \
--out_path "output.wav" \
--voice_dir path/to/tortoise/voices/dir/ \
--speaker_idx "lj" \
--progress_bar True
# 随机音色生成
tts --model_name tts_models/en/multi-dataset/tortoise-v2 \
--text "This is an example." \
--out_path "output.wav" \
--progress_bar True
无论走哪条路径,speaker_id="random"(或不指定音色)都等价于"随机声音生成"——此时仓库实现并不加载任何参考音频,而是由 RandomLatentConverter 从纯噪声向量生成一对随机的 conditioning latents(见 get_random_conditioning_latents,对应模型 TTS/tts/layers/tortoise/random_latent_generator.py)。
三、TortoiseConfig:推理设置与采样策略配置
TortoiseConfig 继承自 TTS/tts/configs/shared_configs.py 的 BaseTTSConfig,全部字段和默认值如下(源码第 8-87 行):
| 参数 | 类型 | 默认值 | 含义 |
|---|---|---|---|
model |
str | "tortoise" |
模型名,用于 setup_model 按名定位实现模块(见 TTS/tts/models/init.py),无特殊需求勿改 |
model_args |
TortoiseArgs | TortoiseArgs() |
模型结构参数,见下一节 |
audio |
TortoiseAudioConfig | TortoiseAudioConfig() |
采样率配置:sample_rate=22050(自回归/condition 用)、diffusion_sample_rate=24000、output_sample_rate=24000 |
model_dir |
str | None |
存放全部 Tortoise 模型权重的目录,见上文 load_checkpoint |
temperature |
float | 0.2 |
自回归模型的采样温度,越大越"有创造性",但稳定性下降 |
length_penalty |
float | 1.0 |
束搜索中的长度惩罚。它作为序列长度的指数参与对分数的除法:>0 鼓励更长输出,<0 鼓励更短输出 |
repetition_penalty |
float | 2.0 |
重复惩罚参数,1.0 表示不惩罚 |
top_p |
float | 0.8 |
核采样(nucleus sampling)概率阈值:只保留累计概率达 top_p 的最小 token 集合参与采样 |
cond_free_k |
float | 2.0 |
无分类器引导(conditioning-free diffusion)的平衡旋钮,取值 [0,∞)。公式:output = cond_present_output*(cond_free_k+1) - cond_absent_output*cond_free_k;cond_free_k 越大,输出越受"无条件信号"主导 |
diffusion_temperature |
float | 1.0 |
注入扩散模型的噪声方差,[0,1]。0 相当于取扩散网络"均值预测",听感会平淡、发糊 |
num_autoregressive_samples |
int | 16 |
从自回归模型采样的候选条数,随后全部交由 CLVP 打分筛选。因为 Tortoise 是概率模型,样本越多越可能出"惊喜" |
diffusion_iterations |
int | 30 |
扩散步数,[0,4000]。步数越多理论上越精细,但源码注释指出超过 250 步基本看不出提升 |
sampler |
str | "ddim" |
扩散采样器:ddim 或 dpm++2m |
需要理解的一点是:这些 config 字段如何进入实际采样。在 inference_with_config 中,temperature / length_penalty / repetition_penalty / top_p / cond_free_k / diffusion_temperature / sampler 会先被收集成一个 settings 字典,若调用方传了 preset 则用预设覆盖,随后再被用户显式传入的 kwargs 覆盖(settings.update(kwargs)),最终展开成 inference(**settings) 调用。因此优先级是 显式 kwargs > preset > config 默认值。
四、TortoiseArgs:三个子网络的完整结构参数
原文档通过 autoclass:: TTS.tts.models.tortoise.TortoiseArgs 自动生成 API 文档,对应实现位于 TortoiseArgs 定义。它是一组纯数据类字段,被 Tortoise.__init__ 逐项拆解去构造三个子网络。以下按源码注释中的分组整理全部默认值。
4.1 通用/推理相关参数
| 参数 | 默认值 | 说明 |
|---|---|---|
autoregressive_batch_size |
1 |
自回归批次大小;为 None 时由 pick_best_batch_size_for_gpu() 按显存自动挑选(空闲 >14GB→16,>10GB→8,>7GB→4,否则 1,见 源码) |
enable_redaction |
False |
是否开启 Wav2Vec 对齐的自动消音(敏感词抹除),开启时会加载 Wav2VecAlignment(TTS/tts/layers/tortoise/wav2vec_alignment.py) |
high_vram |
False |
高显存模式:为 True 时四个子网络常驻 GPU;否则通过 temporary_cuda 上下文管理器用完即搬回 CPU,显存换延迟 |
kv_cache |
True |
自回归推理是否启用 KV Cache 加速 |
ar_checkpoint / clvp_checkpoint / diff_checkpoint |
None |
各子网络独立的 checkpoint 路径(实际加载仍由 load_checkpoint 统一完成) |
num_chars |
255 |
文本 token 数上限相关常量 |
vocoder |
VocConf.Univnet |
声码器类型,来自 TTS/tts/layers/tortoise/vocoder.py 中的 VocConf/VocType 枚举 |
4.2 UnifiedVoice(自回归模型)结构参数
| 参数 | 默认值 | 说明 |
|---|---|---|
ar_max_mel_tokens |
604 |
自回归最大梅尔 token 数 |
ar_max_text_tokens |
402 |
自回归最大文本 token 数 |
ar_max_conditioning_inputs |
2 |
最大 condition 输入条数 |
ar_layers |
30 |
Transformer 层数 |
ar_model_dim |
1024 |
模型维度 |
ar_heads |
16 |
注意力头数 |
ar_number_text_tokens |
255 |
文本词表大小 |
ar_start_text_token |
255 |
文本起始 token id |
ar_checkpointing |
False |
是否启用激活 checkpointing(省显存) |
ar_train_solo_embeddings |
False |
是否单独训练 embedding |
4.3 DiffusionTts(扩散解码器)结构参数
| 参数 | 默认值 | 说明 |
|---|---|---|
diff_model_channels |
1024 |
UNet 风格主干通道数 |
diff_num_layers |
10 |
层数 |
diff_in_channels |
100 |
输入通道(对应 100 维梅尔) |
diff_out_channels |
200 |
输出通道 |
diff_in_latent_channels |
1024 |
潜在 condition 通道 |
diff_in_tokens |
8193 |
音频 token 词表大小 |
diff_dropout |
0 |
dropout 比例 |
diff_use_fp16 |
False |
是否 fp16 |
diff_num_heads |
16 |
注意力头数 |
diff_layer_drop |
0 |
层 dropout |
diff_unconditioned_percentage |
0 |
无条件输入比例(与 cond-free 训练相关) |
4.4 CLVP(候选评分器)结构参数
| 参数 | 默认值 | 说明 |
|---|---|---|
clvp_dim_text |
768 |
文本编码维度 |
clvp_dim_speech |
768 |
语音编码维度 |
clvp_dim_latent |
768 |
共享潜在空间维度 |
clvp_num_text_tokens |
256 |
文本词表大小 |
clvp_text_enc_depth |
20 |
文本编码器深度 |
clvp_text_seq_len |
350 |
文本最大序列长度 |
clvp_text_heads |
12 |
文本编码器注意力头数 |
clvp_num_speech_tokens |
8192 |
语音 token 词表大小 |
clvp_speech_enc_depth |
20 |
语音编码器深度 |
clvp_speech_heads |
12 |
语音编码器注意力头数 |
clvp_speech_seq_len |
430 |
语音最大序列长度 |
clvp_use_xformers |
True |
是否使用 xformers 加速(TTS/tts/layers/tortoise/xtransformers.py) |
duration_const |
102400 |
扩散 condition 采样时长常量(102400 / 24000Hz ≈ 4.27s) |
这些结构参数在 Tortoise.__init__(TTS/tts/models/tortoise.py#L322-L394)中被原样传递到 UnifiedVoice、DiffusionTts、CLVP 和声码器的构造函数中。若你自行加载第三方权重,务必保证 TortoiseArgs 与权重训练时的结构一致,否则 load_state_dict 会失败。
五、源码级理解:一次 Tortoise 推理到底发生了什么
调用 synthesize() → inference_with_config() → inference() 后,主干逻辑在 inference 实现 中。可把它拆成四个阶段:
阶段 1:文本与 condition 编码。 文本先经 VoiceBpeTokenizer 分词(BPE,词表文件位于 TTS/tts/utils/assets/tortoise/tokenizer.json,内部先用英文清洗器 english_cleaners 预处理、空格替换为 [SPACE],见 TTS/tts/layers/tortoise/tokenizer.py),得到 token id 序列;源码强制要求其长度 < 400,否则抛错提示把文本分段。若提供了参考音频,则调用 get_conditioning_latents 提取一对(自回归条件、扩散条件)latent——参考音频会被重采样到 22.05kHz 和 24kHz 两个版本(见 audio_utils.load_required_audio),前者用于自回归条件(截取 cond_length=132300,即 6 秒),后者切成约 4.27s(duration_const)片段算梅尔供扩散模型用。
阶段 2:自回归候选生成与 CLVP 筛选。 在 num_autoregressive_samples 指定的条数范围内,用 Hugging Face generate API 做自回归采样(top_p/temperature/length_penalty/repetition_penalty/max_mel_tokens 全部作用于这一步)。每个候选要先经过 fix_autoregressive_output 修补:把 stop token 替换为静音 token(83),再在尾部补上 45, 45, 248 三个特判 token——注释特别说明不做这一步会导致输出末尾出现生硬的 "BLAH" 类音爆。然后所有候选经 CLVP 对(文本, 音频 token)打分,torch.topk 取出最好的 k 条(k=1 只保留一条,见 inference)。
阶段 3:扩散去噪。 对入围候选,用自回归模型的最后一层隐藏态(而不是离散 token)作为条件,送入 DiffusionTts。采样器由 load_discrete_vocoder_diffuser 构造,它本质是一个 SpacedDiffusion(训练 4000 步的 beta 线性调度 + space_timesteps 采样到目标步数),支持 ddim / dpm++2m 两种采样器,是否启用 cond-free 由 cond_free / cond_free_k 控制。去噪输出的 100 维梅尔经 denormalize_tacotron_mel 还原(归一化常量见 audio_utils.py)。
阶段 4:声码与可选消音。 梅尔交给 UnivNet 声码器转波形;若 enable_redaction=True,再经 Wav2VecAlignment.redact() 依据文本对齐信息抹除敏感词。最终输出采样率为 24kHz。
同时也要注意到该实现为低显存/非高 VRAM 场景做了大量工程化:四个大模型默认都留在 CPU,只有推理到哪一步才通过 temporary_cuda(源码)临时搬到 GPU,用毕再搬回。这就是 high_vram 参数存在的原因——它直接决定速度与显存的取舍。
六、声音克隆实操:voice 目录、参考音频与 latent
6.1 参考音频的组织方式
load_voice(TTS/tts/layers/tortoise/audio_utils.py#L117-L130)对 voice_dir(即 extra_voice_dirs)下的目录结构有固定约定。每个音色是一个子目录,get_voices 会扫描其下所有 .wav、.mp3 与 .pth 文件(get_voices),典型结构:
path/to/voices/dir/
└── lj/ # 音色名 = 文件夹名
├── clip_1.wav
└── clip_2.wav
规则如下:
- 若该音色目录里只有一个
.pth文件,它会被直接视为预计算好的 conditioning latents(load_voice返回(None, torch.load(path)),此时跳过重计算,速度最快); - 否则目录内所有
.wav/.mp3都会被当作参考音频加载,load_required_audio会把每个音频重采样成 (22.05kHz, 24kHz) 的成对张量交给模型现场提取 latents; - 传入多个参考音频时,自回归条件在样本间做平均,扩散条件则按
latent_averaging_mode(0/1/2,见 get_conditioning_latents 参数说明)决定是截取约 4.27s 平均、还是全片段分块平均、抑或逐样本平均后叠加。
仓库内自带的 TTS/tts/utils/assets/tortoise/ 目录目前只有
tokenizer.json(BPE 词表),并不包含示例参考音频。原文档示例中克隆lj声音指的是在你本地自备的 voices 父目录中放置lj/子目录与参考 wav,而不是仓库内置资源。
6.2 随机音色(不提供任何参考音频)
当 speaker_id="random" 时,load_voice 返回 (None, None),代码转而走 get_random_conditioning_latents:惰性加载 RandomLatentConverter(自回归侧 1024 维、扩散侧 2048 维),对常数张量 [0.0] 做一次前向得到一对随机 latents。这也是"随机声音生成"真正的实现来源——每个新 seed 都是一副新嗓子。
七、推理预设(preset):速度与质量的旋钮组合
原文档示例中 preset="ultra_fast" 的取值不是魔法字符串,而是硬编码在 inference_with_config 中的一组现成组合。下表汇总全部预设及其覆盖的参数:
| preset | num_autoregressive_samples | diffusion_iterations | sampler / 其他 |
|---|---|---|---|
single_sample |
8 | 10 | ddim |
ultra_fast |
16 | 10 | ddim |
ultra_fast_old |
16 | 30 | cond_free=False |
very_fast |
32 | 30 | dpm++2m |
fast |
5 | 50 | ddim |
fast_old |
96 | 80 | (沿用默认 ddim) |
standard |
5 | 200 | (默认采样器) |
high_quality |
256 | 400 | (默认采样器) |
直观规律:候选样本数越多、扩散步数越多,质量上限越高但耗时越长——从 ultra_fast 的 16 样本/10 步,到 high_quality 的 256 样本/400 步,正是"表现力上限"与"缓慢"这枚硬币的两面。预设仅覆盖采样数量与步数类设置,而 temperature 等仍来自 config;若同时显式传参(如 API 示例里的 num_autoregressive_samples=1, diffusion_iterations=10),显式参数会覆盖预设,因为代码在 settings.update(presets[...]) 之后又执行了 settings.update(kwargs)。
inference() 本身还暴露了一组与 config 默认值略有差异的兜底默认值(如 temperature=0.8、diffusion_iterations=100、max_mel_tokens=500、half=True),但走 synthesize() 正常入口时会被 config 值覆盖,无需担心。需要额外说明的两个参数:
max_mel_tokens:限制输出长度,单位约 1/20 秒,合理区间 (0,600];k:最终返回几条候选(默认为 1,即只保留 CLVP 评分最高的一条);use_deterministic_seed:传入整数可复现同一条音频(deterministic_state会同时种下 torch 与 random,源码)。
八、实践建议与限制清单
- 适用场景:需要高自然度、强情感表现力或 few-shot 声音克隆的场景;对实时性有硬要求的场景应选择 VITS 等并行模型。
- 显存策略:默认
high_vram=False下 CPU/GPU 反复搬运较慢;显存充足(源码按 >7GB/>10GB/>14GB 分级)时建议设high_vram=True并把autoregressive_batch_size交给自动探测。 - 文本长度:单次推理文本 token 数必须 < 400,超长文本需要自行切分(这也与
fix_autoregressive_output中"语音太长找不到 stop token"的警告一致)。 - 音质细节:关闭 cond-free(
cond_free=False)会明显损失真实感,不建议;diffusion_temperature=0会让声音平淡发糊。 - 可复现性:固定
use_deterministic_seed可复现结果;synthesize()返回字典中的deterministic_seed记录了本次实际使用的种子。 - 训练支持:该模型的 forward/训练接口均未实现(仅推理),若需要训练请关注 XTTS 等其他路径。
九、参考资料与致谢
本模型的 🐸TTS 移植工作由 @manmay-nakhashi 完成。Tortoise 本身的设计思路来自社区公开研究:原始 Tortoise 实现及其加速版、UnivNet 声码器(基于位置可变卷积的 GAN 声码器)、Latent Diffusion(以潜在空间为对象的扩散建模)以及 DALL-E 中的对比学习方法(CLVP 即受其启发,代码注释也标注源自 DALLE-pytorch,见 TTS/tts/layers/tortoise/clvp.py)。如需深入,建议按以下顺序阅读仓库源码:先通读 Tortoise 模型入口,再分别研究 autoregressive.py、diffusion.py、diffusion_decoder.py、clvp.py 与 vocoder.py,即可把本文描述的每一环对应到具体实现。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00