Skip to main content
A small model that does one job well is cheaper and faster than a big one that does everything. The hard parts are the data and the comparison. Here a large model on the free tier writes the training pairs, a 0.5B model learns them with LoRA on a rented GPU, and the same job serves the base model and the adapter side by side from one vLLM server, so the comparison is fair. A judge on the inference API then scores both on prompts neither saw in training.

Build it in your project

Paste this line into your agent (Claude Code, Codex, Cursor, OpenCode, Cline). It sets up your key, then builds the pipeline in your project:

The code

One file per language. Both read RUNCRATE_API_KEY from the environment; nothing secret is in the code.

What the proof run cost

The Python file end to end: 188 teacher-written pairs (148 train, 40 held out), one GPU job that trained, served and predicted, then the judge. Measured on 2026-10-23 against api.runcrate.ai; costs are what the account was billed, read from each response’s X-LLM-Cost header or usage.cost. Result on the 40 held-out requests: the base model got 0 right (it answered in prose and reached for other tools’ commands), the LoRA got 33 right, all 33 exact. The adapter is 34 MB; a job’s outputs are kept 30 days (this proof’s job was deleted afterwards with its outputs). The TypeScript file was run with --dry-run (data written, archive packed, the job spec printed, nothing submitted) to stay within the proof’s GPU budget; it writes the same recipe.py the Python run used.

Scale it

  • Serve the adapter: start an instance on the Inference template with vllm serve Qwen/Qwen2.5-0.5B-Instruct --enable-lora --lora-modules cli=/workspace/adapter, the adapter on a volume, and an HTTPS link to port 8000.
  • More data: the teacher batches run in parallel; thousands of pairs take a minute.
  • A sweep: params on the job runs one training per learning rate or rank as a group, with max_parallel.
  • Safe retries: max_retries: 1 restarts on a new machine if the first one fails to start.

Gotchas found in the proof run

  • The job spent about 6 of its 8 minutes before the command started: machine boot, the driver, and the template’s first start. Budget a few minutes of start-up into max_runtime_minutes; it counts from the command, not the boot.
  • vLLM 0.11 with --enforce-eager was up in 53 s for a 0.5B model; without it, CUDA graph capture adds time on every start.
  • Free the training model before starting vLLM in the same process tree (del model; torch.cuda.empty_cache()) and give vLLM half the memory (--gpu-memory-utilization 0.5).
  • Several teacher pairs were near-duplicates; deduplicate on the request text before splitting, or the held-out set leaks into training.