Qwen/Qwen3.5-2B, one of the supported base models.
Prerequisites
- Python 3.12+ and uv: The quickstart uses
uvto install the Loops client and run the training script. - API key: A workspace API key with org access to Loops, exported as
BASETEN_API_KEY.
Loops is in early access. To enable it for your workspace, fill out the signup form.
Install
Installbaseten-loops with the [tinker] extra into a uv project. Create one first if you don’t have it:
[tinker] extra pulls in baseten-loops-tinker, which re-exports the public API under the tinker namespace so existing import tinker scripts run unchanged.
Verify the install by running uv run python train_loops.py:
baseten-loops-tinker version confirm Baseten’s Tinker compatibility package is installed, not the upstream tinker package.
Provision a trainer
A Loops session pairs a trainer server (forward, backward, and optimizer steps) with a sampling server (generates from current weights). Constructing aServiceClient and calling create_lora_training_client() provisions both and returns a TrainingClient. The call blocks until the trainer is ready, which can take several minutes for a fresh base model.
Start 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_state() publishes the trainer-side weights under the name you pass and returns a bt://loops:<run_id>/weights/<name> URI. The paired sampler picks up the new weights asynchronously through the weight-sync runtime.
List checkpoints
Everysave_state() call creates a checkpoint. The bound TrainingClient lists them with no arguments. Append to train_loops.py:
train_loops.py
run_id your script printed when provisioning. The response includes the same globally unique id and checkpoint name:
id value as the checkpoint_id argument: training_client.get_checkpoint_archive_url(checkpoint_id). From a separate Python session where training_client isn’t in scope, construct tinker.ServiceClient() and call service_client.get_checkpoint_archive_url(checkpoint_id) instead.
Skip the cold start on re-runs
Your first run provisioned a trainer and sampler. The second run doesn’t have to. Grab thesession_id your script printed (session_id=7qrp4v3 in the example output above), point the next run at it, and Loops reuses the same trainer and sampler:
reuse_from_session_id in the body of POST /v1/loops/runs 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.
Next steps
To turn any of these checkpoints into an inference endpoint, runtruss loops checkpoints deploy --run-id <run_id> and pick a checkpoint interactively, or pass --checkpoint-ids to deploy specific ones. See the checkpoints deploy CLI reference for the full option set.
Read Loops concepts to understand the paired-process model before you build longer training workflows: how sessions own trainer and sampling servers, how weight sync works, and how checkpoints land as unzipped folders of paginated presigned URLs rather than single archives.
If you’re migrating from Tinker, the Tinker compatibility page documents what carries over exactly (forward, backward, optim step, sampling, data types) and what behaves differently (checkpoint layout, authentication, cluster routing). The import tinker path used here already covers most cookbook recipes; that page names the three places where behavior has changed.
When you’re ready to call the HTTP API directly (for scripting deployments, fetching checkpoint files programmatically, or integrating Loops into a CI pipeline), the Loops API overview lists every route with its authentication scope, and each route has its own page with the request body, response shape, and an interactive playground.
To generate from a checkpoint instead of publishing it only, swap save_state() for save_weights_and_get_sampling_client(), which publishes weights and returns a SamplingClient pinned to the new version.