SemanticRepair-0.8B

One Qwen3.5-0.8B base with two LoRA adapters, each doing one small job behind an embedding router:

  • repair: when a message does not land clearly on one capability of a chat, it writes every request the message carries on its own line, and names for each line the capability that does its work among the ones the chat offers, or none. A message that asks for nothing comes back as NO_REQUEST.
  • tie-break: when the router and the repair still leave two or more offered capabilities close, it chooses one of them, or none, in which case the person is asked.

Neither adapter answers the question. The router checks every name they give against its own scores, and the host application decides what runs: a model's output alone never authorizes an operation.

The tie-break adapter is a distillation: it learned the choices of a teacher 34 times its size, Qwen3.8-27B, and on 106 tie-breaks it never saw it agrees with that teacher more often than a 35-billion-parameter model does, with 11 times fewer wrong choices.

tie-break: same choice as the teacher, of 106 wrong choices seconds a decision
SemanticRepair-0.8B, tie-break adapter 87 2 0.52
Qwen3.5-4B fine-tuned by Gramscii 84 1 1.1
Qwen3.6-35B-A3B, thinking off (MoE, 3B active) 84 22 1.1
LFM2.5-24B-A2B (MoE, 2B active) 77 28 26.6
Flint (MoE, 40B, 2.5B active) 69 35 13.6

All on the same Apple M4 Pro (64 GB), llama.cpp, one request at a time. A wrong choice is a capability chosen where the teacher chose none or another one; an abstention is not a wrong choice, since the person is then asked.

What is served, revision d53fe43f

file what it is
qwen3.5-0.8b-base-2fc06364-q8_0.gguf the base: Qwen3.5-0.8B at revision 2fc06364, Q8_0, 833,592,320 bytes, sha256 6a94a8273ab1fc5735743967b621eb5a3baba8e36a202f61c20dc47fbb80eb51
semantic-repair-0.8b-2m-lora-f16.gguf the repair adapter, round 2m, F16 LoRA, 21,671,008 bytes, sha256 c8eb2fee74a6b3948f27b303cd6e0e080222928418fc1f463853d475570a9a38
semantic-repair-0.8b-tiebreak-lora-f16.gguf the tie-break adapter, F16 LoRA, 21,671,008 bytes, sha256 11d93fa25377fc9b35b8d36a12080f1d6036d2bdc0849b8690d0a6bb9eaf387f

One base serves both jobs: the two adapters cost 21.7 MB each, not another 800 MB model. Pin the sha256 of all three: a routing decision has to be reproducible.

Also in the repository:

file what it is
semantic-repair-0.8b-2m-q8_0.gguf round 2m's adapter fused into the base, Q8_0, one file: the file the round 2m measures below were taken on
semantic-repair-0.8b-2l-q8_0.gguf round 2l, the previous release (20 October)
semantic-repair-0.8b-2i-q8_0.gguf round 2i, kept for comparison
model.safetensors, config.json, generation_config.json round 2l fused into the base, fp16, for transformers (model_type: qwen3_5_text, the language model alone)
tokenizer.json, tokenizer_config.json, vocab.json, merges.txt the base model's tokenizer
chat_template.jinja the base model's chat template: the tie-break adapter reads it, the repair adapter never does
LICENSE the base model's Apache-2.0 licence

Where it runs

With llama-server, both adapters loaded on the one base, the model loaded only when routing needs it and released after an idle window:

llama-server -m qwen3.5-0.8b-base-2fc06364-q8_0.gguf \
  --lora semantic-repair-0.8b-2m-lora-f16.gguf \
  --lora semantic-repair-0.8b-tiebreak-lora-f16.gguf \
  --jinja --host 127.0.0.1 --port 9250 -ngl 99 -c 32768 --no-context-shift --cache-ram 0

Every request names its adapter at full scale and the other at none, so the two jobs never mix:

"lora": [{"id": 0, "scale": 1.0}, {"id": 1, "scale": 0.0}]

for a repair, and [{"id": 0, "scale": 0.0}, {"id": 1, "scale": 1.0}] for a tie-break. --cache-ram 0 keeps llama-server from holding up to 8 GiB of earlier prompts in memory: every call asks about a message of its own.

The repair adapter

The surface it reads, which is not a chat prompt

Fine-tuned on a bare completion surface. It has never seen a chat template, a system message or a few-shot example. Speak to it the way it was trained:

Capabilities: {the capability ids the chat offers, sorted, comma separated}
{Language}: {the message, verbatim}
=>

It completes with one line per request, capability | request, or the single token NO_REQUEST. A request no offered capability does is written none | request.

  • The newline after => is part of the surface: a prompt that stops at => is a surface the model never read.
  • {Language} is the English name of the language the message is in: Italian, English, French, German, Spanish.
  • The capabilities line is context the model reads, not a formality. Pass the whole list your chat offers, not a shortlist (see What it does not fix).

This is the whole call, on /v1/completions and never on a chat endpoint:

