Read the full writeup →

Multimodal input · guardrails · LoRA distillation

Pacing plans that are checked before you see them

Photograph a race course map and state a goal time. Local OCR and Claude read the map into a course profile, retrieval adds pacing research, and Claude writes a km-by-km plan. Six checks in plain Python (coverage, split sum, hill plausibility, fade, heat, citations) must pass before the plan is shown; failures go back to the model as sentences. A LoRA fine-tune of Qwen2.5-3B, trained on a laptop, tries to do the same job locally.

CourseProfile · hilly_boston_like42.2 km

One of six hand-built course fixtures in assets/example_courses/, with the plausibility allowance verifier.py applies to each segment.

70 held-out scenarios

Claude teacherzero-shot, 157.7 s per plan90.0%parse75.7%pass all
Qwen2.5-3B + LoRAthis project, 53.7 s per plan95.7%parse0.0%pass all
Qwen2.5-3B baseno fine-tune, 17.5 s per plan55.7%parse0.0%pass all

Fine-tuning taught the student the format: parse success went from 55.7% to 95.7%, and it fails coverage, plausibility and heat on under 5% of plans. Where it falls down is arithmetic: 82.9% of its plans miss the goal by more than 90 seconds, and 91.4% report a fade that doesn't match their own splits. (Test set split by scenario id; all three systems ran zero-shot with no retries on the same prompt.)

The six checks

Training took four attempts

1,458 verifier-passed teacher plans (96.0% of 1,519 attempts) trained an MLX LoRA adapter on a 16 GB Mac. Along the way: a sequence length 2.7 times too short, a memory overflow that printed "loss 0.000" instead of crashing, and three divergences, the last despite a custom gradient-clipping loop. The published adapter is the best checkpoint, about 400 clean iterations at validation loss 0.226.

Training and validation loss for the published LoRA model over 400 iterations; validation loss falls from about 1.67 to about 0.23 by iteration 100 and stays flat.

The full writeup has the per-check table, the training incidents and the limitations.

Code on GitHub Long-form notes