Skip to main content
Most flash errors print one clean line; add the global --debug flag before the subcommand (e.g. flash --debug train config.toml) for the full traceback.

Installation & CLI

The CLI installs a single flash command. Its install location must be on your PATH.
  • If you installed with uv tool install freesolo-flash, make sure uv’s tool bin directory is on your PATH (run uv tool update-shell, then restart your shell).
  • Confirm the install with flash version.
The CLI is published to PyPI as freesolo-flash. The bare flash name belongs to an unrelated project. Reinstall the right one:

Authentication

Commands that contact Freesolo authenticate with a Freesolo API key, verified at login. flash train --cost does not start paid training, but on an SFT config it does authenticate.
  • Create a key in your dashboard at freesolo.co.
  • Log in once: flash login --api-key <your-key> (or set FREESOLO_API_KEY instead of passing --api-key).
  • Confirm who the stored key resolves to: flash whoami.
By default the CLI talks to https://api.freesolo.co. To target a different deployment, set --freesolo-url (or FREESOLO_BASE_URL) at login. See Auth & identity.

Environments

The folder you push must contain an environment.py file with a load_environment() function that returns a Freesolo environment. Then:
It prints the published id (your-org/your-project/math) to put in your config’s [environment] id. The folder needs an environment.py at its root and a --project UUID your org can reach. A bare --name is easiest; if you give a qualified one it must be the full namespace/project/name, where the namespace is your org and the project segment matches the --project you passed. A two-segment your-org/math is no longer a valid name - see Environments.
Managed training runs already have the Freesolo SDK. Your local Python environment does not get that SDK automatically from the flash CLI. Install it locally to run or test environment.py directly:
To pull a published env into your project for local work, use flash env pull your-org/your-project/your-env.
flash train --dry-run checks your config at submit time but never imports environment.py, so it cannot prove every worker import resolves. On SFT it reads the packaged dataset and training contract statically; GRPO and OPD do not execute environment code either. If flash runs log <run-id> shows your environment.py failed while importing a package, that package is not available on the worker.The worker installs one managed set - the freesolo SDK and the training stack. To add to it, declare the package under [environment] pip, which is appended to the managed set rather than replacing it, then submit again. See Scorer dependencies for the accepted form.If you would rather not add a dependency, rewrite the import against the standard library - urllib.request and json instead of a vendor HTTP client, for example - or move that logic out of the environment, then republish. See Dependencies are managed.Flash also does not install from a pyproject.toml, requirements.txt, or lockfile bundled with the environment; those describe your local setup only.
Pull the specific file you need instead of the whole environment:
Keep published environments focused on source, small sidecars, and datasets needed by the run. Do not publish virtualenvs, local caches, model weights, or generated artifacts.
Use flash env pull to inspect the exact packaged file:
For clean A/B experiments, publish changed datasets under a fresh env name so old runs, new runs, and local files are easy to tell apart.
SFT estimation happens on the control plane before allocation. It reads the selected packaged JSON or JSONL dataset directly and does not import environment.py, so a dataset available only through Python code cannot be estimated.Package a non-empty object-row dataset at the selected split path, such as dataset/train.jsonl, or point [environment.params] dataset_path at a file inside the package. Fix malformed JSON, missing input fields, or paths that escape the package. The control-plane limits are 32 MiB for the selected dataset or inline records, and 256 KiB for contract_text, contract_path, or TRAINING_CONTRACT.md. These failures stop before paid training starts or a training GPU is allocated.
If your environment module shares a name with an installed Python package, it can shadow or be shadowed by that package. Keep helper module names distinct from installed packages.
It depends on the control plane you submit to:
  • Freesolo’s managed service accepts only the published hub slug returned by flash env push, for example your-org/your-project/your-env.
  • A standalone plane accepts github: references and supported GitHub repository, tree, or blob URLs that resolve to environment.py. It rejects managed hub slugs.
The two forms do not overlap, and a local file path is not a valid training source on either plane. Use flash env pull your-org/your-project/your-env only when you want a local copy of a managed environment to edit or inspect.

Configuration

