LiteASR style compression applied to efwkjn/cohere-asr-ja. The encoder linear layers are compressed using low rank decomposition, reducing size/latency by 44%/~8% with minimal impact on CER.
This model uses a custom inference engine based on PyTorch/torch.compile. And is probably the fastest implementation for offline Cohere, transcribing 273 videos (68.5 hours) in 82.9s (full model 89.5s) on a single RTX 6000 Blackwell with enc8/dec512 batch size. Comparing to hf-audio longform leaderboard results on the official model has WER within margin of error. Leaderboard RTFx is from H200 (a stronger GPU) runs mentioned here. WER change on cv17 en test (n=16393) is statistically insignificant - one-tail trfms < mine p=0.41.
| earnings21 | earnings22 | |||
|---|---|---|---|---|
| RTFx | WER | RTFx | WER | |
| leaderboard | 560 | 8.7 | 523 | 12.66 |
| mine | 2842 | 8.56 | 2867 | 12.45 |
Benchmarks
Latency (s) on JSUT-book (6 files, 4257s, 135 segments) best@5 16GB GPU
| decbsz | nano-cohere-transcribe | encbsz=2 | encbsz=4 | lite(encbsz=4) | |||
|---|---|---|---|---|---|---|---|
| 2 | 23.37 | 21.46 | |||||
| 4 | 14.36 | 14.39 | 13.90 | 13.32 | |||
| 8 | 10.10 | 10.48 | 9.94 | 9.40 | |||
| 16 | 9.76 | 8.55 | 8.07 | 7.50 | |||
| 128 | 10.10 | 6.86 | 6.34 | 5.87 | |||
| 256 | 10.36 | 6.59 | 6.08 | 5.64 |
See my most recent whisper finetune for more evals
| tedx | jsut-book | ||||||||
|---|---|---|---|---|---|---|---|---|---|
| cer | sr | ir | dr | cer | sr | ir | dr | ||
| cohere | 21.7 | 4.9 | 5.7 | 11.2 | 30.1 | 9.6 | 1.4 | 19.1 | |
| ja | 12.3 | 4.8 | 5.3 | 2.2 | 17.5 | 10.9 | 0.7 | 5.9 | |
| lite | 12.4 | 5.2 | 5.0 | 2.3 | 18.7 | 11.5 | 0.8 | 6.3 |
| fleurs | jsut-basic | blue-archive | nekopara | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| cer | sr | ir | dr | cer | sr | ir | dr | cer | sr | ir | dr | cer | sr | ir | dr | ||||
| cohere_b1 | 4.9 | 2.7 | 0.4 | 1.8 | 8.4 | 5.7 | 1.8 | 0.8 | 8.1 | 4.0 | 1.2 | 2.9 | 30.4 | 13.5 | 3.7 | 13.2 | |||
| cohere_b5 | 4.9 | 2.6 | 0.4 | 1.9 | 8.4 | 5.7 | 1.8 | 0.9 | 8.1 | 3.9 | 1.1 | 3.1 | 29.4 | 12.7 | 2.0 | 14.8 | |||
| ja_b1 | 5.0 | 2.8 | 0.4 | 1.8 | 6.7 | 4.9 | 1.0 | 0.8 | 6.6 | 3.8 | 1.5 | 1.4 | 24.5 | 14.1 | 3.5 | 6.9 | |||
| ja_b5 | 4.8 | 2.7 | 0.4 | 1.6 | 6.6 | 4.8 | 1.0 | 0.8 | 6.5 | 3.7 | 1.3 | 1.5 | 23.5 | 12.6 | 1.9 | 9.0 | |||
| lite_b1 | 4.9 | 3.1 | 0.5 | 1.3 | 6.9 | 5.1 | 0.9 | 1.0 | 6.9 | 4.0 | 1.5 | 1.4 | 24.9 | 14.5 | 3.5 | 6.9 | |||
| lite_b5 | 4.8 | 3.1 | 0.5 | 1.3 | 6.9 | 5.0 | 0.9 | 0.9 | 6.7 | 3.8 | 1.3 | 1.6 | 23.8 | 12.9 | 1.8 | 9.0 |
| air | clannad | himanatsu | kanon | sakuuta | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| cer | sr | ir | dr | cer | sr | ir | dr | cer | sr | ir | dr | cer | sr | ir | dr | cer | sr | ir | dr | |||||
| cohere_b1 | 18.2 | 9.4 | 1.5 | 7.3 | 17.5 | 7.7 | 2.0 | 7.8 | 28.1 | 11.1 | 5.3 | 11.7 | 16.8 | 8.1 | 2.2 | 6.5 | 16.1 | 7.7 | 3.3 | 5.1 | ||||
| cohere_b5 | 18.1 | 9.2 | 1.4 | 7.5 | 17.6 | 7.5 | 1.8 | 8.2 | 25.6 | 10.1 | 2.4 | 13.1 | 16.7 | 7.9 | 2.0 | 6.8 | 15.5 | 7.3 | 2.4 | 5.8 | ||||
| ja_b1 | 15.8 | 9.2 | 1.6 | 5.0 | 12.3 | 6.7 | 1.2 | 4.4 | 21.0 | 12.4 | 3.6 | 5.0 | 13.9 | 7.9 | 1.6 | 4.4 | 10.8 | 6.6 | 2.2 | 2.1 | ||||
| ja_b5 | 15.4 | 9.0 | 1.3 | 5.2 | 12.1 | 6.5 | 0.9 | 4.7 | 20.1 | 11.3 | 2.4 | 6.4 | 13.8 | 7.9 | 1.3 | 4.7 | 10.6 | 6.3 | 1.9 | 2.3 | ||||
| lite_b1 | 15.9 | 9.3 | 1.5 | 5.1 | 12.4 | 6.7 | 1.2 | 4.5 | 21.3 | 12.7 | 3.8 | 4.8 | 13.8 | 7.8 | 1.5 | 4.5 | 11.1 | 6.8 | 2.2 | 2.1 | ||||
| lite_b5 | 15.6 | 9.1 | 1.2 | 5.2 | 12.1 | 6.5 | 0.9 | 4.7 | 20.2 | 11.6 | 2.4 | 6.2 | 13.8 | 7.8 | 1.2 | 4.8 | 10.7 | 6.5 | 1.9 | 2.4 |
Usage
from huggingface_hub import snapshot_download
model_dir = 'lite'
snapshot_download('efwkjn/cohere-asr-ja-lite', cache_dir=model_dir, local_dir=model_dir)
import importlib
import torch
from tokenizers import Tokenizer
cohere_asr = importlib.import_module(model_dir + '.cohere_asr')
file = 'audio.wav'
device = 'cuda'
fe = cohere_asr.CohereAsrFeatureExtractor(model_dir, device='cpu')
model = cohere_asr.CohereAsr.from_pretrained(model_dir, device)
tokenizer: Tokenizer = Tokenizer.from_file(model_dir + '/tokenizer.json')
prompt = tokenizer.encode(
'<|startofcontext|><|startoftranscript|><|emo:undefined|>'
'<|ja|><|ja|><|pnc|><|noitn|><|notimestamp|><|nodiarize|>',
add_special_tokens=False,
).ids
prompts = torch.tensor(prompt, dtype=torch.int64, device=device)[None, :].expand(1, len(prompt))
for lst in model.transcribe([file], fe, prompts, compile=False):
print(''.join(tokenizer.decode_batch([x.tolist() for x in lst])))
Performance Notes
- Above benchmarks are with defaults except compile=True and batch size
- Feature extractor can be offloaded if cpu is slow
- If using cpu fe, torch.set_num_threads(1) can be useful, increase workers instead for multithreading
- Workers (default 2 loaders, 4 extractors) may need to be increased based on audio decoding/feature extractor performance relative to gpu
- Audio preprocessing is throttled to maintain a buffer
- Use max-autotune for extra speed
- Compile padding can be tuned: fe.pad_to, model.max_audio_clip, pad='decoder'
- block_size (self, cross) can be tuned to reduce kv cache padding reads (I like 64, 128)
- Pointwise convs were changed to linears because gemm kernels profiled slightly faster
- Encoder batch size split from decoder due to inefficiencies in large batch conv kernels and better pipelining
- Possible optimisations: cache encoder attention p, custom kernels for low rank, cache prefill
Analysis
The following is a tech report style analysis of the method. This isn't useful to run the model. Understanding of the original paper is assumed.
Methodology
The original paper performs PCA with SVD but this requires storing activations needing per sample. We could do it online, running all samples in a single batch and decomposing after each linear but this still requires a non-insignificant amount of memory as samples increases.
Instead I reimplement the compression script to do PCA with eigendecomposition of the covariance matrix with a simple 2 pass method. First pass is quick, determining the mean using per minibatch and reduced after. Second pass accumulates, using a constant for any amount of calibration samples (and can be offloaded to CPU).
Evaluation uses a custom inference implementation with pure greedy search no timestamps, missing minor steps like suppress_tokens. CER is calculated with whisper.normalizers.BasicTextNormalizer; removing whitespace; and using jiwer.process_characters().cer doing a weighted mean instead of arithmetic mean. All other details as implemented in the original.
Whisper
Testing official checkpoints calibrated on English released by efficient-speech with Japanese (using their hf impl), results in some romaji instead of a proper transcriptions. See link for CER results.
I start with efwkjn/whisper-ja-760M calibrating on 512 samples generated with my training code. Figures 1/2 uses the FLEURS ja_jp dev set filtered for duplicate text (n=135).
Figure 1: FLEURS dev CER vs encoder size, coloured by MLP θ.
Figure 2: FLEURS dev CER vs θ, coloured by encoder size.
CER mainly jumps from IR exploding due to looping. Inspecting some problematic generations indicates all clips are extremely low volume and likely out of distribution from the calibration data.
Figure 3: JSUT basic5000 CER vs θ, coloured by encoder size.
In Figure 3 I run another grid search evaluating the first 135 samples of JSUT basic5000 for a comparison on clean audio. There is still a cliff from looping but at higher compression levels. In the previous 2 searches, there is a shift at attn θ=0.9884, reason is unclear (maybe denoising effect).
Figure 4: Calibration samples powers of 2 (8-512) vs shared θ attn/MLP (0.992-1.0), coloured by encoder size.
Next I ablate the number of calibration samples sweeping θ and show results in figure 4. As samples grow, encoder size required to hit a certain θ increases but CER to size with 32+ samples is roughly equal. With the 128 sample data I do a small grid search around the attn θ=0.9884 area and find the same behaviour seen previously.
Conformer
In limitations of the original paper they mention the method is not applied to Conformer convolution layers. But the method can be used on pointwise convs which are mathematically identical to linear layers (with a transposed representation); and depthwise convs have negligible params/flops. In Figure 5 I apply this to efwkjn/cohere-asr-ja.
Figure 5: FLEURS dev CER vs encoder size, coloured by convolution θ.
Convolution layers are significantly more sensitive to compression where the Pareto front is dominated by no to low compression. Attention layers can take more compression than MLP, but θ needs to be lower for similar compression/CER effects compared to Whisper. Unlike Whisper, CER increases gradually with occasional failures throughout where the decoder outputs no text.
So far we grouped θ by layer type, next I separate each tensor and search with NSGA-II optimising for latency/KLD. I calculate KLD on 256 calibration samples and microbench each replacement (with default PyTorch) for a latency cost. k<64 is excluded for no speedup (memory bound without custom kernels), and some values just above multiples of 256 for being slower than full rank, e.g. k>512 for 1280×1280 linears.
Figure 6: FLEURS train CER vs encoder size.
To improve convergence of the 480D space over random init, I seed the population with minimal compression and slowly limit latency. At 0.025 KLD (got bored) I generate 512 new samples to reduce overfitting and search around that cost for a final model. In Figure 6, I select points from both the GS and BO, and benchmark on FLEURS train set (n=2292). Despite GS points optimised on FLEURS dev; and no FLEURS in calibration, BO substantially outperforms GS. I choose the highlighted point to release as cohere-asr-ja-lite.
Figure 7: Compression ratios for cohere-asr-ja-lite.
Compression ratios and derived θ of cohere-asr-ja-lite are shown in Figure 7. Derived θ indicates per type θ is suboptimal with large variation within types especially in early layers which get underestimated. Attention value/out also take significantly less compression than query/key.
To evaluate quality loss, I compare clips from the 9 shortform datasets above (n=131590) with clip-wise bootstrap method. Mean difference is 0.17±0.06 CER (CI assumes clip independence, but effect of dataset bias should be small with similarity of models).
Conclusion
LiteASR is cheap and works reasonably well for a couple minutes of compute given sensible θs. But within the same structure can be substantially improved with a few days of Bayesian optimisation. For a future Whisper finetune I will use structured pruning instead to maximise performance, preliminary healing results show better loss on a smaller model compared to LiteASR.
- Downloads last month
- 6
Model tree for efwkjn/cohere-asr-ja-lite
Base model
CohereLabs/cohere-transcribe-03-2026