{
  "prompt": "Capabilities: document-search, locate-place, opendata-statistics, web-search\nItalian: dov'è sassari? mostramelo nella mappa e dammi le ultime notizie in italia\n=>\n",
  "stop": ["\n=>"],
  "temperature": 0.0,
  "repeat_penalty": 1.0,
  "max_tokens": 512,
  "lora": [{"id": 0, "scale": 1.0}, {"id": 1, "scale": 0.0}]
}

Greedy, because the same words must always produce the same lines: a router that reruns on a different answer each time cannot be reasoned about. repeat_penalty 1.0 is neutral, since a restatement legitimately repeats the words of the message. The stop string cuts a runaway that starts echoing another message in its own trained format. 512 tokens is the answer cap every measure below used; the longest answer they needed was 261 tokens.

Real outputs

From round 2l's Q8_0, the previous release (20 October), greedy, with the 42 capabilities of a real Italian chat in the list. Round 2m answers on the same surface; its measures follow.

message lines time
dov'è sassari? mostramelo nella mappa e dammi le ultime notizie in italia locate-place | mostra la posizione di Sassari sulla mappa
web-search | cerca le ultime notizie in Italia
0.27 s
quanti abitanti ha Cagliari e quali bandi sono aperti per il turismo in Sardegna? opendata-statistics | statistiche abitanti Cagliari
bandi-open | bandi aperti turismo Sardegna
0.18 s
show me the open tenders in Milan and the population of Lombardy gare-open | show me the open tenders in Milan
opendata-statistics | show me the population of Lombardy
0.22 s
no aspetta, riassumimi cosa ci siamo detti summarize-conversation | riassumi la conversazione 0.13 s
non cercare sul web, dimmi cosa dice il disciplinare sui requisiti di partecipazione document-search | dimmi cosa dice il disciplinare sui requisiti di partecipazione 0.15 s
fammi una presentazione sui dati che abbiamo trovato presentations | Presentazione sui dati trovati 0.12 s
book me a table for friday none | book me a table for friday 0.11 s
ciao come stai NO_REQUEST 0.08 s

One message asking for two things becomes two lines, each under its own capability. A request behind a "no wait" is kept, and a "don't search the web" is honoured. A request nothing offered can do is named none instead of being forced onto the nearest capability. A greeting asks for nothing.

What it is measured to do

Round 2m against round 2l, on 22 October, both served by the same llama-server build with the same greedy call. Pairs are counted where the two disagree, with an exact two-sided sign test.

round 2m round 2l
481 real Italian chat questions, held out from every training and selection step, routed through the full path: exact (every capability asked and no other) 165 164
safe: a proper subset reached, nothing wrong run 7 7
wrong executions 9 11
abstained 300 299
questions only one of the two gets right 13 16 (p = 0.71)
held-out test split, 963 rows, the whole answer equal to the target 308 303 (p = 0.56)
probes written before training: requests placed right, of 80 57 59 (p = 0.69)
non-requests answered NO_REQUEST, of 20 20 20
capability named right, held-out lines 1,417 of 1,442 (98.3%) 1,428 of 1,457 (98.0%)
the right capability taken out of the list (464 rows): names the removed one anyway 1 2
17 long briefs: capabilities reached, of the 113 asked 46 40

No difference between the two rounds is significant. Round 2m is the one served because it carries the chat messages of 19 to 22 October and runs as an adapter beside the tie-break, on the same base. Against SemanticRepair-270M, measured with round 2l on 20 October: 167 against 127 real questions placed exactly, 10 wrong executions each.

What it does not fix, which a card naming only the gains would hide

A list without the capability a request needs can bend its words. With 13 capabilities and none for statistics, «abitanti dei comuni della Sardegna negli ultimi 10 anni» comes back as none | statistiche sull'abito dei comuni della Sardegna…: the name is right, the words are not. With the chat's full list of 42 it reads opendata-statistics | statistiche sugli abitanti dei comuni della Sardegna…. It reads the list as context, so pass the whole list the chat offers.

Long briefs lose steps and names. On 17 briefs of 127 to 771 tokens it reaches 46 of the 113 capabilities asked for.

Wrong executions barely moved. 9 of 481, against 11 for round 2l and 10 for the 270M. The gains are in questions placed exactly, not in fewer mistakes.

The restatements are terse. «statistiche abitanti Cagliari» is good input for a router and poor prose. It is not meant to be shown to anyone.

Measured in Italian and English. The base pairs cover five languages, but the capability labels and every measure above are Italian and English.

The tie-break adapter

When it is asked, and how it is read

The router scores the message against every capability the chat offers; the repair restates it when the route is unsure. When two or more capabilities are still close, the tie-break is asked to choose among the ones the router itself offered, or none.

It reads the base model's chat template (llama-server --jinja, thinking off): a system message with the rule, and a user message listing the offered capabilities, each with its title, its description and what it gives (prose, tables, a chart…), the documents the person selected, and the message. Its trained answer is one Markdown item:

- capability: <one id from the list, or none>

Nothing is generated. For each offered capability and for none, the host reads the probability of that whole answer, its end token included, from llama-server's next-token log-probabilities (/completion with n_probs), and normalizes them over the options. The likeliest capability is taken only when its probability is at least 0.9; below that the person is asked to choose. The threshold was chosen on the 104 tie-breaks of 23 October, before the 106 below were read.

