Unable to reproduce benchmarks
Hi,
Thanks to the Mistral team for open sourcing this incredible model!
Hoever, we are unable to reproduce benchmarks mentioned in the paper. The dataset details and inference logic is described below.
Benchmarking Details
Datasets and test split used for benchmarking ✅
| # | Dataset | HuggingFace Path | Config | Split | Test Samples | Other Splits Available |
|---|---|---|---|---|---|---|
| 1 | librispeech_clean | openslr/librispeech_asr |
clean |
test |
2,620 | train.100, train.360, validation |
| 2 | librispeech_other | openslr/librispeech_asr |
other |
test |
2,939 | train.500, validation |
| 3 | voxpopuli | facebook/voxpopuli |
en |
test |
1,842 | train, validation |
| 4 | fleurs_en | google/fleurs |
en_us |
test |
647 | train, validation |
| 5 | fleurs_hi | google/fleurs |
hi_in |
test |
418 | train, validation |
| 6 | earnings22 | distil-whisper/earnings22 |
full |
test |
125 | (only test split exists) |
| 7 | meanwhile | distil-whisper/meanwhile |
— | test |
64 | (only test split exists) |
| 8 | switchboard | hhoangphuoc/switchboard |
— | test |
51,501 | train, validation |
| 9 | common_voice_en | fixie-ai/common_voice_17_0 |
en |
test |
16,393 | train, validation, validated |
| 10 | common_voice_hi | fixie-ai/common_voice_17_0 |
hi |
test |
3,154 | train, validation, other, invalidated, validated |
📊 Reproduction Results — Voxtral Realtime (WER %)
English Short-Form (Table 5 columns)
| Dataset | Delay | Paper WER | Ours WER | Δ | Match? |
|---|---|---|---|---|---|
| LibriSpeech Clean | 480ms | 2.08 | 3.09 | +1.01 | ❌ |
| 960ms | 1.96 | 2.96 | +1.00 | ❌ | |
| 2400ms | 1.82 | 2.86 | +1.04 | ❌ | |
| LibriSpeech Other | 480ms | 5.54 | 6.66 | +1.12 | ❌ |
| 960ms | 4.59 | 5.88 | +1.29 | ❌ | |
| 2400ms | 4.03 | 5.40 | +1.37 | ❌ | |
| VoxPopuli | 480ms | 7.87 | 11.11 | +3.24 | ❌ |
| 960ms | 7.23 | 10.41 | +3.18 | ❌ | |
| 2400ms | 7.06 | 10.12 | +3.06 | ❌ |
English Long-Form (Table 6)
| Dataset | Delay | Paper WER | Ours WER | Δ | Match? |
|---|---|---|---|---|---|
| Meanwhile | 480ms | 5.05 | 6.79 | +1.74 | ❌ |
| 960ms | 4.14 | 5.19 | +1.05 | ❌ | |
| 2400ms | 4.03 | 5.03 | +1.00 | ❌ |
FLEURS (Table 7)
| Dataset | Delay | Paper WER | Ours WER | Δ | Match? |
|---|---|---|---|---|---|
| FLEURS EN | 480ms | 4.90 | 13.00 | +8.10 | ❌❌ |
| 960ms | 4.34 | 9.11 | +4.77 | ❌ | |
| 2400ms | 4.05 | 7.03 | +2.98 | ❌ | |
| FLEURS HI | 480ms | 12.88 | 19.24 | +6.36 | ❌ |
| 960ms | 11.82 | 17.62 | +5.80 | ❌ | |
| 2400ms | 10.73 | 17.06 | +6.33 | ❌ |
Mozilla Common Voice (Table 8)
| Dataset | Delay | Paper WER | Ours WER | Δ | Match? |
|---|---|---|---|---|---|
| Common Voice EN | 480ms | 15.18 | 15.46 | +0.28 | ✅ ~close |
| 960ms | 12.49 | 12.87 | +0.38 | ✅ ~close | |
| 2400ms | 10.51 | 11.20 | +0.69 | ✅ ~close | |
| Common Voice HI | 480ms | 17.22 | 21.70 | +4.48 | ❌ |
| 960ms | 15.04 | 19.27 | +4.23 | ❌ | |
| 2400ms | 13.19 | 17.44 | +4.25 | ❌ |
Inference details
1. Text Normalization — Our Pipeline
_PUNCT_RE = re.compile(r"[^\w\s]", re.UNICODE)
_WS_RE = re.compile(r"\s+")
def normalise_text(text: str) -> str:
text = text.lower()
text = _PUNCT_RE.sub("", text) # strip everything NOT \w or whitespace
text = _WS_RE.sub(" ", text).strip()
return text
Applied to both reference and hypothesis before WER/CER computation.
What this does:
| Before | After |
|---|---|
"Hello, World!" |
"hello world" |
"don't" |
"dont" |
"25 to 30 year" |
"25 to 30 year" |
"U.S.A." |
"usa" |
"CONCORD RETURNED" |
"concord returned" |
Important: Hindi/Devanagari issue ⚠️
The regex [^\w\s] strips all non-word, non-space characters. In Devanagari, dependent vowel signs (matras) like े, ु, ि, ा are Unicode category Mark (Mn/Mc), NOT matched by \w. So our normalization strips them:
Input: "नमस्ते, दुनिया!"
Output: "नमसत दनय"
↑ matras े, ु, ि, ा stripped!
This corrupts Hindi text. However, since the same normalization is applied to both reference and hypothesis, the WER impact depends on whether both sides lose the same characters. If the model faithfully reproduces the matras and the reference has them too, both get stripped symmetrically and WER may not be heavily affected. But any asymmetric stripping (e.g., a word boundary shifts due to padding) could create artificial mismatches.
2. Inference & Padding Logic — Detailed Walkthrough
Overview of the data flow
Raw audio (16 kHz)
│
▼
_right_pad_audio() ← add silence at the END
│
▼
processor() with is_streaming=False
│ ├── left-pads the raw waveform with silence (32 adapter frames ≈ 2.56 s)
│ ├── converts waveform → log-Mel spectrogram (128 bins, hop=160)
│ └── tokenizes the audio into [STREAMING_PAD]*32 + audio tokens
│
▼
model.generate() with max_new_tokens
│
▼
processor.batch_decode(skip_special_tokens=True)
│
▼
normalise_text() ← lower-case, strip punctuation, collapse whitespace
│
▼
evaluate_pair() ← HuggingFace evaluate WER/CER
Right-padding: why τ + 10 frames?
The decoder emits one token per 80 ms adapter frame. For the last word in the utterance, its word-boundary token [W] fires τ frames after the acoustic evidence ends. Then the subword tokens of that word follow at τ+1, τ+2, … without right-padding, the encoder runs out of states and the decoder must hallucinate text without acoustic grounding → inflated WER. The 10-frame headroom covers subword tokens after [W] (~800 ms worth).
Left-padding: 32 [STREAMING_PAD] tokens
The model's AudioConfig has streaming_n_left_pad_tokens=32. When we call the processor with is_streaming=False, it:
- Prepends 32 adapter frames of silence (2.56 s) to the raw waveform
- Injects 32
[STREAMING_PAD]tokens at the start ofinput_ids
Per the paper's §6.3, this acts as attention sinks — the initial attention mass is absorbed by these dummy tokens, stabilizing the decoding. The paper tested 0, 16, and 32 frames and found 32 works best (Table 4).
Generation is greedy, no sampling
model.generate(**model_kwargs, max_new_tokens=batch_max_tokens)
No temperature, do_sample, top_k, or top_p — default greedy decoding (equivalent to temperature=0). Standard evaluation practice.
Thanks again and please reply for any missing details!
Could you please provide the value of max_new_tokens? I suspect that setting it too low is causing the audio tokens to be truncated, which results in frequent deletion errors.
@abhinavkulkarni
@Kaiqfu : I'm generating a single token for every 80ms audio chunk. There is no burst emission of tokens for a single 80ms audio chunk.
model.generate(**model_kwargs, max_new_tokens=batch_max_tokens),this is hf generate. and since the model emits one token every 80 ms, max_new_tokens must be at least as large as the audio duration divided by 80 ms (i.e., the total number of frames); otherwise, generation will be truncated before the full audio is processed. You can check it. @abhinavkulkarni