LINNAEUS
Linnaeus decisio
A 2-billion-parameter model that decides rather than generates. Give it a state and candidate answers — it returns calibrated probabilities over them. No prose, no drift, small enough for a phone.
move the cursor — the specimen parts, then returns
80.78%
held-out macro accuracy
26 groups · 180k examples · upstream 78.21%
73.16%
JevBench v1.2.2
231 public tasks · top of the ~2B class
70.56%
on-device (MLX 8-bit)
same tasks, fully local · +image via VLM
33MB
adapter size
rank-8 LoRA + scalar decision head
The measurements
| group | linnaeus | upstream |
|---|---|---|
| scienceqa | 92.7% | 84.6% |
| esci_us | 57.6% | 51.0% |
| sharc | 73.2% | 66.8% |
| clevr_count | 89.7% | 83.5% |
| aokvqa | 83.7% | 78.1% |
| vqav2_yesno | 85.9% | 80.8% |
| xnli_zh | 78.2% | 75.6% |
| screenqa_choice † | 22.1% | 22.4% |
| macro — all 26 groups | 80.78% | 78.21% |
20 wins, 6 ties, 0 losses across all 26 groups vs upstream 0.8B — shown: the largest gains plus the one cell where we trail. † screenqa_choice is a task-format artifact upstream shares (both at ~22%), not a regression.
Recorded run: seed 42, selected at update 2,800 of 3,600; 205.7 min on one RTX 4090 (23.9 GiB peak), RLCD + cross-entropy, rank-8 LoRA.
| GPT-5.6 Luna | API | 97.4% |
| Jev 1.13.0 (TypeSafe AI) | API | 86.6% |
| SemIf (Qwen3.5-4B) | local 4B | 80.9% |
| Linnaeus-0.1.0-2B | local 2B | 73.2% |
| Qwen3.8 27B (Chutes TEE) | cloud 27B | 69.0% |
| upstream Dohnuts-0.1.0-0.8B | local 0.8B | 65.8% |
| DeBERTa-v3-large classifiers | local <1B | 52.4% |
Head-to-head on identical tasks: ±0 vs Gemma-4-E2B LoRA, −18 net vs the 4B classifiers, +24 to +48 vs every sub-1B model.
| CUDA + fla/Triton kernels | training-time reference | 73.16% |
| torch MPS (reference kernels) | Mac, merged-hf | 71.00% |
| MLX 8-bit | 1.9 GB · Mac + iPhone | 70.56% |
| MLX 4-bit | 1.0 GB · smallest | 67.53% |
The gap is kernel fidelity — fla's chunked delta-rule vs reference implementations — not quantization. Easy + standard tiers match 100%.
The mechanism
Each request carries a state and independent questions. Every candidate ends with a reserved marker; the head scores the hidden state at each marker, and a temperature-calibrated softmax yields the distribution.
Three question genera: choice, noul, score.
predict(
state = { user_intent: "play jazz" },
questions = {
app: choice ─┬─ spotify ▮ 0.999
├─ maps ▮ 0.000
└─ settings ▮ 0.001
urgent: noul ── true ▮ 0.93
level: score ── E[·] = 1.15
})Why it is built this way
Decisions, not generations
Product interfaces need distributions, not prose. Each candidate gets a reserved marker; one forward scores all of them through a shared scalar head — then per-type temperature calibration turns logits into honest probabilities.
A hybrid backbone, kept frozen
Qwen3.5-2B interleaves delta-rule linear attention with full attention (3:1) — long states are cheap to carry. We freeze it entirely and train rank-8 LoRA + the head, upstream’s recipe unchanged, so the comparison is the backbone’s and nothing else’s.
The score-row trick
For on-device export the scalar head is appended as an extra lm_head row. Any stock LM runtime — MLX, llama.cpp, plain torch — then yields the decision score as logits[marker, score_row]. No custom head code ships to any runtime.
Measured humility
4-bit quantization was not the main cost: MLX at bf16 already trails CUDA by ~3pp, all on hard-tier borderline tasks — the fla chunked delta-rule kernel vs reference implementations. We publish the ladder, not the highlight.
The specimens
All specimens at huggingface.co/pi-dal — Apache-2.0.
The garden it grew in
Measured, not claimed: multi-step numeric decisions (JevBench temporal_numeric, 2/15) are the known weak cell. Every figure on this page traces to a replayable artifact in the repository.