v0.77.0: what v0.76 warned about is now refused
v0.77.0 was tagged on 2026-10-26. Upstream gave it no codename and titled it with a sentence again: what v0.76 warned about is now refused. It is a cut of the development branch, not a backport like v0.76.1, and it carries v0.76.1's seven changes. In the release body's words, it "ends the one-release notice v0.76 gave: the config fields and values that v0.76 loaded with a "Soup v0.77 will refuse it" warning now stop the load, and so do fifteen settings that v0.76 accepted without a word and never applied." Fourteen of those fifteen stop the load; the fifteenth, a malformed KTO label, drops the row at data load instead (see below).
151 of the 226 pull requests in this release came from 44 people outside the maintainer. That is the release body's own count; the names are under Credits.
Four things in it cost you something if you skip them:
- A config that loaded on v0.76 can stop loading on v0.77.0. Run
soup doctor --config soup.yamlbefore you upgrade a working setup. See Before you upgrade. - Python 3.10, AWQ export and GPTQ export are gone. pip on Python 3.10 keeps installing v0.76.x, and you can pin it with
soup-cli[train]<0.77. AWQ and GPTQ export need v0.76.x and its[awq]or[gptq]extra. See Install and upgrade. - The two
soup serveroutes that run code need a token on every bind, loopback included. See The soup serve tool routes. - Some finished runs trained something other than what their config said, among them a
dpo,ipoorgrporun resumed from a checkpoint. See Re-run this.
Before you upgrade
soup doctor --config soup.yamlRun it with v0.77.0 installed, on each config you train from. A refused config prints Config validation error:, one indented line per refused setting, then Config could not be loaded (see above)., and the command exits 2. soup train exits 1 on the same config, soup train --dry-run included. Since v0.77.0 soup doctor no longer prints All checks passed! above a refusal (#2100, #2106); its system, GPU and dependency tables still print first. A config that leaves the refused settings unset, or writes them at their defaults, loads as before. The exception is training.quantization on the eight tasks that never quantise: there an explicit 4bit, the schema's default, is refused, and only leaving it unset or writing none loads.
This is what v0.77.0 prints for a config that sets four staged fields and one deprecated value (lines joined where the terminal wrapped them):
Config validation error:
training.lr_groups is accepted but read by nothing; refused as of v0.77. Delete the line.
training.early_stop_patience is accepted but read by nothing; refused as of v0.77. Delete the line.
training.rl_checkpoint_include_ref_model is accepted but read by nothing; refused as of v0.77. Delete the line.
data.video_fps is accepted but read by nothing; refused as of v0.77. Delete the line.
data.remove_unused_columns: true never had an effect (no trainer reads it; the trainers that set it pass false). Delete the line. Refused as of Soup v0.77.
Config could not be loaded (see above).The terminal now prints a value refusal without the Value error, prefix that v0.76 put in front of it. Three rules for what to do with the answer:
- Delete the line, do not rename it. In upstream's words, none of the staged fields "changed what a run did, so deleting them changes no result." For regression gating and early stopping, which several of them promised, use
soup train --gate <suite.yaml>. - Fix it and run it again until it prints no error. The staged-field lines and the deprecated-value lines arrive together in one report, staged lines first. A cross-field refusal, such as
training.relora_stepsontask: dpo, is reported one at a time: the next one shows only after you fix the first. soup planandsoup applyare not this check. At v0.77.0soup plan --configwrites a plan, andsoup apply --dry-runreports no drift, both with exit 0, for a config thatsoup trainrefuses. Usesoup doctor --configorsoup train --dry-run.
Re-run this
Every row is something upstream's changelog or release body says ran wrong in a release before v0.77.0. Most of them did not raise, and most exited 0. Text in quotation marks is upstream's own wording, the rest a close paraphrase of the same entry, and this page adds no explanation of why. Pull request numbers are upstream's, issue first where both are given. "Verified at v0.76.1" means this site's audit read or ran the v0.76.1 code; elsewhere the scope is upstream's "before this release". The remedy is to re-train, re-judge or regenerate the artifact unless the row says otherwise.
Two rows, lora.use_rslora and freeze_layers with LoRA, are settings that now take effect: the same config trains something different on v0.77.0, and re-training matters only if you wanted the setting.
Training artifacts
| What was wrong | Who is affected | PR |
|---|---|---|
| A resumed run "restored the optimizer and the step counter and trained a freshly initialised adapter: on a 4-step run resumed at step 2, the result was two steps from zero." Upstream: "Re-run any dpo, ipo or grpo training that was resumed from a checkpoint." | soup train --resume on task: dpo, ipo or grpo, and task: preference with dpo or ipo. Upstream says grpo with grpo_beta: 0, orpo, simpo, bco, kto, sft and reward_model were not affected. Verified at v0.76.1 | #1768, #1797 |
training.relora_steps on a loss with a frozen reference: "every ReLoRA restart merges the trained adapter into those weights, so from the first restart on the reference was the policy as of that step, without an error." Refused at load since v0.77.0 | relora_steps with task: dpo, ipo, kto or bco, grpo with grpo_beta above 0, or task: preference whose loss or blend names dpo, ipo or bco, on v0.76.0 and v0.76.1 (the merging restart arrived in v0.76.0; through v0.75.2 a restart pruned the adapter instead). To train again, remove relora_steps | #1835, #1891 |
training.freeze_layers / freeze_ratio "did nothing on a LoRA run": every targeted module got an adapter, the frozen layers included. Since v0.77.0 a frozen layer gets no adapter, so such a config "now trains fewer layers than it did before, which is what the option always documented" | sft and tts LoRA runs that set either key. Verified at v0.76.1. On v0.77.0 the freeze needs a list lora.target_modules; a regex, all-linear, lora.target_parameters, lora.use_vera and a plan that freezes every layer are refused when the model is built | #1432, #2145 |
lora.use_rslora: true "loaded on both backends and was never read, so an rsLoRA run trained plain-LoRA scaling (alpha / r, an 8x smaller update at the default r: 64, alpha: 16)". Such configs now train at the rank-stabilised scale and their results change; upstream: "drop the flag to keep the old alpha / r behaviour" | backend: mlx or backend: unsloth with use_rslora: true. Verified at v0.76.1 | #1853, #1899 |
LLaMA Pro with a LoRA rank: "The block expansion runs before get_peft_model, which froze the appended blocks again, so only adapters on the original layers trained while the appended blocks stayed the identity." Refused at load since v0.77.0: on task: sft the error points to lora.r: 0, and on task: pretrain, whose trainer always attaches an adapter, LLaMA Pro is refused and the error points to task: sft | training.expand_layers with freeze_trainable_layers and any lora.r above 0 (the default is 64), on task: sft or pretrain. Verified at v0.76.1 | #1326, #1783, #1875 |
An explicit training.unlearn_alpha: 0.0 ran as 1.0. Upstream now respects it "for pure forget loss in unlearn training (NPO, SimNPO, and RMU) instead of silently falling back to 1.0" | task: unlearn with unlearn_alpha: 0.0. Verified at v0.76.1 | #2108, #2114 |
Under gradient accumulation, PRM and RAFT "logged and clipped losses G times too large". Now the logged loss, train_loss and grad_norm "match a gradient_accumulation_steps: 1 run", and max_grad_norm clips the gradient for the stated loss | task: prm, and task: sft with data.format: raft, at gradient_accumulation_steps above 1 (the default is 4). Verified at v0.76.1 | #1444, #1771 |
| KTO labels: upstream now refuses "malformed KTO labels instead of silently training nulls, floats or containers as desirable or undesirable rows" | task: kto on data whose label is not a boolean, a true / false-style string, or exactly -1, 0 or 1. On v0.77.0 the loader drops such a row with its reason, and soup data validate data.jsonl --format kto counts them and prints the reasons for the first three; fix the labels and re-train. Verified at v0.76.1 | #2119, #2126 |
target_modules: auto on a DeepSeek-V3 base whose config sets q_lora_rank: null, such as Moonlight-16B-A3B: "the key/value and output projections took the adapter and the query path trained none, without a warning." | v0.76.0 and v0.76.1, where the deepseek_v3 row of the auto table arrived. A base with an integer q_lora_rank is unaffected | #2071 |
moe_lora: true dropped LoRA targets. A string target_modules was replaced by the MoE list: all-linear, which target_modules: auto resolves to on qwen4_exp, so "a qwen4_exp base with moe_lora lost the Gated DeltaNet" projections among others, and any other string, a regex, "was dropped outright". Under stream_layers, every name the MoE list does not repeat lost its adapter ("on Qwen3.5-MoE the linear-attention in_proj_qkv / out_proj were dropped"), and "an explicit lora.target_modules list was dropped entirely" | moe_lora: true with a string lora.target_modules, resident or streamed, and moe_lora: true with stream_layers: true. Verified at v0.76.0 and v0.76.1 | #2072, #2095 |
On a Mistral Hub repository that ships consolidated.safetensors beside the shards, soup spectrum scan scored both name sets and "emitted unfrozen_parameters matching no parameter" | Scans of such a repository, and Spectrum runs configured from them: run the scan again on v0.77.0. Verified at v0.76.1 | #1806, #1906 |
Removing the line changes nothing. Refused at load since v0.77.0, and trained without the setting before: training.use_mod: true on a route that installs no router ("Full fine-tuning, LISA and Spectrum on SFT, vision, audio, layer streaming, unsloth and MLX SFT all trained without one"); a non-default moe_aux_loss_coeff on backend: unsloth or with stream_layers ("loading and being ignored"); neftune_alpha on the 14 tasks that never apply it ("silently dropped"); citation_faithful on pretrain; reasoning_effort and train_on_eot on pretrain, classifier, reranker and cross_encoder ("loaded and changed nothing"); vision or audio task: distill ("accepted and ignored by the text-only trainer"); vision or audio SFT on backend: unsloth ("silently ran on transformers"); and lora.rank_pattern / alpha_pattern on MLX or Unsloth ("trained a uniform adapter") | A config that carried one of them. Deleting the key trains what the run trained before. Upstream adds that "a pretrain cache built with the flag has to be rebuilt without it" for train_on_eot; if per-module ranks mattered, train them on backend: transformers | #1752, #1757, #1412, #1749, #1820, #1883, #1406, #1843, #806, #1587, #1186, #2014, #2015, #1996, #2153 |
Served answers
| What was wrong | Who is affected | PR |
|---|---|---|
soup serve --backend sglang with a LoRA adapter: "The backend registered the adapter at startup but never named it, so every answer came from the base model while /v1/models and each response reported the adapter." | Anything answered, judged, benchmarked or gated through soup serve --backend sglang with an adapter as --model: run it again on v0.77.0, whose [sglang] extra needs sglang>=0.5.1. A full model is unaffected. Verified at v0.76.1 | #1724, #1732 |
Verdicts and gates
| What was wrong | Who is affected | PR |
|---|---|---|
tool_call_match and tool_call_args_subset "compared parsed arguments with Python ==", so "a model that answered {"enabled": 1} where the task expected {"enabled": true} scored 1.0". "Scores recorded before this change can be higher than the same outputs score now." | Custom eval task files whose rows use scoring: tool_call_match or tool_call_args_subset, read by soup eval custom, the custom tasks of soup eval gate, the metric leg of soup ship and soup eval quant-check | #1755, #1756 |
The bundled-suite scale moved twice: "scorer revision 6 prevents generation failures from earning credit", and mini_mmlu "no longer sits at a 1.000 ceiling": it "grows from 26 to 40 rows", and "a model that always answers "B" drops from 0.462 to 0.300". Separately, the MCQ extractor "now accepts a lone option letter wrapped in markdown emphasis, quotes, backticks or square brackets" | soup ship and soup eval gate scores and --baseline files from the bundled suites. Upstream: a baseline written by v0.76 "is not compared silently on the new scale: write it again" | #1759, #1958, #1192, #2113, #1442, #1924 |
soup eval gate passed or recorded what it never measured: it "passed a suite with no tasks", "duplicate gate task names overwrote baseline evidence", "an empty judge prompt file scored 0.0 without judging anything", and "a NaN or infinite baseline let soup eval gate pass" | Gate verdicts and baseline files from suites of those shapes. On v0.77.0 an empty or repeated task list and a non-finite baseline exit 3, and an empty judge prompt file fails the gate with exit 2 | #1798, #1821, #1824, #1893, #1910, #1928, #1773, #1779 |
soup drift-alarm could print an OK panel and exit 0 when it read no live or reference row. Upstream: it "exits 1 and shows no OK panel when it could read no live or reference row, naming the file and the keys it reads (output, response, text)" | Drift verdicts whose panel showed n_reference: 0 or n_live: 0: nothing was compared | #1808, #1905 |
| A CMA-ES merge in which every evaluation failed wrote "a "converged" merge of the first sample" and exited 0 | soup adapters merge --strategy cmaes runs whose evaluations all failed. On v0.77.0 the merge exits non-zero and writes no adapter | #1808, #1905 |
Data, configs and records
| What was wrong | Who is affected | PR |
|---|---|---|
| Six data quality commands "scanned alpaca, preference and ShareGPT rows as empty and reported them clean" | Scans of alpaca, DPO, KTO or ShareGPT data with soup data pii, toxicity, score, langdetect, educational or decontaminate: scan again on v0.77.0 | #1427, #2037 |
soup data validate "did not count rows the loader skipped" (not a JSON object, or not valid JSON). Since v0.77.0 they count as invalid in the N/M rows valid line, in --min-valid-fraction and in the exit code | Validation results for files with unparseable lines, and the CI steps that relied on them | #1887, #1908 |
"The gate soup ci init writes passed a file with nine broken rows out of ten." v0.77.0 writes soup data validate <data> --min-valid-fraction 1.0; upstream: "existing workflows are unchanged" | A .github/workflows/soup-gate.yml written by soup ci init before v0.77.0: regenerate it with soup ci init --force and the same flags, or add --min-valid-fraction 1.0 to its validate step | #2055, #2079 |
| Trace and ingest parsers could pair answers from different conversations. Upstream now keeps "context through the last user turn so unrelated answers do not form preference pairs" | Preference files built by soup data from-traces from multi-turn traces, including files written by soup ingest: build them again, and re-train a model trained on them. Verified at v0.76.1 | #1809, #1848 |
"Four commands could overwrite an input with their output." v0.77.0 refuses an output path that is also an input of soup ingest, soup prune-prompt, soup ra-dit or soup data best-of-n | Anyone who gave one of those an output equal to an input: check that input against its source | #1422, #2018 |
soup migrate "dropped the validation split and re-defaulted LoRA settings"; it "now writes what the source trained instead of Soup's defaults", where lora_target: all had become auto, "two of seven projections on Llama" | Configs generated by soup migrate from axolotl, LLaMA-Factory or an Unsloth notebook: generate them again and compare. v0.77.0 also lists every source key it does not carry in one warning | #1807, #1901 |
soup compile "never handed the eval signal to DSPy, TextGrad and GEPA" | Programs from soup compile and tool descriptions from soup compile-tools: compile again | #1810, #1956 |
"The RL checkpoint manifest now reports has_ref_model: false and has_rollout_buffer: false, because no save writes reference-model or rollout-buffer state." Before, it copied both flags from the config, "so a checkpoint holding only the adapter and optimizer claimed both" | Mid-epoch RL checkpoints whose manifest says either is true: they hold the adapter and the optimizer only. Setting either flag to true is refused at load on v0.77.0 | #808, #1980 |
If a row names a verdict, a scan or a config, the artifact is that verdict, scan or config, not the weights: a model trained from it changes only where the row says so.
Not rows, and why. Runs that crashed have no finished artifact to replace: task: pretrain with lora.r: 0 (#1875; LLaMA Pro on pretrain with a LoRA rank finished and is in the LLaMA Pro row above), llava and sharegpt4v rows on the preference tasks (#2064), a preference blend that named dpo or ipo on v0.76.0 or v0.76.1 (it stopped at the first step; since v0.77.0 such a term trains, with the exceptions listed under Behaviour), and MLX on a split smaller than one batch (#1857). Changes to memory or speed only are not rows either: training.gradient_checkpointing (see Behaviour that changes results, memory or exit codes), stream_layers with use_cut_ce and quantization: 8bit on Unsloth (both refused now, below), and use_liger: true reported as applied on model types it has no kernels for (#1947). MLX SFT runs were never seeded before this release, so a run started before it cannot be reproduced; it was not wrong, and there is nothing to re-train.
What is refused at load now
The staged fields and the deprecated values
v0.76.0 warned about these, all but the two named below, and named v0.77. v0.77.0 refuses them.
- 31 staged fields (validated, read by nothing), each refused when set away from its default, on its own line ending
refused as of v0.77. Delete the line.:training.long_context_grpo,vision_grpo,quantize_ref_model,grace_codebook,grace_codebook_size,grace_codebook_dim,lr_groups,early_stop_patience,citation_recall_threshold,forgetting_eval_steps,forgetting_benchmark,forgetting_stop,checkpoint_eval_steps,checkpoint_eval_metric,checkpoint_eval_tasks,checkpoint_keep_top,convergence_window,convergence_rel_tol,rl_checkpoint_include_ref_model,rl_checkpoint_include_rollout_buffer, anddata.video_dir,video_fps,video_maxlen,image_min_pixels,image_max_pixels,image_resize_algorithm,split_thinking,eval_on_each_dataset,resize_vocab,extend_conversation,skip_prepare_dataset. - Two of them were never warned about.
training.rl_checkpoint_include_ref_modelandrl_checkpoint_include_rollout_bufferwere staged after v0.76.0 and were in no release, so v0.77.0 refusestruewith no warning release in between. An explicitfalseloads. data.remove_unused_columns: true. No trainer ever read it. Configs written bysoup autopilotand run-config dumps from v0.75 and earlier carry it. Delete the line.training.quantization: 4bitor8bit, ortraining.load_in_8bit: true, on a task that never quantises the base:distill,classifier,reranker,cross_encoder,prm,moe_lora_routing,unlearnandasr. Delete the line or setquantization: none.soup ab --betaexits 2 and endsRefused as of Soup v0.77.Remove it; for a stricteraccept_h0, lower--alpha.
A soup sweep --param that sets one of the 31 staged fields away from its default in any arm is refused the same way: the command exits 1 before the first arm starts. A deprecated value such as data.remove_unused_columns=true is not part of that pre-check (soup sweep --dry-run exits 0), and it is refused inside each arm that sets it instead. When a staged field's own check fails as well (for example grace_codebook: true without a size and a dimension), only the staged line is shown, so no message asks for a line that the next load refuses.
Fifteen settings v0.76 accepted without a word
This is upstream's list. Each message names the setting and what to change:
training.use_mod: trueon a route that never installs a Mixture-of-Depths router (#1752)- a non-default
training.moe_aux_loss_coeffonbackend: unslothor withstream_layers(#1749) training.expand_layers(LLaMA Pro) with a LoRA rank (#1783), and ontask: pretrain(#1875)lora.r: 0ontask: pretrain(#1875)training.citation_faithfulontask: pretrain(#1843)training.neftune_alphaon the 14 tasks that never apply it (#1883)training.relora_stepswhere the loss has a frozen reference (dpo,ipo,kto,bco,grpowithgrpo_betaabove 0, and blends naming them) (#1891)quantization: 8bitwithbackend: unsloth(#1946)- vision or audio
task: distill(#2014) - vision or audio SFT on
backend: unsloth(#2015) training.stream_layerswithtraining.use_cut_ce(#2023)llavaorsharegpt4vrows ondpo,ipo,simpo,orpo,bcoandpreference; thepixtral-dporecipe is retired (#2064)lora.rank_patternorlora.alpha_patternwithbackend: mlx(#1996)lora.rank_patternorlora.alpha_patternwithbackend: unsloth(#2153)- a KTO
labelthat is not a boolean, one of the documented strings or exactly-1,0or1(#2126). This one is a data check, not a config refusal: the row is dropped at data load with its reason, whichsoup data validateandsoup train --dry-runreport before any model loads, andsoup data validatestill exits 0 while some row is valid unless you pass--min-valid-fraction
The release body names a few more refusals of the same kind, each with the fix in its message: training.reasoning_effort and train_on_eot on tasks whose formatter never reads them (they now load on sft, tts and distill); data.chat_template with backend: mlx; the known Kimi K2 Hub bases loaded resident on the transformers backend, refused at load with Resident Kimi K2 is unsupported on Transformers 5.x (the two resident kimi-k2-* recipes are marked unavailable, and soup recipes use refuses them with exit 3); training.freeze_layers or freeze_ratio with LoRA on a shape peft cannot restrict by layer, refused when the model is built; an eval-gate suite with no tasks or with a task name used twice; an ambiguous registry name:tag; and an output path that is also an input of soup ingest, soup prune-prompt, soup ra-dit or soup data best-of-n.
The refusals grouped by what they touch, with the start of each message, are on Settings Soup refuses at load.
A LoRA target on a streamed embedding or head
Under training.stream_layers, v0.76 checked only the literal names lm_head and embed_tokens at load, so a regex or dotted spelling loaded, and a regex then failed at its first save, or in peft under task: dpo. Since v0.77.0 the check runs when the model is built: on an untied checkpoint any spelling is refused before a streaming buffer is allocated, and on a tied checkpoint, where v0.76 refused the literal names too, the target now trains and saves (#2082). Because the check moved, soup train --dry-run now passes such a config.
Python 3.11 to 3.13
requires-python is >=3.11,<3.14 (#1805). Python 3.10 reached end of life on 2026-10-01, and pip on 3.10 keeps installing v0.76.x. Python 3.13 is now supported; upstream's CI runs it in the nightly and release runs, not in pull-request runs. The Typer bound is typer>=0.15.4,<0.28.
AWQ and GPTQ export are removed
soup export --format awq and --format gptq, soup quantize --to awq and --to gptq, and the [awq] and [gptq] extras are gone (#1951). AWQ and GPTQ export need v0.76.x and its [awq] or [gptq] extra, for example pip install "soup-cli[awq]<0.77"; upstream notes that both projects are archived and incompatible with [train], and names a separate environment with the upstream tools as the other route. What v0.77.0 prints:
Unsupported format: awq
--format awq was removed in v0.77.0.
Supported: gguf, onnx, tensorrt, bitnet, tq1_0, torchao, gguf-udThat is soup export --model ./out --format awq, and it exits 1. soup quantize ./out --to gptq exits 2 with --to must be one of ['gguf', 'onnx', 'tensorrt']. soup export also lost --bits, --group-size and --calibration-samples, so a script that passes them exits 2 with No such option, and --calibration-data is now read only by --format gguf-ud.
Training on a base that is already quantised with AWQ or GPTQ (training.quantization: awq or gptq) is a different thing, and it is not part of this removal. soup deploy autopilot --target rtx-4090-24gb now plans 4-bit QLoRA with vLLM instead of writing quantization: awq, and its --measure-candidates examples read 4bit,8bit,fp8. See Model export.
The soup serve tool routes
POST /v1/tools/python now answers HTTP 501 without running anything unless the host provides OS-level isolation, as POST /v1/tools/bash already did (#2166). It runs where os.unshare can create the namespaces (Linux, Python 3.12 and newer) or where sandbox-exec is available (macOS), and answers 501 on Windows and on Linux with Python 3.11. Where it runs, the code still goes through the RLVR code sandbox, with its timeout and memory limit, as before; what v0.77.0 adds is the isolation requirement and the token.
Both routes now require --tool-auth-token on every bind, loopback included, and answer 401 without it (#2174). --tool-auth-token also reads SOUP_TOOL_AUTH_TOKEN, which keeps the token off the command line. To keep calling the two routes, start the server with the token and send Authorization: Bearer <token> with each request. The isolation cuts the network; the code still runs as the server's user, so use a long random token, and read upstream's docs/serving-and-export.md before you run the server for callers you do not fully trust.
These routes exist on the transformers backend only; the vLLM, SGLang and MII apps have none. soup serve still starts without a token on loopback, and the generation, adapter, web_search and thumbs routes are unchanged: generation, /v1/models, /health and /metrics still take no token on any backend. soup loop watch gained --tool-auth-token (or SOUP_TOOL_AUTH_TOKEN) for its deploy stage, which a token-protected server used to refuse (#1184). See Inference server.
Behaviour that changes results, memory or exit codes
training.gradient_checkpointingnow reaches the trainer (#1778, #1981, #2140). With the defaultfalse,grpo,ipo,bco,reward_modelandonline_dpotrain with checkpointing off, where TRL's own default used to apply, and onbackend: unslothfalsenow disables Unsloth's checkpointing. Settraining.gradient_checkpointing: trueto keep the memory you had; the shipped recipes, templates and examples for those tasks already set it.distill,classifier,reranker,cross_encoder,asrandunlearnnow honourtruetoo; their default is unchanged.lora.use_rsloraandtraining.freeze_layers/freeze_ratiowith LoRA are applied (they were ignored). rsLoRA runs on MLX and Unsloth train at the rsLoRA scale, and a frozen layer gets no adapter, so such a run trains fewer layers than before, as the option always documented. Their results change.- MLX SFT runs are seeded from
training.seedandtraining.data_seed. A run started before this release cannot be reproduced. - A
dpo,ipoorgrporun resumed from a checkpoint trained a freshly initialised adapter before this release; re-run any such resumed training. prmand RAFTsftruns under gradient accumulation logged and clipped losses that weregradient_accumulation_stepstimes too large; they now match the other trainers.training.unlearn_alpha: 0.0is pure forget loss, as documented. It ran as 1.0.task: simpoon the transformers backend caps the prompt todata.max_length // 2tokens, as DPO and ORPO do.- A
dpooripoterm in apreference_loss_weightsblend now trains under any primary (#1785, #1828). With adpooripoprimary the reference runs twice per step and the eval loss is the blend's value; under asimpoororpoprimary the blend makes the reference itself by switching the LoRA adapter off, with one more forward per step, so thereinit_strategy: pissa,oloraorloftqis refused before the model loads and a run without an adapter (lora.r: 0) still stops at the first step.relora_stepsbeside a blend that namesdpooripois refused at config load (#1891). Abcoblend is still refused at trainer setup. - The bundled eval scorers moved to provenance revision 7, and
mini_mmlugrew to 40 rows, so a baseline written by v0.76 is not compared silently on the new scale: write it again. soup trainwith stdin piped or redirected and no--yesexits 3 before it starts; pipingyno longer answers the prompt. Pass--yesin scripts.- Commands that used to exit 0 when nothing succeeded (#1905):
soup sweepexits 1 when every arm failed (one completed arm still exits 0); a CMA-ES merge in which every evaluation failed exits non-zero and writes no adapter;soup drift-alarmexits 1 when it could read no live or reference row;soup eval autoexits 1 when no leg evaluated anything;soup loop watch --detachchecks its numbers in the parent and starts no child when they are invalid. - More exit-code changes.
soup eval gateexits 3 on an empty, missing or misspelled task list, a repeated task name and a non-finite baseline, and an empty judge prompt file fails the gate with exit 2.soup recipes useon an unavailable recipe exits 3.soup data validatecounts rows the loader skipped as invalid, and still exits 0 while any row is valid unless you pass--min-valid-fraction; a workflow thatsoup ci initwrites on v0.77.0 passes--min-valid-fraction 1.0, and an existing workflow keeps its old step until you regenerate it withsoup ci init --force. The table is on Exit codes. - A streamed
soup trainon Linux, WSL2 included, setsPYTORCH_CUDA_ALLOC_CONF=expandable_segments:Truebefore CUDA starts, unless you exported a value of your own (#2156). - The first layer-streamed run on a Hub base after upgrading re-shards into a new
<slug>@<commit>cache slot, which costs disk space and time; the old slot is left in place. - The layer-streaming disk pre-flight sizes the cache from the checkpoint's headers (#2039). A dense base under
quantization: 4bitis charged more, because embeddings, head and norms stay at the stream dtype (Llama-3.1-8B 4.14 GB to 5.70 GB), so a run that passed by less than that margin is now refused before sharding; a fused MoE base is charged what it really writes. - Audit-log lines written by older versions get the same masking as new lines, applied once when the log is next written (#2065).
What is new
DPO, ORPO, SimPO and KTO on Apple Silicon
Through v0.76.1 backend: mlx ran supervised fine-tuning and refused every other task at config load. v0.77.0 trains task: dpo, orpo, simpo and kto there through Soup's own MLX loop, on TRL's objectives: DPO's sigmoid loss with dpo_beta, ORPO and SimPO with no reference model, and KTO with kto_beta against a reference precomputed once from the base (#1846, #2073, #2097, #2111). task: preference stays refused on MLX (tracked as #2202), and so does grpo. Upstream qualified ORPO, SimPO and KTO on Qwen2.5-0.5B-Instruct in bf16 and 4-bit on an M4 Pro, and says for SimPO and KTO that this shows the objective moving, not alignment quality.
MLX text SFT gains native DoRA (lora.use_dora: true, #1920), training.eval_steps counted in optimizer updates (#2074), the loss watchdog (#2115) and seeded runs (#1855): training.seed and data_seed are applied, so two runs with the same seed reproduce each other's losses. A split smaller than one batch no longer crashes the run (#1857). lora.use_rslora is now applied on MLX and Unsloth, and lora.rank_pattern / alpha_pattern and data.chat_template are refused at load on MLX. See Apple Silicon MLX backend.
Layer streaming reaches DeepSeek-V3-family MoE models
Layer streaming is Soup's implementation of layer-to-layer streaming of a frozen base (L2L, Pudipeddi et al. 2020). It now admits model_type: deepseek_v3, gated on resident-versus-streamed parity forward and backward on a tiny from-config model on CPU (#1914); no real DeepSeek-V3 checkpoint was compared against a resident run, and there is no NF4 parity check for it. That takes the allowlist to eleven architectures with the same five streaming tasks; no parity was measured for the preference losses on DeepSeek-V3. DeepSeek-V3's FP8 block-quantised checkpoints are dequantised at shard time (#2061). Kimi-K2-Base and Kimi-K2-Instruct configs are admitted behind a field-by-field check, verified on CPU with a tiny K2-shaped checkpoint only; nothing has been trained on K2 itself (#2107). Still opt-in (stream_layers: true) and still BETA. New streaming keys:
training.stream_expert_layout: per_expertstreams one routed expert at a time, DeepSeek-V3 family only (#2029). On one RTX 5070 Laptop 8 GB, the same Moonlight-16B LoRA config peaked at 1.0 GB of GPU memory against 3.1 GB fused (NF4 experts against bf16, different cache tiers), at about 2.7x the step time; that is one run each, and the record's controlled pair reads 1.9 GB against 3.9 GB at about 2x (record).training.stream_recompute: off | autokeeps streamed activations instead of recomputing them in the backward, with bit-identical gradients, ontask: sftonly (#2152). On the same card's RAM tier a Mistral-7B NF4 step took 22% / 17% / 8.5% off the step time at 512 / 1024 / 2048 tokens, with no gain on a read-bound disk tier and slower at a high VRAM fill (record). The defaultonkeeps the shipped behaviour.training.stream_loss_chunkruns the output head and the loss in token chunks,task: sftonly (#1880). It matches the unchunked step within 1e-5 of the largest value, not bit for bit, and it has not been measured on a GPU.training.stream_nf4_scale_cachekeeps the expanded NF4 block scales on the device, bit-identical and off by default (#1892). It costs 4 bytes of VRAM per 64-element block.
See Streaming a Mixture-of-Experts base, Layer streaming and Scaling a streaming run.
Provenance you can check
soup registry push --data <file> records the file's SHA-256 as data_hash: the file registered as the training input, not proof of what the run trained on. --idempotency-key makes a push safe to retry, and an ambiguous name:tag is refused (#1907, #1934, #1964). soup train records the SHA-256 of a local training file at startup (#2024). On the transformers backend, resident, layer-streamed and multi-GPU runs record the Hub commit of the base they loaded (#1972, #2094, #2127); Unsloth and MLX loads and local or cached-path hints are not bound (#1902). soup ship --require-provenance checks evidence against the adapter, task and source bytes, suite version and verdict policy it was stamped with (#2025); its help says it requires --evidence, --config, --adapter and task eval, and calls --data-sha256 "caller-asserted". In upstream's words, this "is not a model run, attestation, signature or forgery protection"; the unkeyed hashes detect drift. See Model registry and soup ship.
GRPO can score with an LLM judge
training.reward_fn: judge_rank sends all of a prompt's completions to one judge call and scores each against the others (#2117), on task: grpo only and not with modality: vision. The judge is set with training.grpo_judge, checked at load, and the run must state training.grpo_judge_max_calls: a run that could exceed it is refused after the data loads and before the model does. Prompts and completions are sent to the judge host you configure, and a request the judge answers with 429 or a 5xx, or that times out, is retried up to twice inside each counted call, so the host can see up to three requests per counted call. No quality result is claimed. See RLVR.
Data, speech, recipes and the CLI
pip install "soup-cli[documents]"provides the PDF and DOCX readers forsoup data ingest; Markdown and TXT need no extra (#2096).soup merge --low-memorymerges a plain LoRA adapter into a safetensors base one tensor at a time, with values identical to the regular merge in upstream's words; it needs the fp16 save format and refuses adapters it cannot reproduce exactly (#2006).- User-supplied files with a UTF-8 BOM load: JSON, CSV and TXT datasets (#1954), and the agent, tokenizer, bench, draft, edit, shrink, diagnose, can, generate and autopilot readers (#2155). The six data quality commands read alpaca, preference, KTO and ShareGPT rows (#2037).
- Sesame CSM fine-tunes from raw transcript and audio pairs through a native trainer (#1769), with
data.format: audioandquantization: none. Raw-audio Spark and Oute stay refused. training.fast_loraswitches the Fast-LoRA kernels on for SFT on the transformers backend, text only, withquantizationnoneor4bitandlora.dropout: 0(#1973). It is verified on CPU only, and no speedup is claimed.- Two recipes,
qwen3.5-4b-dpoandqwen3.5-2b-dpo(#2129, #2160), with no result claimed;pixtral-dpois retired, andkimi-k2-sftandkimi-k2-thinking-grpoare marked unavailable. - A RunPod controller behind
soup train --cloud runpod(#2081). It is plan-only by default: it writes a stub and contacts nothing.--cloud-submitneedsRUNPOD_API_KEYandRUNPOD_SSH_PRIVATE_KEYand otherwise exits 1. It has not been run against a real RunPod account yet (tracked as #2098). soup --version(#2105).- Two reference pages generated from the code,
docs/config-reference.mdanddocs/cli-reference.md, which upstream's test suite keeps equal to what the code does (#1799).
Counts at the tag: 171 recipes, 1070 test files, 36 pip extras (documents is new; awq and gptq are gone), 31 staged fields, each refused when set away from its default, and 11 streaming architectures with the same five streaming tasks. The 78 top-level commands and 29 command groups are unchanged.
Security
No security advisory is published with this release. In upstream's words it "tightens six behaviours": two on the soup serve tool routes (above), one in the RunPod controller, and three that the changelog lists under Security. Upgrading is enough for the last four; the tool routes need the token.
POST /v1/tools/pythonruns only under OS-level isolation (#2166), and both code routes need--tool-auth-tokenon every bind (#2174): see The soup serve tool routes.- The RunPod controller runs
sshandscpby the absolute path found onPATH, never from the current directory, withoutRUNPOD_API_KEYin their environment, and fetches results into a new directory, moving only the expected output directory into place (#2081). - The
documentsextra requirespypdf>=6.21.2. - Replies to Soup's judge client, and to
soup data generate's providers and Magpie, are size-bounded (#1929, which closes #1819). A judge reply over 1 MiB, and an HTTP generation or Magpie reply over 8 MiB, is refused while it arrives, gzip expansion included, before it is parsed, and the response body is not logged. Magpie drops the affected sample and continues within its attempt limit. The judge cap coverssoup eval judge, the eval-gatejudge_model,soup ship's judge, online DPO, GRPOjudge_rank,soup data best-of-n,soup data evolveandsoup expect; the generation cap coverssoup data generateand Magpie. - Printed text drops bidi controls and invisible separators (#1953). Soup's terminal sanitiser now also removes U+202A to U+202E, U+2066 to U+2069, U+061C, U+200E, U+200F, U+200B, U+2028, U+2029 and U+FEFF from dataset, adapter and evidence text before it is printed, so a crafted row cannot reorder or hide what a preview shows. ZWJ and ZWNJ are kept for Arabic, Persian, Indic and emoji text.
Also in this release, outside upstream's six: soup ui checks the Host header on a loopback bind (#1839), and soup ui --public prints and opens a loopback URL instead of 0.0.0.0, which Windows cannot reach (#2110); --public keeps the bearer gate. Audit-log lines written by older versions get the same masking as new lines, applied once when the log is next written (#2065). training.grpo_judge is checked at load: plain http only to loopback, and no private, link-local or reserved IP literal (training.online_dpo_judge refuses such an IP literal at load too, and checks plain http later, at trainer setup).
Supported versions. 0.77.x is supported. 0.76.x, the last line that runs on Python 3.10 and exports AWQ and GPTQ, receives security fixes only, until 0.78.0 is released. Anything below 0.76 is not supported. See SECURITY.md at v0.77.0.
What was measured, and what was not
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. This release's DeepSeek-V3-family, FP8-source, per-expert, chunked-loss and no-recompute streaming are outside what it measured; upstream says the next preprint version covers them and cites L2L. See The paper.
Twelve records were added under benchmarks/ at this tag, the failed ones included. Both measured streaming rows on this page (per_expert and stream_recompute) come from one RTX 5070 Laptop 8 GB. Two of the records are worth naming here: gate-kimi-k2-admission.md is CPU only, with no K2 weights, and one of its checks fails as registered; and probe-rtx5070-moonlight-fp8-source.md, the GPU loss check of an FP8-block Moonlight source against its bf16 copy, failed two of its limits and is published as written. The FP8 bit-exactness that does hold is a CPU gate on a tiny DeepSeek-V3: streamed from its FP8 copy, it matches a resident model holding the same dequantised weights bit for bit. The records and their scopes are listed on Measurement records.
No benchmarks/ record was added for MLX, soup merge --low-memory or fast_lora.
Known limitations
These are upstream's, in plain terms.
training.rl_checkpoint_include_ref_modelandrl_checkpoint_include_rollout_bufferwent from accepted to refused with no warning release in between; they were staged after v0.76.0.- AWQ and GPTQ export are gone. They need v0.76.x and its
[awq]or[gptq]extra, for examplepip install "soup-cli[awq]<0.77"; upstream notes that both projects are archived and incompatible with[train]. task: simpoon the transformers backend cuts a prompt longer thandata.max_length // 2tokens even when the whole row would fit, until #1196 lands (#1185).training.fast_lorais verified on CPU only (float32 and float64 against peft). The bf16 and fp16, NF4 and streamed-CUDA checks have not been run, and no speedup is claimed (#792).training.stream_loss_chunkhas not been measured on a GPU; the peak-VRAM table is still open (#1693).training.stream_recomputeis SFT only, andautoworks on the RAM tier only. On a read-bound disk tier it gains nothing, and at a high VRAM fill it is slower (measured on one RTX 5070 Laptop).- Kimi K2 admission is verified on CPU with a tiny K2-shaped checkpoint only; nothing has been trained on K2. Its tokenizer needs
--trust-remote-codeandpip install tiktoken. On a full-size K2, setup briefly allocates about 22.5 GB of CPU commit per MoE layer before moving it to meta (#2154). Resident K2 loads are refused at load, and Kimi-K2-Thinking and Kimi-K2-Instruct-0905 have no supported route yet (#2104). stream_expert_layout: per_expertcovers the DeepSeek-V3 family only and costs step time, about 2x to 2.7x on Moonlight-16B (see the record). A cache volume that is not NVMe is warned about (#2170).- After upgrading, the first pinned layer-streamed run re-shards into a new
<slug>@<commit>cache slot; the old slot stays on disk and nothing names or removes it (#2196). - The RunPod controller has not been run against a real RunPod account yet (#2098). It makes one attempt, copies the results once with a 15-minute limit into a new directory next to the output path, and deletes the Pod afterwards; a failed or timed-out copy removes that directory when it is empty and names it in the error otherwise. Local input files are not uploaded, so the base and the data must be reachable from the Pod.
soup export --format ggufstill clones llama.cppb5270, which cannot convert Qwen3.5. Since this release it refuses before cloning and names--llama-cpp <dir>; moving the pin is open (#1911).- Base-revision binding covers the transformers backend. Unsloth and MLX loads, and local or cached-path hints, are not bound yet (#1902).
lora.rank_patternandalpha_patternare refused on MLX and Unsloth until those trainers apply them (MLX: #1996).freeze_layersandfreeze_ratiowith LoRA need a list oflora.target_modules. A regex,all-linear,lora.target_parametersand VeRA are refused when the model is built (#2201).soup merge --low-memoryrefuses adapters it cannot reproduce exactly (DoRA,modules_to_save, rank or alpha patterns, embedding adapters) and names the regular merge instead./v1/tools/pythonis unavailable (501) on Windows and on Linux with Python 3.11, as/v1/tools/bashalready was. Where it runs, a failure inside the submitted code is reported astimed_out: truewith an emptystderr, as in v0.76 (#2194).- On MLX,
task: preferencestays refused (#2202). - Still open from v0.76.0: fields that reach no consumer outside the staged table (#1814),
soup doctor --configcoverage (#1815), config-error wording (#1816) and vague messages for rows of an unknown shape (#1818). - Layer streaming remains BETA and opt-in, and
--cuda-graphsremains experimental.
One more, read from the v0.77.0 binary rather than from upstream's list: soup plan and soup apply --dry-run accept a config that soup train refuses, both with exit 0 (see Before you upgrade).
Install and upgrade
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.0. Run soup doctor --config before the first run of each existing config.
- Python 3.10: pip and pipx stay on v0.76.x there; to pin it,
pip install "soup-cli[train]<0.77"(v0.76.x supports Python 3.10 to 3.12). If pipx built Soup's environment with Python 3.10, reinstall it on a newer interpreter, for examplepipx install --force --python python3.12 "soup-cli[train]". - AWQ or GPTQ export: AWQ and GPTQ export need v0.76.x and its
[awq]or[gptq]extra, for examplepip install "soup-cli[awq]<0.77"; upstream notes that both projects are archived and incompatible with[train], and names a separate environment with the upstream tools as the other route. v0.76.x receives security fixes only, until 0.78.0 is released. - PDF and DOCX ingest:
pip install "soup-cli[documents]". Markdown and TXT need no extra. soup servetool routes: start the server with--tool-auth-token <token>(or setSOUP_TOOL_AUTH_TOKEN) and sendAuthorization: Bearer <token>to/v1/tools/pythonand/v1/tools/bash.
Docker
The release body publishes an image: ghcr.io/makazhanalpamys/soup:0.77.0, also tagged latest. It is Ubuntu 24.04 with Python 3.12 on a CUDA 12.8 runtime, installs exactly this release with the train, serve, data and eval extras, and starts soup with /workspace as its working directory. The host needs a driver that supports CUDA 12.8 (570 or newer on GeForce) (#2092).
docker pull ghcr.io/makazhanalpamys/soup:0.77.0
docker run --gpus all -v "$(pwd)":/workspace ghcr.io/makazhanalpamys/soup:0.77.0 train --config soup.yaml --yesKeep the --yes. docker run without -it gives soup train no terminal on stdin, and since v0.77.0 soup train exits 3 in that case unless --yes is passed, so the same command without it stops before training.
Credits
151 of the 226 pull requests in this release came from 44 people outside the maintainer, in the release body's count. In the release body's order, by merged pull requests:
| Who | PRs |
|---|---|
| Nurkhan Esenbek (@kok-o) | 34 |
| Nariman Abdikarim (@Nari-Ab) | 14 |
| Yang Bai (@ybai08) | 12 |
| @dryn85-max | 8 |
| @ndablizov | 8 |
| AmixDigital (@Amix29) | 6 |
| 苏俊杰 (@Momoyeyu) | 5 |
| Abdulwaarith Zakariyya (@abdulwaarith0) | 5 |
| Samarth Mukhija (@Samearth17) | 5 |
| @vaputa | 4 |
| Srinivasan R (@Srinivasan8888) | 4 |
| Pernav Jain (@here-2007) | 3 |
| Tal (@taljeon) | 3 |
With two pull requests each: Dipak Chaudhari (@dchaudhari7177), Younes Beriane (@drakeo338), Faisal Fayaz (@Faisal-Fayaz), Areen Agrawal (@Areen-09), Aditya Pratap Singh (@akkupratap323), Vincent Le (@VincentTLe), Shivansh Shukla (@ShivanshShukla), Abhishek Jha (@SpinachDigital) and Rahul Seervi (@Rahul-cs50).
With one each: Sohaib Kamash (@Nick-800), Batyrkhan (@Batyrkhantzy111), Matt Van Horn (@mvanhorn), Tanmay Garg (@Tanmay9223), Vladimir Babin (@chiliec), Nehal K (@sloemo01), Aykut Güney (@KeuqS), Emre K. (@kocaemre), Abhinay Kumar (@abhinyaay), Alexis (@bernalalexis-try), @DBVamsi, @dipesh-singh07, George Tonmoy Roy (@george07-t), Aaditya Desai (@aadityad12), Kuanysh (@kuanysh228), Mohammad Hijjawi (@MohammadHijjawi97), martin (@Martini147), @Shutaru, Vishal Kumar Singh (@Vishalkumarsingh6428), Aaron Probha (@AaronProbha18), Vansh Sharma (@VanshSharmaPES) and Daivik Awasthi (@daivikawasthi01).
Reports that led to fixes in this release, as the release body thanks them: @feloau, @bobbonauta, @maximilianfeix, @Ercaner1988, @VincentTLe, @abdulwaarith0, @taljeon, @yunaremaia, @Amix29, @akkupratap323, @abdulbasit-ogunrinu and @Yi-111-a.
What each person found or built, with every issue and pull request, is in the release notes and the changelog at the tag.
See also
- v0.76.1: the bundled DPO examples train again and v0.76.0: settings that never took effect now stop the load: the releases before this one, and the warnings this one enforces.
- Settings Soup refuses at load: the refusals grouped, with the start of each message.
- Exit codes: the full table.
- Apple Silicon MLX backend, Streaming a Mixture-of-Experts base, Layer streaming, Inference server and Model export.
- What's new across v0.71 to v0.77.
- The upstream changelog at the tag.
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.