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

# Custom algorithms

> Write a local Python training loop and loss while Flash manages the GPU and checkpoints.

Use `algorithm = "custom"` for DPO, distillation, or another objective that needs
its own training loop. Your Python script runs on your machine and calls a
managed Flash GPU for sampling, scoring, backward passes, and optimizer steps.
You own data loading, prompt formatting, rewards, and logging.

Custom training currently supports **text-only `Qwen/Qwen3.5-9B` on one Modal
H100**, with up to **4,096 tokens per sequence** and **LoRA rank 64**. Other
models, images, multi-GPU shapes, and the built-in `thinking` switch are not
supported in this mode. Custom distillation scripts supply their own teacher access.

## Start from a DPO example

Install the custom-loss extra in the Python environment running the CLI, then
create an editable script and matching config:

```bash theme={null}
pip install --upgrade 'freesolo-flash[custom]'
flash login
flash custom init algorithm.py --project <project-uuid>
```

`flash custom init` creates `algorithm.py` and `algorithm.toml`, preserving
existing files by refusing to overwrite them. The starter demonstrates paired
chosen/rejected answers, a frozen reference distribution, a local PyTorch DPO
loss, metrics, and checkpoint state. Replace its toy pairs with your data.

```toml algorithm.toml theme={null}
project = "<project-uuid>"
model = "Qwen/Qwen3.5-9B"
algorithm = "custom"

[custom]
file = "algorithm.py"
max_wall_seconds = 3600
idle_seconds = 900

[train]
max_steps = 10
save_every = 2
```

The script path is relative to the config file. Do not add `[environment]`,
`[wandb]`, or `thinking`: the local script owns those concerns. Set learning
rates in `client.optim_step(...)`, not in `[train]`. See
[Custom configuration](https://docs.freesolo.co/reference/configuration.md#custom) for the accepted fields.

```bash theme={null}
flash train algorithm.toml --dry-run
flash train algorithm.toml --cost
flash train algorithm.toml
```

Both previews require authentication and a server advertising custom-training
support. They validate without allocating a GPU. The quote is a session cost
cap based on `max_wall_seconds`, not a prediction of your script's runtime.

## Write the training loop

Define `async def main(client)` for nonblocking local control, or
`def main(client)` for blocking calls. For the async form, await every operation:

| Operation | Use it to |
| - | - |
| `encode(text)`, `decode(tokens)` | Convert between text and the model's tokens. |
| `sample(prompt, max_tokens=64, temperature=1.0, num_samples=1)` | Generate from a token-ID prompt. |
| `forward(data, top_k=0)` | Score the supplied token sequences; optionally return up to 20 top alternatives per position. |
| `forward_backward(data, metrics=None)` | Accumulate gradients for an explicitly weighted token loss. |
| `forward_backward_custom(data, loss_fn)` | Compute a differentiable loss locally and accumulate its gradients on the GPU. |
| `optim_step(learning_rate=..., max_grad_norm=1.0, algorithm_state=...)` | Apply accumulated gradients and advance the optimizer step. |
| `zero_grad()` | Clear accumulated gradients without an update. |
| `save_checkpoint(name, algorithm_state=...)` | Save at a completed step that does not already have a durable checkpoint; names must be unique and `step-N` names are reserved. |
| `status()`, `logs(after=0)` | Inspect the session and its operation logs. |

Construct batches with `from flash.custom import Datum`. A `Datum` holds a list
of token IDs and optional weights. Position `i` scores the next token,
`tokens[i + 1]`, so each row has `len(tokens) - 1` log probabilities or weights.
A batch contains at most 16 rows. Each row has at least two tokens and must fit
`train.max_context_tokens`.

`forward_backward` requires explicit weights and computes the sum of
`-weight * logprob` across tokens and rows. Set prompt-target weights to zero
and apply your own averaging denominator. Gradients accumulate until
`optim_step` or `zero_grad`.

For `forward_backward_custom`, define `loss_fn(data, logprobs)` returning a
finite scalar PyTorch tensor and a dictionary of finite numeric metrics. The
provided log-probability tensors support local autograd, including losses that
compare multiple sequences. The generated DPO starter shows this pattern.

Operations within one session execute in order. Async control lets other local
work run while awaiting the GPU; it does not run simultaneous optimizer
mutations. If a request fails with an unresolved outcome, call
`retry_pending()` before issuing another operation. This retries the pending
operation without applying it twice.

## Keep the controller running

The `flash train` process is your controller. Run it in a persistent terminal
such as `tmux`; `--background` is rejected for custom training. **Ctrl-C cancels
the run** and preserves completed checkpoints. An unhandled script exception
also cancels it. This differs from SFT and GRPO, where Ctrl-C only detaches.

Flash reserves the GPU while your script prepares data or waits for external
rewards. Charges use measured session time, capped at the accepted wall-time
quote. The session also stops at its idle or wall-time limit. See
[Custom-session billing](https://docs.freesolo.co/reference/cost-model.md#custom-session-billing).

Use the normal run commands from another terminal:

```bash theme={null}
flash runs status <run-id> --json
flash runs log <run-id> --follow
flash runs checkpoint <run-id>
flash runs cancel <run-id>
```

## Resume a checkpoint

Pass a JSON-serializable `algorithm_state` object of at most 1 MiB to `optim_step` so periodic saves
include your data cursor, reference scores, and other algorithm state. Automatic
saves occur on the `save_every` cadence, at `save_at_steps`, and at the final
step; the final adapter is also published.
The script is responsible for restoring its own state from
`client.restored_state` and continuing from `client.step`.

To continue, submit a **new run** with the exact saved step:

```toml theme={null}
[custom]
file = "algorithm.py"
resume_from = "<source-run-id>/step-10"

[train]
max_steps = 20
save_every = 2
```

Use a custom checkpoint from the same base model, with matching LoRA rank and
alpha. The new `max_steps` must exceed the restored step and is the total target,
not the number of additional steps. `/final` and `train.init_from_adapter` are
not custom-resume selectors. In-place `load_checkpoint` is unavailable for
managed sessions; use `custom.resume_from` for durable continuation.

Custom runs do not automatically retry a failed local controller. Restart from
a completed checkpoint. Deploy or download the saved adapter with the normal
[checkpoint workflow](https://docs.freesolo.co/guides/deploy-and-chat.md).


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