model must be one of the ids in the curated catalog. List the valid ids:
Managed runs train catalog models only. See Supported models.
Flash rejects unknown config sections and [train] keys at parse time. Check the key against the configuration reference and validate the config:
Dry-run also flags [train] keys your CLI version does not recognize, which usually means an outdated CLI.
algorithm must be sft (the default), grpo, or opd. Fix the value and re-validate with --dry-run.
Flash rejects a run at submit when its training context is longer than the base model’s cap. SFT checks train.max_context_tokens; GRPO and OPD check the rollout prompt plus max_completion_tokens. Lower those to at or below the cap - see context window.
A GRPO or OPD run reserves max_completion_tokens out of max_context_tokens, so a context that is not larger than the completion budget leaves nothing for the prompt itself. That is rejected at parse time, before a GPU worker is provisioned:
Raise max_context_tokens above max_completion_tokens, leaving enough room for your longest prompt. Note that thinking = true raises the default completion budget, so enabling it can trip this on a config that previously parsed.
The source adapter is authoritative: omit both train.lora_rank and train.lora_alpha alongside init_from_adapter. --dry-run resolves the source rank, alpha, and base-model identity automatically. See Warm-start safely.Warm start itself is no longer restricted by algorithm: SFT, GRPO, and OPD can each continue an adapter from any of the three. If an older config avoided a warm-started SFT run because it was rejected, that restriction is gone.
GRPO accepts exactly 2, 4, or 8. Any other value - 1, 3, 6, or anything larger - is rejected at submit, before a GPU is allocated. Flash never rewrites the value you wrote, so pick one of the three.A step is also capped at 512 completions:
Lower one factor to raise the other. This ceiling is checked against the values you authored (or their defaults), so a smaller dataset does not lift it. See the GRPO rollout shape.
Unknown arguments now come back with the closest real flag for the command you ran, and say when a flag you passed belongs at the root instead of on the subcommand. Take the suggestion, or run flash <command> --help.
GPU selection is automatic when [gpu] type is omitted. A string hard-pins one class; a list names acceptable classes that Flash cost-ranks together. Card count is auto-sized only when you omit type and count; [gpu] count and --gpus N pin a ceiling that never escalates, and either form of type without count stays a single-card constraint.[gpu] providers is an ordered soft preference, not an allowlist, so another configured provider may still win when the preferred providers cannot place the run. Use scalar [gpu] provider for a hard pin. provider and providers cannot be combined. See GPU configuration.

Run fit and resource use

Expected. GRPO samples multiple completions, scores them, and updates from that group of attempts. For the same model, it usually needs more room and costs more than SFT. To spend less, use a smaller model, reduce max_completion_tokens or max_context_tokens, or start with SFT. Dropping group_size from 8 to 4 or 2 reduces cost too, but it does not make a run that does not fit fit - GRPO’s memory floor is effectively flat in the group. The next entry below covers what to change when a run does not fit.
If you pinned [gpu] count (or --gpus N), that ceiling is the usual cause: the error names the smallest count that would fit, so raise it or drop the pin and let Flash auto-size.When nothing fits even at eight cards, the error names the knobs that actually shrink that algorithm. They are not the same across algorithms:
  • GRPO responds to max_context_tokens, max_completion_tokens, and lora_rank. Required memory is effectively flat in prompts_per_step, so lowering it does not help here.
  • OPD rollout concurrency is prompts_per_step x group_size, so lowering either shrinks the run, as do max_completion_tokens and max_context_tokens. Distillation needs no group variance, so group_size = 1 is fine.
  • SFT responds to max_context_tokens, lora_rank, and batch_size.
A larger base model than the task needs is the other common cause. If you recently enabled thinking = true, reasoning and the final answer share the same token budget.

Training runs

A successful run bills at the accepted quote; a cancellation is prorated and capped at that quote. In the preview, Setup is not billed, while Train and required finalization contribute to Billable. Review flash train config.toml --cost, reduce the run horizon or save_at_steps cadence if needed, and see Charges and cancellations.
Warmup can take several minutes, especially for GRPO, OPD, and larger models. flash runs log <run-id> -f shows warming up (stage=...) while it progresses. Check the quote’s Setup, Train, and Billable fields rather than assuming all work before the first optimizer update is free. Once updates begin, the log streams metrics such as reward, grad_norm, kl, entropy, and completion length. If warmup stops advancing, keep following the log and use flash runs cancel <run-id> if you do not want the run to continue billing.
Add funds, or lower the pre-flight estimate before submitting again - see Lowering cost.
The task is too hard for the model at its current ability: if no rollout ever scores, there’s nothing for GRPO to reinforce. Try a stronger or larger base model, make the task easier to start, or double-check that your reward returns a positive reward for good answers.
Usually the reward is not discriminative: it scores almost everything the same. Make the reward function separate better answers from worse ones so GRPO has a spread of scores to learn from. See Environments.
When thinking = true, the model emits a reasoning trace before its answer, and that trace counts against the same max_completion_tokens budget as the answer. A max_completion_tokens tuned for a non-thinking run is usually too small once reasoning is added: the reasoning eats the budget and the actual answer is truncated or never emitted. If your reward parses the answer (e.g. extracts a JSON object), it then sees nothing and scores ~0 across the board — even though the model is “working”.Fixes:
  • Raise max_completion_tokens so the reasoning and the answer both fit (e.g. a task that needs ~200 answer tokens may need max_completion_tokens = 2048 with thinking on), and make sure max_context_tokens is large enough to hold the prompt plus that budget.
  • Optionally set thinking_length_penalty_coef to nudge the model toward shorter reasoning so the answer reliably lands inside the budget.
  • Score the answer text by default. In thinking mode response_text remains string-compatible answer text and also exposes response_text.completion, response_text.thinking, and response_text.raw for rewards that intentionally inspect reasoning.
