Skip to main content
The Together Python SDK includes a Tinker-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.
Install the packages and export your API key:

Swap the import

Replace the tinker import with the Together client:
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. 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. Billing starts when the resource is ready. See Pricing.

Run the example scripts

The 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:
  • 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:
Each script also runs on Tinker when you switch the import back, so you can compare training curves across both backends.
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.

How Tinker calls map to post-training

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.
  • 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. 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.

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: 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 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:

Train on an existing resource

To attach to a model resource that’s already running, pass its ID to ServiceClient:
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.
  • 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

Concepts

How work splits between your code and Together AI GPUs, and the model resources, sessions, and checkpoints behind the Tinker calls.

Supported models

Pick a base model by training mode, context length, and GPU type.

Monitoring

Watch sessions and resources in the console.

Manage checkpoints

Download the weights or resume a run from a checkpoint.