Read Tinker as a hosted LoRA post-training service: local control loops, remote GPU workers, token-level RL contracts, pipelined clock cycles, checkpoint export, and the boundaries that separate an SDK from infrastructure such as DeepEP.
Personalize this lesson
Adapt explanations and teaching visuals to your background and preferred voice.
Start by changing one part of a language model's behavior. Perhaps you have a reward function, a small dataset, or a tool-using environment. Operating a multi-node cluster would delay the test, while a high-level trainer could hide the tokens, log probabilities, and optimizer boundaries that determine whether the experiment is valid.
Tinker is Thinking Machines Lab's managed hosted low-rank adaptation (LoRA) service for this job. A Python process that you run locally builds prompts, environments, rewards, and loss inputs. Remote workers run the model's forward and backward passes, apply optimizer updates, and generate samples. The public software development kit (SDK) and Tinker Cookbook are Apache-2.0 open-source code; the hosted service, model catalog, data handling, and application programming interface (API) access are separate product and terms boundaries.[1][2][3]
This lesson follows one training update from your central processing unit (CPU) process to a remote worker and back. It treats four primitives as a contract: forward_backward, optim_step, sample, and save_state. It then builds the reinforcement learning (RL) path, explains clock-cycle pipelining, and ends with a comparison that keeps Tinker distinct from DeepEP. Tinker can expose training without asking you to build the cluster. DeepEP is a communication kernel and runtime component that you would operate inside such a cluster.
Tinker has three names that look like one product but describe different surfaces.
| Surface | Role | Who operates it | What you can change |
|---|---|---|---|
| Tinker service | Hosted worker pool, storage, scheduler, and model runtime | Thinking Machines Lab | Your requests, data, loss choices, and loop timing |
tinker SDK | Python client, types, futures, command-line interface (CLI), and API conversion code | Open-source contributors under Apache-2.0 | Client code, tests, adapters, and local orchestration |
tinker-cookbook | Open recipes for supervised fine-tuning (SFT), RL, Direct Preference Optimization (DPO), distillation, evaluation, and export | Open-source contributors under Apache-2.0 | Dataset builders, environments, reward functions, and recipes |
The hosted service isn't the same thing as the GitHub repository. Cloning the SDK doesn't give you the worker pool, model weights, scheduler, storage, or failure-recovery system. Conversely, using the service doesn't require you to accept a fixed high-level RL trainer. The low-level calls are intentionally small.[4][2]
The model catalog is a service capability, not a promise encoded in the Python package. It can change independently from an SDK commit. Current documentation describes LoRA fine-tuning for dense and mixture-of-experts (MoE) models and a catalog that changes over time.[4] GLM-5.2 and DeepSeek-V4 need independent capability checks. Check the live response and model terms before designing a run around either name.
That distinction matters for frontier post-training. A model may fit the service's architecture adapter and still be absent from your account, restricted by third-party terms, or unavailable for a particular loss or input modality. Treat get_server_capabilities and the current model page as runtime evidence, not as static metadata in a blog post.
The phrase control plane here means the code that decides what should happen next. Your process can load a dataset, render a chat template, call an environment, grade an answer, compute group-relative advantages, select a loss, and decide when to checkpoint. It can also start many requests concurrently with asyncio.
The phrase data plane means the model-heavy work that the hosted workers execute. A worker pool receives serialized model inputs and loss tensors, runs distributed forward and backward computation, applies the optimizer state, and returns compact outputs. Sampling workers generate token IDs and optional log probabilities. Storage workers make named checkpoints addressable through tinker:// paths.
This split lets a reward function change without modifying distributed model code, but it creates failure surfaces. Local code can produce a malformed target tensor; remote workers can reject a model or run out of capacity; a sampler can use an older checkpoint than the trainer; a service retry can duplicate an operation unless request and checkpoint names are idempotent.
Ask, "Where does this state live?" Prompt rows, reward labels, and experiment configuration live in your loop. LoRA parameters, optimizer moments, and worker placement live in the service. A returned future is only a handle to remote progress. It isn't a local gradient tensor unless you explicitly request an output that contains one.
Treat each API operation as a state transition.
| Primitive | Client | Input | Remote effect | Returned evidence |
|---|---|---|---|---|
forward_backward | TrainingClient | Datum list, loss name, optional config | Computes loss and accumulates gradients for current adapter state | Per-datum outputs, metrics, and a future |
optim_step | TrainingClient | Adam hyperparameters | Applies accumulated gradients and advances adapter plus optimizer state | Optimizer metrics and a future |
sample | SamplingClient | Tokenized prompt, count, sampling parameters | Generates one or more completions from a model snapshot | Token IDs, stop reason, optional log probabilities |
save_state | TrainingClient | Name, optional time to live (TTL), overwrite flag | Persists train-resume checkpoint, optionally with optimizer state on load | tinker:// checkpoint path |
The SDK also exposes save_weights_for_sampler and save_weights_and_get_sampling_client. Those operations publish a sampler-compatible adapter snapshot. save_state is about restoring training state; a sampler checkpoint is about evaluating or serving a chosen weight version. Don't use the two names interchangeably.
The four operations are asynchronous at the network boundary. Each returns a future-like object. The server still executes operations in an ordered clock-cycle schedule for a training client. Calling a method with an _async suffix changes how your local event loop waits; it doesn't turn the worker pool into an unbounded asynchronous parameter server.[2][4]
forward_backward receives a DatumA Datum has model_input plus loss_fn_inputs. model_input is a token or multimodal sequence. loss_fn_inputs holds tensors needed by a named loss. For SFT, that commonly means target_tokens and weights. For RL, it can include sampled logprobs, advantages, target_tokens, and an action mask before cookbook code removes or consumes the mask.
The SDK chunks large lists before sending them. The pinned source uses a maximum of 1,024 datums per chunk and an estimated payload cap, then assigns sequence IDs so chunks are processed in the intended order. A long batch can therefore become multiple remote requests while still yielding one combined future result.[2]
The loss name is a protocol value. The pinned SDK declares cross_entropy, importance_sampling, ppo, cispo, and dro as supported loss identifiers. That list is not a guarantee that every model or service deployment enables every option. Validate against the current server capability and docs when a run starts.[2][4]
optim_step advances adapter stateoptim_step consumes gradients accumulated by the preceding forward/backward work and updates trainable parameters with AdamW-equivalent behavior. The SDK documentation calls out a default weight decay of zero, which differs from some PyTorch defaults.[2]
The optimizer state stays remote. You don't download a Python optimizer object after every step. This keeps the loop small, but it means your checkpoint policy must explicitly preserve optimizer state when you need a faithful resume. Loading weights alone resets optimizer history; loading with optimizer restores the saved moments when the checkpoint contains them.
The operation ordering is part of correctness. An optimizer request submitted before its matching forward/backward result can still be valid because the service sequences requests for that training client. A separate client, a retry, or an accidental concurrent loop can violate the intended order. Use one owner for a training client and log request IDs and step numbers in your local loop.
sample returns evidence for learningSamplingClient.sample accepts a ModelInput, number of completions, and SamplingParams. Each returned sequence includes generated token IDs and a stop reason. If requested, it includes one log probability per generated token. compute_logprobs can score prompt tokens for a separate comparison.
The decoded string is for a human or verifier. The token identifiers (IDs) and log probabilities are the learning evidence. If you decode and re-tokenize before constructing an RL Datum, a different tokenizer version or normalization rule can shift token boundaries. The reward may still look correct while the policy-gradient target points at the wrong positions.
num_samples is a group primitive. A math problem with eight independent completions gives a group over which the Cookbook centers rewards. Eight separate calls can work, but one grouped call makes the intended comparison and batching visible. Concurrent calls across different prompts add another axis of parallelism.[4][3]
save_state creates a recovery pointsave_state("step-0042") stores a named training checkpoint. load_state restores model weights but not optimizer state. load_state_with_optimizer restores both when available. A TTL can make a checkpoint expire, and overwrite controls whether the name can be replaced.[2]
For evaluation or deployment, save sampler-compatible weights instead. The Cookbook's export path downloads the adapter, merges it with the base model when requested, and can build a parameter-efficient fine-tuning (PEFT) adapter directory or a standard Hugging Face model. The merged model is portable, but its base-model license and tokenizer terms still apply.
Tinker is a LoRA training service, not a full-parameter training service. LoRA keeps the frozen base matrix and learns a low-rank update:
Here and are small trainable matrices, is the rank, and scales the update. The service stores and updates the adapter path while sharing the base model across runs. This is how a multi-tenant worker pool can serve many experiments without copying a full model and optimizer state for each run.[1][5]
LoRA doesn't mean "small model." Tinker has documented support for large dense and MoE models, including trillion-parameter examples in its announcements. The memory savings come from the trainable state and optimizer path, not from making every base-model weight cheap to move. A huge model still needs a compatible hosted topology, checkpoint format, and model-specific adapter implementation.[6][4]
Rank changes capacity and memory. A larger rank can express a broader update but costs more adapter parameters and optimizer state. Training only attention, only a multilayer perceptron (MLP), or the unembedding layer changes which behavior can move. The create_lora_training_client options make those choices explicit. Don't compare runs with different target modules as if rank were the only variable.
LoRA also changes what "full control" means. You control the algorithm, data, reward, sampling schedule, and loss inputs. You don't control arbitrary base-weight tensors, kernel fusion, placement, or collective algorithms. That is a productive boundary for post-training research, but it isn't equivalent to owning the full training stack.
Start with a supervised example before adding rewards. A rendered conversation becomes an input sequence and a target sequence. A weight tensor marks which target positions contribute to cross-entropy. Prompt positions usually receive zero weight; assistant positions receive one or a chosen fractional weight.
For one datum, the core alignment is:
| Position | model_input | target_tokens | weights | Training meaning |
|---|---|---|---|---|
| 0 | prompt token | next token | 0 | Context only |
| 1 | prompt token | next token | 0 | Context only |
| 2 | assistant token | next token | 1 | Update adapter |
| 3 | assistant token | next token | 1 | Update adapter |
The arrays must share the expected shifted length. The Cookbook's conversation_to_datum helper handles renderer-specific role markers, target shifting, and response masks. Read that helper when a loss curve looks impossible. A one-token offset can produce a decreasing loss while training the model on the wrong targets.
The smallest SFT loop has one forward_backward and one optim_step. The Cookbook submits both before waiting so they can share a worker clock cycle. It repeats the pair while recording loss and evaluation metrics. A decrease in loss is evidence that the chosen weighted tokens are easier for the adapter to predict; it isn't evidence that a task reward improved.
RL adds a second model interaction. The current policy samples a completion, an environment or verifier assigns reward, and the loop converts that evidence into a training datum. The reward function can be local and deterministic, a remote grader, a unit-test sandbox, or another model. Tinker doesn't choose it for you.
The Cookbook's basic Group Relative Policy Optimization (GRPO) path looks like this:
Datum objects with sampled log probabilities and token-aligned advantages.forward_backward(..., loss_fn="importance_sampling").optim_step with the chosen Adam parameters.The group baseline is a variance-reduction choice, not a universal reward definition. Code tasks might use unit-test pass rate, while tool-use environments can combine success, invalid-call penalties, and latency budgets. Preference models may return dense scores. The model sees a token-level signal only after your loop maps those outcomes into advantages and masks.
The Cookbook's trajectory converter emits one or more datums per trajectory. One trajectory can split when a later observation isn't a prefix of the sequence accumulated so far. That happens naturally with tool calls, branches, or context compaction.
The resulting loss_fn_inputs include:
| Field | Shape idea | Source | Meaning |
|---|---|---|---|
target_tokens | One target per input position | Shifted sampled sequence | Token to score |
logprobs | One sampled logprob per target | Sampler | Behavior-policy evidence |
advantages | One scalar copied across action tokens | Group reward | Credit assigned to action |
mask | Zero for observation, one for action | Trajectory converter | Which positions are trainable |
Observation positions receive zero logprob, zero advantage, and zero mask. Action positions receive the sampled logprob and trajectory advantage. A parse-error policy can set an action mask to zero while retaining the rest of the trajectory. That makes an invalid turn visible in metrics without teaching the model to reproduce malformed output.
The named loss consumes the fields it supports. importance_sampling compares current log probabilities with sampled behavior-policy log probabilities. Proximal Policy Optimization (ppo) adds clipping behavior. The cispo and dro losses use other importance and truncation choices. A custom loss can run on log probabilities through forward_backward_custom, but the SDK still translates your derivative into a backend-supported weighted cross-entropy pass.[2][3]
Suppose a sampler returns four response tokens and four log probabilities. The training input usually contains prompt context plus the response prefix, while the target array contains the response tokens shifted one position to the left. The prompt padding is deliberate. It keeps array positions aligned while assigning zero learning signal to context.
If the arrays differ by one token, an operation can fail loudly. Worse, if they have the same length after accidental padding, the operation can succeed while applying the wrong credit to every token. Log a short prefix of tokens, log probabilities, advantages, and masks for one group before launching a long run.
The reward should be reproducible enough to debug. Save the prompt ID, sampler checkpoint path, sampled tokens, stop reason, reward components, environment version, and advantage statistics. A reward value without that evidence can't tell you whether the model improved or the grader changed.
Tinker worker pools execute training clients in lock-step clock cycles. A cycle can include forward/backward work and an optimizer step for one or more LoRA models sharing the pool. Multi-tenancy keeps small batches from leaving the entire pool idle, but it also means a small request can observe latency shaped by the pool's schedule.[4]
The local future is a queueing tool. Submit work early, then await results after the requests have been admitted. The naive loop below often spans three cycles:
1fwd = await client.forward_backward_async(batch, "cross_entropy")
2await fwd.result_async()
3opt = await client.optim_step_async(adam_params)
4await opt.result_async()The pipelined loop submits both operations before waiting:
1fwd = await client.forward_backward_async(batch, "cross_entropy")
2opt = await client.optim_step_async(adam_params)
3fwd_result = await fwd.result_async()
4opt_result = await opt.result_async()The service can place both in one cycle because the training client preserves request order. For several batches, enqueue the next forward/backward plus optimizer pair before consuming the current result. The Cookbook's train_step uses this pattern and returns training log probabilities for diagnostics.[4][3]
Pipeline depth isn't free. A deeper queue can hide network latency and keep GPUs busy, but it can also increase the amount of local data waiting for a result and complicate failure handling. In RL, over-enqueueing samples can also increase policy lag. Keep a bounded number of in-flight groups and log the sampler checkpoint version against the trainer step.
The clock is not the same as GPU kernel overlap inside a model. A worker may overlap collective communication and matrix operations internally, while your client still misses a cycle by waiting before submitting the next request. Read both layers when tuning throughput.
Tinker has three artifact states.
| Artifact | Purpose | Restores optimizer? | Typical consumer |
|---|---|---|---|
| Training state | Resume the same loop | Yes with load_state_with_optimizer | Training client |
| Sampler weights | Evaluate a chosen policy snapshot | Not a training resume | Sampling client, OpenAI-compatible adapter |
| Merged Hugging Face model | Portable base plus LoRA update | No training history | vLLM, SGLang, Transformers, offline inference |
The checkpoint name is part of your experiment schema. Include a run ID, step, dataset revision, and reward version in your own metadata even if the service accepts a shorter name. A checkpoint path says where the bytes are; it doesn't explain why that version should be trusted.
save_state can preserve a training resume point. save_weights_for_sampler makes a sampler path. save_weights_and_get_sampling_client combines save and client creation for a convenient evaluation step. The Cookbook's weights utilities download the adapter, build an HF model, build a PEFT adapter, or publish a checkpoint to a hub. Each export choice creates a new license and security review surface.[4][3]
Merging (W_0 + BA) turns an adapter into a standalone model directory. It doesn't make the base model license disappear. It also doesn't preserve the remote optimizer state. Keep the original tinker:// path and training metadata next to the exported model so a future evaluator can reproduce the exact adapter, base revision, tokenizer, and renderer.
Evaluation should use a fresh sampler client for the saved path or a path explicitly tied to the training client. Don't assume that a long-lived sampler automatically sees the latest adapter. A serving client built from an older path can produce plausible text from a stale policy and make a reward curve look flat.
Both pinned source snapshots use Apache-2.0. That license lets you inspect, modify, and redistribute client and recipe code under its terms. It doesn't grant rights to Thinking Machines Lab's hosted service, third-party model weights, or every dataset copied into a recipe.[2][3]
The service terms describe Tinker as online services for hosting, training, fine-tuning, evaluating, storage, and related APIs. They distinguish customer datasets, hosted models, fine-tuned models, third-party model terms, and available weights. The service may add or remove model access, and third-party models remain subject to their own terms.[7]
Your dataset is not automatically safe because the SDK serializes it. You are responsible for having the rights to send prompts, images, tool traces, and labels to a hosted service. The service terms grant the provider a limited license to access and process customer datasets to provide the service and comply with law. Personal-data deletion and export obligations depend on the applicable agreement and processing terms.[7]
Keep secrets out of the dataset and logs. API keys belong in environment variables or a secret manager. Reward traces can contain private source code, credentials emitted by a tool, or user data. Redact before logging and set checkpoint retention explicitly. A sampler checkpoint can be as sensitive as the training data because the adapter may memorize rare examples.
Data residency, subprocess behavior, storage TTL, deletion, and enterprise commitments are product questions. Don't infer them from an open-source test or from a local tinker:// path. Ask the current service and agreement for the requirement your deployment needs.
Tinker is built by Thinking Machines Lab. The company maintains the hosted service, SDK repository, Cookbook, documentation, model integrations, and customer-facing terms. The public repositories show the code and commit history, but a contributor count or a list of top contributors is only a snapshot. Use the pinned commit and the live repository history when attribution matters.
At review time, GitHub's contributor view for the SDK is led by danobi, andriigrynenko, dphuang2, and derek-tml. The Cookbook has a broader contributor set led by YujiaBao, joschu, nealwu, danobi, Tiiiger, and dphuang2. Treat that ordering as a live snapshot, not a governance hierarchy.[8][9]
The Cookbook's contribution guide asks contributors to run Ruff, Pyright, and tests, and describes an open-science workflow. Its code structure separates configuration builders from heavyweight datasets, environments, rollout strategies, and training loops. Those conventions are governance through code review and reproducibility rather than a formal foundation with independent technical steering.[3]
| Question | Evidence to inspect | Safe statement |
|---|---|---|
| Who owns hosted runtime? | Thinking Machines Lab service terms and announcements | Thinking Machines Lab operates the service |
| Who can change SDK code? | GitHub repository permissions and review history | Repository maintainers and accepted contributors |
| What is licensed? | LICENSE files at pinned commits | SDK and Cookbook source are Apache-2.0 |
| Which models are available? | Live capabilities and model docs | Catalog is dynamic and model-specific |
| Who owns your data? | Current service agreement and data processing addendum (DPA) | Follow the contract, not an inference from GitHub |
Don't call the SDK a community-governed replacement for the service. Its inspectable client and recipe layer depend on a company-operated API. That open-source boundary is valid, but it isn't the same governance model as a self-hosted distributed trainer.
You can test a reward function, a custom loss, or an environment from a CPU-oriented development machine. Thinking Machines Lab handles scheduling, resource allocation, worker placement, and failure recovery for hosted runs. This turns the first experiment into a Python problem instead of a cluster bootstrap problem.[1]
forward_backward and sample expose the token and logprob evidence that many high-level trainers hide. You can implement GRPO-style relative rewards, PPO-like clipping, DPO data preparation, prompt distillation, or a custom logprob loss. The Cookbook provides a starting point without forcing every project into one environment abstraction.[1][4]
The same TrainingClient can run cross-entropy SFT and RL losses. The same SamplingClient can generate evaluation completions and behavior-policy traces. This reduces the number of adapters in a staged post-training project.
LoRA lets a service share a frozen base model across training runs. The documented model range includes large dense and MoE models, and the general-availability announcement added Kimi K2 Thinking as a trillion-parameter example.[6][4] A researcher can test an idea on a small model, then check whether a large model is in the current catalog without rewriting the loop.
Sampler checkpoints can be evaluated through the SDK or an OpenAI-compatible interface. Cookbook utilities can download and merge adapters into Hugging Face layouts. You can move a successful experiment to an existing serving stack after validating tokenizer, license, and runtime compatibility.[6][3]
LoRA constrains the update space. A behavior that requires broad base-weight changes may not transfer, and a LoRA result doesn't answer full fine-tuning questions. If your research question is optimizer scaling, full-rank catastrophic forgetting, or expert-weight surgery, Tinker isn't the right primary system.
Your code can be deterministic while service capacity, model availability, and scheduling latency change. A pinned SDK doesn't pin the remote runtime. Record capability responses and service metadata at run start.
Every sample, grader, checkpoint, and training request crosses a boundary. A chatty environment can spend more time waiting on transport than grading. Batch prompts, use concurrency, and keep a bounded pipeline. API acceptance doesn't make secrets or high-cardinality debug payloads safe to send.
An RL sampler can continue using an older sampler checkpoint while training updates a newer adapter. A deep local queue can hide that mismatch. Include a policy version in each trajectory and reject or measure samples beyond your staleness budget.
An Apache-2.0 SDK doesn't turn a hosted run into a self-hosted run. Customer data, model weights, available checkpoints, and downstream use are governed by the current service agreement and third-party model licenses.[7]
The service can recover worker failures, but your environment may not be idempotent. If a tool call charges money, mutates a repository, or sends a message, a retry can repeat the side effect. Give every external action an idempotency key and store a local trajectory receipt.
The following standard-library example doesn't call Tinker. It checks token alignment before a local adapter submits RL data. Its small scope fits a code review or preflight test.
1from dataclasses import dataclass
2
3@dataclass(frozen=True)
4class RLDatum:
5 input_tokens: tuple[int, ...]
6 target_tokens: tuple[int, ...]
7 sampled_logprobs: tuple[float, ...]
8 advantages: tuple[float, ...]
9 mask: tuple[float, ...]
10
11def validate(datum: RLDatum) -> None:
12 lengths = {
13 len(datum.input_tokens),
14 len(datum.target_tokens),
15 len(datum.sampled_logprobs),
16 len(datum.advantages),
17 len(datum.mask),
18 }
19 if len(lengths) != 1:
20 raise ValueError(f"unaligned lengths: {sorted(lengths)}")
21 if any(value not in (0.0, 1.0) for value in datum.mask):
22 raise ValueError("mask must contain only 0.0 or 1.0")
23 if any(mask == 0.0 and advantage != 0.0 for mask, advantage in zip(datum.mask, datum.advantages)):
24 raise ValueError("masked positions must have zero advantage")
25
26datum = RLDatum(
27 input_tokens=(10, 11, 12),
28 target_tokens=(11, 12, 13),
29 sampled_logprobs=(0.0, -0.4, -0.8),
30 advantages=(0.0, 0.0, 1.5),
31 mask=(0.0, 0.0, 1.0),
32)
33validate(datum)
34print("Tinker RL contract: valid")Expected output:
1Tinker RL contract: validThis check doesn't prove the reward is meaningful or that the model used the intended checkpoint. It catches shape and mask mistakes before a remote request spends GPU time.
The happy path is short. Production debugging isn't. Start with the evidence closest to the token boundary.
| Symptom | Likely boundary | First check | Durable fix |
|---|---|---|---|
Loss is NaN | Input tensor or custom loss | Finite target, weight, logprob, and advantage values | Reject non-finite rows before submission |
| Loss falls but reward doesn't | Wrong target or reward alignment | Decode sampled tokens and inspect shifted targets | Save token-level fixture and renderer version |
| Reward is flat at zero | Degenerate groups or broken grader | Reward histogram and group variance | Skip constant groups and test verifier offline |
| Reward rises then collapses | Stale policy or excessive update | Policy version, Kullback-Leibler (KL) divergence, advantage scale, learning rate | Bound in-flight groups and tune update size |
| Requests are slow despite small data | Missed clock cycles | Submission timestamps and cycle gaps | Submit forward/backward plus optimizer before await |
| Sample text doesn't change | Stale sampler checkpoint | tinker:// path and save step | Rebuild sampler client after save |
| Resume diverges | Optimizer state omitted | Load method and checkpoint manifest | Use load_state_with_optimizer for continuation |
| Exported model differs | Base revision or tokenizer drift | Hash base files and tokenizer config | Pin base revision and preserve renderer metadata |
| Retry repeats external action | Non-idempotent environment | Tool receipts and retry logs | Add idempotency keys and replay-safe adapters |
| API returns model unavailable | Catalog or terms mismatch | Capability response at run start | Fail fast with model and account evidence |
| Data appears in logs | Over-broad telemetry | Redacted request and artifact logs | Remove secrets and set retention/TTL |
| Adapter loads but quality regresses | Unsupported target modules or rank | Training-client config and evaluation matrix | Compare rank and module scope explicitly |
An actionable incident record contains the SDK commit, Cookbook commit, model identifier, capability response, base revision, tokenizer revision, renderer name, loss name and config, optimizer parameters, sampler checkpoint path, trajectory sample, reward components, and checkpoint path. Without those fields, a replay is guesswork.
The SDK retries some transport failures through its client holder. A retry can be safe for a read, but a training request or external environment call needs a sequence contract. The pinned SDK assigns sequence IDs to training requests and preserves turn ordering across chunks. Your environment must add its own idempotency around side effects.[2]
For evaluation, prefer pure functions and immutable fixtures. For a tool call, write a durable record before invoking the tool, then mark completion with the same operation ID. If the process crashes between those writes, replay can inspect the record instead of blindly repeating the action.
A model string in a tutorial is not a production approval. The service can add or remove models. A third-party model can have a separate license, usage restriction, or attribution requirement. Query capabilities, retain the response, and link your run manifest to the model terms you accepted.[7]
Read the repositories in an order that follows one request instead of scanning every file.
src/tinker/lib/public_interfaces/service_client.py: client creation, model capability calls, and the boundary between base and saved models.src/tinker/lib/public_interfaces/training_client.py: request sequencing, chunking, forward_backward, optim_step, save, and load behavior.src/tinker/lib/public_interfaces/sampling_client.py: sample, logprob options, multiprocessing, and sampler creation.src/tinker/types/datum.py: allowed tensor fields, shape checks, and serialization assumptions.src/tinker/proto/request_conv.py: how a Datum becomes a wire request.src/tinker/lib/api_future_impl.py: how remote completion and errors reach local code.src/tinker/resources/training.py and src/tinker/resources/sampling.py: HTTP endpoint names and response conversion.src/tinker/LICENSE and pyproject.toml: license, dependencies, and package identity.The key path is TrainingClient._run_fwd_bwd to _take_turn to resources/training.py. That path explains why multiple chunks can be submitted concurrently while sequence IDs keep server order. The optimizer path uses the same turn counter. The sampler path is separate and can be created from a saved sampler checkpoint.
tinker_cookbook/rl/types.py: Env, trajectories, transitions, stop reasons, and group notation.tinker_cookbook/rl/data_processing.py: advantage centering, trajectory splitting, masks, and datum assembly.tinker_cookbook/rl/train.py: clock-cycle pipeline, loss selection, training logprob diagnostics, and checkpoint hooks.tutorials/104_first_rl.py: the smallest readable GRPO-style loop.tutorials/103_async_patterns.py: concurrent sampling and group batching.tutorials/501_export_hf.py: adapter download, merge, and validation.CONTRIBUTING.md: builder conventions, async guidance, testing, and review expectations.LICENSE: Apache-2.0 grant for the cookbook source.The source commits used here are 3eb9e87d52efacede992931b1bb51d000b0c70ed for tinker and 0b5c01eaee49bdb0d476f4f383e1c0fb9aced590 for tinker-cookbook. Future docs can change names, models, or supported losses. Pin the source before quoting line-level behavior.
Both projects matter for frontier-scale post-training, but they solve different problems.
| Dimension | Tinker | DeepEP |
|---|---|---|
| Main job | Hosted LoRA post-training API and recipe layer | Expert-parallel communication library for MoE dispatch and combine |
| Primary user | Researcher writing a local SFT or RL loop | Systems engineer building a distributed MoE runtime |
| Compute ownership | Thinking Machines Lab's managed workers | Your cluster, accelerator fabric, and process groups |
| Core interface | forward_backward, optim_step, sample, checkpoints | Dispatch/combine buffers, all-to-all transport, synchronization |
| Trainable state | LoRA adapter plus remote optimizer state | No model-training policy or reward loop |
| RL abstraction | Rewards, trajectories, advantages, masks, loss names | None. It moves expert tokens and gradients efficiently |
| Model support | Service catalog and adapter integrations | Runtime and hardware constraints you configure |
| License boundary | Apache-2.0 SDK/Cookbook plus hosted terms | Apache-2.0 source plus your infrastructure and model terms |
| Failure focus | API retries, stale sampler, checkpoint, data and terms | Rank failure, collective hang, buffer capacity, network topology |
DeepEP can sit below a self-hosted trainer that runs models like DeepSeek's MoE systems. It doesn't replace the RL loop. Tinker can help a researcher run a reward experiment on an available MoE model without assembling expert-parallel collectives. It doesn't expose DeepEP's dispatch buffers, RDMA tuning, or expert routing internals.
The projects complement each other in a curriculum. Tinker teaches the algorithm contract and service boundary. DeepEP teaches the communication contract that makes sparse expert training and inference fit a real cluster. Megatron or another distributed trainer connects both contracts into a full-parameter or adapter training runtime.
Don't benchmark them as alternatives. A hosted service's wall-clock latency and a communication kernel's tokens-per-second number answer different questions. Compare the total experiment cost and reproducibility boundary, not one operation's microbenchmark.
Use a tiny deterministic task before trying a large model.
The exercise should produce a run manifest alongside reward numbers. Record the model identifier, rank, target modules, renderer, loss, optimizer settings, checkpoint path, sample tokens, reward list, group mean, and mask counts. A manifest lets another reader inspect every contract boundary; screenshots omit that evidence.
For a large model, scale one dimension at a time. Increase prompt length, group size, number of concurrent environments, adapter rank, or pipeline depth separately. A reward change after four simultaneous changes can't tell you which boundary moved.
forward_backward computes gradients, optim_step applies them, sample produces token evidence, and save_state creates a training recovery point.Answer every question, then check your score. Score 75% or higher to mark this lesson complete.
8 questions remaining.
Announcing Tinker
Thinking Machines Lab · 2025
Tinker Python SDK
Thinking Machines Lab · 2026
Tinker Cookbook
Thinking Machines Lab · 2026
Tinker Documentation
Thinking Machines Lab · 2026
LoRA Without Regret
Thinking Machines Lab · 2026
Tinker General Availability
Thinking Machines Lab · 2026
Tinker Terms of Service
Thinking Machines Lab · 2026
Tinker Python SDK Contributors
Tinker Contributors · 2026
Tinker Cookbook Contributors
Tinker Cookbook Contributors · 2026
Questions and insights from fellow learners.