> ## Documentation Index
> Fetch the complete documentation index at: https://docs.together.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrate from Tinker

> Run a Tinker training script on Together AI post-training by changing its import line.

The Together Python SDK includes a [Tinker](https://tinker-docs.thinkingmachines.ai/)-compatible client, so a training loop written against the `tinker` SDK runs on post-training after you change its import line and, if needed, its base model. The client replaces only the service calls, so your rendering, tokenization, and grading code runs unchanged, including the `tinker_cookbook` helpers it imports.

## Requirements

* Python 3.11 or later.
* The Together Python SDK 2.41.0 or later, with the `tinker` extra. The extra installs `tinker` 0.23.1, the version the client builds on, so it replaces a newer `tinker` you already have installed.
* The `tinker-cookbook` package, if your script imports `tinker_cookbook`.
* `TOGETHER_API_KEY` exported in your shell.
* A credit balance of at least \$100, the [minimum for creating a model resource](/docs/post-training/pricing#minimum-credit-balance).

Install the packages and export your API key:

```bash theme={null}
pip install -U "together[tinker]>=2.41.0" tinker-cookbook
export TOGETHER_API_KEY=<your_key>
```

## Swap the import

Replace the `tinker` import with the Together client:

```diff theme={null}
-import tinker
+import together.post_training.tinker as tinker
```

`tinker.types` resolves to the real Tinker types, so the prompts and `Datum` objects your code builds pass through unchanged.

Besides the import, the only value you might need to change is the base model. Tinker and Together host different catalogs, so pick an ID from [Supported models](/docs/post-training/supported-models).

When the script calls `create_lora_training_client`, the client provisions a model resource for the base model, waits until it's ready, and opens a low-rank adapter (LoRA) session on it. The wait takes a few minutes for a small model, and longer when GPU capacity is scarce. The client waits up to an hour, then raises `TimeoutError` and stops the queued resource. To wait longer, create the resource with `ModelResourcesClient.create(..., timeout=None)` and [train on it as an existing resource](#train-on-an-existing-resource). Billing starts when the resource is ready. See [Pricing](/docs/post-training/pricing).

## Run the example scripts

The [post-training cookbook](https://github.com/togethercomputer/post-training-cookbook) ships three Tinker scripts in `demos/tinker-migration/`, each adapted from a `tinker_cookbook` recipe and already switched to the Together client. They import it the same way as the one-line swap above:

```python theme={null}
import together.post_training.tinker as tinker
```

* `grpo_gsm8k.py`: A minimal GRPO loop on GSM8K.
* `grpo_gsm8k_async.py`: The same loop, sampling each batch while the previous one trains.
* `sft_no_robots.py`: Supervised fine-tuning on No Robots.

From a cookbook checkout, install the Tinker dependencies and run a short smoke test:

```bash theme={null}
uv sync --locked --group tinker
uv run python demos/tinker-migration/grpo_gsm8k.py --batch-size 8 --group-size 4 --max-steps 3
```

Each script also runs on Tinker when you switch the import back, so you can compare training curves across both backends.

<Note>
  The demos build their training data in Tinker's format, with `tinker_cookbook` helpers or by hand. When you write a loop on the cookbook or on the SDK directly, build samples with the cookbook's `build_cross_entropy_sample` or `build_policy_sample` instead. Both take per-token rows and shift them to the target positions for you. See [Data and samples](/docs/post-training/concepts#data-and-samples).
</Note>

## How Tinker calls map to post-training

| Tinker call | On Together |
| - | - |
| `ServiceClient()` | Creates the client without provisioning anything. |
| `create_lora_training_client(base_model, rank)` | Provisions a model resource, waits until it's ready, and opens a LoRA session on it. |
| `create_training_client_from_state(path)` | Provisions a model resource and opens a session from a training checkpoint, loading the weights only. |
| `create_training_client_from_state_with_optimizer(path)` | Provisions a model resource and opens a session from a training checkpoint, restoring the optimizer state too. |
| `forward_backward(data, loss_fn)` | Runs `forward_backward` with the matching built-in loss function: `cross_entropy`, `importance_sampling`, `ppo`, `cispo`, or `dro`. |
| `forward(data, loss_fn)` | Runs a forward-only pass and returns per-datum log-probabilities in `loss_fn_outputs`. |
| `forward_backward_custom(data, loss_fn)` | Reads log-probabilities with `forward`, runs [your loss function](/docs/post-training/custom-loss-functions) client-side, and submits the gradients with `custom_forward_backward`. |
| `optim_step(adam_params)` | Runs `optim_step`. |
| `save_weights_and_get_sampling_client()` | Publishes the weights with `weights_sync`, then returns a sampling client for them. |
| `sample(...)`, `compute_logprobs(...)` | Run on the session's generator replica. |
| `save_state(name)` | Saves a [training checkpoint](/docs/post-training/checkpoints#training-checkpoints) and returns its ID, which `create_training_client_from_state` accepts. |
| `save_weights_for_sampler(name)` | Saves an [inference checkpoint](/docs/post-training/checkpoints#inference-checkpoints) and returns its ID, which `download_checkpoint` accepts. |

As in Tinker, every call also has an `_async` variant, except `create_training_client_from_state` and `create_training_client_from_state_with_optimizer`.

## Behavior differences

Each of these differences affects how your results compare with a Tinker run, and some need a change to your script:

* **Sampling clients aren't snapshots:** Together samples from the most recently published weights. After a later `save_weights_and_get_sampling_client` call, an earlier sampling client raises `RuntimeError` instead of serving the old policy. Create a new sampling client after each publish, or pass `allow_stale=True` to keep a client that samples whatever weights are live.
* **Fractional weights apply to cross-entropy only:** The policy-gradient loss functions accept only `0` and `1` in `weights`. The client warns when it sees fractional values, and the server then fails the operation. To include or exclude tokens, use `mask`.
* **`forward_backward` returns no per-datum outputs by default:** Its `loss_fn_outputs` list is empty unless you pass `return_loss_fn_outputs=True`, an argument Tinker doesn't accept. Scripts that read log-probabilities from it, such as the `tinker_cookbook` recipe `sl_loop.py`, need that argument or a separate `forward` call. The total loss is in `metrics["loss:sum"]`.
* **Stop sequences must be strings:** Together's sampler takes stop strings, so the client drops stop sequences given as token IDs and warns. Generation still stops at the model's end-of-sequence token.
* **Module selection is fixed:** `train_mlp` and `train_attn` can't be turned off, so the client warns on non-default values and ignores them. `seed` and `train_unembed` pass through to the session's LoRA config.
* **Checkpoint names are server-assigned:** Training and inference checkpoints get a generated ID, whatever `name` you pass. The client warns on the `ttl_seconds`, `overwrite`, and `retry_config` arguments and ignores them.
* **Custom loss functions train only weighted positions:** `forward_backward_custom` applies your gradients only at positions whose `weights` are nonzero. A datum with no `weights` gets all zeros, so it trains nothing. Pass `weights` of `1` on every position your loss reads.
* **`forward` returns zeros at unweighted positions:** Positions whose `weights` are `0` come back as `0.0` in `loss_fn_outputs`, not as their log-probabilities. To read every position, pass `weights` of `1` everywhere.
* **Text input only:** `model_input` must contain only encoded text chunks. Image chunks raise `ValueError`.
* **Loss inputs must be one-dimensional:** A two-dimensional `loss_fn_inputs` tensor raises `ValueError`, so loss functions that need several target tokens per position, such as top-K distillation, aren't supported.
* **`dro` needs a `beta`:** Pass `loss_fn_config={"beta": ...}`. The loss function has no default.
* **LoRA rank and alpha:** The maximum rank is 64 on Qwen models and 32 on every other model. The client never sends `lora_alpha`, so every session uses Together's default of `64`, whatever rank you pass.
* **Sampling arguments:** `topk_prompt_logprobs` above `20` raises `ValueError`.
* **Checkpoints aren't served by path:** There's no `tinker://` path to pass to an OpenAI-compatible endpoint. To call a trained model, deploy its inference checkpoint first. See [Deploy a checkpoint](/docs/post-training/deployment).
* **The project comes from your API key:** `ServiceClient` accepts `project_id` and `user_metadata` and ignores them. Every call runs in the project that your `TOGETHER_API_KEY` belongs to.
* **Transport options are ignored:** `ServiceClient` accepts Tinker's HTTP options and ignores them. It warns on `http_client`, `max_retries`, and `timeout`. Any other unknown argument raises `TypeError`.
* **Some calls aren't available:** `create_rest_client` raises `TinkerError` from the `tinker` package, and passing `weights_access_token` raises `NotImplementedError`. To catch the error, import it with `from tinker import TinkerError`, because `together.post_training.tinker` doesn't re-export it. The client doesn't define `create_sampling_client`, `get_tokenizer`, `get_info`, `get_base_model`, `get_telemetry`, `load_state`, `load_state_with_optimizer`, or `get_server_capabilities`, so calls to them raise `AttributeError`. Sampling clients don't define `get_tokenizer` or `get_base_model` either. `ServiceClient` also has no `close` and isn't a context manager, so close the training client instead, as in [Clean up GPUs](#clean-up-gpus). To get a tokenizer, call `tinker_cookbook.tokenizer_utils.get_tokenizer(base_model)`, as the example scripts do. To sample from a saved checkpoint instead of `create_sampling_client`, see [Sample a saved checkpoint](/docs/post-training/checkpoints#sample-a-saved-checkpoint).

## Replace `RestClient` calls

`create_rest_client` isn't available, so call the Together SDK for the run and checkpoint lookups that Tinker's `RestClient` handles:

| Tinker call | On Together |
| - | - |
| `list_training_runs`, `get_training_run` | `client.post_training.sessions.list()`, `client.post_training.sessions.retrieve(session_id)` |
| `list_checkpoints`, `list_user_checkpoints` | `client.post_training.checkpoints.list()`, filtered by `session_id` or `base_model` |
| `get_checkpoint_archive_url` | `download_checkpoint(client, checkpoint_id)` from `together.post_training`, which downloads the files. See [Download the weights](/docs/post-training/checkpoints#download-the-weights). |
| `whoami` | `client.whoami()` |
| `get_server_capabilities` | `client.post_training.supported_models.get()` |
| `delete_checkpoint`, `set_checkpoint_ttl_from_tinker_path`, `publish_checkpoint_from_tinker_path` | No equivalent. |

`client` is a `together.Together` client.

## Clean up GPUs

Tinker's training client has no teardown call, so the Together client registers its own cleanup, which stops the session and the model resource the client provisioned. The cleanup runs when the script exits normally, raises an uncaught exception, is interrupted with `Ctrl+C`, or receives `SIGTERM`. Only `SIGKILL` and `os._exit` skip it, which leaves the resource billing until it [stops on its own](/docs/post-training/model-resources#timers) after an hour idle.

In a long-lived process such as a notebook, call `training_client.close()` to release the GPUs as soon as training ends, or use the training client as a context manager:

```python theme={null}
import together.post_training.tinker as tinker

service_client = tinker.ServiceClient()
with service_client.create_lora_training_client(
    base_model="Qwen/Qwen3.8-27B", rank=32
) as training_client:
    ...  # your training loop
```

## Train on an existing resource

To [attach to a model resource](/docs/post-training/model-resources#attach-to-an-existing-resource) that's already running, pass its ID to `ServiceClient`:

```python theme={null}
service_client = tinker.ServiceClient(
    model_resources_id="your_model_resources_id"
)
```

The resource must be LoRA-enabled and serve the same base model you pass to `create_lora_training_client`. The client opens its session on that resource. On exit, it stops the session but leaves the resource running.

`model_resources_id` is a Together-only argument, so a script that passes it no longer runs unchanged on Tinker.

## Troubleshooting

* **Creating the training client fails because the base model isn't supported:** The model ID isn't in the post-training catalog. Pick one from [Supported models](/docs/post-training/supported-models).
* **Sampling raises `RuntimeError` about a stale sampling client:** The script reused a sampling client after publishing newer weights. Create a new one after each `save_weights_and_get_sampling_client` call, or pass `allow_stale=True`.
* **Reward stays at zero:** If the script's renderer has thinking turned on, completions can use up `max_tokens` on reasoning and never reach the answer. Raise `max_tokens`, or use a renderer with thinking turned off.
* **`ServiceClient` raises `TypeError`:** The script passes an argument the Together client doesn't accept. Remove it.

## Next steps

<CardGroup cols={2}>
  <Card title="Concepts" icon="bulb" href="/docs/post-training/concepts">
    How work splits between your code and Together AI GPUs, and the model resources, sessions, and checkpoints behind the Tinker calls.
  </Card>

  <Card title="Supported models" icon="list" href="/docs/post-training/supported-models">
    Pick a base model by training mode, context length, and GPU type.
  </Card>

  <Card title="Monitoring" icon="chart-line" href="/docs/post-training/monitoring">
    Watch sessions and resources in the console.
  </Card>

  <Card title="Manage checkpoints" icon="device-floppy" href="/docs/post-training/checkpoints">
    Download the weights or resume a run from a checkpoint.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.