UncleanCode commited on
Commit
19eb87a
·
verified ·
1 Parent(s): e4c97d2

Add Pocket (300M, CPU) variant: int8/fp32 ONNX, PyTorch checkpoint, vocab, CPU inference script, results; README variants table

Browse files
README.md CHANGED
@@ -20,6 +20,7 @@ tags:
20
  - ctc
21
  - omnilingual-asr
22
  - streaming
 
23
  ---
24
 
25
  # Dynamic ASR
@@ -39,6 +40,66 @@ Dynamic ASR is Meta's [Omnilingual ASR](https://github.com/facebookresearch/omni
39
 
40
  Both are the same size (1B, ~1.95 GB bf16 state dict) and load the same way.
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  ## Results
43
 
44
  All test data is held out (never trained on). Scores are WER / CER after lower-casing and stripping punctuation; "no-diacritic" CER folds tone marks and dots. Decoding: greedy CTC with a Latin-script logit mask (the duplex setting).
@@ -180,7 +241,8 @@ Full fine-tune of all transformer layers (CNN feature encoder frozen), bf16 auto
180
  ## Licence and attribution
181
 
182
  - **Weights: CC-BY-SA-4.0.** Several training sources are CC-BY-SA-4.0 (WAXAL Media Trust subsets, Open.Bible, OpenSLR 70/86), so the fine-tuned weights are shared alike under CC-BY-SA-4.0. The base model omniASR_CTC_1B_v2 is Apache-2.0 (© Meta Platforms).
183
- - **Code (`transcribe.py`, `code/`): Apache-2.0** (`LICENSE-CODE`).
 
184
  - Attribution: Google WAXAL (Media Trust subsets), Google FLEURS (CC-BY-4.0), OpenSLR 70 Nigerian English and OpenSLR 86 Yoruba (Google, CC-BY-SA-4.0), Open.Bible / Biblica (CC-BY-SA-4.0), NaijaS2ST (CC-BY-4.0), IgboAPI (CC-BY-4.0), IgboSynCorp (CC0-1.0, Harvard Dataverse doi:10.7910/DVN/YB9FWK), Meta Omnilingual ASR (Apache-2.0), Qwen3 (Apache-2.0).
185
 
186
  ## Citation
 
20
  - ctc
21
  - omnilingual-asr
22
  - streaming
23
+ - onnx
24
  ---
25
 
26
  # Dynamic ASR
 
40
 
41
  Both are the same size (1B, ~1.95 GB bf16 state dict) and load the same way.
42
 