What it is measured to do

106 tie-breaks, 53 Italian and 53 English, from conversations whose group shares nothing with the training rows, each answered by the teacher, Qwen3.8-27B:

of 106
same choice as the teacher (a capability, or none) 87
abstained where the teacher chose one: the person is asked 17
wrong choices: a capability where the teacher chose none 2
another capability than the teacher's 0

Read through the host's own call on the served base and adapters, one request at a time on an Apple M4 Pro: 0.52 seconds a decision on average, 0.36 median, 2.6 at most.

On the 104 tie-breaks of 23 October, where its threshold was chosen: 91 the same, 12 abstained, 1 wrong.

The other models of the table at the top answered the same 106 tie-breaks with the same prompt by generating their answer: Qwen3.5-4B fine-tuned on the same kind of data, Qwen3.6-35B-A3B with thinking off, LFM2.5-24B-A2B and Flint, each on llama.cpp on the same machine.

What it does not fix

It learned the teacher's caution too. Where the teacher chose none, it chooses none: when an official-statistics capability competes with funding calls and tenders, the teacher mostly answered none, and so does the adapter, even on a question as plain as «unemployment rate in Italy in 2023 according to Eurostat». The person is asked instead: a slower answer, not a wrong one.

It chooses only among what the router offered. A capability the router did not put in the contest cannot be chosen, however right it would be.

The data they were trained on

Repair, round 2m: 130,748 training rows and 1,396 validation rows.

  • The published Gramscii-IT/semantic-repair-routing pairs, the ones SemanticRepair-270M was trained on, every line now carrying the capability that does its work as two judge models, Qwen3.8-27B and Gemma-4-31B, agreed on it; a row they disagreed on is left out.
  • 57,473 generated rows in Italian and English, written by Qwen3.8-27B (about two thirds) and Gemma-4-31B (about one third), kept only with the capability each was written for; rows for the capabilities that held few lines, and trap and contrast rows for the pairs earlier rounds confused, kept when both judges found nothing the line leaves out.
  • 1,500 long briefs, each step of their plans a line.
  • 6,719 chat messages from scripted conversations played through the application, whose capability the run that answered them confirmed and both judges confirmed reading the message alone, each with the line Qwen3.8-27B wrote for that capability and both judges confirmed.

Each row's prompt lists the capabilities its lines name plus a random draw of the others, because real chats offer lists as varied as that. For some rows the right capability is taken out of the list and the answer becomes none. One generated row in a hundred goes to validation, and no row equals a question of the benches above.

Tie-break: 23,992 training rows and 104 validation rows, from 18,598 scripted conversations played through the application, every answer written by the teacher, Qwen3.8-27B; the 106 tie-breaks of the table and the 104 validation rows were taken out by conversation group before training.

Training

repair, round 2m tie-break
base Qwen/Qwen3.5-0.8B at 2fc06364, its language model only the same
method LoRA, rank 16, alpha 32, dropout 0.05, on every linear layer but the embeddings and the head (186 modules, 10.8M parameters) the same
schedule one pass: 16,344 updates of 8 examples, learning rate 1×10⁻⁴, 100 warm-up steps one pass: 2,999 updates of 8 examples, the same rate
tokens 26.8M in the examples, 3.6M of them in the answers 27.4M in the examples, 160K in the answers
loss on the answer only on the answer only
hardware one NVIDIA A100 64 GB of the Leonardo supercomputer in Bologna, thanks to CINECA: 2 hours 51 minutes the same, 59 minutes
release F16 LoRA GGUF; also fused into the base and quantized to Q8_0 F16 LoRA GGUF

Both adapters start from the original base; neither continues an earlier round.

Licence

Apache-2.0, as its base model Qwen3.5-0.8B. What it writes is yours, and so is the responsibility for what you let it decide.

Downloads last month
471
Safetensors
Model size
0.8B params
Tensor type
F16
·
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for Gramscii-IT/SemanticRepair-0.8B

Finetuned
(587)
this model

Dataset used to train Gramscii-IT/SemanticRepair-0.8B

Evaluation results

  • Exact capability set (%) on 481 real Italian chat questions, held out from training
    test set self-reported
    34.300
  • Wrong executions (%), lower is better on 481 real Italian chat questions, held out from training
    test set self-reported
    1.900
  • Whole answer equal to the target (%) on Held-out test split (963 rows)
    test set self-reported
    32.000
  • Requests placed on the right capability (%) on 80 requests and 20 non-requests
    test set self-reported
    71.250
  • Non-requests answered NO_REQUEST (%) on 80 requests and 20 non-requests
    test set self-reported
    100.000
  • Lines whose capability is named right (%) on Held-out labelled lines (1,442 lines)
    test set self-reported
    98.300
  • Same choice as the teacher, Qwen3.8-27B (%) on 106 held-out tie-breaks (53 Italian, 53 English), no group shared with training
    test set self-reported
    82.100
  • Wrong choices (%), lower is better on 106 held-out tie-breaks (53 Italian, 53 English), no group shared with training
    test set self-reported
    1.900