Metadata-Version: 2.4
Name: tinker-cost
Version: 0.1.0
Summary: Cost estimation for the Thinking Machines Lab Tinker SDK
Project-URL: Repository, https://github.com/confiscate/tinker-cost
Author-email: Henry Lau <confiscate@gmail.com>
License-Expression: Apache-2.0
Keywords: cost estimation,machine learning,tinker
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.11
Requires-Dist: click>=8.0.0
Requires-Dist: rich>=13.0.0
Description-Content-Type: text/markdown

# tinker-cost

Cost estimation for the [Thinking Machines Lab Tinker SDK](https://github.com/thinking-machines-lab/tinker).

Pass in the `Datum` list you already built for training — no manual token counting required. Token lengths are read directly from `datum.model_input.length`.

> This package implements the feature requested in [thinking-machines-lab/tinker-cookbook#298](https://github.com/thinking-machines-lab/tinker-cookbook/issues/298). A [PR to the Tinker SDK](https://github.com/thinking-machines-lab/tinker/pulls) that integrates this directly as `tinker cost` is pending review.

## Installation

```bash
pip install tinker-cost
# or
uv add tinker-cost
```

## Usage

### Estimate training cost

```python
from tinker_cost import estimate_training_cost

# `data` is the same List[Datum] you pass to forward_backward — no changes needed
est = estimate_training_cost(
    "Qwen/Qwen3.5-9B",
    data=my_dataset,
    num_epochs=3,
    batch_size=8,
)

print(f"Dataset:        {est.num_datums} datums")
print(f"Steps:          {est.num_steps}")
print(f"Avg seq length: {est.avg_tokens_per_datum:.0f} tokens")
print(f"Total tokens:   {est.total_tokens:,}")
print(f"Estimated cost: ${est.total_cost:.2f}")
```

```
Dataset:        2000 datums
Steps:          750
Avg seq length: 512 tokens
Total tokens:   3,072,000,000
Estimated cost: $4087.30
```

### Estimate sampling cost

```python
from tinker_cost import estimate_sampling_cost

# `prompts` is a List[ModelInput] or List[SampleRequest]
est = estimate_sampling_cost(
    "Qwen/Qwen3.5-9B",
    prompts=my_prompts,
    max_new_tokens=512,
)

print(f"Prompts:        {est.num_prompts}")
print(f"Prompt tokens:  {est.total_prompt_tokens:,}")
print(f"Prefill cost:   ${est.prefill_cost:.4f}")
print(f"Sample cost:    ${est.sample_cost:.4f}")
print(f"Estimated cost: ${est.total_cost:.4f}")
```

### Check rates

```python
from tinker_cost import PRICING

prefill_rate, sample_rate, train_rate = PRICING["Qwen/Qwen3.5-9B"]
print(f"Train: ${train_rate}/M tokens")
```

## API reference

### `estimate_training_cost(model, data, *, num_epochs, batch_size) → TrainingCostEstimate`

| Parameter | Type | Description |
|---|---|---|
| `model` | `str` | Tinker model ID |
| `data` | `Sequence[Datum]` | Your full training dataset — token lengths read from `datum.model_input.length` |
| `num_epochs` | `int` | How many times the full dataset is trained over (default: 1) |
| `batch_size` | `int` | Datums per `forward_backward` call — used to compute `num_steps` for reporting only, does not affect cost (default: 1) |

**`TrainingCostEstimate` fields:** `model`, `num_datums`, `total_tokens`, `num_steps`, `avg_tokens_per_datum`, `total_cost`

---

### `estimate_sampling_cost(model, prompts, *, max_new_tokens) → SamplingCostEstimate`

| Parameter | Type | Description |
|---|---|---|
| `model` | `str` | Tinker model ID |
| `prompts` | `Sequence[ModelInput \| SampleRequest]` or `int` | Prompt objects or total prompt token count |
| `max_new_tokens` | `int` | Max output tokens per prompt (worst-case) |

**`SamplingCostEstimate` fields:** `model`, `num_prompts`, `total_prompt_tokens`, `max_new_tokens`, `prefill_cost`, `sample_cost`, `total_cost`

---

## How it works

### How the standalone package relates to the Tinker SDK

This package has no hard dependency on `tinker`. It reads token counts via duck typing — it calls `.model_input.length` on whatever objects you pass in. If those objects are `tinker.Datum` instances (which have that property), it works. You install both packages separately and they never interact at import time:

```bash
pip install tinker tinker-cost
```

In your code you import from `tinker` as normal and call `tinker_cost.estimate_training_cost` with the `Datum` objects you already have.

### How token counts are calculated

**Training** (`estimate_training_cost`): token count per datum comes from `datum.model_input.length`. The total is `sum(lengths) × num_epochs`. A `forward_backward` call is billed at the **train** rate per token, which covers the full forward + backward pass. Prefill and sample rates are not used here.

**Sampling** (`estimate_sampling_cost`): prompt token count comes from `model_input.length` (or `sample_request.prompt.length`). Prompt tokens are billed at the **prefill** rate; generated tokens are estimated as `num_prompts × max_new_tokens` and billed at the **sample** rate.

### Pricing table

Rates are **hardcoded** from the [Tinker models page](https://tinker-docs.thinkingmachines.ai/tinker/models/) and embedded at package release time — no network requests are made at runtime. Every call is pure local arithmetic. The tradeoff is that when TML updates their prices, this package needs a new release. The `# Source:` comment in `_estimator.py` links directly to where to get updated values.
