File size: 10,786 Bytes
d4121b4
 
7aa0cc9
 
d4121b4
 
 
 
 
7aa0cc9
b36e359
 
d4121b4
b36e359
7aa0cc9
 
b36e359
7aa0cc9
b36e359
7aa0cc9
b36e359
 
 
 
 
d4121b4
 
b36e359
d4121b4
b36e359
 
d4121b4
b36e359
 
 
 
 
 
d4121b4
b36e359
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7aa0cc9
 
 
b36e359
7aa0cc9
b36e359
7aa0cc9
b36e359
 
 
 
7aa0cc9
 
 
b36e359
7aa0cc9
b36e359
 
 
 
 
 
 
d4121b4
 
b36e359
7aa0cc9
b36e359
 
 
7aa0cc9
b36e359
 
 
 
7aa0cc9
b36e359
 
 
 
 
 
7aa0cc9
b36e359
7aa0cc9
b36e359
 
 
 
 
 
 
7aa0cc9
b36e359
7aa0cc9
b36e359
 
 
 
 
7aa0cc9
 
d4121b4
b36e359
d4121b4
 
 
 
 
 
b36e359
 
 
 
 
 
7aa0cc9
d4121b4
7aa0cc9
b36e359
d4121b4
7aa0cc9
d4121b4
7aa0cc9
b36e359
7aa0cc9
b36e359
7aa0cc9
b36e359
7aa0cc9
b36e359
 
 
 
 
 
 
 
 
 
 
 
 
d4121b4
7aa0cc9
 
 
b36e359
7aa0cc9
b36e359
 
 
 
 
 
 
 
 
 
 
 
7aa0cc9
 
 
b36e359
 
93c6960
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b36e359
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93c6960
b36e359
 
93c6960
b36e359
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93c6960
b36e359
 
 
 
 
 
 
7aa0cc9
b36e359
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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
---
license: mit
language:
- en
tags:
- function-calling
- tiny-model
- edge-ai
- tool-use
- router
- pytorch
- gguf
pipeline_tag: text-generation
library_name: generic
widget:
- text: "weather in tokyo today"
  example_title: "Web Search Intent"
- text: "tell me a joke"
  example_title: "Abstain (none)"
- text: "who is marie curie"
  example_title: "Person Search"
- text: "latest iphone 16 price in india"
  example_title: "Price Search"
datasets:
- Safeeq/FCtiny
---

<div align="center">

# πŸ”€ TinyLM-FC β€” Tiny Function-Calling Router
### ~0.47M Parameters | CPU < 4ms | MIT License

