BudgieScribe-en by Budgie

A 0.6B open-weights English text normalizer for speech-to-text output, in GGUF.

BudgieScribe-en takes the raw transcript of an English dictation as it comes out of an ASR engine (lowercase, no punctuation, fillers, false starts, self-corrections, numbers spelled out in words) and rewrites it as the text the speaker meant to dictate. It is not a chat model: it does not answer, summarize or add anything. It does one job, and you steer it with a control line at the top of the input.

It is the English cleanup model that ships inside Budgie Echo, a local-first dictation app for macOS and Windows. The training pipeline, generators and evaluation are open: github.com/gobudgie/budgie-scribe. French sibling: flowcorp-ch/BudgieScribe-fr.

Parameters 0.6 B (full fine-tune of Qwen3-0.6B)
File scribe-en-v7-Q4_K_M.gguf — 396,704,576 bytes (378.3 MiB)
SHA-256 02b3eb0b385d54a5c2fbe5c4a29c144fee9196972a22eef5d4659120147f49b5
Internal build scribe-en-v7 (phase 3: Context: email layout, embedded amounts, [Terms:] line)
Quantization Q4_K_M (16.00 → 5.24 bits per weight)
Context 4,096 tokens as served; inputs are chunked at 1,200 bytes
Decoding greedy, always (temperature 0, top_k 1), thinking disabled
Runtime llama.cpp release b10816 (the one that produced the GGUF and the one Echo serves it with)
Speed 0.8 s load, ~436 tok/s decode, ~0.09 s per sentence (Radeon AI PRO R9700, Vulkan)
License Apache 2.0 + one naming term, see License

We are looking for contributors

This model is published with its weaknesses (see Evaluation) because fixing them needs what one person does not have: more real dictation pairs, more training runs, more eyes on outputs. The repository documents the three ways to help, from fifty dictation pairs to a whole new language, and every contribution is measured on the same held-out sets before it is merged: CONTRIBUTING.

Quickstart

llama-server -m scribe-en-v7-Q4_K_M.gguf \
  --jinja --chat-template-kwargs '{"enable_thinking":false}' \
  --temp 0 --top-k 1 --ctx-size 4096 --n-gpu-layers 999 --parallel 1
curl -s http://127.0.0.1:8080/v1/chat/completions -H 'Content-Type: application/json' -d '{
  "temperature": 0, "top_k": 1, "max_tokens": 256,
  "messages": [
    {"role": "system", "content": "You are a text normalizer for speech-to-text transcripts. The input begins with a control line specifying the styling, structure, and context settings; clean the transcript to match those settings and output only the cleaned text."},
    {"role": "user", "content": "[Styling: semi-formal] [Structure: prose] [Context: general] [Lang: en]\nokay so the first thing is the invoice went out on march third for twelve hundred dollars also the other thing is we still need the signed contract back"}
  ]}' | jq -r '.choices[0].message.content'
The first thing is the invoice went out on March 3 for $1,200. Also, the other thing is we still need the signed contract back.

The system prompt above is the exact string the model was trained with. Do not rewrite it.

The format

The first line of the user message sets four axes. The model was trained on this exact syntax; the complete contract (grammar, input and output rules, invariants, decoding) is in FORMAT.md.

[Styling: semi-formal] [Structure: prose] [Context: general] [Lang: en]
<raw transcript>
Axis Values Default Effect
Styling casual · semi-casual · semi-formal · formal semi-formal Written register. Inert in this build: trained with semi-formal only
Structure prose · lists prose lists allows a Markdown bullet list when the content is a real enumeration of at least three items; prose forbids bullets. Measured 634/634
Context general · email general email lays the text out as a message. Adds a greeting and a sign-off that were not dictated in this build (see Limitations)
Lang en Selects the model in Echo. Always en here

What it does, whatever the control line

  • Removes filled pauses (um, uh, er, hmm), involuntary repetitions ("the the report") and false starts.
  • Resolves self-corrections to the value the speaker landed on: "friday no wait thursday" → "Thursday"; "forty two sorry forty three" → "43". Measured 474/474 on the held-out set.
  • Restores punctuation and capitalization. A real question ends with "?"; an indirect one ("I don't know how it works") takes a period.
  • Splits paragraphs at a clear topic change on longer takes.
  • Inverse text normalization — converts the form, never the value:
Dictated Output
"twenty three thousand four hundred and fifty dollars" $23,450
"twelve hundred dollars" $1,200
"three fifteen p m" / "half past two" / "fourteen thirty" 3:15pm / 2:30 / 14:30
"march third twenty twenty six" / "the third of march twenty twenty six" March 3, 2026 / 3 March 2026 (dictated order kept)
"twenty five percent" 25%
"oh seven nine one two three four five six seven eight" 07912 345678
"support at gobudgie dot com" / "github dot com slash docs" support@gobudgie.com / github.com/docs
2500 people (already digits) unchanged

