Skip to main content
Describe a run in a TOML config, then pass it to flash train. Every run requires project, model, and environment.id. algorithm defaults to sft. Run the submit-time checks without starting a run using flash train config.toml --dry-run; it allocates no training GPU, though an SFT config with no cached workload profile starts a billed profile run first (see flash train).

Top level

[environment]

Environment code reads the secret normally:
Never put secret values in [environment.params]; those values are recorded on the run. Every name you list under [environment].secrets must resolve to a value in your shell, .env, or .env.local when you submit. Names such as FREESOLO_API_KEY, RUN_ID, and GITHUB_TOKEN are reserved by Flash.

Scorer dependencies

If your reward function imports a third-party package the managed runtime does not ship, declare it under [environment] pip and the worker installs it before your environment loads:
Each entry is a requirement string, so the list form is required - pip = "pymongo" is rejected with the bracketed form to use. Entry syntax is validated at submit, before a GPU is allocated, because a malformed entry would otherwise fail mid-install on hardware you are already paying for:
  • Only requirements, never pip options. A leading - (--no-deps, --target=..., --extra-index-url=...) is rejected: those change how the mandatory worker requirement installs.
  • No credentials in a URL. The spec is stored and uploaded in plaintext, so a token in a direct or VCS requirement URL would be written to the run record. Any userinfo before the host is rejected, as is any query string.
Private requirements that need authentication to install are therefore not supported: [environment] secrets supplies values to your environment at runtime, after installation, so it cannot authenticate pip. Vendor the package into the folder you publish, or depend on a publicly installable one. Your requirements install alongside Flash’s own managed worker requirement rather than replacing it, so the runtime your environment loads into is never displaced. A transient package-index failure is retried before the run is given up on; a genuine resolution or build failure is terminal, because retrying it would only burn GPU time. A dependency you forgot to declare otherwise surfaces on the worker as a scorer that cannot import. flash env test runs against your local interpreter, so a package installed on your machine but missing from pip passes locally and fails only once the run starts - declare everything your scorer imports, rather than relying on a local pass.

[train]

[train] is one flat table shared by all three algorithms. Keys that apply everywhere are below; the rest are scoped by algorithm and a knob the run’s algorithm cannot consume is rejected at submit rather than silently ignored. See Knobs are scoped by algorithm. --dry-run resolves warm-start rank and alpha for you. Local --cost may warn and estimate with defaults instead.

Knobs are scoped by algorithm

There are no [sft], [grpo], or [opd] tables: every knob lives under [train], and the error names the key you set. --dry-run surfaces these without allocating a GPU.

GRPO and OPD need a stated horizon

SFT measures its dataset, so an uncapped SFT config prices fine. GRPO and OPD quote offline from the catalog with nothing measuring the pool, so a config that sets neither max_examples nor a positive max_steps is refused rather than quoted:
Set either one. [environment.params] max_examples does not count: it reaches your environment as an opaque load_environment() kwarg that the starter templates ignore, so only [train] max_examples is applied by the worker.

The optimizer batch has a different name per algorithm

SFT authors batch_size; GRPO and OPD author prompts_per_step. They are not interchangeable, so each is rejected under the other algorithm, naming the key you meant:
Under SFT, a measured workload profile turns batch_size into examples-per-update against your real tokenized dataset. Under GRPO and OPD there is no profile: prompts_per_step is the optimizer batch, and each step samples prompts_per_step x group_size completions. Do not carry a value across when porting a config: a typical SFT batch_size is far smaller than the 64 GRPO and 8 OPD defaults.

SFT-specific

Its only algorithm-only key is batch_size - every rollout knob is rejected, because SFT trains on your dataset’s completions and never generates.

When sequence packing is off

