v0.77.1: data commands keep their input
v0.77.1 was tagged on 28 October 2026. Upstream gave it no codename and titled it with a sentence again: data commands keep their input; plan and apply refuse what train refuses. It is a cut of the development branch after v0.77.0, not a backport: it carries every pull request merged since that tag. In the release body's words, "the fixes that matter most are three. soup data commands no longer write their output over their own input. soup plan and soup apply now refuse the configs soup train refuses. And the README's Docker example works again. Nothing in v0.77.0 is taken back."
22 of the 28 pull requests in this release came from 11 people outside the maintainer. That is the release body's own count; the names are under Credits. The six maintainer pull requests are #2205, #2213, #2242, #2244, #2245 and #2261.
This is not a security release. No security advisory is published with it. Its Security section says only that it "tightens how external programs are started" (see External programs). The supported versions are unchanged: 0.77.x is supported, 0.76.x receives security fixes only until 0.78.0 is released, and anything below 0.76 is not supported.
Coming from v0.76.x or older? Read v0.77.0 first. A config that loaded on v0.76 can stop loading on v0.77, and v0.77.1 changes none of that. It adds one thing to the same family:
soup planandsoup applynow stop on the configssoup trainstops on.
What to do
pip install -U soup-cli
soup --version
soup doctor --config soup.yamlWith pipx, pipx upgrade soup-cli replaces the first line. soup --version prints soup v0.77.1. Run soup doctor --config before the first run of each existing config.
From v0.77.0, a valid soup.yaml needs no change. Four kinds of setup do:
- A script that wrote a
soup dataresult over its own input now fails. It exits non-zero before the data is read, and the input is left alone. Write to a new file and rename it. See Data commands keep their input. - A pipeline that planned a config
soup trainrefuses now stops atsoup plan. Exit 1, no plan written. Delete the line the message names. See soup plan and soup apply. - A tool that Soup found only in the current directory, or through a relative
PATHentry, is no longer found. On Windows the current directory is also left out of the search for the programs Soup starts. Put the tool's directory onPATHas an absolute path, or give the tool's own path. See External programs. - A caller of
/v1/tools/pythonthat treated every non-success as a timeout now seestimed_out: falsefor an ordinary failure. See Also fixed.
Two more things are worth a look, neither a re-train:
- Re-run one ingest, if it matters to you:
soup ingest --source langfuse --logson an export that carries scores of0,0.0orfalse. See Re-run this. - The VRAM pre-flight prices runs differently. Some long-sequence, large-vocabulary runs, and some runs without gradient checkpointing, that it let through are now refused, inside the range its estimate was fitted on;
--allow-oom-attemptstill launches them. See One peak-VRAM estimate.
Coming from v0.76.x or older, Python 3.10 stays on v0.76.x (pip install "soup-cli[train]<0.77"), and AWQ and GPTQ export were removed in v0.77.0. See Docker and install for the image.
Re-run this
| What was wrong | Who is affected | PR |
|---|---|---|
Upstream: "soup ingest --source langfuse --logs dropped scores of 0, 0.0 and false; they now reach the thumbs-down signal and preference-pair construction. Only a missing or null score falls back to rating." | soup ingest --source langfuse --logs on a Langfuse export that carries a score of 0, 0.0 or false, run through v0.77.0. Re-run the ingest on such an export; anything built from the earlier output, preference pairs included, was built without those scores. | #1856, #1871 by @akkupratap323 |
This is the one re-run row on this page: upstream frames no other change in this release as a wrong finished run. The refusals below apply from v0.77.1 on. They do not restore an input that an earlier version already replaced, which in upstream's words could not be recovered, so check any dataset you passed through one of the commands below with an --output that named it.
Data commands keep their input
In the release body's count, eighteen soup data commands "used to write their result over one of their own inputs and exit 0; the original could not be recovered." Each now compares its outputs with its inputs before the data is read, and refuses the same file under any spelling: the same string, ./x, sub/../x, the absolute path, a case variant on a case-insensitive file system, a hard link or a symlink. The message names both paths and says what to change. An output that is a different file works as before, and default output names are unchanged.
soup data sample d.jsonl -o d.jsonl --n 2--output 'd.jsonl' is the same file as the input 'd.jsonl'; choose a different --outputThat run exits 1 and d.jsonl is untouched. Write the result to another path, and rename it afterwards if you wanted an in-place edit:
soup data sample d.jsonl -o d.sampled.jsonl --n 2The eighteen, and what each one's output used to replace (#2245, closes #2225; #2261, refs #2226):
| Command | Exit code | What used to be replaced, in the release body's words |
|---|---|---|
decontaminate, toxicity, langdetect, pii, educational | 1 | An input that had the command's default output name: clean.jsonl, toxicity.jsonl, langdetect.jsonl, pii.jsonl or educational.jsonl. decontaminate also compares --output with --benchmark-file. |
lint, doctor, topics | lint 3, the others 1 | The JSON report replaced the dataset. |
from-traces | 1 | The preference pairs, often an empty file, replaced the trace file. |
canary insert | 1 | The result replaced the input. It also refuses a --manifest that is the dataset or the --output. |
sample, dedup, filter, convert, merge, active-sample | 1 | The result replaced the input. |
augment, evolve | 2 | The result replaced the input. |
Details that decide whether a script still works:
- A default output name that the input already has is refused too:
merged.jsonl,prefs.jsonl,active_samples.jsonl,augmented.jsonl, and the five default names in the first row. The advice the v0.77.0 notes gave, to pass--outputa different path, is now what the error message says. - The exit codes follow each command's other path errors. 1 for most,
lint3 andaugmentandevolve2. Which message comes first for a missing input depends on the command:dedupandlintreport the missing file,mergeandpiithe refusal. - On Windows a trailing space or dot,
::$DATAand a junction are refused as the same file too. Upstream lists these spellings under the first five commands; they were also refused when checked onsample,dedupandpii. canary insert --manifest: before, the dataset write replaced the manifest that had just been written, and in upstream's words the only record of the inserted secrets was lost.soup data cleanalready refused this and is unchanged.
Where it stops. soup data persona-mix still writes over its --prompts input when --output names it; that was reproduced on v0.77.1 for this page. mix, forge, generate, gen-magpie, recipe and canary check have not been checked yet. Both are tracked in #2226, so "every soup data command" is not a safe reading. See Data tools, Data Forge and Data Moat II for the commands.
soup plan and soup apply refuse what soup train refuses
Since v0.77.1 (#2242) soup plan and soup apply run the validation soup train runs and print the same Config validation error: lines: a staged field, a refused deprecated value or any other schema error exits 1, writes no plan and leaves an existing state file unchanged. Before, they checked only for unknown keys, so a config with a staged field set, a refused deprecated value such as data.remove_unused_columns: true or any other schema error got a plan with exit 0, and soup apply and soup apply --dry-run accepted it. Upstream's release body calls this "the gap the v0.77.0 notes said was open."
This config is refused on purpose, because training.early_stop_patience is a staged field:
base: org/model
task: sft
data:
train: ./data.jsonl
training:
early_stop_patience: 5soup plan --config soup.yamlConfig validation error:
training.early_stop_patience is accepted but read by nothing; refused as of v0.77. Delete the line.soup apply --dry-run and soup apply print the same lines, exit 1 and do not mark an existing state file applied. A valid config plans and applies as before. With several unknown keys, every line of the message is now indented, as soup train prints it. The YAML read limits are unchanged.
Which commands refuse a staged field or a refused deprecated value, at v0.77.1: soup train exits 1, soup doctor --config exits 2, soup recipes verify --config and soup ship --config exit 3, and soup plan and soup apply exit 1.
One config still loads under soup train and not under soup plan or soup apply. A distill soup.yaml with training.minillm_enabled: true, training.minillm_teacher_mix_ratio: 0 and no training.minillm_on_policy loads only under soup train --minillm-on-policy, which sets the field before validation. soup plan and soup apply have no such flag, so they refuse that config with minillm_enabled with minillm_on_policy=false requires minillm_teacher_mix_ratio > 0. Write training.minillm_on_policy: true in the YAML.
Other changes to the same two commands:
soup applyno longer reads the model. It compares hashes only, so it makes no request to the Hub.soup planprints a dim line before its first Hub request:Reading the model's config.json from the Hub to size the run (HF_HUB_OFFLINE=1 skips this)...peak_vram_gbis the shared estimate, ornull. The plan recordspeak_vram_gb: nullwith apeak_vram_reasonwhen it cannot price the run, instead of the fixed 8.0 GB it recorded for every run through v0.77.0. A config that leavesbatch_sizeat its defaultautois one of those. A state file written before this still loads. The reverse does not hold: a v0.77.1 state file withpeak_vram_gb: nullmakes the v0.77.0soup applyexit 2 with a barefloat()error (checked on both binaries for this page).
See Pre-flight and tooling and Settings Soup refuses at load.
GRPO trains on Apple Silicon
Since v0.77.1, task: grpo with backend: mlx trains natively through Soup's own MLX loop (#2182 by @dryn85-max, the dense baseline, step 1 of #1976): it generates num_generations completions per prompt from the current policy, scores them with the built-in accuracy or format reward and takes one policy-gradient step per update, on the objective Soup's transformers path trains on TRL 0.29.1; held-out prompts are scored once after training as val_reward. It was refused at config load through v0.77.0. MLX now trains six tasks: sft, dpo, orpo, simpo, kto and grpo. task: preference stays refused (#2202).
This config loads on v0.77.1: soup doctor --config prints no error and soup train --dry-run --yes ends with Config valid. Ready to train! (the dry run was checked on six chatml rows, {"messages": [{"role": "user", ...}, {"role": "assistant", "content": "#### 5"}]}, whose final assistant turn is the gold answer).
base: Qwen/Qwen2.5-0.5B-Instruct
task: grpo
backend: mlx
data:
train: ./train.jsonl
format: chatml
max_length: 256
training:
reward_fn: accuracy
num_generations: 4
batch_size: 4
gradient_accumulation_steps: 2
grpo_beta: 0.1
lr: 1e-5
quantization: none
lora:
r: 16
alpha: 32What to know before you run it:
- The base must be unquantized. Use
quantization: noneand a bf16, fp16 or fp32 checkpoint. A quantized base is refused only after the model loads, sosoup doctor --configand--dry-runpass it. A 4-bitmlx-community/...base, which the MLX recipes in the catalog use for SFT, is refused for GRPO. - Refused at config load:
quantization: 8bit,lora.r: 0, custom or ensemblereward_fnsettings (only the built-inaccuracyandformatrewards score), non-text modalities, andgrpo_variant. Among others, the reward-hack, echo-trap, mid-run RL checkpoint and curriculum settings,rollout_backendand the other GRPO stability settings are refused too, each by name. Thedapoin this loop's objective is TRL's defaultloss_type, not a Soupgrpo_variant, so this does not add DAPO or the GRPO variants to MLX. --resumeand--hf-resumeare refused at launch (exit 1), because only the adapter is saved.- Validation: on
backend: mlxthe loader holdsdata.val_splitout (default 0.1) and scores it once after training asval_reward. Setdata.val_split: 0to train on every row.training.eval_stepsis refused for this task, because there is no mid-run evaluation. soup doctor --confighas nine combinations now.grpoonmlxhas four settings (training.use_galore,use_ring_attention,moe_lora,use_fsdp2_compile); with none switched on it printsNone of the 4 setting(s) known to be unread on task=grpo backend=mlx is switched on in this config.The other eight are unchanged.- No recipe exists for it. The catalog is still 171 recipes.
Upstream qualified the loop on Qwen2.5-0.5B-Instruct on an M4 Pro with a synthetic arithmetic task that it describes as easy and fully verifiable, and says the run "shows the loop training, not reasoning quality in general." No figure from it is reproduced here: upstream's docs print one without a record under benchmarks/, and Soup publishes no MLX speed, memory or timing figure. The settings, refusals and the loop's details are on Apple Silicon MLX backend.
soup infer --backend mlx
soup infer takes --backend transformers|mlx (default transformers). With --backend mlx, native text LoRA adapters and MLX base models load and generate without Torch or PEFT, through a shared strict loader that checks every adapter name, shape and finite value and refuses unsupported tasks, controls, targets and variants. Dense and genuine 4-bit bases are covered, and base-only generation works. It is part 1 of #1986 (#2121 by @Amix29).
soup infer --model ./out --input prompts.jsonl --output answers.jsonl --backend mlx- It needs Apple Silicon and the
[mlx]extra. The loader's message when they are missing isNative MLX generation requires soup-cli[mlx]; install it on Apple Silicon.(read in the code). - It refuses, one named option at a time, with exit 1:
--taskother thantext(MLX generation requires --task text),--batch-sizeabove 1 (MLX generation requires --batch-size 1) and--cuda-graphs(MLX generation does not support --cuda-graphs). A--devicevalue is refused too (MLX selects its native device; omit --device), and so is a--hubother thanhf. Any other--backendvalue exits 2. - A native Soup adapter given to the default backend is refused with
Native MLX adapter detected; select --backend mlx, and a malformed nativeadapter_config.jsonis refused with a message naming the file. soup chaton MLX is still open (#1986, #2123).
No speed, memory or timing figure exists for it. The option refusals above were run on a machine without MLX; the loading and generation path was read in the code and not run. See Experiment tracking for soup infer and Apple Silicon MLX backend.
One peak-VRAM estimate
soup train's pre-flight and soup profile priced the same run with two formulas in two units, and neither charged the logits and loss chain, which decides long, large-vocabulary runs. Since v0.77.1 soup train's pre-flight, soup profile, the MCP profile tool and soup plan price a run with one estimate (#2146 by @abdulwaarith0). It reads the model's config.json and safetensors headers, never the weights, and sums weights, LoRA adapters, gradients, optimizer state, activations, the logits and loss chain over the vocabulary, the 4-bit dequantization copy and overhead.
Upstream calibrated it on 39 measured runs: resident two-step sft runs on one RTX 3060 Laptop 6 GB under WSL2, across seven dense models from 135M to 3B parameters (the record). The record states that the coefficients are fitted on those same rows, so its bands show how well they fit them, not the error on a run they were not fitted to.
Where it refuses and where it warns.
- Inside the range it was fitted on, which upstream prints as "dense models of 3B and below on one 6 GB card", a run the estimate says does not fit is refused, and
--allow-oom-attemptlaunches it anyway. The pre-flight now refuses some long-sequence, large-vocabulary runs, and some runs without gradient checkpointing, that it let through. - Outside it (a larger model, a mixture-of-experts model,
mediumorselectivecheckpointing,use_ligeroruse_cut_ce,backend: unsloth; none of these is among the 39 measured runs) a run that does not fit gets the estimate as a warning, with a footnote that it may overstate the peak, and starts. - One case is refused outside the range too: a run whose weights, the CUDA context, its adapters and the gradients of the parameters it trains alone are more than the card holds, plus their optimizer state for the AdamW optimizers, unless GaLore replaces the optimizer on
sft,ttsorpretrain.--allow-oom-attemptlaunches that as well. Optimizer state counts only foradamw_torch,adamw_torch_fused,schedule_free_adamwandadamw_bnb_8bit. - The shipped
longcontexttemplate (an 8B model at 131,072 tokens) is outside the range, and its header says how to turn onuse_liger. - A config that leaves
batch_sizeat its defaultautois not priced by thesoup trainpre-flight, which returns without pricing it (checked at v0.77.1). Set a number to get the check.
How it prices the pieces. gradient_checkpointing is priced as the trainer applies it: auto resolves on the card as sft does, selective is priced as no checkpointing (an upper bound), and tasks other than sft and tts checkpoint fully. Tasks without an LM head (classifier, reranker, cross_encoder, embedding, reward_model) are charged no logits.
Reading a Hub model's shape can reach the Hub. The pre-flight, soup profile and soup plan read a Hub model's config.json and write it into the Hugging Face cache, unless HF_HUB_OFFLINE or TRANSFORMERS_OFFLINE is set. soup plan prints a dim line before it does, soup apply compares hashes only and does not read the model, and the MCP profile tool reads only a local directory or the local cache. A Hub model with no readable config.json is priced from a shape guessed from its name.
What soup profile prints. Its numbers change, and its JSON gains the exact bytes per term, the fit verdict, where the model's shape came from and gradient_checkpointing_tier (off, full, medium or selective). gradient_checkpointing in that JSON, and in the MCP profile result, stays the config's own value. See Experiment tracking and Pre-flight and tooling.
External programs are started by an absolute path
Two maintainer changes (#2205, #2213) with one visible effect. In the changelog's list, nvidia-smi, sysctl, the disk-probe tools, git, ollama, the llama.cpp binaries, trtllm-build, docker, the Modal CLI, gh, ssh and scp (the RunPod and Lambda controllers), the code-sandbox tools, accelerate for soup train --gpus and the soup adapters bisect --eval-command program now run by the absolute path found in an absolute PATH directory.
- Not searched: empty and relative
PATHentries (.,bin, and on Windows\binorC:bin), and the current directory. If a tool is only in the current directory or in a relativePATHentry, Soup no longer finds it. Put its directory onPATHas an absolute path, or give the tool's path. - On Windows the current directory is left out of the program search (#2213), for
soupand for the programs it starts, eval scripts and the scripts behind a customreward_fnincluded.soupsetsNoDefaultCurrentDirectoryInExePath=1for itself when it is not set. A value you already set is kept, and per upstream any non-empty value,0included, already leaves the current directory out, so no setting brings the old search back. To run a program from the current directory, give its path (./tool.exe, or an absolute path in quotes) or put the directory onPATHas an absolute path. - A set but empty
PATHEXTon Windows falls back to.COM;.EXE;.BAT;.CMD. soup adapters bisectexits 2 before the first probe when its--eval-commandprogram is not onPATH(--eval-command program 'x' was not found on PATH; install it, put it on PATH, or give its path). When the checkpoint id fills in the program name, each probe resolves it the same way, and a name not onPATHstill counts as a failed probe. Through v0.77.0 a program that could not be found counted as a failed probe and bisect printedBROKEN_ATon the first checkpoint (checked on both binaries for this page), so exit 2 no longer tells a verdict from a rejected command by itself: read the first line.--eval-commandis split with POSIX shell rules, in which a backslash is an escape. A Windows path there takes forward slashes or quotes;.\tool.exewould reach the program as.tool.exe.- The macOS code sandbox runs
sandbox-execfrom/usr/bin/sandbox-execonly, and a llama.cpp checkout given as--llama-cpp .runs its binaries by absolute path. - Cloud controllers.
soup train --cloud lambdarefuses anoutputwith a part that starts with-, as--cloud runpodalready did (Cloud plan failed: Lambda output_dir parts must not start with '-', exit 2), and both controllers stop before launching with "OpenSSH ssh and scp must be installed and on PATH" when they are missing. The RunPod controller still has not been run against a real RunPod account (#2098).
The changes are in the Security section of the release body, which says only that the release "tightens how external programs are started" and that upgrading is enough. See Environment variables and Adapter lifecycle.
soup ship --progress jsonl
soup ship takes an opt-in --progress jsonl (#2171 by @kok-o). It writes versioned start, progress, end and error events to stderr, one JSON object per line, so a CI job can follow a verdict without parsing console text. Ordinary output, stdout and exit codes are unchanged.
soup ship --evidence evidence.json --progress jsonl 2> progress.jsonlWith offline --evidence that file holds exactly two lines (the other phases appear on live runs):
{"schema_version":1,"event":"start","phase":"verdict","side":null,"suite":null,"repeat":null,"done":null,"total":null}
{"schema_version":1,"event":"end","phase":"verdict","side":null,"suite":null,"repeat":null,"done":null,"total":null}- Fields:
schema_version(1),event,phase(load,task_eval,suite_evalorverdict),side(base,tunedor null),suite,repeat(the one-based noise-floor repeat, or null) anddoneandtotal(counts, or null); anerrorevent also carrieserror, alwaysexecution_aborted. Noise-floor repeats get explicit identities, and a group that aborts carries the fixed codeexecution_aborted. - Exit codes: a completed DON'T SHIP (exit 2) ends with
end. Evidence that cannot be read ends withstartthenerror("error":"execution_aborted", never the exception text) and exit 3.--progresswith any value butjsonlexits 3 withError: --progress must be jsonl. A Click usage error keeps the ordinary format and emits no events. - In JSONL mode, stderr is exclusive to the events. Per the code, warnings, logging and tracebacks do not appear on it, and an unexpected exception becomes exit 1 with start and error events and no traceback.
- No clock or throughput field exists, and no task text, response, path or credential is an event field. It is not a progress bar.
The offline --evidence paths and the option check were run on v0.77.1; the events of live groups were read in the code. See soup ship.
Unknown config keys name the Soup field
Unknown config keys still refuse the load; what changed is the hint (#2099, #2138 by @Nari-Ab). A key written the way another library names it now names the Soup field: learning_rate suggests lr, num_train_epochs suggests epochs, and training.lerning_rate suggests training.lr instead of an unrelated field. "Use X" is for those names and one-edit typos of them; a name that is only similar gets the "did you mean" ranking, with a cutoff of 0.65 in every section. per_device_eval_batch_size, per_gpu_eval_batch_size, eval_batch_size, warmup_steps and warmup_step have no Soup counterpart and get a sentence saying so, at the top level and under training: alike. learning_rate_decay is no longer sent to a different setting. The hints are diagnostics: the schema still decides what loads.
This config is refused on purpose, and soup doctor --config soup.yaml prints:
base: org/model
task: sft
data:
train: ./data.jsonl
training:
learning_rate: 2e-5
per_device_eval_batch_size: 4
warmup_steps: 10
bacth_size: 4Config validation error:
unknown config key 'training.learning_rate' - use training.lr. Refused.
unknown config key 'training.per_device_eval_batch_size' - Soup has no separate evaluation batch size; evaluation runs at the training batch size. Refused.
unknown config key 'training.warmup_steps' - Soup has no step-count warmup setting; warmup is a fraction of the training steps, set with training.warmup_ratio (0 to 0.5). Refused.
unknown config key 'training.bacth_size' - did you mean 'batch_size'? Refused.
Config could not be loaded (see above).The command exits 2. See Settings Soup refuses at load.
Layer streaming
Three changes, none of them with a hardware record.
- A LoRA target on a streamed
embed_tokensorlm_headof an untied checkpoint is accepted, and it saves and resumes (#2159 by @kok-o). Through v0.77.0 it was refused when the streamed model was built. Since v0.77.1 the adapter saves with PEFT's defaults and resumes through a Trainer checkpoint orload_adapterwith every tensor kept, reloads keep the runtime's device map, and DPO's reference adapter can attach through the streaming wrappers. This completes item 1 of #1048, following the build-time guard from #2082. Upstream's checks are CPU tests with PEFT 0.20.0 and 0.21.0: they establish correctness, not CUDA speed or memory, and no figure is published for it. Saving needs CPU memory for each adapted boundary matrix for a moment, the run's matching shard cache has to stay available through saving and resume, and loading weights into an arbitrary new named adapter is still unsupported. A tied checkpoint keeps both weights resident and follows the normal PEFT path; this change does not touch it. - Building a streamed fused-expert MoE no longer asks the CPU for each full expert tensor (#2157 by @ybai08, closes #2154). The skeleton is built on
meta, but each MoE layer first created its expert tensor on the CPU (15 GB for DeepSeek-V3, 22.5 GB for Kimi K2 in bf16) before moving it. On Linux with strict overcommit, a small machine, or Windows with too little page file this failed withDefaultCPUAllocator: can't allocate memoryornot enough memory. Every CPU request of 1 MiB or more is now made onmetawhile the skeleton is built, and a model that registers a buffer that large is built the old way with the buffer named in a warning. A refused CPU allocation now gets a short message and a fix hint. This is a setup fix: nothing has been trained on K2. - Two reads of a streamed routed expert per step instead of three (experimental, API only; #2158 by @Batyrkhantzy111).
build_streamed_model(..., two_read_experts=True)keeps the decoder checkpoint but no longer fetches routed experts in its replay, so the forward and the expert's own backward read each expert once, and the router reuses the forward's top-k choice while its scores stay differentiable. It needs theper_expertlayout and reaches DeepSeek-V3-family bases only. There is no config field, flag or default change, sosoup trainstill reads each routed expert about three times per step. A forward hook on a two-read layer, its MoE block or its routed experts is refused with an error. On CPU the losses and gradients equal the resident model's bit for bit. No hardware speed record is part of this release (its scope is discussed in #2170).
See Layer streaming and Streaming a Mixture-of-Experts base.
Also fixed
/v1/tools/pythonreports what failed (#2204 by @Nari-Ab, closes #2194). A timeout istimed_out: truewithexit_code: 124and no output. Other process failures carry their stdout, stderr and exit code withtimed_out: false, and output overflow isexit_code: 1. Every HTTP 401 from the tool routes carriesWWW-Authenticate: Bearer. Through v0.77.0stderrwas always empty andtimed_outwastruefor any failed run, so a caller that treated every non-success as a timeout now seestimed_out: falsefor an ordinary failure. A line number in a Python traceback counts a six-line network-guard prefix, so the first line of your code is line 7. The route is still unavailable (501) on Windows and on Linux with Python 3.11. This was verified on the function the route calls, not over HTTP. See Inference server.soup eval gatehandles a malformed suite (#1895 by @Nari-Ab, reported by @taljeon in #1878). Malformed YAML and non-mapping roots ([1],5,hello, and the empty[],false,0,"") exit 3 withCannot load suite:and a message that names the file. Before, malformed YAML and a list or scalar root exited 1 with a bareError: TypeErrororParserErrorline, and an empty root such as[]exited 3 with a generic validation message.soup train --gatewith such a suite exits 1, and the training callbacks keep their fatal-failure policy. See Eval-gated training.- Adapter commands name the missing dependency (#2141 by @Nari-Ab, closes #2056). They reported corrupt weights when NumPy or safetensors was missing. Read-only operations (
check,diff,scan) now ask for NumPy, and merge and arithmetic ask for NumPy and safetensors:numpy package required; pip install "soup-cli[train]" (or pip install numpy)andnumpy and safetensors packages required; pip install "soup-cli[train]" (or pip install safetensors numpy), both exit 1 (run with NumPy blocked). See Adapter check. soup exportcleans up after a failed step (#1464 by @ShivanshShukla in #2164). The temporary.soup_merge_tmp_<adapter>/directory is removed when llama.cpp is not found, a clone or install fails, or the merge dies while saving, for the gguf, onnx, tensorrt, bitnet and gguf-ud paths; before, a multi-gigabyte directory was left next to the adapter. It removes only a directory that export created: one that was already there is left alone, and the output says so. This was read in the code and not run.- Spike recovery hints are written safely (#2169 by @Nari-Ab). They refuse symbolic-link targets and are replaced atomically, which keeps other hard links and the previous hint when the write fails, without consuming an attempt.
soup profile --gpuand the MCPprofiletool know the RTX 30-series laptop GPUs (#2144 by @abdulwaarith0).--gpu "rtx3060 laptop"was "Unknown GPU", and the laptop 3060 has 6 GB, not the desktop card's 12. Each part now has its own entry with NVIDIA's memory, the names the driver reports resolve to it, and a size written as a word ("A100 40GB") reaches the matching entry.- A malformed model file no longer stops the estimate. A model's
config.jsonthat is malformed, or states a figure no model has, is treated as an unreadableconfig.jsonby the VRAM pre-flight,soup plan,soup profileand the MCPprofiletool (soup profileprices a guessed shape and says so;soup planrecords that it could not price the run). A malformed.safetensorsheader is skipped and the parameter count comes fromconfig.json, approximately. - A mixture-of-experts model known only by its cached
config.jsonis priced with all its experts, not as a dense model of a few billion parameters; the estimate's note says the count is approximate. - Names from a config or a command line reach the terminal cleaned.
soup profileprints the LoRA target modules, andsoup adapters bisectthe checkpoint ids, with control characters and bidi marks removed. training.curriculum_dynamicis documented as telemetry (#2142 by @Nari-Ab). Its weights are logged tocurriculum_history.jsonlforsoup runs curriculum-curveand do not change which samples a batch contains. The schema descriptions, reference entries and training guide now say so; behaviour is unchanged.
Five v0.77.0 statements corrected
Upstream corrected five statements about v0.77.0 in its own text (the v0.77.0 CHANGELOG, SECURITY.md, docs/backends-and-ops.md and the README What's New). No behaviour changed; the only source edits are two comments (#2244).
soup doctor --config. The System, GPU, System Resources and Dependencies tables still print first. What moved is theAll checks passed!line: the config is checked before it, and a refused, missing or unreadable config exits 2 without it. The earlier text had said the config is checked "before the environment summary".- A malformed KTO label is dropped, not refused. A KTO row whose
labelis not a boolean, atrue/false-style string or exactly-1,0or1is dropped with a warning (soup trainnames the first such row and stops only when none is left;soup data validate --min-valid-fraction 1.0exits 2). It is not refused at load, as the earlier text listed it: fourteen of the settings on upstream's list of fifteen stop the load, not fifteen. - A
dpooripoterm under asimpoororpoprimary trains (since #1828), against an adapter-off reference. Only with full fine-tuning, which has no adapter to switch off, does it still stop at the first step, with a message that names the fix. The #1785 entry had said it always stopped. This is not a statement about every blend of preference losses. - Telemetry: as released, nothing is sent. The bundled analytics key is a placeholder, so an opted-in command prints
Notice: telemetry is not yet live.and makes no network request. Only an operator's ownSOUP_POSTHOG_KEY, and optionally an HTTPSSOUP_POSTHOG_ENDPOINT, gives one HTTPS POST at command exit; the payload list now names every field it carries. The earlier text that said to set both is wrong: a key alone is enough, and without an endpoint the bundled default is used. Upstream'sSECURITY.mdadds that an endpoint you set must be HTTPS and that loopback, private and internal hosts are refused. - Which commands refuse a staged field or a refused deprecated value:
soup trainexits 1,soup doctor --configexits 2,soup recipes verify --configandsoup ship --configexit 3.soup planandsoup applydid not at v0.77.0; since v0.77.1 they exit 1 (see above).
See the v0.77.0 page for the release these statements describe.
Measurement records
Three files were added under benchmarks/ at this tag, which makes 53 records. No existing record changed, and no record covers MLX GRPO, soup infer --backend mlx, two_read_experts or the Windows PATH change.
gate-2049-resident-vram.md(#2146): the 39 resident runs behind the shared estimate. One RTX 3060 Laptop 6 GB under WSL2 on Windows 11, seven dense models from 135M to 3B parameters, vocabularies from 32,000 to 151,936. The record says its coefficients are fitted on those rows, so the figures it prints show the fit and not an error on unseen runs. Seven of the rows allocated more than the free memory and spilled into shared memory under WSL2; in the record's words, on a native Linux driver those runs would have run out of memory. No model of 7B or more, no mixture-of-experts model, nomediumorselectiverow, no fused loss and no Unsloth run was measured.gate-2050-selective-checkpoint.mdand its pre-registered protocol (#2050; #2161 by @Shutaru): selective attention checkpointing of streamed NF4, on one RTX 3090 on Windows with Mistral-7B, five alternating pairs per comparison. Only saving Q/K/V/O at sequence length 2048 met the rule fixed in advance: 3.1% less measured time (interval 3.0% to 3.4%), for +1.342 GB retained. Q/K/V at 2048 and Q/K/V at 512 were inconclusive, and Q/K/V/O at 512 was slower in all five pairs. The record did not isolate the cause of the short-sequence variation. Every logit, loss, LoRA gradient tensor and updated parameter matched exactly over two updates. The installer is benchmark-only and nothing insoup trainuses it, and default flash attention and other GPUs were not measured, so this is not a claim that streaming got faster.- The
stream_loss_chunk_vramharness runs each arm in its own process (#2151 by @ybai08), because only the first arm per process got the RAM tier. It is step 1 of #1693's order, and it still has no GPU record.
The preprint (DOI 10.5281/zenodo.21771064) is unchanged: no number it states was re-measured or corrected, and its measurements stay scoped to the Soup versions it names. Two-read routed experts, saving a streamed LoRA on an untied embedding or head, and the selective-checkpointing record are outside what it measured. The records and their scopes are listed on Measurement records.
Docker and install
pip install -U soup-cli
soup --versionWith pipx, pipx upgrade soup-cli. The Python range is unchanged at >=3.11,<3.14; there is no dependency change in this release.
The image is ghcr.io/makazhanalpamys/soup:0.77.1, also tagged latest. The Dockerfile is unchanged since v0.77.0: Ubuntu 24.04 with Python 3.12 on a CUDA 12.8 runtime, installing exactly this release with the train, serve, data and eval extras. The host driver must support CUDA 12.8.
docker pull ghcr.io/makazhanalpamys/soup:0.77.1
docker run --gpus all -v "$(pwd)":/workspace ghcr.io/makazhanalpamys/soup:0.77.1 train --config soup.yaml --yesKeep the --yes. docker run without -t gives soup train no terminal on stdin, and since v0.77.0 soup train exits 3 in that case unless --yes is passed. The README's Docker example left it out and exited 3 on v0.77.0; v0.77.1's README passes it, and so do its four translations (#2244, closes #2229). The image was not pulled for this page.
Credits
22 of the 28 pull requests in this release came from 11 people outside the maintainer, in the release body's count. Grouped by person, in the release body's order, with what each found or built:
| Who | PRs | What |
|---|---|---|
| Nariman Abdikarim (@Nari-Ab) | 7 | Unknown-key hints that know other libraries' parameter names (#2138); soup eval gate exits 3 on a malformed suite (#1895, reported by @taljeon); adapter commands name the missing dependency (#2141); /v1/tools/python failure diagnostics and WWW-Authenticate (#2204); safe spike recovery hints (#2169); the curriculum_dynamic telemetry contract (#2142); the GGUF progress test isolated from a developer's own converter (#2207); the Windows-path test change that keeps the suite green on pytest 9.2.0 (from #2284) |
| Yang Bai (@ybai08) | 3 | The streamed fused-expert MoE build without a CPU allocation first (#2157); the stream_loss_chunk_vram harness with one process per arm (#2151); the fresh-process streaming test on this tree (#2208) |
| Nurkhan Esenbek (@kok-o) | 2 | Save and resume of streamed LoRA on an untied embedding or head (#2159); soup ship --progress jsonl (#2171) |
| Abdulwaarith Zakariyya (@abdulwaarith0) | 2 | The single peak-VRAM estimate and its calibration on 39 runs (#2146); the RTX 30-series laptop GPUs (#2144); the pytest 9.2.0 diagnosis and fix (#2286) |
| Saurav Mukherjee (@SauravMukherjee44) | 2 | Vision and audio checkpointing coverage (#2180); the atomic-writer destination-directory pin (#2179) |
| dryn85-max (@dryn85-max) | 1 | Native GRPO on Apple Silicon (#2182) |
| AmixDigital (@Amix29) | 1 | Native MLX inference, soup infer --backend mlx (#2121) |
| Batyrkhan (@Batyrkhantzy111) | 1 | Two reads of a streamed routed expert per step (#2158) |
| Aditya Pratap Singh (@akkupratap323) | 1 | Langfuse zero scores kept as negative feedback (#1871) |
| Shivansh Shukla (@ShivanshShukla) | 1 | soup export removes its temporary merge directory on failure (#2164) |
| Shutaru (@Shutaru) | 1 | The selective attention checkpointing measurement for streamed NF4 (#2161) |
Reports that led to fixes in this release, as the release body thanks them: @taljeon (#1878) and @abdulwaarith0 (#2286). The six maintainer pull requests (#2205, #2213, #2242, #2244, #2245 and #2261) carry no credit line here.
Known limitations
New in this release, in upstream's terms:
- The pre-flight's estimate was fitted on dense models of 3B and below on one 6 GB card (
benchmarks/gate-2049-resident-vram.md). No model of 7B or more, no mixture-of-experts model, nomediumorselectivetier, no fused loss and no Unsloth run was measured, so for those it warns and starts instead of refusing, unless the run's weights, CUDA context and trained-parameter state alone are more than the card holds (see above). A model known only by its cachedconfig.jsonis counted from that file (approximate for a mixture-of-experts one). - A distill config with
minillm_enabled: trueandminillm_teacher_mix_ratio: 0needstraining.minillm_on_policy: truewritten in the YAML forsoup planandsoup apply, which have no--minillm-on-policyflag. soup data persona-mixstill writes over its--promptsinput when--outputnames it.mix,forge,generate,gen-magpie,recipeandcanary checkhave not been checked yet. Both are tracked in #2226.- MLX GRPO is the dense baseline.
quantization: 8bit,lora.r: 0, custom or ensemblereward_fnsettings and non-text modalities are refused at load, and--resumeis refused because only the adapter is saved (#1976). Native MLX inference covers text LoRA adapters only, andsoup chaton MLX is open (#1986, #2123). two_read_expertshas no hardware speed record yet; its scope is discussed in #2170.
Upstream repeats the limitations of v0.77.0, except two that it marks as fixed since v0.77.0 and no longer limiting, and it does not repeat the first two items of the v0.77.0 list (the rl_checkpoint_include_* refusal and the AWQ and GPTQ removal; see that page): the 22.5 GB CPU commit while a full-size Kimi K2 is set up (#2154) and the empty stderr of a failing /v1/tools/python call (#2194). The soup plan and soup apply gap and the refusal of a LoRA target on an untied streamed embedding or head, both listed in v0.77.0, are closed above. The v0.77.0 list is on its page.
What was checked, and what was not
Checked for this page, on the v0.77.1 binary (a light install with no Torch, MLX or GPU, on tiny local inputs, with the network off): the output refusals of the eighteen soup data commands, with each exit code and several spellings, and persona-mix still overwriting its prompts; soup plan, soup apply and soup apply --dry-run on a staged field (exit 1, no plan, state file unchanged), on a valid config, and the peak_vram_gb: null state file on both v0.77.0 and v0.77.1; soup doctor --config and soup train --dry-run --yes on an MLX GRPO config, the --resume and quantization: 8bit refusals, and the nine-combination table; soup ship --evidence --progress jsonl on SHIP, DON'T SHIP and unreadable evidence; the soup infer --backend mlx option refusals; the unknown-key messages; soup adapters bisect on both binaries; the adapter dependency messages with NumPy blocked; the soup train --cloud lambda output refusal; and the VRAM pre-flight against synthetic card sizes. Test files go from 1070 to 1106, and the catalog stays at 171 recipes.
Read in the code and not run: the MLX GRPO training loop and MLX generation (no Apple Silicon here); streaming, including the untied embedding and head adapter, the MoE skeleton build and two-read experts (no Torch or CUDA here); /v1/tools/python, verified at the function level because the HTTP route answers 501 on Windows; soup export cleanup; the spike recovery hint write; the Windows PATH rules, with the symlink and junction spellings as upstream lists them; the cloud controllers' behaviour with ssh or scp missing; and any Hub request. The Docker image was not pulled.
Not measured, and not claimed: an MLX speed, memory, time or accuracy figure, a speed or memory effect of the streamed boundary adapter or of two-read experts, and the estimate's error on any run outside the 39 it was fitted on. The selective-checkpointing record is a benchmark of an installer nothing in soup train uses.
See also
- v0.77.0: what v0.76 warned about is now refused: the release before this one, and the refusals this one extends to
soup planandsoup apply. - Settings Soup refuses at load, Configuration and Pre-flight and tooling: the load checks,
soup planand the hardware-fit calculator. - Exit codes: the full table.
- Data tools, Data Forge and Data Moat II: the
soup datacommands. - Apple Silicon MLX backend, soup ship, Inference server and Environment variables.
- Layer streaming, Streaming a Mixture-of-Experts base and Measurement records.
- What's new across v0.71 to v0.77.
- The upstream changelog at the tag and the comparison with v0.77.0.
Soup is free and Apache-2.0. If it saved you a training run, starring the repo costs nothing and helps most. You can also fund the GPU time behind the work a 4 GB laptop cannot reach.