chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:25:10 +08:00
commit c397331b1e
3684 changed files with 990993 additions and 0 deletions
+41
View File
@@ -0,0 +1,41 @@
# Reproducing the FunASR-vs-whisper.cpp benchmark
Scripts and method behind [`../BENCHMARKS.md`](../BENCHMARKS.md).
## Metric (authoritative FunASR口径)
- **micro-CER** = `Σ edit_distance / Σ reference_chars` over all files (not a per-file mean).
- **normalize_zh**: `re.sub(r'[^\w一-鿿]', '', text).upper()` — drop punctuation/whitespace,
keep word chars + CJK, upper-case. (SenseVoice meta tags `<|...|>` are stripped first.)
- **RTF** = `Σ compute_time / Σ audio_duration`, model-load time excluded.
`compute_cer.py` implements exactly this:
```bash
python compute_cer.py --refs testset.json --hyp_dir <hyps>/ [--time_file <times>.txt]
```
`testset.json` is a list of `{"id"/"key", "ref", "duration"}`; `<hyps>/{key}.txt` are
the transcripts; `<times>.txt` has `key compute_seconds` per line.
## Producing hypotheses
FunASR (this runtime), per clip:
```bash
# SenseVoice / Paraformer: ids -> detok
build/bin/llama-funasr-sensevoice -m sensevoice-small.gguf -a $k.wav > $k.ids
python ../sensevoice/detok.py <model>/chn_jpn_yue_eng_ko_spectok.bpe.model $k.ids > $k.txt
build/bin/llama-funasr-paraformer -m paraformer.gguf -a $k.wav > $k.ids
python ../paraformer/detok_paraformer.py <model>/tokens.json $k.ids > $k.txt
# Fun-ASR-Nano: text directly
build/bin/llama-funasr-cli --enc funasr-encoder.gguf -m qwen3-0.6b-q8_0.gguf -a $k.wav --chunk 15 > $k.txt
```
Compute time is on each tool's stderr (`encode … s` / `enc … dec … s`).
whisper.cpp, per clip (forced Chinese, no timestamps):
```bash
whisper-cli -m models/ggml-<size>.bin -l zh -nt -t 8 $k.wav > $k.txt # 2>stderr has "total time"/"load time"
```
RTF compute time = `(total load) ms`.
## Notes
- Run all systems with the **same thread count** (here `-t 8` / 8 threads) for a fair RTF.
- Whisper does its own internal 30 s windowing; the FunASR segmentation口径 is documented
in `BENCHMARKS.md` (see the methodology/caveats sections).
@@ -0,0 +1,63 @@
#!/usr/bin/env python3
"""Compute micro-CER (normalize_zh) and RTF for an ASR system's hypotheses.
Usage:
python compute_cer.py --refs testset.json --hyp_dir <dir of {key}.txt> \
[--time_file <key compute_seconds per line>]
- micro-CER = sum(edit distance) / sum(reference chars), over all files.
- normalize_zh(text) = re.sub(r'[^\\w一-鿿]', '', text).upper() (the FunASR口径)
- RTF = sum(compute_time) / sum(audio_duration) (model-load excluded)
testset.json: list of {"id" or "key", "ref", "duration"}.
"""
import argparse, json, glob, os, re
import numpy as np
def normalize_zh(s):
s = re.sub(r"<\|[^|]*\|>", "", s) # drop SenseVoice meta tags, if any
return re.sub(r"[^\w一-鿿]", "", s).upper()
def edist(r, h):
r, h = list(r), list(h)
if not r: return len(h)
d = np.arange(len(h)+1)
for i in range(1, len(r)+1):
prev = d[0]; d[0] = i
for j in range(1, len(h)+1):
cur = d[j]
d[j] = min(d[j]+1, d[j-1]+1, prev + (r[i-1] != h[j-1]))
prev = cur
return int(d[len(h)])
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--refs", required=True)
ap.add_argument("--hyp_dir", required=True)
ap.add_argument("--time_file", default=None)
a = ap.parse_args()
refs = {}
dur = {}
for it in json.load(open(a.refs)):
k = f"{it.get('id', it.get('key')):03d}" if isinstance(it.get('id', it.get('key')), int) else str(it.get('key'))
refs[k] = it["ref"]; dur[k] = float(it.get("duration", 0))
times = {}
if a.time_file and os.path.exists(a.time_file):
for ln in open(a.time_file):
p = ln.split()
if len(p) >= 2:
try: times[p[0]] = float(p[1])
except ValueError: pass
E = N = 0; rt = ad = 0.0; n = 0
for p in glob.glob(os.path.join(a.hyp_dir, "*.txt")):
k = os.path.splitext(os.path.basename(p))[0]
if k not in refs: continue
h = normalize_zh(open(p).read()); r = normalize_zh(refs[k])
E += edist(r, h); N += len(r); n += 1
if k in times: rt += times[k]; ad += dur[k]
cer = E / max(N, 1) * 100
print(f"files={n} micro-CER={cer:.2f}%", end="")
if ad > 0: print(f" RTF={rt/ad:.4f} ({ad/rt:.1f}x real-time)")
else: print()
if __name__ == "__main__":
main()