[![GitHub](https://img.shields.io/badge/GitHub-Source%20Code-black?logo=github)](https://github.com/Safeeq008/tiny-fc-lm)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Python 3.10+](https://img.shields.io/badge/Python-3.10%2B-blue.svg)](https://www.python.org/)
[![Parameters](https://img.shields.io/badge/Parameters-~0.47M-red.svg)](#architecture)
[![CPU Latency](https://img.shields.io/badge/CPU%20Latency-~3.2ms-purple.svg)](#benchmarks)
[![GGUF](https://img.shields.io/badge/Ollama%2FlllamaCpp-GGUF%20Available-green.svg)](#gguf--ollama)

</div>

A **sub-half-million-parameter decoder-only transformer** trained completely from scratch to act as a lightweight, low-latency **function-calling router**. Given a user utterance, TinyLM decides whether to invoke an external tool (`web_search`) with structured arguments, or abstain (`none`) for chit-chat and greetings.

Built as an educational and empirical case study: *how small can a specialized router be while still maintaining high precision?*

---

## πŸš€ Quick Links

| Resource | Link |
| :--- | :--- |
| πŸ“¦ **Full Source Code, Training & Eval Pipeline** | [github.com/Safeeq008/tiny-fc-lm](https://github.com/Safeeq008/tiny-fc-lm) |
| πŸ€— **Model Weights (this page)** | [huggingface.co/Safeeq/FCtiny](https://huggingface.co/Safeeq/FCtiny) |
| πŸ“„ **Detailed README & Architecture Docs** | [README.md on GitHub](https://github.com/Safeeq008/tiny-fc-lm/blob/main/README.md) |
| 🐳 **Docker Deployment** | [Dockerfile](https://github.com/Safeeq008/tiny-fc-lm/blob/main/Dockerfile) · [DEPLOY.md](https://github.com/Safeeq008/tiny-fc-lm/blob/main/DEPLOY.md) |
| πŸ¦™ **Ollama / llama.cpp Guide** | [OLLAMA.md](https://github.com/Safeeq008/tiny-fc-lm/blob/main/OLLAMA.md) |

---

## πŸ“¦ Files in This Repository

| File | Size | Description |
| :--- | :--- | :--- |
| `model.safetensors` | ~1.8 MB | PyTorch weights in safe zero-copy format |
| `config.json` | ~1 KB | Architecture hyperparameters |
| `tokenizer.json` | ~121 KB | Custom 2048-vocab ByteLevel BPE tokenizer |
| `tinyfc.gguf` | ~1.0 MB | Quantized model for Ollama & llama.cpp |

---

## ⚑ Quickstart

### Option A β€” Python (from this Hub)

```bash
git clone https://github.com/Safeeq008/tiny-fc-lm.git
cd tiny-fc-lm
pip install -r requirements.txt
python infer.py
```

### Option B β€” GGUF via Ollama (zero-install inference)

```bash
# Download the GGUF and Modelfile from GitHub
curl -LO https://github.com/Safeeq008/tiny-fc-lm/raw/main/Modelfile

# Pull the GGUF from Hugging Face and register it
ollama pull hf.co/Safeeq/FCtiny:latest
ollama run hf.co/Safeeq/FCtiny
```

Or using the local GGUF:
```bash
# Download tinyfc.gguf from the Files tab above, then:
ollama create tinyfc -f Modelfile
ollama run tinyfc "weather in new york today"
```

### Option C β€” FastAPI REST Server (from GitHub)

```bash
git clone https://github.com/Safeeq008/tiny-fc-lm.git
cd tiny-fc-lm && pip install -r requirements.txt
python serve.py 8000
```
- Web UI: `http://localhost:8000`
- Swagger docs: `http://localhost:8000/docs`

### Option D β€” Docker

```bash
docker pull ghcr.io/safeeq008/tiny-fc-lm:latest  # or build locally:
git clone https://github.com/Safeeq008/tiny-fc-lm.git
docker build -t tiny-fc-lm . && docker run -p 8000:8000 tiny-fc-lm
```

---

## πŸ” Python Inference (Manual)

```python
import torch
from safetensors.torch import load_file
from tokenizers import Tokenizer

# ── Clone the repo for model.py ──
# git clone https://github.com/Safeeq008/tiny-fc-lm.git
import sys; sys.path.insert(0, "tiny-fc-lm")
from model import TinyLM

# 1. Load weights + tokenizer (download from Files tab above)
state_dict = load_file("model.safetensors")
tok = Tokenizer.from_file("tokenizer.json")

# 2. Instantiate model
model = TinyLM(vocab=2048, d=80, n_layers=4, n_heads=4, ffn_mult=4, max_len=80)
model.load_state_dict(state_dict)
model.eval()

# 3. Encode prompt with special tokens (<user>=1, <call>=2)
user_input = "weather in chennai today"
prompt_ids = [1] + tok.encode(user_input).ids + [2]

# 4. Greedily decode
with torch.no_grad():
    ids = torch.tensor([prompt_ids])
    for _ in range(30):
        logits = model(ids)[0]
        next_id = logits[0, -1].argmax().item()
        if next_id == 3:  # <end> token
            break
        ids = torch.cat([ids, torch.tensor([[next_id]])], dim=-1)
    output_ids = ids[0, len(prompt_ids):].tolist()
    print(tok.decode(output_ids))
# β†’ web_search|query=chennai weather today|recency=day

# For full constrained decoding + live DuckDuckGo dispatch, see:
# https://github.com/Safeeq008/tiny-fc-lm/blob/main/infer.py
```

---

## πŸ“ Architecture

| Hyperparameter | Value | Notes |
| :--- | :---: | :--- |
| **Total Parameters** | 471,760 (~0.47M) | Trainable weight count |
| **Layers** | 4 | Transformer decoder blocks |
| **Hidden Dim** | 80 | Embedding & layer dimensionality |
| **Attention Heads** | 4 | Head dim = 20 (even for RoPE) |
| **Positional Encoding** | RoPE | ΞΈ = 10000.0 |
| **Normalization** | RMSNorm | Ξ΅ = 1e-6, pre-norm |
| **FFN** | GELU (4Γ— width) | Hidden dim = 320 |
| **Weight Tying** | βœ… Yes | Input embeddings = output head |
| **Vocabulary Size** | 2,048 | Custom ByteLevel BPE |
| **Context Length** | 80 tokens | Max prompt + generation |

---

## πŸ“Š Benchmarks

Evaluated on 2,000 in-distribution samples (`val.jsonl`), 1,000 held-out phrasing samples (`test_ood.jsonl`), and 1,000 domain-shift samples (`test_ood_domain.jsonl`):

| Metric | Val (In-Distribution) | Phrasing OOD (Held-Out Phrasings) | Domain Shift OOD (Novel Entities & Global) |
| :--- | :---: | :---: | :---: |
| **Exact Match Accuracy** | **99.90%** | **82.90%** | **0.40%** *(entity vocabulary bounded)* |
| **Routing Decision Accuracy** | **99.95%** | **89.50%** | **95.40%** |
| **Routing Precision (Tool)** | **100.00%** | **89.15%** | **100.00%** |
| **Routing Recall (Tool)** | **99.95%** | **99.40%** | **95.40%** |
| **Query Slot Exact Match** | **99.90%** | **82.90%** | **0.40%** |
| **Recency Slot Accuracy** | **99.90%** | **84.90%** | **54.60%** |
| **Syntactic Validity Rate** | **100.0%** | **99.80%** | **99.30%** |
| **CPU Inference Latency** | **~23.8 ms** | **~24.0 ms** | **~27.8 ms** |

### Per-Intent Performance (Phrasing OOD)

Key utility intents have been fortified against phrasing shifts and temporal ambiguity:

| Intent Category | Val Exact % | OOD Exact % | OOD Routing % | Status |
| :--- | :---: | :---: | :---: | :--- |
| **How-to / Guides** | 100.0% | **100.0%** | 100.0% | **Resolved (was 86.4%)** |
| **Sports Scores** | 100.0% | **100.0%** | 100.0% | **Resolved (was 88.5%)** |
| **Product Prices** | 100.0% | **96.2%** | 100.0% | **Resolved (was 84.8%)** |
| **General Concept** | 100.0% | **98.8%** | 98.8% | High fidelity |
| **Weather** | 99.4% | **97.4%** | 100.0% | High fidelity |
| **Stock Quotes** | 100.0% | **96.2%** | 100.0% | High fidelity |
| **Web Search** | 100.0% | **100.0%** | 100.0% | High fidelity |
| **Weekly Recap** | 100.0% | **80.3%** | 100.0% | 100% routing fidelity |
| **News** | 99.7% | **56.4%** | 97.0% | 97% routing fidelity |
| **Chit-Chat (Abstain)** | 100.0% | **41.0%** | 42.2% | Abstract banter vs tool call |

---

## πŸ”€ Output Protocol

TinyLM outputs a strict, pipe-delimited schema:

```
web_search|query=<search query>|recency=<day|week|any>
none
```

**Examples:**

| User Input | Model Output |
| :--- | :--- |
| `weather in tokyo today` | `web_search\|query=tokyo weather today\|recency=day` |
| `latest AI news` | `web_search\|query=latest ai news\|recency=day` |
| `iphone 16 price in india` | `web_search\|query=iphone 16 price india\|recency=week` |
| `hi how are you` | `none` |
| `tell me a joke` | `none` |
| `what is 2 + 2` | `none` |

---

## πŸ¦™ GGUF & Ollama

A `tinyfc.gguf` artifact (~1.09 MB, fp16) is available in the **Files** tab above. This is a canonical Llama-architecture twin (~512k params, SwiGLU MLP) trained on the same task and tokenizer, exported for llama.cpp-compatible runtimes.

**Quick usage:**
```bash
# Download tinyfc.gguf from the Files tab, then:
ollama create tinyfc -f Modelfile
ollama run tinyfc "latest ethereum price"
# β†’ web_search|query=ethereum price today|recency=day
```

See the full guide in [OLLAMA.md](https://github.com/Safeeq008/tiny-fc-lm/blob/main/OLLAMA.md).

---

## πŸ—οΈ Reproduce from Scratch

Full pipeline available at [github.com/Safeeq008/tiny-fc-lm](https://github.com/Safeeq008/tiny-fc-lm):

```bash
git clone https://github.com/Safeeq008/tiny-fc-lm.git
cd tiny-fc-lm && pip install -r requirements.txt

python gen_data.py      # 1. Generate 80k synthetic training samples (train, val, phrasing OOD, domain OOD)
python train_tok.py     # 2. Train custom 2048-vocab BPE tokenizer
python train.py         # 3. Train TinyLM-FC model (~5-10 min, CPU)
python eval.py          # 4. Full benchmark suite across all splits
python infer.py         # 5. Interactive CLI with live DuckDuckGo search
python serve.py 8000    # 6. FastAPI server + web UI

# Optional: GGUF/Ollama path
python train_llama.py   # 7. Train Llama-arch twin
python export_gguf.py   # 8. Convert to GGUF
ollama create tinyfc -f Modelfile && ollama run tinyfc
```

---

## ⚠️ Known Limitations

1. **Single-tool**: Strictly routes `web_search` only β€” no multi-tool schemas.
2. **English only**: Custom BPE tokenizer trained on English synthetic corpus.
3. **80-token context window**: Inputs > ~60 user tokens are silently truncated.
4. **Synthetic training data & domain shift boundary**: Real-world query distributions are far noisier. While **tool routing transfers at 95.40%** to novel domains and unobserved tasks, **exact entity string extraction is bounded by the 0.47M parameter vocabulary** for unseen entities.
5. **Constrained decoding required**: Raw greedy decoding may produce malformed outputs; use `infer.py`'s constrained decoder for production.

---

## πŸ“„ License

Released under the **MIT License** β€” free for research, benchmarking, commercial use, and edge deployment.

**Repository:** [github.com/Safeeq008/tiny-fc-lm](https://github.com/Safeeq008/tiny-fc-lm)