Card: name the training data, explain the CTC head, add profile analysis and figures
Browse files- .gitattributes +2 -0
- README.md +105 -27
- ctc_effect.png +3 -0
- profile.png +3 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
ctc_effect.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
profile.png filter=lfs diff=lfs merge=lfs -text
|
README.md
CHANGED
|
@@ -13,6 +13,8 @@ tags:
|
|
| 13 |
- speculative-decoding
|
| 14 |
- ctc
|
| 15 |
base_model: CohereLabs/cohere-transcribe-arabic-07-2026
|
|
|
|
|
|
|
| 16 |
pipeline_tag: automatic-speech-recognition
|
| 17 |
---
|
| 18 |
|
|
@@ -30,6 +32,94 @@ Two things are packaged here that the original does not have:
|
|
| 30 |
|
| 31 |
Together: **158.5 s β 4.82 s** on a 33-second Arabic clip, on a six-core desktop CPU.
|
| 32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
---
|
| 34 |
|
| 35 |
## Performance
|
|
@@ -63,10 +153,9 @@ int8, batch 16, 12 threads, preallocated KV cache, draft head enabled:
|
|
| 63 |
| 14-minute talk (ar) | 832.8 s | 98.36 s | 8.47Γ |
|
| 64 |
| **total** | **954.4 s** | **117.3 s** | **8.14Γ** |
|
| 65 |
|
| 66 |
-
### What the draft head is worth
|
| 67 |
|
| 68 |
-
|
| 69 |
-
*worse*:
|
| 70 |
|
| 71 |
| configuration | wall | vs int8 | WER from fp32 |
|
| 72 |
|---|---|---|---|
|
|
@@ -97,7 +186,8 @@ and the 14-minute recording β the only statistically meaningful one at 2,086 w
|
|
| 97 |
|
| 98 |
A GPU is both faster **and** more accurate β bf16 is a far lighter perturbation than int8. This repo
|
| 99 |
is for when there is no GPU. Worth knowing: the gap is 4.2Γ at batch 1 and 13.2Γ batched, because
|
| 100 |
-
only the GPU gains from being fed more work.
|
|
|
|
| 101 |
|
| 102 |
---
|
| 103 |
|
|
@@ -129,9 +219,7 @@ Loading takes about 10 s and needs ~2.9 GB of RAM.
|
|
| 129 |
|
| 130 |
---
|
| 131 |
|
| 132 |
-
## How
|
| 133 |
-
|
| 134 |
-
### int8 dynamic quantization
|
| 135 |
|
| 136 |
Every `nn.Linear` β 1.81 B of the 2.07 B parameters β is stored as int8. Convolutions, layer norms
|
| 137 |
and embeddings stay fp32. Weights use one per-tensor scale, `float32(max|W| / 127.5)`, with a zero
|
|
@@ -142,24 +230,11 @@ That last detail has a visible consequence: there is one activation scale for th
|
|
| 142 |
handing a layer a wider tensor quantizes it more coarsely. This is why the model's output shifts
|
| 143 |
slightly with batch size, and why speculative decoding cannot be bit-identical to greedy.
|
| 144 |
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
(**22.8 ms** against a 1069 ms decoder), and the decoder then verifies up to K+1 tokens for roughly
|
| 151 |
-
the price of one.
|
| 152 |
-
|
| 153 |
-
Architecture: one `ParakeetEncoderBlock` copied from `encoder.layers[47]`, then a projection, a
|
| 154 |
-
LayerNorm and an output matrix copied from the base model's `decoder.proj`, `decoder.norm` and
|
| 155 |
-
`proj_out`. Blank is `pad_token_id` = 2, which never appears in a transcript. Trained by
|
| 156 |
-
self-distillation on 18.3 hours of Egyptian Arabic labelled by the model itself β no annotation
|
| 157 |
-
involved. Acceptance 0.331, **2.66 tokens per decoder forward**.
|
| 158 |
-
|
| 159 |
-
**A wrong guess cannot produce a wrong word.** The verifier keeps only the prefix its own argmax
|
| 160 |
-
agrees with, then appends one token taken straight from the decoder, so a drafted token reaches the
|
| 161 |
-
output only if the decoder would have produced it anyway. A completely wrong draft degrades to
|
| 162 |
-
ordinary greedy decoding. What a bad draft costs is time, not accuracy.
|
| 163 |
|
| 164 |
---
|
| 165 |
|
|
@@ -176,6 +251,7 @@ ordinary greedy decoding. What a bad draft costs is time, not accuracy.
|
|
| 176 |
this model to CUDA raises `NotImplementedError`. For a GPU, use the base model in bf16.
|
| 177 |
* **int8 costs accuracy.** 2.06% WER from fp32. If you have the RAM and can spare the speed, fp32 is
|
| 178 |
more faithful; if you have a GPU, bf16 is both faster and more accurate.
|
|
|
|
| 179 |
* Inherits every limitation of the base model, including its language and domain coverage.
|
| 180 |
|
| 181 |
---
|
|
@@ -207,11 +283,13 @@ the weights bit-perfect.
|
|
| 207 |
|
| 208 |
## License and attribution
|
| 209 |
|
| 210 |
-
Apache 2.0, inherited from [CohereLabs/cohere-transcribe-arabic-07-2026](
|
| 211 |
|
| 212 |
**Changes made to the original** (Apache 2.0 Β§4b): weights of all `nn.Linear` modules quantized to
|
| 213 |
int8 as described above; a separately trained CTC draft head added; no change to the architecture,
|
| 214 |
the tokenizer, or the training data of the base model.
|
| 215 |
|
| 216 |
The base repository is **gated**. This derivative is not, so if you need the original weights,
|
| 217 |
-
please obtain them from Cohere Labs directly and accept their terms.
|
|
|
|
|
|
|
|
|
| 13 |
- speculative-decoding
|
| 14 |
- ctc
|
| 15 |
base_model: CohereLabs/cohere-transcribe-arabic-07-2026
|
| 16 |
+
datasets:
|
| 17 |
+
- oddadmix/lahgtna-v3-small
|
| 18 |
pipeline_tag: automatic-speech-recognition
|
| 19 |
---
|
| 20 |
|
|
|
|
| 32 |
|
| 33 |
Together: **158.5 s β 4.82 s** on a 33-second Arabic clip, on a six-core desktop CPU.
|
| 34 |
|
| 35 |
+

|
| 36 |
+
|
| 37 |
+
---
|
| 38 |
+
|
| 39 |
+
## The draft head, and why it helps
|
| 40 |
+
|
| 41 |
+
The decoder here is autoregressive: one forward pass per token, ~130 per chunk. On a CPU at batch 1
|
| 42 |
+
that is not an arithmetic problem β it is a *per-call* problem. Measured on this machine:
|
| 43 |
+
|
| 44 |
+
| tokens per decoder forward | 1 | 4 | 8 | 16 | 32 |
|
| 45 |
+
|---|---|---|---|---|---|
|
| 46 |
+
| ms per forward | 22.06 | 24.55 | 27.38 | 27.17 | **28.72** |
|
| 47 |
+
| ms per token | 22.06 | 6.14 | 3.42 | 1.70 | **0.90** |
|
| 48 |
+
|
| 49 |
+
**Thirty-two tokens cost 1.30Γ what one token costs.** The per-token price falls 24-fold. Almost all
|
| 50 |
+
of a decoder call is fixed overhead, and the arithmetic in between is nearly free.
|
| 51 |
+
|
| 52 |
+
That is exactly the condition speculative decoding needs. If something cheap can *guess* the next K
|
| 53 |
+
tokens, the decoder can check all of them in a single forward for roughly the price of checking one.
|
| 54 |
+
|
| 55 |
+
**The CTC head is that cheap thing.** It sits on the encoder output and is frame-synchronous and
|
| 56 |
+
non-autoregressive β CTC assumes tokens are conditionally independent given the audio, so the whole
|
| 57 |
+
hypothesis comes out of **one** forward pass. That independence assumption makes it a poor
|
| 58 |
+
transcriber and an excellent drafter. Measured cost: **22.8 ms** on a 10-second clip, against a
|
| 59 |
+
1069 ms decoder β a 2.1% tax.
|
| 60 |
+
|
| 61 |
+
The effect, measured: **2.66 tokens retired per decoder call** instead of 1, which is
|
| 62 |
+
**1.29Γβ1.48Γ** wall clock depending on the clip, as shown in the figure above.
|
| 63 |
+
|
| 64 |
+
### A wrong guess cannot produce a wrong word
|
| 65 |
+
|
| 66 |
+
The verifier keeps only the prefix its own argmax agrees with, then appends one token taken straight
|
| 67 |
+
from the decoder. **A drafted token reaches the output only if the decoder would have produced it
|
| 68 |
+
anyway.** A completely wrong draft degrades to ordinary greedy decoding. What a bad draft costs is
|
| 69 |
+
time, not accuracy β which is why the accuracy column below barely moves.
|
| 70 |
+
|
| 71 |
+
---
|
| 72 |
+
|
| 73 |
+
## Training data for the CTC head
|
| 74 |
+
|
| 75 |
+
| | |
|
| 76 |
+
|---|---|
|
| 77 |
+
| dataset | [`oddadmix/lahgtna-v3-small`](https://huggingface.co/datasets/oddadmix/lahgtna-v3-small) β multi-dialect Arabic speech |
|
| 78 |
+
| used for training | **4,256 clips Β· 18.33 h** from the `train` split (median clip 15.3 s, filtered to 1β30 s) |
|
| 79 |
+
| held out | **1,024 clips Β· 4.58 h** from the `test` split, never trained on |
|
| 80 |
+
| labels | **the base model's own transcripts** β self-distillation |
|
| 81 |
+
| epochs | 12, AdamW, `F.ctc_loss` with blank = `pad_token_id` = 2 |
|
| 82 |
+
|
| 83 |
+
**Only the audio from that dataset was used.** The targets are not its transcripts β they are what
|
| 84 |
+
[CohereLabs/cohere-transcribe-arabic-07-2026](https://huggingface.co/CohereLabs/cohere-transcribe-arabic-07-2026) itself emits on that audio. The drafter's job is to predict
|
| 85 |
+
*this model's* output, so training it on anything else would teach it to propose tokens the verifier
|
| 86 |
+
would reject. It also means no human annotation was involved anywhere in this head.
|
| 87 |
+
|
| 88 |
+
Labels were produced by an fp32 GPU replica of the deployed int8 model rather than by bf16, which
|
| 89 |
+
cut label noise against the deployed model from 2.81% to 1.74% WER.
|
| 90 |
+
|
| 91 |
+
**Vocabulary saturates almost immediately.** Only 274 of the 16,384 vocabulary entries contain an
|
| 92 |
+
Arabic character at all β the rest belong to other scripts the multilingual base model supports β and
|
| 93 |
+
the tokenizer is sub-word for Arabic at ~2.4 tokens per word. After **0.83 hours**, the types seen
|
| 94 |
+
10+ times already covered **99.24% of all token occurrences**. Past that point more data buys
|
| 95 |
+
acoustic quality, not new symbols.
|
| 96 |
+
|
| 97 |
+
The head is **data-limited, not capacity-limited**: acceptance went 0.143 at 0.83 h to 0.331 at
|
| 98 |
+
18.3 h, and test loss was still improving on the metric that matters when labelling stopped for
|
| 99 |
+
time. More hours would help.
|
| 100 |
+
|
| 101 |
+
---
|
| 102 |
+
|
| 103 |
+
## Where the time goes
|
| 104 |
+
|
| 105 |
+

|
| 106 |
+
|
| 107 |
+
Two independent measurements agree on this: forward hooks attributing time to architectural
|
| 108 |
+
components, and `torch.profiler` attributing it to ATen kernels. The kernel view:
|
| 109 |
+
|
| 110 |
+
| ATen kernel | self CPU | share | calls | what it is |
|
| 111 |
+
|---|---|---|---|---|
|
| 112 |
+
| `quantized::linear_dynamic` | 900.9 ms | 58.5% | 4,079 | the int8 GEMMs β the real work |
|
| 113 |
+
| `aten::mkldnn_convolution` | 264.4 ms | 17.2% | 149 | conformer convs, still fp32 |
|
| 114 |
+
| SDPA flash attention | 55.1 ms | 3.6% | 928 | attention proper |
|
| 115 |
+
| `aten::aminmax` | 36.1 ms | 2.3% | 4,079 | the per-call activation scale β pure dynamic-quant overhead |
|
| 116 |
+
| `aten::cat` | 34.9 ms | 2.3% | 952 | KV cache growing one token at a time |
|
| 117 |
+
| view / empty / as_strided | ~74 ms | 4.8% | 48,600 | dispatch overhead, no arithmetic |
|
| 118 |
+
|
| 119 |
+
`aminmax` fires exactly once per `linear_dynamic` β that is dynamic quantization recomputing an
|
| 120 |
+
activation scale on every single call. And 48,600 calls to pure-metadata operations do no arithmetic
|
| 121 |
+
at all. The decoder issues roughly 3,520 of the 4,079 GEMM calls, each on a 1Γ1024 vector.
|
| 122 |
+
|
| 123 |
---
|
| 124 |
|
| 125 |
## Performance
|
|
|
|
| 153 |
| 14-minute talk (ar) | 832.8 s | 98.36 s | 8.47Γ |
|
| 154 |
| **total** | **954.4 s** | **117.3 s** | **8.14Γ** |
|
| 155 |
|
| 156 |
+
### What the draft head is worth, referenced to fp32
|
| 157 |
|
| 158 |
+
fp32 is the only reference that separates *different* from *worse*:
|
|
|
|
| 159 |
|
| 160 |
| configuration | wall | vs int8 | WER from fp32 |
|
| 161 |
|---|---|---|---|
|
|
|
|
| 186 |
|
| 187 |
A GPU is both faster **and** more accurate β bf16 is a far lighter perturbation than int8. This repo
|
| 188 |
is for when there is no GPU. Worth knowing: the gap is 4.2Γ at batch 1 and 13.2Γ batched, because
|
| 189 |
+
only the GPU gains from being fed more work. The draft head also transfers to the GPU, where it
|
| 190 |
+
gives 1.40Γ at batch 1 β almost exactly what it gives on the CPU, for the same underlying reason.
|
| 191 |
|
| 192 |
---
|
| 193 |
|
|
|
|
| 219 |
|
| 220 |
---
|
| 221 |
|
| 222 |
+
## How the quantization works
|
|
|
|
|
|
|
| 223 |
|
| 224 |
Every `nn.Linear` β 1.81 B of the 2.07 B parameters β is stored as int8. Convolutions, layer norms
|
| 225 |
and embeddings stay fp32. Weights use one per-tensor scale, `float32(max|W| / 127.5)`, with a zero
|
|
|
|
| 230 |
handing a layer a wider tensor quantizes it more coarsely. This is why the model's output shifts
|
| 231 |
slightly with batch size, and why speculative decoding cannot be bit-identical to greedy.
|
| 232 |
|
| 233 |
+
The draft head architecture: one `ParakeetEncoderBlock` copied from `encoder.layers[47]`, then a
|
| 234 |
+
projection, a LayerNorm and an output matrix copied from the base model's `decoder.proj`,
|
| 235 |
+
`decoder.norm` and `proj_out`. Folding those three into a single matrix β the obvious "free"
|
| 236 |
+
initialisation β measured **worse than random init**, because the real path contains a LayerNorm and
|
| 237 |
+
a LayerNorm cannot be folded into a matrix product.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 238 |
|
| 239 |
---
|
| 240 |
|
|
|
|
| 251 |
this model to CUDA raises `NotImplementedError`. For a GPU, use the base model in bf16.
|
| 252 |
* **int8 costs accuracy.** 2.06% WER from fp32. If you have the RAM and can spare the speed, fp32 is
|
| 253 |
more faithful; if you have a GPU, bf16 is both faster and more accurate.
|
| 254 |
+
* The draft head saw 18.3 h of one dataset's dialect mix. Acoustics far from it will accept less.
|
| 255 |
* Inherits every limitation of the base model, including its language and domain coverage.
|
| 256 |
|
| 257 |
---
|
|
|
|
| 283 |
|
| 284 |
## License and attribution
|
| 285 |
|
| 286 |
+
Apache 2.0, inherited from [CohereLabs/cohere-transcribe-arabic-07-2026](https://huggingface.co/CohereLabs/cohere-transcribe-arabic-07-2026), copyright Cohere Labs.
|
| 287 |
|
| 288 |
**Changes made to the original** (Apache 2.0 Β§4b): weights of all `nn.Linear` modules quantized to
|
| 289 |
int8 as described above; a separately trained CTC draft head added; no change to the architecture,
|
| 290 |
the tokenizer, or the training data of the base model.
|
| 291 |
|
| 292 |
The base repository is **gated**. This derivative is not, so if you need the original weights,
|
| 293 |
+
please obtain them from Cohere Labs directly and accept their terms. Speech data for the draft head
|
| 294 |
+
came from [`oddadmix/lahgtna-v3-small`](https://huggingface.co/datasets/oddadmix/lahgtna-v3-small); please observe that dataset's own
|
| 295 |
+
licence and terms for any redistribution of the audio itself (none of it is included here).
|
ctc_effect.png
ADDED
|
Git LFS Details
|
profile.png
ADDED
|
Git LFS Details
|