Add Pocket (300M, CPU) variant: int8/fp32 ONNX, PyTorch checkpoint, vocab, CPU inference script, results; README variants table
Browse files- README.md +63 -1
- config.json +13 -0
- pocket/model.pt +3 -0
- pocket/model_fp32.onnx +3 -0
- pocket/model_int8.onnx +3 -0
- pocket/results.json +121 -0
- pocket/transcribe_pocket.py +90 -0
- pocket/vocab.json +0 -0
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
|
|
|