S1-mini-GGUF by [Superwhisper](https://superwhisper.com)
32
stars
14
commits
2
linked in READMEs
Aug 28, 2026
updated
GGUF builds of superwhisper/s1-mini, release v1, for llama.cpp, Ollama, LM Studio, and anything else built on llama.cpp. You can use it in your own dictation app too, just check the license first.
S1-mini is a 0.6B-parameter text normalizer for speech-to-text output. It takes a raw ASR transcript and rewrites it as clean written text: fillers removed, false starts and self-corrections resolved to the value the speaker landed on, punctuation and capitalization applied, and spoken numbers, dates, times, currency and email addresses rendered in written form.
At Q4_K_M it is a 462 MiB file that runs comfortably on a laptop CPU, and on a held-out set of 7,519 English cases it reaches 94.8% token accuracy.
The model covers English only. It is not a chat model and will not follow general instructions; it does one job, and you steer it with a control line at the top of the input. Full documentation lives in the BF16 repository.
| File | Type | Size | Notes |
|---|---|---|---|
s1-mini-q4_k_m.gguf | Q4_K_M | 462 MB | Recommended. The build the published accuracy was measured on. |
s1-mini-f16.gguf | F16 | 1.4 GB | Unquantized conversion, the intermediate the Q4_K_M is produced from. |
Both files share the same skeleton: architecture qwen3, 311 tensors, 28
blocks, a 40,960-token context window, and an embedded chat template. The
Q4_K_M build keeps the most quantization-sensitive tensors at Q6_K (29 of 311)
and the bulk at Q4_K, while normalization parameters stay F32 in both builds.
[!NOTE] The Hub sidebar reports 0.8B parameters for this repo. Qwen3-0.6B sets
tie_word_embeddings, but shipslm_head.weightas a materialized copy of the input embedding, so the 155.6M-parameter embedding is counted twice: 751.6M tensor elements against 596.0M unique parameters. Both builds carry that layout through unchanged, andQwen/Qwen3-0.6Breports 0.8B on the Hub for the same reason. The 0.6B above counts unique parameters.
Every request needs the system prompt and a control line, exactly as shown.
System prompt:
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.
The user message is a control line, then a newline, then the transcript:
[Styling: <value>] [Structure: <value>] [Context: <value>]
<raw transcript>
| Axis | Values | What it does |
|---|---|---|
Styling | casual, semi-casual, semi-formal, formal | Sets the register: how much capitalization, apostrophe and contraction cleanup to apply. |
Structure | prose, lists | Whether the model may break enumerable content into a bulleted list. It needs at least three items, and anything that isn't really a list stays as prose. |
Context | general, email | Destination conventions. email turns on greeting-line and sign-off-block layout. |
The three axes are independent and every combination was trained.
[!IMPORTANT] The system prompt and the control line are part of the input format the model was trained on. Skip either one, change the system prompt's wording, or send values outside the trained sets, and the model can hallucinate or produce garbled output. Always send both, exactly as shown.
Filler-only or noise-only input correctly yields an empty string, so treat an empty result as valid rather than as a failure.
The register decides how much of the speaker's voice survives into the
written text. casual writes everything lowercase with apostrophes stripped
and colloquialisms kept. semi-casual keeps the speaker's phrasing but
capitalizes I and its contractions. semi-formal is standard written
English with contractions kept and colloquialisms smoothed (gonna becomes
going to), a good default. formal also expands contractions (I am,
cannot).
Here is the same input under all four registers:
Input: hmm im gonna be late theres a cute dog outside i cant just walk past him
| Styling | Output |
|---|---|
casual | hmm im gonna be late. theres a cute dog outside. i cant just walk past him |
semi-casual | hmm, I'm gonna be late. there's a cute dog outside. I can't just walk past him |
semi-formal | I'm going to be late. There's a cute dog outside. I can't just walk past him. |
formal | I am going to be late. There is a cute dog outside. I cannot just walk past him. |
Filled pauses like um and uh are removed in every register.
prose keeps everything in sentences and paragraphs. lists permits the
model to break enumerable content into Markdown bullets, and it is
deliberately conservative about it: it wants at least three items, and
content that is not clearly an enumeration stays as prose. Here is the same
input under both values:
Input: so for the trip we need to pack sunscreen and then also a first aid kit and um chargers for everything
Structure: prose
So for the trip, we need to pack sunscreen and then also a first aid kit and chargers for everything.
Structure: lists
So for the trip, we need to pack:
- Sunscreen
- A first aid kit
- Chargers for everything
general produces flowing text, while email reshapes the transcript into
email layout, with a greeting line, the body and a sign-off block separated by
blank lines. Here is the same input under both values:
Input: hey sarah just wanted to follow up on the proposal can you send the numbers by end of week thanks john
Context: general
Hey Sarah, just wanted to follow up on the proposal. Can you send the numbers by end of week? Thanks, John.
Context: email
Hey Sarah,
Just wanted to follow up on the proposal. Can you send the numbers by end of week?
Thanks,
John
Turn thinking off via the template. The embedded chat template is Qwen3's,
which defaults to thinking mode. S1-mini was trained with thinking off, so the
assistant turn must start with an empty <think> block, or you will get no
usable output. In llama.cpp that means --jinja together with
--chat-template-kwargs '{"enable_thinking":false}'. Don't substitute
--reasoning-budget 0: it suppresses the think block a different way, and the
output degrades (fillers survive into the result).
Use greedy decoding. Normalization is deterministic and sampling only adds
variance, so always pass --temp 0 explicitly. Do not rely on the file's
defaults: these builds carry general.sampling.temp = 0.6, top_p = 0.95 and
top_k = 20 in their metadata, inherited from Qwen3-0.6B rather than from
S1-mini, which is trained for greedy decoding. Runtimes differ in whether they
honor those keys, and llama.cpp's own default is temperature 0.8, so set it on
every request.
llama-server -hf superwhisper/s1-mini-GGUF:Q4_K_M --jinja --chat-template-kwargs '{"enable_thinking":false}' --temp 0
(Use -m s1-mini-q4_k_m.gguf instead of -hf if you already have the file.)
curl -s http://localhost:8080/v1/chat/completions -H 'Content-Type: application/json' -d '{
"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]\nso um i need to like send the the report by uh friday no wait make that thursday"}
],
"temperature": 0
}' | jq -r '.choices[0].message.content'
# I need to send the report by Thursday.
Single shot from the command line:
llama-cli -m s1-mini-q4_k_m.gguf --jinja --chat-template-kwargs '{"enable_thinking":false}' --temp 0 -st \
-sys "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." \
-p "[Styling: semi-formal] [Structure: prose] [Context: general]
so um i need to like send the the report by uh friday no wait make that thursday"
# I need to send the report by Thursday.
If you use the raw /completion endpoint, you need to match the training
prefix exactly. The assistant turn opens with an empty think block:
<|im_start|>system
{system prompt}<|im_end|>
<|im_start|>user
{control line}
{transcript}<|im_end|>
<|im_start|>assistant
<think>
</think>
Written out, the assistant prefix is
<|im_start|>assistant\n<think>\n\n</think>\n\n, with two newlines inside the
think block and two more after it.
Hard-code the non-thinking prompt format in the Modelfile rather than relying on a thinking toggle:
FROM ./s1-mini-q4_k_m.gguf
SYSTEM """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."""
TEMPLATE """<|im_start|>system
{{ .System }}<|im_end|>
<|im_start|>user
{{ .Prompt }}<|im_end|>
<|im_start|>assistant
<think>
</think>
"""
PARAMETER temperature 0
PARAMETER num_ctx 4096
ollama create s1-mini -f Modelfile
ollama run s1-mini "[Styling: semi-formal] [Structure: prose] [Context: general]
so um i need to like send the the report by uh friday no wait make that thursday"
Load s1-mini-q4_k_m.gguf, set temperature to 0, and disable the
thinking/reasoning toggle. If you see <think> tags or get empty output, the
template's thinking branch is still active, and the assistant turn needs to
begin with the empty think block shown above.
The published number, 94.8% token accuracy on a held-out English test set of
7,519 cases, was measured with greedy decoding on the s1-mini-q4_k_m.gguf
build in this repo.
S1-mini is Apache 2.0 plus a naming clause, the same base license it inherits from Qwen3-0.6B, so these builds can be embedded in open-source and commercial software alike: dictation apps, meeting-notes tools, live captioning, voice-driven editors, or any pipeline that has to turn raw ASR output into text a person will read. At 462 MB the Q4_K_M build is small enough to ship on-device.
It is a post-processing stage rather than a standalone system:
audio ──▶ ASR (Whisper, Parakeet, …) ──▶ S1-mini ──▶ clean text
Nothing about the model is Superwhisper-specific. The two things to get right in any integration are the input format documented above and the thinking flag; nearly every integration bug traces back to one of those.
[!IMPORTANT] Read the LICENSE before you ship. Apache 2.0 is permissive but not obligation-free: you must retain the license text and the NOTICE file, and state significant changes if you redistribute a modified version. It also carries one additional term: the model must keep its name, "S1-mini" by "Superwhisper", with that exact capitalization, wherever it's used. If you are bundling S1-mini into a commercial dictation app or redistributing the weights yourself, confirm the terms cover your case rather than assuming they do.
S1-mini is released under Apache 2.0, which it inherits from Qwen3-0.6B, plus one additional term: wherever it's used, it must keep its name, "S1-mini" by "Superwhisper", with that exact capitalization. See LICENSE and NOTICE.
@misc{s1mini2026,
title = {S1-mini: a small text normalizer for speech-to-text output},
author = {Superwhisper},
year = {2026},
url = {https://huggingface.co/superwhisper/s1-mini}
}
Built on Qwen3:
@misc{qwen3technicalreport,
title = {Qwen3 Technical Report},
author = {Qwen Team},
year = {2025},
eprint = {2505.09388},
archivePrefix = {arXiv},
primaryClass = {cs.CL},
url = {https://arxiv.org/abs/2505.09388}
}
S1-mini-GGUF by [Superwhisper](https://superwhisper.com)
32
stars
14
commits
2
linked in READMEs
Aug 28, 2026
updated
GGUF builds of superwhisper/s1-mini, release v1, for llama.cpp, Ollama, LM Studio, and anything else built on llama.cpp. You can use it in your own dictation app too, just check the license first.
S1-mini is a 0.6B-parameter text normalizer for speech-to-text output. It takes a raw ASR transcript and rewrites it as clean written text: fillers removed, false starts and self-corrections resolved to the value the speaker landed on, punctuation and capitalization applied, and spoken numbers, dates, times, currency and email addresses rendered in written form.
At Q4_K_M it is a 462 MiB file that runs comfortably on a laptop CPU, and on a held-out set of 7,519 English cases it reaches 94.8% token accuracy.
The model covers English only. It is not a chat model and will not follow general instructions; it does one job, and you steer it with a control line at the top of the input. Full documentation lives in the BF16 repository.
| File | Type | Size | Notes |
|---|---|---|---|
s1-mini-q4_k_m.gguf | Q4_K_M | 462 MB | Recommended. The build the published accuracy was measured on. |
s1-mini-f16.gguf | F16 | 1.4 GB | Unquantized conversion, the intermediate the Q4_K_M is produced from. |
Both files share the same skeleton: architecture qwen3, 311 tensors, 28
blocks, a 40,960-token context window, and an embedded chat template. The
Q4_K_M build keeps the most quantization-sensitive tensors at Q6_K (29 of 311)
and the bulk at Q4_K, while normalization parameters stay F32 in both builds.
[!NOTE] The Hub sidebar reports 0.8B parameters for this repo. Qwen3-0.6B sets
tie_word_embeddings, but shipslm_head.weightas a materialized copy of the input embedding, so the 155.6M-parameter embedding is counted twice: 751.6M tensor elements against 596.0M unique parameters. Both builds carry that layout through unchanged, andQwen/Qwen3-0.6Breports 0.8B on the Hub for the same reason. The 0.6B above counts unique parameters.
Every request needs the system prompt and a control line, exactly as shown.
System prompt:
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.
The user message is a control line, then a newline, then the transcript:
[Styling: <value>] [Structure: <value>] [Context: <value>]
<raw transcript>
| Axis | Values | What it does |
|---|---|---|
Styling | casual, semi-casual, semi-formal, formal | Sets the register: how much capitalization, apostrophe and contraction cleanup to apply. |
Structure | prose, lists | Whether the model may break enumerable content into a bulleted list. It needs at least three items, and anything that isn't really a list stays as prose. |
Context | general, email | Destination conventions. email turns on greeting-line and sign-off-block layout. |
The three axes are independent and every combination was trained.
[!IMPORTANT] The system prompt and the control line are part of the input format the model was trained on. Skip either one, change the system prompt's wording, or send values outside the trained sets, and the model can hallucinate or produce garbled output. Always send both, exactly as shown.
Filler-only or noise-only input correctly yields an empty string, so treat an empty result as valid rather than as a failure.
The register decides how much of the speaker's voice survives into the
written text. casual writes everything lowercase with apostrophes stripped
and colloquialisms kept. semi-casual keeps the speaker's phrasing but
capitalizes I and its contractions. semi-formal is standard written
English with contractions kept and colloquialisms smoothed (gonna becomes
going to), a good default. formal also expands contractions (I am,
cannot).
Here is the same input under all four registers:
Input: hmm im gonna be late theres a cute dog outside i cant just walk past him
| Styling | Output |
|---|---|
casual | hmm im gonna be late. theres a cute dog outside. i cant just walk past him |
semi-casual | hmm, I'm gonna be late. there's a cute dog outside. I can't just walk past him |
semi-formal | I'm going to be late. There's a cute dog outside. I can't just walk past him. |
formal | I am going to be late. There is a cute dog outside. I cannot just walk past him. |
Filled pauses like um and uh are removed in every register.
prose keeps everything in sentences and paragraphs. lists permits the
model to break enumerable content into Markdown bullets, and it is
deliberately conservative about it: it wants at least three items, and
content that is not clearly an enumeration stays as prose. Here is the same
input under both values:
Input: so for the trip we need to pack sunscreen and then also a first aid kit and um chargers for everything
Structure: prose
So for the trip, we need to pack sunscreen and then also a first aid kit and chargers for everything.
Structure: lists
So for the trip, we need to pack:
- Sunscreen
- A first aid kit
- Chargers for everything
general produces flowing text, while email reshapes the transcript into
email layout, with a greeting line, the body and a sign-off block separated by
blank lines. Here is the same input under both values:
Input: hey sarah just wanted to follow up on the proposal can you send the numbers by end of week thanks john
Context: general
Hey Sarah, just wanted to follow up on the proposal. Can you send the numbers by end of week? Thanks, John.
Context: email
Hey Sarah,
Just wanted to follow up on the proposal. Can you send the numbers by end of week?
Thanks,
John
Turn thinking off via the template. The embedded chat template is Qwen3's,
which defaults to thinking mode. S1-mini was trained with thinking off, so the
assistant turn must start with an empty <think> block, or you will get no
usable output. In llama.cpp that means --jinja together with
--chat-template-kwargs '{"enable_thinking":false}'. Don't substitute
--reasoning-budget 0: it suppresses the think block a different way, and the
output degrades (fillers survive into the result).
Use greedy decoding. Normalization is deterministic and sampling only adds
variance, so always pass --temp 0 explicitly. Do not rely on the file's
defaults: these builds carry general.sampling.temp = 0.6, top_p = 0.95 and
top_k = 20 in their metadata, inherited from Qwen3-0.6B rather than from
S1-mini, which is trained for greedy decoding. Runtimes differ in whether they
honor those keys, and llama.cpp's own default is temperature 0.8, so set it on
every request.
llama-server -hf superwhisper/s1-mini-GGUF:Q4_K_M --jinja --chat-template-kwargs '{"enable_thinking":false}' --temp 0
(Use -m s1-mini-q4_k_m.gguf instead of -hf if you already have the file.)
curl -s http://localhost:8080/v1/chat/completions -H 'Content-Type: application/json' -d '{
"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]\nso um i need to like send the the report by uh friday no wait make that thursday"}
],
"temperature": 0
}' | jq -r '.choices[0].message.content'
# I need to send the report by Thursday.
Single shot from the command line:
llama-cli -m s1-mini-q4_k_m.gguf --jinja --chat-template-kwargs '{"enable_thinking":false}' --temp 0 -st \
-sys "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." \
-p "[Styling: semi-formal] [Structure: prose] [Context: general]
so um i need to like send the the report by uh friday no wait make that thursday"
# I need to send the report by Thursday.
If you use the raw /completion endpoint, you need to match the training
prefix exactly. The assistant turn opens with an empty think block:
<|im_start|>system
{system prompt}<|im_end|>
<|im_start|>user
{control line}
{transcript}<|im_end|>
<|im_start|>assistant
<think>
</think>
Written out, the assistant prefix is
<|im_start|>assistant\n<think>\n\n</think>\n\n, with two newlines inside the
think block and two more after it.
Hard-code the non-thinking prompt format in the Modelfile rather than relying on a thinking toggle:
FROM ./s1-mini-q4_k_m.gguf
SYSTEM """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."""
TEMPLATE """<|im_start|>system
{{ .System }}<|im_end|>
<|im_start|>user
{{ .Prompt }}<|im_end|>
<|im_start|>assistant
<think>
</think>
"""
PARAMETER temperature 0
PARAMETER num_ctx 4096
ollama create s1-mini -f Modelfile
ollama run s1-mini "[Styling: semi-formal] [Structure: prose] [Context: general]
so um i need to like send the the report by uh friday no wait make that thursday"
Load s1-mini-q4_k_m.gguf, set temperature to 0, and disable the
thinking/reasoning toggle. If you see <think> tags or get empty output, the
template's thinking branch is still active, and the assistant turn needs to
begin with the empty think block shown above.
The published number, 94.8% token accuracy on a held-out English test set of
7,519 cases, was measured with greedy decoding on the s1-mini-q4_k_m.gguf
build in this repo.
S1-mini is Apache 2.0 plus a naming clause, the same base license it inherits from Qwen3-0.6B, so these builds can be embedded in open-source and commercial software alike: dictation apps, meeting-notes tools, live captioning, voice-driven editors, or any pipeline that has to turn raw ASR output into text a person will read. At 462 MB the Q4_K_M build is small enough to ship on-device.
It is a post-processing stage rather than a standalone system:
audio ──▶ ASR (Whisper, Parakeet, …) ──▶ S1-mini ──▶ clean text
Nothing about the model is Superwhisper-specific. The two things to get right in any integration are the input format documented above and the thinking flag; nearly every integration bug traces back to one of those.
[!IMPORTANT] Read the LICENSE before you ship. Apache 2.0 is permissive but not obligation-free: you must retain the license text and the NOTICE file, and state significant changes if you redistribute a modified version. It also carries one additional term: the model must keep its name, "S1-mini" by "Superwhisper", with that exact capitalization, wherever it's used. If you are bundling S1-mini into a commercial dictation app or redistributing the weights yourself, confirm the terms cover your case rather than assuming they do.
S1-mini is released under Apache 2.0, which it inherits from Qwen3-0.6B, plus one additional term: wherever it's used, it must keep its name, "S1-mini" by "Superwhisper", with that exact capitalization. See LICENSE and NOTICE.
@misc{s1mini2026,
title = {S1-mini: a small text normalizer for speech-to-text output},
author = {Superwhisper},
year = {2026},
url = {https://huggingface.co/superwhisper/s1-mini}
}
Built on Qwen3:
@misc{qwen3technicalreport,
title = {Qwen3 Technical Report},
author = {Qwen Team},
year = {2025},
eprint = {2505.09388},
archivePrefix = {arXiv},
primaryClass = {cs.CL},
url = {https://arxiv.org/abs/2505.09388}
}