← TinyGPT · docs · devlog · roadmap · speedup
source: docs/specialist_v1_findings.md · view on GitHub ↗

First specialist run — toolcall-v1 findings

Date: 2026-05-31 · Hardware: M5 Pro / 48 GB Goal: Train a tool-calling specialist as the first real Wave 3 artifact + validate the full pipeline.

TL;DR

Pipeline works end-to-end. Model doesn’t yet tool-call. Found and fixed a major LoRA-save bug along the way. Have a baseline to iterate from.

What ran

base:           SmolLM2-135M-Instruct (HF, 134.5M params, ctx=8192)
data:           NousResearch/hermes-function-calling-v1
                11,230 records · 8.4M tokens · 6.5M scored (mask=1)
adapter:        RS-LoRA rank=16 alpha=32, LoRA+ B-LR×16
                921,600 trainable params (0.68%)
training:       2000 steps · batch 1 · max-seq 1024 · lr 1e-4
                pack-mode bucket (4 length buckets)
                483.9 s wall, 4.1 step/s
output:         /tmp/specialist/toolcall-v1.lora (3.5 MB)

What worked

  1. HF model loadtinygpt hf-load /tmp/smollm2 works clean, sample at 64 tok/s with coherent output
  2. Dataset download + schema sniff — hermes JSONL detected and converted correctly (format: sft, confidence 75%)
  3. Training loop — 2000 steps clean, no NaN/diverge, loss bouncy but trending low (final batch 0.007)
  4. LoRA save → reload → sample — adapter persists, loads back via sample --lora, runs at 134 tok/s (vs 154 base)

What didn’t work

On a novel tool-calling prompt (a get_weather tool not in training), the trained model still hallucinates the weather answer rather than emitting <tool_call>. Same behavior as the base.

On a training-distribution prompt (an actual hermes record re-fed), the model emits JSON-schema-like text (continuing the schema fields with "name":, "description":, "enum":) instead of the expected <tool_call>{"name": ..., "arguments": ...}</tool_call> response.

Diagnosis: The model picked up surface format adjacency (JSON chatter, role tokens) but did not learn the task semantics (when to emit tool_call). Likely causes:

Pre-flight: bugs found + fixed during this run

A0: LoRA save bug (FIXED)

Symptom: SFT/DPO/finetune saved adapter as 217 B header-only file, entries:[] empty. Training ran fine but weights were never serialized.

Root cause: SFT.swift defaults useDora=true (curated recipe). --rs-lora/--vera/etc. only set peftVariant — they didn’t turn DoRA off. So makeAdapterLinear() returned DoraLinear instances. Writers cast as? LoraLinear → nil → skip. Trainable param walker handled both classes so the count was always right, masking the bug.

Fix: PEFT-variant flags now auto-disable DoRA (one-line patch in SFT.swift, commit f566023).

Latent regression coverage gap: no save+reload XCTest existed. Worth adding to prevent recurrence.

What to try next, ranked

Option A — train longer + more data (cheapest)

Option B — bigger base

Option C — better prompt match at inference

Option D — task-specific eval harness

Recommendation

Do C + D first (~1 hour combined), then A or B depending on what the eval shows. Don’t grind more compute without knowing whether the LoRA is even being applied at inference correctly.

Reproduction

BIN=/tmp/tinygpt-sft-fix/Build/Products/Release/tinygpt

$BIN sft /tmp/smollm2 \
  --data /tmp/data/hermes.jsonl \
  --out /tmp/specialist/toolcall-v1.lora \
  --template chatml \
  --steps 2000 --batch 1 --max-seq 1024 \
  --rank 16 --alpha 32 --lr 1e-4 --rs-lora \
  --pack-mode bucket --length-bucket 4 --lora-plus-ratio 16

$BIN sample /tmp/smollm2 --lora /tmp/specialist/toolcall-v1.lora \
  --prompt "$(cat /tmp/tool-prompt.txt)" --tokens 100 --temperature 0

What’s now in the backlog