Instructions to use mlboydaisuke/Dia2-1B-LiteRT with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- LiteRT
How to use mlboydaisuke/Dia2-1B-LiteRT with LiteRT:
# No code snippets available yet for this library. # To use this model, check the repository files and the library's documentation. # Want to help? PRs adding snippets are welcome at: # https://github.com/huggingface/huggingface.js
- Moshi
How to use mlboydaisuke/Dia2-1B-LiteRT with Moshi:
# pip install moshi # Run the interactive web server python -m moshi.server --hf-repo "mlboydaisuke/Dia2-1B-LiteRT" # Then open https://localhost:8998 in your browser
# pip install moshi import torch from moshi.models import loaders # Load checkpoint info from HuggingFace checkpoint = loaders.CheckpointInfo.from_hf_repo("mlboydaisuke/Dia2-1B-LiteRT") # Load the Mimi audio codec mimi = checkpoint.get_mimi(device="cuda") mimi.set_num_codebooks(8) # Encode audio (24kHz, mono) wav = torch.randn(1, 1, 24000 * 10) # [batch, channels, samples] with torch.no_grad(): codes = mimi.encode(wav.cuda()) decoded = mimi.decode(codes) - Dia2
How to use mlboydaisuke/Dia2-1B-LiteRT with Dia2:
from dia2 import Dia2, GenerationConfig, SamplingConfig dia = Dia2.from_repo("mlboydaisuke/Dia2-1B-LiteRT", device="cuda", dtype="bfloat16") config = GenerationConfig( cfg_scale=2.0, audio=SamplingConfig(temperature=0.8, top_k=50), use_cuda_graph=True, ) result = dia.generate("[S1] Hello Dia2!", config=config, output_wav="hello.wav", verbose=True) - Notebooks
- Google Colab
- Kaggle
Dia2-1B β LiteRT (on-device, two-speaker dialogue TTS)
Dia2-1B (Nari Labs, Apache-2.0) β a two-speaker
dialogue text-to-speech model β re-authored to run on-device with LiteRT CompiledModel.
Verified on a Pixel 8a (Tensor G3).
Dia2 is a Moshi-style RQ-Transformer. Once per 12.5 Hz frame a 30-layer temporal transformer emits a word-timing action plus Mimi codebook 0; a 3-layer depformer then autoregressively fills the remaining 31 codebooks for that same frame. Mimi (32 quantizers) decodes the codes to 24 kHz audio.
Everything runs on CPU (fp32), because fp16 collapses these deep stacks on ARM XNNPACK. The KV caches, RoPE, embedding sums, the depformer's projections and all sampling live on the host; the graphs are pure step functions.
Correction (2026-07-10). An earlier version of this card said the Mali ML Drift GPU delegate rejects the language models' KV-step
FULLY_CONNECTEDweight shapes. That rejection is real on LiteRT 2.1.3 and fixed in 2.1.5. The depformer's own compile failure was in our graph: a rank-5 reshape inside the fused-QKV authoring (ML Drift's maximum tensor rank is 4). Slicing the last dimension into thirds gives 237/237 nodes delegated; it then miscomputed at both default and FP32 precision, which is the known BMM + broadcast-ADDbug, and pre-expanding the attention mask host-side from[1,1,1,D]to[1,NH,1,D]brings it to corr 1.000000.Measured end to end on a Pixel 8a: with the depformer on the GPU the audio is bit-identical to the CPU path (4288/4288 codebook tokens equal, waveform corr 1.000000, RMS diff 0.000000), but it is no faster β 3906 depformer calls cost 76.4 s on CPU and 78.3 s on GPU.
CompiledModel.run()only enqueues; the GPU work is paid inside the firstreadFloat()(17.1 s enqueue + 61.2 s readback), so timingrun()alone suggests a 4.4x win that does not exist. Per call the GPU costs 21.1 ms against the CPU's 19.7 ms: a 3-layer single-token step graph cannot amortise dispatch and synchronisation. The GPU is not the obstacle, and it is also not the answer at this graph size.
Files
| File | Size | Input | Output |
|---|---|---|---|
dia2_temporal_fp32.tflite |
3.0 GB | emb [1,1,1024], RoPE cos/sin [1,1,1,128], mask [1,1,1,257], packed K/V [1,240,256,128] |
hidden [1,1,1024], action [1,1,2], cb0 [1,1,2050], new K/V |
dia2_depformer_wi{0,1,2}_fp32.tflite |
164 MB each | dep_in [1,1,1024], RoPE, mask [1,1,1,32], packed K/V [1,24,31,128] |
hidden [1,1,1024], new K/V |
dia2_mimi_dequant.tflite |
68 MB | codes [1,32,1] float |
latent [1,512,1] |
dia2_mimi_decode_t256.tflite |
164 MB | latent [1,512,256] |
audio [1,1,491520] @ 24 kHz |
dia2_combined_main.f16 / dia2_combined_second.f16 |
101 MB each | β | text embedding Γ projection, [49280,1024] |
dia2_temporal_audio.f16 |
134 MB | β | audio embeddings, [32,2050,1024] |
dia2_dep_audio.f16 / dia2_dep_logits.f16 |
130 MB each | β | depformer embeddings / logit weights, [31,2050,1024] |
dia2_dep_in.f16 |
6 MB | β | depformer input projections, [3,1024,1024] |
dia2_constants.json |
β | β | delays, weight schedule, token ids |
dia2_prefix.json |
13 kB | β | baked two-speaker voice prompt |
Three things that are easy to get wrong
1. Both text streams carry real word tokens. Channels 0 and 1 are not new-word/pad markers.
On a new word the main stream emits the word's first text token while the second stream emits
NEW_WORD; during the padding frames that follow, the main stream drains the rest of the word and
the second stream drains a two-word lookahead. Feeding markers instead produces fluent, confident,
completely wrong speech.
2. Undo the delay pattern before decoding. Codebook cb lags the aligned timeline by 16 frames
(cb0) or 18 frames (the rest). Codes are stored at audio[cb][t+1], so
aligned[cb][Ο] = audio[cb][delay[cb] + Ο], and the result is max(delay) frames shorter. Skipping
this yields muffled, unintelligible audio.
3. Mimi decode must be a single pass. The decode path is upsample β causal decoder transformer β SEANet, so its receptive field is unbounded. Decoding disjoint windows starts each one with no history and costs ~13% relative error (corr 0.991 against a full-sequence decode). The graph here spans 256 frames; leave the unused tail zeroed and causality makes it exact β corr 0.999999.
The speaker is sampled
With no voice prefix Dia2 samples the speaker identity, so the voice changes on every run (median
F0 wanders over a ~120 Hz range). Classifier-free guidance does not fix this β measured over 8
matched seeds, the F0 spread is 144 Hz at cfg_scale=1.0 and 134 Hz at 2.0; what guidance buys is
steadier output levels. The model's own remedy is a voice prefix.
Building a prefix normally needs Whisper word timings and a Mimi encoder, both host-only, so
dia2_prefix.json ships a precomputed prompt (aligned Mimi codes, new_word_steps, prefix word
entries). On device only the warm-up runs: the temporal transformer replays the prompt to prime both
KV caches β no Mimi encoder, no sampling, no depformer. The generated speakers then track their
prompts (measured on device: S1 214 Hz / S2 114 Hz, against prompts of 247 Hz / 88 Hz; without a
prefix S2 never drops below 214 Hz).
Classifier-free guidance (cfg_scale = 2.0, Dia2's default) is subtle: the guided logits
uncond + scaleΒ·(cond β uncond) only select the top-k candidate set, while the draw is a
temperature softmax over the conditional logits restricted to that set. It therefore needs a
second, unconditional branch (text forced to zero/pad, same audio codes, its own KV cache).
Usage β Kotlin (Android, LiteRT CompiledModel)
import com.google.ai.edge.litert.Accelerator
import com.google.ai.edge.litert.CompiledModel
val options = CompiledModel.Options(Accelerator.CPU)
val temporal = CompiledModel.create("$dir/dia2_temporal_fp32.tflite", options, null)
val depformer = Array(3) { CompiledModel.create("$dir/dia2_depformer_wi${it}_fp32.tflite", options, null) }
// One temporal step: embedding + RoPE + additive mask + packed KV cache.
val inputs = temporal.createInputBuffers()
val outputs = temporal.createOutputBuffers()
inputs[0].writeFloat(embedding) // [1,1,1024] = text embed + 32 audio embeds, summed on host
inputs[1].writeFloat(ropeCos(frame)) // [1,1,1,128]
inputs[2].writeFloat(ropeSin(frame))
inputs[3].writeFloat(cache.mask()) // [1,1,1,257]; -3e38 on unwritten slots, 0 on the tail
inputs[4].writeFloat(cache.keys) // [1,240,256,128]
inputs[5].writeFloat(cache.values)
temporal.run(inputs, outputs)
// outputs: hidden [1024], action [2], cb0 logits [2050], new K/V (append to the cache)
Usage β Python (LiteRT CompiledModel)
import numpy as np
from ai_edge_litert.interpreter import Interpreter
# Mimi decode: 32 codes per frame -> latent -> 24 kHz audio, one shot over 256 frames.
dequant = Interpreter(model_path="dia2_mimi_dequant.tflite"); dequant.allocate_tensors()
decode = Interpreter(model_path="dia2_mimi_decode_t256.tflite"); decode.allocate_tensors()
latents = np.zeros((1, 512, 256), np.float32) # tail stays zeroed: the path is causal
for tau in range(num_frames): # aligned (undelayed) codes, [32, num_frames]
dequant.set_tensor(dequant.get_input_details()[0]["index"],
aligned[:, tau].reshape(1, 32, 1).astype(np.float32))
dequant.invoke()
latents[0, :, tau] = dequant.get_tensor(dequant.get_output_details()[0]["index"]).reshape(-1)
decode.set_tensor(decode.get_input_details()[0]["index"], latents)
decode.invoke()
audio = decode.get_tensor(decode.get_output_details()[0]["index"]).reshape(-1)[:num_frames * 1920]
Performance and memory
On a Pixel 8a a 4-second utterance takes 190 s: 71 warm-up frames (temporal only, Γ2 guidance
branches) plus ~67 generated frames, each running 2 temporal steps and 2Γ31 depformer stages. The
process peaks at **4.6 GB RSS** and settles around 3.2 GB β on an 8 GB phone, close other apps.
Validation
Every ported component was checked against the reference implementation on host before it reached the device:
| Component | Check | Result |
|---|---|---|
| StateMachine (multiplex + lookahead) | vs recorded reference frame stream | 0/60 mismatches |
Tokenizer + parse_script |
vs reference entries | 10/10 exact |
| Depformer 31-stage KV glue | vs torch depformer | corr 1.0000, argmax 31/31 |
| Mimi decode | vs torch full-sequence decode | corr 0.999999 |
| Voice-prefix warm-up | vs reference warm-up + generation | 0 mismatches (71 + 63 frames) |
License
Apache-2.0, inherited from nari-labs/Dia2-1B. The Mimi codec graphs derive from kyutai/mimi (CC-BY-4.0).
- Downloads last month
- 30
Model tree for mlboydaisuke/Dia2-1B-LiteRT
Base model
nari-labs/Dia2-1B