Tinker Python SDK
repository·main·Indexed 20 days ago
https://github.com/thinking-machines-lab/tinkerThe official Python SDK for the Tinker API (v0.24.0), providing programmatic access to the Tinker platform. It includes a RestClient for managing training runs, checkpoints, and metadata, and a SamplingClient for text generation and inference. The SDK features a unified interface for synchronous and asynchronous operations via APIFuture and AwaitableConcurrentFuture, and provides comprehensive error handling through a hierarchy of TinkerError, APIError, and SidecarError exceptions.
What's inside tinker
- Tinker is a Python SDK designed for interacting with the Tinker platform. For detailed technical documentation, API references, and advanced usage guides, visit the official documentation site at tinker-docs.thinkingmachines.ai.
Use SamplingClient for text generation and inference
mainThe
SamplingClientis used to generate text tokens from either a base model or weights saved via aTrainingClient.Obtaining a Client
You typically obtain a
SamplingClientthrough one of two methods:service_client.create_sampling_client(): To use a base model.training_client.save_weights_and_get_sampling_client(): To use specific trained weights.
Multi-processing and Subprocess Isolation
- Multi-processing:
SamplingClientis picklable and safe to pass to multiple processes/workers. However, you should always create the client in the main process and then pass it to workers.ServiceClientandTrainingClientmust remain managed in the main process. - Subprocess Isolation: To prevent CPU-heavy user code (like grading or environment interactions) from stalling networking IO and heartbeats due to GIL contention, set the environment variable
TINKER_SUBPROCESS_SAMPLING=1. This runssample()andcompute_logprobs()in a dedicated subprocess transparently.
sampling_client = service_client.create_sampling_client(base_model="Qwen/Qwen3-8B") prompt = types.ModelInput.from_ints(tokenizer.encode("The weather today is")) params = types.SamplingParams(max_tokens=20, temperature=0.7) future = sampling_client.sample(prompt=prompt, sampling_params=params, num_samples=1) result = future.result()Requirements for generating Tinker SDK documentation
mainWhen generating documentation for the Tinker Python SDK, note the following constraints:
- Only types, classes, and methods that have an attached doc-string will have documentation generated.
- Generated artifacts must be checked into the repository.
Resume training from a checkpoint
mainTo resume training from saved weights, you can use two different methods depending on whether you need to restore the optimizer state:
create_training_client_from_state(path): Loads only the model weights. The optimizer state is reset. Use this to continue training from a specific weight checkpoint without necessarily needing the previous momentum.create_training_client_from_state_with_optimizer(path): Loads both model weights and the optimizer state (e.g., Adam momentum). Use this to resume training exactly where it left off for maximum continuity.
# Option 1: Weights only, optimizer resets training_client = service_client.create_training_client_from_state( "tinker://run-id/weights/checkpoint-001" ) # Option 2: Full state restoration (weights + optimizer momentum) training_client = service_client.create_training_client_from_state_with_optimizer( "tinker://run-id/weights/checkpoint-001" )How AwaitableConcurrentFuture bridges concurrent.futures and asyncio
mainAn
AwaitableConcurrentFutureis a specific implementation ofAPIFuturethat wraps a standard Pythonconcurrent.futures.Future.It is primarily used by Tinker API methods to bridge the gap between thread-based concurrency (
concurrent.futures) and coroutine-based concurrency (asyncio). This allows you to take a standard thread-based future andawaitit in an async loop, or use standardconcurrent.futuresmethods like.done()on the underlying object.# Typically received from API methods api_future = rest_client.get_training_run("run-id") # Use it asynchronously result = await api_future # Or use it synchronously result = api_future.result()Monitor MoE training metrics in ForwardBackwardOutput
mainWhen training Mixture of Experts (MoE) models,
ForwardBackwardOutputprovides telemetry to monitor expert routing health.Key Metrics:
e_frac_with_tokens:mean: Fraction of experts receiving at least one token. A value decreasing over time suggests routing collapse.e_frac_oversubscribed:mean: Fraction of experts receiving more than the 'perfect balance' (total tokens / num experts). Increasing values are concerning.e_max_violation:mean: The average amount the most overloaded expert exceeds perfect balance.e_max_violation:max: The worst-case load imbalance in any single layer.e_min_violation:mean: How much the least loaded expert is below perfect balance. Typically negative; values becoming more negative over time are concerning.
Use the RestClient for Tinker API operations
mainThe
RestClientis used for REST API operations such as listing checkpoints, retrieving metadata, and managing training runs. You typically obtain an instance by callingservice_client.create_rest_client().Key capabilities include:
- Listing checkpoints (training or sampler) and user checkpoints.
- Retrieving training run information and metadata.
- Managing checkpoint visibility (publishing/unpublishing) and TTL (Time To Live).
- Downloading checkpoint archives via signed URLs.
- Deleting checkpoints.
rest_client = service_client.create_rest_client() training_run = rest_client.get_training_run("run-id").result() print(f"Training Run: {training_run.training_run_id}, LoRA: {training_run.is_lora}") checkpoints = rest_client.list_checkpoints("run-id").result() print(f"Found {len(checkpoints.checkpoints)} checkpoints") for checkpoint in checkpoints.checkpoints: print(f" {checkpoint.checkpoint_type}: {checkpoint.checkpoint_id}")How APIFuture objects handle async and sync operations
mainAn
APIFutureis an abstract base class for handling asynchronous operations that provides a unified interface for both synchronous and asynchronous access. This allows you to use the same object in different execution contexts.- In an async context: You can
awaitthe future directly or useawait future.result_async(). - In a sync context: You can call
future.result(), which will block the current thread until the operation completes.
This pattern is useful when an API method returns a handle to a long-running task, and you need the flexibility to either wait for it immediately or integrate it into an
asyncioevent loop.# In async context future = training_client.forward_backward(data, "cross_entropy") result = await future # Or await future.result_async() # In sync context future = training_client.forward_backward(data, "cross_entropy") result = future.result()- In an async context: You can
Generate Tinker SDK documentation
mainTo generate the auto-generated API documentation for the Tinker Python SDK, run the documentation generation script using
uv.uv run scripts/generate_docs.pySave and load model checkpoints
mainSave weights
Use
save_state(name, ttl_seconds=None)to save model weights to persistent storage. Returns anAPIFuturecontaining the checkpoint path.Load weights (weights only)
Use
load_state(path)to load model weights from a Tinker path (e.g.,"tinker://run-id/weights/checkpoint-001"). This does not restore optimizer state (e.g., Adam momentum), so the optimizer will reset.Load weights and optimizer state
Use
load_state_with_optimizer(path)to restore both model weights and the optimizer state. This is required to resume training with the same momentum and optimizer trajectory.# Save after training save_future = training_client.save_state("checkpoint-001") result = await save_future print(f"Saved to: {result.path}") # Resume training with restored optimizer momentum load_future = training_client.load_state_with_optimizer( "tinker://run-id/weights/checkpoint-001" ) await load_futureExport weights for inference with SamplingClient
mainTo use trained weights for inference, you can either save them specifically for a sampler or create a client directly from the current state.
Save specifically for a sampler
Use
save_weights_for_sampler(name, ttl_seconds=None)to get a path specifically intended for aSamplingClient.Create a SamplingClient from a path
Use
create_sampling_client(model_path, retry_config=None)with a saved weight path.Direct conversion
Use
save_weights_and_get_sampling_client(name=None, retry_config=None)to save the current weights and immediately return aSamplingClientconfigured with them.# Option 1: Save and then create client save_future = training_client.save_weights_for_sampler("sampler-001") result = await save_future sampling_client = service_client.create_sampling_client(model_path=result.path) # Option 2: Direct conversion sampling_client = training_client.save_weights_and_get_sampling_client() # Use for inference prompt = types.ModelInput.from_ints(tokenizer.encode("Hello")) params = types.SamplingParams(max_tokens=20) result = sampling_client.sample(prompt, 1, params).result()How to use APIFuture for sync and async operations
mainThe
APIFutureclass provides a unified interface for handling asynchronous operations that can be accessed in both synchronous and asynchronous contexts. This is the standard way to handle results from Tinker API methods that involve long-running or concurrent tasks.Asynchronous Usage
In an
asynccontext, you can await the future directly or use theresult_async()method. Awaiting the object directly is equivalent to callingawait future.result_async().# In async context future = training_client.forward_backward(data, "cross_entropy") result = await future # Or await future.result_async()Synchronous Usage
In a synchronous context, use the
result()method. This call will block the current thread until the operation completes or the timeout is reached.# In sync context future = training_client.forward_backward(data, "cross_entropy") result = future.result()# In async context future = training_client.forward_backward(data, "cross_entropy") result = await future # In sync context future = training_client.forward_backward(data, "cross_entropy") result = future.result()