加载中…
加载中…
This repository is a conversion to LiteRT of Edge0/Audio8-TTS-Realistic-Air (renamed from Edge0/Audio8-TTS-Realistic-0.6b), made with litert-torch 0.9.4 and ai-edge-quantizer 0.9.0 for LiteRT 2.2.0: in the Android app under android/, a Pixel 11 Pro speaks an 18-token English sentence (5.48 s of audio) at a real-time factor of 0.853, and a Galaxy S26 at 0.797 (6.36 s of audio), each the median of 6 runs (2026-11-03). The real-time factor is the time from text to samples divided by the length of the speech: below 1, the speech is ready before it would have finished playing.
Edge0/Audio8-TTS-Realistic-Air is a text-to-speech model by Edge0, built on Audio8-TTS-Preview-0.6b. Its model card says that it supports 23 languages, and that it clones a voice zero-shot from a reference recording and its transcript or speaks without a reference. It uses a DualAR design inspired by Fish Audio S2 Pro: a 24-layer slow transformer predicts one semantic token per audio frame (2,048 samples, 46 ms at 44.1 kHz), a 4-layer fast transformer predicts the frame's 10 codec codebooks one at a time, and a neural codec turns the frames into audio. The transcript must match the reference recording exactly. A cloned voice speaking another language than its recording's: see Limitations.
This repository holds five LiteRT graphs (slow AR, fast AR, the codec decoder in two window sizes, the codec encoder), the source repository's tokenizer, two reference voices, a Python host loop for desktops and an Android sample app. The two autoregressive graphs hold int8 weights with float activations (dynamic-range quantization) and run on the CPU. In the app the codec decoder runs on the GPU; if the GPU's output fails a check at load, the same file runs on the CPU.
pip install ai-edge-litert tokenizers soundfile scipy numpy huggingface_hub
hf download litert-community/Audio8-TTS-Realistic-0.6b --local-dir audio8r
cd audio8r
python audio8_tts_litert.py --model-dir . --text "Hello from LiteRT, fully on device." \
--voice voices/en_librispeech_1272 --out hello.wav
# Japanese with the bundled Japanese voice
python audio8_tts_litert.py --model-dir . --text "今日は天気が良いので、公園まで散歩に行きましょう。" \
--voice voices/ja_funasr_example --out konnichiwa.wav
# clone your own voice from a clip of up to 10 s and its exact transcript
python audio8_tts_litert.py --model-dir . --register-voice me.wav --ref-text "exact transcript of me.wav" --voice-out voices/me
python audio8_tts_litert.py --model-dir . --text "This sentence is spoken in my voice." --voice voices/me --out me_out.wav
# without a reference voice
python audio8_tts_litert.py --model-dir . --text "This utterance does not use a reference voice." --out noref.wav
The host loop runs every graph on the CPU through the ai-edge-litert Interpreter (--threads, default 4). It uses the sampler of the source repository's generation_config.json (temperature 0.7, top-p 0.9, top-k 50, at most 512 frames) and the vendor's repetition-aware re-draw; --temperature, --top-p, --top-k, --max-new-tokens and --seed change them. The source model card's own example samples at temperature 0.8, top-p 0.95, top-k 50. The host loop prefills a prompt in chunks through the smallest prefill signature that holds what is left of it (prefill_64 or prefill_256). XNNPACK packs the slow graph's weights separately for each of its three signatures, so the third one costs memory: on an Apple M4 Max with ai-edge-litert 2.2.0, generating the frames of four test sentences peaked at 2,074.7 MiB of resident memory, against 1,670.6 MiB with this repository's first slow file, which had no prefill_64 (2026-11-03). The Android app keeps one packed copy in a weight cache file (below).
The app under android/ (Kotlin, LiteRT 2.2.0 CompiledModel API) speaks a typed sentence in one of the two bundled voices, or in your own voice: "Record my voice (10 s)" records you reading a sentence it shows, and that sentence becomes the voice's transcript. Both AR graphs run on the CPU with 4 threads; the slow AR updates its KV cache in place and prefills a prompt in chunks of 256 positions, a last chunk of up to 64 positions through prefill_64. At load the app creates and warms both GPU codec decoders, runs the 128-frame one on one test window and compares its output with the CPU output of the same file on the same codes (that CPU output is bundled in the app); the GPU decodes when the correlation is 0.99 or more, the CPU otherwise. A sentence may have up to 49 tokens. Build, install and file push steps: android/README.md. The app's code is under the MIT license (android/LICENSE).
Changes since this repository's first release (2026-11-02), measured in the app on both phones:
xnnpack/ folder of its cache directory (named by the SHA-256 of slow_ar_int8.tflite and the LiteRT version; 365,205,496 bytes on the Pixel 11 Pro, 365,259,896 on the Galaxy S26) and maps that file at every later load. Peak resident memory of the app process after load (VmHWM): 2.01 GB on the Pixel 11 Pro and 2.31 GB on the Galaxy S26, against 3.13 and 3.39 GB with the first release's slow file without a cache (2026-11-03). Each load hashes the 558 MB slow file first: 0.3 to 1.0 s on the Pixel 11 Pro, 0.2 s on the Galaxy S26. The first load after installing also writes the cache and compiles the GPU's programs: 23.9 and 45.4 s in two first loads on the Pixel 11 Pro. Android may delete the cache folder when storage runs low, and the next load writes the file again; if the slow graph cannot be created with the cache, the app creates it without.prefill_64 call instead of a padded prefill_256 call. For an English sentence of 12 tokens (283 positions) and a Japanese one of 48 tokens (319 positions) in a recorded voice, the prompt prefill took 235 and 233 ms on the Galaxy S26 against 339 and 330 ms, and 514 and 745 ms on the Pixel 11 Pro against 972 and 1,163 ms (medians of 6 runs, 2026-11-03). In each Pixel 11 Pro process (12 sentences, in both forms), the slow AR slowed from about 12 to 16–27 ms per frame from the 4th, 5th or 6th sentence on, under CPU frequency caps.FRAME_CAP line; the host loop prints a [cap] line.| File | Bytes | Role |
|---|---|---|
slow_ar_int8.tflite |
558,303,696 | Slow AR, 24 layers. Signatures prefill_256 (up to 256 prompt positions per call), prefill_64 (up to 64) and decode; the three share one set of weights. The KV cache of 1,024 positions is 48 graph inputs and outputs. Returns the 4,097 logits the sampler picks from (4,096 semantic tokens and the end of speech) and the hidden state for the fast AR. Dynamic int8 weights (per channel), int8 embedding tables. |
fast_ar_int8.tflite |
67,780,320 | Fast AR step, 4 layers, with a 10-position KV cache as graph input and output. Called 10 times per frame: position 0 takes the slow AR's hidden state, positions 1 to 9 the previous codebook token. Dynamic int8 weights, int8 embedding table. |
codec_decoder_fp16_T128.tflite |
261,633,152 | Codec decoder for up to 128 frames (5.9 s) per call, float16 weights, rewritten for mobile GPUs (see Codec decoder on the GPU). |
codec_decoder_fp16_T192.tflite |
261,829,760 | The same decoder for up to 192 frames (8.9 s) per call. |
codec_encoder_fp16_10s.tflite |
419,269,280 | Codec encoder for voice registration: 10.03 s of 44.1 kHz audio to 10 × 216 codes. |
tokenizer.json |
12,217,872 | The source repository's tokenizer (Qwen2 BPE with the semantic and role tokens), unchanged. |
voices/en_librispeech_1272/, voices/ja_funasr_example/ |
7,310 | Two reference voices (codec codes and transcript): an English LibriSpeech dev-clean speaker (CC BY 4.0) and the Japanese example clip of FunAudioLLM/Fun-ASR-Nano-2512 (Apache-2.0). |
audio8_tts_litert.py |
13,505 | Python host loop: prompt, chunked prefill, the vendor's sampler, the fast AR loop, windowed codec decode, voice registration. |
android/ |
Android sample app with its own README and license. | |
LICENSE, NOTICE |
11,357, 95 | The source repository's files. |
SHA256SUMS |
SHA-256 of every other file. |
The codec checkpoint and the codec code are the same files in the Realistic and Preview source repositories, so the codec graphs come from litert-community/Audio8-TTS-Preview-0.6b: the encoder unchanged, the two decoders rewritten. The two voices are that repository's files.
The two codec decoders as first published in the Preview repository gave a wrong output on the LiteRT GPU accelerator: on a Pixel 11 Pro and a Galaxy S26 (OpenCL, LiteRT 2.2.0 and 2.3.2) and on an Apple M4 Max (Metal), at the GPU's default precision (float16) and at float32. The Preview repository has carried the rewritten decoders since 2026-11-02 (commit 3d59a74c). The decoders here carry three graph changes:
On an Apple M4 Max CPU, the rewritten decoders give the original's output byte for byte on all 32 test code sequences (15 for 128 frames, 17 for 192). On the GPUs of both phones at the default precision, their output correlates with that CPU output at 0.9998 or more on every speech test sequence, and the Pixel 11 Pro run in Accuracy decodes on the GPU. The rewrite is codec_gpu_rewrite.py in hf-to-litertlm (audio8_tts_work/).
The reference is the vendor's own code at revision 1e857568 in float32 on the CPU (PyTorch 2.12.1, transformers 4.57.6), with the sampler of generation_config.json and seeded draws. The test set has 30 sentences in six of the 23 languages: 6 English and 6 Japanese with a reference voice of the same language; 3 each in Arabic, Portuguese, Russian and Turkish without a reference, and 1 each of those four with the English reference voice; one English and one Japanese without a reference. Set B repeats set A with every sampling seed raised by 100,000. Each output was transcribed by whisper large-v3-turbo and compared with its text (errors counted in words, in characters for Japanese), compared with the reference clip by TitaNet-L (speaker cosine), and scored by UTMOS22.
| Run | Errors en / ja / ar / pt / ru / tr | Speaker cosine: en / ja / other languages with the English voice | UTMOS22 | Ended by end of speech |
|---|---|---|---|---|
| Reference, set A | 1 / 0 / 6 / 4 / 0 / 1 | 0.708 / 0.756 / 0.530 | 3.905 | 30 of 30 |
| Reference, set B | 3 / 0 / 4 / 2 / 1 / 1 | 0.712 / 0.756 / 0.558 | 3.964 | 30 of 30 |
| This repository, Mac CPU, set A | 6 / 0 / 4 / 0 / 0 / 0 | 0.709 / 0.764 / 0.574 | 3.904 | 30 of 30 |
| This repository, Mac CPU, set B | 2 / 0 / 8 / 0 / 0 / 1 | 0.706 / 0.762 / 0.566 | 3.911 | 30 of 30 |
| This repository, Pixel 11 Pro app, set A | 1 / 0 / 4 / 1 / 0 / 1 | 0.714 / 0.755 / 0.524 | 3.927 | 30 of 30 |
| This repository, Pixel 11 Pro app, set B | 1 / 0 / 4 / 2 / 0 / 0 | 0.698 / 0.746 / 0.571 | 3.960 | 30 of 30 |
The errors are out of 94 English words, 162 Japanese characters, 33 Arabic, 41 Portuguese, 31 Russian and 28 Turkish words per set; N below is that count. Two pass rules:
The Mac run used ai-edge-litert 2.2.0 (Interpreter, 4 threads) on an Apple M4 Max, the slow graph at a KV cache of 2,048 positions and the Preview's codec decoders. With a KV cache of 1,024 positions, the slow graph gives bit-identical logits and hidden states on the 38 prompts tested (the 30 above and 8 more), and the rewritten decoders give byte-identical CPU output. The phone run is the app with the files of this repository's first release, the codec on the GPU and the slow AR's KV cache in the app's ping-pong mode (two buffer sets, each call reading one and writing the other); updating that cache in place gives the same codes on both phones (the two speed-test sentences, every run).
The slow file of this release is the same export with a prefill_64 signature added. On the Mac CPU its prefill_256 and decode give bit-identical outputs to the first file's on the 38 prompts; a prompt prefilled as 256 positions plus one prefill_64 call leaves the same KV cache and logits as 256 positions plus a padded prefill_256 call (12 of 12 runs), a prompt of up to 64 positions gives the same outputs through prefill_64 as through a padded prefill_256 (4 of 4), and the host loop makes the same codes with either file (4 of 4 sentences). In the app, the current file read from its weight cache made the same codes as the first file without a cache (12 of 12 runs on each phone).
Not shipped, measured on the same test: int4 weights in the slow AR. Four int4 recipes made an AR frame (one slow decode, ten fast steps) 4.6 to 15.2 % faster in our runner on the two phones on one thread, and none passes the rule above on the Mac CPU. The first (int4 in blocks of 32 on every linear layer) fails it on Turkish (8 errors against a bar of 4), Arabic (14 against 12) and UTMOS22 (3.834 against 3.864). The other three, each tested as a file with this repository's int8 prefill and its own int4 decode, fail it on set B's UTMOS22 (3.750, 3.801 and 3.850 against 3.864). The Preview repository's int8 codec decoder lowers UTMOS22 from 3.904 to 3.690 on the same codes, on every one of the 30 sentences.
Measured with LiteRT 2.2.0 on a Galaxy S26 (SM-S942Q, SM8850, Android 16) and a Pixel 11 Pro (Tensor G6, Android 17).
The files of this repository: both AR graphs on the CPU (4 threads), the slow AR's KV cache updated in place and its weights read from the weight cache, the codec decoder on the GPU. Each sentence was spoken 6 times, 3 in each of two app processes (2026-11-03).
| Phone | Sentence | Speech | Real-time factor, median (range) | Text to samples | Prompt prefill | Slow AR per frame | Fast AR per frame (10 calls) | Codec decode |
|---|---|---|---|---|---|---|---|---|
| Galaxy S26 | English | 6.36 s, 137 frames | 0.797 (0.711 to 0.858) | 5.07 s | 208 ms | 10.53 ms | 13.16 ms | 1.51 s, one 192-frame call |
| Galaxy S26 | Japanese | 5.29 s, 114 frames | 0.755 (0.712 to 0.818) | 4.00 s | 207 ms | 10.49 ms | 13.61 ms | 1.00 s, one 128-frame call |
| Pixel 11 Pro | English | 5.48 s, 118 frames | 0.853 (0.843 to 0.858) | 4.67 s | 317 ms | 11.93 ms | 15.66 ms | 1.03 s, one 128-frame call |
| Pixel 11 Pro | Japanese | 5.11 s, 110 frames | 0.882 (0.875 to 0.892) | 4.50 s | 321 ms | 12.27 ms | 15.77 ms | 1.03 s, one 128-frame call |
Our benchmark runner on the same phones, LiteRT 2.2.0 CompiledModel API. A value is the median time per call (writing the inputs, running, reading the first output back; the AR graphs' KV state stays on the device), with the number of timed calls in parentheses. CPU: XNNPACK, 4 threads, read from the runner's process in every run. GPU: the LiteRT GPU accelerator (OpenCL) at its default precision (float16). NPU: the Galaxy S26's NPU, through graphs compiled ahead of time for SM8850 with QAIRT 2.47. Our runner's CPU rows and the GPU rows of the two codec decoders were measured on 2026-11-08, the other rows on 2026-11-01 and 2026-11-02.
In this card's earlier versions the CPU rows of this table ran on one thread, not four: our runner passed the thread count under an option name that LiteRT 2.2.0 does not read.
| Graph and call | Backend | Galaxy S26 | Pixel 11 Pro | File shipped here |
|---|---|---|---|---|
slow_ar_int8.tflite, decode |
CPU | 9.26 ms (10), 9.07 ms (10) | 14.93 ms (10), 15.93 ms (10) | yes |
slow_ar_int8.tflite, prefill_256 |
CPU | 142.7 ms (6) | 336.9 ms (6) | yes |
slow_ar_int8.tflite, prefill_64 |
CPU | 38.0 ms (6) | 106.0 ms (6) | yes |
The same slow AR with a KV cache of 2,048 positions, decode |
CPU | 12.38 ms (10), 12.05 ms (10) | 19.96 ms (10), 23.38 ms (10) | no |
The same, prefill_256 |
CPU | 238.8 ms (3) | 677.5 ms (3) | no |
fast_ar_int8.tflite, step |
CPU | 1.099 ms (20) | 7.252 ms (20) | yes |
Slow AR rewritten for the GPU (float16, KV 2,048), decode |
GPU | 21.0 ms (10) | 53.2 ms (10) | no |
The same, prefill_256 |
GPU | 106.8 ms (5) | 242.1 ms (5) | no |
Fast AR rewritten for the GPU (float16), step |
GPU | 3.683 ms (20) | 19.935 ms (20) | no |
The GPU slow AR compiled for the NPU, decode |
NPU | 21.54 ms (10) | not run | no |
The same, prefill_256 |
NPU | 160.2 ms (5) | not run | no |
The GPU fast AR compiled for the NPU, step |
NPU | 2.306 ms (30) | not run | no |
codec_decoder_fp16_T128.tflite |
GPU | 927.0 ms (6) | 1,155.8 ms (8) | yes |
codec_decoder_fp16_T192.tflite |
GPU | 1,430.0 ms (4) | 1,785.7 ms (8) | yes |
codec_decoder_fp16_T128.tflite, in the app (its CPU fallback) |
CPU | 2,820.9 to 2,859.1 ms (3) | 6,305.3 to 6,490.5 ms (3) | yes |
| The rewritten T128 decoder compiled for the NPU | NPU | 1,742.6 ms (8) | not run | no |
codec_encoder_fp16_10s.tflite |
CPU | no value (below) | no value (below) | yes |
On the Galaxy S26 the GPU rows of the two codec decoders are the first calls from a rested phone: one call on one input, then three timed calls, in each of two runs. In longer runs that phone lowered its CPU clock limits before the timed calls ended, and in the first of the two T192 runs it lowered a clock limit 4.1 s after the graph was ready, so that row counts the four timed calls that ended before it. On the Pixel 11 Pro they are eight timed calls on one input in one run, after one call on each of the 15 (T128) or 17 (T192) test inputs and two warm-up calls, without a CPU clock limit; in the same session the first calls from the rested phone took 1,153.2 and 1,660.4 ms (6 and 2 calls). The encoder row has no value on either phone: on four threads each phone lowered the clock limit of its large cores before the first timed call ended, in all four runs (0.1 to 1.4 s after the graph was ready on the Galaxy S26, 5.9 to 11.7 s on the Pixel 11 Pro). The CPU row of codec_decoder_fp16_T128.tflite is the app's own call on real codes, one per app process after a warm-up call (2026-11-02). This card's first release listed our runner's CPU calls of the Preview's float16 and int8 T128 decoders instead; the phones' CPU frequency was capped while they ran, so they are left out.
A frame takes one slow decode call and ten fast step calls. At a KV cache of 2,048 positions that is 12.38 + 10 × 1.099 ms on the Galaxy S26's CPU against 21.0 + 10 × 3.683 ms for the GPU-rewritten AR graphs (not shipped), and 19.96 + 10 × 7.252 ms on the Pixel 11 Pro's CPU against 53.2 + 10 × 19.935 ms. On one thread our runner's step took 1.01 ms on the Galaxy S26 and 2.04 ms on the Pixel 11 Pro, against 1.10 ms and 7.2 to 8.0 ms on four threads in the same sessions (medians of 50 calls in each of two runs, 2026-11-08); in the app, on four threads, the fast AR took 13.16 to 15.77 ms per frame of ten calls (the app table above). We did not look into why the runner's four-thread step is slower than the app's on the Pixel 11 Pro. In a separate session on the Galaxy S26, the per-call medians of a frame added up to 44.7 ms for those graphs compiled for the NPU and 24.4 ms for the CPU graphs on one thread. So the AR graphs here run on the CPU, at a KV cache of 1,024 positions. A GPU prefill with a CPU decode was measured for this release and not shipped: the GPU-rewritten slow AR's prefill_256 at a KV cache of 1,024 positions, with its 48 KV tensors read back for the CPU graph, added up to 85.7 ms on the Galaxy S26 (medians: GPU body 75.9 + KV read back 6.8 + embedding 3.0 ms) and 327.9 ms on the Pixel 11 Pro (177.0 + 144.6 + 6.3 ms), against 220.5 and 607.4 ms for the CPU prefill_256 on one thread in the same sessions (142.7 and 336.9 ms on four threads, in the table). The app's CPU prompt prefill was 175 and 327 ms in this card's first release, so the GPU path would save at most 89 ms per prompt on the Galaxy S26 and nothing on the Pixel 11 Pro.
NPU (our runner, 2026-11-02 and 2026-11-03). We found no NPU form to ship. On the Galaxy S26, with graphs compiled ahead of time for its NPU:
prefill_256 (160.2 ms in the table) was faster than on the CPU but slower than on the GPU, whose prefill this release does not use either (above), and a frame was slower than on the CPU (above). One graph holding a frame's ten fast steps and the sampler took 19.76 and 19.26 ms per frame (medians of 30 calls in each of two runs) against 9.48 and 9.55 ms for ten steps of the shipped CPU graph on one thread without the sampler, which the app runs in Kotlin (10 × 1.099 ms on four threads, in the table); its codes differed from the CPU's in 29 of 76 test frames (the NPU compiler ran its float operations at float16; the same graph on the phone's CPU gave the CPU's codes in all 76). The AR graphs were not run on the speech test above.On the Pixel 11 Pro (LiteRT 2.3.0 and its NPU plugin for the Tensor chip, which compiles on the phone), the compiler refused the T128 decoder with float16 weights and with int8 weights (UNIMPLEMENTED: DeduceInputSlice not non-identity InternalPadding not yet implemented.); we did not try ahead-of-time compilation for it.
[cap] line, and the app says so in its message (an 80-token English text reached the cap in 2 of 2 runs on the Mac and lost its last sentence).Source: Edge0/Audio8-TTS-Realistic-Air (the old name Edge0/Audio8-TTS-Realistic-0.6b redirects to it) at revision 1e857568bfd4f02931aca3988ee773a44d2d26d3; the three later commits on its main branch (925bbd4c on 2026-11-01, 7c82ad18 and 23c305cc on 2026-11-02) change only README.md and assets/realistic-hero.svg. The export, quantization and decoder rewrite commands are in the Audio8-TTS-Realistic-0.6b section of REPRODUCE.md in hf-to-litertlm, with the scripts under audio8_tts_work/. Run again on 2026-11-03, they rebuilt slow_ar_int8.tflite, fast_ar_int8.tflite and both codec decoders with the SHA-256 of the files here.
Apache-2.0, inherited from the source model by Edge0 (LICENSE, a copy of the source repository's file). Attribution is in NOTICE. The Android app under android/ is under the MIT license (android/LICENSE).
Changes from the original work. The slow and fast AR transformers were exported from the checkpoint's weights to LiteRT graphs with the KV cache as graph inputs and outputs, and their weights quantized to dynamic int8. The codec graphs come from the Preview conversion; the decoders' graphs were rewritten as described above. The tokenizer is unchanged. The host loop and the app port the vendor's prompt format and sampler.