Files
2026-07-13 13:25:10 +08:00

105 lines
5.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 ~810 % vs
whisper.cpp 2231 % — 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`),
~4460 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.