mobilebytesensei/betterflow-indicconformer-ctc
Betterflow — IndicConformer CTC, ONNX int8 (8 Indic languages)
ONNX int8 exports of AI4Bharat's per-language IndicConformer hybrid CTC/RNNT checkpoints, prepared for on-device offline ASR through sherpa-onnx's OfflineRecognizer.from_nemo_ctc.
These are derived artefacts. We are not the authors of the weights.
Provenance and licence
Each language directory carries export-meta.json with the sha256 of the source `.nemo`, so provenance is checkable rather than asserted.
Please cite AI4Bharat for the underlying models.
Contents
Eight languages: gu pa bn mr ml te ta kn.
<lang>/model.int8.onnx 140,337,395 B (identical size across languages, DISTINCT weights)
<lang>/tokens.txt 67,605 B 5,633 entries
<lang>/export-meta.json source sha256, checksums, export settingsint8 sha256 — all eight distinct
The eight files are byte-identical in size because they share an architecture. They are different models — the checksums above are the check that matters.
⚠️ Two things that will silently break this
1. `normalize_type` must be `per_feature`. It is stamped on every graph. If it is stripped or set to "", the model loads, runs, and returns EMPTY with no error. Verify by decoding, never by a successful load.
Required ONNX metadata: vocab_size=5633, subsampling_factor=4, normalize_type=per_feature, feature_dim=80.
2. `tokens.txt` opens with Bengali tokens in every language directory — this is correct. The vocabulary is AI4Bharat's shared 22-language tokenizer (5,633 = 22 × 256 + 1) and the CTC head is multi-softmax. It looks like a packaging error and is not. The file is byte-identical across all eight (ee60967630213f31…); it is duplicated per directory so each bundle installs independently.
Unlike the 600m multilingual checkpoint, these per-language models did learn to suppress the other 21 blocks — measured 100% own-script output on all eight, 0/400 wrong-alphabet characters — so no `language_id` masking is required.
Measured quality
Scored on held-out ARTPARK-IISc/Vaani, 50 clips × 50 speakers per language, against the general-purpose multilingual Whisper bundle these replace.
Script purity is the headline, not WER. A 0% score means the incumbent was not producing the language's alphabet at all. Catastrophic failures fell from 93/400 to 5/400.
te and kn remain insertion-bound and are not recommended without further evaluation.
Runtime cost
⚠️ Peak RSS scales with utterance length — it is not a constant
Conformer self-attention is O(T²) in sequence length, so memory is a function of how long the speaker talks. Measured in-process (Android arm64, sherpa-onnx 1.13.4, int8, 2 threads, one process per utterance, ±1 MB reproducible):
Budget accordingly: a typical 3–10 s dictation utterance costs under 350 MB, but a two-minute monologue costs 2.7 GB. If you have a memory ceiling, cap utterance length — that is the cheapest control and it belongs in your app, not in the model.
The int8 build quantizes all 54 `Conv` nodes to ConvInteger. Leaving them in fp32 — as some other re-exports do — makes ONNX Runtime allocate an im2col workspace that scales with clip length, pushing peak RSS to 2,099–2,635 MB from the same nominal precision. If you are getting 4× the memory you expect from an int8 Conformer, check your Conv node dtypes.
Cost of that choice: ConvInteger is ~3.6× slower than fp32 Conv on CPU.
Not evaluated here
Device-side accuracy, languages outside the eight, streaming/partial decoding (these are offline models), and the RNNT head (not exported — it needs numba/CUDA).
