別“機械音”:Maya1讓AI語音也能觸動你的心弦
在人工智能領域,語音合成技術一直是研究熱點之一。如今,Maya Research團隊推出了一款開源的AI語音合成模型——Maya1。它專為生成富有情感的語音而設計,通過自然語言描述來定義聲音特征,支持多種情緒表達,為語音交互帶來了全新的體驗。
一、項目概述
Maya1 是一個開源的AI語音合成模型,基于30億參數的Transformer架構和SNAC神經編解碼器,能夠通過自然語言描述生成具有特定情感和特征的語音。它支持20多種情緒表達,如大笑、哭泣、嘆氣等,并且可以實時流式傳輸音頻,適用于游戲配音、播客制作、語音助手開發等多種場景。
二、核心功能
(一)自然語言聲音設計
用戶可以通過簡單的自然語言描述(如“30歲美國女性,聲音溫柔,語氣真誠”)定義聲音特征,無需復雜的參數調整。這種設計方式極大地降低了聲音設計的門檻,使得非專業人士也能輕松創建符合需求的聲音。
(二)豐富的情緒表達
Maya1 支持20多種情緒表達,如大笑(laugh)、哭泣(cry)、嘆氣(sigh)等。通過在文本中添加情緒標簽(如`<laugh>`),用戶可以精準地控制語音中的情緒表達,讓語音更具表現力和感染力。
(三)實時流式傳輸
采用SNAC神經編解碼器,Maya1 能夠支持低延遲(約100毫秒)的實時音頻生成。這一特性使其非常適合語音助手、游戲對話等需要即時反饋的場景,能夠為用戶提供流暢的語音交互體驗。
(四)高效部署
基于30億參數的輕量級Transformer架構,Maya1 可以在單GPU上運行。同時,它支持vLLM推理框架,能夠有效降低推理成本,適合高并發場景,為企業和個人開發者提供了高效、經濟的部署選擇。
三、技術揭秘
(一)架構
Maya1 基于30億參數的Transformer架構,類似于Llama。它通過生成SNAC編解碼器的音頻token序列,而不是直接生成波形,從而實現了高效的語音合成。
(二)SNAC編解碼器
SNAC編解碼器通過多尺度分層壓縮(約12Hz/23Hz/47Hz),將音頻高效編碼為7-token幀。這種編碼方式不僅降低了碼率(約0.98kbps),還保證了音頻的高質量輸出。
(三)訓練過程
Maya1 的預訓練使用了大規模英文語音數據,涵蓋了多種口音和語速。基于錄音棚級語音樣本,標注了20多種情緒和身份標簽,使得模型能夠生成具有豐富情感和多樣特征的語音。
(四)聲音描述
Maya1 采用XML屬性式自然語言描述(如`<descriptinotallow="...">`),避免模型將描述內容“念”出來,從而確保了語音合成的自然性和準確性。
(五)推理優化
支持vLLM引擎集成,結合自動前綴緩存(APC)機制,顯著降低了重復生成的計算成本。同時,兼容WebAudio環形緩沖,便于瀏覽器端實時播放,進一步提升了模型的實用性和靈活性。
四、應用場景
(一)游戲開發
在游戲開發中,Maya1 可以為游戲角色生成帶情緒的對話,增強游戲的沉浸感。例如,讓NPC在對話中帶有冷笑或憤怒情緒,使玩家更容易投入到游戲情節中。
(二)播客與有聲書
對于播客和有聲書制作,Maya1 能夠自動配音,支持多角色對話和情感表達。這不僅可以節省專業配音演員的成本,還能提升內容的吸引力,為聽眾帶來更加豐富的情感體驗。
(三)AI語音助手
通過打造自然、富有情感的語音交互體驗,Maya1 讓語音助手在回應時能表達同情、喜悅等情緒,從而更好地滿足用戶的情感需求,提高用戶對語音助手的滿意度和依賴度。
(四)短視頻創作
在短視頻創作中,Maya1 可以快速生成帶情緒的旁白,提升視頻的表達力和觀眾的沉浸感,幫助創作者更好地傳達視頻內容和情感。
(五)無障礙應用
Maya1 還可以用于無障礙應用,讓屏幕閱讀器用溫暖、自然的聲音幫助視障人士更好地理解內容,為視障人士提供更加人性化和便捷的信息獲取方式。
五、快速使用
(一)模型下載
從Hugging Face模型庫中下載Maya1模型。
Make sure git-lfs is installed (https://git-lfs.com)
git lfs install
git clone https://huggingface.co/maya-research/maya1(三)使用示例
#!/usr/bin/env python3
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from snac import SNAC
import soundfile as sf
import numpy as np
CODE_START_TOKEN_ID = 128257
CODE_END_TOKEN_ID = 128258
CODE_TOKEN_OFFSET = 128266
SNAC_MIN_ID = 128266
SNAC_MAX_ID = 156937
SNAC_TOKENS_PER_FRAME = 7
SOH_ID = 128259
EOH_ID = 128260
SOA_ID = 128261
BOS_ID = 128000
TEXT_EOT_ID = 128009
def build_prompt(tokenizer, description: str, text: str) -> str:
"""Build formatted prompt for Maya1."""
soh_token = tokenizer.decode([SOH_ID])
eoh_token = tokenizer.decode([EOH_ID])
soa_token = tokenizer.decode([SOA_ID])
sos_token = tokenizer.decode([CODE_START_TOKEN_ID])
eot_token = tokenizer.decode([TEXT_EOT_ID])
bos_token = tokenizer.bos_token
formatted_text = f'<descriptinotallow="{description}"> {text}'
prompt = (
soh_token + bos_token + formatted_text + eot_token +
eoh_token + soa_token + sos_token
)
return prompt
def extract_snac_codes(token_ids: list) -> list:
"""Extract SNAC codes from generated tokens."""
try:
eos_idx = token_ids.index(CODE_END_TOKEN_ID)
except ValueError:
eos_idx = len(token_ids)
snac_codes = [
token_id for token_id in token_ids[:eos_idx]
if SNAC_MIN_ID <= token_id <= SNAC_MAX_ID
]
return snac_codes
def unpack_snac_from_7(snac_tokens: list) -> list:
"""Unpack 7-token SNAC frames to 3 hierarchical levels."""
if snac_tokens and snac_tokens[-1] == CODE_END_TOKEN_ID:
snac_tokens = snac_tokens[:-1]
frames = len(snac_tokens) // SNAC_TOKENS_PER_FRAME
snac_tokens = snac_tokens[:frames * SNAC_TOKENS_PER_FRAME]
if frames == 0:
return [[], [], []]
l1, l2, l3 = [], [], []
for i in range(frames):
slots = snac_tokens[i*7:(i+1)*7]
l1.append((slots[0] - CODE_TOKEN_OFFSET) % 4096)
l2.extend([
(slots[1] - CODE_TOKEN_OFFSET) % 4096,
(slots[4] - CODE_TOKEN_OFFSET) % 4096,
])
l3.extend([
(slots[2] - CODE_TOKEN_OFFSET) % 4096,
(slots[3] - CODE_TOKEN_OFFSET) % 4096,
(slots[5] - CODE_TOKEN_OFFSET) % 4096,
(slots[6] - CODE_TOKEN_OFFSET) % 4096,
])
return [l1, l2, l3]
def main():
Load the best open source voice AI model
print("\n[1/3] Loading Maya1 model...")
model = AutoModelForCausalLM.from_pretrained(
"maya-research/maya1",
torch_dtype=torch.bfloat16,
device_map="auto",
trust_remote_code=True
)
tokenizer = AutoTokenizer.from_pretrained(
"maya-research/maya1",
trust_remote_code=True
)
print(f"Model loaded: {len(tokenizer)} tokens in vocabulary")
Load SNAC audio decoder (24kHz)
print("\n[2/3] Loading SNAC audio decoder...")
snac_model = SNAC.from_pretrained("hubertsiuzdak/snac_24khz").eval()
if torch.cuda.is_available():
snac_model = snac_model.to("cuda")
print("SNAC decoder loaded")
Design your voice with natural language
description = "Realistic male voice in the 30s age with american accent. Normal pitch, warm timbre, conversational pacing."
text = "Hello! This is Maya1 <laugh_harder> the best open source voice AI model with emotions."
print("\n[3/3] Generating speech...")
print(f"Description: {description}")
print(f"Text: {text}")
Create prompt with proper formatting
prompt = build_prompt(tokenizer, description, text)
Debug: Show prompt details
print(f"\nPrompt preview (first 200 chars):")
print(f" {repr(prompt[:200])}")
print(f" Prompt length: {len(prompt)} chars")
Generate emotional speech
inputs = tokenizer(prompt, return_tensors="pt")
print(f" Input token count: {inputs['input_ids'].shape[1]} tokens")
if torch.cuda.is_available():
inputs = {k: v.to("cuda") for k, v in inputs.items()}
with torch.inference_mode():
outputs = model.generate(
**inputs,
max_new_tokens=2048, Increase to let model finish naturally
min_new_tokens=28, At least 4 SNAC frames
temperature=0.4,
top_p=0.9,
repetition_penalty=1.1, Prevent loops
do_sample=True,
eos_token_id=CODE_END_TOKEN_ID, Stop at end of speech token
pad_token_id=tokenizer.pad_token_id,
)
Extract generated tokens (everything after the input prompt)
generated_ids = outputs[0, inputs['input_ids'].shape[1]:].tolist()
print(f"Generated {len(generated_ids)} tokens")
Debug: Check what tokens we got
print(f" First 20 tokens: {generated_ids[:20]}")
print(f" Last 20 tokens: {generated_ids[-20:]}")
Check if EOS was generated
if CODE_END_TOKEN_ID in generated_ids:
eos_position = generated_ids.index(CODE_END_TOKEN_ID)
print(f" EOS token found at position {eos_position}/{len(generated_ids)}")
Extract SNAC audio tokens
snac_tokens = extract_snac_codes(generated_ids)
print(f"Extracted {len(snac_tokens)} SNAC tokens")
Debug: Analyze token types
snac_count = sum(1 for t in generated_ids if SNAC_MIN_ID <= t <= SNAC_MAX_ID)
other_count = sum(1 for t in generated_ids if t < SNAC_MIN_ID or t > SNAC_MAX_ID)
print(f" SNAC tokens in output: {snac_count}")
print(f" Other tokens in output: {other_count}")
Check for SOS token
if CODE_START_TOKEN_ID in generated_ids:
sos_pos = generated_ids.index(CODE_START_TOKEN_ID)
print(f" SOS token at position: {sos_pos}")
else:
print(f" No SOS token found in generated output!")
if len(snac_tokens) < 7:
print("Error: Not enough SNAC tokens generated")
return
Unpack SNAC tokens to 3 hierarchical levels
levels = unpack_snac_from_7(snac_tokens)
frames = len(levels[0])
print(f"Unpacked to {frames} frames")
print(f" L1: {len(levels[0])} codes")
print(f" L2: {len(levels[1])} codes")
print(f" L3: {len(levels[2])} codes")
Convert to tensors
device = "cuda" if torch.cuda.is_available() else "cpu"
codes_tensor = [
torch.tensor(level, dtype=torch.long, device=device).unsqueeze(0)
for level in levels
]
Generate final audio with SNAC decoder
print("\n[4/4] Decoding to audio...")
with torch.inference_mode():
z_q = snac_model.quantizer.from_codes(codes_tensor)
audio = snac_model.decoder(z_q)[0, 0].cpu().numpy()
Trim warmup samples (first 2048 samples)
if len(audio) > 2048:
audio = audio[2048:]
duration_sec = len(audio) / 24000
print(f"Audio generated: {len(audio)} samples ({duration_sec:.2f}s)")
Save your emotional voice output
output_file = "output.wav"
sf.write(output_file, audio, 24000)
print(f"\nVoice generated successfully!")
if __name__ == "__main__":
main()六、結語
Maya1 作為一款開源的AI語音合成模型,憑借其自然語言聲音設計、豐富的情緒表達、實時流式傳輸和高效部署等核心功能,為語音合成領域帶來了新的突破和創新。它在游戲開發、播客與有聲書制作、AI語音助手、短視頻創作以及無障礙應用等多個場景中具有廣泛的應用前景,有望為用戶和開發者提供更加豐富、自然和情感化的語音交互體驗。
Hugging Face模型庫:???https://huggingface.co/maya-research/maya1??
本文轉載自??小兵的AI視界??,作者:AGI小兵

