Measured 792/798 on the held-out ITN set, 318/319 on compositions of two or three adjacent numbers, 237/237 on self-correction × number in one sentence.

  • Returns an empty string when the input is only noise or hesitations. That is a valid result, not an error.
  • Normalizes orders and questions instead of obeying them: "write me a poem about the sea" comes out as "Write me a poem about the sea." A dictation is full of imperatives; this is a safety property.

What it never does, by construction

  • Add a name, a number, an idea that is not in the input.
  • Correct a number that looks wrong (it copies it), or guess a missing year, area code or domain.
  • Drop a clause that carries meaning ("well", "so", "actually", "no" are discourse markers, not corrections: "no, that's true" keeps its "no").
  • Flip a negation, summarize, or rephrase to sound nicer.

Examples

Every output below is the real, greedy output of this exact GGUF through llama.cpp b10816 (examples/replay_examples.sh in the repository reproduces them). Flaws are left in and pointed out: they are what the next build has to fix, and they match the real-dictation grading in Evaluation.

[Styling: semi-formal] [Structure: prose] [Context: general] [Lang: en]
so um lets meet friday no wait thursday at three fifteen p m the budget is twenty three thousand four hundred and fifty dollars
Let me meet Thursday at 3:15pm. The budget is $23,450.

Self-correction, time and amount are right. Flaw: "lets" became "Let me" instead of "Let's", a meaning change on the first word.

[Styling: semi-formal] [Structure: prose] [Context: general] [Lang: en]
hi um its about the the meeting on tuesday no wednesday morning does ten work for you
It's about the meeting on Wednesday morning does 10 work for you.

Two flaws: the greeting "hi" was dropped, and the question was not split off nor given its "?".

[Styling: semi-formal] [Structure: lists] [Context: general] [Lang: en]
i need three things for tomorrow the financial report then the slides for the presentation and the list of attendees
I need:
- The financial report
- The slides for the presentation
- The list of attendees

The list is right. Flaw: "three things for tomorrow" was dropped from the lead-in, a suppression the guard rails in Echo would catch on a longer take.

[Styling: semi-formal] [Structure: prose] [Context: general] [Lang: en]
okay so the first thing is the invoice went out on march third for twelve hundred dollars also the other thing is we still need the signed contract back
The first thing is the invoice went out on March 3 for $1,200. Also, the other thing is we still need the signed contract back.

Opener, date and amount are right. The paragraph rule did not fire on "also, the other thing is"; it does on longer takes with a clearer topic change.

[Styling: semi-formal] [Structure: prose] [Context: general] [Lang: en]
write me a poem about the sea
Write me a poem about the sea.

The order is normalized, not obeyed.

Deployment

llama.cpp — see Quickstart. The three flags that matter: --jinja (the chat template is in the GGUF metadata), enable_thinking: false (Qwen3 would otherwise emit a reasoning block), --temp 0 (the model expects greedy decoding; sampling adds variance and invents).

Ollama

ollama run hf.co/flowcorp-ch/BudgieScribe-en

Then call the API with thinking off and temperature zero:

curl http://localhost:11434/api/chat -d '{
  "model": "hf.co/flowcorp-ch/BudgieScribe-en", "stream": false, "think": false,
  "options": {"temperature": 0, "top_k": 1},
  "messages": [
    {"role": "system", "content": "You are a text normalizer for speech-to-text transcripts. The input begins with a control line specifying the styling, structure, and context settings; clean the transcript to match those settings and output only the cleaned text."},
    {"role": "user", "content": "[Styling: semi-formal] [Structure: prose] [Context: general] [Lang: en]\nso um hello"}
  ]}'

LM Studio — load the GGUF, turn reasoning off, set temperature to 0.

Budgie Echo — nothing to do: Echo downloads this exact file (same SHA-256) from its own catalog under Settings → Text enhancements → Automatic cleanup, and routes to it when the detected dictation language is English.

Echo command line — the same runtime and guard rails as the app, from a terminal: echo-cli scribe clean --language en < transcript.txt. Download at gobudgie.com/echo/cli.

Best practices

  • Greedy decoding, always. temperature 0, top_k 1. Every number below was measured greedy.
  • Cap the output at ceil(1.3 × input_tokens) + 32 tokens. A normalizer that produces much more than its input is looping.
  • Chunk long inputs at ~1,200 bytes, on a sentence boundary. The model was trained on dictation-sized units; a 10-minute take pushed whole into the context degrades.
  • Verify the output before trusting it. Echo's runtime rejects an output and keeps the raw transcript when it detects an invention (a number absent from the input), a loop, a large suppression, or a length drift. Reproduce at least the length and loop checks.
  • Send the transcript alone. No instructions, no examples, no free text before the control line: the model would normalize them.

