初始化项目,由ModelHub XC社区提供模型

Model: superwhisper/s1-mini
Source: Original Platform
This commit is contained in:
ModelHub XC
2026-10-12 21:41:23 +08:00
commit eb77594541
13 changed files with 152448 additions and 0 deletions

37
.gitattributes vendored Normal file
View File

@@ -0,0 +1,37 @@
*.7z filter=lfs diff=lfs merge=lfs -text
*.arrow filter=lfs diff=lfs merge=lfs -text
*.bin filter=lfs diff=lfs merge=lfs -text
*.bz2 filter=lfs diff=lfs merge=lfs -text
*.ckpt filter=lfs diff=lfs merge=lfs -text
*.ftz filter=lfs diff=lfs merge=lfs -text
*.gz filter=lfs diff=lfs merge=lfs -text
*.h5 filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text
*.joblib filter=lfs diff=lfs merge=lfs -text
*.lfs.* filter=lfs diff=lfs merge=lfs -text
*.mlmodel filter=lfs diff=lfs merge=lfs -text
*.model filter=lfs diff=lfs merge=lfs -text
*.msgpack filter=lfs diff=lfs merge=lfs -text
*.npy filter=lfs diff=lfs merge=lfs -text
*.npz filter=lfs diff=lfs merge=lfs -text
*.onnx filter=lfs diff=lfs merge=lfs -text
*.ot filter=lfs diff=lfs merge=lfs -text
*.parquet filter=lfs diff=lfs merge=lfs -text
*.pb filter=lfs diff=lfs merge=lfs -text
*.pickle filter=lfs diff=lfs merge=lfs -text
*.pkl filter=lfs diff=lfs merge=lfs -text
*.pt filter=lfs diff=lfs merge=lfs -text
*.pth filter=lfs diff=lfs merge=lfs -text
*.rar filter=lfs diff=lfs merge=lfs -text
*.safetensors filter=lfs diff=lfs merge=lfs -text
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
*.tar.* filter=lfs diff=lfs merge=lfs -text
*.tar filter=lfs diff=lfs merge=lfs -text
*.tflite filter=lfs diff=lfs merge=lfs -text
*.tgz filter=lfs diff=lfs merge=lfs -text
*.wasm filter=lfs diff=lfs merge=lfs -text
*.xz filter=lfs diff=lfs merge=lfs -text
*.zip filter=lfs diff=lfs merge=lfs -text
*.zst filter=lfs diff=lfs merge=lfs -text
*tfevents* filter=lfs diff=lfs merge=lfs -text
tokenizer.json filter=lfs diff=lfs merge=lfs -text

217
LICENSE Normal file
View File

@@ -0,0 +1,217 @@
Copyright 2026 Superwhisper
This model is a derivative work of Qwen3-0.6B, Copyright 2024 Alibaba Cloud,
licensed under the Apache License, Version 2.0.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
------------------------------------------------------------------------
ADDITIONAL TERM
In addition to the terms of the Apache License, Version 2.0 above: 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, "S1-mini" by "Superwhisper", using that exact
capitalization, regardless of any other name under which the model or
a product incorporating it is marketed or distributed.

11
NOTICE Normal file
View File

@@ -0,0 +1,11 @@
S1-mini
Copyright 2026 Superwhisper
This work is a derivative of Qwen3-0.6B, Copyright 2024 Alibaba Cloud,
licensed under the Apache License, Version 2.0.
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, "S1-mini" by "Superwhisper", using that exact capitalization.
This restates the ADDITIONAL TERM of the LICENSE file, which is the
operative text.

417
README.md Normal file
View File