Flash packs multiple examples into one sequence when it can prove the packing is boundary-safe, which every catalog model on the managed worker image qualifies for. Multimodal rows are never packed. --cost, --dry-run, and train all warn when your run cannot pack. An unpacked run trains exactly one example per optimizer update, so batch_size no longer groups examples. It is not inert, though - it still keys the workload profile and sizes the GPU for an auto-sized run, so changing it can bill another profile run and move the card you land on. The default learning rate is tuned for a batched update, so expect noisier steps and lower learning_rate if you are comparing against a packed run.

GRPO-specific

OPD-specific

OPD derives updates from epochs, retained prompts, and prompts_per_step unless positive max_steps sets an exact horizon; one of those or max_examples is required. An SFT warm-start is recommended. There is no auxiliary EOS loss. For termination problems, use an SFT warm-start, verify teacher quality, set a hard prompt budget, add suitable stop_sequences, and inspect truncated_rollouts. The local --cost estimate may itemize teacher usage for diagnosis, but only GPU time is charged.

Managed teachers

The teacher is platform-managed: teacher_model takes one of these friendly aliases, and a provider or repository identifier is never accepted. An unsupported value is rejected before a GPU is provisioned.

Image-bearing OPD

Distilling a prompt that contains an image requires a teacher that can see it. Select one of the image-capable aliases above; any other teacher is rejected, with the aliases you can use named in the error. When the images are in your dataset file, Flash sees them at submit and rejects a text-only teacher before a GPU is allocated. When your environment code attaches the image instead - building it inside build_prompt_messages, say - nothing can see it until that code runs, so the same rejection happens on the worker after allocation. Both fail the run rather than distilling blind. The restriction is not bureaucratic. A text-only teacher answers a request carrying an image with a normal 200 and no indication that it ignored the pixels, so without the check a run would train, bill, and quietly distil from a teacher that never saw the image. Flash verifies the image survived into the teacher’s own tokenization and fails the run if it did not.
Multi-turn image-bearing OPD is not supported and is rejected up front. Single-turn image prompts are the supported shape. Requires Flash 1.1.42.

Structured outputs

structured_outputs constrains GRPO and OPD rollouts; SFT rejects it. Set exactly one JSON schema, regex, choice, or json_object constraint. Optional whitespace controls are documented in the guide. Combining constraints or using unsupported grammar forms is rejected. With thinking = true, reasoning remains free-form and the grammar begins after </think>. See Structured outputs.

[gpu]

A pinned GPU class is honored on retries and in cost estimates. Use type when hardware identity matters; everything else about allocation is managed.

Multi-GPU runs

Omit both type and count and Flash sizes the run for you. It picks the smallest geometry-safe card count your configuration actually fits on, then ranks the fitting shapes by cost per step. A run that needs more memory than one card holds is placed on two, four, or eight instead of being rejected. Only rentable shapes are ever provisioned: 1, 2, 4, and 8. Setting count turns it into a hard ceiling that never escalates:
A ceiling of 3 therefore never buys 4 cards - it permits 1 or 2. An explicit count = 1 on a run that does not fit one card is an error rather than a silent escalation, and it names the smallest count that would work:
If nothing fits even at eight cards, the error names the knobs that actually shrink that algorithm - they differ per algorithm. See Pre-flight says the run is too large.
Pinning type without a count stays a single-card pin, so naming a specific card never quietly bills you for four of them. Auto-sizing applies only when you omit both.
flash train --gpus N sets the same ceiling from the command line without editing the config. Cost is quoted for the shape the run actually occupies, so a multi-card run is priced on every card it held.

[wandb]

Optional Weights & Biases logging labels. These values are non-secret; set WANDB_API_KEY in your local environment when submitting a run to enable logging to your own W&B account.

Overrides & composition

Set any value at submit time without editing the file. The override flag is --set (repeatable, dotted keys):
The config file is the base, --config overlays merge onto it, and --set/--gpus win over both, applied left to right. An absent --gpus is indistinguishable from no flag, so a [gpu] count authored in the file is never silently downgraded.