模型简介
模型特点
模型能力
使用案例
库名称: transformers 标签:
- 语音克隆
- 音频
- unsloth
- orpheus
- snac
- hypaai
- lora
- 合并模型 许可证: apache-2.0 数据集:
- hypaai/Hypa_Fleurs
- MrDragonFox/Elise
- canopylabs/zac-sample-dataset
- google/fleurs 语言:
- 伊博语
- 约鲁巴语
- 豪萨语
- 英语 基础模型:
- canopylabs/orpheus-3b-0.1-ft 管道标签: 文本转语音
Hypa_Orpheus-3b-0.1-ft (16位合并版)
这是经过16位量化和合并、内存优化的canopylabs/orpheus-3b-0.1-ft
微调版本,采用Unsloth和LoRA技术优化,专为富有表现力的多语言文本转语音设计,尤其适用于非洲低资源语言。本模型提供以下能力:
- 文本转语音生成
- 为代表性不足的口音提供语音合成
- 语音克隆与情感合成
- 多语言低资源语音AI研究
模型详情
模型概述
本模型基于超过300小时(7.5万样本)的尼日利亚口音和低资源语言(伊博语、约鲁巴语、豪萨语)平行文本-语音数据集训练。关键数据集来自AfroVoices对真实YouTube数据的转录(标记为随机说话人,约100+小时)。为避免灾难性遗忘并增强多语言能力,我们加入了使用原始8种Orpheus语音通过默认情感提示生成的合成语音-文本数据。
最终训练集还包含新说话人如:
- Eniola(40小时) - 女性,清晰有力
- Moyo(40小时) - 女性,专业清晰
- Lovelyn(35小时) - 女性,温暖腼腆
- Precious(30小时) - 女性,友好温和
本模型在非洲低资源多语言TTS任务上达到最先进性能(参见下方训练数据)
基础模型详情
Canopy Labs
发布的默认Orpheus-TTS模型支持以下语音和情感:
语音: tara
, leah
, jess
, leo
, dan
, mia
, zac
, 和 zoe
。
情感: <laugh>
, <chuckle>
, <sigh>
, <cough>
, <sniffle>
, <groan>
, <yawn>
, 和 <gasp>
。
通过合成数据生成和添加,我们的微调模型也保留了这些语音和情感。更多语音情感信息请访问基础模型卡片。
模型生成样本
🎧 试听Hypa Orpheus-TTS生成的样本
文本输入 | 音频输出 | 语言 | 语音 |
---|---|---|---|
我明天要为客人做饭,需要知道如何制作蒜泥蛋黄酱。你能给我一个分步食谱吗。 | 英语 | Emmanuel | |
Ina dafa abinci don bakin gobe kuma ina bukatar sanin yadda ake yin ailoli. Za ka iya ba ni girke-gireken matakan daya bayan daya? | 豪萨语 | Emmanuel | |
Ina dafa abinci don bakin gobe kuma ina bukatar sanin yadda ake yin ailoli. Za ka iya ba ni girke-gireken matakan daya bayan daya? | 豪萨语 | Eniola | |
Èmi máa se oúnjẹ fún àwọn àlejò l'ọ́la mo sì nílò láti mọ bí wọn ti ńṣe aioli. Ṣe o lè fún mi ni àwọn ìlànà ìdáná ẹlẹ́sẹẹsẹ? | 约鲁巴语 | Eniola | |
我明天要为客人做饭,需要知道如何制作蒜泥蛋黄酱。你能给我一个分步食谱吗。 | 英语 | Eniola | |
M na-esi nri maka ndị ọbịa echi ma achọ ịmata otú esi esi aioli. Ị nwere ike inye m usoro ntụziaka? | 伊博语 | Eniola | |
M na-esi nri maka ndị ọbịa echi ma achọ ịmata otú esi esi aioli. Ị nwere ike inye m usoro ntụziaka? | 伊博语 | Lovelyn | |
我明天要为客人做饭,需要知道如何制作蒜泥蛋黄酱。你能给我一个分步食谱吗。 | 英语 | Lovelyn |
训练详情
训练概述
- 基础模型: canopylabs/orpheus-3b-0.1-ft
- 训练引擎: Unsloth + LoRA
- LoRA配置: r=1024, alpha=1024, dropout=0.0, 全注意力+FFN适配
- 量化: 训练时4位(bnb);最终模型内存效率极高
- 总步数: 18,014 (1轮)
- 批次大小: 1 × 4 (梯度累积)
- GPU: A100 40GB (最大使用55%显存)
步数 | 训练损失 | 验证损失 |
---|---|---|
5,000 | 3.9496 | 3.8790 |
10,000 | 3.8863 | 3.79497 |
15,000 | 3.8544 | 3.75323 |
数据集概述
- 数据来源:
- ✅ 人工对齐的YouTube转录(随机)
- ✅ Orpheus TTS生成的合成语音
- ✅ 非洲英语、伊博语、约鲁巴语、豪萨语的平行文本-音频对
- 总时长: 300+小时(多口音)
- 关键说话人: 45+独特声音(见下方说话人分布图)
我们计划类似Hypa_Fleurs项目开源完整数据集。
许可与引用
本模型基于开源许可证(apache-2.0)发布。完整细节请参阅LICENSE文件。
在您的工作中使用本模型时,请同时引用本模型和基础模型canopylabs/orpheus-3b-0.1-ft
:
@misc{canopylabsorpheus,
title={Orpheus-3b-0.1-ft: 多语言文本转语音模型},
author={Canopy Labs},
year={2025},
publisher={Hugging Face},
howpublished={\url{https://huggingface.co/canopylabs/orpheus-3b-0.1-ft}},
note={Orpheus的TTS表达微调版}
}
@misc{hypaorpheus4bit,
title={Hypa_Orpheus-3b-0.1-ft (LoRA-4bit)},
author={Hypa AI},
year={2025},
note={非洲语言微调的Orpheus TTS},
url={https://huggingface.co/hypaai/Hypa_Orpheus-3b-0.1-ft-unsloth-bnb-4bit}
}
致谢
- Canopy Labs团队: 创建基础模型并开源
- AfroVoices专家: 提供翻译专业知识和高质量数据集
- 社区支持: 感谢所有支持者、贡献者和用户
联系与贡献
如有任何问题、建议或贡献,请在本仓库提交issue或联系hypa.ai.ng@gmail.com。欢迎贡献!
结语
通过发布Hypa_Orpheus,我们希望赋能非洲语言多语言语音技术的研究与发展。
Hypa AI始终致力于开发不仅技术先进而且具有文化敏感性的智能解决方案,确保AI的未来像它所服务的世界一样多元包容。
AfroVoices作为Hypa AI的子公司,致力于在智能时代放大非洲声音、语言和文化。专注于弥合数字表征差距,AfroVoices为非洲语言策划数据集和资源,促进AI技术的包容性和文化欣赏。其使命超越技术创新,旨在全球舞台上颂扬非洲语言多样性的丰富性。
使用指南
Unsloth推理
安装所需包。
%%capture
import os
if "COLAB_" not in "".join(os.environ.keys()):
!pip install unsloth
else:
# 仅在Colab笔记本中执行! 其他环境使用pip install unsloth
!pip install --no-deps bitsandbytes accelerate xformers==0.0.29.post3 peft trl==0.15.2 triton cut_cross_entropy unsloth_zoo
!pip install sentencepiece protobuf datasets huggingface_hub hf_transfer
!pip install --no-deps unsloth
!pip install snac
下载模型(SNAC编码器/解码器和我们微调的Hypa_Orpheus)。
import torch
from snac import SNAC
from unsloth import FastLanguageModel
dtype = None # None自动检测。Tesla T4/V100用Float16,Ampere+用Bfloat16
load_in_4bit = True # 使用4位量化减少内存占用。可设为False。
model, tokenizer = FastLanguageModel.from_pretrained(
model_name = "hypaai/Hypa_Orpheus-3b-0.1-ft-unsloth-merged_16bit",
max_seq_length= 2048, # 长上下文可任意设置
dtype = dtype,
load_in_4bit = load_in_4bit,
#token = "hf_...", # 使用meta-llama/Llama-2-7b-hf等门控模型时需要
)
snac_model = SNAC.from_pretrained("hubertsiuzdak/snac_24khz")
snac_model = snac_model.to("cuda")
创建文本提示,选择语音,传入模型。
prompts = [
"""Mo nífẹ̀ẹ́sí láti ṣe Ph.D sùgbọ́n mi ò ì tíì pinnu ẹ̀ka tí màá ṣe. Àwọn anfaani tí óń dé oríṣiríṣi àwọn olùgbọ́ káàkiri àgbáyé wo ni mo ní""",
]
chosen_voice = "Eniola" # 单说话人设为None
FastLanguageModel.for_inference(model) # 启用原生2倍速推理
snac_model.to("cpu")# 将snac_model从cuda移至cpu
prompts_ = [(f"{chosen_voice}: " + p) if chosen_voice else p for p in prompts]
all_input_ids = []
for prompt in prompts_:
input_ids = tokenizer(prompt, return_tensors="pt").input_ids
all_input_ids.append(input_ids)
start_token = torch.tensor([[ 128259]], dtype=torch.int64) # 人类开始标记
end_tokens = torch.tensor([[128009, 128260]], dtype=torch.int64) # 文本结束, 人类结束标记
all_modified_input_ids = []
for input_ids in all_input_ids:
modified_input_ids = torch.cat([start_token, input_ids, end_tokens], dim=1) # SOH SOT文本EOT EOH
all_modified_input_ids.append(modified_input_ids)
all_padded_tensors = []
all_attention_masks = []
max_length = max([modified_input_ids.shape[1] for modified_input_ids in all_modified_input_ids])
for modified_input_ids in all_modified_input_ids:
padding = max_length - modified_input_ids.shape[1]
padded_tensor = torch.cat([torch.full((1, padding), 128263, dtype=torch.int64), modified_input_ids], dim=1)
attention_mask = torch.cat([torch.zeros((1, padding), dtype=torch.int64), torch.ones((1, modified_input_ids.shape[1]), dtype=torch.int64)], dim=1)
all_padded_tensors.append(padded_tensor)
all_attention_masks.append(attention_mask)
all_padded_tensors = torch.cat(all_padded_tensors, dim=0)
all_attention_masks = torch.cat(all_attention_masks, dim=0)
input_ids = all_padded_tensors.to("cuda")
attention_mask = all_attention_masks.to("cuda")
generated_ids = model.generate(
input_ids=input_ids,
attention_mask=attention_mask,
max_new_tokens=1200,
do_sample=True,
temperature=0.6,
top_p=0.95,
repetition_penalty=1.1,
num_return_sequences=1,
eos_token_id=128258,
use_cache = True
)
token_to_find = 128257
token_to_remove = 128258
token_indices = (generated_ids == token_to_find).nonzero(as_tuple=True)
if len(token_indices[1]) > 0:
last_occurrence_idx = token_indices[1][-1].item()
cropped_tensor = generated_ids[:, last_occurrence_idx+1:]
else:
cropped_tensor = generated_ids
mask = cropped_tensor != token_to_remove
processed_rows = []
for row in cropped_tensor:
masked_row = row[row != token_to_remove]
processed_rows.append(masked_row)
code_lists = []
for row in processed_rows:
row_length = row.size(0)
new_length = (row_length // 7) * 7
trimmed_row = row[:new_length]
trimmed_row = [t - 128266 for t in trimmed_row]
code_lists.append(trimmed_row)
def redistribute_codes(code_list):
layer_1 = []
layer_2 = []
layer_3 = []
for i in range((len(code_list)+1)//7):
layer_1.append(code_list[7*i])
layer_2.append(code_list[7*i+1]-4096)
layer_3.append(code_list[7*i+2]-(2*4096))
layer_3.append(code_list[7*i+3]-(3*4096))
layer_2.append(code_list[7*i+4]-(4*4096))
layer_3.append(code_list[7*i+5]-(5*4096))
layer_3.append(code_list[7*i+6]-(6*4096))
codes = [torch.tensor(layer_1).unsqueeze(0),
torch.tensor(layer_2).unsqueeze(0),
torch.tensor(layer_3).unsqueeze(0)]
# codes = [c.to("cuda") for c in codes]
audio_hat = snac_model.decode(codes)
return audio_hat
my_samples = []
for code_list in code_lists:
samples = redistribute_codes(code_list)
my_samples.append(samples)
from IPython.display import display, Audio
if len(prompts) != len(my_samples):
raise Exception("提示与样本数量不匹配")
else:
for i in range(len(my_samples)):
print(prompts[i])
samples = my_samples[i]
display(Audio(samples.detach().squeeze().to("cpu").numpy(), rate=24000))
# 清理以节省RAM
del my_samples,samples
标准推理
安装所需包。
%%capture
!pip install snac ipywebrtc
下载模型(SNAC和Hypa_Orpheus)
import torch
from transformers import AutoModelForCausalLM, Trainer, TrainingArguments, AutoTokenizer
from snac import SNAC
# 加载预训练SNAC模型并移至CPU
snac_model = SNAC.from_pretrained("hubertsiuzdak/snac_24khz")
snac_model = snac_model #.to("cpu")
print("我们已将编码器/解码器模型加载到cpu,如需更快推理可使用gpu")
# 加载Orpheus模型和分词器,将模型移至GPU加速推理
model_name = "hypaai/Hypa_Orpheus-3b-0.1-ft-unsloth-merged_16bit"
model = AutoModelForCausalLM.from_pretrained(model_name, torch_dtype=torch.bfloat16)
model.cuda()
tokenizer = AutoTokenizer.from_pretrained(model_name)
创建提示并选择语音和情感。
# Orpheus-TTS支持的语音列表
voices = [
"Eniola", "tara", # 女性,对话式,清晰
"Moyo", "leah", # 女性,温暖,温和
"Gift", "jess", # 女性,活力,年轻
"Prince", "leo", # 男性,权威,深沉
"Emmanuel", "dan", # 男性,友好,随意
"Cynthia", "mia", # 女性,专业,清晰
"Kolade", "zac", # 男性,热情,动态
"Lovelyn", "zoe" # 女性,平静,舒缓
]
# Orpheus-TTS支持的情感标签
emotions = [
"<laugh>", # 笑声
"<chuckle>", # 轻笑
"<sigh>", # 叹气
"<cough>", # 咳嗽
"<sniffle>", # 抽泣
"<groan>", # 呻吟
"<yawn>", # 哈欠
"<gasp>" # 喘息
]
# 创建提示
prompts = [
"嘿,我是Eniola 9000,一个能像真人一样说话的语音生成模型。",
# "我还学会了理解和产生副语言特征,比如叹气、轻笑或打哈欠!",
# "我住在旧金山,有,呃,让我看看,37亿...好吧,就说有很多参数吧。",
]
chosen_voice = "Eniola" # "tara" # 其他语音见github
prompts = [f"{chosen_voice}: " + p for p in prompts] # 批量创建提示
print(prompts)
将提示标记化为inputIDs,填充并创建注意力掩码。
# 将每个提示标记化为输入ID
all_input_ids = []
for prompt in prompts:
input_ids = tokenizer(prompt, return_tensors="pt").input_ids
all_input_ids.append(input_ids)
# 添加特殊标记表示提示开始和结束
start_token = torch.tensor([[128259]], dtype=torch.int64) # 人类开始
end_tokens = torch.tensor([[128009, 128260]], dtype=torch.int64) # 文本结束, 人类结束
all_modified_input_ids = []
for input_ids in all_input_ids:
modified_input_ids = torch.cat([start_token, input_ids, end_tokens], dim=1) # SOH SOT文本EOT EOH
all_modified_input_ids.append(modified_input_ids)
# 将所有序列填充至相同长度并创建对应注意力掩码
all_padded_tensors = []
all_attention_masks = []
max_length = max([modified_input_ids.shape[1] for modified_input_ids in all_modified_input_ids])
for modified_input_ids in all_modified_input_ids:
padding = max_length - modified_input_ids.shape[1]
# 左填充
padded_tensor = torch.cat([torch.full((1, padding), 128263, dtype=torch.int64), modified_input_ids], dim=1)
attention_mask = torch.cat([torch.zeros((1, padding), dtype=torch.int64), torch.ones((1, modified_input_ids.shape[1]), dtype=torch.int64)], dim=1)
all_padded_tensors.append(padded_tensor)
all_attention_masks.append(attention_mask)
all_padded_tensors = torch.cat(all_padded_tensors, dim=0)
all_attention_masks = torch.cat(all_attention_masks, dim=0)
# 将所有填充序列移至GPU加速计算
input_ids = all_padded_tensors.to("cuda")
attention_mask = all_attention_masks.to("cuda")
从模型生成输出标记并解析为语音
print("*** Model.generate速度较慢 - 实时流式推理请见github上的vllm实现")
print("*** 增减推理参数可获得更富表现力但稳定性较低的生成")
# 生成输出标记
with torch.no_grad():
generated_ids = model.generate(
input_ids=input_ids,
attention_mask=attention_mask,
max_new_tokens=1200,
do_sample=True,
temperature=0.6,
top_p=0.95,
repetition_penalty=1.1,
num_return_sequences=1,
eos_token_id=128258,
)
# 处理生成标记(将输出解析为语音)
token_to_find = 128257 # 音频开始标记(相关输出)
token_to_remove = 128258 # 结束/终止标记(音频结束/相关输出)
token_indices = (generated_ids == token_to_find).nonzero(as_tuple=True)
print(token_indices)
# 切片张量排除不需要的标记
if len(token_indices[1]) > 0:
last_occurrence_idx = token_indices[1][-1].item()
cropped_tensor = generated_ids[:, last_occurrence_idx+1:]
else:
cropped_tensor = generated_ids
# mask = cropped_tensor != token_to_remove
# 存储清理后的标记序列
processed_rows = []
for row in cropped_tensor:
masked_row = row[row != token_to_remove]
processed_rows.append(masked_row)
# 通过修剪和调整标记值准备(音频编码)标记序列用于音频解码
code_lists = []
for row in processed_rows:
row_length = row.size(0) # 确定标记序列长度
new_length = (row_length // 7) * 7 # 确保序列长度是7的倍数,解码器要求
trimmed_row = row[:new_length]
trimmed_row = [t - 128266 for t in trimmed_row] # 调整标记值匹配解码器预期输入范围
code_lists.append(trimmed_row)
用SNAC解码器解码输出
# 将标记序列处理为SNAC解码器预期的格式
def redistribute_codes(code_list):
"""将扁平化的标记列表重组为三个独立层级,调整每个标记值以对齐解码器预期"""
layer_1 = [] # 最粗粒度层
layer_2 = [] # 中间层
layer_3 = [] # 最细粒度层
num_groups = (len(code_list) + 1) // 7 #计算code_list中完整7标记组的数量
for i in range(num_groups):
idx = 7 * i # 当前组的起始索引
# 第1层接收组的第一个标记
layer_1.append(code_list[idx])
# 第2层接收第二个标记,减去4096调整
layer_2.append(code_list[idx + 1] - 4096)
# 第3层接收第三和第四个标记,分别减去8192和12288调整
layer_3.append(code_list[idx+2]-(2*4096))
layer_3.append(code_list[idx+3]-(3*4096))
# 第2层接收第五个标记,减去16384调整
layer_2.append(code_list[idx+4]-(4*4096))
# 第3层接收第六和第七个标记,分别减去20480和24576调整
layer_3.append(code_list[idx+5]-(5*4096))
layer_3.append(code_list[idx+6]-(6*4096))
codes = [
torch.tensor(layer_1).unsqueeze(0), # 形状: (1, len(layer_1))
torch.tensor(layer_2).unsqueeze(0), # 形状: (1, len(layer_2))
torch.tensor(layer_3).unsqueeze(0) # 形状: (1, len(layer_3))
] # 将列表转为PyTorch张量并添加批次维度
audio_hat = snac_model.decode(codes) # 使用SNAC模型将结构化编码解码为音频波形
return audio_hat
my_samples = []
for code_list in code_lists:
samples = redistribute_codes(code_list) # 从处理后的标记序列生成音频样本
my_samples.append(samples)
# 展示音频
from IPython.display import display, Audio
if len(prompts) != len(my_samples):
raise Exception("提示与样本数量不匹配")
else:
for i in range(len(my_samples)):
print(prompts[i])
samples = my_samples[i]
display(Audio(samples.detach().squeeze().to("cpu").numpy(), rate=24000))
- 仓库: [暂无]
- 论文: [暂无]
- 演示: [暂无]
这个基于llama的模型使用Unsloth和Huggingface的TRL库训练速度提升了2倍。