43
+ ## Variants
44
+
45
+ | folder | model | parameters | files | runtime |
46
+ |---|---|---|---|---|
47
+ | `real/` (**default**) | omniASR_CTC_1B_v2 fine-tuned on licensed real speech | 1B | `model.pt` (1.95 GB, bf16) | PyTorch / fairseq2 (`transcribe.py`) |
48
+ | `codeswitch-experimental/` | same, plus synthetic code-switched speech | 1B | `model.pt` (1.95 GB, bf16) | PyTorch / fairseq2 (`transcribe.py`) |
49
+ | `pocket/` | omniASR_CTC_300M_v2 student distilled from a 1B Dynamic ASR teacher | 300M | `model_int8.onnx` (367 MB), `model_fp32.onnx` (1.30 GB), `model.pt` (1.30 GB), `vocab.json` | onnxruntime on CPU (`pocket/transcribe_pocket.py`) |
50
+
51
+ The Pocket numbers below use a different scorer from the 1B tables in Results, so compare Pocket only with the teacher row in its own table.
52
+
53
+ ## Pocket (300M, CPU)
54
+
55
+ `pocket/` is a smaller model for CPU and offline use. It is Meta's `omniASR_CTC_300M_v2` (300M-parameter wav2vec2 CTC, Apache-2.0, same tokenizer and 20 ms frame rate as the 1B models), distilled from a 1B Dynamic ASR teacher. The teacher is an internal checkpoint (`real/` further fine-tuned with real-voice spliced code-switch clips); it is not one of the files in this repository. Checkpoint: P2, step 2500.
56
+
57
+ **Training.** Loss = CTC on the reference transcripts + KL divergence to the teacher's per-frame distribution; CNN feature encoder frozen. Data: the licensed real speech listed under Training data, code-switch clips spliced from that same licensed speech, and an oversample of 0.9 h of real code-switched training clips. No synthetic TTS audio and no African Voices data.
58
+
59
+ **Files**
60
+
61
+ | file | size | what it is |
62
+ |---|---|---|
63
+ | `pocket/model_int8.onnx` | 367 MB | int8-quantised ONNX (recommended for CPU) |
64
+ | `pocket/model_fp32.onnx` | 1.30 GB | fp32 ONNX |
65
+ | `pocket/model.pt` | 1.30 GB | PyTorch checkpoint: a dict with `model` (state dict for `omniASR_CTC_300M_v2`), `step` and the training `args` |
66
+ | `pocket/vocab.json` | 127 KB | CTC vocabulary (index 0 = blank, 1–3 = special tokens) |
67
+ | `pocket/transcribe_pocket.py` | | CPU inference: onnxruntime, greedy CTC decoding, 16 kHz mono input |
68
+ | `pocket/results.json` | | the scores and CPU benchmark below |
69
+
70
+ ONNX graph: input `wav` (float32, `[1, samples]`, 16 kHz mono), output `logprobs` (`[1, frames, 10288]`).
71
+
72
+ **Usage**
73
+
74
+ ```bash
75
+ pip install onnxruntime numpy soundfile scipy huggingface_hub
76
+ huggingface-cli download Modularcomputing/dynamic-asr --include "pocket/model_int8.onnx" "pocket/vocab.json" "pocket/transcribe_pocket.py" --local-dir .
77
+ python pocket/transcribe_pocket.py audio.wav # int8 (default)
78
+ python pocket/transcribe_pocket.py audio.wav --model pocket/model_fp32.onnx # fp32
79
+ ```
80
+
81
+ Any wav is mixed to mono and resampled to 16 kHz; clips over ~20 s are cut at quiet points and transcribed in pieces. Output format is the same as the 1B models.
82
+
83
+ **Results** (CER, greedy CTC, no language model). *gdev* = the general dev set (WAXAL validation + OpenSLR-70, 187 clips); it was used for checkpoint selection, so it is a dev set, not a test set. *Real code-switch* = the held-out 94-clip real code-switched test set described above, never used for training or selection. Scoring: NFC, lowercase, punctuation and apostrophes removed, English number words to digits, Igbo tone marks removed from both sides, Yoruba tone marks kept.
84
+
85
+ | model | English | Hausa | Igbo | Pidgin | Yoruba | gdev mean | real code-switch |
86
+ |---|---|---|---|---|---|---|---|
87
+ | Pocket int8 ONNX | 0.0571 | 0.0522 | 0.0595 | 0.0963 | 0.1359 | 0.0802 | 0.1558 |
88
+ | Pocket fp32 ONNX | 0.0571 | 0.0514 | 0.0595 | 0.0925 | 0.1350 | 0.0791 | 0.1558 |
89
+ | 1B teacher (greedy) | 0.0294 | 0.0489 | 0.0598 | 0.0823 | 0.1288 | 0.0698 | 0.1375 |
90
+
91
+ - int8 adds about 0.001 to the gdev mean (mostly Pidgin) and nothing on real code-switch.
92
+ - English is the largest gap to the teacher (0.057 vs 0.029).
93
+
94
+ **CPU speed** (Intel Xeon Platinum 8480+, batch 1, 20 clips totalling 110 s, one fresh process per setting)
95
+
96
+ | model | threads | RTF | median latency per clip | peak RAM |
97
+ |---|---|---|---|---|
98
+ | int8 ONNX | 4 | 0.071 | 358 ms | 939 MB |
99
+ | fp32 ONNX | 4 | 0.186 | 952 ms | 2287 MB |
100
+
101
+ Pocket shares the limitations listed below, with higher error than the 1B models.
102
+
103
  ## Results
104
 
105
  All test data is held out (never trained on). Scores are WER / CER after lower-casing and stripping punctuation; "no-diacritic" CER folds tone marks and dots. Decoding: greedy CTC with a Latin-script logit mask (the duplex setting).
 
241
  ## Licence and attribution
242
 
243
  - **Weights: CC-BY-SA-4.0.** Several training sources are CC-BY-SA-4.0 (WAXAL Media Trust subsets, Open.Bible, OpenSLR 70/86), so the fine-tuned weights are shared alike under CC-BY-SA-4.0. The base model omniASR_CTC_1B_v2 is Apache-2.0 (© Meta Platforms).
