> ## Documentation Index
> Fetch the complete documentation index at: https://docs.together.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Supervised fine-tuning quickstart

> Run your first post-training session with supervised fine-tuning on instruction-following data, then download the trained weights.

[Supervised fine-tuning (SFT)](/docs/post-training/sft) trains a model to imitate example responses. Each example pairs a prompt with the response you want, and training makes the model more likely to produce that response. Use it when you have demonstrations of the behavior you want, such as human-written answers or a fixed output format. SFT needs no reward function, so it's the simplest method to start with.

In this tutorial, you'll train a low-rank adapter (LoRA) on the [No Robots](https://huggingface.co/datasets/HuggingFaceH4/no_robots) dataset with a ready-made recipe from the [post-training cookbook](https://github.com/togethercomputer/post-training-cookbook), then download the trained weights.

## Requirements

Before you begin, make sure you have:

* Python 3.11 or later and [uv](https://docs.astral.sh/uv/getting-started/installation/).
* A [Together AI account](https://api.together.ai/) with a credit balance of at least \$100, the minimum for creating a model resource. See [Pricing](/docs/post-training/pricing#minimum-credit-balance).
* Your API key exported as `TOGETHER_API_KEY`. Get one from [API keys](https://api.together.ai/settings/api-keys) in the Together AI console.
* A Weights & Biases API key exported as `WANDB_API_KEY`. Most cookbook recipes log to Weights & Biases, including the one in this quickstart. To run without an account, export `WANDB_MODE=offline` instead.
* Optionally, a Hugging Face token exported as `HF_TOKEN` for gated models or datasets.

```bash theme={null}
export TOGETHER_API_KEY=<your_key>
export WANDB_API_KEY=<your_wandb_key>     # or: export WANDB_MODE=offline
export HF_TOKEN=<your_huggingface_token>  # gated models and datasets only
```

## Step 1: Clone the cookbook

Clone the cookbook and install its dependencies:

```bash theme={null}
git clone https://github.com/togethercomputer/post-training-cookbook.git
cd post-training-cookbook
uv sync --locked
```

## Step 2: Review the recipe

Open `recipes/sft/no_robots.py`. The recipe defines a config object and passes it to the cookbook's SFT training loop:

```python recipes/sft/no_robots.py theme={null}
from post_training_cookbook.sft.config import SFTConfig
from post_training_cookbook.sft.train import TRAIN_ON_LAST, train

config = SFTConfig(
    model="Qwen/Qwen3.8-27B",
    enable_thinking=False,
    dataset_name="HuggingFaceH4/no_robots",
    dataset_split="train",
    batch_size=8,
    learning_rate=1e-4,
    max_length=4096,
    max_steps=50,
    train_on_what=TRAIN_ON_LAST,
    training_checkpoint_interval=50,
    wandb_project="sft-no-robots",
)

if __name__ == "__main__":
    train(config)
```

Each No Robots example is a conversation with a user message and a human-written assistant response. The recipe trains the model to produce the assistant response from the conversation that precedes it. These settings shape the run:

| Setting | What it does |
| - | - |
| `model="Qwen/Qwen3.8-27B"` | Selects the base model to train from the [catalog](/docs/post-training/supported-models). |
| `enable_thinking=False` | Turns off the `<think>` output that Qwen3.8-27B's chat template enables by default. With thinking on, the template renders each prompt differently from the full conversation, so the recipe skips every row and the run ends with nothing trained. |
| `train_on_what=TRAIN_ON_LAST` | Trains on the final assistant turn only. `TRAIN_ON_ALL` trains on every assistant turn. |
| `max_length=4096` | Leaves room for the longer multi-turn conversations in the dataset. |
| `max_steps=50` | Keeps the run short enough to finish in minutes. Without a cap, `train` makes a full pass over the dataset. |
| `training_checkpoint_interval=50` | Saves a training checkpoint at the last step, which you need to [continue training this adapter with GRPO](#continue-with-grpo). |

Under the hood, `train` provisions a trainer-only model resource and attaches a session. For each batch, it runs `forward_backward` with the cross-entropy loss, then `optim_step`. [Supervised fine-tuning](/docs/post-training/sft) explains each piece.

## Step 3: Run training

Run the recipe:

```bash theme={null}
uv run python recipes/sft/no_robots.py
```

The recipe prints status lines while it waits for the model resource and the session to become ready, which usually takes a few minutes and longer when GPUs are scarce. If the resource is still queued after an hour, the SDK stops it and the recipe raises `TimeoutError`, as [Create a resource](/docs/post-training/model-resources#create-a-resource) explains. Then the recipe prints one line per step. The first step takes longer than the rest, and the inference checkpoint at the end can take several minutes to save. Expect output like this:

```text theme={null}
[model-resources:3b9d6e2c-8f41-4a7e-b5d0-9e2c7a1f4d63] created, waiting for READY status...
[model-resources:3b9d6e2c-8f41-4a7e-b5d0-9e2c7a1f4d63] status=MODEL_RESOURCES_STATUS_PENDING elapsed=0.1s
...
[model-resources:3b9d6e2c-8f41-4a7e-b5d0-9e2c7a1f4d63] ready in 122.6s
[session:7c2e9f1a-4b3d-4e8a-9c21-0f6d2a8b5e47] created, waiting for RUNNING status...
...
[session:7c2e9f1a-4b3d-4e8a-9c21-0f6d2a8b5e47] ready in 61.2s
Session ready: 7c2e9f1a-4b3d-4e8a-9c21-0f6d2a8b5e47
Run artifacts: runs/Qwen3.8-27B_sft-no-robots_1.00e-04_8_20261001T153000Z
[step 1] loss=1.8907 (loss_sum=4985.7 over 2637 slots) lr=0.0001 samples=8 elapsed=117.2s (fb=116.4s optim=0.8s)
[step 2] loss=1.8073 (loss_sum=2665.8 over 1475 slots) lr=9.8e-05 samples=8 elapsed=122.2s (fb=4.1s optim=0.8s)
...
[step 50] loss=1.6347 (loss_sum=2468.4 over 1510 slots) lr=2e-06 samples=8 elapsed=316.9s (fb=4.2s optim=0.5s)
[step 50] training checkpoint: <training_checkpoint_id>
[step 50] inference checkpoint: <inference_checkpoint_id>
Training completed in 606.9s
```

`loss` is the mean cross-entropy per trained token, so it stays comparable across steps even as the batch's token count changes. It varies from batch to batch because each batch holds different examples, so watch its trend rather than single steps. `lr` decays linearly toward zero over the run.

The recipe also logs each step to `metrics.jsonl` in the run directory and to Weights & Biases. At the last step, it saves a training checkpoint and an inference checkpoint, then stops the session and the model resource.

## Step 4: Retrieve the trained model

The final inference checkpoint is registered in your project's model registry. The `inference checkpoint` line prints its ID. The run directory records the checkpoint and the session that produced it:

```bash theme={null}
cat runs/*/manifest.json | grep session_id
cat runs/*/checkpoints.jsonl
```

`checkpoints.jsonl` holds one `training` entry and one `inference` entry, each with its `step` and `ref`. Each `ref` is the checkpoint's ID.

To download the weights, replace `<session_id>` with the ID from `manifest.json` and run the following. The script reads the session's last inference checkpoint and passes its ID to `download_checkpoint`. The merged variant is the full model, about 56 GB for Qwen3.8 27B, so check your free disk space first:

```python theme={null}
from together import Together
from together.post_training import download_checkpoint

client = Together()
session = client.post_training.sessions.retrieve("<session_id>")
checkpoint = session.inference_checkpoints[-1]
print(checkpoint.step, checkpoint.id)

paths = download_checkpoint(
    client,
    checkpoint.id,
    variant="CHECKPOINT_VARIANT_MERGED",
    output_dir="./sft_model",
)
print(f"Downloaded {len(paths)} files")
```

`CHECKPOINT_VARIANT_MERGED` downloads the base model with the adapter merged in, ready for any inference framework. `CHECKPOINT_VARIANT_ADAPTER` downloads only the adapter weights, in a layout PEFT can't load, as [Download the weights](/docs/post-training/checkpoints#download-the-weights) explains. To serve the model on Together AI, see [Deploy a checkpoint](/docs/post-training/deployment).

<Check>
  You trained a LoRA with post-training and downloaded its weights.
</Check>

## Confirm the resource stopped

The recipe stops the session and the model resource when it exits, so billing has already ended. If the run was interrupted, the resource may still be billing, or it may not be ready yet and start billing once it is. This script finds your resources that are waiting for capacity (`PENDING`), starting up (`CREATING`), or ready (`READY`), and stops each one:

```python theme={null}
from together import Together

client = Together()
for status in (
    "MODEL_RESOURCES_STATUS_PENDING",
    "MODEL_RESOURCES_STATUS_CREATING",
    "MODEL_RESOURCES_STATUS_READY",
):
    resources = client.post_training.model_resources.list(
        status=[status], created_by="me"
    ).data
    for resource in resources:
        print(resource.id, resource.status, resource.base_model)
        client.post_training.model_resources.stop(resource.id, force=True)
```

`created_by="me"` limits the list to resources you created, so the script leaves other people's resources in a shared project alone. `force=True` also stops any session that's still attached. Without it, the API refuses to stop a resource while a session is active.

If you have other runs in progress, the script stops their resources too. To stop only this run's resource, pass the ID from the `[model-resources:<id>] created` line in the run's output to `client.post_training.model_resources.stop("your_resource_id", force=True)`.

You can also stop resources from the **Model resources** tab under [Post-training](https://api.together.ai/custom-training/resources) in the console. [Manage model resources](/docs/post-training/model-resources) covers stopping, idle timers, and what a resource costs while it runs.

## Continue with GRPO

A common pattern is to teach the model a format with SFT, then optimize for task accuracy with group relative policy optimization (GRPO). The GRPO run continues the same adapter from the training checkpoint this recipe saved. [Chain SFT and GRPO](/docs/post-training/sft#chain-sft-and-grpo) shows how.

## Next steps

<CardGroup cols={2}>
  <Card title="RL quickstart" icon="refresh" href="/docs/post-training/quickstart-rl">
    Train a model with GRPO and an exact-match reward on grade-school math.
  </Card>

  <Card title="Supervised fine-tuning" icon="book" href="/docs/post-training/sft">
    Loss masks, data preparation, and chaining SFT into GRPO.
  </Card>

  <Card title="Concepts" icon="bulb" href="/docs/post-training/concepts">
    How work splits between your code and Together AI GPUs, and the model resources, sessions, and checkpoints behind the recipe.
  </Card>

  <Card title="Deploy a checkpoint" icon="rocket" href="/docs/post-training/deployment">
    Deploy the trained model on dedicated model inference.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.