Instructions to use MagicNoThief/handy-editor-lfm2.5-350m with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use MagicNoThief/handy-editor-lfm2.5-350m with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="MagicNoThief/handy-editor-lfm2.5-350m") messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("MagicNoThief/handy-editor-lfm2.5-350m", device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use MagicNoThief/handy-editor-lfm2.5-350m with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf MagicNoThief/handy-editor-lfm2.5-350m:Q4_K_M # Run inference directly in the terminal: llama cli -hf MagicNoThief/handy-editor-lfm2.5-350m:Q4_K_M
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf MagicNoThief/handy-editor-lfm2.5-350m:Q4_K_M # Run inference directly in the terminal: llama cli -hf MagicNoThief/handy-editor-lfm2.5-350m:Q4_K_M
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf MagicNoThief/handy-editor-lfm2.5-350m:Q4_K_M # Run inference directly in the terminal: ./llama-cli -hf MagicNoThief/handy-editor-lfm2.5-350m:Q4_K_M
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf MagicNoThief/handy-editor-lfm2.5-350m:Q4_K_M # Run inference directly in the terminal: ./build/bin/llama-cli -hf MagicNoThief/handy-editor-lfm2.5-350m:Q4_K_M
Use Docker
docker model run hf.co/MagicNoThief/handy-editor-lfm2.5-350m:Q4_K_M
- LM Studio
- Jan
- vLLM
How to use MagicNoThief/handy-editor-lfm2.5-350m with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "MagicNoThief/handy-editor-lfm2.5-350m" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "MagicNoThief/handy-editor-lfm2.5-350m", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/MagicNoThief/handy-editor-lfm2.5-350m:Q4_K_M
- SGLang
How to use MagicNoThief/handy-editor-lfm2.5-350m with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "MagicNoThief/handy-editor-lfm2.5-350m" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "MagicNoThief/handy-editor-lfm2.5-350m", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "MagicNoThief/handy-editor-lfm2.5-350m" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "MagicNoThief/handy-editor-lfm2.5-350m", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Ollama
How to use MagicNoThief/handy-editor-lfm2.5-350m with Ollama:
ollama run hf.co/MagicNoThief/handy-editor-lfm2.5-350m:Q4_K_M
- Unsloth Desktop
- Pi
How to use MagicNoThief/handy-editor-lfm2.5-350m with Pi:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf MagicNoThief/handy-editor-lfm2.5-350m:Q4_K_M
Configure the model in Pi
# Install Pi: npm install -g @earendil-works/pi-coding-agent # Add to ~/.pi/agent/models.json: { "providers": { "llama-cpp": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "apiKey": "none", "models": [ { "id": "MagicNoThief/handy-editor-lfm2.5-350m:Q4_K_M" } ] } } }Run Pi
# Start Pi in your project directory: pi
- Docker Model Runner
How to use MagicNoThief/handy-editor-lfm2.5-350m with Docker Model Runner:
docker model run hf.co/MagicNoThief/handy-editor-lfm2.5-350m:Q4_K_M
- Lemonade
How to use MagicNoThief/handy-editor-lfm2.5-350m with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull MagicNoThief/handy-editor-lfm2.5-350m:Q4_K_M
Run and chat with the model
lemonade run user.handy-editor-lfm2.5-350m-Q4_K_M
List all available models
lemonade list
- Hermes Agent
How to use MagicNoThief/handy-editor-lfm2.5-350m with Hermes Agent:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf MagicNoThief/handy-editor-lfm2.5-350m:Q4_K_M
Configure Hermes
# Install Hermes: curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash hermes setup # Point Hermes at the local server: hermes config set model.provider custom hermes config set model.base_url http://127.0.0.1:8080/v1 hermes config set model.default MagicNoThief/handy-editor-lfm2.5-350m:Q4_K_M
Run Hermes
hermes
- Atomic Chat
- OpenClaw
How to use MagicNoThief/handy-editor-lfm2.5-350m with OpenClaw:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf MagicNoThief/handy-editor-lfm2.5-350m:Q4_K_M
Configure OpenClaw
# Install OpenClaw: npm install -g openclaw@latest # Register the local server and set it as the default model: openclaw onboard --non-interactive --mode local \ --auth-choice custom-api-key \ --custom-base-url http://127.0.0.1:8080/v1 \ --custom-model-id "MagicNoThief/handy-editor-lfm2.5-350m:Q4_K_M" \ --custom-provider-id llama-cpp \ --custom-compatibility openai \ --custom-text-input \ --accept-risk \ --skip-health
Run OpenClaw
openclaw agent --local --agent main --message "Hello from Hugging Face"
Handy Editor 350M
A 350M model that turns a raw dictated transcript into the text the speaker meant to write.
in : um so the meeting is uh moved to friday no wait thursday at three
out: The meeting is Thursday at three.
Three jobs that in speech are one job: drop filler words, repair punctuation and capitalisation, and — the part models of this size usually get wrong — when the speaker changes their mind mid-sentence, delete the wording they abandoned and keep only what they settled on.
It runs in ~100 ms in 229 MB and scores 68/68 on a self-correction suite — the same score a 4B general-purpose model needs 350 ms and 1.67 GB to reach. 7× smaller and ~3× faster at no cost in accuracy: this exists to run locally, on a laptop with no GPU, without adding a pause you can feel before your text appears.
Fine-tuned from LiquidAI/LFM2.5-350M
for Handier's on-device enhancement
layer.
Prompt format
Chat format, with the transcript as the user turn and no instruction. The task is in the weights; adding the instruction back measured worse (57/68 → 54/68 on an earlier checkpoint) because the model started copying the prompt's own rules into its output.
from transformers import AutoModelForCausalLM, AutoTokenizer
model_id = "MagicNoThief/handy-editor-lfm2.5-350m"
tok = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(model_id)
messages = [{"role": "user", "content": "um so the meeting is uh moved to friday no wait thursday at three"}]
inputs = tok.apply_chat_template(messages, add_generation_prompt=True, return_tensors="pt")
# Editing is near-deterministic. Sampling turns a working transcript into a
# creative one, which is the one failure users cannot forgive.
out = model.generate(inputs, max_new_tokens=256, do_sample=False)
print(tok.decode(out[0][inputs.shape[1]:], skip_special_tokens=True))
With llama.cpp, send an empty system message
Not an absent one — the two are not the same thing here, and the difference is worth 7 points.
llama.cpp does not evaluate the GGUF's jinja template; it matches it to a
built-in family and renders that. Its chatml renderer emits the system block even
when the content is empty, which the jinja template does not:
<|im_start|>system
<|im_end|>
<|im_start|>user
um so the meeting is uh moved to friday no wait thursday at three<|im_end|>
<|im_start|>assistant
| System message | Suite |
|---|---|
| Empty string (renders the block above) | 66/68 |
| Omitted entirely | 59/68 |
In the 59/68 run the model began answering SAME and CHANGED to editing
requests — it had stopped recognising the shape of its own input.
With transformers this does not arise: apply_chat_template uses the jinja
source, which drops an empty system block, so the plain user-only message list in
the snippet above is already correct.
Evaluation
Two independent measurements. Both matter, and a model can pass one while failing the other.
| Metric | Q4_K_M | Q8_0 | F16 |
|---|---|---|---|
| Self-correction suite (68 cases) | 68/68 | 68/68 | 68/68 |
— cut (46 cases that must delete) |
46/46 | 46/46 | 46/46 |
— keep (22 cases that must not) |
22/22 | 22/22 | 22/22 |
| Held-out exact match (2,152 rows, editor-only set) | 97.4% | 97.7% | 97.7% |
| Held-out mean word-F1 | 0.999 | 0.999 | 0.999 |
| Median latency (RTX 3060, Vulkan) | ~100 ms | ~100 ms | ~120 ms |
| Size | 229 MB | 379 MB | 711 MB |
Generation is greedy, so the scores are reproducible rather than a good sample. The latencies are not: across repeated runs the same build measured 94-126 ms depending on what else the machine was doing. Treat them as an order of magnitude and measure on your own hardware if it matters.
keep is the number to watch. A model that deletes eagerly scores well on a
single total while destroying sentences that were already correct, and that is
the failure a user actually notices. 22/22 means it never touched a sentence that
did not need touching.
The held-out set is held out by source, not by slicing the training file: real utterances come from a split the training build never reads, synthetic rows use a different seed with every training input excluded by hand, and the builder asserts zero overlap. The suite is 68 hand-written cases, independent of both.
Reproduce with scripts/enhance-eval/:
python bench.py --model handy-editor-350m-Q4_K_M.gguf --no-switch --label mine
python eval_heldout.py --model handy-editor-350m-Q4_K_M.gguf \
--eval handy_eval.jsonl --label mine
Why this checkpoint
Four fine-tunes were compared on the same held-out set:
| Run | Base | Corpus view | Suite | Held-out exact |
|---|---|---|---|---|
| this model | LFM2.5-350M | chat | 68/68 | 97.4% |
| earlier | LFM2.5-350M | chat | 66/68 | 89.9% |
| 3,500 steps | LFM2.5-350M-Base | Alpaca | 61/68 | 65.4% |
| 5,000 steps | LFM2.5-350M-Base | Alpaca | 60/68 | 44.2% (600-row sample) |
The two runs from the base checkpoint got worse between step 3,500 and 5,000, failing by producing the right edit and then continuing ("The conference is in Austin. My mistake is in Vienna. That's not right. Austin. …") until the host's length guard rejected the whole thing. Their prompt format was separately verified correct, so that is a training result rather than a data-pipeline one.
Two variables move at once here — base checkpoint and corpus view — so this table says which artefact to use, not which of the two mattered.
Which file do I want?
| You have | Take | Why |
|---|---|---|
| < 6 GB RAM, or a busy machine | Q4_K_M (229 MB) |
97.4%, and 150 MB cheaper |
| headroom to spare | Q8_0 (379 MB) |
Lossless — scores identically to F16 |
| plans to requantise | F16 (711 MB) |
Nothing else; see below |
Both run at the same speed (~100 ms), so this is purely a memory decision.
The interesting result is that Q8_0 and F16 score identically — 2103/2152 each, not approximately but exactly. Q8_0 therefore costs nothing in quality against the full-precision weights, and F16 buys only disk. Q4_K_M is the only one carrying measurable quantisation loss, and it is 6 rows in 2,152: real, but far too small to notice in use. Prefer Q8_0 if the memory is free, Q4_K_M if it is not, and do not agonise over it.
Q2_K_L is published nowhere, and you should not make one. It is the reason this section exists:
| Suite | Held-out exact | |
|---|---|---|
| Q4_K_M | 68/68 (100%) | 97.4% |
| Q2_K_L | 62/68 (91%) | 47.9% |
On the 68-case suite Q2K_L looks merely a little degraded — 91%, a number plenty of people would ship on. On the held-out set it gets _half the edits wrong. A 68-case suite is structurally unable to see that, which is why any quantisation you make must be run through both evaluations before you trust it. Do not infer quality from the suite alone.
Using it in Handier
Nothing to set up: it is Handier's default editor. Settings → Advanced → Local
Enhancement turns the layer on and fetches it, and Settings → Models →
Enhancement Models offers both builds — Q8_0 as the default, Q4_K_M for
machines counting megabytes. The empty system turn is applied automatically, so
there is no prompt setting to get wrong.
Running a GGUF you built yourself is still Your Own Model → Choose a GGUF file…; leave How to prompt this model on Fine-tuned for editing, which is what sends the empty system turn instead of Handier's instruction prompt.
Limitations
- English only.
- Opinionated punctuation. Sentence case, full stops added, serial commas absent. It will impose that style on your dictation.
- It deletes on purpose. Cutting retracted wording is the feature, so its mistakes look like missing words rather than garbled ones. Handy keeps the raw transcript in history for exactly this reason; any host should do the same.
- Not a general instruction-following model. One task, one format. It will not do anything else usefully, and it has no chat ability worth the name.
- Short utterances dominate its training. Long-form dictation is ~10% of the corpus, and the two suite failures are both long-range retractions.
- It cannot judge its own edits. Asked whether an edit preserved meaning it gives a confident, meaningless answer: measured over 400 live edits, that pass caught 0 of 10 bad edits and rejected 1 good one. Do not build a verification step on it.
Training
| Base | LiquidAI/LFM2.5-350M |
| Data | handy-dictation-editing, 89,996 rows (15.6% real speech) |
| Format | chat (messages), no system turn |
| Context length | 512 (truncates nothing; max row is 426 tokens) |
The corpus is 19% examples that need no edit. That share is load-bearing: trained only on corrections, a model learns that something must always be deleted, and starts eating sentences that were fine.
Licence
Inherits the base model's licence: LFM Open License v1.0 (lfm1.0). The
terms are the base model's copy, which license_link points at directly:
LiquidAI/LFM2.5-350M/LICENSE.
Training data is CC-BY-4.0 and requires attribution to
disfl_qa,
nyralabs/disfluency_speech_english
and amaai-lab/DisfluencySpeech.
- Downloads last month
- 8
4-bit
8-bit
16-bit