Skip to main content
Most Tinker code runs on Loops with one install change, so migrating is largely a packaging exercise. The forward pass, backward pass, optimizer step, sampling, and all shared types carry over without modification. Checkpoints come back as paginated presigned URLs rather than a single archive, authentication uses a BASETEN_API_KEY instead of a Thinking Machines key, and cluster routing resolves to your Baseten org rather than a Tinker tenant.

Compatibility at a glance

The baseten-loops[tinker] extra installs a tinker namespace package so existing imports work unchanged. The table below shows where Loops and Tinker align and where they diverge.

Install the Tinker compatibility package

Add baseten-loops with the [tinker] extra to your project. The extra pulls in baseten-loops-tinker, which provides the tinker namespace. If you don’t have a uv project yet, initialize one first:
Once installed, existing code that starts with import tinker works without modification:

Use with tinker-cookbook

tinker-cookbook depends on the original Thinking Machines tinker package. Without an override, installing tinker-cookbook first pulls in that package, and its files conflict with the tinker namespace provided by baseten-loops[tinker] when that extra is added later. The fix is to declare a uv override in pyproject.toml before installing any dependencies. There is no CLI command for this step. Add the override first:
Then add the dependencies:
The baseten-loops[tinker] extra provides the tinker namespace instead of the original package.

What’s the same

The training loop API is call-compatible with Tinker. forward, backward, optim_step, save_weights, and the sampling interface share the same method names, and the shared types (Datum, ModelInput, TensorData, SamplingParams, and AdamParams) are all available under tinker.types with the same field names.

What’s different

Loss inputs are one-dimensional

Keep every loss_fn_inputs tensor one-dimensional, one value per token position. Loops reads the flat TensorData.data buffer and ignores any second dimension, so it does not support the (N, K) form Tinker uses for top-K soft targets in distillation. Recipes that build 1-D target_tokens, weights, logprobs, and advantages port unchanged; a recipe that builds (N, K) targets does not.

Image inputs must be inline

Tinker’s ImageAssetPointerChunk references an image asset stored out of band. Loops ships the type so that tinker imports and ModelInput parsing resolve, but Baseten samplers don’t fetch out-of-band assets, so constructing one raises ValueError naming ImageChunk as the supported path. Recipes that read images from disk or memory and build ImageChunk values port unchanged; recipes that pass asset pointers do not.

Checkpoints come back as folders

Tinker returns a single archive URL for a checkpoint. Loops returns a folder of files behind paginated presigned URLs, because weight sync writes an unzipped folder rather than a compressed archive. Consumer code paginates using ?page_token= and ?page_size= query parameters instead of downloading a single file. See GET /v1/loops/checkpoints/{checkpoint_id}/files for the route.

Authentication is a Baseten API key

Set BASETEN_API_KEY in your environment before constructing ServiceClient; the SDK reads it by default. Pass api_key=... only when you need to override the environment variable. The Thinking Machines key used by Tinker is not accepted. See API keys for how to generate one.

LoRA module selection is fixed per model family

Tinker lets you choose which module groups a LoRA adapter trains through the train_mlp, train_attn, and train_unembed flags on create_lora_training_client. Loops picks target modules server-side per model family: attention and MLP on, unembed off, for every supported family except Kimi K2.5 VL, which trains attention and unembed but not MLP. The compatibility package accepts these flags when they match your model family’s coverage and raises NotImplementedError at submission when they diverge, rather than silently ignoring them.

Cluster routing is per-org

Loops sessions resolve to the caller’s Baseten org and the cluster configured for that org. Tinker uses per-tenant scoping, where the tenant determines the cluster. In practice this means you don’t choose a cluster when creating a session. Your org’s configuration determines it automatically. To place workloads in a specific Baseten team, pass team="..." to ServiceClient; see Choose a team. Loops must be enabled for your organization before sessions can start; fill out the signup form to request access.

Run tinker-cookbook recipes on Loops

The cookbook recipes contain self-contained examples covering supervised fine-tuning, reinforcement learning from human feedback, distillation, and sampling. Recipes that build one-dimensional loss inputs run on Loops without modification; recipes that build (N, K) targets do not. See Loss inputs are one-dimensional. Running a recipe end to end is the fastest way to confirm that your environment is configured correctly and that the tinker namespace is resolving to the Loops compatibility package.