# Quick Start ## Installation ```bash uv pip install tinker ``` Set your API key (get one from the [Tinker Console](https://tinker.thinkingmachines.ai/keys)): ```bash export TINKER_API_KEY="your-api-key-here" ``` This gives you the Python SDK (`import tinker`) and the CLI (`tinker run list`, `tinker checkpoint download`). ______________________________________________________________________ This page walks through the two main LLM fine-tuning workflows — supervised fine-tuning (SFT) and reinforcement learning (RL) — showing how each step maps to Tinker SDK calls. ## Supervised Fine-Tuning (SFT) SFT trains a model to imitate examples: ServiceClientconnect → TrainingClientcreate_lora → forward_backward"cross_entropy" → optim_stepupdate weights → SamplingClientsave_weights → sampleevaluate 1. **[Create clients](#create-clients)** — connect to Tinker, create a TrainingClient 1. **[Prepare data](#prepare-training-data)** — tokenize examples into `Datum` objects with loss masks 1. **[Train](#forward-backward)** — `forward_backward` (gradients) + `optim_step` (update weights) 1. **[Evaluate](#sample-text)** — save weights, create a SamplingClient, sample ## Reinforcement Learning (RL) RL trains a model to maximize a reward signal: TrainingClientcreate_lora → SamplingClienton-policy → samplerollouts → reward+ logprobs → forward_backward"importance_sampling" → optim_stepupdate → repeat 1. **[Create clients](#create-clients)** — connect to Tinker, create a TrainingClient 1. **[Get on-policy sampler](#save-and-load-weights)** — `save_weights_and_get_sampling_client` 1. **[Sample rollouts](#sample-text)** — generate completions from the on-policy model 1. **[Score](#compute-log-probabilities)** — compute rewards and log-probabilities 1. **[Train](#forward-backward)** — `forward_backward` with RL loss + `optim_step` 1. **Repeat** — new weights → new SamplingClient → sample again ______________________________________________________________________ ## API Cheatsheet ### Create clients ```python import tinker from tinker import types # Entry point — reads TINKER_API_KEY from environment service_client = tinker.ServiceClient() # Training client (LoRA fine-tuning) training_client = service_client.create_lora_training_client( base_model="Qwen/Qwen3-8B", rank=32 ) # Sampling client (text generation) sampling_client = service_client.create_sampling_client( base_model="Qwen/Qwen3-8B" ) # Tokenizer tokenizer = training_client.get_tokenizer() ``` > **Subprocess sampling** > > `SamplingClient` is picklable — you can pass it to other processes for parallel sampling. If your training loop has CPU-heavy work (grading, environment logic), set `TINKER_SUBPROCESS_SAMPLING=1` to run `sample()` and `compute_logprobs()` in a dedicated subprocess, preventing GIL contention. ### Prepare training data A `Datum` is a single training example. It contains input tokens, target tokens, and per-token loss weights (0 = ignore, 1 = compute loss). Position `i` of the input predicts token `i + 1`, so build both from the full sequence by shifting one position: ```python full_sequence = prompt_tokens + completion_tokens n_prefix = len(prompt_tokens) - 1 datum = types.Datum( model_input=types.ModelInput.from_ints(tokens=full_sequence[:-1]), loss_fn_inputs=dict( weights=[0.0] * n_prefix + [1.0] * len(completion_tokens), # 0 for prompt, 1 for completion target_tokens=full_sequence[1:], # shifted by 1 from input ) ) ``` RL losses (importance_sampling, PPO, CISPO) take `logprobs` and `advantages` instead of `weights` — the advantages act as the mask, so pad the prompt positions with zeros: ```python rl_datum = types.Datum( model_input=types.ModelInput.from_ints(tokens=full_sequence[:-1]), loss_fn_inputs=dict( target_tokens=full_sequence[1:], logprobs=[0.0] * n_prefix + sampling_logprobs, # from the rollout policy advantages=[0.0] * n_prefix + advantages, # reward - baseline; 0 masks a position ) ) ``` ### Sample text ```python prompt = types.ModelInput.from_ints(tokenizer.encode("The capital of France is")) params = types.SamplingParams(max_tokens=50, temperature=0.7, stop=["\n"]) # Single sample — sample_async returns the result directly result = await sampling_client.sample_async(prompt=prompt, num_samples=1, sampling_params=params) print(tokenizer.decode(result.sequences[0].tokens)) # Multiple samples in one call result = await sampling_client.sample_async(prompt=prompt, num_samples=8, sampling_params=params) for seq in result.sequences: print(tokenizer.decode(seq.tokens)) ``` ### Compute log-probabilities Used for scoring in RL (comparing the training policy against the sampling policy). ```python # Prompt logprobs result = await sampling_client.sample_async( prompt=prompt, num_samples=1, sampling_params=types.SamplingParams(max_tokens=1), include_prompt_logprobs=True, ) print(result.prompt_logprobs) # [None, -9.5, -1.6, ...] # Shorthand — compute_logprobs_async returns the result directly logprobs = await sampling_client.compute_logprobs_async(prompt) # Top-k logprobs (for distillation) result = await sampling_client.sample_async( prompt=prompt, num_samples=1, sampling_params=types.SamplingParams(max_tokens=1), include_prompt_logprobs=True, topk_prompt_logprobs=5, ) print(result.topk_prompt_logprobs) # [None, [(token_id, logprob), ...], ...] ``` ### Forward-backward Computes gradients for the given data and loss function. Returns immediately with a future. ```python # SFT: cross-entropy loss fwdbwd_future = await training_client.forward_backward_async(data=[datum], loss_fn="cross_entropy") fwdbwd_result = await fwdbwd_future.result_async() print(f"Loss: {fwdbwd_result.loss}") # RL losses fwdbwd_future = await training_client.forward_backward_async(data, "importance_sampling") fwdbwd_future = await training_client.forward_backward_async(data, "ppo") fwdbwd_future = await training_client.forward_backward_async(data, "cispo") fwdbwd_future = await training_client.forward_backward_async(data, "dro") # Custom loss fwdbwd_future = await training_client.forward_backward_custom_async(data, my_loss_fn) ``` See [Loss Functions](https://tinker-docs.thinkingmachines.ai/tinker/losses/index.md) for the math behind each loss. ### Optimizer step Updates model weights using the gradients from the last `forward_backward`. ```python optim_future = await training_client.optim_step_async( types.AdamParams(learning_rate=1e-4) ) await optim_future.result_async() ``` ### Save and load weights ```python # Save weights → get a sampling client for evaluation sampling_client = training_client.save_weights_and_get_sampling_client(name="checkpoint-1") # Save full state (weights + optimizer) for resuming training_client.save_state(name="step-100") # Resume from weights only training_client = await service_client.create_training_client_from_state_async( path="tinker://run-id/sampler_weights/checkpoint-1" ) # Resume with optimizer state training_client = await service_client.create_training_client_from_state_with_optimizer_async( path="tinker://run-id/weights/step-100" ) ``` ### Vision inputs ```python import requests image_data = requests.get("https://example.com/image.png").content model_input = tinker.ModelInput(chunks=[ types.EncodedTextChunk(tokens=tokenizer.encode("<|im_start|>user\n<|vision_start|>")), types.ImageChunk(data=image_data, format="png"), types.EncodedTextChunk(tokens=tokenizer.encode("<|vision_end|>Describe this image<|im_end|>\n<|im_start|>assistant\n")), ]) ``` ### Concurrent requests ```python import asyncio # Multiple samples from the same prompt — just increase num_samples result = await sampling_client.sample_async(prompt=prompt, num_samples=16, sampling_params=params) for seq in result.sequences: # 16 independent completions print(tokenizer.decode(seq.tokens)) # Multiple different prompts in parallel — use asyncio.gather # sample_async returns results directly, so gather gives you the results results = await asyncio.gather( sampling_client.sample_async(prompt=prompt1, num_samples=1, sampling_params=params), sampling_client.sample_async(prompt=prompt2, num_samples=1, sampling_params=params), sampling_client.sample_async(prompt=prompt3, num_samples=1, sampling_params=params), ) # Pipeline training: overlap forward-backward with optimizer step fwdbwd = await training_client.forward_backward_async(batch1, "cross_entropy") optim_future = training_client.optim_step(types.AdamParams(learning_rate=1e-4)) next_fwdbwd = await training_client.forward_backward_async(batch2, "cross_entropy") await optim_future ``` See [Clock Cycles & Pipelining](https://tinker-docs.thinkingmachines.ai/tinker/under-the-hood/index.md) for more on throughput optimization. ______________________________________________________________________ ## Next steps - **[Loss Functions](https://tinker-docs.thinkingmachines.ai/tinker/losses/index.md)** — math behind each loss function - **[Models & Pricing](https://tinker-docs.thinkingmachines.ai/tinker/models/index.md)** — available models and costs - **[Tutorials](https://tinker-docs.thinkingmachines.ai/tutorials/index.md)** — interactive notebooks for hands-on learning - **[API Reference](https://tinker-docs.thinkingmachines.ai/tinker/api-reference/serviceclient/index.md)** — full method signatures