Whistle: Spraak-naar-tekst in 16,9 MB

Vandaag lanceren we Whistle, een spraakherkenningsmodel voor mobiele apparaten, wearables, robots, smart homes, automotive en microcontrollers. Het is één bestand van 16,9 MB, draait op de CPU zonder afhankelijkheden en wordt geladen in dezelfde C++ engine als Needle, vanuit dezelfde container en met dezelfde kwantisatie.

Functionaliteiten

Whistle voert drie taken uit, volledig op het apparaat zelf:

  • Transcriptie: 16 kHz mono-audio, tot 30 seconden per sessie, in het Engels, Duits, Frans, Spaans, Italiaans, Nederlands en Pools. De taal wordt automatisch gedetecteerd, tenzij deze handmatig wordt opgegeven.
  • Woord-tijdstempels: Elk woord wordt voorzien van een starttijd, eindtijd en waarschijnlijkheid, uitgelijnd via de attention van de decoder.
  • Spraak-embedding: De output van de encoder, bestaande uit één rij per 80 ms frame, zonder dat er een transcriptie wordt gedecodeerd.

Het model

Architectuur overzicht

ENCODER

  • Waveform: 16 kHz mono, tot 30 s = 480.000 samples.
  • Log-mel: 80 bins, 25 ms window, 10 ms hop, 250-3500 Hz, genormaliseerd per kanaal → 3.000 frames.
  • Convolutional stem: 128 kanalen, kernel 9, drie halveringen → 375 frames, één per 80 ms.
  • Simple Attention blocks × 8: Gedeeld met Needle; self-attention over elk frame tegelijk, 4 mHC lanes, Monarch Hadamard MLP in plaats van de FFN.

DECODER

  • Cross memory: K en V worden één keer per clip geprojecteerd, 375 frames × 8 lagen, gedeeld door elke beam.
  • Laddered Simple Attention blocks × 8: Gedeeld met Needle; GQA 8q : 2kv, 48 qk / 64 v, 3-tap causal conv, engram op lagen 3 en 7, breedte 512.
  • Gated cross attention: Elke decoder-laag leest de encoder: $x \leftarrow x + \sigma(g) \cdot \text{softmax}(\hat{q} \hat{K}^T / \sqrt{d}) V$.
  • Beam search × 5: Lengte-genormaliseerde log-waarschijnlijkheid, keyword-bias via Aho-Corasick automaton.
  • Transcript: 8.192 tekstfragmenten + 7 taal-tokens, tot maximaal 320 stuks; woordtijden afgeleid van de eigen attention van de decoder.

Opmerking: Blokken gemarkeerd als "shared" maken gebruik van de code van Needle, niet van een kopie ervan. De parameter --audio-depth selecteert de decoder-lagen; de encoder draait altijd alle acht lagen.

Technische details

De front end 16 kHz mono-audio wordt verdeeld in frames met een window van 25 ms en een hop van 10 ms naar 80 log-mel bins, band-beperkt tot 250-3500 Hz en genormaliseerd per kanaal. Dertig seconden resulteert in 3.000 frames. Een convolutional stem van 128 kanalen en kernel 9 halveert dit aantal drie keer, waardoor 375 frames overblijven (één per 80 ms). Elke volgende fase draait op deze snelheid, en de embedding geeft één rij per frame terug.

De encoder Acht Simple Attention-blokken: vier mHC residual lanes en een Monarch Hadamard MLP in plaats van het feed-forward netwerk, dezelfde blokken die Needle gebruikt. De attention is niet causaal; een frame op 3 seconden kan attention hebben bij een frame op 12 seconden.

De decoder Acht Laddered Simple Attention-blokken met een breedte van 512, 8 query-heads tegenover 2 KV-heads, 48-dimensionale queries en keys, 64-dimensionale values, een 3-tap causale convolutie op Q, K en V, en engram-lookups op lagen 3 en 7 over 18.432 slots. Dit is de bloklijst van Needle met een ander aantal lagen.

Het spraakspecifieke onderdeel is één toevoeging per laag. Elke decoder-laag leest de encoder via een gated cross attention, $x \leftarrow x + \sigma(g) \cdot \text{softmax}(\hat{q} \hat{K}^T / \sqrt{d}) V$, met een per laag geleerde gate en K en V afgeleid van de clip. Deze projecties worden één keer uitgevoerd bij binnenkomst van de clip (375 frames over 8 lagen) en worden vervolgens vastgehouden voor de gehele decodering. Vijf beams kosten daarom vijf korte transcript-caches, en niet vijf volledige passes over de audio.

Decodering Vijf beams worden gescoord op basis van lengte-genormaliseerde log-waarschijnlijkheid. Keyword-biasing maakt gebruik van een Aho-Corasick automaton over de doorgegeven frases, parallel aan de beams, en verhoogt hun log-waarschijnlijkheid naarmate de automaton vordert. Het transcript is begrensd op 320 tokens. De vocabulaire bestaat uit 8.192 tekstfragmenten plus zeven taal-tokens (één per taal), waardoor de gedetecteerde taal als een token wordt uitgezonden in plaats van via een apart kanaal.

De "ladder" bevindt zich in de decoder. Elke diepte vanaf 2 lagen is getraind als een eigen model, en --audio-depth selecteert er één bij het laden. De encoder wordt nooit gesneden: alle acht blokken draaien bij elke diepte.

Stilte De engine meet het luidheidsbereik van de clip voordat de decoder start. Onder een bepaalde drempelwaarde wordt een leeg transcript en een lege taal teruggegeven, en wordt de beam search niet gestart.

Benchmarks

