# 文本转语音（TTS）微调指南

微调 TTS 模型可以让它们适应你的特定数据集、使用场景，或你想要的风格和语气。目标是定制这些模型，以克隆声音、调整说话风格和语调、支持新语言、处理特定任务等等。我们还支持 **语音转文本（STT）** 模型，例如 OpenAI 的 Whisper。

使用 [Unsloth](https://github.com/unslothai/unsloth)，你可以微调 **任何** TTS 模型（`transformers` 兼容），速度比采用 Flash Attention 2 的其他实现快 1.5 倍，且内存占用减少 50%。

⭐ **Unsloth 支持任何 `transformers` 兼容的 TTS 模型。** 即使我们暂时还没有对应的 notebook 或上传版本，它也仍然受支持，例如：你可以尝试微调 Dia-TTS 或 Moshi。

{% hint style="info" %}
零样本克隆能捕捉语气，但会缺少节奏和表达，往往听起来机械且不自然。微调则能实现更准确、更逼真的声音复现。 [在此阅读更多](#fine-tuning-voice-models-vs.-zero-shot-voice-cloning).
{% endhint %}

### 微调 Notebooks：

我们还把 TTS 模型（原始版和量化版）上传到了我们的 [Hugging Face 页面](https://huggingface.co/collections/unsloth/text-to-speech-tts-models-68007ab12522e96be1e02155).

| [Sesame-CSM (1B)](https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Sesame_CSM_\(1B\)-TTS.ipynb) | [Orpheus-TTS (3B)](https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Orpheus_\(3B\)-TTS.ipynb) | [Whisper Large V3](https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Whisper.ipynb) （STT） |
| ------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| [Spark-TTS (0.5B)](https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Spark_TTS_\(0_5B\).ipynb)   | [Llasa-TTS (1B)](https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Llasa_TTS_\(1B\).ipynb)     | [Oute-TTS (1B)](https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Oute_TTS_\(1B\).ipynb)  |

{% hint style="success" %}
如果你发现输出时长最多达到 10 秒，请将`max_new_tokens = 125` 从默认值 125 提高。由于 125 个 token 对应 10 秒音频，若要更长的输出，就需要设置更高的值。
{% endhint %}

### 选择并加载 TTS 模型

对于 TTS，通常更偏好较小的模型，因为它们对终端用户来说延迟更低、推理更快。微调参数少于 3B 的模型往往最理想，而我们的主要示例使用的是 Sesame-CSM (1B) 和 Orpheus-TTS (3B)，后者是一款基于 Llama 的语音模型。

#### Sesame-CSM (1B) 详情

**CSM-1B** 是基础模型，而 **Orpheus-ft** 是在 8 位专业配音演员的数据上微调得到的，因此声音一致性是两者的关键差异。CSM 需要每个说话人的音频上下文才能表现良好，而 Orpheus-ft 已内置这种一致性。

从像 CSM 这样的基础模型开始微调通常需要更多算力，而直接从像 Orpheus-ft 这样的微调模型开始则能带来更好的开箱即用效果。

为帮助 CSM，我们新增了采样选项，并提供了一个示例，展示如何使用音频上下文来提升声音一致性。

#### Orpheus-TTS (3B) 详情

Orpheus 先在大规模语音语料上预训练，擅长生成逼真的语音，并内置支持笑声、叹息等情绪提示。它的架构使其成为最容易使用和训练的 TTS 模型之一，因为它可以通过 llama.cpp 导出，这意味着它在所有推理引擎上都有很好的兼容性。对于不受支持的模型，你只能保存 LoRA 适配器 safetensors。

#### 加载模型

由于语音模型通常体积较小，你可以使用 LoRA 16 位或完整微调 FFT 来训练模型，这可能会带来更高质量的结果。要以 LoRA 16 位加载：

```python
from unsloth import FastModel

model_name = "unsloth/orpheus-3b-0.1-pretrained"
model, tokenizer = FastModel.from_pretrained(
    model_name,
    load_in_4bit=False  # 使用 4 位精度（QLoRA）
)
```

运行此代码时，Unsloth 会下载模型权重；如果你偏好 8 位，可以使用 `load_in_8bit = True`，或者若要完整微调则设置 `full_finetuning = True` （确保你有足够的 VRAM）。你也可以将模型名称替换为其他 TTS 模型。

{% hint style="info" %}
**注意：** Orpheus 的 tokenizer 已经包含用于音频输出的特殊 token（后面会详细介绍）。你 *不* 需要单独的 vocoder——Orpheus 会直接输出音频 token，这些 token 可以解码成波形。
{% endhint %}

### 准备你的数据集

至少，TTS 微调数据集应包含 **音频片段及其对应的转录文本** （文本）。我们使用 [*Elise* 数据集](https://huggingface.co/datasets/MrDragonFox/Elise) ，它是一个约 3 小时的单说话人英语语音语料。它有两个变体：

* [`MrDragonFox/Elise`](https://huggingface.co/datasets/MrDragonFox/Elise) ——一个增强版，其中将 **情绪标签** （如 \<sigh>、\<laughs>）嵌入到转录中。这些尖括号标签表示表情（大笑、叹息等），并会被 Orpheus 的 tokenizer 视为特殊 token
* [`Jinsaryko/Elise`](https://huggingface.co/datasets/Jinsaryko/Elise) ——基础版本，转录中不含特殊标签。

该数据集按每条样本对应一个音频和一段转录来组织。在 Hugging Face 上，这些数据集通常包含如下字段： `audio` （波形）， `text` （转录文本），以及一些元数据（说话人名称、音高统计等）。我们需要向 Unsloth 提供一个音频-文本对的数据集。

{% hint style="success" %}
与其只关注语气、节奏和音高，不如优先确保你的数据集注释完整并且已正确规范化。
{% endhint %}

{% hint style="info" %}
对于某些模型，例如 **Sesame-CSM-1B**，你可能会注意到使用说话人 ID 0 时，不同生成结果之间存在声音变化，因为它是一个 **基础模型**——它没有固定的声音身份。说话人 ID token 主要有助于保持 **一次对话中的一致性**，而不是在不同生成结果之间保持一致。

要获得一致的声音，请提供 **上下文示例**，例如几个参考音频片段或之前的语句。这有助于模型更可靠地模仿目标声音。没有这些，哪怕使用相同的说话人 ID，出现变化也是正常的。
{% endhint %}

**选项 1：使用 Hugging Face Datasets 库** ——我们可以使用 Hugging Face 的 `datasets` 库加载 Elise 数据集：

```python
from datasets import load_dataset, Audio

# 加载 Elise 数据集（例如带情绪标签的版本）
dataset = load_dataset("MrDragonFox/Elise", split="train")
print(len(dataset), "samples")  # Elise 中大约有 1200 个样本

# 确保所有音频的采样率为 24 kHz（Orpheus 期望的采样率）
dataset = dataset.cast_column("audio", Audio(sampling_rate=24000))
```

这会下载该数据集（约 1.2k 个样本，大小约 328 MB）。其中的每一项 `数据集` 都是一个字典，至少包含：

* `"audio"`：音频片段（波形数组及采样率等元数据），以及
* `"text"`：转录字符串

Orpheus 支持如下标签： `<laugh>`, `<chuckle>`, `<sigh>`, `<cough>`, `<sniffle>`, `<groan>`, `<yawn>`, `<gasp>`等等。例如： `"I missed you <laugh> so much!"`。这些标签用尖括号括起来，模型会将它们视为特殊 token（它们与 [Orpheus 期望的标签](https://github.com/canopyai/Orpheus-TTS) 如 `<laugh>` 和 `<sigh>`相匹配。在训练期间，模型会学会把这些标签与相应的音频模式关联起来。带标签的 Elise 数据集本身已经包含了许多这类标签（例如，在数据卡中列出了 336 处 “laughs”、156 处 “sighs”等）。如果你的数据集缺少这类标签，但你想加入它们，可以在音频中包含这些表达时手动标注转录文本。

**选项 2：准备自定义数据集** ——如果你有自己的音频文件和转录文本：

* 将音频片段（WAV/FLAC 文件）整理到一个文件夹中。
* 创建一个 CSV 或 TSV 文件，包含文件路径和转录文本列。例如：

  ```
  filename,text
  0001.wav,你好！
  0002.wav,<sigh> 我非常累。
  ```
* 使用 `load_dataset("csv", data_files="mydata.csv", split="train")` 来加载它。你可能需要告诉数据集加载器如何处理音频路径。另一种方法是使用 `datasets.Audio` 特性按需加载音频数据：

  ```python
  from datasets import Audio
  dataset = load_dataset("csv", data_files="mydata.csv", split="train")
  dataset = dataset.cast_column("filename", Audio(sampling_rate=24000))
  ```

  然后 `dataset[i]["audio"]` 将包含音频数组。
* **确保转录文本已规范化** （不要使用 tokenizer 可能不认识的异常字符，除非是使用了情绪标签）。还要确保所有音频的采样率一致（如有必要，请重采样到模型期望的目标采样率，例如 Orpheus 的 24kHz）。

总之，对于 **数据集准备**:

* 你需要一个 **（音频，文本）列表** 对。
* 使用 HF `datasets` 库来处理加载以及可选的预处理（如重采样）。
* 在文本中包含任何 **特殊标签** ，即你希望模型学习的内容（确保它们采用 `<angle_brackets>` 格式，这样模型会把它们当作独立 token）。
* （可选）如果是多说话人场景，你可以在文本中加入说话人 ID token，或使用单独的说话人嵌入方案，但这超出了本基础指南的范围（Elise 是单说话人）。

### 使用 Unsloth 微调 TTS

现在，让我们开始微调！我们会用 Python 代码演示（你可以在 Jupyter notebook、Colab 等环境中运行）。

**步骤 1：加载模型和数据集**

在我们所有的 TTS notebooks 中，我们启用 LoRA（16 位）训练，并禁用 QLoRA（4 位）训练，方式如下： `load_in_4bit = False`。这样通常能让模型更好地学习你的数据集，并获得更高的准确率。

```python
from unsloth import FastLanguageModel
import torch
dtype = None # 自动检测。Tesla T4、V100 使用 Float16，Ampere+ 使用 Bfloat16
load_in_4bit = False # 使用 4bit 量化以减少内存占用。可以设为 False。

model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = "unsloth/orpheus-3b-0.1-ft",
    max_seq_length= 2048, # 长上下文可任选！
    dtype = dtype,
    load_in_4bit = load_in_4bit,
    #token = "hf_...", # 如果使用 meta-llama/Llama-2-7b-hf 之类的受限模型，请使用这个
)

from datasets import load_dataset
dataset = load_dataset("MrDragonFox/Elise", split = "train")
```

{% hint style="info" %}
如果内存非常有限，或者数据集很大，你可以流式加载或分块加载。这里，3 小时的音频很容易装入 RAM。如果使用你自己的数据集 CSV，也可以类似地加载。
{% endhint %}

**步骤 2：进阶 - 为训练预处理数据（可选）**

我们需要为 Trainer 准备输入。对于文本转语音，一种方法是以因果方式训练模型：将文本和音频 token ID 连接为目标序列。不过，由于 Orpheus 是一个只解码器的 LLM，会输出音频，我们可以把文本作为输入（上下文），把音频 token id 作为标签。实际上，如果模型配置将其识别为文本转语音，Unsloth 的集成可能会自动完成这一步。如果没有，我们可以这样做：

```python
# 对文本转录进行分词
def preprocess_function(example):
    # 对文本分词（保留 <laugh> 之类的特殊 token 不变）
    tokens = tokenizer(example["text"], return_tensors="pt")
    # 展平为 token ID 列表
    input_ids = tokens["input_ids"].squeeze(0)
    # 模型会在这些文本 token 后生成音频 token。
    # 训练时，我们可以将 labels 设为 input_ids（这样它会学习预测下一个 token）。
    # 但这只涵盖了文本 token 预测下一个文本 token（它可能是音频 token 或结束符）。
    # 更复杂的方法：附加一个表示音频开始的特殊 token，然后让模型生成其余部分。
    # 为简单起见，使用与输入相同的 labels（模型会学习根据自身输出序列）。
    return {"input_ids": input_ids, "labels": input_ids}

train_data = dataset.map(preprocess_function, remove_columns=dataset.column_names)
```

{% hint style="info" %}
上述方法只是简化版。实际上，要正确微调 Orpheus，你需要把 *音频 token 作为训练标签的一部分*。Orpheus 的预训练很可能是把音频转换为离散 token（通过音频 codec），并训练模型根据前面的文本预测这些 token。对于新的声音数据微调，你同样需要为每个音频片段获取音频 token（使用 Orpheus 的音频 codec）。Orpheus 的 GitHub 提供了一个数据处理脚本——它会把音频编码为 `<custom_token_x>` token 序列。
{% endhint %}

不过， **Unsloth 可能会把这些细节抽象掉**：如果模型是一个 FastModel，并且关联了一个知道如何处理音频的 processor，那么它可能会自动把数据集中的音频编码为 token。如果没有，你就必须手动把每个音频片段编码成 token ID（使用 Orpheus 的 codebook）。这一步超出了本指南的基础范围，但请记住，单纯使用文本 token 并不能教会模型真实的音频——它必须与音频模式相匹配。

我们假设 Unsloth 提供了一种直接输入音频的方法（例如，通过设置 `processor` 并传入音频数组）。如果 Unsloth 还不支持自动音频分词，你可能需要使用 Orpheus 仓库中的 `encode_audio` 函数获取音频的 token 序列，然后把它们作为标签。（数据集条目确实包含 `phonemes` 以及一些声学特征，这表明它有一条处理流程。）

**步骤 3：设置训练参数和 Trainer**

```python
from transformers import TrainingArguments,Trainer,DataCollatorForSeq2Seq
from unsloth import is_bfloat16_supported

trainer = Trainer(
    model = model,
    train_dataset = dataset,
    args = TrainingArguments(
        per_device_train_batch_size = 1,
        gradient_accumulation_steps = 4,
        warmup_steps = 5,
        # num_train_epochs = 1, # 设置这个即可进行一次完整训练。
        max_steps = 60,
        learning_rate = 2e-4,
        fp16 = not is_bfloat16_supported(),
        bf16 = is_bfloat16_supported(),
        logging_steps = 1,
        optim = "adamw_8bit",
        weight_decay = 0.01,
        lr_scheduler_type = "linear",
        seed = 3407,
        output_dir = "outputs",
        report_to = "none", # WandB 等可用这个
    ),
)
```

我们做 60 步是为了加快速度，但你可以设置 `num_train_epochs=1` 来完整运行一次，并关闭 `max_steps=None`。如果是多 GPU 配置，使用 per\_device\_train\_batch\_size >1 可能会导致错误；为避免问题，请确保将 CUDA\_VISIBLE\_DEVICES 设为单个 GPU（例如，CUDA\_VISIBLE\_DEVICES=0）。按需调整。

**步骤 4：开始微调**

这将开始训练循环。你应该会每 50 步看到一次 loss 日志（如 `logging_steps`所设置）。训练可能需要一些时间，取决于 GPU——例如，在 Colab T4 GPU 上，3 小时数据跑几个 epoch 可能需要 1-2 小时。Unsloth 的优化会让它比标准 HF 训练更快。

**步骤 5：保存微调后的模型**

训练完成后（或者当你觉得已经足够时中途停止），保存模型。这里保存的只是 LoRA 适配器，而不是完整模型。若要保存为 16bit 或 GGUF，请继续向下看！

```python
model.save_pretrained("lora_model")  # 本地保存
tokenizer.save_pretrained("lora_model")
# model.push_to_hub("your_name/lora_model", token = "...") # 在线保存
# tokenizer.push_to_hub("your_name/lora_model", token = "...") # 在线保存
```

这会保存模型权重（对于 LoRA，如果基础模型没有完全微调，可能只保存适配器权重）。如果你在 `--push_model` 中使用 CLI 或 `trainer.push_to_hub()`，你可以直接把它上传到 Hugging Face Hub。

现在你应该已经在该目录下拥有一个微调好的 TTS 模型了。下一步是测试它，如果支持的话，你可以使用 llama.cpp 将其转换为 GGUF 文件。

### 微调语音模型 vs. 零样本语音克隆

人们常说，只需 30 秒音频，就能用像 XTTS 这样的模型克隆声音——无需训练。从技术上讲，这是真的，但这并没有抓住重点。

零样本语音克隆，在 Orpheus 和 CSM 等模型中也可用，本质上是一种近似。它能捕捉说话人声音的整体 **语气和音色** ，但无法完整再现其表达范围。你会丢失诸如说话速度、措辞、发声习惯，以及韵律细微差别等细节——这些才构成了一种声音的 **个性和独特性**.

如果你只想要一个不同的声音，并且可以接受相同的表达方式，那么零样本通常已经足够了。但语音仍然会遵循 **模型的风格**，而不是说话者的风格。

对于任何更个性化或更富表现力的需求，你都需要使用 LoRA 等方法进行训练，才能真正捕捉一个人的说话方式。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://unsloth.ai/docs/zh/ji-chu/text-to-speech-tts-fine-tuning.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
