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. flash train config.toml --dry-run runs submit-time checks without starting paid training or allocating a training GPU. See Cost and billing for estimate behavior.

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, alpha, and the source base-model identity for you. Local --cost may warn and estimate with defaults instead.

Warm-start from any algorithm

init_from_adapter works for every source/target pair. SFT, GRPO, and OPD each read a source adapter produced by any of the three, including same-algorithm continuation - sftsft to keep training on more data, or grpogrpo to extend a run. SFT was previously rejected as a warm-start target; it is now accepted like the other two.
Across the lineage keep the same base model. The source adapter’s rank and alpha are authoritative, so setting lora_rank or lora_alpha alongside init_from_adapter is rejected at submit, and a source rank above the model’s serving cap is rejected too. A warm-started SFT run also inherits its source’s base-model pin rather than resolving its own, so the continued adapter stays deployable when the base model’s upstream tip moves.

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 estimates its packaged dataset, so an uncapped SFT config prices fine. GRPO and OPD quote offline from the catalog with nothing reading the prompt 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, batch_size is examples per optimizer update. Under GRPO and OPD, prompts_per_step is prompts per optimizer update, 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 examples when it can do so safely; multimodal rows are never packed. --cost, --dry-run, and train warn when packing is off. An unpacked run trains exactly one example per optimizer update, regardless of a larger authored batch_size. That value can still affect the quote and GPU choice. The default learning rate assumes a batched update, so expect noisier steps and consider a lower learning_rate than for a packed run.

GRPO-specific

The GRPO rollout shape is a fixed set

GRPO group_size accepts exactly 2, 4, or 8, and defaults to 8. Any other value - including 1, 3, 6, or anything above 8 - is rejected at submit, before a GPU is looked up or allocated. Flash never rewrites the value you authored, so an unsupported group_size fails fast instead of quietly training a different shape than you asked for. A step also has a completion ceiling:
The default 64 x 8 is exactly 512. Admission checks the authored values, or their defaults when omitted, so an oversized shape is refused up front - a smaller retained dataset cannot rescue it later. To raise one factor, lower the other. At execution the effective prompt count may clamp down to the number of valid prompts left after max_examples and prompt-budget filtering; group_size is never changed. Rollout generation, reward work, and cost all scale with the effective prompts_per_step x group_size product. These limits are GRPO’s. OPD keeps its own group_size default of 1 and is not restricted to the same set.

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.
Multi-turn image-bearing OPD is not supported. Flash rejects statically visible image-bearing multi-turn inputs before allocation, while images attached later by environment execution are rejected by the worker guard. 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]

The authored class constraint and provider routing are honored in estimates, submissions, and retries. Use a scalar type or provider when hardware or provider identity is mandatory; use lists when several outcomes are acceptable.
This allows either GPU class, prefers Lambda and then RunPod, and still permits another configured provider if neither preference can place the run. Replace providers with provider = "lambda" to make Lambda a hard requirement.

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.
Authoring type without a count, whether as a scalar pin or an acceptable list, stays a single-card constraint. Auto-sizing across multiple cards applies only when you omit both keys.
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. A run’s VRAM requirement is a whole-run figure, and a multi-card shape pools its cards to meet it. The quote spells out both numbers so a shape that fits does not read as a rejection:
The per-card size stays visible because that is what you check against a provider listing. If an authored single-card pin cannot hold the run, the error now names the card count that would - for example it fits on 2 cards -- raise the card ceiling with --gpus 2 - so a run needing more than the largest single card is not a dead end.

[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.