The Second Coming of the Kani - A significantly improved text-to-speech library that pushes the boundaries of neural audio generation.
KaniTTS-2 is a research-grade TTS system built on causal language models with advanced architectural innovations. It's simple to use, but powerful under the hood.
Major architectural improvements over the first release:
pip install kani-tts-2
pip install -U "transformers==4.56.0"
from kani_tts import KaniTTS
# Initialize model
model = KaniTTS('nineninesix/your-model-name-here')
# Generate speech (simple)
audio, text = model("Hello, world!")
# Save to file (requires soundfile)
model.save_audio(audio, "output.wav")
That's it! Three lines for high-quality TTS. π
KaniTTS-2 introduces speaker embeddings for true voice control. Extract a speaker's voice characteristics from a reference audio sample and use it to generate speech in that voice!
from kani_tts import KaniTTS
from kani_tts import SpeakerEmbedder
# Initialize TTS model
model = KaniTTS('nineninesix/your-model-name')
# Initialize speaker embedder
embedder = SpeakerEmbedder()
# Extract speaker embedding from reference audio (any sample rate supported)
speaker_embedding = embedder.embed_audio_file("reference_voice.wav") # Returns [1, 128] tensor
# Generate speech with that voice
audio, text = model(
"This is a cloned voice speaking!",
speaker_emb=speaker_embedding
)
model.save_audio(audio, "cloned_voice.wav")
The speaker embedder uses a WavLM-based model trained to extract speaker characteristics:
from kani_tts import SpeakerEmbedder
embedder = SpeakerEmbedder(
model_name="nineninesix/speaker-emb-tbr", # Default WavLM model
device="cuda", # or "cpu"
max_duration_sec=30.0 # Max audio length (longer will be truncated)
)
# From audio file (any sample rate, automatically resampled)
embedding = embedder.embed_audio_file("voice.wav")
# From numpy array (specify sample rate for automatic resampling)
import numpy as np
audio_array = np.random.randn(16000 * 5) # 5 seconds
embedding = embedder.embed_audio(audio_array, sample_rate=16000)
# Save embedding for later use
import torch
torch.save(embedding, "my_voice.pt")
# Load and use saved embedding
audio, text = model("Hello!", speaker_emb="my_voice.pt")
Pro tip: Longer reference audio (10-20 seconds) generally produces better embeddings. Audio at any sample rate is supported (automatic resampling). Make sure the audio is clean and contains only the target speaker! See Voice Cloning Best Practices for more details.
Some models are trained with language/accent tags for better multi-lingual control:
from kani_tts import KaniTTS
model = KaniTTS('nineninesix/your-multilingual-model')
# Check if model supports language tags
print(f"Status: {model.status}") # 'available_language_tags' or 'no_language_tags'
# Show available language tags
model.show_language_tags()
# Output:
# ==================================================
# Available language tags:
# --------------------------------------------------
# 1. en_US
# 2. fr_FR
# 3. de_DE
# ==================================================
# Generate with specific language tag
audio, text = model(
"Bonjour le monde!",
language_tag="fr_FR",
speaker_emb=speaker_embedding
)
Note: Language tags are particularly useful for controlling accents when your model was trained with accent labels. Check model metadata to see if tags are available.
KaniTTS-2 moves sampling parameters to generation time for easier experimentation:
from kani_tts import KaniTTS
# Initialize model (basic config only)
model = KaniTTS(
'nineninesix/your-model-name',
max_new_tokens=3000, # Max generation length (default: 3000)
suppress_logs=True, # Suppress library logs (default: True)
show_info=True, # Show model info on init (default: True)
)
# Control sampling at generation time
audio, text = model(
"Your text here",
temperature=0.7, # Lower = more deterministic (default: 1.0)
top_p=0.9, # Nucleus sampling threshold (default: 0.95)
repetition_penalty=1.2, # Penalize repetition (default: 1.1)
speaker_emb=speaker_emb, # Optional: speaker embedding
language_tag="en_US" # Optional: language tag
)
Why move to generation time? This lets you:
When initialized, KaniTTS-2 displays a beautiful banner with model information:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β N I N E N I N E S I X πΌ β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
/\_/\
( o.o )
> ^ <
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Model: nineninesix/kani-tts-2
Device: GPU (CUDA)
Mode: Available language tags (3 language tags)
Tags: en_US, fr_FR, de_DE
Configuration:
β’ Sample Rate: 22050 Hz
β’ Max Tokens: 3000
β’ Speaker Embedding Dim: 128
β’ Text Vocab Size: 64400
β’ Tokens per Frame: 4
β’ Audio Step: 0.25
β’ Learnable RoPE: Enabled (per-layer frequency scaling)
β’ Alpha Range: [0.5, 2.0]
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Ready to generate speech! π΅
You can disable this banner by setting show_info=False, or show it again anytime with model.show_model_info().
By default, Kani-TTS suppresses all logging output from transformers, NeMo, and PyTorch to keep your console clean. Only your print() statements will be visible.
from kani_tts import KaniTTS
# Default behavior - logs are suppressed
model = KaniTTS('your-model-name')
# To see all library logs (for debugging)
model = KaniTTS('your-model-name', suppress_logs=False)
# You can also manually suppress logs at any time
from kani_tts import suppress_all_logs
suppress_all_logs()
The generated audio is a NumPy array sampled at 22kHz:
import numpy as np
import soundfile as sf
audio, text = model("Generate speech from this text")
# Audio is a numpy array
print(audio.shape) # (num_samples,)
print(audio.dtype) # float32/float64
# Save using soundfile
sf.write('output.wav', audio, 22050)
# Or use the built-in method
model.save_audio(audio, 'output.wav', sample_rate=22050)
You can listen to generated audio directly in Jupyter notebooks or IPython:
from kani_tts import KaniTTS
from IPython.display import Audio as aplay
model = KaniTTS('nineninesix/your-model-name')
audio, text = model("Hello, world!")
# Play audio in notebook
aplay(audio, rate=model.sample_rate)
KaniTTS(model_name, **kwargs)Main TTS interface.
Parameters:
model_name (str): HuggingFace model ID or local pathmax_new_tokens (int): Max generation length (default: 3000)device_map (str): Device mapping for model (default: "auto")suppress_logs (bool): Suppress library logs (default: True)show_info (bool): Display model info banner (default: True)text_vocab_size, tokens_per_frame, audio_step, use_learnable_rope, alpha_min, alpha_max, speaker_emb_dim (all optional, read from model config if None)Methods:
model(text, language_tag=None, speaker_emb=None, temperature=1.0, top_p=0.95, repetition_penalty=1.1) β (audio, text)model.generate(...) β Same as __call__model.save_audio(audio, path) β Save audio to filemodel.show_model_info() β Display model bannermodel.show_language_tags() β Display available language tags (if supported)model.load_speaker_embedding(path) β Load speaker embedding from .pt fileSpeakerEmbedder(model_name, device, max_duration_sec)Extract speaker embeddings from audio.
Parameters:
model_name (str): HuggingFace model ID (default: "nineninesix/speaker-emb-tbr")device (str): "cuda" or "cpu" (default: auto-detect)max_duration_sec (float): Max audio length in seconds (default: 30.0)Methods:
embedder.embed_audio(audio, sample_rate=16000) β [1, 128] tensorembedder.embed_audio_file(path) β [1, 128] tensorConvenience function:
from kani_tts import compute_speaker_embedding
embedding = compute_speaker_embedding(audio_or_path, sample_rate=16000)
Here's a complete example showing how to clone a voice and generate speech:
from kani_tts import KaniTTS
from kani_tts import SpeakerEmbedder
import soundfile as sf
# 1. Initialize models
print("Loading TTS model...")
tts = KaniTTS('nineninesix/kani-tts-2-model')
print("Loading speaker embedder...")
embedder = SpeakerEmbedder()
# 2. Extract speaker embedding from reference audio (any sample rate)
print("Extracting speaker characteristics...")
speaker_emb = embedder.embed_audio_file("reference_speaker.wav")
# Save embedding for later reuse
import torch
torch.save(speaker_emb, "my_cloned_voice.pt")
# 3. Generate speech with cloned voice
print("Generating speech...")
audio, text = tts(
"This is a test of voice cloning with KaniTTS-2. Pretty cool, right?",
speaker_emb=speaker_emb,
temperature=0.8, # Slightly less random
top_p=0.92, # Nucleus sampling
repetition_penalty=1.15 # Avoid repetition
)
# 4. Save output
tts.save_audio(audio, "cloned_output.wav")
print(f"β
Generated {len(audio)/tts.sample_rate:.2f}s of audio")
# 5. For multi-lingual models, specify language
if tts.status == 'available_language_tags':
tts.show_language_tags()
audio_fr, _ = tts(
"Bonjour, comment allez-vous?",
language_tag="fr_FR",
speaker_emb=speaker_emb
)
tts.save_audio(audio_fr, "french_cloned.wav")
KaniTTS-2 is based on a causal language model architecture with specialized modifications for high-quality audio generation. Think of it as GPT, but instead of predicting the next word, it predicts the next audio token sequence.
Two-Stage Pipeline:
Standard RoPE (Rotary Position Embeddings) uses fixed frequencies for position encoding. KaniTTS-2 introduces per-layer learnable alpha parameters that scale RoPE frequencies:
alpha value in range [alpha_min, alpha_max]Audio tokens are organized in frames (4 tokens per frame, representing 4 codebook channels):
tokens_per_frame: Number of tokens in each audio frame (default: 4)audio_step: Position increment per frame (e.g., 0.25 means each frame advances position by 0.25)This dual encoding scheme helps the model understand the difference between text tokens (discrete linguistic units) and audio tokens (continuous temporal frames).
Instead of discrete speaker IDs (which require fine-tuning), KaniTTS-2 uses continuous speaker embeddings:
Optional language identifiers prepended to text input:
<language_tag>: <text> (e.g., "en_US: Hello world")The model uses an extended vocabulary with special control tokens:
Text Tokens (0 - 64399):
<start_of_text> (1), <end_of_text> (2)Control Tokens (64400+):
<start_of_speech>, <end_of_speech>: Speech boundaries<start_of_human>, <end_of_human>: Human turn markers<start_of_ai>, <end_of_ai>: AI turn markers<pad>: Padding tokenAudio Tokens (64410+):
[c0, c1, c2, c3] where each ci is from codebook iInput text + optional (language_tag, speaker_emb)
β
Tokenization + special tokens
β
LLaMA-based causal LM with:
- Learnable RoPE (per-layer alpha)
- Frame-level position encoding
- Speaker embedding conditioning
β
Audio token sequence (4 tokens per frame)
β
NeMo NanoCodec decoder
β
22kHz waveform output
KaniTTS-2 works with modified LLaMA-based causal language models (LFM2) trained for TTS with:
β Required characteristics:
β Optional features (configured via model metadata or init params):
speaker_emb_dim in config)use_learnable_rope, alpha_min, alpha_max)tokens_per_frame, audio_step)language_settings in config)How to check model compatibility:
model = KaniTTS('model-name', show_info=True)
# The banner will display all supported features!
For voice cloning:
For generation quality:
max_new_tokens for longer generations (up to ~3000 for ~40s)For multi-lingual models:
model.show_language_tags() to see available tagsFor optimal voice cloning results, follow these critical recommendations:
1. Reference Audio Quality is Critical
The quality of your reference audio directly impacts model behavior and output quality:
Poor reference quality β Model confusion, inconsistent voice characteristics, artifacts in output Good reference quality β Stable voice reproduction, natural-sounding speech, better prosody
2. Multiple Audio Samples β Better Speaker Representation
To capture a speaker's voice characteristics more accurately:
from kani_tts import SpeakerEmbedder
import torch
embedder = SpeakerEmbedder()
# Record 5-10 different audio samples of the same speaker
# (different sentences, varied intonation and speaking styles)
sample_files = [
"speaker_sample_1.wav",
"speaker_sample_2.wav",
"speaker_sample_3.wav",
"speaker_sample_4.wav",
"speaker_sample_5.wav",
]
# Extract embeddings from all samples
embeddings = [embedder.embed_audio_file(f) for f in sample_files]
# Average the embeddings to get a more generalized representation
averaged_embedding = torch.stack(embeddings).mean(dim=0)
# Use the averaged embedding for generation
audio, text = model(
"Your text here",
speaker_emb=averaged_embedding
)
Why averaging helps:
Recommendation: Record 5-10 different audio samples (15-25 seconds each) with varied content and speaking styles, then average their embeddings for best results.
Contributions are welcome! Please feel free to submit a Pull Request.
@article{liquidai2025lfm2,
title={LFM2 Technical Report},
author={Liquid AI},
journal={arXiv preprint arXiv:2511.23404},
year={2025}
}
@inproceedings{emilialarge,
author={He, Haorui and Shang, Zengqiang and Wang, Chaoren and Li, Xuyuan and Gu, Yicheng and Hua, Hua and Liu, Liwei and Yang, Chen and Li, Jiaqi and Shi, Peiyang and Wang, Yuancheng and Chen, Kai and Zhang, Pengyuan and Wu, Zhizheng},
title={Emilia: A Large-Scale, Extensive, Multilingual, and Diverse Dataset for Speech Generation},
booktitle={arXiv:2501.15907},
year={2025}
}
@article{emonet_voice_2025,
author={Schuhmann, Christoph and Kaczmarczyk, Robert and Rabby, Gollam and Friedrich, Felix and Kraus, Maurice and Nadi, Kourosh and Nguyen, Huu and Kersting, Kristian and Auer, SΓΆren},
title={EmoNet-Voice: A Fine-Grained, Expert-Verified Benchmark for Speech Emotion Detection},
journal={arXiv preprint arXiv:2506.09827},
year={2025}
}
@inproceedings{gengembre24_interspeech,
title = {Disentangling prosody and timbre embeddings via voice conversion},
author = {Nicolas Gengembre and Olivier {Le Blouch} and CΓ©dric Gendrot},
year = {2024},
booktitle = {Interspeech 2024},
pages = {2765--2769},
doi = {10.21437/Interspeech.2024-207},
issn = {2958-1796},
}
Prohibited activities include:
By using this model, you agree to comply with these restrictions and all applicable laws.
Models: Pretrained Model, English Model
Pretraining Framework Train your own TTS model on your language or accent from scratch using this open-source pretraining framework: KaniTTS2-Pretrain.
Example Dataset: https://huggingface.co/datasets/nineninesix/kanitts2-es-nano-codec-speaker-emb-dataset
The training code is under active development and will continue to receive updates and improvements.
If you use this code in your research, please cite:
@software{kani_tts_2,
author = {Nineninesix},
title = {KaniTTS2: Text-to-Speech Model with Frame-level Position Encoding},
year = {2026},
publisher = {Hugging Face},
howpublished = {\url{https://github.com/nineninesix-ai/kani-tts-2}},
note = {Open-source TTS model}
}
mohammed-bahumaish/kani-tts-2-vllm
Coming soon.
Have a question, feedback, or need support? Please fill out our contact form and we'll get back to you as soon as possible.
Made with πΌ by nineninesix
Python
100.0%
The Second Coming of the Kani - A significantly improved text-to-speech library that pushes the boundaries of neural audio generation.
KaniTTS-2 is a research-grade TTS system built on causal language models with advanced architectural innovations. It's simple to use, but powerful under the hood.
Major architectural improvements over the first release:
pip install kani-tts-2
pip install -U "transformers==4.56.0"
from kani_tts import KaniTTS
# Initialize model
model = KaniTTS('nineninesix/your-model-name-here')
# Generate speech (simple)
audio, text = model("Hello, world!")
# Save to file (requires soundfile)
model.save_audio(audio, "output.wav")
That's it! Three lines for high-quality TTS. π
KaniTTS-2 introduces speaker embeddings for true voice control. Extract a speaker's voice characteristics from a reference audio sample and use it to generate speech in that voice!
from kani_tts import KaniTTS
from kani_tts import SpeakerEmbedder
# Initialize TTS model
model = KaniTTS('nineninesix/your-model-name')
# Initialize speaker embedder
embedder = SpeakerEmbedder()
# Extract speaker embedding from reference audio (any sample rate supported)
speaker_embedding = embedder.embed_audio_file("reference_voice.wav") # Returns [1, 128] tensor
# Generate speech with that voice
audio, text = model(
"This is a cloned voice speaking!",
speaker_emb=speaker_embedding
)
model.save_audio(audio, "cloned_voice.wav")
The speaker embedder uses a WavLM-based model trained to extract speaker characteristics:
from kani_tts import SpeakerEmbedder
embedder = SpeakerEmbedder(
model_name="nineninesix/speaker-emb-tbr", # Default WavLM model
device="cuda", # or "cpu"
max_duration_sec=30.0 # Max audio length (longer will be truncated)
)
# From audio file (any sample rate, automatically resampled)
embedding = embedder.embed_audio_file("voice.wav")
# From numpy array (specify sample rate for automatic resampling)
import numpy as np
audio_array = np.random.randn(16000 * 5) # 5 seconds
embedding = embedder.embed_audio(audio_array, sample_rate=16000)
# Save embedding for later use
import torch
torch.save(embedding, "my_voice.pt")
# Load and use saved embedding
audio, text = model("Hello!", speaker_emb="my_voice.pt")
Pro tip: Longer reference audio (10-20 seconds) generally produces better embeddings. Audio at any sample rate is supported (automatic resampling). Make sure the audio is clean and contains only the target speaker! See Voice Cloning Best Practices for more details.
Some models are trained with language/accent tags for better multi-lingual control:
from kani_tts import KaniTTS
model = KaniTTS('nineninesix/your-multilingual-model')
# Check if model supports language tags
print(f"Status: {model.status}") # 'available_language_tags' or 'no_language_tags'
# Show available language tags
model.show_language_tags()
# Output:
# ==================================================
# Available language tags:
# --------------------------------------------------
# 1. en_US
# 2. fr_FR
# 3. de_DE
# ==================================================
# Generate with specific language tag
audio, text = model(
"Bonjour le monde!",
language_tag="fr_FR",
speaker_emb=speaker_embedding
)
Note: Language tags are particularly useful for controlling accents when your model was trained with accent labels. Check model metadata to see if tags are available.
KaniTTS-2 moves sampling parameters to generation time for easier experimentation:
from kani_tts import KaniTTS
# Initialize model (basic config only)
model = KaniTTS(
'nineninesix/your-model-name',
max_new_tokens=3000, # Max generation length (default: 3000)
suppress_logs=True, # Suppress library logs (default: True)
show_info=True, # Show model info on init (default: True)
)
# Control sampling at generation time
audio, text = model(
"Your text here",
temperature=0.7, # Lower = more deterministic (default: 1.0)
top_p=0.9, # Nucleus sampling threshold (default: 0.95)
repetition_penalty=1.2, # Penalize repetition (default: 1.1)
speaker_emb=speaker_emb, # Optional: speaker embedding
language_tag="en_US" # Optional: language tag
)
Why move to generation time? This lets you:
When initialized, KaniTTS-2 displays a beautiful banner with model information:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β N I N E N I N E S I X πΌ β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
/\_/\
( o.o )
> ^ <
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Model: nineninesix/kani-tts-2
Device: GPU (CUDA)
Mode: Available language tags (3 language tags)
Tags: en_US, fr_FR, de_DE
Configuration:
β’ Sample Rate: 22050 Hz
β’ Max Tokens: 3000
β’ Speaker Embedding Dim: 128
β’ Text Vocab Size: 64400
β’ Tokens per Frame: 4
β’ Audio Step: 0.25
β’ Learnable RoPE: Enabled (per-layer frequency scaling)
β’ Alpha Range: [0.5, 2.0]
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Ready to generate speech! π΅
You can disable this banner by setting show_info=False, or show it again anytime with model.show_model_info().
By default, Kani-TTS suppresses all logging output from transformers, NeMo, and PyTorch to keep your console clean. Only your print() statements will be visible.
from kani_tts import KaniTTS
# Default behavior - logs are suppressed
model = KaniTTS('your-model-name')
# To see all library logs (for debugging)
model = KaniTTS('your-model-name', suppress_logs=False)
# You can also manually suppress logs at any time
from kani_tts import suppress_all_logs
suppress_all_logs()
The generated audio is a NumPy array sampled at 22kHz:
import numpy as np
import soundfile as sf
audio, text = model("Generate speech from this text")
# Audio is a numpy array
print(audio.shape) # (num_samples,)
print(audio.dtype) # float32/float64
# Save using soundfile
sf.write('output.wav', audio, 22050)
# Or use the built-in method
model.save_audio(audio, 'output.wav', sample_rate=22050)
You can listen to generated audio directly in Jupyter notebooks or IPython:
from kani_tts import KaniTTS
from IPython.display import Audio as aplay
model = KaniTTS('nineninesix/your-model-name')
audio, text = model("Hello, world!")
# Play audio in notebook
aplay(audio, rate=model.sample_rate)
KaniTTS(model_name, **kwargs)Main TTS interface.
Parameters:
model_name (str): HuggingFace model ID or local pathmax_new_tokens (int): Max generation length (default: 3000)device_map (str): Device mapping for model (default: "auto")suppress_logs (bool): Suppress library logs (default: True)show_info (bool): Display model info banner (default: True)text_vocab_size, tokens_per_frame, audio_step, use_learnable_rope, alpha_min, alpha_max, speaker_emb_dim (all optional, read from model config if None)Methods:
model(text, language_tag=None, speaker_emb=None, temperature=1.0, top_p=0.95, repetition_penalty=1.1) β (audio, text)model.generate(...) β Same as __call__model.save_audio(audio, path) β Save audio to filemodel.show_model_info() β Display model bannermodel.show_language_tags() β Display available language tags (if supported)model.load_speaker_embedding(path) β Load speaker embedding from .pt fileSpeakerEmbedder(model_name, device, max_duration_sec)Extract speaker embeddings from audio.
Parameters:
model_name (str): HuggingFace model ID (default: "nineninesix/speaker-emb-tbr")device (str): "cuda" or "cpu" (default: auto-detect)max_duration_sec (float): Max audio length in seconds (default: 30.0)Methods:
embedder.embed_audio(audio, sample_rate=16000) β [1, 128] tensorembedder.embed_audio_file(path) β [1, 128] tensorConvenience function:
from kani_tts import compute_speaker_embedding
embedding = compute_speaker_embedding(audio_or_path, sample_rate=16000)
Here's a complete example showing how to clone a voice and generate speech:
from kani_tts import KaniTTS
from kani_tts import SpeakerEmbedder
import soundfile as sf
# 1. Initialize models
print("Loading TTS model...")
tts = KaniTTS('nineninesix/kani-tts-2-model')
print("Loading speaker embedder...")
embedder = SpeakerEmbedder()
# 2. Extract speaker embedding from reference audio (any sample rate)
print("Extracting speaker characteristics...")
speaker_emb = embedder.embed_audio_file("reference_speaker.wav")
# Save embedding for later reuse
import torch
torch.save(speaker_emb, "my_cloned_voice.pt")
# 3. Generate speech with cloned voice
print("Generating speech...")
audio, text = tts(
"This is a test of voice cloning with KaniTTS-2. Pretty cool, right?",
speaker_emb=speaker_emb,
temperature=0.8, # Slightly less random
top_p=0.92, # Nucleus sampling
repetition_penalty=1.15 # Avoid repetition
)
# 4. Save output
tts.save_audio(audio, "cloned_output.wav")
print(f"β
Generated {len(audio)/tts.sample_rate:.2f}s of audio")
# 5. For multi-lingual models, specify language
if tts.status == 'available_language_tags':
tts.show_language_tags()
audio_fr, _ = tts(
"Bonjour, comment allez-vous?",
language_tag="fr_FR",
speaker_emb=speaker_emb
)
tts.save_audio(audio_fr, "french_cloned.wav")
KaniTTS-2 is based on a causal language model architecture with specialized modifications for high-quality audio generation. Think of it as GPT, but instead of predicting the next word, it predicts the next audio token sequence.
Two-Stage Pipeline:
Standard RoPE (Rotary Position Embeddings) uses fixed frequencies for position encoding. KaniTTS-2 introduces per-layer learnable alpha parameters that scale RoPE frequencies:
alpha value in range [alpha_min, alpha_max]Audio tokens are organized in frames (4 tokens per frame, representing 4 codebook channels):
tokens_per_frame: Number of tokens in each audio frame (default: 4)audio_step: Position increment per frame (e.g., 0.25 means each frame advances position by 0.25)This dual encoding scheme helps the model understand the difference between text tokens (discrete linguistic units) and audio tokens (continuous temporal frames).
Instead of discrete speaker IDs (which require fine-tuning), KaniTTS-2 uses continuous speaker embeddings:
Optional language identifiers prepended to text input:
<language_tag>: <text> (e.g., "en_US: Hello world")The model uses an extended vocabulary with special control tokens:
Text Tokens (0 - 64399):
<start_of_text> (1), <end_of_text> (2)Control Tokens (64400+):
<start_of_speech>, <end_of_speech>: Speech boundaries<start_of_human>, <end_of_human>: Human turn markers<start_of_ai>, <end_of_ai>: AI turn markers<pad>: Padding tokenAudio Tokens (64410+):
[c0, c1, c2, c3] where each ci is from codebook iInput text + optional (language_tag, speaker_emb)
β
Tokenization + special tokens
β
LLaMA-based causal LM with:
- Learnable RoPE (per-layer alpha)
- Frame-level position encoding
- Speaker embedding conditioning
β
Audio token sequence (4 tokens per frame)
β
NeMo NanoCodec decoder
β
22kHz waveform output
KaniTTS-2 works with modified LLaMA-based causal language models (LFM2) trained for TTS with:
β Required characteristics:
β Optional features (configured via model metadata or init params):
speaker_emb_dim in config)use_learnable_rope, alpha_min, alpha_max)tokens_per_frame, audio_step)language_settings in config)How to check model compatibility:
model = KaniTTS('model-name', show_info=True)
# The banner will display all supported features!
For voice cloning:
For generation quality:
max_new_tokens for longer generations (up to ~3000 for ~40s)For multi-lingual models:
model.show_language_tags() to see available tagsFor optimal voice cloning results, follow these critical recommendations:
1. Reference Audio Quality is Critical
The quality of your reference audio directly impacts model behavior and output quality:
Poor reference quality β Model confusion, inconsistent voice characteristics, artifacts in output Good reference quality β Stable voice reproduction, natural-sounding speech, better prosody
2. Multiple Audio Samples β Better Speaker Representation
To capture a speaker's voice characteristics more accurately:
from kani_tts import SpeakerEmbedder
import torch
embedder = SpeakerEmbedder()
# Record 5-10 different audio samples of the same speaker
# (different sentences, varied intonation and speaking styles)
sample_files = [
"speaker_sample_1.wav",
"speaker_sample_2.wav",
"speaker_sample_3.wav",
"speaker_sample_4.wav",
"speaker_sample_5.wav",
]
# Extract embeddings from all samples
embeddings = [embedder.embed_audio_file(f) for f in sample_files]
# Average the embeddings to get a more generalized representation
averaged_embedding = torch.stack(embeddings).mean(dim=0)
# Use the averaged embedding for generation
audio, text = model(
"Your text here",
speaker_emb=averaged_embedding
)
Why averaging helps:
Recommendation: Record 5-10 different audio samples (15-25 seconds each) with varied content and speaking styles, then average their embeddings for best results.
Contributions are welcome! Please feel free to submit a Pull Request.
@article{liquidai2025lfm2,
title={LFM2 Technical Report},
author={Liquid AI},
journal={arXiv preprint arXiv:2511.23404},
year={2025}
}
@inproceedings{emilialarge,
author={He, Haorui and Shang, Zengqiang and Wang, Chaoren and Li, Xuyuan and Gu, Yicheng and Hua, Hua and Liu, Liwei and Yang, Chen and Li, Jiaqi and Shi, Peiyang and Wang, Yuancheng and Chen, Kai and Zhang, Pengyuan and Wu, Zhizheng},
title={Emilia: A Large-Scale, Extensive, Multilingual, and Diverse Dataset for Speech Generation},
booktitle={arXiv:2501.15907},
year={2025}
}
@article{emonet_voice_2025,
author={Schuhmann, Christoph and Kaczmarczyk, Robert and Rabby, Gollam and Friedrich, Felix and Kraus, Maurice and Nadi, Kourosh and Nguyen, Huu and Kersting, Kristian and Auer, SΓΆren},
title={EmoNet-Voice: A Fine-Grained, Expert-Verified Benchmark for Speech Emotion Detection},
journal={arXiv preprint arXiv:2506.09827},
year={2025}
}
@inproceedings{gengembre24_interspeech,
title = {Disentangling prosody and timbre embeddings via voice conversion},
author = {Nicolas Gengembre and Olivier {Le Blouch} and CΓ©dric Gendrot},
year = {2024},
booktitle = {Interspeech 2024},
pages = {2765--2769},
doi = {10.21437/Interspeech.2024-207},
issn = {2958-1796},
}
Prohibited activities include:
By using this model, you agree to comply with these restrictions and all applicable laws.
Models: Pretrained Model, English Model
Pretraining Framework Train your own TTS model on your language or accent from scratch using this open-source pretraining framework: KaniTTS2-Pretrain.
Example Dataset: https://huggingface.co/datasets/nineninesix/kanitts2-es-nano-codec-speaker-emb-dataset
The training code is under active development and will continue to receive updates and improvements.
If you use this code in your research, please cite:
@software{kani_tts_2,
author = {Nineninesix},
title = {KaniTTS2: Text-to-Speech Model with Frame-level Position Encoding},
year = {2026},
publisher = {Hugging Face},
howpublished = {\url{https://github.com/nineninesix-ai/kani-tts-2}},
note = {Open-source TTS model}
}
mohammed-bahumaish/kani-tts-2-vllm
Coming soon.
Have a question, feedback, or need support? Please fill out our contact form and we'll get back to you as soon as possible.
Made with πΌ by nineninesix
Python
100.0%