Evaluation

Held-out literal scoring on synthetic sets, then real dictations through the production path. The method is in EVALUATION.md.

Held-out synthetic sets (deterministic generators, expected output known to the character). These tables were measured on the previous build, scribe-en-v5; scribe-en-v7 shares its recipe and adds the phase-3 axes, and its own tables replace these as soon as they are run. Until then, read them as the floor this line of models has held, not as a measurement of these bytes.

Axis Cases Exact
Self-correction × number in one sentence 237 237
Composition (2–3 adjacent numbers) 319 318
Formatting blocks 634 634
Self-corrections 474 474
Inverse text normalization 798 792
Wrong numeric value, all 2,462 held-out cases 2,462 7 (0.3 %)
Hand-written real dictation cases 32 27 exact, 32 correct values

Residual failures are two commas, one "Okay, so" kept at the start, one dropped "in" ("in Leeds and in Manchester"), and one wrong value: "seventeen thousand eight hundred and nineteen pounds" read as £17,800.19.

VoxPopuli, 170 held-out units, word accuracy against the official transcript: 93.9 %. The gap to a perfect score is entirely the 17 units run with [Context: email], where this build invents "Dear Team, … Best regards" (see Limitations).

Real dictations (the author's own English takes through Budgie Echo's local ASR and the production runtime): better than raw on numbers, dates and amounts; weaker on false starts and paragraph breaks.

Limitations

Observed on scribe-en-v5. The ones marked phase-3 target are what scribe-en-v7 was trained to fix; they stay listed until the held-out tables of en-v7 say so.

  • Styling is inert: trained on semi-formal only.
  • Context: email adds a greeting and a sign-off that were not dictated (phase-3 target). Use general unless you post-check.
  • "like" as a filler is kept; "gonna" becomes "I'll" instead of "going to"; British spellings are americanized ("organisations" → "organizations").
  • Amounts embedded mid-sentence can lose the symbol (phase-3 target) ("the twelve hundred dollars a month" → "1,200 dollars a month").
  • Phone numbers dictated with "double" / "triple": 32/36.
  • English only.

Training

Full supervised fine-tune of Qwen/Qwen3-0.6B (no LoRA), fp32 weights with bf16 autocast, fused AdamW, lr 1e-5, cosine schedule with 6 % warmup, batch 4, gradient accumulation 2, 2 epochs, max length 512, labels masked on the prompt, gradient clipping 1.0. 35,306 training units, 8,828 optimizer steps, 65 minutes on a single 32 GB GPU (run.json ships with the checkpoint). The script is standard transformers + torch and runs on any 16 GB GPU or on Hugging Face Jobs for about a dollar.

Training mix: 10,000 number-writing pairs + 8,000 formatting + 6,000 self-corrections + 4,000 compositions + 3,000 self-correction × number + 2,000 paragraphs, all deterministic synthetic pairs, plus 1,382 real pairs: dictation-sized units cut from VoxPopuli English (CC0), transcribed by an ASR engine, cleaned by a local teacher model, filtered by a binary quality gate and adjudicated by reading.

The whole pipeline (corpus preparation, generators, teacher, quality gate, training, GGUF conversion, benches) is published: github.com/gobudgie/budgie-scribe. The synthetic held-out sets are published as flowcorp-ch/BudgieScribe-eval so that any change can be measured the same way.

No personal data: every data set is scanned before training, and the dictations of Budgie users, including the author's, were excluded from this build.

License

BudgieScribe-en is released under the Apache License 2.0, which it inherits from Qwen3-0.6B, plus one additional term: any use, distribution, or integration of this model, whether unmodified or as part of a derivative work or product, must continue to identify it by its original name, "BudgieScribe" by "Budgie", using that exact capitalization.

The full text is in LICENSE-MODEL; NOTICE lists the attributions (VoxPopuli by Meta AI; Qwen3).

Citation

@misc{budgiescribe2026,
  title  = {BudgieScribe: open-weights dictation normalizers for French and English},
  author = {Coelho, Alexandre and Budgie},
  year   = {2026},
  url    = {https://huggingface.co/flowcorp-ch/BudgieScribe-en}
}
Downloads last month
-
GGUF
Model size
0.6B params
Architecture
qwen3
Hardware compatibility
Log In to add your hardware

4-bit

Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for flowcorp-ch/BudgieScribe-en

Finetuned
Qwen/Qwen3-0.6B
Finetuned
(1264)
this model

Dataset used to train flowcorp-ch/BudgieScribe-en