File size: 7,215 Bytes
0b79309
 
 
 
 
 
 
 
 
 
 
3852c07
0b79309
 
 
 
 
 
 
 
 
 
 
 
 
 
3852c07
 
 
 
 
 
 
 
 
 
0b79309
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3852c07
 
 
 
 
 
 
 
 
 
0b79309
 
3852c07
 
 
ce056af
3852c07
c1b42be
3852c07
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c1b42be
3852c07
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0b79309
 
c1b42be
 
3852c07
e0499fe
 
 
 
 
 
 
 
 
 
 
0b79309
 
e0499fe
 
 
3852c07
0b79309
3852c07
 
 
 
 
0b79309
3852c07
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
---
license: mit
language:
- en
pipeline_tag: text-generation
tags:
- function-calling
- tool-calling
- on-device
- small-model
- grammar-constrained-decoding
- edge
library_name: pytorch
model-index:
- name: thimble-v6
  results:
  - task:
      type: text-generation
      name: Function calling (ordered strict exact match)
    dataset:
      name: Seal-Tools in-domain
      type: seal-tools
    metrics:
    - type: exact_match
      value: 33.1
      name: Seal-Tools in-domain
  - task:
      type: text-generation
      name: Function calling (ordered strict exact match)
    dataset:
      name: Seal-Tools out-of-domain
      type: seal-tools
    metrics:
    - type: exact_match
      value: 28.1
      name: Seal-Tools out-of-domain
  - task:
      type: text-generation
      name: Function calling (ordered strict exact match)
    dataset:
      name: Mobile Actions
      type: mobile-actions
    metrics:
    - type: exact_match
      value: 86.3
      name: Mobile Actions
  - task:
      type: text-generation
      name: Function calling (ordered strict exact match)
    dataset:
      name: DroidCall
      type: droidcall
    metrics:
    - type: exact_match
      value: 52.5
      name: DroidCall
  - task:
      type: text-generation
      name: Function calling (ordered strict exact match)
    dataset:
      name: BFCL v4 single-turn
      type: bfcl
    metrics:
    - type: exact_match
      value: 23.5
      name: BFCL v4 single-turn
---

# 🧡 Thimble

**A 48M-parameter tool-calling model that beats [Needle 2](https://cactuscompute.com/needle)
on 3 of its 5 published benchmarks β€” including their toughest, Seal-Tools β€” with 150Γ— less training data.**

[**GitHub (code, evals, full experimental record)**](https://github.com/nikshepsvn/thimble) Β· MIT Β· 48.12M params Β· 11.5MB at 2-bit Β· $260 total build cost

![Results](results.png)

## 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](https://github.com/nikshepsvn/thimble/blob/master/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:

```bash
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.*