Word Error Rate (WER)

Lager is beter. Ontbrekende waarden betekenen dat de auteurs van dat model deze benchmark niet hebben gepubliceerd. Moonshine is alleen voor Engels; Whisper rapporteert geen SPGI.

ModelLibriSpeech test-cleanLibriSpeech test-otherSPGISpeechEarnings-22FLEURS (gemiddelde)TED-LIUMAMIMLS (gemiddelde)
WhistleVooraanVooraanVooraanVooraanVooraan---
Whisper base-----VooraanVooraanVooraan
Moonshine tiny v2--------

Opmerking: De AMI-cijfers van Whisper zijn gebaseerd op AMI-IHM, een andere subset dan de AMI die door de andere twee wordt gerapporteerd.

Prestaties (op Apple M4 Pro CPU)

Grootte (MB)

  • Kleiner is beter.
  • Whistle: 16,9 MB
  • Whisper base: 145,3 MB
  • Moonshine tiny v2: 41,9 MB

Tijd tot eerste token (ms)

  • Kleiner is beter.
  • Whistle: 11,1 ms
  • Whisper base: 73,2 ms
  • Moonshine tiny v2: 22,8 ms

Decodering (tokens per seconde)

  • Hoger is beter.
  • Whistle: 1.319/s
  • Whisper base: 266/s
  • Moonshine tiny v2: 262/s

Technische details benchmarks: Elk model is gedraaid op de officiële runtime met standaardinstellingen: de C++ engine van Whistle met 5 beams, openai-whisper, en moonshine-voice non-streaming over de gehele audio. Tijd tot het eerste token wordt gemeten van audio-input tot het eerste token. Decodering is het aantal tokens gedeeld door de tijd daarna, zodat de encoder niet dubbel wordt geteld. Whisper vult elke input aan tot 30 seconden, waardoor de tijd tot het eerste token constant is ongeacht de clipduur. Bij Whistle varieert dit: 5,9 ms bij 5 seconden, 11,1 ms bij 10 seconden en 36,3 ms bij 30 seconden.

Word error rates zijn gescoord met de Whisper-normaliseerders. Die van Whistle zijn gemeten over 86.174 uitingen. De cijfers van Whisper en Moonshine zijn de gepubliceerde cijfers van hun respectievelijke auteurs, gebaseerd op de meertalige checkpoints in plaats van de Engels-alleen versies. Er is geverifieerd dat geen test-audio voorkomt in de trainings- of validatiedata van Whistle door audio-checksums en speaker-ID's te vergelijken over elke gerapporteerde testset.

Gebruik van de engine

De functie needle_load leest elk model dat een .cact-bestand bevat, waardoor dezelfde binary spraak, tekst of beide kan verwerken:

# Alleen spraak
needle --model whistle.cact --audio clip.wav

# Alleen tekst (Needle 3)
needle --model needle3.cact --tools tools.json --prompt "turn off the kitchen lights"

# Combinatie van spraak en tekst
needle --model needle3.cact --model whistle.cact --tools tools.json --audio clip.wav

Bij de derde optie neemt needlecomplete de clip direct over. De engine transcribeert deze, beantwoordt het transcript aan de hand van de tools en retourneert één JSON-object met de calls en de spraakvelden (waarbij de spraakvelden beginnen met audio). De aanroeper hoeft het transcript niet zelf af te handelen.

Voorbeeld output:

{
  "function_calls": [
    {
      "name": "set_lights",
      "arguments": {
        "room": "kitchen",
        "on": false
      }
    }
  ],
  "confidence": 0.94,
  "audio_text": "turn off the kitchen lights",
  "audio_language": "en"
}

Aan de slag

Installatie:

pip install cactus-needle

Gebruik in Python:

import needle
print(needle.transcribe("clip.wav")["text"])
# output: turn off the kitchen lights

Voor 16 kHz WAV of raw samples is alleen de basisinstallatie nodig. Voor andere sample rates en microfoonopname is de [mic] extra vereist, die soxr en sounddevice toevoegt.

Elke call retourneert de tekst, de taal, de milliseconden tot het eerste token en de tokens per seconde van de decoder daarna.

  • word_timestamps=True: voegt elk woord toe met tijden en waarschijnlijkheid.
  • keywords=["Siobhan", "Krzysztof"]: verhoogt de log-waarschijnlijkheid van deze frases tijdens de search.
  • language="de": forceert de taal in plaats van deze te detecteren.
  • needle.Whistle(): hetzelfde model als een object, voor embed(audio) of om een getunede .cact vast te houden.

needle whistle playground transcribeert vanaf de microfoon in de terminal, en needle whistle compare voert dezelfde clip door Whistle, Whisper en Moonshine naast elkaar uit, inclusief timing.

Implementatie (Deploy)

De engine wordt geleverd als prebuilt voor zeventien targets, waaronder macOS, Linux, Android, iOS, watchOS, Windows on ARM, RISC-V, MIPS, de browser en een WASI-component. Elke map bevat een needle binary, libneedle.a en needle.h, en kan elke .cact laden die wordt meegegeven.

Voorbeeld installatie en gebruik op macOS:

needle download macos-arm64
needle download whistle
./macos-arm64/needle --model whistle.cact --audio clip.wav --audio-word-timestamps

needleload, needletranscribe en needle_embed vormen de volledige spraak C API. De engine leest geen omgevingsvariabelen; elk gedrag is een gecompileerde standaardinstelling of een expliciete vlag.

De weights zijn beschikbaar op Hugging Face, de engine en de platform-mappen staan in Cactus-Compute/needle3, en de broncode is te vinden op GitHub.