Qwen/Qwen3.5-2B, one of the supported base models.
The trainer and sampler bill for their GPUs while they run; the final step shuts them down.
Prerequisites
- A workspace API key, set as
BASETEN_API_KEY. - Python 3.12+ and uv to install the SDK and run the script.
- The Baseten CLI to manage and shut down the run.
Install
Installbaseten-loops into a uv project. Create one first if you don’t have it:
uv run python train_loops.py:
Provision a trainer
The SDK creates the run, so you don’t need to runtruss loops push first.
A Loops session pairs a trainer (forward, backward, and optimizer steps) with a sampler (generates from current weights). Constructing a ServiceClient and calling create_lora_training_client() provisions the trainer and returns a TrainingClient; the sampler comes later, when you first create a sampling client. The call blocks until the trainer is ready, which takes several minutes for a small base model like this one and can reach tens of minutes for the largest supported models. The SDK gives up waiting after an hour.
Replace the contents of train_loops.py with the provision step:
train_loops.py
Run a training round trip
The smallest complete round trip is one forward pass, one backward pass, one optimizer step, and one weight save. The block below mirrors the canonical supervised fine-tuning (SFT) example: it tokenizes a prompt-and-answer pair, masks the prompt positions from the loss, runs the round trip, and saves a named checkpoint. Append totrain_loops.py:
train_loops.py
forward_backward() is the first training operation you submit after provisioning. save_weights_for_sampler() publishes a sampler checkpoint under sampler_weights/ that you can deploy to inference. This checkpoint omits optimizer state, so you can’t resume training from it; use save_state() when you need a resumable checkpoint.
Sample from the tuned weights
You can generate from the checkpoint you saved without deploying anything.create_sampling_client() takes the bt:// URI that save_weights_for_sampler() returned and binds a SamplingClient to those weights. The first call on a run provisions the run’s paired sampler, so your first sample() waits through its cold start; later calls reuse it. To hide that cold start behind training steps, see Overlap trainer and sampler startup. Append to train_loops.py:
train_loops.py
step-1 weights your trainer published seconds earlier, without a restart or a deploy step in between. In a longer run, this same call is how you evaluate checkpoints mid-training.
List checkpoints
Everysave_weights_for_sampler() call creates a checkpoint. The bound TrainingClient lists them with list_checkpoints(), no arguments needed. Append to train_loops.py:
train_loops.py
transformers about PyTorch being unavailable and from the Hugging Face Hub about unauthenticated requests. Both are harmless here: the client only uses transformers for tokenization, and the tokenizer download works without a token.
For resource management outside the training process, use the Baseten CLI or REST API. Use the run_id your script printed when provisioning.
- Baseten CLI
- REST API
null.
REST API output
id and the checkpoint name. Use the ID to inspect the checkpoint files:
- Baseten CLI
- REST API
Output
next_page_token back as the page_token query parameter until next_page_token is null; each page contains up to 1,000 files by default.
To download the weight files from either S3 or a Baseten volume, call download_checkpoint() with the globally unique id value as the checkpoint_id argument:
training_client isn’t in scope, construct a ServiceClient() and call the same method on it. To work with presigned URLs for an S3-backed checkpoint instead, call get_checkpoint_archive_url().
Skip the cold start on re-runs
Your first run provisioned a trainer and sampler. The second run doesn’t have to. Grab therun_id your script printed (run_id=run123 in the example output above), point the next run at it, and Loops reuses the same trainer and sampler:
reuse_from_session_id, LOOPS_REUSE_FROM_SESSION_ID) still works but is deprecated in favor of the run ID. From the HTTP API, send reuse_from_session_id in the body of POST /v1/loops/trainers or POST /v1/loops/samplers.
Reuse is best-effort. If the prior trainer is stopped, failed, or unhealthy, Loops provisions a fresh one and your script still runs.
Shut down the session
The trainer and sampler bill for their GPUs until you deactivate them. While aTrainingClient is open, it keeps the training session warm with a background health-check ping, so close the client (or exit the script) when you finish training. Then check what’s live and shut it down:
- Baseten CLI
- REST API
baseten loops run list automatically excludes runs with status.name set to INACTIVE and shows one row per remaining run, with its run ID, base model, and status. The REST API returns inactive runs too, so the example uses jq to apply the same filter. Both interfaces deactivate the trainer and its paired sampler while preserving your checkpoints. You can still list checkpoints, fetch their files, and deploy them to inference after shutdown.
Next steps
- Deploy a checkpoint: Serve
step-1as a dedicated inference deployment and call it over the OpenAI-compatible route. - Train on your data: Replace the single example with a batched loop, resumable checkpoints, and mid-training evals.
- Loops concepts: Sessions, trainers, samplers, checkpoints, and how weight sync works.
- Loops API reference: Every HTTP route, for scripting session lifecycles and CI pipelines.