Prerequisites
- A Baseten workspace with Loops access to
Qwen/Qwen3.6-35B-A3B, and its API key. Qwen3.6-35B-A3B trains in either availability mode. - uv and Python 3.12 or later.
Install the cookbook
Install the Loops cookbook from PyPI into a uv project. The cookbook needs Python 3.12 or later, installs thebaseten-loops SDK, and provides the loops command. loops runs both recipes; each training config names its recipe with recipe = "rl" or recipe = "sft". Create a project first if you don’t have one:
examples/counting/train.toml, from your project’s directory. To change an example, copy its directory into your project at the same path. A config in your project wins over the installed one, and loops check prints the directory of the config it used.
Create a .env file in your project’s directory and set BASETEN_API_KEY to a workspace API key. The commands load .env from the training config’s directory or the directory you run them from. Never commit .env.
A run uses the serverless availability mode when serverless supports the model and the run’s LoRA rank and sequence length, and the dedicated mode otherwise. To choose a mode, add BASETEN_LOOPS_SCOPE=serverless or BASETEN_LOOPS_SCOPE=deployment to .env.
Install the vision extra:
Run the example
To check the configuration, run the following command. It loads the episode and the task file, asks Loops about the model, and starts nothing:runs/counting/. run.json has the Loops run ID, and steps.jsonl has the metrics of each update.
Read the episode
examples/counting/count_episode.py defines CountEpisode, the whole runtime of this example. The cookbook creates one episode for each attempt at a task, calls its run() method to get the reply, and calls its grade() method to get the reward.
A task is a count and a seed. examples/counting/tasks.jsonl holds 512 tasks, and make_tasks.py writes them:
examples/counting/tasks.jsonl
run() draws the picture and sends it to the policy as an image part, beside the text of the question:
examples/counting/count_episode.py
draw() returns a 448 by 448 PNG. The question asks for the number only. parse_count() keeps the first whole number in the reply. A reply with no whole number has no guess.
The reward gives partial credit for a near miss:
examples/counting/count_episode.py
Read the training config
examples/counting/train.toml names the episode and the task file:
examples/counting/train.toml
agent_timeout_seconds = 90 caps the wait for a reply at 90 seconds. An attempt that returns no number scores 0.
The model and training settings:
examples/counting/train.toml
- Thinking is off, and a reply can have at most 16 tokens. A number needs only a few tokens.
- Each update trains 8 pictures with 16 attempts each, for 50 updates. 50 updates use 400 of the 512 tasks, so every update trains on pictures the policy hasn’t seen.
mode = "sync",max_steps_off_policy = 0, andmax_policy_delta_in_trajectory = 0make every trained attempt come from the current policy. The trainer waits while the sampler counts.max_group_losses = 0trains a group only when every attempt in it finished.max_consecutive_unusable_groups = 20stops the run when 20 groups in a row have nothing to train, for example when every attempt scores the same.
Read the results
Monitorreward/mean in steps.jsonl. Each update trains on new pictures, so a reward that rises from update to update is a reward on pictures the policy hadn’t trained on. For the other metrics to watch, see Check whether an RL run is learning.
The cookbook’s recorded run of train.toml on serverless Loops took 24 minutes. The base model and the last checkpoint then each counted 64 held-out pictures, 8 times each:
The base model mostly counted one or two circles too many. After training, almost every reply was within two circles, and most misses were one too few. Every picture size gained: exact counts rose from 43% to 83% at 9 to 12 circles, and from 12% to 53% at 17 to 20.
To score your own checkpoint on pictures it didn’t train on, see Evaluate a post-trained model.
Resume a stopped run
If the run stops before its last update, continue it with--resume. The command finds the run of this config that trained most recently, and continues from the last checkpoint it saved:
max_steps counts the updates of the whole run, so the resumed run makes only the updates that are left. To check the resumed run first, run uv run loops check examples/counting/train.toml --resume. If no run has trained, or the run saved no checkpoint, the command exits with code 2 and starts nothing.
Shut down the run
loops run leaves the Loops run active. This serverless run incurs no token charges while idle, so deactivation is optional. To end it and its paired sampler, run the following command with your run directory:
Deploy the checkpoint
The run’s sampler checkpoint is ready to serve. The deployment loads the adapter on the base weights, downloads the base weights with thehf_access_token in workspace secrets, and bills for its GPU while it’s live. With the Baseten CLI installed, list the run’s checkpoints and deploy the one you want:
Next steps
- Vision: How image inputs become tokens, and which models accept them.
- Fine-tune a vision model with the Loops SDK: SFT on images, written against the SDK directly.
- Design RL environments and rewards: Graders, task sizing, and exploits.
- Train a coding agent on Terminal-Bench: The same recipe with a multi-turn agent in Blaxel sandboxes.
- Deploy a checkpoint: Serve the trained adapter.