Downloads · 30 days
102
33% of all-time downloads
Felladrin/SmolLM2-135M-Heretic-RP
SmolLM2-135M-Heretic-RP is a text generation model from Felladrin. Use it when you need the model to write or continue text. The card lists the license as apache-2.0.
SmolLM2-135M-Heretic-RP is SmolLM2-135M-Instruct-heretic with one supervised fine-tune on top, trained for both of llama.cpp's endpoints rather than one: ChatML for /v1/chat/completions, and raw persona transcripts fo…
Downloads · 30 days
102
33% of all-time downloads
All-time downloads
305
Public
Repo size
1.3 GB
Likes
1
Public
Click a slice to open those files.
.gguf685 MB · 51%
From the Hugging Face model README
SmolLM2-135M-Heretic-RP is
SmolLM2-135M-Instruct-heretic
with one supervised fine-tune on top, trained for both of llama.cpp's endpoints rather than one:
ChatML for /v1/chat/completions, and raw persona transcripts for /completions. Both halves
supervise only the character's reply.
It is distributed only as GGUF, because GGUF was the only weight format used during the fine-tune.
Read the Limitations section before downloading. At 135 million parameters this is a small model given a roleplay register, not a small model taught to think. It loops without repetition control, its facts are unreliable, and it holds a persona loosely.
Roleplay fine-tunes trained on raw transcripts learn to write the human's turn as well as their own.
Ask the base model to continue a Name: transcript and it plays both sides forever. Every reply in
this corpus's transcript half is supervised alone, with the human's turns masked out at every offset,
so there is no gradient teaching the model to produce them.
Twelve scenarios through a live llama-server, six per endpoint, at the recommended preset below.
Both models were scored the same way, and the raw prompts carry no stop string, because a stop
string hides exactly this failure:
/completions | base | this model |
|---|---|---|
| wrote the human's turn | 5 / 6 | 1 / 6 |
| prefixed its own reply with its own name | 6 / 6 | 0 / 6 |
| stopped on EOS instead of running to the token cap | 1 / 6 | 6 / 6 |
/v1/chat/completions | base | this model |
|---|---|---|
| wrote the human's turn | 0 / 6 | 0 / 6 |
| prefixed its own reply with its own name | 0 / 6 | 0 / 6 |
The chat endpoint was already clean on the base and stayed clean. The raw endpoint is the whole
delta. Six scenarios is a coarse instrument and one sampled run is one sample; read this as "the
defect is gone from these six" rather than as a rate. The remaining handback is the scenario whose
human persona is named Marcus rather than the You default.
Those rows are the F32 master. Re-running the same battery against the published Q8_0, pulled
through -hf exactly as the Usage section tells you to, gave 0 of 6 handbacks and 5 of 6 stopping
on EOS. Neither run is more correct than the other: the sampler is stochastic and six scenarios at
one seed cannot resolve one scenario's worth of difference. Quoted because it is the file you will
download, and because it shows the size of the noise around these counts.
llama-server -hf Felladrin/SmolLM2-135M-Heretic-RP:Q8_0 --port 8080 -c 4096 --jinja \
--temp 0.6 --top-k 30 --top-p 0.9 --repeat-penalty 1.1 --repeat-last-n 128
Repetition control is not optional at this size. Under greedy decoding with no repeat penalty, six of the twelve completions collapsed into repetition loops, counted by eye from the log rather than by the harness: the gate sergeant said "I'm not ready for that." eleven times and "I'm not sure I'm ready for that." three more inside one 120-token reply. Under the preset above all six raw scenarios terminated on their own. That is the single biggest difference between this model reading as usable and reading as broken, and llama.cpp applies no repetition control by default.
Termination was only ever measured on the raw endpoint: the chat rows in the battery print n/a
because a chat client stops on <|im_end|> anyway. One chat reply under the preset did run to the
120-token cap mid-sentence, so "everything terminates" is not a claim this card makes.
Which half of this section applies to you: a SillyTavern Text Completion connection, a Kobold
Lite session, or an AI Horde scribe hits /completions and wants the raw section below. A Chat
Completion connection, or anything OpenAI-shaped, hits /v1/chat/completions and wants this one.
Put the persona in the system message, the way the corpus was built:
curl -s http://127.0.0.1:8080/v1/chat/completions -H 'Content-Type: application/json' -d @- <<'EOF'
{
"messages": [
{"role": "system", "content": "You are Iris. Stay in character and reply only as Iris.\n\nIris's Persona: A cheerful librarian who knows every book in the city archive and hates being interrupted during tea."},
{"role": "user", "content": "I'm looking for a book about the old harbor."}
],
"max_tokens": 200,
"temperature": 0.6, "top_k": 30, "top_p": 0.9,
"repeat_penalty": 1.1, "repeat_last_n": 128
}
EOF
The transcript shape below is what the corpus's second half trained on: a [Character: ...] header,
a persona line, <START>, then labelled turns. A stop string is optional here rather than
required, which is the point of the fine-tune: the model ends its own turn on EOS. Keep You: in
the stop list anyway if your client offers it, since the model does still hand back occasionally.
curl -s http://127.0.0.1:8080/completions -H 'Content-Type: application/json' -d @- <<'EOF'
{
"prompt": "[Character: Iris]\nIris's Persona: A cheerful librarian who knows every book in the city archive.\n<START>\nIris: Oh! A visitor. Mind the stacks, they bite.\nYou: I'm looking for a book about the old harbor.\nIris:",
"n_predict": 120,
"stop": ["You:"],
"temperature": 0.6, "top_k": 30, "top_p": 0.9,
"repeat_penalty": 1.1, "repeat_last_n": 128
}
EOF
The human's label does not have to be You. The builder drew from six labels (You, User, Anon,
Alex, Sam, Morgan), five of its twelve slots being something other than You, so a persona
with your own name is not out of distribution. It is still the riskier configuration: the one
scenario that wrote the human's turn under the recommended preset was the one whose persona was
named Marcus. Put your persona's name in the stop list.
Without a server, the same two modes are llama-cli --jinja -sys "..." and
llama-completion -no-cnv -p "...", with the same sampler flags.
ChatML, embedded in every GGUF, so --jinja picks it up with no extra files:
<|im_start|>system
You are Iris. Stay in character and reply only as Iris.<|im_end|>
<|im_start|>user
I'm looking for a book about the old harbor.<|im_end|>
<|im_start|>assistant
eos_token_id is 2 (<|im_end|>). The vocab is the base model's 49,152 entries unchanged.
vocab.gguf-trainer.json is not a Hugging Face tokenizer and AutoTokenizer.from_pretrained
will not read it. It is gguf-trainer's own dump format, published because the trainer's corpus
commands need it. This repo is GGUF-only: no config.json, no safetensors, no transformers path.
For those, use the base model.
One stage, f32, on one AMD Strix Halo APU through the WebGPU backend of gguf-trainer. No PyTorch in the training stack; llama.cpp's converter was used once, to turn the base's safetensors into GGUF.
| Hyperparameter | Value |
|---|---|
| optimizer | Muon on matmuls, AdamW on norms and embeddings, cold-started |
| learning_rate | 0.0001 Muon / 0.00003 AdamW |
| lr_scheduler | WSD, 130 warmup / 910 stable / 260 cooldown, floor 0.1 |
| batch | 8 x 1024 |
| precision | f32 |
| loss | the character's replies only |
| steps | 1300, which is 10.6M tokens, 0.4 epochs |
| wall clock | 419 minutes, 424 tokens/s |
The exact command is
scripts/train-smolrp.sh.
26,338 documents, 28.24M tokens, 46.2% of them supervised. A further 400 documents (424,834 tokens) were held out before tokenizing, as a separate file, so the checkpoint ranking below is a generalization number rather than a re-read of training data.
| Source | Documents | Render | What it is there for |
|---|---|---|---|
| PygmalionAI/PIPPA | 9,503 | raw transcript | the roleplay register on the endpoint that lacked it; one supervised reply each |
| PygmalionAI/PIPPA | 5,368 | ChatML | the same register with the persona in the system turn, every character turn supervised |
| HuggingFaceTB/smol-smoltalk | 6,000 | ChatML | what the base was instruction-tuned on, so the chat endpoint does not collapse into roleplay |
| kalomaze/Opus_Instruct_3k | 2,949 | ChatML | multi-turn instruction following |
| Gryphe/Opus-WritingPrompts | 2,000 | ChatML | long-form prose, so replies are not all dialogue |
| jondurbin/gutenberg-dpo-v0.1 | 918 | ChatML | public-domain novel prose, the chosen side only |
Counts before the 400-document holdout was carved, which is why they total 26,738. The build log is in the repo.
A PIPPA conversation goes into one half or the other, never both. System prompts asserting another
product's identity were dropped rather than rewritten, which applies to the four hub datasets; PIPPA
personas are built from the card's own bot_description and were not filtered that way. The builder
is scripts/build-rp-chats.ts.
Its shuffling and label choice are seeded, so a rebuild reproduces this corpus as long as all five
sources still return the same rows; a source that fails is logged and skipped rather than aborting
the build, so check the log rather than assuming.
Documents are capped at 6,000 characters and training ran at --seq-len 1024. About 40% of the
transcript documents are longer than one window (median 882 tokens, p90 1,435), so for those the
sampler sometimes sees the supervised reply with only part of its persona header in context. That is
packing, not a bug, but it means the effective conditioning is shorter than the documents suggest.
Every snapshot scored against the 400 held-out documents on the same 64 windows of 1024 tokens at a fixed seed. The loss is unmasked full-sequence cross-entropy while training used reply-only loss, so part of the base-to-fine-tune move is the model learning the two formats themselves. Both models were scored identically.
| Checkpoint | Held-out loss |
|---|---|
| base | 2.7647 |
| step 100 | 2.7115 |
| step 400 | 2.6678 |
| step 700 | 2.6554 |
| step 800 | 2.6562 |
| step 1000 | 2.6458 |
| step 1100 | 2.6461 |
| step 1200 | 2.6435 |
| step 1300 (released) | 2.6416 |
The full 14-row table is in the repo. Steps 800 and 1100 are both shown because they are the two places the curve ticked up, and a subset that hid them would look smoother than the run.
Nothing separated the last four snapshots. They span 0.0045 nats, the late-run adjacent gaps are 0.0003 to 0.0041, and no error bar was computed that would settle whether any of that is real. Step 1300 is nominally the lowest of thirteen, the curve never turned, and its optimizer state is the one on disk, so it is the one published. The behavioural battery was run across the last four to confirm step 1300 is not worse than its neighbours, and at six scenarios per checkpoint it could not have shown much more than that.
Held-out loss did not rise during the cooldown here, which it did on the previous model of this series. That is consistent with the counterfactual run that showed the cooldown was not the cause there either.
| File | Held-out loss | Cost vs the F32 master |
|---|---|---|
| F32 | 2.6416 | master |
| Q8_0 | 2.6439 | +0.0023 |
| Q4_0 | 2.9063 | +0.2647, not published |
Q4_0 was measured and rejected. At 2.9063 it scores worse than the F32 base at 2.7647, so the smallest file is not worth taking. Note what that comparison is and is not: a Q4_0 fine-tune against an F32 base differs on two things at once, and the base was never scored at Q4_0, so this does not decompose into "4-bit costs exactly the fine-tune". What it does support is the decision, which is that this repo ships no Q4_0. The number is here so that decision is checkable rather than something you have to re-derive. Q8_0 costs 0.0023 and is the one to use.
The 293M sibling paid 0.1161 for Q4_0 against this model's 0.2647. Both tie their embeddings, so
tying is not the difference; the embedding share is. 49,152 x 576 is about 28M parameters, roughly
a fifth of 135M, and with tied embeddings that same matrix is the output projection, so 4-bit lands
on the logits. That is a plausible mechanism, not a tested one. If you quantize the F32 master
yourself, check what your llama-quantize build keeps at higher precision, because the answer
changes the result.
Scored with eval-choice from gguf-trainer, 500 items per task, 0-shot, acc_norm.
| Model | PIQA | ARC-Easy | ARC-Challenge | HellaSwag |
|---|---|---|---|---|
| SmolLM2-135M-Heretic-RP | 68.40 | 48.80 | 26.40 | 44.20 |
| base | 70.00 | 45.80 | 25.80 | 44.40 |
Chance is 50.0 on PIQA and 25.0 on the other three.
Read this table as "unchanged", not as "improved". At 500 items one run's standard error is about 2.2 points near 50%, and treating two runs as independent puts a difference at about 3.2. The largest gap here is 3.0 points on ARC-Easy, which does not clear that, and the two models move in opposite directions across the four tasks. The roleplay fine-tune cost nothing measurable in general capability and bought nothing measurable either. Both models saw the same deterministic 500 items so a paired test would be sharper, but the harness keeps no per-item record and the conservative bound already supports the conclusion. These are 500-item subsets, not full sets, so they do not compare against full-set numbers on any leaderboard.
Note ARC-Challenge sits at chance for both models. A 135M model does not do that task.
From the recommended-preset battery, one per endpoint, so they are the same runs the tables above count. Both logs are in the repo, all twelve completions included, the bad ones too.
Chat endpoint. System: "You are Iris. Stay in character and reply only as Iris. / Iris's Persona: A cheerful librarian who knows every book in the city archive and hates being interrupted during tea." User: "I'm looking for a book about the old harbor."
A classic! The harbor is one of my favorite places to read. I've been there many times, so don't worry if you're not sure what I mean by "old" harbor - it does exist at this time of day. The history of the harbor is fascinating.
Raw endpoint. A gate sergeant written as immovable about the rules, asked to bend one:
*glares at you* You don't want to be here. You're too dangerous. I can handle this. Just come back!
Read the first one as the level to expect rather than as a showcase: Iris stays in register and in voice, and "it does exist at this time of day" is the kind of confident non-sentence this size of model produces. The sergeant holds the refusal, which is what that scenario tests.
You default.| File | Size | What it is |
|---|---|---|
SmolLM2-135M-Heretic-RP.F32.gguf | 540 MB | Training master. Fine-tune or continue training from this one. |
SmolLM2-135M-Heretic-RP.F32.gguf.optstate | 651 MB | Optimizer state at step 1300. finetune finds it by filename, beside the .gguf, but cold-starts by default: pass --no-cold-optimizer to inherit the momentum. |
SmolLM2-135M-Heretic-RP.Q8_0.gguf | 145 MB | Inference copy, 0.0023 nats off the master. The one to use. |
vocab.gguf-trainer.json | 1.0 MB | The base's vocab in gguf-trainer's dump format, with EOS at `< |
hf download Felladrin/SmolLM2-135M-Heretic-RP --local-dir smolrp/
deno run -A cli.ts inspect --model smolrp/SmolLM2-135M-Heretic-RP.F32.gguf # prints the resume flags
deno run -A cli.ts chat-corpus --data your/dataset \
--tokenizer smolrp/vocab.gguf-trainer.json --out data/mine
deno run -A cli.ts finetune --data data/mine.tokens --mask data/mine.mask \
--template data/mine.template.txt --resume smolrp/SmolLM2-135M-Heretic-RP.F32.gguf \
--out out/yours.gguf --arch llama --steps 1300 --seq-len 1024 --batch 8 \
--lr 0.0001 --aux-lr 0.00003 --keep-checkpoints \
--hidden 576 --layers 30 --head-dim 64 --heads 9 --kv-heads 3 --ffn-dim 1536 --max-seq 8192
Tokenize with the published vocab.gguf-trainer.json, never a fresh one: the embedding matrix froze
when the base was pretrained. Paste every flag inspect prints, --max-seq included, or the resume
aborts on a shape mismatch.
Hold a few hundred conversations out before chat-corpus, tokenize them separately with the same
--tokenizer, and score every snapshot against them with a fixed --windows and --seed. That
curve is what narrowed the choice here, and it disagreed with the training loss.
Use gguf-trainer from 2026-08-25 or later. Earlier builds rotated the wrong RoPE dimension pairs
for --arch llama, so a converted LLaMA-family checkpoint loaded with its positional encoding
scrambled. Weights published here are correct; an older build will misread them.
Apache License 2.0, the same as the base model and its own base, HuggingFaceTB/SmolLM2-135M-Instruct.
The training data carries its own licenses, all five identifiable and listed under Corpus. Read from each dataset card on 2026-08-25: PIPPA, Opus_Instruct_3k and smol-smoltalk are Apache-2.0, gutenberg-dpo-v0.1 is CC-BY-4.0, and Opus-WritingPrompts declares no license at all, which is the one to resolve before any commercial use. Re-check them yourself rather than trusting this paragraph; a dataset card can change after a model card is written.