The same trap applies to any reasoning model you call as an LLM judge from a reward: give the judge call enough max_tokens or it returns empty content and the judge silently scores 0.
In Qwen3.5 thinking mode, the chat template treats prior and next assistant turns differently: it strips literal <think> blocks from non-final assistant history, then pre-opens <think>\n in the next generation prompt. A naive multi-turn SFT transcript that puts <think>...</think> in every assistant turn can therefore train on a tag layout that inference will never render. The symptom is doubled, missing, or misplaced thinking tags, or an adapter that behaves differently in training-style evals than it does when served.Fixes:
  • For message-shaped multi-turn SFT targets, keep intermediate assistant turns as the actual code, tool, or action text only.
  • Put <think>...</think> plus the final answer only in the final assistant target.
  • Do not add a second opener for the template’s pre-opened <think>\n. It is already part of the prompt.
OPD refines a model that already has the output format, so warm-start from a finished SFT run and check that the teacher clearly beats the student on held-out examples:
There is no auxiliary EOS loss. For rollouts that reach max_completion_tokens without stopping:
  • Start from an SFT adapter with the desired stopping behavior.
  • Give the teacher a hard answer or reasoning budget in the system prompt.
  • Add stop_sequences when the task has reliable textual terminators.
  • Track truncated_rollouts to diagnose termination.
If the cap is tight enough that most rollouts are cut off, OPD cannot align a teacher signal at all and the run fails naming the cap rather than exiting on an opaque subprocess status:
Raise max_completion_tokens when you see that.Text-only OPD supports single- and multi-turn environments but not tool-calling ones. Image-bearing OPD remains single-turn only. Statically visible image-bearing multi-turn inputs fail before allocation; images added later by environment execution are rejected by the worker guard. kl_penalty_coef must be greater than 0.
No. Ctrl-C during flash train detaches you; the run continues and can accrue billable work. Resume with flash runs log <run-id> -f, or stop it with flash runs cancel <run-id>. For machine-readable status, see Run management.
Expected. Cancellation waits for the run to stop and release its GPU before confirming, which can take several minutes. The CLI waits that out; the run is marked cancelled when it completes.
Flash retries a limited number of times, resuming from a saved checkpoint where it safely can and failing the run rather than silently restarting from scratch.Watch logs with flash runs log <run-id> -f or poll status with flash runs status <run-id> -f. If the same shape repeatedly fails before useful metrics, see Run fit and resource use for the knobs that shrink your algorithm.
A failed checkpoint upload now reports its cause in the run’s heartbeat and log rather than leaving a silent gap, so flash runs status <run-id> and flash runs log <run-id> say why the save did not land. A later successful upload of the same checkpoint clears the reported failure.Training continues through a failed periodic upload - save_every saves are best-effort after bounded retries. Use save_at_steps when a specific checkpoint must exist; those saves are mandatory. See Control the update horizon and checkpoints.
The failure names how much was requested against what the card had, not just that an OOM happened:
Those figures separate a request the card could never have served from one that missed narrowly, which point at different fixes. Host-RAM exhaustion is classified separately - a bigger card does not fix it. For the knobs that shrink each algorithm, see Run fit and resource use.

Serving

A run cancelled or preempted before finalizing has no final adapter, so a plain flash models deploy <run-id> cannot serve it. The error lists the run’s saved checkpoint steps and the exact command to use — deploy one of them instead:
Expected: every deploy runs a bounded smoke before the alias routes to the new revision, and a large model takes a few minutes to warm and verify. A failed smoke leaves the alias unchanged. See Deploy & chat.
Serving is billed per token for requests. Prompt, completion, and cached prompt token rates are listed in Supported models. flash models undeploy <run-id> disables the alias and immutable revisions.
Use openai_base_url from flash models deployments --json, set model to the run alias or a full immutable revision, and pass your Freesolo API key.
  • 401: missing or invalid key.
  • 402: insufficient org balance.
  • 403: the key’s org does not own the adapter.
  • 503: temporarily unavailable; retry with backoff.
See Use it from your own code.

Getting help

Still stuck? Reach out through freesolo.co/contact with the run id (flash runs list), the failing command, and its --debug traceback.