244
+ - **Code (`transcribe.py`, `code/`, `pocket/transcribe_pocket.py`): Apache-2.0** (`LICENSE-CODE`).
245
+ - **Pocket weights (`pocket/`): CC-BY-SA-4.0**, like the 1B weights. Base model omniASR_CTC_300M_v2 is Apache-2.0 (© Meta Platforms).
246
  - Attribution: Google WAXAL (Media Trust subsets), Google FLEURS (CC-BY-4.0), OpenSLR 70 Nigerian English and OpenSLR 86 Yoruba (Google, CC-BY-SA-4.0), Open.Bible / Biblica (CC-BY-SA-4.0), NaijaS2ST (CC-BY-4.0), IgboAPI (CC-BY-4.0), IgboSynCorp (CC0-1.0, Harvard Dataverse doi:10.7910/DVN/YB9FWK), Meta Omnilingual ASR (Apache-2.0), Qwen3 (Apache-2.0).
247
 
248
  ## Citation
config.json CHANGED
@@ -23,6 +23,19 @@
23
  "path": "codeswitch-experimental/model.pt",
24
  "trained_on": "same real speech + synthetic code-switched audio (Flash-DPO TTS)",
25
  "status": "experimental"
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  }
27
  },
28
  "decoding": {
 
23
  "path": "codeswitch-experimental/model.pt",
24
  "trained_on": "same real speech + synthetic code-switched audio (Flash-DPO TTS)",
25
  "status": "experimental"
26
+ },
27
+ "pocket": {
28
+ "path": "pocket/model_int8.onnx",
29
+ "alt_paths": [
30
+ "pocket/model_fp32.onnx",
31
+ "pocket/model.pt"
32
+ ],
33
+ "vocab": "pocket/vocab.json",
34
+ "architecture": "omniASR_CTC_300M_v2 student distilled from a 1B Dynamic ASR teacher",
35
+ "runtime": "onnxruntime CPU, greedy CTC, 16 kHz mono",
36
+ "script": "pocket/transcribe_pocket.py",
37
+ "results": "pocket/results.json",
38
+ "status": "CPU variant"
39
  }
40
  },
