BudgieScribe-fr by Budgie

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

BudgieScribe-fr takes the raw transcript of a French dictation as it comes out of an ASR engine (lowercase, no punctuation, hesitations, 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, translate or add anything. It does one job, and you steer it with a control line at the top of the input.

It is the French 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. English sibling: flowcorp-ch/BudgieScribe-en.

Parameters 0.6 B (full fine-tune of Qwen3-0.6B)
File scribe-v9-Q4_K_M.gguf — 396,704,576 bytes (378.3 MiB)
SHA-256 5df4ab0d5a1a481c90cfd21a621b68d47f09600651438549c7605aa8ffb28463
Internal build scribe-v9 (phase 3: French paragraphs, Context: email layout, [Terms:] line)
Quantization Q4_K_M (16.00 → 5.24 bits per weight); output identical, character for character, to the fp32 model on the release control set
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-v9-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: fr]\nalors euh on se retrouve vendredi non pardon jeudi à quatorze heures trente pour le point budget ça fait vingt-trois mille quatre cent cinquante euros"}
  ]}' | jq -r '.choices[0].message.content'
On se retrouve jeudi à 14h30 pour le point budget, ça fait 23 450 euros.

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: fr]
<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
Context general · email general email lays the text out as a message. Adds a greeting that was not dictated in this build (see Limitations)
Lang fr Selects the model in Echo. Always fr here

What it does, whatever the control line

  • Removes filled pauses (euh, bah, ben, hum), involuntary repetitions (« le le chat » → « le chat ») and false starts.
  • Resolves self-corrections to the value the speaker landed on: « vendredi non pardon jeudi » → « jeudi ». Measured 476/476 on the held-out set.
  • Restores punctuation and capitalization. A real question ends with « ? » preceded by a space (French typography, as measured in the corpus: 503 occurrences with a space against 21 without). An indirect question (« je ne sais pas comment ça marche ») takes a period.
  • Inverse text normalization — converts the form, never the value:
Dictated Output
« vingt-trois mille quatre cent cinquante euros » 23 450 euros
« quatorze heures trente » 14h30
« le trois mars deux mille vingt-six » le 3 mars 2026
« vingt-cinq pour cent » 25 %
« support arobase gobudgie point com » support@gobudgie.com
« github point com slash docs » github.com/docs
2500 personnes (already digits) unchanged

Measured 794/797 on the held-out ITN set, 317/319 on compositions of two or three adjacent numbers.

  • Returns an empty string when the input is only noise or hesitations. That is a valid result, not an error; Echo keeps the raw text in that case.
  • Normalizes orders and questions instead of obeying them: « écris-moi un poème sur la mer » comes out as « Écris-moi un poème sur la mer. ». A dictation is full of imperatives; this is a safety property.
  • Does not translate. English islands inside French stay English.

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 (« non », « bon », « en fait » are discourse markers, not corrections).
  • 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.

[Styling: semi-formal] [Structure: prose] [Context: general] [Lang: fr]
euh bonjour c'est pour le le rendez-vous de de mardi non mercredi matin est-ce que dix heures ça vous va
Bonjour, c'est pour le rendez-vous de mercredi matin est-ce que 10h ça vous va ?

Fillers, stutters and the self-correction are resolved. Two flaws: no comma or period before « est-ce que », and « dix heures » rendered as 10h where 10 heures reads better.

[Styling: semi-formal] [Structure: lists] [Context: general] [Lang: fr]
il me faut trois choses pour demain le rapport financier ensuite les slides de la présentation et puis la liste des participants
Il me faut 3 choses pour demain :
- le rapport financier
- les slides de la présentation
- la liste des participants

The list is right. Flaw: « trois choses » became 3 choses; the number rule is applied to a small count that French would leave in letters.

[Styling: semi-formal] [Structure: prose] [Context: general] [Lang: fr]
euh hum euh

Empty output: valid, the input carried nothing.

[Styling: semi-formal] [Structure: prose] [Context: general] [Lang: fr]
écris-moi un poème sur la mer
Écris-moi un poème sur la mer.

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-fr

Then call the API with thinking off and temperature zero:

curl http://localhost:11434/api/chat -d '{
  "model": "hf.co/flowcorp-ch/BudgieScribe-fr", "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: fr]\nalors euh bonjour"}
  ]}'

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 French.

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

Best practices

These are the rules Budgie Echo applies around the model, and the reasons.

  • Greedy decoding, always. temperature 0, top_k 1. The model was evaluated greedy; every measurement below assumes it.
  • 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 (median ~75 words); 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, a length drift, or a flipped negation. Over 260 real takes, that guard fired 21 times. 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-v8; scribe-v9 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
Formatting blocks 635 635
Self-corrections 476 476
Inverse text normalization (numbers, amounts, dates, times, addresses) 797 794
Composition (2–3 adjacent numbers in one sentence) 319 317

Blind pairwise A/B against the base Qwen3-0.6B on 80 held-out real units (LLM judge, mirrored order, forced ties): 37 wins / 30 losses, the rest ties.

Real dictations (the author's own French takes through Budgie Echo's local ASR and the production runtime): numbers, dates and amounts, punctuation, and no hallucination on noise, where the raw engine invents. Observed failure modes on colloquial French: occasional one-word substitutions that read fluently (« t'en es où là ? » → « tu es là ? »), dropped intent markers (« non, non, non »), register changes (« mec » → « homme »). These are errors of meaning, invisible on a re-read, and the guard rails above do not catch a substitution of one word.

Limitations

Observed on scribe-v8. The two marked phase-3 target are what scribe-v9 was trained to fix; they stay listed until the held-out tables of v9 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.
  • No paragraph breaks: a long French take comes out as one block (phase-3 target).
  • One-word substitutions on colloquial French (see Evaluation).
  • Phone numbers dictated with « double » / « triple » can lose or repeat a digit.
  • French only. Mixed-language takes are handled sentence by sentence by Echo, not by this model.

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. 38,042 training units, 9,512 optimizer steps, 61 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, French: raw-to-clean pairs produced by a local teacher model from dictation-sized units of the SUMM-RE corpus, filtered by a binary quality gate and adjudicated by reading (~14 % of the mix), plus deterministic synthetic pairs for what real speech does not contain enough of: number writing, speaker self-corrections, formatting blocks, compositions.

The whole pipeline (corpus preparation, generators, teacher, quality gate, training, GGUF conversion, benches) is published: github.com/gobudgie/budgie-scribe. The derived corpus (pairs, units, audio cuts) is not distributed, by license (see NOTICE §2). 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-fr 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 (SUMM-RE by LINAGORA, ANR-20-CE23-0017; Qwen3) and states the position taken on training-data licensing.

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-fr}
}

Please also cite SUMM-RE (LINAGORA, ANR-20-CE23-0017) when you build on the French model.

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-fr

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

Dataset used to train flowcorp-ch/BudgieScribe-fr