AI Tools 6 min read

Cactus Whistle: 17 MB Speech-to-Text, Tested in French

I ran Cactus Whistle, a 16.9 MB open speech-to-text model, on Québec French, accented English and noise. Here is what it got wrong.

Abstract editorial illustration of a tiny amber cactus-like shape beside a sound wave turning into small blocks, with a small teal square next to a larger faded rectangle, flat shapes on a navy background.

Cactus Whistle is a speech-to-text model from Cactus Compute, released under Apache-2.0. The whole model is one 16.9 MB file that runs on a plain CPU, with no GPU and no cloud call. Its model card lists English, French, German, Spanish, Italian, Dutch and Polish, and it was published on September 30, so it is still very new.

In this post I go over what Cactus Whistle claims, what happened on four short clips of Québec French and French-accented English, how it did against Whisper tiny and base on the same clips, and who should bother. My verdict: the size is real, the French is good but not magic, and on my hardware it was not the fast one the vendor charts show.

Here is why I care. A Trellis seller stands in a warehouse with a phone and wants to say “bump the blue yoga mats by two dollars” instead of typing. Mettons we send that audio to a cloud API: a bill per minute, and pricing plans on somebody else’s server. I made up the yoga mats, but not the problem. I wrote last week about a budget cap that did not cap, and the cheapest cloud bill to control is the one you never open.

What did I actually run?

I installed cactus-needle 3.2.1 with pip on an Intel N95 mini-PC: four cores, 16 GB of RAM, no GPU. The model file on disk is 16,919,407 bytes, which matches the card.

For audio I generated four clips with OpenAI’s text-to-speech: two in a Montréal accent, one English with a French-Canadian accent, one neutral American English. Each is 5.6 to 8.7 seconds at 16 kHz. That is a stand-in, and a synthetic voice is cleaner than a real person at a hockey game. I also made noisy copies, white noise at 10 dB signal-to-noise. The total is 82 reference words, so read everything below as a smell test and not a benchmark.

Abstract editorial illustration of a tiny amber cactus-like shape beside a sound wave turning into small blocks, with a small teal square next to a larger faded rectangle, flat shapes on a navy background.
A 16.9 MB model next to a much larger one. The size gap is the part I could confirm on disk.

I ran each clip five times and kept the median time. I did not test long audio, streaming, a microphone, a phone or the WebAssembly build.

Does Cactus Whistle understand Québec French?

Mostly, until the speaker stops being polite. The first clip was standard-ish French: “J’ai besoin de mettre à jour le prix de mes annonces Amazon avant la fin de semaine, puis de vérifier le budget publicitaire.” Whistle returned it word for word, zero errors in 23 words. Whisper tiny and base both wrote “Amazon” as “à ma zone”.

The second clip was how I talk to friends: “Pis là, faut que je check mon inventaire FBA, parce qu’on est short sur les tapis de yoga bleus pis la livraison arrive pas avant mardi.” Whistle wrote “Pileur, faut que je tchèque mon inventaire FBA” and then “tapis diogables” for “tapis de yoga bleus”. Seven errors in 26 words. The “tchèque” is almost funny: it spelled the English loanword the way a French person says it.

Whisper tiny also wrote “diogables” on that clip, so the gap between written French and joual with English mixed in is not only Whistle’s. Bon.

Whistle has keyword biasing, where you hand it the words your users say. I passed keywords=['FBA', 'tapis de yoga', 'short'] and the errors on that clip dropped from seven to four. “check” and “tapis de yoga” came back right. For an Amazon seller app a list of product names and acronyms is cheap to maintain, and that is the first feature I would use.

English with a French accent came out perfect. The American clip wrote “yogamats” as one word, which my scorer counts as two errors.

How does Cactus Whistle compare with Whisper tiny and base?

I ran the same files through faster-whisper with the int8 tiny and base models on the same four cores, beam size 5. That is a different runtime from OpenAI’s own Whisper code, so call it Whisper weights on a fast engine. The Whisper README lists tiny at 39 M parameters and base at 74 M.

Errors out of 82 words, clean first and then noisy:

  • Whistle: 9 clean, 15 noisy.
  • Whisper tiny: 8 clean, 18 noisy.
  • Whisper base: 10 clean, 12 noisy.

That is a tie on clean audio and a small win for base in noise. With 82 words I would not defend a ranking, only that Whistle plays in the same league. The noisy joual clip is where everyone falls apart: Whistle heard “FBI” for FBA, tiny heard “FDA”, base heard “RFA”.

Size is where Whistle changes the picture. Its file is 16.9 MB. The int8 tiny model I downloaded is 75.5 MB and base is 145.2 MB, which is 4.5 and 8.6 times bigger, measured on disk. Nobody wants a 145 MB download inside an app for voice notes.

The accuracy numbers in the card are the vendor’s own, and I did not rerun them. It reports 4.31 percent word error rate on LibriSpeech test-clean, 10.49 on test-other, 21.4 on FLEURS and 24.9 on MLS, the last two averaged over its seven languages. Do not assume French sits near the 4 percent figure.

Is Cactus Whistle fast?

Not on my machine. The card says that on an Apple M4 Pro a 10-second clip gets its first token in 11.1 ms, against 73.2 ms for Whisper base, and that Whistle decodes 1,319 tokens a second.

On the N95 the library reported a median time to first token of 2.5 to 4.1 seconds, and a whole transcription took 3.3 to 6.7 seconds. Whisper tiny through faster-whisper took 1.8 to 2.5 seconds and base took 3.6 to 4.8. So Whistle lost to tiny on every clip, lost to base on the two French clips, and edged base on the two English ones.

I do not know why the gap is so big, and I did not profile it. Peak memory was 96 MB for the whole Python process, numpy included, and I liked that number.

One more thing before you ship it. The pip package prints a notice that it collects anonymous usage counts (function name, version, OS), and you turn that off with NEEDLE_TELEMETRY=0. For a product that sells “audio stays on the device”, I would set that in the first commit. The Needle repo holding the engine is Apache-2.0 too, which I checked in its LICENSE file.

Try Cactus Whistle in ten minutes

python3 -m venv .venv && . .venv/bin/activate
pip install cactus-needle
NEEDLE_TELEMETRY=0 python3 -c "import needle; print(needle.transcribe('clip.wav'))"

That is the README’s own example. Use 16 kHz mono WAV, and add language='fr' when you know it, since detection is one more thing to get wrong on a short clip. The devices guide covers air-gapped setups, which matters because the first run reaches out to Hugging Face.

Who should use Cactus Whistle, and who should skip it

Use it if your constraint is size or privacy: a mobile app, a small box, a browser build, a product that must not send audio out. For that, 17 MB against 75 MB is the whole argument.

Skip it for now if you transcribe on a server and the audio may leave the building. Me, I’d take faster-whisper with base there. It was equal or better on my clips, faster than Whistle on this CPU, and nobody asks me to explain a two-week-old engine to a customer.

I have not recorded my own voice, or a seller in a loud warehouse, and that is the test that matters. Next I will put a real phone recording of me ordering yoga mats in joual through both engines, with the keyword list, and write down which one gets “pis là” right.

Dominic Plouffe

CTO of Trellis, with exits at FuseTalk, 360pi and DeadHeat. I write about making agents and big data work together safely, and I'm building yaaf, an open-source platform for running teams of AI agents that actually finish the job: yaaf.dplouffe.ca