🧡 Thimble

A 48M-parameter tool-calling model that beats Needle 2 on 3 of its 5 published benchmarks β€” including their toughest, Seal-Tools β€” with 150Γ— less training data.

GitHub (code, evals, full experimental record) Β· MIT Β· 48.12M params Β· 11.5MB at 2-bit Β· $260 total build cost

Results

TL;DR

Suite Thimble v6 Needle 2 (45M)
Seal-Tools in-domain (700) 33.1 32.6 βœ… their flagship suite
Mobile Actions (961) 86.3 63.7 βœ… +22.6
DroidCall (200) 52.5 17.0 βœ… 3.1Γ—
Well-formed JSON 100.0 93.4 βœ… by construction
Seal-Tools out-of-domain (654) 28.1 28.7 ❌ βˆ’0.6
BFCL v4 single-turn (3,641) 23.5 42.6 ❌ their data moat

Metric: ordered strict exact match β€” a row passes only if the function names, call order, and every argument value match. Their metric, their published numbers, unmodified. Needle 2 trained on 153B tokens; Thimble saw ~1B.

Two things to know before quoting the table: the Seal-in margin (+0.5 on 700 rows) is within sampling noise and we say so, and the pre-registered model selector actually picked a sibling checkpoint that scored worse β€” the failure is diagnosed, both models' results are published, and the full story is in RESULTS.md.

Why a thimble beats a needle

1. Tool calling is five decisions, not a generation problem. A grammar compiled from the tool schemas force-feeds all JSON structure β€” braces, quotes, and every argument key. The model is consulted at exactly five choice points: refuse or call Β· which tool Β· include this optional? Β· what value Β· stop or continue. Malformed JSON, hallucinated parameter names, and calls to nonexistent tools are unreachable, not unlikely. At 45M parameters, capacity spent learning that { follows [ is capacity wasted.

2. Every training example earns its place. Row accuracy factors as P(name sequence) Γ— pⁿ. Each version measured which factor was binding and attacked only that. The final data round was synthesized directly against the previous model's diagnosed failure buckets β€” spurious optional arguments, wrong-slot entity binding, date canonicalization β€” with a mid-training causal check (+3.3 points at constant LR, attributable to the corrective data alone).

3. Anneal, don't retrain. A controlled twin experiment: the corrective corpus fed from scratch diluted (28.4); the same corpus annealed into the learning-rate decay phase of a continued run concentrated (33.1). The decay phase is where a WSD-trained model crystallizes β€” that's where the good data belongs.

What didn't work (measured, not guessed)

The most reusable part of the project. Each idea was killed by an A/B, not an argument:

idea result
Span-copy heads βˆ’30 pts
Pointer/copy head βˆ’16 pts
Down-weighting grammar-forced tokens (RFT-style) βˆ’12 pts β€” structure tokens carry call-sequencing signal
From-scratch retrain on corrective data βˆ’4.7 vs annealing
Field-set reranking βˆ’1.4 β€” training had already fixed its target bucket
Beam / RL / best-of-N oracle-capped below target
RLOO fine-tune on the annealed checkpoint diverges at every LR β€” sharp minima and policy gradients don't mix
Matching Seal's gold numeric typing not learnable β€” 74% of params are mixed-convention noise

Model details

Parameters 48.12M (fp32; ~11.5MB at Needle's own 2-bit standard vs their 14MB)
Architecture deep-thin gated trunk: d=448, 20 layers, GQA 8/4, SwiGLU Γ—2.0, QK-norm, sandwich RMSNorm, tied embeddings
Tokenizer 16,384 BPE, digits as singletons, JSON structural chars as singletons
Context 768 tokens
Decoding grammar-constrained, five choice points, plan-conditioned retrieval between calls
Training Muon (trunk) + AdamW, WSD schedule, EMA, weighted CE matched to the error distribution, decay-phase data annealing

Files & usage

  • thimble-v6.pt β€” checkpoint (torch.load(..., weights_only=False) β†’ {"model": state_dict, "cfg": dict})
  • tokenizer.json β€” BPE vocab + merges

The guarantees live in the decoding harness, so inference goes through the repo:

git clone https://github.com/nikshepsvn/thimble
cd thimble && uv venv && uv pip install -e .
# put thimble-v6.pt in checkpoints/, tokenizer.json in data/

python demo.py "make a reservation at Nobu for 2 people at 7pm and text Sam saying dinner is on"
# [{"name": "createReservation",
#   "arguments": {"partySize": 2, "restaurant": "Nobu", "time": "7pm"}},
#  {"name": "sendMessage",
#   "arguments": {"body": "dinner is on", "contact": "Sam"}}]

python demo.py "sing me a happy birthday song"
# []  (refused: no tool applies)

python scripts/final_eval.py --ckpt thimble-v6 --suite seal-tools-in  # reproduce the table

Real output, not a mock β€” typed integers, two-call composition, and refusal, with structure guaranteed by the grammar.

Integrity

Public corpora (xlam, ToolACE, Dolci, Glaive, official benchmark train splits) plus stepwise-validated, evidence-filtered synthetic data. Every training row passed an 8-gram contamination firewall against every evaluation query of every reported suite (BFCL included). Champion selection by held-out dev loss only; nothing was ever tuned on an eval set; every negative result is published.

Built by one person and an AI assistant in about a week of evenings, for about the price of a game console. The failures are the useful part.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support

Evaluation results