105 lines
5.8 KiB
Markdown
105 lines
5.8 KiB
Markdown
# FunASR (llama.cpp / GGUF) vs whisper.cpp — CPU benchmark
|
||
|
||
How does the FunASR llama.cpp runtime compare with [whisper.cpp](https://github.com/ggml-org/whisper.cpp),
|
||
the de-facto on-device ASR runtime, on **Chinese** speech? This page reports a
|
||
head-to-head on identical hardware and audio.
|
||
|
||
**TL;DR — for Chinese ASR on CPU, FunASR is ~2.7× more accurate than whisper.cpp at
|
||
every model tier, and faster.**
|
||
|
||
## Results
|
||
|
||
Dataset: **184 real Mandarin clips with human references** (the standard FunASR
|
||
benchmark set). Metric: **micro-CER** with `normalize_zh` (lower is better). Speed:
|
||
real-time factor on **CPU, 8 threads** (model-load excluded). whisper forced to
|
||
Chinese (`-l zh`).
|
||
|
||
| system | **CER** (micro, normalize_zh) ↓ | speed ↑ | size |
|
||
|---|---|---|---|
|
||
| **FunASR Fun-ASR-Nano** | **8.06** (fp32 ref) / **8.42** (Q8 runtime) | LLM decode¹ | enc + Qwen3-0.6B GGUF |
|
||
| **FunASR SenseVoiceSmall** | **7.81** (fp32 ref) / **8.17** (Q8 runtime) | **~20× real-time** | 449 MB (f16) |
|
||
| **FunASR Paraformer** | **10.18** (fp32 ref) / **9.89** (Q8 runtime) | **~21× real-time** | 401 MB (f16) |
|
||
| whisper.cpp base | 31.33 | 9.9× | 142 MB |
|
||
| whisper.cpp small | 22.12 | 4.6× | 466 MB |
|
||
| whisper.cpp large-v3-turbo | 23.15 | 3.2× | 1.6 GB |
|
||
|
||
**Each FunASR row shows two numbers:** the published **fp32 reference** (PyTorch,
|
||
the number on funasr.com / the model cards) and the **Q8 llama.cpp CPU runtime**
|
||
measured here. The ~0.3 % gap is normal int8 quantization + VAD segment boundaries;
|
||
Q8 is the real CPU/edge deployment config. Either way, **FunASR ~8–10 % vs
|
||
whisper.cpp 22–31 % — a 2.7×+ accuracy gap that holds at every tier.**
|
||
|
||
¹ Fun-ASR-Nano runs an autoregressive 0.6B LLM decoder (slower than the encoder-only
|
||
SenseVoice/Paraformer; it is the accuracy leader). A clean RTF lands once the CLI
|
||
separates model-load from compute.
|
||
|
||
### Transparency / segmentation (read this before quoting numbers)
|
||
|
||
- **Segmentation differs by system, each using its natural strategy:** FunASR uses an
|
||
`fsmn-vad` front end (segments → ASR → concatenate); whisper.cpp uses its built-in
|
||
30 s windowing. This is a fair system-level comparison.
|
||
- **Engine-internal VAD is now implemented** — a native ggml FSMN-VAD built into the
|
||
binaries (`--vad fsmn-vad.gguf`). The **bare binary, with no Python front end**, now
|
||
reaches the reference end-to-end: SenseVoiceSmall **8.01 %**, Paraformer **9.85 %**,
|
||
Fun-ASR-Nano **8.30 %** (micro, normalize_zh, full 184). The built-in C++ VAD matches
|
||
the PyTorch `fsmn-vad` front end (segment boundaries within ~10 ms, slightly better
|
||
CER), so the runtime is now fully self-contained.
|
||
- For full disclosure, **bare binary with no VAD at all (whole-clip)** is higher —
|
||
SenseVoiceSmall 9.99 %, Paraformer 12.82 % — because long clips decoded as one segment
|
||
are out-of-distribution; that is exactly what the built-in VAD fixes.
|
||
|
||
## Why FunASR wins on Chinese
|
||
|
||
1. **Training data.** SenseVoice / Paraformer / Fun-ASR-Nano are trained primarily on
|
||
large-scale Mandarin; Whisper is a general multilingual model where Chinese is a
|
||
small slice. On Chinese homophones Whisper makes substitution errors the FunASR
|
||
models do not (example below).
|
||
2. **Architecture → speed.** Paraformer is non-autoregressive (CIF predictor + one
|
||
decoder pass) and SenseVoiceSmall is encoder + CTC (one forward pass); Whisper is
|
||
autoregressive (one step per output token).
|
||
|
||
## Qualitative example (clip 002)
|
||
|
||
| system | output (excerpt) |
|
||
|---|---|
|
||
| ground truth | 我想问,我在**滨海新区**有房…所以我必须拿到**抚养权** |
|
||
| FunASR (Nano / SenseVoice / Paraformer) | …我在**滨海新区**有房…拿到**抚养权**… ✓ |
|
||
| whisper base | …我在**冰海心区**有房…我想要**扶养权**…上学**方面**… ✗ |
|
||
| whisper small | …我在**冰海新区**有房…我想要**抚养全**… ✗ |
|
||
| whisper large-v3-turbo | …滨海新区…上学**方面**… ✗ |
|
||
|
||
## Methodology
|
||
|
||
- **Data:** the standard 184-clip Mandarin benchmark set (`benchmark/testset.json`),
|
||
~44–60 s each, with human references.
|
||
- **Metric (canonical):** **micro-average CER** (`Σ edits / Σ ref chars`) after
|
||
**`normalize_zh`**: `re.sub(r'[^\w一-鿿]', '', text).upper()` (strip punctuation/
|
||
whitespace, keep word chars + CJK, upper-case; SenseVoice `<|...|>` tags stripped).
|
||
This is the canonical FunASR口径 — the same one behind the published fp32 numbers.
|
||
(A macro-average / simplified-normalize variant gives different, non-canonical
|
||
numbers; it is not used here.)
|
||
- **FunASR fp32 reference:** PyTorch, micro + normalize_zh, 184 set — SenseVoice 7.81,
|
||
Paraformer 10.18, Fun-ASR-Nano 8.06 (matches funasr.com / READMEs / model cards).
|
||
- **FunASR Q8 runtime:** this llama.cpp runtime (Q8 LLM / f16 encoder) + `fsmn-vad`
|
||
front end (`max_single_segment_time=30000`), full 184. SenseVoice uses `use_itn=True`
|
||
to match the reference.
|
||
- **whisper.cpp:** ggml `base` / `small` / `large-v3-turbo`, `-l zh`, internal 30 s
|
||
windowing, full 184.
|
||
- **Speed (RTF):** `Σ compute_time / Σ audio_duration`, model-load excluded, **8 threads
|
||
for all systems**.
|
||
|
||
## Caveats (fair use)
|
||
|
||
- This is a **Chinese** benchmark — FunASR's home turf. Whisper is a *general
|
||
multilingual* model (translation, 99 languages, timestamps); for English / other
|
||
languages it is the stronger general choice. The takeaway is specifically:
|
||
**for Chinese ASR on CPU, FunASR is the accuracy + speed leader.**
|
||
- SenseVoiceSmall also outputs language ID / emotion / audio-event; Paraformer is
|
||
Mandarin-specialised; Fun-ASR-Nano is the most accurate (LLM decoder). Pick per use case.
|
||
|
||
## Reproduce
|
||
|
||
See [`benchmarks/`](benchmarks/) — `compute_cer.py` (micro-CER + normalize_zh + RTF)
|
||
and the per-system run commands. Produce hypotheses with each tool, then compute CER
|
||
against the references and RTF against clip durations.
|