Views
No views yet
| Parameters | 135M (non-embedding)* |
| Training Tokens | 500B |
| Training Data | 95% Speech (Yodas + Emilia) + 5% Text (Nemotron-CC) |
| Tokenizer | soda-research/marin-mimi-bpe-8cb-16k-tokenizer |
| Architecture | Qwen3-based Transformer (cold-start, random initialization) |
# audio-first interleaved sequence
interleaved_seq1 = "<|begin_of_text|><|audio_start|>{AUDIO_TOKENS_1}<|audio_end|><|text_start|>{TEXT_TOKENS_1}<|text_end|><|audio_start|>{AUDIO_TOKENS_2}<|audio_end|><|text_start|>{TEXT_TOKENS_2}<|text_end|>...<|end_of_text|>"
# text-first interleaved sequence
interleaved_seq2 = "<|begin_of_text|><|text_start|>{TEXT_TOKENS_1}<|text_end|><|audio_start|>{AUDIO_TOKENS_1}<|audio_end|><|text_start|>{TEXT_TOKENS_2}<|text_end|><|audio_start|>{AUDIO_TOKENS_2}<|audio_end|>...<|end_of_text|>"<|begin_of_text|>: Marks the start of every sequence<|text_start|> / <|text_end|>: Delimit text segments (can appear multiple times in a sequence of multiple utterances)<|audio_start|> / <|audio_end|>: Delimit audio segments (can appear multiple times in a sequence of multiple utterances)<|end_of_text|>: Marks the end of the complete sequenceNote: Typically a tokenizer (like ours) automatically prepends<|begin_of_text|>to input, so you don't need to include it manually in your prompts. A sequence can contain multiple utterances (i.e., multiple chunks), each with their own text/audio delimiters.
<|begin_of_text|> by tokenizer):# Audio Continuation
<|audio_start|> {audio_context}
→ model generates: {continued_audio} ... # possibly with interleaved text tokens
# TTS (Voice Cloning)
<|text_start|> {transcript} <|text_end|> <|audio_start|> {prompt_audio} <|audio_end|> <|text_start|> {target_text} <|text_end|> <|audio_start|>
→ model generates: {target_audio} <|audio_end|>
# TTS (Unconditioned)
<|text_start|> {text} <|text_end|> <|audio_start|>
→ model generates: {audio} <|audio_end|>
# ASR (Automatic Speech Recognition)
<|audio_start|> {input_audio} <|audio_end|> <|text_start|>
→ model generates: {transcription} <|text_end|>Note: For audio conversion, you will need the helper functions defined in the Utility Functions section below to convert between waveforms and discrete audio tokens.
1import torch
2from transformers import AutoModelForCausalLM, AutoTokenizer, MimiModel
3
4device = "cuda" if torch.cuda.is_available() else "cpu"
5
6# Load SODA
7model_name = "soda-research/soda-135m-base"
8tokenizer = AutoTokenizer.from_pretrained(model_name)
9model = AutoModelForCausalLM.from_pretrained(model_name, torch_dtype=torch.float32, device_map="auto")
10model.eval()
11
12# Load Mimi audio codec
13mimi_model = MimiModel.from_pretrained("kyutai/mimi").to(device)1import librosa
2
3audio, sr = librosa.load("audio_prefix.wav", sr=None)
4audio_24k = librosa.resample(audio, orig_sr=sr, target_sr=24000)
5
6# [Recommended] Trim silence from the beginning and end for more stable generation
7_, idx = librosa.effects.trim(audio_24k, top_db=40)
8audio_24k = audio_24k[max(0, idx[0]-2400) : idx[1]+2400] # keep ~100ms padding
9
10audio_str = audio_to_str(audio_24k, mimi_model, device)
11
12prompt = f"<|audio_start|>{audio_str}"
13
14inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
15with torch.no_grad():
16 outputs = model.generate(
17 **inputs,
18 max_new_tokens=1000, # correspond to ~10s
19 min_new_tokens=100, # correspond to ~1s
20 do_sample=True,
21 temperature=1.0,
22 top_p=0.9,
23 eos_token_id=tokenizer.eos_token_id, # <|end_of_text|>; model may interleave text and audio
24 )
25
26# Extract all audio segments from the output (model may interleave text and audio)
27generated_text = tokenizer.decode(outputs[0], skip_special_tokens=False)
28audio_parts = generated_text.split("<|audio_start|>")
29audio_segments = []
30for part in audio_parts[1:]:
31 content = part.split("<|audio_end|>")[0] if "<|audio_end|>" in part else part.strip()
32 if content:
33 audio_segments.append(content)
34
35full_audio_str = "".join(audio_segments)
36full_audio_str = full_audio_str[: (len(full_audio_str) // 8) * 8]
37audio_out = str_to_audio(full_audio_str, mimi_model, device)
38
39import soundfile as sf
40sf.write("continuation_output.wav", audio_out.T, 24000)1import librosa
2
3# Load reference audio (resample to 24kHz for Mimi)
4audio, sr = librosa.load("reference_voice.wav", sr=None)
5audio_24k = librosa.resample(audio, orig_sr=sr, target_sr=24000)
6
7# [Recommended] Trim silence from the beginning and end for more stable generation
8_, idx = librosa.effects.trim(audio_24k, top_db=40)
9audio_24k = audio_24k[max(0, idx[0]-2400) : idx[1]+2400] # keep ~100ms padding
10
11# Encode reference audio to token string
12audio_str = audio_to_str(audio_24k, mimi_model, device)
13
14# Construct prompt
15prompt_text = "The transcript of the reference audio."
16target_text = "The text you want to synthesize in this voice."
17
18# [Recommended] Add a leading whitespace to both text inputs.
19# The Emilia training data has transcripts that start with a space, so matching
20# that format at inference time improves performance. Not strictly required.
21prompt_text = " " + prompt_text.strip()
22target_text = " " + target_text.strip()
23
24prompt = (
25 f"<|text_start|>{prompt_text}<|text_end|>"
26 f"<|audio_start|>{audio_str}<|audio_end|>"
27 f"<|text_start|>{target_text}<|text_end|>"
28 f"<|audio_start|>"
29)
30
31# Generate
32inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
33with torch.no_grad():
34 outputs = model.generate(
35 **inputs,
36 max_new_tokens=1500,
37 do_sample=True,
38 temperature=1.1,
39 top_p=0.8,
40 eos_token_id=tokenizer.convert_tokens_to_ids("<|audio_end|>"),
41 )
42
43# Decode generated audio
44generated_str = tokenizer.decode(outputs[0][inputs.input_ids.shape[1]:], skip_special_tokens=False)
45generated_str = generated_str.replace("<|audio_end|>", "")
46generated_str = generated_str[: (len(generated_str) // 8) * 8] # align to 8 codebooks
47audio_out = str_to_audio(generated_str, mimi_model, device)
48
49# Save output
50import soundfile as sf
51sf.write("tts_output.wav", audio_out.T, 24000)1text = "The text you want to synthesize."
2
3# [Recommended] Add a leading whitespace to the text input.
4# The Emilia training data has transcripts that start with a space, so matching
5# that format at inference time improves performance. Not strictly required.
6text = " " + text.strip()
7
8# Construct prompt
9prompt = f"<|text_start|>{text}<|text_end|><|audio_start|>"
10
11# Generate
12inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
13with torch.no_grad():
14 outputs = model.generate(
15 **inputs,
16 max_new_tokens=1500,
17 do_sample=True,
18 temperature=1.1,
19 top_p=0.8,
20 eos_token_id=tokenizer.convert_tokens_to_ids("<|audio_end|>"),
21 )
22
23# Decode generated audio
24generated_str = tokenizer.decode(outputs[0][inputs.input_ids.shape[1]:], skip_special_tokens=False)
25generated_str = generated_str.replace("<|audio_end|>", "")
26generated_str = generated_str[: (len(generated_str) // 8) * 8]
27audio_out = str_to_audio(generated_str, mimi_model, device)
28
29# Save output
30import soundfile as sf
31sf.write("tts_uncond_output.wav", audio_out.T, 24000)1import librosa
2
3audio, sr = librosa.load("input_speech.wav", sr=None)
4audio_24k = librosa.resample(audio, orig_sr=sr, target_sr=24000)
5
6# [Recommended] Trim silence from the beginning and end for more stable generation
7_, idx = librosa.effects.trim(audio_24k, top_db=40)
8audio_24k = audio_24k[max(0, idx[0]-2400) : idx[1]+2400] # keep ~100ms padding
9
10audio_str = audio_to_str(audio_24k, mimi_model, device)
11
12prompt = f"<|audio_start|>{audio_str}<|audio_end|><|text_start|>"
13
14inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
15with torch.no_grad():
16 outputs = model.generate(
17 **inputs,
18 max_new_tokens=1200,
19 do_sample=True,
20 temperature=0.3, # lower temperature gives more stable transcriptions
21 top_p=0.9,
22 eos_token_id=tokenizer.convert_tokens_to_ids("<|text_end|>"),
23 )
24
25generated_text = tokenizer.decode(outputs[0][inputs.input_ids.shape[1]:], skip_special_tokens=False)
26transcription = generated_text.replace("<|text_end|>", "").strip()
27print("Transcription:", transcription)1prompt = "<|text_start|>The future of artificial intelligence"
2
3inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
4with torch.no_grad():
5 outputs = model.generate(
6 **inputs,
7 max_new_tokens=200,
8 do_sample=True,
9 temperature=1.0,
10 top_p=0.9,
11 eos_token_id=tokenizer.eos_token_id,
12 )
13
14generated_text = tokenizer.decode(outputs[0][inputs.input_ids.shape[1]:], skip_special_tokens=False)
15print(generated_text)1import numpy as np
2import torch
3from transformers import MimiModel
4
5UNICODE_OFFSET = 0xE000
6NUM_CODEBOOKS = 8
7CODEBOOK_SIZE = 2048
8MIMI_SAMPLE_RATE = 24000
9
10def codes_to_chars(codes, codebook_size=CODEBOOK_SIZE, unicode_offset=UNICODE_OFFSET):
11 """Convert Mimi codec output (num_codebooks, seq_len) → string."""
12 if isinstance(codes, torch.Tensor):
13 codes = codes.cpu().numpy()
14 codes = codes.copy()
15 for i in range(codes.shape[0]):
16 codes[i] += unicode_offset + i * codebook_size
17 codes = codes.T.reshape(-1)
18 return "".join([chr(c) for c in codes])
19
20def chars_to_codes(chars, num_codebooks=NUM_CODEBOOKS, codebook_size=CODEBOOK_SIZE, unicode_offset=UNICODE_OFFSET):
21 """Convert string → Mimi codec codes (num_codebooks, seq_len)."""
22 codes = np.array([ord(c) for c in chars])
23 codes = codes.reshape(-1, num_codebooks).T
24 for i in range(codes.shape[0]):
25 codes[i] -= unicode_offset + i * codebook_size
26 return torch.tensor(codes)
27
28def audio_to_str(audio_numpy, mimi_model, device):
29 """Encode audio waveform (24kHz) → discrete token string."""
30 audio_tensor = torch.tensor(audio_numpy).to(device).unsqueeze(0)
31 if len(audio_tensor.shape) == 2:
32 audio_tensor = audio_tensor.unsqueeze(1)
33 with torch.no_grad():
34 audio_codes = mimi_model.encode(audio_tensor)
35 codes = audio_codes[0][0].cpu()[:NUM_CODEBOOKS, :]
36 return codes_to_chars(codes)
37
38def str_to_audio(audio_str, mimi_model, device):
39 """Decode discrete token string → audio waveform (24kHz)."""
40 codes = chars_to_codes(audio_str).to(device).unsqueeze(0)
41 with torch.no_grad():
42 audio_decoded = mimi_model.decode(codes).audio_values[0]
43 return audio_decoded.cpu().numpy()1@article{soda2026,
2 author = {Manakul, Potsawee and Gan, Woody Haosheng and Bartelds, Martijn and Sun, Guangzhi and Held, William and Yang, Diyi},
3 title = {Scaling Open Discrete Audio Foundation Models with Interleaved Semantic, Acoustic, and Text Tokens},
4 journal = {arXiv preprint arXiv:2602.16687},
5 year = {2026}
6}