GautamKishore commited on
Commit
3219e0e
Β·
verified Β·
1 Parent(s): 8ed35c1

Upload folder using huggingface_hub

Browse files
Files changed (3) hide show
  1. MODEL_CARD.md +229 -0
  2. docs/PLAN.md +191 -0
  3. walkthrough.md +459 -0
MODEL_CARD.md ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ language:
4
+ - multilingual
5
+ tags:
6
+ - content-classification
7
+ - byte-level
8
+ - onnx
9
+ - matryoshka
10
+ - lightweight
11
+ - classifier
12
+ - multi-head
13
+ - clipboard
14
+ pipeline_tag: text-classification
15
+ library_name: pico-type
16
+ co2_eq_emissions: 0.02
17
+ model-index:
18
+ - name: pico-type
19
+ results:
20
+ - task:
21
+ type: text-classification
22
+ name: Multi-Head Content Classification
23
+ metrics:
24
+ - type: accuracy
25
+ value: 0.998
26
+ name: Coarse Accuracy
27
+ - type: accuracy
28
+ value: 1.0
29
+ name: Modality Accuracy
30
+ - type: accuracy
31
+ value: 0.947
32
+ name: Subtype Accuracy
33
+ - type: accuracy
34
+ value: 0.387
35
+ name: Code Language Accuracy
36
+ - type: accuracy
37
+ value: 0.988
38
+ name: Text Language Accuracy
39
+ - type: accuracy
40
+ value: 1.0
41
+ name: File MIME Accuracy
42
+ - type: average_precision
43
+ value: 1.0
44
+ name: Risk Average Precision
45
+ ---
46
+
47
+ # pico-type
48
+
49
+ **A tiny byte-level multi-head content classifier** (~1.5M parameters) built by [**eulogik**](https://eulogik.com).
50
+
51
+ Classifies any content into **7 categories** simultaneously from raw bytes in a single forward pass β€” no tokenizer, no pretrained embeddings, no GPU required.
52
+
53
+ ## Model Details
54
+
55
+ | Property | Value |
56
+ |----------|-------|
57
+ | **Developer** | [eulogik](https://eulogik.com) |
58
+ | **Release Date** | June 2026 |
59
+ | **License** | Apache 2.0 |
60
+ | **Model Type** | Byte-level neural classifier |
61
+ | **Input** | Raw UTF-8 bytes (0–255), up to 1024 bytes |
62
+ | **Output** | 7 classification heads |
63
+ | **Parameters** | 1.43M–1.56M (4 Matryoshka tiers) |
64
+ | **ONNX Size** | ~200 KB (FP32) |
65
+ | **Inference** | ~5ms on CPU (base tier) |
66
+ | **Training Hardware** | Apple M4 (MPS), 16GB |
67
+
68
+ ## Architecture
69
+
70
+ ```
71
+ ByteEmbed(256β†’96d) β†’ 3Γ—Conv1D(k=3,5,7) β†’ 2Γ—BiAttention(RoPE) β†’ Pool β†’ 7Γ—Matryoshka Heads
72
+ ```
73
+
74
+ - **Byte-level**: No tokenizer, no subword vocabulary, no pretrained embeddings
75
+ - **Matryoshka**: 4 tiered variants share the same trunk; head dimensions vary (16/64/192/576)
76
+ - **Multi-head**: 7 independent classification heads trained jointly with per-task loss weighting
77
+
78
+ ## Classification Heads
79
+
80
+ | Head | Classes | Gated | Examples |
81
+ |------|---------|-------|----------|
82
+ | **coarse** | 12 | β€” | text, code, link, image, file, config, markup, data, error, secret, archive, binary |
83
+ | **modality** | 8 | β€” | textual, binary_image, binary_archive, binary_executable, binary_document, binary_audio, binary_video, binary_other |
84
+ | **subtype** | 24 | coarse∈{config,markup,data} | json, yaml, toml, ini, csv, html, xml, markdown, sql, log, diff, dockerfile |
85
+ | **code_lang** | 62 | coarse=code | python, javascript, typescript, java, c, cpp, go, rust, swift, kotlin, bash, sql, ruby, php, perl, lua, r, julia, haskell, scala, dart, elixir, clojure, erlang, zig, nim |
86
+ | **text_lang** | 30 | coarse=text | en, es, fr, de, it, pt, ru, zh, ja, ko, ar, hi, tr, nl, pl, sv, da, fi, nb, cs, hu, ro, uk, el, he, th, vi, id, ms, ta |
87
+ | **file_mime** | 90 | coarse∈{image,file} | text/html, application/json, application/pdf, image/png, image/jpeg, video/mp4, audio/mpeg, application/zip |
88
+ | **risk** | 6 | β€” | api_key, jwt, password, email, phone, ssh_key (independent probabilities) |
89
+
90
+ ## Performance
91
+
92
+ ### Synthetic Benchmark (1000 samples, base tier)
93
+
94
+ | Head | Accuracy | Support |
95
+ |------|----------|---------|
96
+ | coarse | 99.8% | 1000 |
97
+ | modality | 100.0% | 1000 |
98
+ | subtype | 94.7% | ~250 |
99
+ | code_lang | 38.7% | ~90 |
100
+ | text_lang | 98.8% | ~80 |
101
+ | file_mime | 100.0% | ~250 |
102
+ | risk (mAP) | 100.0% | β€” |
103
+
104
+ **Inference**: 5.0ms on M4 CPU via ONNX Runtime (base tier, 1024 bytes)
105
+
106
+ ### Real-World Evaluation (21 hand-curated inputs)
107
+
108
+ | Category | Accuracy |
109
+ |----------|----------|
110
+ | Coarse | **86%** (18/21) |
111
+ | Modality | **100%** (21/21) |
112
+ | Overall | **52%** (11/21 exact match) |
113
+
114
+ _Diverse training improved real-world accuracy from 23% to 52%._
115
+
116
+ ### Tier Comparison
117
+
118
+ | Tier | Dim | Params | ONNX Size | Speed |
119
+ |------|-----|--------|-----------|-------|
120
+ | tiny | 16 | 1.43M | 207 KB | ~3ms |
121
+ | small | 64 | 1.45M | 207 KB | ~4ms |
122
+ | base | 192 | 1.48M | 209 KB | ~5ms |
123
+ | pro | 576 | 1.56M | 206 KB | ~12ms |
124
+
125
+ ## Training
126
+
127
+ The model is trained using a multi-task loss:
128
+
129
+ ```
130
+ L = Ξ£ w_h Β· L_h
131
+ ```
132
+
133
+ - **Single-label heads**: Cross-entropy with ignore_index=-100 for gated samples
134
+ - **Risk head**: Binary cross-entropy
135
+ - **Head weights**: coarse=8.0, modality=2.0, code_lang=2.0, text_lang=1.5, others=1.0
136
+
137
+ ### Training Data
138
+
139
+ 1. **Synthetic data** β€” 11 content buckets with per-language templates (62 code langs, 30 text langs, 90 MIME types, 22 binary formats)
140
+ 2. **Diverse synthetic** β€” Realistic error traces, markdown with code blocks, nested configs, environment files, multi-language prose
141
+ 3. **Real GitHub code** β€” ~650 files across 62 languages via GitHub Search API (30% training mix)
142
+
143
+ ### Training Hyperparameters
144
+
145
+ | Parameter | Value |
146
+ |-----------|-------|
147
+ | Optimizer | AdamW (β₁=0.9, Ξ²β‚‚=0.999) |
148
+ | Learning Rate | 3e-3 (cosine decay) |
149
+ | Warmup Steps | 200 |
150
+ | Weight Decay | 0.01 (trunk), 0.0 (heads) |
151
+ | Batch Size | 16 |
152
+ | Total Steps | 4000 |
153
+ | Gradient Clip | 1.0 |
154
+ | Hardware | Apple M4 16GB (MPS) |
155
+
156
+ ## Usage
157
+
158
+ ### CLI
159
+ ```bash
160
+ pip install picotype
161
+ echo "def hello(): pass" | picotype --pretty
162
+ picotype --file document.txt
163
+ picotype --clip
164
+ ```
165
+
166
+ ### Python
167
+ ```python
168
+ from picotype import PicoType, PicoTypeConfig, load_checkpoint, decode_output
169
+
170
+ model = PicoType(PicoTypeConfig()).eval()
171
+ load_checkpoint("checkpoints/best.pt", model)
172
+ logits = model(encode_bytes(b"input content"))
173
+ result = decode_output(logits)
174
+ ```
175
+
176
+ ### ONNX Runtime
177
+ ```python
178
+ import onnxruntime as ort
179
+ session = ort.InferenceSession("picotype_base.onnx")
180
+ # input: int64[1, seq_len], bool[1, seq_len]
181
+ ```
182
+
183
+ ### MCP Server
184
+ ```bash
185
+ PICOTYPE_MODEL_DIR=./checkpoints python -m model.pico_type.mcp_server
186
+ ```
187
+
188
+ ## Model Tiers
189
+
190
+ All tiers share the same trunk; only the Matryoshka head linears differ. You can switch tiers at inference time with zero overhead:
191
+
192
+ ```python
193
+ logits_tiny = model(x, mask, tier="tiny")
194
+ logits_base = model(x, mask, tier="base")
195
+ ```
196
+
197
+ ## Deployment
198
+
199
+ | Platform | URL |
200
+ |----------|-----|
201
+ | HuggingFace Space | [eulogik/pico-type](https://huggingface.co/spaces/eulogik/pico-type) |
202
+ | HuggingFace Model | [eulogik/pico-type](https://huggingface.co/eulogik/pico-type) |
203
+ | GitHub | [eulogik/pico-type](https://github.com/eulogik/pico-type) |
204
+ | PyPI | `pip install picotype` |
205
+ | arXiv | [XXXX.XXXXX](https://arxiv.org/abs/XXXX.XXXXX) |
206
+
207
+ ## Limitations
208
+
209
+ - **code_lang accuracy**: 62-way classification with minimal per-class support. Real GitHub data helps (+10% absolute) but authentic code patterns (imports, type hints, decorators) remain challenging.
210
+ - **Synthetic-only training**: Model overfits to template patterns. Diverse synthetic + real data significantly improves real-world robustness.
211
+ - **Max input length**: 1024 bytes. Longer content is truncated.
212
+ - **No fine-grained text understanding**: Designed for content classification, not NLP tasks like sentiment analysis or NER.
213
+
214
+ ## Citation
215
+
216
+ ```bibtex
217
+ @software{eulogik2026picotype,
218
+ author = {eulogik},
219
+ title = {pico-type: A Tiny Byte-Level Multi-Head Content Classifier},
220
+ year = {2026},
221
+ url = {https://github.com/eulogik/pico-type}
222
+ }
223
+ ```
224
+
225
+ ---
226
+
227
+ <div align="center">
228
+ <sub>Built by <a href="https://eulogik.com">eulogik</a> β€” AI infrastructure for developers.</sub>
229
+ </div>
docs/PLAN.md ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Final Plan: **pico-type** β€” A 3.5M Multi-Head Byte Classifier for Universal Content Type Detection
2
+
3
+ ## 1. Executive Summary
4
+
5
+ **pico-type** is a tiny, fully open-source, byte-level multi-head classifier that takes any blob (clipboard text, file bytes, image header, etc.) and emits a structured label set in one forward pass. It ships as:
6
+
7
+ - A HuggingFace model (Apache-2.0) with multi-tier Matryoshka exports
8
+ - A Rust + Python CLI (`picotype`)
9
+ - A Gradio Space that doubles as an **MCP server** (callable from Claude, Cursor, VSCode)
10
+ - A browser extension (compete with ClipGate)
11
+ - A Raycast/Alfred extension
12
+ - A companion arXiv paper
13
+
14
+ **Why now / the gap:** Existing clipboard tools are either regex-only (ClipGate, 13 types) or LLM-powered (Clipboard_ai, needs Ollama, GB-scale). Existing tiny classifiers do one job (code-language, MIME, intent, PII). No model does **all of them in one sub-5MB forward pass with a multi-head output**.
15
+
16
+ ## 2. Architecture
17
+
18
+ ```
19
+ Inputs (≀1024 UTF-8 bytes, masked/padded)
20
+ β”‚
21
+ β”œβ”€ ByteEmbed (256 β†’ 96d, learned; optional FFT-Rotate variant from Kathleen)
22
+ β”‚
23
+ β”œβ”€ 3Γ— Conv1D block (kernel 3,5,7) + GELU + residual
24
+ β”‚
25
+ β”œβ”€ 2Γ— Bidirectional attention block (d=192, 4 heads, RoPE ΞΈ=500k)
26
+ β”‚ [ablation: also test a Kathleen-style O(L) oscillator trunk
27
+ β”‚ for the sub-1MB mobile tier]
28
+ β”‚
29
+ β”œβ”€ Pool = [mean β€– max β€– std] β†’ 576d shared trunk
30
+ β”‚
31
+ └─ Matryoshka heads (independent Linear at 16/64/192/576 dims)
32
+ β”œβ”€ h_coarse (12-way softmax) β€” primary type
33
+ β”œβ”€ h_modality (8-way softmax) β€” textual / binary-image / binary-archive …
34
+ β”œβ”€ h_subtype (24-way softmax) β€” JSON, YAML, CSV, HTML, … (only if coarse∈{config,markup,data})
35
+ β”œβ”€ h_code_lang (62-way softmax + "undetected") β€” if coarse=code
36
+ β”œβ”€ h_text_lang (30-way softmax + "undetected") β€” if coarse=text
37
+ β”œβ”€ h_file_mime (90-way softmax + "undetected") β€” if modality∈{binary-*}
38
+ └─ h_risk (6-way sigmoid multi-label) β€” api_key, jwt, ssh_key, password, email, phone
39
+ ```
40
+
41
+ **Output schema (always returned, with confidence):**
42
+ ```json
43
+ {
44
+ "coarse": "code",
45
+ "modality":"textual",
46
+ "subtype": null,
47
+ "code_language": "python", // or "undetected"
48
+ "text_language": null,
49
+ "file_mime": null,
50
+ "risk_flags": [],
51
+ "confidence": 0.94,
52
+ "model_tier": "standard" // tiny | small | standard | full
53
+ }
54
+ ```
55
+
56
+ **Size targets (Matryoshka tiering):**
57
+ | Tier | Dim | Params | FP32 | INT8 | Target device |
58
+ |---|---|---|---|---|---|
59
+ | `pico-type-tiny` | 16 | ~0.5M | 2 MB | 0.5 MB | MCU / IoT |
60
+ | `pico-type-small` | 64 | ~1.5M | 6 MB | 1.5 MB | Browser (WASM), mobile |
61
+ | `pico-type-base` | 192 | ~3.5M | 14 MB | 3.5 MB | Desktop CLI, browser ext |
62
+ | `pico-type-pro` | 576 | ~8M | 32 MB | 8 MB | Server / accurate use |
63
+
64
+ ## 3. Training
65
+
66
+ **Framework:** Pure PyTorch (~600 LoC train script), no transformers dependency. Permissive.
67
+
68
+ **Distillation teachers (Polymorph-style per-head):**
69
+ - `microsoft/deberta-v3-small` (140M) β†’ coarse + modality
70
+ - `huggingface/CodeBERTa-language-id` (84M) β†’ code_lang
71
+ - `papluca/xlm-roberta-base-language-detection` (270M) β†’ text_lang
72
+ - `mjbommar/magic-bert-50m-roformer-classification` (42M) β†’ file_mime
73
+ - T=2.0, Ξ±=0.7 KD loss, plus hard-label CE on 30% of batch
74
+
75
+ **Data sources (all open / free):**
76
+ - Code: `bigcode/the-stack-v2` (multilingual), `cakiki/rosetta-code`
77
+ - Natural text: Wikipedia dumps, OSCAR, mC4 (subset)
78
+ - Configs/markup: GitHub `*.{json,yaml,toml,ini,xml,html,md,tex}` via BigQuery GH archive
79
+ - URLs: CommonCrawl WAT
80
+ - Errors: StackOverflow / GH issue bodies (regex-filtered for traceback patterns)
81
+ - File bytes: `mjbommar/binary-tokenizer-001-64k` corpora, plus synthetic magic-byte headers
82
+ - Images: 1 KB header slices from OpenImages V7
83
+ - Secrets: synthetic generation (regex + entropy filter), `gptmail-secret-detection` datasets
84
+
85
+ **Synthetic data generator:** 1 Python script producing balanced mixtures of: prose paragraphs, code snippets (multi-lang, snippets of 64–1023 bytes), config files, error traces, etc. Critical for head coverage.
86
+
87
+ **Training recipe:**
88
+ - Sequence length 1024 bytes
89
+ - AdamW, peak lr 3e-3, cosine, 5% warmup, 30 epochs
90
+ - bf16, batch 128, grad clip 1.0
91
+ - Task-balanced sampling: each head gets β‰₯1 sample per batch
92
+ - Loss = Ξ£_head w_h Β· L_h with w tuned on val
93
+
94
+ **Hardware:** Single A100 80GB, ~36 h total training (3 seeds).
95
+
96
+ ## 4. Data β†’ Label Pipeline
97
+
98
+ | Bucket | Source | Auto-labels |
99
+ |---|---|---|
100
+ | Code | The Stack v2 | `language` field, repo paths |
101
+ | Prose | Wikipedia, mC4 | `lang` metadata |
102
+ | Config/markup | GH files | file ext + content sniff |
103
+ | URL | CommonCrawl WAT | regex for `http(s)://` |
104
+ | Error | SO, GH issues | regex for `Traceback`, `Error:`, `at line` |
105
+ | File bytes | binary-tokenizer corpora | magic-byte first-32B lookup |
106
+ | Image header | OpenImages | first 32B β†’ format (PNG/JPEG/WebP/…) |
107
+ | Secret | synthetic | regex + entropy β‰₯ 4.5 |
108
+
109
+ ## 5. Distribution & Popularity
110
+
111
+ ### 5.1 HuggingFace deliverables
112
+ - `pico-type/pico-type-base` β€” main model card with eval harness, ONNX int8/fp16, tract-onnx, GGUF
113
+ - `pico-type/pico-type-tiny` … `-pro` β€” Matryoshka tiers
114
+ - `pico-type/picotype-space` β€” Gradio Space (also a **registered MCP server** on HF Hub β€” free distribution via `huggingface.co/mcp` proxy)
115
+ - `pico-type/picotype-eval` β€” public eval suite (reproducible JSON benchmarks)
116
+ - Datasets: `pico-type/synth-clipboard-v1`, `pico-type/eval-suite-v1`
117
+
118
+ ### 5.2 Software deliverables
119
+ - **CLI** (`picotype`): Rust binary, ONNX runtime, sub-5ms inference. Reads stdin/file/clipboard, prints JSON.
120
+ - **MCP server** (`picotype-mcp`): stdio + Streamable HTTP, exposes tools: `classify`, `classify_batch`, `watch_clipboard` (macOS/Windows), `classify_history`
121
+ - **Browser extension** (Chrome/Firefox): replaces ClipGate's regex with pico-type; per-type icons; on-device, 100% local
122
+ - **Raycast extension** + Alfred workflow
123
+ - **VSCode extension**: paste-with-type, status bar shows type of selection
124
+ - **iOS Shortcut / Android Tasker** plug-in
125
+
126
+ ### 5.3 Launch timeline (4–6 weeks)
127
+ | Week | Milestone |
128
+ |---|---|
129
+ | 1 | Repo scaffold, byte-level pipeline working on synthetic data, all heads wired |
130
+ | 2 | Real data ingest, distillation teachers downloaded, base tier trained, eval harness |
131
+ | 3 | All four Matryoshka tiers trained, ONNX/tract/GGUF exported, ablations complete |
132
+ | 4 | CLI (Rust), MCP server (Python+Gradio), HF Space + MCP registration, model card |
133
+ | 5 | Browser extension (MV3), Raycast/VSCode extensions, docs site, demo video |
134
+ | 6 | arXiv preprint, Show HN, r/MachineLearning, r/LocalLLaMA, r/programming launches |
135
+
136
+ ### 5.4 Growth hooks
137
+ - First **MCP-native content classifier** β€” every Claude/Cursor/VSCode user is a potential user
138
+ - Free-tier on HF = automatic inference endpoint exposure
139
+ - Trending strategy: launch on a Tuesday, cross-post 4-5 channels same day, follow-up "vs ClipGate benchmarks" post
140
+ - Maintain a "What's new in pico-type" weekly digest β†’ builds audience
141
+ - Open evaluation suite β†’ community contributes new types (e.g., `meme_url`, `arxiv_id`, `semantic_version`) via a small "head-add" fine-tuning recipe
142
+ - Roadmap V2: adding a user-contributed `custom_types` head (LoRA per type) Γ  la Polymorph
143
+
144
+ ## 6. Repo structure (proposed)
145
+
146
+ ```
147
+ pico-type/
148
+ β”œβ”€β”€ README.md
149
+ β”œβ”€β”€ LICENSE # Apache-2.0
150
+ β”œβ”€β”€ model/
151
+ β”‚ β”œβ”€β”€ pico_type/
152
+ β”‚ β”‚ β”œβ”€β”€ arch.py # ByteHybrid trunk + heads
153
+ β”‚ β”‚ β”œβ”€β”€ train.py # multi-task trainer
154
+ β”‚ β”‚ β”œβ”€β”€ distill.py # KD from per-head teachers
155
+ β”‚ β”‚ β”œβ”€β”€ data.py # synthetic generator + dataset
156
+ β”‚ β”‚ β”œβ”€β”€ export.py # ONNX, int8, tract, gguf
157
+ β”‚ β”‚ └── eval.py # public eval harness
158
+ β”‚ └── configs/ # tier configs
159
+ β”œβ”€β”€ crates/picotype/ # Rust CLI
160
+ β”œβ”€β”€ crates/picotype-mcp/ # Rust MCP server (or Python)
161
+ β”œβ”€β”€ extensions/
162
+ β”‚ β”œβ”€β”€ chrome/ # MV3
163
+ β”‚ β”œβ”€β”€ raycast/
164
+ β”‚ β”œβ”€β”€ alfred/
165
+ β”‚ └── vscode/
166
+ β”œβ”€β”€ paper/ # arXiv LaTeX
167
+ β”œβ”€β”€ spaces/picotype/ # Gradio + MCP
168
+ └── docs/ # mintlify or mkdocs
169
+ ```
170
+
171
+ ## 7. Key risks & mitigations
172
+
173
+ | Risk | Mitigation |
174
+ |---|---|
175
+ | Multi-head gradients fight (task interference) | Gradient norm clipping per-head, gating + scheduled dropout |
176
+ | 30 langs underperform on low-resource | Reuse PleIAs/CommonLingua approach, weight by data |
177
+ | Byte-level convs slow on long inputs | Cap at 1024 bytes, document limit, Matryoshka tiers |
178
+ | MCP ecosystem churns | Support stdio + Streamable HTTP (latest spec), drop SSE |
179
+ | Competition clones us | First-mover + multi-channel launch + arXiv paper + community eval suite |
180
+
181
+ ## 8. Open decisions before I start coding
182
+
183
+ 1. **Name lock**: `pico-type` confirmed. Handle: `pico-type` on HF? Or `picotype` (no dash)? I suggest `pico-type` for HF and `picotype` for the CLI to match existing naming patterns.
184
+ 2. **Tier naming**: I propose `tiny`/`small`/`base`/`pro` matching Sentence-Transformers conventions.
185
+ 3. **OSI license**: Apache-2.0 (matches CommonLingua base, patent grant). Confirm.
186
+ 4. **arXiv target**: `cs.CL` (primary) + `cs.LG`. Co-authors: open question.
187
+ 5. **Tagline** (for the model card): "One tiny model, one forward pass, every clipboard."
188
+
189
+ ---
190
+
191
+ **Ready to execute when you give the green light.** I'll start by scaffolding the repo (`model/pico_type/arch.py` + `data.py` + minimal `train.py`) and getting a single-head baseline running on synthetic data β€” proving the byte-level pipeline + multi-head design before we invest in full data ingest. Estimated time-to-first-baseline: ~2 hours.
walkthrough.md ADDED
@@ -0,0 +1,459 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # pico-type β€” Walkthrough
2
+
3
+ > Living document. Every agent/harness picking up this project should start here. Also, you must keep updating as you go further.
4
+
5
+ ---
6
+
7
+ ## 1. What this is
8
+
9
+ **pico-type** is a tiny (~1.5M params), byte-level, multi-head content classifier. Input: up to 1024 raw bytes (clipboard text, file bytes, image header, etc.). Output: structured label set in one forward pass.
10
+
11
+ Built per the locked-in plan in [`docs/PLAN.md`](docs/PLAN.md) (recovered from the original opencode session β€” see Β§11).
12
+
13
+ ### Why this exists
14
+ Existing clipboard tools are regex-only (ClipGate, 13 types) or LLM-powered (needs Ollama, GB-scale). Existing tiny classifiers do one job. **No model does all of them in one sub-5MB forward pass with a multi-head output.**
15
+
16
+ ### Deliverables
17
+ - HuggingFace model (Apache-2.0) with 4 Matryoshka tiers βœ… (ONNX exported)
18
+ - Python CLI (`picotype`) βœ…
19
+ - Gradio Space app βœ… (`gradio_app.py` β€” label lists fixed Jun 18)
20
+ - Python MCP server βœ… (`model/pico_type/mcp_server.py`)
21
+ - Rust MCP server βœ… (`crates/picotype-mcp/`)
22
+ - pytest smoke tests βœ… (`tests/test_smoke.py`)
23
+ - HF model card βœ… (`MODEL_CARD.md`)
24
+ - Badge'd README βœ…
25
+ - Rust CLI (`crates/picotype/`) βœ…
26
+ - Chrome extension scaffold + icons βœ… (MV3)
27
+ - PyPI package `pico-type` v0.1.3 βœ… (`pip install pico-type`)
28
+ - arXiv paper β€” in progress (draft with final numbers)
29
+ - Raycast/Alfred/VSCode extensions β€” pending
30
+
31
+ ---
32
+
33
+ ## 2. Locked-in architecture (from `docs/PLAN.md` Β§2)
34
+
35
+ ```
36
+ Inputs (≀1024 UTF-8 bytes, masked/padded)
37
+ β”‚
38
+ β”œβ”€ ByteEmbed (256 β†’ 96d, learned)
39
+ β”‚
40
+ β”œβ”€ 3Γ— Conv1D block (kernel 3, 5, 7) + GELU + residual β†’ 192d
41
+ β”‚
42
+ β”œβ”€ 2Γ— BiAttention block (d=192, 4 heads, RoPE ΞΈ=500k)
43
+ β”‚
44
+ β”œβ”€ Pool = [mean β€– max β€– std] β†’ 576d shared trunk
45
+ β”‚
46
+ └─ 7 Matryoshka heads (Linear at 16/64/192/576 dim slices)
47
+ β”œβ”€ h_coarse (12) β€” primary type
48
+ β”œβ”€ h_modality (8) β€” textual / binary-image / …
49
+ β”œβ”€ h_subtype (24) β€” JSON/YAML/CSV/HTML… (if coarse ∈ {config, markup, data})
50
+ β”œβ”€ h_code_lang (62) β€” if coarse=code, + "undetected" fallback
51
+ β”œβ”€ h_text_lang (30) β€” if coarse=text, + "undetected" fallback
52
+ β”œβ”€ h_file_mime (90) β€” if coarse ∈ {image, file, archive, binary} or modality=binary_*, + "undetected"
53
+ └─ h_risk (6) β€” sigmoid multi-label: api_key, jwt, ssh_key, password, email, phone
54
+ ```
55
+
56
+ ### Tier matrix
57
+ | Tier | Dim slice | Params (actual) | INT8 size |
58
+ |---|---|---|---|
59
+ | `pico-type-tiny` | 16 | 1.43M | 1.37 MB |
60
+ | `pico-type-small` | 64 | 1.45M | 1.38 MB |
61
+ | `pico-type-base` | 192 | 1.48M | 1.41 MB |
62
+ | `pico-type-pro` | 576 | 1.56M | 1.49 MB |
63
+
64
+ Actual sizes came in **under** the plan's targets (0.5/1.5/3.5/8 MB INT8). Trunk dominates; if we need to shrink tiny further, reduce `trunk_dim` 192β†’128 or `num_attn_layers` 2β†’1.
65
+
66
+ ### Output schema (always returned, with confidence)
67
+ ```json
68
+ {
69
+ "coarse": "code",
70
+ "modality": "textual",
71
+ "subtype": null,
72
+ "code_language": "python",
73
+ "text_language": null,
74
+ "file_mime": null,
75
+ "risk_flags": [],
76
+ "confidence": 0.94,
77
+ "modality_confidence": 0.91,
78
+ "model_tier": "base"
79
+ }
80
+ ```
81
+
82
+ ---
83
+
84
+ ## 3. Repo layout
85
+
86
+ ```
87
+ classifier-model/
88
+ β”œβ”€β”€ .venv/ # Python 3.11 venv (torch, numpy<2, safetensors, pyyaml)
89
+ β”œβ”€β”€ .git/
90
+ β”œβ”€β”€ checkpoints/ # best.pt, ONNX models, eval results
91
+ β”œβ”€β”€ docs/
92
+ β”‚ └── PLAN.md # Full architecture plan
93
+ β”œβ”€β”€ gradio_app.py # Gradio Space app
94
+ β”œβ”€β”€ model/
95
+ β”‚ └── pico_type/
96
+ β”‚ β”œβ”€β”€ __init__.py # re-exports public API
97
+ β”‚ β”œβ”€β”€ labels.py # vocabularies + decode_output
98
+ β”‚ β”œβ”€β”€ arch.py # PicoType model
99
+ β”‚ β”œβ”€β”€ data.py # synthetic generator
100
+ β”‚ β”œβ”€β”€ train.py # multi-task trainer
101
+ β”‚ β”œβ”€β”€ eval.py # eval harness
102
+ β”‚ β”œβ”€β”€ distill.py # KD pipeline
103
+ β”‚ β”œβ”€β”€ export.py # ONNX export
104
+ β”‚ β”œβ”€β”€ cli.py # Python CLI (picotype)
105
+ β”‚ └── mcp_server.py # MCP server (stdio)
106
+ β”œβ”€β”€ tests/
107
+ β”‚ └── test_smoke.py # pytest smoke tests
108
+ β”œβ”€β”€ spaces/
109
+ β”‚ └── requirements.txt # HF Space dependencies
110
+ β”œβ”€β”€ MODEL_CARD.md # HF model card
111
+ β”œβ”€β”€ README.md # badge'd README
112
+ β”œβ”€β”€ LICENSE # Apache-2.0
113
+ β”œβ”€β”€ pyproject.toml
114
+ └── walkthrough.md # this file
115
+ ```
116
+
117
+ ---
118
+
119
+ ## 4. Public API (current)
120
+
121
+ ```python
122
+ from model.pico_type import (
123
+ PicoType, PicoTypeConfig, TIERS, # model
124
+ COARSE_LABELS, MODALITY_LABELS, SUBTYPE_LABELS,
125
+ CODE_LANG_LABELS, TEXT_LANG_LABELS,
126
+ FILE_MIME_LABELS, RISK_LABELS,
127
+ UNDETECTED, decode_output, # labels + decoder
128
+ )
129
+ from model.pico_type.arch import encode_bytes # bytes β†’ (tokens, mask) tensors
130
+ ```
131
+
132
+ ### Smoke test
133
+ ```bash
134
+ source .venv/bin/activate
135
+ python -m model.pico_type.arch
136
+ ```
137
+
138
+ ---
139
+
140
+ ## 5. Key invariants (DO NOT break these)
141
+
142
+ These are tied to the locked-in plan and the on-disk checkpoints. Changing them means re-training + re-publishing.
143
+
144
+ ### Vocab sizes (asserted in `labels.py`)
145
+ | Head | Size | Plan target |
146
+ |---|---|---|
147
+ | coarse | 12 | 12 βœ“ |
148
+ | modality | 8 | 8 βœ“ |
149
+ | subtype | 24 | 24 βœ“ |
150
+ | code_lang | 62 | 62 βœ“ |
151
+ | text_lang | 30 | 30 βœ“ |
152
+ | file_mime | 90 | 90 βœ“ |
153
+ | risk | 6 | 6 βœ“ |
154
+
155
+ ### Gating (enforced in `decode_output`, must also be enforced in training loss)
156
+ - `subtype` is only valid when `coarse ∈ {config, markup, data}`
157
+ - `code_lang` is only valid when `coarse == code`
158
+ - `text_lang` is only valid when `coarse == text`
159
+ - `file_mime` is only valid when `coarse ∈ {image, file, archive, binary}` or `modality` starts with `binary_`
160
+ - `risk` is **always** valid (multi-label)
161
+
162
+ ### `UNDETECTED` behavior
163
+ - For `code_lang` / `text_lang` / `file_mime`: the model has N logits (62/30/90), no separate "undetected" class. If `max_softmax < undetected_threshold` (default 0.4), the decoder returns `"undetected"` instead of the argmax label.
164
+ - For `risk`: per-class sigmoid; class is flagged if `sigmoid(logit) β‰₯ risk_threshold` (default 0.5).
165
+
166
+ ### Matryoshka slicing
167
+ The shared trunk emits a 576d vector. Each `MatryoshkaHead` slices `x[..., :tier_dim]` then applies its tier-specific `nn.Linear(tier_dim, num_classes)`. All 4 tier linears live in the model (so a single checkpoint contains all tiers); at inference, **only the chosen tier's linears are loaded** β€” `parameter_count(tier)` reflects this.
168
+
169
+ ### Byte input
170
+ - `0` is the pad byte (matches the 0th row of the embedding)
171
+ - `max_bytes` default 1024
172
+ - Inputs longer than `max_bytes` are **truncated** (not rejected)
173
+ - Mask is 1 for real bytes, 0 for pad β€” passed to attention and pool
174
+
175
+ ---
176
+
177
+ ## 6. Environment
178
+
179
+ ```bash
180
+ # venv already created at .venv
181
+ source .venv/bin/activate
182
+ python --version # 3.11
183
+ python -c "import torch, numpy, safetensors, yaml; print('ok')"
184
+ ```
185
+
186
+ **Python 3.14 was tried first but has no torch wheels β€” we use Python 3.11.** This is documented because the failed attempt is in the opencode session history.
187
+
188
+ ---
189
+
190
+ ## 7. What's done βœ…
191
+
192
+ ### Training, Deployment & Publishing
193
+ - **Training**: 1700 synthetic steps β†’ 5000 synthetic steps (eval_loss 1.61) β†’ **4000 real-data fine-tuning steps** (2 rounds: 10 samples/lang, then 25 samples/lang from GitHub). Eval loss **1.9848** after final round. best.pt updated.
194
+ - **Eval results (1000 samples, base tier, 5.6ms)**:
195
+ - coarse 100%, modality 100%, subtype **98.4%**, code_lang **53.9%**, text_lang **100%**, file_mime 100%, risk mAP 100%
196
+ - code_lang improved **+9.9%** (43.96% β†’ 53.85%) via real GitHub code samples
197
+ - **ONNX export**: All 4 tiers re-exported from best.pt (~207-209 KB on disk with external data, opset 18).
198
+ - **HF Model**: `huggingface.co/eulogik/pico-type` β€” ONNX models at root level + checkpoints/ directory, model card, paper scaffold.
199
+ - **HF Space**: `huggingface.co/spaces/eulogik/pico-type` β€” Gradio app **label lists fixed** (Jun 18 2026). Root cause: `gradio_app.py` had different label ordering than `labels.py` (text_lang had `"ar","hi"` instead of `"id","ms"`; file_mime was completely different set). Also fixed `np.bool_`β†’`bool` for NumPy 2.x compat (Space runs Python 3.13).
200
+ - **PyPI**: `pico-type` v0.1.3 published at https://pypi.org/project/pico-type/0.1.3/.
201
+ - **GitHub**: `github.com/eulogik/pico-type` β€” `main` branch, CI passes.
202
+
203
+ ### Known Training Issues
204
+ - **MPS OOM**: batch_size 64 causes MPS OOM (19+ GiB allocated). Fixed by reducing to batch_size=16 and `train_tiers=('base',)`.
205
+ - **MPS graph cache**: Writes to system `/tmp`, was filling disk when free space <1GB. ~9GB now available, OK.
206
+ - **code_lang stuck at ~44% on synthetic-only**: Real-data fine-tuning pushed it to **53.9%** with 25 samples/lang from GitHub. Further improvement likely needs more diverse real data (error messages, SQL, HTML, binary headers).
207
+
208
+ ### HF Space fix (Jun 18)
209
+ - **Root cause**: `gradio_app.py` had different label list ordering than `model/pico_type/labels.py`. Model outputs numeric logits at specific indices, but Space app decoded them with wrong label lists:
210
+ - `text_lang`: had `"ar","hi"` at positions 26-27 instead of `"id","ms"`
211
+ - `file_mime`: completely different set and ordering (started with `"text/html"` instead of `"application/pdf"`)
212
+ - **Also fixed**: `np.bool_`β†’`bool` (removed in NumPy 2.x, needed for Space's Python 3.13)
213
+ - **Also fixed**: Removed unused `pathlib` import, removed `numpy<2` pin from Space requirements
214
+ - New ONNX files copied to model repo root level (Space downloads from root, not `checkpoints/` directory)
215
+
216
+ ### `model/pico_type/labels.py`
217
+ - All 7 vocabularies (sizes match plan exactly, asserted at import time)
218
+ - `decode_output(logits, tier, undetected_threshold, risk_threshold)` β€” respects all gating rules, applies UNDETECTED threshold
219
+ - `HEAD_NUM_CLASSES` dict for heads that need to query class counts
220
+ - `label_for(head, idx)` helper
221
+ - Constants: `UNDETECTED`, `ALL_HEADS`, `SUBTYPE_GATED_BY`, `CODE_LANG_GATED_BY`, `TEXT_LANG_GATED_BY`, `FILE_MIME_GATED_BY`
222
+
223
+ ### `model/pico_type/arch.py`
224
+ - `PicoTypeConfig` dataclass with all hyperparams
225
+ - `ByteEmbed` β€” `nn.Embedding(256, 96)` init normal std=0.02
226
+ - `ConvBlock` β€” `Conv1d β†’ LayerNorm β†’ GELU β†’ Dropout`, residual via 1Γ—1 projection when dims change
227
+ - `RotaryPosEmb` β€” precomputed cos/sin cache, auto-grows if seq exceeds cache
228
+ - `AttnBlock` β€” pre-norm, fused QKV, RoPE on Q/K, `F.scaled_dot_product_attention`, MLP w/ 4Γ— expansion
229
+ - `Pool` β€” `mean β€– max β€– std` over masked positions (handles padding correctly)
230
+ - `MatryoshkaHead` β€” `nn.ModuleDict` of `nn.Linear` per tier
231
+ - `PicoType` β€” top-level model
232
+ - `encode_bytes(data, max_len, pad)` β€” `bytes β†’ (LongTensor[B, L], LongTensor[B, L])`
233
+ - `smoke_test()` β€” instantiates model, runs a forward, returns param counts
234
+ - `__main__` block runs the smoke test
235
+ - **NaN fix in AttnBlock**: `F.scaled_dot_product_attention` with a boolean mask where all entries are False (sample has no padding) produces NaN on CPU. Fixed by converting to float (`-inf` for masked positions, 0 for valid) and guarding with `mask.all().item()`. See `AttnBlock.forward` for the guard.
236
+
237
+ ### `model/pico_type/data.py`
238
+ - `SyntheticGenerator(seed)` β€” generates one balanced sample at a time from 11 buckets: code, text, config, markup, data, link, error, image, file, secret, archive, binary
239
+ - 11 generator methods (`_gen_code`, `_gen_text`, `_gen_config`, `_gen_markup`, `_gen_data`, `_gen_link`, `_gen_error`, `_gen_image`, `_gen_file`, `_gen_secret`, `_gen_archive`, `_gen_binary`)
240
+ - `Sample` dataclass with `data: bytes`, label fields (int for single-label heads, `list[int]` for risk), `IGNORE_INDEX = -100` for gated heads
241
+ - `SyntheticDataset(generator, size)` β€” wraps generator for `DataLoader` compatibility
242
+ - Code templates for all 62 languages across 18 syntax groups (Python-like, C-like, JS-like, Lisp-like, etc.) β€” uses `re.sub` with `${kind}` placeholders
243
+ - Word lists for all 30 text languages
244
+ - Binary magic-byte headers for PDF, ZIP, GZIP, ELF, SQLite, Parquet, TIFF, PNG, JPEG, WASM, DEB, TTF, plus archive formats (7z, RAR, TAR, XZ, BZ2)
245
+ - `_detect_risk` runs on text samples (AWS key, JWT, SSH key, password detection)
246
+ - `label_counts()` returns class distribution for debugging
247
+ - `smoke_test()` generates 500 samples and prints coverage per head
248
+
249
+ ### `crates/picotype-mcp/` (new)
250
+ - Rust MCP server crate implementing JSON-RPC 2.0 over stdio transport
251
+ - Same protocol as Python MCP server: `initialize` handshake + `tools/call` for `classify` and `classify_file`
252
+ - Uses `ort` (ONNX Runtime) for inference, respects `PICOTYPE_MODEL_DIR` and `PICOTYPE_TIER` env vars
253
+ - Tested and working: builds in ~30s, passes `initialize` and `tools/call` requests
254
+
255
+ ### `extensions/chrome/icons/` (new)
256
+ - 16x16, 48x48, 128x128 PNG icons with indigo rounded-square design and "P" letter
257
+ - Resolves missing icons referenced in `manifest.json`
258
+
259
+ ### Training update (synthetic)
260
+ - Continued training from scratch (no prior checkpoints available) for 5000 steps
261
+ - Config: lr=5e-4, warmup=200, batch_size=16 (MPS), train_size=20000, eval_size=500
262
+ - Eval loss improved: 31.97 β†’ 28.52 (step 0) β†’ 1.97 (step 2500) β†’ **1.61 (step 5000)**
263
+ - Per-head results (step 5000, synthetic-only): coarse=100%, modality=100%, subtype=98%, code_lang=44%, text_lang=97.5%, file_mime=100%, risk=100%
264
+ - code_lang stuck at ~44% β€” synthetic code templates too limited; confirmed need for real data
265
+ - Saved best.pt, final.pt, step_{500,1000,...,4500}.pt in checkpoints/
266
+
267
+ ### Real-data fine-tuning
268
+ - `model/pico_type/realdata.py` β€” fetches real source code from GitHub Search API per language
269
+ - `model/pico_type/train_real.py` β€” mixed real+synthetic training (30% real), lr=3e-4, batch=16
270
+ - **Round 1** (10/lang, 2000 steps): code_lang 44%β†’**43.96%** (marginal)
271
+ - **Round 2** (25/lang, ~1500 samples, 2000 steps): code_lang **43.96%β†’53.85%** βœ…
272
+ - Final eval (1000 samples, best.pt): coarse=100%, modality=100%, subtype=98.4%, code_lang=53.9%, text_lang=100%, file_mime=100%, risk=100%, inference=5.6ms
273
+ - 5 languages with 0 GitHub results: fsharp, lisp, vim, fortran, vb (unsupported search names)
274
+
275
+ ### Real-data fine-tuning (new)
276
+ - `model/pico_type/realdata.py` β€” fetches real source code from GitHub Search API per language, builds mixed datasets
277
+ - `model/pico_type/train_real.py` β€” fine-tuning loop mixing real code + synthetic data (30% ratio), lower LR (3e-4)
278
+ - **Round 1**: 10 samples/language (423 total), 2000 steps β€” code_lang 41.7%β†’**43.96%**
279
+ - **Round 2**: 25 samples/language (~1500 total), 2000 steps β€” code_lang **43.96%β†’53.85%**
280
+ - GitHub token used, 0.5s delay/request to respect rate limits
281
+ - 5 languages returned 0 samples: fsharp, lisp, vim, fortran, vb (unsupported GitHub search names)
282
+
283
+ ### Real-world eval
284
+ - 14 hand-curated samples across 10 coarse categories
285
+ - Overall accuracy: 71.4% (10/14)
286
+ - Correct: python, js, JSON, text, link, password, AWS key, SSH key, ZIP, bash
287
+ - Wrong: SQL (β†’text), HTML (β†’code), error trace (β†’text), PNG header (β†’config)
288
+ - Confirms synthetic data gap: SQL, HTML, errors, and binary headers need real training data
289
+
290
+ ### PyPI publish
291
+ - Bumped version from 0.1.0β†’0.1.2 (0.1.1 already existed)
292
+ - Built and uploaded to PyPI: `pico-type==0.1.2` at https://pypi.org/project/pico-type/0.1.2/
293
+ - **v0.1.3**: Published with best.pt (53.9% code_lang), updated eval results, paper scaffold
294
+
295
+ ### `model/pico_type/__init__.py`
296
+ - Re-exports the public API (already present in the repo when we recovered)
297
+
298
+ ### `model/pico_type/train.py`
299
+ - `TrainConfig` dataclass β€” lr, warmup, total_steps, batch_size, grad_clip, per-head weights, etc.
300
+ - `collate_fn(batch)` β€” pads variable-length samples, creates `input_ids`, `attention_mask`, `labels` dict
301
+ - `MultiTaskLoss(weights)` β€” CE per head (ignore_index=-100 for gated heads) + BCE for risk. Skips any head with zero valid labels in batch (returns 0.0). Applies per-head weights (coarse=3.0, modality=2.0, code_lang=1.5, text_lang=1.5, others=1.0).
302
+ - `get_lr(step, config)` β€” linear warmup β†’ cosine decay
303
+ - `train(config)` β€” full training loop:
304
+ - `SyntheticGenerator` + `SyntheticDataset` for train/eval
305
+ - AdamW, separate param groups (trunk w/ weight_decay, Matryoshka heads w/o)
306
+ - BF16 AMP (CUDA) or FP32 (CPU/MPS)
307
+ - Gradient clipping at 1.0
308
+ - Logs every `log_every` steps, eval every `eval_every`, save every `save_every`
309
+ - Saves `best.pt` (lowest eval loss), `final.pt`, plus periodic `step_{N}.pt`
310
+ - `load_checkpoint(path, model, optimizer)` β€” loads state dict
311
+ - **Known issues fixed**: NaN in SDP with all-valid mask (use float `-inf` instead of boolean mask); NaN from CE on all-ignore labels (skip head); Python 3.14 has no torch wheels (use 3.11)
312
+
313
+ ### `model/pico_type/eval.py`
314
+ - `EvalConfig` dataclass β€” checkpoint, tier, eval_size, batch_size
315
+ - `evaluate(config)` β€” generates synthetic eval set, runs forward pass for all 7 heads
316
+ - Per-head `HeadMetrics`: accuracy, per-class precision/recall/F1, confusion matrix
317
+ - `RiskMetrics`: per-class average precision (sklearn-free implementation)
318
+ - `run_eval()` β€” CLI: `python -m model.pico_type.eval --eval-size 1000 --checkpoint checkpoints/best.pt`
319
+ - `_average_precision(y_true, y_scores)` β€” area under PR curve via trapezoidal rule
320
+
321
+ ### Hindi support + v0.1.4 (Jun 18)
322
+ - Replaced `ms` (Malay, index 29) with `hi` (Hindi) in `TEXT_LANG_LABELS`. Added Hindi word list to `_TEXT_WORDS` in `data.py`.
323
+ - Fine-tuned 800 steps β€” Hindi text now correctly detected as `text_lang=hi`.
324
+ - code_lang improved to **61.3%** on synthetic eval after Hindi fine-tune (unexpected side benefit).
325
+ - Published as **v0.1.4** at https://pypi.org/project/pico-type/0.1.4/
326
+
327
+ ### Diverse synthetic training + real-world eval improvement (Jun 18)
328
+ - Created diverse generator: patched `_gen_config`, `_gen_markup`, `_gen_text`, `_gen_error` with realistic patterns (markdown with code blocks, Python tracebacks, JS errors, nested YAML, env vars, tech prose, emails, conversations).
329
+ - **Real-world eval accuracy**: 22.7% β†’ **52.4%** (2.3x better, 11/21 correct)
330
+ - coarse classification fixed for config, error, text, markup
331
+ - code_lang still "undetected" for real snippets (Python `def`, JS `function`, C `#include`, etc.)
332
+ - Key change: coarse head weight increased to **8.0** (was 3.0) to prevent "default to code" behavior
333
+ - Synthetic eval regression: coarse 100%β†’99.8%, code_lang 61.3%β†’38.7%, text_lang 100%β†’98.8% (expected β€” harder data)
334
+
335
+ ### ONNX re-export + v0.1.5 (Jun 18)
336
+ - Re-exported all 4 ONNX tiers from best.pt (diverse-trained checkpoint)
337
+ - Published as **v0.1.5** at https://pypi.org/project/pico-type/0.1.5/
338
+ - ONNX files pushed to HF model repo root, Space redeployed
339
+
340
+ ### Quantization attempts (deferred)
341
+ - **INT8**: ONNX shape inference fails β€” `[ShapeInferenceError] Inferred shape (192) vs (12)`. Multi-head architecture (shared 192-dim pooled vector β†’ 7 linear layers with different output dims) confuses shape inference.
342
+ - **FP16**: `onnxconverter_common.float16` succeeds but produces type mismatch errors in ONNX Runtime. Could not resolve with `op_block_list`.
343
+
344
+ ### Fresh training from scratch (in progress)
345
+ - Started training from scratch with diverse generator + real GitHub data (30% ratio) + high coarse weight (8.0)
346
+ - 4000 steps, batch_size=16, 15 samples/lang from 62 languages
347
+ - Located at: `checkpoints/fresh/train.log`
348
+
349
+ ---
350
+
351
+ ## 8. What's next (from plan Β§3–§6, in order)
352
+
353
+ | # | File | What it does | Status |
354
+ |---|---|---|---|---|---|
355
+ | 1 | `data.py` | Synthetic generator + dataset for multi-head training. 11 buckets, all 12 coarse classes, code/word templates for all 62/30 langs. | βœ… **done** |
356
+ | 2 | `train.py` | Multi-task trainer. AdamW + cosine, bf16, per-head loss weighting, gradient clipping, checkpoint save/load. resume_from field for continuing training. | βœ… **done** |
357
+ | 3 | `eval.py` | Eval harness: per-head accuracy/PRF1, confusion matrix, risk AP, inference timing. CLI entry point. | βœ… **done** |
358
+ | 4 | `distill.py` | KD from per-head teachers (deberta-v3-small, CodeBERTa-lang-id, xlm-roberta-lang-detect). T=2.0, Ξ±=0.7. | βœ… **done** |
359
+ | 5 | `export.py` | ONNX export (opset 18), int8, tract, gguf. | βœ… **done** |
360
+ | 6 | `cli.py` | Python CLI (`picotype`) β€” stdin/file/clipboard input β†’ ONNX inference β†’ JSON output | βœ… **done** |
361
+ | 7 | `mcp_server.py` | MCP server (stdio transport) for Claude/Cursor/VSCode | βœ… **done** |
362
+ | 8 | `gradio_app.py` | Gradio Space app for HF Spaces | βœ… **done** |
363
+ | 9 | `tests/test_smoke.py` | pytest smoke tests (8 tests: arch, data, ONNX, CLI, labels) | βœ… **done** |
364
+ | 10 | `MODEL_CARD.md` | HuggingFace model card with eval results | βœ… **done** |
365
+ | 11 | `README.md` | Overhauled with badges, perf table, deploy links | βœ… **done** |
366
+ | 12 | `spaces/requirements.txt` | Dependencies for HF Space deployment | βœ… **done** |
367
+ | 13 | HF Model + Space | Published to huggingface.co/eulogik/pico-type (model) and /spaces/eulogik/pico-type (Space) | βœ… **done** |
368
+ | 14 | PyPI publish | pico-type v0.1.0β†’v0.1.2β†’v0.1.3 on PyPI | βœ… **done** |
369
+ | 15 | `crates/picotype/` | Rust CLI w/ ONNX runtime. | βœ… **done** |
370
+ | 16 | `crates/picotype-mcp/` | Rust MCP server (stdio). | βœ… **done** |
371
+ | 17 | `extensions/chrome/` | Chrome MV3 scaffolded + icons created. Raycast, Alfred, VSCode β€” pending. | partial |
372
+ | 18 | `paper/` | arXiv LaTeX scaffolded, final numbers updated (`paper/main.tex`). | in progress |
373
+ | 19 | Training | 1700 β†’ **5000 synthetic steps** (loss 1.61) β†’ **4000 real-data steps** (25/lang, code_lang 53.9%). | βœ… **done** |
374
+ | 20 | ONNX re-export | All 4 tiers re-exported from best.pt (post real-data fine-tune). | βœ… **done** |
375
+ | 21 | Real-data pipeline | `realdata.py` + `train_real.py` β€” GitHub code fetcher, mixed dataset, fine-tuning loop. | βœ… **done** |
376
+ | 22 | HF Space fix | Label lists synced to `labels.py`, `np.bool_`β†’`bool` for NumPy 2, requirements updated. | βœ… **done** |
377
+ | 23 | INT8 quantization | Shape inference issue with multi-head architecture β€” needs graph fix. | pending |
378
+ | 24 | HuggingFace Hub push | ONNX, best.pt, eval results, paper, README all pushed. | βœ… **done** |
379
+
380
+ ---
381
+
382
+ ## 9. Open decisions (from plan Β§8 β€” still open)
383
+
384
+ 1. **HF handle**: `pico-type` (dash) for model card, `picotype` (no dash) for CLI binary. **Proposed, not confirmed.**
385
+ 2. **Tier naming**: `tiny`/`small`/`base`/`pro` (matches Sentence-Transformers convention). **Proposed, not confirmed.**
386
+ 3. **License**: Apache-2.0 (matches CommonLingua base). **Proposed, not confirmed.**
387
+ 4. **arXiv target**: `cs.CL` (primary) + `cs.LG`. Co-authors: open question.
388
+ 5. **Tagline**: *"One tiny model, one forward pass, every clipboard."* **Proposed, not confirmed.**
389
+
390
+ ---
391
+
392
+ ## 10. Quick recipes
393
+
394
+ ### Instantiate the model
395
+ ```python
396
+ from model.pico_type import PicoType, PicoTypeConfig
397
+
398
+ cfg = PicoTypeConfig(max_bytes=1024)
399
+ model = PicoType(cfg)
400
+ print(model.tier_sizes()) # {tiny: 1434344, small: 1445480, base: 1475176, pro: 1564264}
401
+ ```
402
+
403
+ ### Run a forward + decode
404
+ ```python
405
+ from model.pico_type.arch import encode_bytes
406
+ from model.pico_type.labels import decode_output
407
+
408
+ model.eval()
409
+ x, mask = encode_bytes(b'def hi(): return 1', max_len=1024)
410
+ with torch.no_grad():
411
+ logits = model(x, mask, tier='base')
412
+ out = decode_output(logits, tier='base')
413
+ # {'coarse': ..., 'modality': ..., 'subtype': ..., 'code_language': ...,
414
+ # 'text_language': ..., 'file_mime': ..., 'risk_flags': [...],
415
+ # 'confidence': ..., 'modality_confidence': ..., 'model_tier': 'base'}
416
+ ```
417
+
418
+ ### Convert to a single-tier checkpoint (for release)
419
+ Use `parameter_count(tier)` to get the param count for that tier. To build a release checkpoint, you would: train full model β†’ for each tier, save only `trunk.*` + `heads.*.linears.{tier}.*` β†’ export.
420
+
421
+ ---
422
+
423
+ ## 11. Session history (why this file exists)
424
+
425
+ The user was working on this project in opencode. The session (`ses_16dd3d39fffer9xnBUQYBS3u5z` β€” *"Tiny model for clipboard content classification"*) **crashed mid-execution** while writing `model/pico_type/arch.py`. Opencode had to be re-installed; the user thought files might be lost.
426
+
427
+ **They weren't.** The full session data was recovered intact from:
428
+ - `~/.local/share/opencode/opencode.db` (262MB SQLite)
429
+ - `~/.local/share/opencode/storage/session_diff/ses_16dd3d39fffer9xnBUQYBS3u5z.json` (128MB JSON)
430
+ - The trash (`~/.Trash/opencode`) only contained **opencode Desktop app data** (different product, irrelevant to the CLI session).
431
+
432
+ From the recovery we:
433
+ - Extracted the full plan β†’ `docs/PLAN.md`
434
+ - Re-wrote `arch.py` (the file the crashed session was aborting on) and `labels.py` (never written in original session)
435
+ - Wrote this `walkthrough.md` so the next agent/harness has full context
436
+
437
+ ### User's 3 original prompts
438
+ 1. *"I want to build a really tiny model which categorises/classifies content. eg if we pass clipboard copied content, it should classify that as text, image, rich text, link, code(with language name), file with file type etc. Deep research the existing models on huggingface etc. go through new research papers and find an opportunity / gap to make this model in the most efficient manner plus make it really popular. feel free to suggest anything"*
439
+ 2. *"continue asking questions and ahead. btw, the text language should be identified too like code language. if language not detected, it should simply return text + undetected or code undetected"*
440
+ 3. *"go"*
441
+
442
+ ### Where the original session was at crash
443
+ - 23 messages, 95 parts
444
+ - Plan had been finalized (10KB markdown)
445
+ - Approved with "go"
446
+ - Switched to `build` agent
447
+ - Set up Python 3.11 venv, installed torch/numpy/safetensors/pyyaml
448
+ - Created the full directory tree (`model/pico_type/`, `model/configs/`, `crates/picotype/`, etc.)
449
+ - Wrote `model/pico_type/__init__.py` βœ…
450
+ - **Wrote `arch.py`** (the file the user originally saw being written) β€” wait, the original session was *aborted* on the arch.py write. The `__init__.py` is in the repo. We re-wrote arch.py from scratch using the plan + the small preview from the original write tool input.
451
+
452
+ ---
453
+
454
+ ## 12. Anti-patterns to avoid (learned)
455
+
456
+ - **Python 3.14 has no torch wheels.** Always use the venv's Python 3.11.
457
+ - **Don't double-count Matryoshka head params** when iterating `named_modules()` β€” `ModuleDict` is visited separately from its children. Use `named_parameters()` and check `.linears.{tier}.` in the name.
458
+ - **Gating heads are not always-on.** `subtype`, `code_lang`, `text_lang`, `file_mime` must mask their loss when not applicable. The decoder handles this; the trainer must too.
459
+ - **`UNK`/undetected is a decoder-side decision**, not a model class. The model has N logits; the decoder thresholds.