@@ -0,0 +1,417 @@
---
license: other
license_name: s1-mini-license
license_link: LICENSE
base_model: Qwen/Qwen3-0.6B
base_model_relation: finetune
library_name: transformers
pipeline_tag: text-generation
language:
- en
tags:
- asr
- automatic-speech-recognition
- text-normalization
- inverse-text-normalization
- punctuation
- truecasing
- speech-to-text
- dictation
- post-processing
- qwen3
---
# S1-mini by [Superwhisper](https://superwhisper.com)
<div align="center">
<img src="./banner.jpg" alt="S1-mini banner" width="100%">
[![Website](https://img.shields.io/badge/Website-blue?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxjaXJjbGUgY3g9IjEyIiBjeT0iMTIiIHI9IjEwIi8+PGxpbmUgeDE9IjIiIHkxPSIxMiIgeDI9IjIyIiB5Mj0iMTIiLz48cGF0aCBkPSJNMTIgMmExNS4zIDE1LjMgMCAwIDEgNCAxMCAxNS4zIDE1LjMgMCAwIDEtNCAxMCAxNS4zIDE1LjMgMCAwIDEtNC0xMCAxNS4zIDE1LjMgMCAwIDEgNC0xMHoiLz48L3N2Zz4=)](https://superwhisper.com)
[![Discord](https://img.shields.io/badge/Discord-5865F2?logo=discord&logoColor=white)](https://discord.gg/tF98XvJNvB)
[![v1](https://img.shields.io/badge/v1-2EA44F?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0yMC41OSAxMy40MUwxMy40MiAyMC41OGEyIDIgMCAwIDEtMi44MyAwTDIgMTJWMmgxMGw4LjU5IDguNTlhMiAyIDAgMCAxIDAgMi44MnoiLz48bGluZSB4MT0iNyIgeTE9IjciIHgyPSI3LjAxIiB5Mj0iNyIvPjwvc3ZnPg==&logoColor=white)](https://huggingface.co/superwhisper/s1-mini/tree/v1)
</div>
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.
On a held-out set of 7,519 English cases it reaches 94.8% token accuracy, and
the quantized build is a 462 MiB file that runs comfortably on a laptop CPU.
This is release v1, and it covers English only. S1-mini 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.
Fine-tuned from [Qwen/Qwen3-0.6B](https://huggingface.co/Qwen/Qwen3-0.6B). If
you want to run it in llama.cpp, Ollama, LM Studio, or anything else built on
llama.cpp, grab the GGUF builds from
[superwhisper/s1-mini-GGUF](https://huggingface.co/superwhisper/s1-mini-GGUF).
You can use it in your own dictation app too, just check the license first.
Releases are tagged, so you can pin one:
`from_pretrained("superwhisper/s1-mini", revision="v1")`.
## Model overview
| | |
|---|---|
| Type | Causal language model, fine-tuned for a single transformation task |
| Base model | [Qwen/Qwen3-0.6B](https://huggingface.co/Qwen/Qwen3-0.6B) |
| Parameters | 596M total (0.44B non-embedding), embeddings tied |
| Layers | 28 |
| Attention heads | 16 for Q, 8 for KV (GQA) |
| Precision | BF16 |
| Recommended input | Up to ~1,000 tokens; chunk longer transcripts |
| Language | English |
| License | Apache 2.0 + naming clause ([LICENSE](https://huggingface.co/superwhisper/s1-mini/blob/main/LICENSE)) |
> [!NOTE]
> The Hub sidebar reports 0.8B parameters for this repo. `config.json` sets
> `tie_word_embeddings`, but `model.safetensors` still stores `lm_head.weight`
> as 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. The layout is inherited from `Qwen/Qwen3-0.6B`, which reports 0.8B
> on the Hub for the same reason. The table above counts unique parameters.
**Input.** The model expects the system prompt, then a control line, a
newline, and one raw ASR transcript, which will usually arrive lowercase and
unpunctuated. That is the shape it was trained on.
**Output.** It returns the cleaned transcript as plain text and nothing else,
with no preamble and no explanation. Under `Structure: lists` the output may
contain Markdown bullets, and under `Context: email` it may contain blank
lines separating a greeting, body and sign-off. When the input is nothing but
filler or noise, the correct output is an empty string, and that is what you
get.
## Quickstart
Qwen3 support landed in `transformers` 4.51.0; with anything older you will
get `KeyError: 'qwen3'`.
```bash
pip install "transformers>=4.51.0" torch
```
```python
from transformers import AutoModelForCausalLM, AutoTokenizer
MODEL = "superwhisper/s1-mini"
tok = AutoTokenizer.from_pretrained(MODEL)
model = AutoModelForCausalLM.from_pretrained(MODEL, torch_dtype="auto")
# Required. Use this exact system prompt.
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."
)
def normalize(transcript, styling="semi-formal", structure="prose", context="general"):
control = f"[Styling: {styling}] [Structure: {structure}] [Context: {context}]"
messages = [
{"role": "system", "content": SYSTEM},
{"role": "user", "content": f"{control}\n{transcript}"},
]
text = tok.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
enable_thinking=False, # required, see below
)
inputs = tok(text, return_tensors="pt").to(model.device)
out = model.generate(**inputs, max_new_tokens=1024, do_sample=False)
return tok.decode(out[0][inputs.input_ids.shape[1]:], skip_special_tokens=True)
raw = "so um i need to like send the the report by uh friday no wait make that thursday"
print(normalize(raw))
# I need to send the report by Thursday.
```
`torch_dtype="auto"` picks up the BF16 in `config.json`. At 0.6B this runs
comfortably on CPU; add `device_map="auto"` to place it on a GPU automatically
(requires `accelerate`).
## The control line
Every input starts with 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.
### Styling
The register decides how much of the speaker's voice survives into the
written text.
| Value | Behavior |
|---|---|
| `casual` | Everything lowercase, apostrophes stripped, colloquialisms kept, final period usually omitted. |
| `semi-casual` | Keeps the speaker's phrasing. `I` and its contractions are capitalized, sentence starts stay lowercase, final period usually omitted. |
| `semi-formal` | Standard written English: full capitalization and punctuation, contractions kept, colloquialisms smoothed (`gonna` becomes `going to`). A good default. |
| `formal` | Like `semi-formal`, with contractions expanded (`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. Apostrophes
are decided by the register rather than the raw transcript, so the input can
arrive as `im` or `I'm` and the output comes out the same either way.
### Structure
`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
```
### Context
`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
```
## Examples
All of these were measured on the BF16 weights in this repo with greedy
decoding, under `[Styling: semi-formal] [Structure: prose] [Context: general]`.
| Input | Output |
|---|---|
| `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.` |
| `i think the answer is forty two no sorry forty three` | `I think the answer is 43.` |
| `let's meet at half past two tomorrow uh actually make it three fifteen p m` | `Let's meet at 3:15pm tomorrow.` |
| `the invoice came to twenty three thousand four hundred and fifty dollars and it's due on march third twenty twenty six` | `The invoice came to $23,450, and it's due on March 3, 2026.` |
| `send it to support at superwhisper dot com` | `Send it to support@superwhisper.com.` |
| `um` | *(empty string)* |
## Set `enable_thinking=False`
The chat template comes from Qwen3 unchanged, and Qwen3 turns on thinking mode
by default. S1-mini was trained with thinking off and has no reasoning traces
in its training data.
> [!WARNING]
> If you leave the flag out you will usually get **no usable output at all**:
> the model emits an empty `<think>` block and stops. This is the single most
> common way to get a blank result from this model.
The flag makes the template emit an empty think block before the assistant
turn. That is the exact prefix the model saw during training. The template
ships as `chat_template.jinja` in this repo, so `apply_chat_template` picks it
up with no extra configuration. If you build prompts by hand instead, the
literal string is:
```
<|im_start|>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.<|im_end|>
<|im_start|>user
[Styling: semi-formal] [Structure: prose] [Context: general]
<raw 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.
## Deployment
**vLLM.** Serve normally, then disable thinking per request:
```bash
vllm serve superwhisper/s1-mini
```
```bash
curl http://localhost:8000/v1/chat/completions -H "Content-Type: application/json" -d '{
"model": "superwhisper/s1-mini",
"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 send the report by uh friday"}
],
"temperature": 0,
"chat_template_kwargs": {"enable_thinking": false}
}'
```
**SGLang.** The same request shape works here, since `chat_template_kwargs`
is supported on the OpenAI-compatible endpoint.
```bash
python -m sglang.launch_server --model-path superwhisper/s1-mini
```
**llama.cpp.** Use the [GGUF
builds](https://huggingface.co/superwhisper/s1-mini-GGUF). The template must be
applied with thinking disabled, which means passing `--jinja` together with
`--chat-template-kwargs`. Don't substitute `--reasoning-budget 0`, which
suppresses the think block a different way and degrades the output:
```bash
llama-server -hf superwhisper/s1-mini-GGUF:Q4_K_M --jinja --chat-template-kwargs '{"enable_thinking":false}' --temp 0
```
**Ollama and LM Studio.** Both work from the GGUF builds, but both inherit
Qwen3's thinking-on-by-default template. Make sure the assistant turn begins
with the empty think block shown above, or you will get blank output.
## Best practices
1. **Send the system prompt and the control line, exactly as documented.**
They are the only steering mechanism, and the model was never trained
without them.
2. **Decode greedily.** `generation_config.json` already ships `do_sample:
false`, and for good reason: normalization is a deterministic
transformation, and sampling only adds variance. If you override the
config, use temperature 0.
3. **Size `max_new_tokens` to the input.** Output length closely tracks input
length; `1.3 × input_tokens + 32` is a safe ceiling, and much cheaper than
leaving it at 1024.
4. **Chunk long transcripts at sentence boundaries.** The model is built for
dictation-length input; keep single passes under roughly 1,000 tokens.
5. **Expect an empty string sometimes.** Filler-only input returns nothing,
and your pipeline should treat that as a valid result rather than a
failure.
## Evaluation
Evaluated on a held-out English test set of 7,519 cases covering real ASR
output and synthetic stress sets for numbers, self-corrections, lists, email
and adversarial inputs: 94.8% token accuracy, measured greedy on the Q4_K_M
GGUF build. The BF16 weights here should do at least as well.
## Using S1-mini in your own app
S1-mini is Apache 2.0 plus a naming clause, the same base license it inherits
from Qwen3-0.6B, so it 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.
It is a post-processing stage rather than a standalone system:
```
audio ──▶ ASR (Whisper, Parakeet, …) ──▶ S1-mini ──▶ clean text
```
The ASR's raw transcript becomes the transcript line, your app's settings
choose the three control-line values, and the model returns text ready to
display. At 0.6B it is small enough to ship on-device, and the
[GGUF builds](https://huggingface.co/superwhisper/s1-mini-GGUF) exist for
exactly that.
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](https://huggingface.co/superwhisper/s1-mini/blob/main/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.
## License
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](https://huggingface.co/superwhisper/s1-mini/blob/main/LICENSE) and [NOTICE](https://huggingface.co/superwhisper/s1-mini/blob/main/NOTICE).
## Citation
```bibtex
@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:
```bibtex
@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}
}
```

3
banner.jpg Normal file
View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c17fc850506c928b2213cac6becec3b315fb96c0064b97a25de9be71964c5fbb
size 154019

89
chat_template.jinja Normal file
View File

@@ -0,0 +1,89 @@
{%- if tools %}
{{- '<|im_start|>system\n' }}
{%- if messages[0].role == 'system' %}
{{- messages[0].content + '\n\n' }}
{%- endif %}
{{- "# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>" }}
{%- for tool in tools %}
{{- "\n" }}
{{- tool | tojson }}
{%- endfor %}
{{- "\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{\"name\": <function-name>, \"arguments\": <args-json-object>}\n</tool_call><|im_end|>\n" }}
{%- else %}
{%- if messages[0].role == 'system' %}
{{- '<|im_start|>system\n' + messages[0].content + '<|im_end|>\n' }}
{%- endif %}
{%- endif %}
{%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}
{%- for message in messages[::-1] %}
{%- set index = (messages|length - 1) - loop.index0 %}
{%- if ns.multi_step_tool and message.role == "user" and message.content is string and not(message.content.startswith('<tool_response>') and message.content.endswith('</tool_response>')) %}
{%- set ns.multi_step_tool = false %}
{%- set ns.last_query_index = index %}
{%- endif %}
{%- endfor %}
{%- for message in messages %}
{%- if message.content is string %}
{%- set content = message.content %}
{%- else %}
{%- set content = '' %}
{%- endif %}
{%- if (message.role == "user") or (message.role == "system" and not loop.first) %}
{{- '<|im_start|>' + message.role + '\n' + content + '<|im_end|>' + '\n' }}
{%- elif message.role == "assistant" %}
{%- set reasoning_content = '' %}
{%- if message.reasoning_content is string %}
{%- set reasoning_content = message.reasoning_content %}
{%- else %}
{%- if '</think>' in content %}
{%- set reasoning_content = content.split('</think>')[0].rstrip('\n').split('<think>')[-1].lstrip('\n') %}
{%- set content = content.split('</think>')[-1].lstrip('\n') %}
{%- endif %}
{%- endif %}
{%- if loop.index0 > ns.last_query_index %}
{%- if loop.last or (not loop.last and reasoning_content) %}
{{- '<|im_start|>' + message.role + '\n<think>\n' + reasoning_content.strip('\n') + '\n</think>\n\n' + content.lstrip('\n') }}
{%- else %}
{{- '<|im_start|>' + message.role + '\n' + content }}
{%- endif %}
{%- else %}
{{- '<|im_start|>' + message.role + '\n' + content }}
{%- endif %}
{%- if message.tool_calls %}
{%- for tool_call in message.tool_calls %}
{%- if (loop.first and content) or (not loop.first) %}
{{- '\n' }}
{%- endif %}
{%- if tool_call.function %}
{%- set tool_call = tool_call.function %}
{%- endif %}
{{- '<tool_call>\n{"name": "' }}
{{- tool_call.name }}
{{- '", "arguments": ' }}
{%- if tool_call.arguments is string %}
{{- tool_call.arguments }}
{%- else %}
{{- tool_call.arguments | tojson }}
{%- endif %}
{{- '}\n</tool_call>' }}
{%- endfor %}
{%- endif %}
{{- '<|im_end|>\n' }}
{%- elif message.role == "tool" %}
{%- if loop.first or (messages[loop.index0 - 1].role != "tool") %}
{{- '<|im_start|>user' }}
{%- endif %}
{{- '\n<tool_response>\n' }}
{{- content }}
{{- '\n</tool_response>' }}
{%- if loop.last or (messages[loop.index0 + 1].role != "tool") %}
{{- '<|im_end|>\n' }}
{%- endif %}
{%- endif %}
{%- endfor %}
{%- if add_generation_prompt %}
{{- '<|im_start|>assistant\n' }}
{%- if enable_thinking is defined and enable_thinking is false %}
{{- '<think>\n\n</think>\n\n' }}
{%- endif %}
{%- endif %}

30
config.json Normal file
View File

@@ -0,0 +1,30 @@
{
"architectures": [
"Qwen3ForCausalLM"
],
"attention_bias": false,
"attention_dropout": 0.0,
"bos_token_id": 151643,
"eos_token_id": 151645,
"head_dim": 128,
"hidden_act": "silu",
"hidden_size": 1024,
"initializer_range": 0.02,
"intermediate_size": 3072,
"max_position_embeddings": 40960,
"max_window_layers": 28,
"model_type": "qwen3",
"num_attention_heads": 16,
"num_hidden_layers": 28,
"num_key_value_heads": 8,
"rms_norm_eps": 1e-06,
"rope_scaling": null,
"rope_theta": 1000000,
"sliding_window": null,
"tie_word_embeddings": true,
"torch_dtype": "bfloat16",
"transformers_version": "4.51.0",
"use_cache": true,
"use_sliding_window": false,
"vocab_size": 151936
}

10
generation_config.json Normal file
View File

@@ -0,0 +1,10 @@
{
"bos_token_id": 151643,
"do_sample": false,
"eos_token_id": [
151645,
151643
],
"pad_token_id": 151643,
"transformers_version": "4.51.0"
}

151388
merges.txt Normal file

File diff suppressed because it is too large Load Diff

3
model.safetensors Normal file
View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:69d2057077ab4dc738aaaab75d2a8ffa141e3a09fb9d956198cfce46f381131a
size 1503300328

BIN
tokenizer.json (Stored with Git LFS) Normal file

Binary file not shown.

239
tokenizer_config.json Normal file
View File

@@ -0,0 +1,239 @@
{
"add_bos_token": false,
"add_prefix_space": false,
"added_tokens_decoder": {
"151643": {
"content": "<|endoftext|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": true
},
"151644": {
"content": "<|im_start|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": true
},
"151645": {
"content": "<|im_end|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": true
},
"151646": {
"content": "<|object_ref_start|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": true
},
"151647": {
"content": "<|object_ref_end|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": true
},
"151648": {
"content": "<|box_start|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": true
},
"151649": {
"content": "<|box_end|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": true
},
"151650": {
"content": "<|quad_start|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": true
},
"151651": {
"content": "<|quad_end|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": true
},
"151652": {
"content": "<|vision_start|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": true
},
"151653": {
"content": "<|vision_end|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": true
},
"151654": {
"content": "<|vision_pad|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": true
},
"151655": {
"content": "<|image_pad|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": true
},
"151656": {
"content": "<|video_pad|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": true
},
"151657": {
"content": "<tool_call>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": false
},
"151658": {
"content": "</tool_call>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": false
},
"151659": {
"content": "<|fim_prefix|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": false
},
"151660": {
"content": "<|fim_middle|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": false
},
"151661": {
"content": "<|fim_suffix|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": false
},
"151662": {
"content": "<|fim_pad|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": false
},
"151663": {
"content": "<|repo_name|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": false
},
"151664": {
"content": "<|file_sep|>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": false
},
"151665": {
"content": "<tool_response>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": false
},
"151666": {
"content": "</tool_response>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": false
},
"151667": {
"content": "<think>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": false
},
"151668": {
"content": "</think>",
"lstrip": false,
"normalized": false,
"rstrip": false,
"single_word": false,
"special": false
}
},
"additional_special_tokens": [
"<|im_start|>",
"<|im_end|>",
"<|object_ref_start|>",
"<|object_ref_end|>",
"<|box_start|>",
"<|box_end|>",
"<|quad_start|>",
"<|quad_end|>",
"<|vision_start|>",
"<|vision_end|>",
"<|vision_pad|>",
"<|image_pad|>",
"<|video_pad|>"
],
"bos_token": null,
"chat_template": "{%- if tools %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0].role == 'system' %}\n {{- messages[0].content + '\\n\\n' }}\n {%- endif %}\n {{- \"# Tools\\n\\nYou may call one or more functions to assist with the user query.\\n\\nYou are provided with function signatures within <tools></tools> XML tags:\\n<tools>\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n</tools>\\n\\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\\n<tool_call>\\n{\\\"name\\\": <function-name>, \\\"arguments\\\": <args-json-object>}\\n</tool_call><|im_end|>\\n\" }}\n{%- else %}\n {%- if messages[0].role == 'system' %}\n {{- '<|im_start|>system\\n' + messages[0].content + '<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}\n{%- for message in messages[::-1] %}\n {%- set index = (messages|length - 1) - loop.index0 %}\n {%- if ns.multi_step_tool and message.role == \"user\" and message.content is string and not(message.content.startswith('<tool_response>') and message.content.endswith('</tool_response>')) %}\n {%- set ns.multi_step_tool = false %}\n {%- set ns.last_query_index = index %}\n {%- endif %}\n{%- endfor %}\n{%- for message in messages %}\n {%- if message.content is string %}\n {%- set content = message.content %}\n {%- else %}\n {%- set content = '' %}\n {%- endif %}\n {%- if (message.role == \"user\") or (message.role == \"system\" and not loop.first) %}\n {{- '<|im_start|>' + message.role + '\\n' + content + '<|im_end|>' + '\\n' }}\n {%- elif message.role == \"assistant\" %}\n {%- set reasoning_content = '' %}\n {%- if message.reasoning_content is string %}\n {%- set reasoning_content = message.reasoning_content %}\n {%- else %}\n {%- if '</think>' in content %}\n {%- set reasoning_content = content.split('</think>')[0].rstrip('\\n').split('<think>')[-1].lstrip('\\n') %}\n {%- set content = content.split('</think>')[-1].lstrip('\\n') %}\n {%- endif %}\n {%- endif %}\n {%- if loop.index0 > ns.last_query_index %}\n {%- if loop.last or (not loop.last and reasoning_content) %}\n {{- '<|im_start|>' + message.role + '\\n<think>\\n' + reasoning_content.strip('\\n') + '\\n</think>\\n\\n' + content.lstrip('\\n') }}\n {%- else %}\n {{- '<|im_start|>' + message.role + '\\n' + content }}\n {%- endif %}\n {%- else %}\n {{- '<|im_start|>' + message.role + '\\n' + content }}\n {%- endif %}\n {%- if message.tool_calls %}\n {%- for tool_call in message.tool_calls %}\n {%- if (loop.first and content) or (not loop.first) %}\n {{- '\\n' }}\n {%- endif %}\n {%- if tool_call.function %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {{- '<tool_call>\\n{\"name\": \"' }}\n {{- tool_call.name }}\n {{- '\", \"arguments\": ' }}\n {%- if tool_call.arguments is string %}\n {{- tool_call.arguments }}\n {%- else %}\n {{- tool_call.arguments | tojson }}\n {%- endif %}\n {{- '}\\n</tool_call>' }}\n {%- endfor %}\n {%- endif %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if loop.first or (messages[loop.index0 - 1].role != \"tool\") %}\n {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n<tool_response>\\n' }}\n {{- content }}\n {{- '\\n</tool_response>' }}\n {%- if loop.last or (messages[loop.index0 + 1].role != \"tool\") %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n {%- if enable_thinking is defined and enable_thinking is false %}\n {{- '<think>\\n\\n</think>\\n\\n' }}\n {%- endif %}\n{%- endif %}",
"clean_up_tokenization_spaces": false,
"eos_token": "<|im_end|>",
"errors": "replace",
"model_max_length": 131072,
"pad_token": "<|endoftext|>",
"split_special_tokens": false,
"tokenizer_class": "Qwen2Tokenizer",
"unk_token": null
}

1
vocab.json Normal file

File diff suppressed because one or more lines are too long