41
  "decoding": {
pocket/model.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:315a5f44e5b47e6503f22cfaf696caf80048be5aee21ecbf2d04d0dfb2b8ec6b
3
+ size 1304038762
pocket/model_fp32.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8fe64e7dd2b52931eaa6f957ce084f3af9392d36a561a38d5e2a6539a866ed66
3
+ size 1304478206
pocket/model_int8.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5431eba442f651351e69e1bf91a467253a39350b465595a9b04a6f5bfb7aa33f
3
+ size 366630967
pocket/results.json ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model": "Dynamic ASR Pocket",
3
+ "architecture": "omniASR_CTC_300M_v2 student distilled from a 1B Dynamic ASR teacher (omniASR_CTC_1B_v2 fine-tune)",
4
+ "checkpoint": "P2, step 2500",
5
+ "decoding": "greedy CTC, no language model",
6
+ "input": "16 kHz mono float32",
7
+ "scoring": "CER after: NFC, lowercase, punctuation and apostrophes to spaces, English number words to digits, dot-below variants unified, Igbo tone marks stripped from both sides (Yoruba tone marks kept)",
8
+ "eval_sets": {
9
+ "gdev": "general dev set (WAXAL validation + OpenSLR-70; 187 clips, 1269 s; eng/hau/ibo/pcm/yor). Used for checkpoint selection, so it is a dev set, not a test set.",
10
+ "real_codeswitch": "held-out real code-switched speech test set (94 clips, 758 s); never used for training or selection"
11
+ },
12
+ "results": {
13
+ "pocket_int8_onnx": {
14
+ "gdev_cer": {
15
+ "eng": 0.0571,
16
+ "hau": 0.0522,
17
+ "ibo": 0.0595,
18
+ "pcm": 0.0963,
19
+ "yor": 0.1359,
20
+ "mean": 0.0802
21
+ },
22
+ "real_codeswitch_cer": 0.1558,
23
+ "real_codeswitch_wer": 0.4311,
24
+ "real_codeswitch_english_word_recall": 0.3009
25
+ },
26
+ "pocket_fp32_onnx": {
27
+ "gdev_cer": {
28
+ "eng": 0.0571,
29
+ "hau": 0.0514,
30
+ "ibo": 0.0595,
31
+ "pcm": 0.0925,
32
+ "yor": 0.135,
33
+ "mean": 0.0791
34
+ },
35
+ "real_codeswitch_cer": 0.1558,
36
+ "real_codeswitch_wer": 0.4333,
37
+ "real_codeswitch_english_word_recall": 0.3009
38
+ },
39
+ "pocket_pytorch_fp32": {
40
+ "gdev_cer": {
41
+ "eng": 0.0571,
42
+ "hau": 0.0514,
43
+ "ibo": 0.0595,
44
+ "pcm": 0.0925,
45
+ "yor": 0.135,
46
+ "mean": 0.0791
47
+ },
48
+ "real_codeswitch_cer": 0.1558,
49
+ "real_codeswitch_wer": 0.4333,
50
+ "real_codeswitch_english_word_recall": 0.3009
51
+ },
52
+ "teacher_1b_greedy": {
53
+ "gdev_cer": {
54
+ "eng": 0.0294,
55
+ "hau": 0.0489,
56
+ "ibo": 0.0598,
57
+ "pcm": 0.0823,
58
+ "yor": 0.1288,
59
+ "mean": 0.0698
60
+ },
61
+ "real_codeswitch_cer": 0.1375,
62
+ "real_codeswitch_wer": 0.3968,
63
+ "real_codeswitch_english_word_recall": 0.4248
64
+ }
65
+ },
66
+ "sizes_bytes": {
67
+ "model_int8.onnx": 366630967,
68
+ "model_fp32.onnx": 1304478206,
69
+ "model.pt": 1304038762
70
+ },
71
+ "cpu_benchmark": {
72
+ "cpu": "Intel Xeon Platinum 8480+",
73
+ "batch": 1,
74
+ "clips": 20,
75
+ "audio_s": 110.1,
76
+ "runs": [
77
+ {
78
+ "model": "pocket_int8.onnx",
79
+ "threads": 4,
80
+ "rtf": 0.0706,
81
+ "ms_median": 357.7,
82
+ "ms_p90": 509.3,
83
+ "peak_rss_mb": 939.1,
84
+ "load_s": 1.02
85
+ },
86
+ {
87
+ "model": "pocket_int8.onnx",
88
+ "threads": 2,
89
+ "rtf": 0.133,
90
+ "ms_median": 693.8,
91
+ "ms_p90": 947.1,
92
+ "peak_rss_mb": 935.2,
93
+ "load_s": 1.0
94
+ },
95
+ {
96
+ "model": "pocket_int8.onnx",
97
+ "threads": 1,
98
+ "rtf": 0.139,
99
+ "ms_median": 708.1,
100
+ "ms_p90": 995.2,
101
+ "peak_rss_mb": 935.2,
102
+ "load_s": 0.92
103
+ },
104
+ {
105
+ "model": "pocket_fp32.onnx",
106
+ "threads": 4,
107
+ "rtf": 0.1857,
108
+ "ms_median": 952.1,
109
+ "ms_p90": 1294.9,
110
+ "peak_rss_mb": 2287.3,
111
+ "load_s": 3.21
112
+ }
113
+ ]
114
+ },
115
+ "notes": [
116
+ "The 1B teacher is an internal checkpoint used for distillation; it is not one of the model files in this repository.",
117
+ "English is the largest gap between Pocket and the teacher."
118
+ ],
119
+ "training_data": "licensed real speech (same sources as the 1B models), real-voice splice code-switch clips built from that licensed speech, and an oversample of 0.9 h of real code-switched training clips; no synthetic TTS audio; no African Voices data",
120
+ "distillation": "CTC loss on references + KL divergence to the 1B teacher's per-frame distribution; CNN feature encoder frozen"
121
+ }
pocket/transcribe_pocket.py ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Dynamic ASR Pocket (300M, CPU): offline transcription with onnxruntime and greedy CTC decoding.
3
+
4
+ pip install onnxruntime numpy soundfile scipy
5
+ python transcribe_pocket.py clip.wav [more.wav ...] # int8 model (default)
6
+ python transcribe_pocket.py clip.wav --model model_fp32.onnx # fp32 model
7
+
8
+ Input: any wav; it is mixed to mono and resampled to 16 kHz. Clips longer than ~20 s are cut at quiet points.
9
+ Model files are looked up next to this script: model_int8.onnx (or model_fp32.onnx) and vocab.json.
10
+ Code licence: Apache-2.0. Model weights: CC-BY-SA-4.0.
11
+ """
12
+ import argparse, json, os, time
13
+ import numpy as np
14
+
15
+ HERE = os.path.dirname(os.path.abspath(__file__))
16
+ SR = 16000
17
+
18
+
19
+ class PocketASR:
20
+ def __init__(self, model=None, vocab=None, threads=4):
21
+ import onnxruntime as ort
22
+ so = ort.SessionOptions()
23
+ so.intra_op_num_threads = threads
24
+ so.inter_op_num_threads = 1
25
+ so.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
26
+ self.sess = ort.InferenceSession(model or os.path.join(HERE, "model_int8.onnx"), so,
27
+ providers=["CPUExecutionProvider"])
28
+ v = json.load(open(vocab or os.path.join(HERE, "vocab.json"), encoding="utf-8"))
29
+ self.pieces = v["pieces"] # index 0 = CTC blank; 1-3 = special tokens (never emitted)
30
+
31
+ def logprobs(self, wav):
32
+ # input "wav": float32 [1, T] at 16 kHz -> output "logprobs": [1, frames, vocab]
33
+ return self.sess.run(None, {"wav": np.asarray(wav, dtype=np.float32)[None]})[0][0]
34
+
35
+ def greedy(self, lp):
36
+ out, prev = [], -1
37
+ for p in lp.argmax(-1):
38
+ if p != prev and p > 3:
39
+ out.append(self.pieces[p])
40
+ prev = p
41
+ return " ".join("".join(out).split())
42
+
43
+ def transcribe(self, wav):
44
+ if len(wav) < SR // 10:
45
+ return ""
46
+ return self.greedy(self.logprobs(wav))
47
+
48
+
49
+ def load_wav(path):
50
+ import soundfile as sf
51
+ y, sr = sf.read(path, dtype="float32", always_2d=True)
52
+ y = y.mean(1)
53
+ if sr != SR:
54
+ from math import gcd
55
+ from scipy.signal import resample_poly
56
+ g = gcd(sr, SR)
57
+ y = resample_poly(y, SR // g, sr // g).astype(np.float32)
58
+ return y
59
+
60
+
61
+ def transcribe_long(asr, y, win=20.0):
62
+ """Audio over ~20 s: cut at the quietest 100 ms in the last 4 s of each window, transcribe pieces, join."""
63
+ if len(y) <= win * SR:
64
+ return asr.transcribe(y)
65
+ out, s = [], 0
66
+ while s < len(y):
67
+ e = min(len(y), s + int(win * SR))
68
+ if e < len(y):
69
+ lo = s + int((win - 4) * SR)
70
+ fr = y[lo:e][: (e - lo) // 1600 * 1600].reshape(-1, 1600)
71
+ e = lo + int(np.argmin((fr ** 2).mean(1))) * 1600 + 800
72
+ out.append(asr.transcribe(y[s:e]))
73
+ s = e
74
+ return " ".join(t for t in out if t)
75
+
76
+
77
+ if __name__ == "__main__":
78
+ ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
79
+ ap.add_argument("wav", nargs="+")
80
+ ap.add_argument("--model", default=None, help="path to model_int8.onnx (default) or model_fp32.onnx")
81
+ ap.add_argument("--vocab", default=None)
82
+ ap.add_argument("--threads", type=int, default=4)
83
+ a = ap.parse_args()
84
+ asr = PocketASR(a.model, a.vocab, a.threads)
85
+ for p in a.wav:
86
+ y = load_wav(p)
87
+ t = time.perf_counter()
88
+ txt = transcribe_long(asr, y)
89
+ dt = time.perf_counter() - t
90
+ print(f"{p}\t{txt}\t[{len(y) / SR:.1f} s audio, {dt * 1000:.0f} ms, RTF {dt / max(len(y) / SR, 1e-3):.3f}]")
pocket/vocab.json ADDED
The diff for this file is too large to render. See raw diff