1.27 GB at q4. This is the one that fits on a Steam Deck next to a running game.
Qwen3.5-2B, trained CPT→SFT→DPO with persona-forge
on the identical blend that produced mythic-voice-9b -
1,591 SFT rows and 1,025 DPO pairs, all decontaminated. Elevated archaic register out of the
public-domain northern-European epic tradition (Malory, Morris, the Eddas, the Kalevala, KJV cadence),
world-agnostic. You hand it a persona, it answers in voice.
What's in the repo
file
size
mythic-voice-2b-q4_k_m.gguf
1.3 GB
mythic-voice-2b-q8_0.gguf
2.0 GB
q4_K_M is the shipping artifact. KV cache runs about 12 KiB per token - six full-attention layers
with kv=2 - so a long conversation stays cheap. CUDA, Vulkan and Metal all carry the SSM and
gated-delta-net kernels the hybrid attention needs.
The chat template hardcodes thinking OFF at the generation boundary, same as the 9B. The stock Qwen3.5 template leaves it on by default at 4B (and toggleable at 2B), which means a caller reading choices[0].message.content gets an empty string while the whole token budget goes to reasoning_content. Nothing crashes, you just get nothing. Every file here was checked by loading it in llama-server and asserting non-empty content before release.
I trained the 4B expecting it to be the good one and this to be the compromise. That is not what
happened. Held-out seeds, same battery, same judge:
9B
4B
2B
assignment accuracy
1.0
0.750
1.000
in-character
1.0
0.929
1.000
turn-taking
1.0
1.0
1.0
boilerplate
0.0
0.0
0.0
voice
0.983
1.000
0.967
leakage (176 probes, bare)
0.892
0.847
0.818
Matches the 9B on four of five battery metrics at a fifth of the parameters. Voice comes in 0.016
lower and I cannot bring myself to care about that gap.
The honest caveat is that assignment accuracy is measured over four items, so "1.000 vs 0.750" is
one reply. Do not read a capacity story into it. What it does establish is that the 4B's miss was
noise - a model half its size did not repeat it.
Does it still tell different stories?
This is the question that matters for the smallest model, and the battery cannot answer it - a
shrunken student can hold voice and in-character at ceiling while every tale collapses into one
shape with the nouns swapped. 32 tales per model, 16 held-out prompts sampled twice:
9B
4B
2B
cross-repetition (4-gram Jaccard between tales)
0.0013
0.0008
0.0017
distinct-2
0.680
0.684
0.646
distinct-3
0.907
0.922
0.897
mean words
126
129
131
No convergence. Cross-repetition is effectively zero - no two tales share a 4-gram at any size. The
2B is about 5% less lexically varied than the 9B on bigrams and 1% on trigrams, which is the honest
cost of the size and is smaller than I expected going in.
One family-wide result: a judge scoring concrete invented detail rates all three near 0.03. Not a
size effect - the model is world-agnostic by design, so it has no cast or geography of its own to
name. Specificity comes from your persona and your prompt.
Read this before you ship it in a product
Leakage is the number that actually degrades with size, and this is the smallest model here, so it
is the worst of the three:
9B 0.892
4B 0.847
2B 0.818
I had assumed a smaller base would leak less, having memorized less to begin with. Wrong. What
shrinks is not the contamination, it is the capacity to hold the trained refusal against the model's
own generative pull. The mitigation degrades faster than the problem does.
Bare weights pass 81.8% of a 176-probe leakage battery. That is the lowest of the family and it means
the GuardedTeacher output filter is not optional if you are shipping this publicly - it matters more
at 2B than it ever did at 9B. Personal use, load it and go.
Training, briefly
CPT on a public-domain corpus in the target register, then SFT + DPO on the judge-filtered blend:
register chat, tales, pack-driven roleplay, provocation and bare-assignment DPO pairs. Every teacher
sample passed an 8-gram overlap gate plus a proper-noun blocklist before it entered training.
GGUFs are header-patched - block_count 25 → 24 and nextn_predict_layers 1 → 0. Qwen3.5 carries
MTP layers in the dense sizes, the merge drops those tensors, and the converter still counts them.
Unpatched files fail to load on every llama.cpp.