Whistle's 86,174-utterance benchmark: test your own clips first
Cactus Whistle's card reports word error rates over 86,174 utterances. Score 20 of your own clips against a human transcript before choosing it over hosted STT.

Treat a model card benchmark as a filter, not a verdict. Whistle's Hugging Face card says its word error rates were measured over 86,174 utterances, with comparisons to Whisper and Moonshine models. Your audio has its own accents, music beds and product names, so score 20 clips of your own against a human-checked transcript before you commit.
Whistle facts (16.9 MB, seven languages, Apache 2.0, 16 kHz mono input) are from the card read on 2026-10-13. The Sume steps use video-inspect and remix-media as documented on origin/main b28f3b0.
| Step | What to do | Sume surface |
|---|---|---|
| 1. Pick clips | 20 clips from real jobs, mixed noise and speakers | Your library |
| 2. Reference | Human-correct one transcript per clip | Optional: start from a Sume transcript |
| 3. Candidate A | Run Whistle on 16 kHz mono audio | audio-detach with sample_rate: 16000, channels: mono |
| 4. Candidate B | Hosted transcript with a language hint | POST /v1/video-inspect with transcribe: true |
| 5. Score | Word error rate per clip, then the worst five | Your script |
What should I look at besides the average?
Names, numbers and brand terms: an average can hide a model that gets product names wrong in every clip. Whistle handles one pass up to 30 seconds and windows beyond that, so include a few clips longer than 30 seconds to see whether the seams cost words.
How do I get a draft reference transcript?
Run video-inspect with transcribe: true and segmentation.mode: "sentence" and correct the output by hand. For heavy reference work, the remix media transcribe verb returns a word-timed transcript with per-word confidence; its default engine is WhisperX, and the billed scribe_v2 engine needs allow_billed_stt: true.
# word error rate between a reference and a hypothesis (pure Python)
def wer(ref: str, hyp: str) -> float:
r, h = ref.lower().split(), hyp.lower().split()
d = [[0] * (len(h) + 1) for _ in range(len(r) + 1)]
for i in range(len(r) + 1):
d[i][0] = i
for j in range(len(h) + 1):
d[0][j] = j
for i in range(1, len(r) + 1):
for j in range(1, len(h) + 1):
cost = 0 if r[i - 1] == h[j - 1] else 1
d[i][j] = min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost)
return d[-1][-1] / max(len(r), 1)
print(wer("say hello to sume", "say hello to some"))Sources
Related posts
More in Models
- Whistle speech model: Apache 2.0, 16.9 MB, offline or Sume STT?
Cactus Whistle is a 16.9 MB Apache 2.0 speech-to-text model for seven languages. Run it on-device, or send the clip to Sume STT for other languages.
- ACE-Step on Sume: 240 s max, billed per second not per minute
ACE-Step is the Music Router id ace-step: 5–240 s via duration_seconds, billed per second of audio, so a 45 s clip costs less than a 60 s one.
- CassetteAI Music on Sume: a 90 s track bills 2 minutes
CassetteAI Music runs on Sume's Music Router as cassetteai-music: 10–180 s via duration_seconds, billed per started minute, so 90 s costs two minutes.
- ElevenLabs Music v2.5 on Sume: 300 s max, started-minute billing
ElevenLabs Music v2.5 is the Music Router id elevenlabs-music-2.5: 10–300 s via duration_seconds, billed per started minute, so 61 s costs two minutes.
Written by Sume