Skip to main content
The scripts are in the repository, not in the installed package. Clone it, then cd recipes/04-train/hosted-loop before running the commands below. Browse this recipe on GitHub.
Four calls from graded rows to a chat completion from the trained model, all on the platform. One key, one A10G run, no GPU of your own. What you will learn: the shape of a train set and a task-disjoint holdout on the platform, what wai.train returns and how to wait on it, what wai.serve gives you back, and how to call the served adapter. The rows are deliberately small; this is the wiring check, not a result. You need WHILEAI_API_KEY (or wai login); no model key, since the rows come from the template writer and a scripted agent.

What each step does

One run of python run.py with the defaults (--seed 1 --budget 96, SFT on Qwen/Qwen3-4B, one epoch): 24 train rows over 7 tasks, and a 72-row holdout because the splitter seeds the held-out side by failure signature first (see below). The loss numbers are one run’s; the platform trainer is not seeded, so yours will differ:
The rows here are small on purpose (a scripted agent, template situations) so the loop finishes in minutes. The loss drop shows the wiring works; it says nothing about the agent. The reply under == call is the same kind of wiring check: it shows the endpoint answers, and yours will differ. Replace scripted_agent and judge with yours, or point data at rows you already graded.

What to know before you run it

  • Only two bases serve. Qwen/Qwen3-4B and microsoft/phi-4. The trainer’s defaults (Qwen2.5-0.5B for SFT, 1.5B for GRPO and DPO) train faster but cannot be hosted; wai.train warns and wai.serve refuses. SFT runs on an A10G and takes about a minute here; GRPO and DPO run on an L40S (--method grpo --steps 10 took 137 s on Qwen3-4B).
  • Cold starts. The serving GPU scales to zero. The first call after idle can take a few minutes; call waits up to fifteen, and a 502, 503 or 504 while the container is still waking is retried inside that window rather than raised.
  • Thinking mode. Qwen3 reasons before it answers unless told not to. call sends chat_template_kwargs: {"enable_thinking": false} so the reply is the answer, not the reasoning.
  • Cost. SFT here is about a minute of A10G, GRPO a few minutes of L40S, and run.training["cost_usd"] says what that came to: an estimate at Modal’s list price (cost_basis names the rate and the day, estimate: A10G at $1.10/h, modal.com/pricing 2026-09-20), so a run this size is a few cents; print(run) shows it as about $0.02 (A10G, 56 s, estimate). Serving bills while the GPU is awake; the endpoint idles back to zero on its own, and rollouts and judge calls are not priced.
  • Holdout. split_pseudo_production moves whole tasks and seeds the held-out side with one task per failure signature first, so on a tiny set (7 tasks here) the holdout ends up larger than the fraction asks. That is fine for a wiring check; a real set has hundreds of tasks.

Where it shows up

run.url is the loss curve and the before/after on the platform. wai.models() lists what the account hosts, wai.get_run(run_id) returns the points, and the dataset cards link to the run. Docs: https://docs.while.ai/api/training
Last modified on September 22, 2026