v0.76.0: settings that never took effect now stop the load
v0.76.0 was tagged on 2026-10-12. Upstream gave it no codename and titled it with a sentence again, and the sentence is the finding: settings that Soup validated, documented and accepted, and then read nowhere on the path they claimed to configure, now stop the load. A soup.yaml could ask for one thing and quietly train another. This release refuses the settings that never took effect, warns about the ones that are still tolerated and prints the release in which upstream says they will be refused, evaluates the validation split that Soup always held out and, on the transformers backend, never used, and applies the grpo_beta KL penalty to every GRPO variant. Three of the refused settings were not simply ignored: training.use_longlora and training.ebft_variant ran and trained wrongly, and training.quantization_aware: true ran a post-training int8 quantization on some trainers and was not applied on the others. A run that set one of them is in the Re-run this table.
490 pull requests were merged between 2026-09-12, the day v0.75.0 shipped, and this tag, and 418 of them came from 80 people outside the maintainer. Counting only the 483 merged after the v0.75.0 release pull request, it is 412 of 483 from the same 80. It carries the fixes of v0.75.1 and v0.75.2, which were backports cut off v0.75.0.
Three things in it cost you something if you skip them:
- A config that loaded on v0.75 can stop loading. Run
soup doctor --config soup.yamlbefore you train. The refusals are grouped by what they touch, with the start of each message, on Settings Soup refuses or warns about at load. - The exit codes of eleven gate and verdict commands changed. A CI step that tests for the old number can now pass a failing gate without saying so. See Exit codes of the gate commands.
- Dozens of fixes mean a run you already finished may have been wrong, most of them without an error and with a normal-looking loss curve. They are in the Re-run this table.
Before you train
pip install -U "soup-cli[train]"
soup doctor --config soup.yamlsoup doctor --config loads your config with the v0.76.0 rules. A config that is refused prints the error and then Config could not be loaded (see above)., and the command exits 2. A config that only triggers warnings prints a Warning: line for each one and exits 0. soup train exits 1 on the same refused config, before it imports the training stack.
Config validation error:
training -> optimizer: Value error, optimizer='adam_mini' is no longer supported: transformers 5.x rejects it in TrainingArguments after the model has loaded, so Soup refuses it at config load instead. Use one of: adamw_torch, adamw_torch_fused, adamw_bnb_8bit, adafactor. See soup_cli.utils.optimizer_zoo.SUPPORTED_OPTIMIZERS for the complete list.
Config could not be loaded (see above).Three rules for what you do with the answer. A config that has to load on both v0.75 and v0.76 needs the refused key removed, not renamed. For most of them v0.75 ignored the key; for a few (quantization_aware: true, use_longlora, ebft_variant) it applied them incorrectly, so keeping them preserves nothing worth keeping. For those few, a run that finished on v0.75 did not do what its config said: see the Re-run this table. Second, soup doctor --config stops at the first cross-field refusal. Unknown keys are reported together, but a model-level refusal prints the first one it hits, so fix that setting and run it again until it prints no error. And third, an empty result is not an all-clear. The part of soup doctor --config that names settings your backend does not read is small: it has four declared entries (SFT on MLX with 18 settings, SFT on Unsloth with three, and SFT on the transformers backend with a vision or an audio modality with two each). Outside those it reports nothing, and its line None of the N setting(s) known to be unread ... is switched on in this config. says the same thing whether or not a setting is unread. Upstream tracks that as #1815. The refusals themselves are not part of that table: those that happen at config load come from loading the config, so soup doctor --config reports them on every task. A few refusals happen later, at trainer setup (for example task: ppo with the reward-hack flags), and soup doctor --config cannot see those.
Breaking changes
A setting that never took effect, or took effect wrongly, is refused at load
Each message names the field and what to change. Some of these settings were ignored, a few ran and ran wrongly (the Re-run table has those), and some stopped the run after the model had loaded, such as an optimizer name that transformers 5.x rejects in TrainingArguments; all of them now stop at load instead. The main ones, with the pull request that refused them:
training.quantization_aware: true(#1222). It never ran quantization-aware training.quantization_aware: fp8andquantization_aware: questare different code paths and are unaffected.training.use_longlora(#1240). Upstream's message says the implementation leaks future tokens and is not S² shifted sparse attention. To extend the context, settraining.rope_scaling_typeand train plain LoRA.training.ebft_variant(andebft_temperatureon its own) (#1230), and every value oftraining.gdpo_variant(#1309).- Eight optimizer names (
adam_mini,adamw_apex_fused,ao_adamw_4bit,ao_adamw_8bit,ao_adamw_fp8,badam,came_pytorch,dion), andmuonandadamw_hfon every backend except MLX (#1269, #1283). The allowlist is 24 names, down from 32. training.rope_scaling_typewhere no setup reads it (#1697), andlongropeeverywhere (#1239). Only the transformers text setups ofsft,pretrainandttsrescale RoPE.training.freeze_layersandfreeze_ratiooutside the textsftandttspath on the transformers backend (#1497, #1581),training.auto_mixed_precisionoutsidesftandtts(#1618), anduse_ligeranduse_flash_attnoutsidesftandtts(#806).backend: unslothon the tasks that have no unsloth setup (#1357).training.kernel_auto_compose(#801), BitNet training (#825, #981), Sesame CSM and raw-audio Spark and Oute TTS (#265, #1112, #1113).relora_stepsunlessquantization: noneis stated explicitly (#693),freeze_trainable_layerswithoutexpand_layersor with a value that differs from it (#1396, #1409),expand_layerson a quantized base (#1237, #1308) or outside the textsftandpretrainpath (#1325, #1390), andloraplus_lr_ratiowhere no LoRA B matrix trains (#745, #1210, #1324).task: ktoatbatch_size: 1, for every KTO run (#1420), andtask: embeddingwith the default contrastive loss atbatch_size: 1(#1234).data.mask_history: truewithtrain_on_responses_only: false(#761),data.add_new_tokensanddata.new_special_tokenswhere no trainer adds them (#1358), anddata.chat_templateon tasks that do not use a chat template (#804).
Other refusals follow the same rule, and the changelog lists every one. The list, grouped by what each setting touches, with the trigger and the start of the message for each, is on Settings Soup refuses or warns about at load. Unknown keys have refused the load since v0.75.0 and that has not changed.
Settings that are unread but still tolerated for one more release (lr_groups, early_stop_patience, forgetting_eval_steps and 26 other staged fields) warn at load now, and upstream says v0.77 will refuse them. See Deprecations and the v0.77 deadline.
Validation loss is evaluated, and three tasks stop holding rows out
data.val_split (default 0.1) held rows out of training on every run, and no trainer on the transformers backend ever evaluated them (#1223, #1313). Every task that gets a split now evaluates it at the end of each epoch, or every N optimizer steps with the new training.eval_steps, and records val_loss. Runs take a few percent longer. On backend: mlx nothing changes: mlx-lm evaluates the split on its own cadence, as before. training.eval_steps is refused where it cannot work: on ppo, online_dpo and unlearn, on backend: mlx (mlx-lm evaluates by itself), and when there is no validation split to evaluate.
grpo, ppo and online_dpo evaluate by generating, so by default they neither evaluate nor withhold rows. data.val_split is ignored for them with a note, and every row trains. That changes the rows a default config trains on. On grpo, training.eval_steps turns evaluation on and records val_reward, never val_loss. See Training.
Every GRPO variant applies grpo_beta
gspo, dapo, dr_grpo, bnpo, two_sided and rft validated grpo_beta and dropped it (#1232, #1249). Each now adds the KL penalty, so an existing variant config will not reproduce a prior run. grpo_beta: 0 is valid and runs KL-free, skipping the reference forward pass (#1247, #1295); it is refused together with reward_hack_mitigation: kl_control or pid_lagrangian. See GRPO Plus.
Exit codes of the gate commands
Eleven commands now share one scheme (#813, #921): 0 pass, 2 gate failure, regression or drift, 3 usage or input error, with 1 left for something that broke. The eleven are soup ship, soup expect, soup data validate, soup data lint, soup eval gate, soup eval quant-check, soup eval against, soup eval behavior, soup eval checklist, soup lock check and soup recipes verify. A mistyped flag on one of them is 3. It is not "every gate and verdict command": soup diagnose, soup data doctor, soup eval unlearning, soup lock write, soup ab, soup adapters ... and the probes keep Click's 2 for a mistyped flag.
| Command | Condition | v0.75.2 | v0.76.0 |
|---|---|---|---|
soup eval gate | a task failed or regressed | 1 | 2 |
soup eval against | regression | 1 | 2 |
soup lock check | drift | 3 | 2 |
soup expect | an expectation failed | 3 | 2 |
soup expect | suite or data rejected | 2 | 3 |
soup data validate | missing file, unknown or undetectable format (an unsupported file extension is still 1) | 1 | 3 |
soup eval quant-check | a task is MAJOR | 0 | 2 |
soup eval quant-check | a model fails to load | 0 (a stub scored it) | 1, unless --allow-stub |
soup eval behavior, soup eval checklist | run with no evidence | 0 | 3 |
soup diagnose | a requested probe did not run | scored OK | 3 (NOT_RUN); MAJOR is still 2 |
| the commands in the scheme | unknown flag or malformed number | 2 | 3 |
soup plugins install | any name | 0 | 2 |
The step that now passes silently is any test for the old number. A CI script that treats soup eval gate exit 1 as "the gate failed", soup lock check exit 3 as "drift", or soup expect exit 3 as "an expectation failed" no longer matches a failing gate: it gets 2. A step that fails on any non-zero exit keeps working but cannot tell the causes apart. The workflow that soup ci init writes and the pre-push hook from soup eval gate-install were updated to match; an older committed workflow still fails the job on any non-zero. Every row, with the commands whose codes did not move, is on Exit codes.
Other behaviour changes
training.dpo_beta_scheduleandtraining.dpo_ref_regen_epochsnow run ondpo,ipoandpreference. Through v0.75.2 a run that set either key stopped when training started, because the callback was not a Hugging FaceTrainerCallback; upstream records thedpo_beta_schedulecrash as anAttributeErrorat step 0 (#1229, #1294).training.dpo_ref_regen_epochsis wired under LoRA (#1345, #1636): it copies the active adapter into the frozenrefadapter every Nth epoch, and it is refused withlora.r: 0.training.loraplus_lr_ratioapplies on every LoRA task instead of onlysft,pretrainandembedding, so those runs produce different weights (#745, #1080). It is refused onbackend: mlx(#1324).- An FP8 request that cannot run, or whose conversion fails, stops the run instead of training in bf16 (#835, #1154; #1152, #1333). An NVFP4 request that cannot be applied still prints a yellow advisory and trains in bf16.
soup export --format torchaoandtraining.nvfp4resolve their torchao classes from the module that defines them, and the[qat]extra now needstorchao>=0.18.0(#826, #1066). See Export.soup data preprocessstops on a chat row it cannot tokenize instead of dropping it (#1180, #1182), and its cache key now includes the chat template and the other inputs that change the rows (#938, #1082, #1253), so training from a cache written by an older Soup is refused withcache hash mismatch: re-runsoup data preprocessto rebuild it.- ReLoRA restarts follow the paper contract and need an explicit
quantization: none(#693, #1133). See LoRA quality. data.mask_historynow keeps only the last assistant turn in the loss and requiresdata.train_on_responses_only: true(#761, #1101).soup data recipe --executerefuses silent placeholder generation (#817, #922). A recipe with anllm_textorjudgenode needs--provideror the explicit--offline, and exits 2 without either. See Plugins.soup benchis a group.soup bench inferis the old inference benchmark, andsoup bench <model>still runs it. See Benchmarks.soup serve --auto-quantexits 2 instead of choosing a format (#816, #1035).gradient_checkpointing: falseis honoured on 4-bit, 8-bit and mxfp4 runs, and the SFT tiersmediumandselectivenow make different trade-offs (#797, #1045; #796, #1036). Peak memory can rise on a run that used to inherit full-block checkpointing.--devicenow means something onsoup infer,chat,diff,benchandsoup serve(#1443, #1478). An unknown value is refused. On a CPU-only host the model loads in float32 pinned to the CPU, which upstream says doubles its RAM use (a 7B model needs about 28 GB instead of 14 GB).soup data score --benchmarkswithout--benchmark-fileexits 1, and the decontaminated cell readsnot runinstead of0(#1448, #1518). See Data Forge.- Relative image and audio paths resolve against
data.image_dirordata.audio_dir, or the data file's own directory, on every load path (#1599; #1598, #1604 forsoup data inspect). See Data formats. --jsonoutput is written straight to stdout forsoup audit-log tail,soup bom emit,soup profile,soup version --json --full,soup costandsoup eval against --json-only(#1468, #1470, #1480, #1485): it is no longer folded at 80 columns or passed through Rich markup.
Install pins
- Python is still
>=3.10,<3.13, andtorch>=2.6.0,transformers>=5.16.1,<6.0.0,trl>=0.29.0,<1.0.0andpeft>=0.20.0,<1.0.0are unchanged.[train]now declaresrequests>=2.32.2,[audio]addstorchaudio>=2.6.0,<3.0.0, and there is a new[sigstore]extra (sigstore>=4.4.0,<5.0.0, not part of[all]). clickis now declared and bounded:click>=8.0.0,<8.5. click 8.5.0 drops the description of every positional argument from everysoup <command> --helppage, so a fresh install that resolved the newest click showedADAPTER TEXT [required]with nothing after it (#1790). If another package in your environment needs click 8.5 or newer, install Soup in its own environment;pipxdoes that.typer>=0.9.0,<0.21.0is declared, but only typer 0.15.4 and newer start on every click the metadata admits (#1802).pip install -U soup-clidoes not upgrade a dependency that already satisfies its range, so an environment that already holds an older typer fails on every command,soup --helpincluded, withType not yet supported: int | None. Fix it withpip install -U "typer>=0.15.4,<0.21".
Re-run this
Every row is something upstream's changelog says ran wrong in a release before v0.76.0. Most of them did not raise, and most exited 0. Text in quotation marks in the first column is upstream's own wording, the rest is a close paraphrase of the same entry, and this page adds no explanation of why. The pull request numbers are upstream's, issue first where the changelog gives both. The "Who is affected" column says which runs each row concerns. For every row the remedy is to re-train, re-judge or regenerate the artifact: nothing here can be repaired in place unless the row says so.
| What was wrong | Who is affected | PR |
|---|---|---|
"In TRL 0.29.1, dataset packing drops the unpreserved labels column and falls back to training all input tokens except the first." Prompt and system tokens were trained on. | SFT with training.packing: true and assistant-only masking on, which is the default | #1236, #1304 |
The text path re-tokenised the rendered template with special tokens on, so a template that renders {{ bos_token }} (Llama-3, Gemma, Mistral) on a tokenizer whose post-processor also prepends BOS trained on [bos, bos, ...] and adapters were "off by one token from how they are prompted". The text path now takes the chat template as the only source of special tokens: one BOS for such a template, zero for one that renders none (TinyLlama, Zephyr, Soup's data.chat_template presets). | SFT with data.train_on_responses_only: false | #788 |
soup data preprocess cached chat rows "without the EOS the live path trains on, so a cached run could train with no stop token", and cached rows with no labels, so a pre_tokenized run "trained on the prompt". | Runs trained from a soup data preprocess cache: upstream says they "may have trained without a stop token on templates that render none" and to re-run soup data preprocess and retrain. Upstream also records (#1038) that through v0.75.2 the training gate refused a cache whose metadata.json was present, so such a run started only from a cache without that file | #938, #1054, #1120 |
| The distillation trainer returned a mean over its own microbatch, so "the same effective batch split four ways previously produced four times the gradient and changed the effective learning rate". | task: distill with gradient_accumulation_steps above 1; soup draft distill and soup shrink --heal run it | #720, #742, #932, #943 |
warmup_ratio was turned into a fixed step count from raw dataset rows before the trainer existed, "causing warmup to shrink or cover the entire run so peak learning rate was never reached". Transformers now resolves it over the real optimizer steps. | GRPO; SFT with packing or multipack; pretrain with packing | #1431, #1507 |
GRPO accuracy and verifiable math rewards compared an Alpaca output or a reference turn such as 6*7=42\n#### 42 raw, "so every correct completion scored 0.0, every group had zero reward variance and GRPO got no signal, with no warning". Separately, a correct completion written with \text{}, a degree mark, \frac12 or x = 7 "scored 0.0 against a gold in the other" spelling. | GRPO with reward_fn: accuracy or verifiable math rewards | #1226, #1316, #1332, #1346, #1371 |
Every non-standard grpo_variant: "before, they validated grpo_beta and dropped it". Their losses, gradients and trajectories change now that they apply it. | GRPO with gspo, dapo, dr_grpo, bnpo, two_sided or rft | #1232, #1249 |
| For a pair the judge could not rank, "every such pair trained as a win for the second completion, and the loss was averaged over all pairs. With the judge server down, a whole run learned arbitrary labels and finished normally." | Online DPO against a judge that answers a tie, a failed or unreadable call, or a verdict that changes when the two completions are swapped | #1225, #1282 |
loraplus_lr_ratio was "silently ignored on every LoRA task except sft, pretrain and embedding", and those runs now "produce different weights". | dpo, kto, orpo, simpo, ipo, bco, grpo, ppo, online_dpo, reward_model, distill, classifier and asr with loraplus_lr_ratio set | #745, #1080 |
{dpo: 0.7, simpo: 0.3} "printed Multi-objective preference loss: 0.70·dpo + 0.30·simpo at setup and then trained DPO alone scaled by 0.7, with nothing in the output saying so". Since #1513 a blend stops at the first step; since #1556 a blend naming only simpo and orpo runs, and one naming dpo or ipo still stops. To train one loss, remove the weights and set training.preference_loss. | Any task with training.preference_loss_weights | #1425, #1513, #1556 |
Upstream: PRM, MoLE, PPO and unlearning now "honor configured optimizer, schedule, warmup, weight decay, clipping and effective batch settings". Unlearn used a fixed 256-token limit and now tokenizes to data.max_length (default 2048); on unlearn, batch_size: auto now resolves to 1 rather than to a memory estimate. | Runs of those four tasks that set any of those options | #805, #1564 |
PiSSA, OLoRA, LoftQ and VeRA (lora.init_strategy, lora.use_vera) were not wired through the PEFT constructor every transformers trainer uses. Unsupported combinations now fail at config load "instead of silently training a different adapter". | Runs of any LoRA task other than sft and pretrain that set one of them | #794, #1037 |
Upstream built every LoRA config through one shared helper "so rank_pattern and alpha_pattern work consistently outside SFT and pretraining". | LoRA runs other than SFT and pretrain with either pattern | #803, #923 |
data.chat_template was applied by some trainers and not others. It is now applied "consistently to conversational preference, reward-model, GRPO, and Online DPO trainers", and refused on non-chat tasks. | DPO-family, reward-model, GRPO and Online DPO runs that set it | #804, #919 |
soup migrate wrote no quantization line for an unquantized source, "causing Soup to default them to 4-bit QLoRA upon loading, while full fine-tunes defaulted to LoRA r=64". LLaMA-Factory pref_loss: ipo became plain dpo, and unmappable values loaded as dpo with no warning. | Configs generated by soup migrate | #1215, #1302, #1214, #1560 |
"Only sft.py, pretrain.py and stream_setup.py ever read the flag; the 8 DPO and 7 GRPO recipes that set it got attention-only LoRA." On a fused-expert MoE, "by module names alone Mixtral and MiniMax adapted attention only, Qwen2-MoE attention plus its shared expert, Qwen3.5-MoE the shared expert only". | MoE runs with moe_lora: true | #798, #1074, #1421, #1590 |
"A gate trained on CUDA before this fix kept most of its initial weights, so retrain it." (82.6 to 91.4% of the gate's weights had not moved after 8 steps at the default lr.) | task: moe_lora_routing on CUDA | #1266, #1312 |
| "Since v0.74.0 that made the first step fail" outside DeepSpeed, and before that the base trained in pure bf16 "where most AdamW steps at PRM learning rates round away". PRM now trains on fp32 master weights. | task: prm on CUDA | #1235, #1250 |
soup train --resume on task: prm or moe_lora_routing was "retraining from step 0 and overwriting checkpoints". | Resumed runs of those tasks | #1220, #1303 |
soup expect ran with no judge and passed every preference dataset at any threshold; a misspelled argument ran an expectation "on its defaults of 1 and 1,048,576 tokens and passed a 5000-token row with exit 0"; suite keys other than expectations were dropped; a UTF-8 BOM hid the first row from PII and other checks. | Data gates run with soup expect before v0.76.0 | #1433, #1505, #1428, #1461, #1483, #1499, #1434, #1457 |
soup diagnose and live_eval read healthy models answering in Cyrillic, Greek, Arabic, Hebrew, Hindi or CJK "as MAJOR mode collapse". | Diagnose verdicts on non-Latin output | #1233, #1301 |
GRPO "now skips non-finite optimizer steps instead of corrupting adapter weights": a watchdog zeroes a NaN or Inf gradient before optimizer.step(). The variant loss kernels also clamp the importance ratio before exponentiation, so a diverging policy no longer produces "infinite terms" and NaN gradients. | GRPO runs that went to NaN, and GRPO variant runs | #342, #1084, #1609, #1610 |
reward_hack_detector: info_rm on a two-valued reward whose first step sat near a 50% success rate recorded "a phantom separation of ~31,623 as the baseline", so a run could be halted or its beta changed on a false signal. | GRPO with info_rm on a 0/1 reward and a halt or a mitigation other than log_only | #1438, #1630 |
task: ppo with reward_hack_detector, reward_hack_mitigation or echo_trap_enabled: the run was announced and then "never halted, mitigated or trapped", because the signal buffer was never written. task: ppo still runs; those three flags are now refused on it before the model loads, and task: grpo is where the buffer is fed. | PPO runs that set those flags | #1441, #1514 |
ULD with wasserstein_aligned added about V/2 to the loss at each student position with no teacher target. minillm_enabled with the default offline settings forwarded the teacher and then "blend it at weight zero". | Distillation with those settings | #1426, #1541, #692, #977 |
| RoPE was configured after the model was built, and "changing the config after construction left the rotary frequencies unchanged". | SFT and pretrain with rope_scaling_type | #793, #931 |
ReLoRA restarts did not follow the paper contract. Each one now merges the PEFT-scaled LoRA update into the base weight and reinitializes lora_A and lora_B; the final output is a standalone dense model, and --resume is refused. | relora_steps | #693, #1133 |
fp8_attention: true on a card that could not run it, and either FP8 option with torchao missing or a conversion that failed, printed a yellow advisory and trained in bf16; fp8_attention also refused Ada cards such as the RTX 40-series, which can run FP8. quantization_aware: fp8 "converted the model on any card, Ampere included"; both options now require compute capability 8.9 or newer. nvfp4: true on Blackwell asked torchao for a class it does not export, "a yellow advisory and a bf16 run". nvfp4 and fp8_attention on task: sft were read by nothing. quantization_aware: true ran torchao's int8 weight-only quantization after LoRA on the trainers that applied it, "a LoRA run had zero trainable parameters", and was not applied on the others. | Anyone who set one of those | #835, #1044, #1154, #1152, #1333, #826, #1066, #800, #1022, #1222, #1275 |
task: embedding with the default contrastive loss at batch_size: 1: "the loss and gradient are exactly 0 and the adapter never moves". task: kto at batch_size: 1: "TRL's KL term is degenerate at a per-device batch of 1". | Embedding and KTO runs at batch size 1 | #1234, #1296, #1420, #1476 |
On a SimPO row whose chosen outgrows data.max_length: "Every LoRA tensor went NaN while transformers' logging_nan_inf_filter logged the loss as 0.0." | task: simpo | #1208, #1500 |
LLaMA Pro freeze_trainable_layers: with lora.r: 0, 0 or a negative value "silently trained the whole expanded model", and any positive value trained only the appended blocks. Under LoRA the value changed nothing (#1326). | expand_layers with lora.r: 0 and a freeze_trainable_layers of 0 or a negative value | #1409, #1586 |
training.use_longlora: true: "the LongLoRA override rolled the query/key projections of half the attention heads along the sequence with wrap-around, under full causal attention, so earlier positions could read keys computed from the last tokens of each sequence (future tokens leaked into the training loss)", and no S² grouped attention ran at all. | SFT on the transformers backend with use_longlora | #1240, #1278 |
training.ebft_variant: the EBFT term it added to the SFT loss "scored each position's logits against that position's own input token, with no causal shift, so it rewarded copying the input over predicting the next token". | SFT with ebft_variant | #1230, #1279 |
training.quantization_aware: true: upstream says it "never ran quantization-aware training". Where it was applied (the transformers model setup of sft, dpo, kto, orpo, ipo, bco, simpo, grpo, ppo and pretrain, which tts and preference also run) it ran torchao's post-training int8 weight-only quantization over every Linear layer after LoRA, adapter included, so a LoRA run had zero trainable parameters. Upstream adds that none of the runs it tried (SFT, SFT with gradient checkpointing, full fine-tuning, DPO) completed. Everywhere else the key was not applied at all. | Any config that set it. A run that did finish did not do quantization-aware training. quantization_aware: fp8 and quantization_aware: quest are different code paths and are unaffected | #1222, #1275 |
rope_scaling_type: llama3 on a checkpoint that already ships Llama 3.1 RoPE scaling rebuilt the checkpoint's native block (upstream's example extends one to 262144 tokens) "as factor 2 over 131072, so 35 of the 64 frequency pairs of an 8B head rotated up to 8x faster than in pretraining", and the run printed only "Long-context enabled". It now composes with the native block, and extending a checkpoint already scaled in another way is refused. | Text sft, pretrain and tts runs that extended a Llama 3.1 checkpoint with rope_scaling_type: llama3 | #1239, #1311 |
Three refused settings trained without the setting: backend: unsloth on tasks with no unsloth setup ran plain transformers while printing "unsloth (fast mode)", freeze_layers trained every layer, add_new_tokens added nothing. For these three, removing the key does not change what trains. use_longlora, ebft_variant and quantization_aware: true are different: they ran, and wrongly (see the rows above). | A config that carried one of those three settings; the other refused settings are listed there with their messages | #1357, #1372, #1497, #1581, #1358 |
format: tool-calling "dropped every row soup agent synth writes, and soup train --dry-run still said "Ready to train!" on 0 rows". format: auto read one row, so "a first row that happens to lack an optional key" picked the poorer converter for the whole file. | Tool-calling data and files whose first row lacks an optional key | #1217, #1321, #1424, #1496 |
Rows loaded through a streaming data.interleave, a remote URI or a Hub dataset kept their image and audio paths as written, "so a relative path was opened against the working directory and data.image_dir / data.audio_dir had no effect". | Vision or audio runs on those load paths with relative media paths | #1599 |
soup data lint failed correct conversational DPO data on prompt_leak, and length_bias measured prompt plus answer. | Lint verdicts on conversational preference data | #810, #916 |
soup data forge --judge-provider, soup data augment and soup distill-prompt with the provider down or rate-limiting: failed calls "came back as empty text instead of raising", and the commands still reported success. | Synthetic-data runs with an unreachable provider: open the dataset and filter empty answers | #1221, #1261, #1274, #1463 |
soup reward stress reported the built-in format reward "robust (not gameable)" although it awarded 1.0 to <think></think>a. A numeric reward.py from soup reward synth now reads the gold and the completion with the shared answer parser the built-in rewards use, so regenerate old ones. | Reward verifiers checked with stress or generated with synth before v0.76.0 | #814, #918, #1350, #1533 |
A model that failed to load in soup eval quant-check was replaced by a deterministic stub that reported OK. | Quantization checks that passed on stubs | #809, #917 |
The soup diagnose forgetting probe scored "the first 12 rows of --dataset", the adapter's own training data, so it could report OK 1.000 after catastrophic forgetting. Live soup eval behavior scored by the oracle label word instead of whether the model refused. | Live soup diagnose and soup eval behavior --base-model verdicts | #1436, #1508, #1335 |
soup registry diff, soup eval compare and soup eval leaderboard used the oldest eval row per benchmark instead of the newest, and --baseline registry:// took a re-measured benchmark's score and provenance from a stale row. | Comparisons and baselines where a benchmark was measured more than once | #1270, #1291, #1224, #1260 |
A --baseline recorded before v0.76.0 is on a different scale for mini_common_sense (24 items became 40), mini_arithmetic (36 became 40) and mini_tool_call. The bundled scorer revision moved from 1 to 5. The custom-task contains scorer now requires a whole token, so #### 420 no longer contains 42. | soup ship and soup eval gate baselines and custom-task scores | #405, #1111, #1192, #1462, #1673, #1342, #1490 |
Annex XI and XII documents wrote 0 and 0.000 kWh for training compute and energy where nothing measured them, "presented as measurements". They now say not measured. | Compliance documents from soup train --annex-xi without --track-energy | #1446, #1569 |
soup sweep sent ten task values to the SFT wrapper and never read backend: mlx, "so an arm trained next-token cross-entropy while the run registry recorded the configured task". Diverged arms are now sorted after finite losses. | Sweeps on distill, tts, classifier, reranker, cross_encoder, prm, unlearn, moe_lora_routing, online_dpo, asr or MLX | #1213, #1397, #944, #946 |
The vLLM, SGLang and DeepSpeed-MII engines were handed the rendered chat template as a string and added their own BOS (fixed for the transformers backend in v0.75.0). A streamed chat request (stream: true) skipped --structured-output and --reasoning-parser. | Verdicts, benchmarks and usage.prompt_tokens taken through those three engines; streamed requests | #867, #890, #995, #891, #996, #1448, #1607 |
soup loop watch recorded canary_verdict: OK from the activation call's bare 2xx although no canary split or verdict ever ran, and soup loop canary only wrote .soup/loop.yaml. | soup loop manifests from earlier releases | #815, #982, #1104 |
soup merge --save-format 4bit_forced did not quantize every linear module: transformers was left to skip lm_head. It now quantizes every linear module and refuses a model with tied embeddings by name. | 4bit_forced merges | #1448, #1606 |
soup train --find-lr that could not run printed one yellow line, "wrote a report whose recommended_lr came from the LR schedule alone, and exited 0". It now names the cause, exits 1 and writes no report. | A recommended learning rate from a sweep that could not run | #1203, #1334 |
A failed adapter merge deleted out_dir before the replacement was in place (soup shrink, soup draft distill), and soup reward synth -o reward.py --force overwrote and then deleted a working verifier on a refusal. | Anyone whose merge or synth ended in an error | #1450, #1454 |
grad_accum_auto_tune built its monitor with a hard-coded 24 GB, so the advisory "could never fire" on 4 to 20 GB cards. Installed soup local-rl train unit files ignored --min-pairs and -o. | Small-card runs with the advisory; nightly local-rl units | #1620, #1625, #1448, #1517 |
If a row names a verdict, such as a ship decision, a diagnose result or a benchmark, the artifact is the verdict and not the weights: the weights from that run are not touched by the row unless it says the training changed.
New commands and options
| What | What it does | Where |
|---|---|---|
soup adapters check, soup adapters audit | check scans a LoRA adapter for silent failure and ends in one contract line, alive or inactive: <reason>; audit compares what a finished run recorded against the config that started it and exits 2 on a divergence (#1721, #1738, #762, #763) | Is the adapter alive |
soup bench train, soup bench infer | bench train trains a fixed number of steps in a scratch directory and exits 1 when the model was not training; covers task: sft on the transformers backend (#836, #1146) | Benchmarks |
soup data clean | Sanitizes and repairs a dataset without touching the input file: control characters, zero-width spaces and BOMs by default, heuristic repairs behind opt-in flags (#718) | Clean a dataset |
soup recipes verify | Checks on the meta device, with no weights downloaded, that every shipped recipe, template and example can attach a LoRA adapter, or one config with --config (#1116, #1117) | Recipes |
soup rewind, training.rewind_log | Names the dataset rows behind a loss spike; a single-process SFT run writes <output>/rewind.jsonl by default (resumed runs and packing turn it off with one warning; #1000, #1018) | Find the rows behind a loss spike |
soup infer --batch-size, --cuda-graphs | Opt-in batched inference (default stays 1); --cuda-graphs is experimental and needs PyTorch 2.14 or newer. On one RTX 5070 Laptop, unpinned, Qwen2.5-1.5B in FP16, the slowest run with graphs decoded 2.9x faster than the fastest run without. Record: benchmarks/gate-v0.76.0-cuda-graph-decode.md (#765, #1083 for --batch-size; #1322 for --cuda-graphs) | Benchmarks |
soup ab | A two-sided, variance-robust test: a normal-inverse-gamma Bayes factor that decides reject_h0, with a confidence-sequence rule that decides accept_h0. v0.75.2's test was an mSPRT (a Gaussian-mixture likelihood ratio read against Wald's two boundaries). --beta is retired: it is ignored with a warning, and upstream says v0.77 will refuse it (#1227, #1265, #1418) | Trace ecosystem |
soup draft measure | Reports the break-even acceptance rate and the best draft length (#843, #1042) | Speculative drafts |
| Sigstore keyless signing | soup adapters sign --backend sigstore and soup attest emit --sign sigstore, verified against an out-of-band certificate identity and OIDC issuer. Needs pip install "soup-cli[sigstore]"; without the extra both signing commands exit 2 and write nothing. Shipped in v0.76.0; upstream's tests mock the signer and benchmarks/ has no end-to-end record (#1114, #1115) | Supply chain security |
soup train --nodes, --node-rank, --master-addr, --master-port | Multi-node Accelerate launches (#40, #853) | Multi-GPU |
soup doctor | Reports each NVIDIA GPU's compute capability and architecture and the precision it supports (#829, #947), and the MLX backend (#659, #660) | Getting started, MLX backend |
| 170 recipes | Recipes that could not load their model were fixed or removed (#1398); new DPO and GRPO recipes for newer model families. Counted from the catalog at the tag | Recipes, Recipe catalog |
| Training options | Token-chunked checkpointed KL distillation, training.distill_chunk_size (#743); the PEFT LoRA-FA optimizer, training.use_lorafa (#962); training.eval_steps; an experimental QuEST mixed-route SFT path whose retained result is evaluation-only (#1105); data.format: cross_encoder, valid only with task: cross_encoder | Modality II, LoRA quality, Training |
| Layer streaming disk tier | A background direct-I/O reader, training.stream_read_ahead and SOUP_LAYER_STREAM_STRIPE_DIRS | Layer streaming disk tier |
| READMEs | The repository README is now also available in Arabic, Japanese and Simplified Chinese, beside Turkish (#1100, #1733) | Contributing |
Counts at the tag: 170 recipes, 78 top-level commands (rewind is new), 29 command groups (bench became one), 37 pip extras (sigstore is new), 24 optimizer names and 20 values in the data.format list (19 name a row shape, and the 20th is auto).
Layer streaming
Layer streaming is still opt-in and still BETA. The disk tier now reads on a background thread through direct I/O, and can stripe across NVMe drives with SOUP_LAYER_STREAM_STRIPE_DIRS. On one RTX 5070 Laptop 8 GB with a synthetic 70B-shaped NF4 store of 36 GB (random weights), a cold step took 110.5 s on the previous synchronous reader and 16.2 to 17.3 s on the direct-I/O reader; across two NVMe drives a 17.4 s step fell to 9.97 s. A sustained-load probe measured 1.28x and 1.41x in two rounds and reached no formal verdict. A store that fits RAM is still faster on the RAM tier, which stream_source: auto picks. That is one box, a synthetic store and an 8 GB card. stream_pin: true on the disk tier now refuses a run the box cannot page-lock, where it used to announce and proceed. The knobs, the refusals and every caveat are on Layer streaming disk tier.
A post-repair row was recorded for v0.76.0: Llama-3.1-8B NF4 on a different RTX 3050 Laptop 4 GB at 1935 MHz, 208.6 tok/s, 2.40 GB peak allocated. The 119.6 tok/s row was taken at 952 MHz, on v0.72.2, before the repair. The upstream record says a throughput figure is only comparable at the clock it was taken at, so the two rows are printed with their clocks and no ratio is given. The table is under Layer streaming. Separately, #1143 routes the streamed NF4 forward through bitsandbytes' own fused-versus-dequantized dispatch, the same kernels as resident NF4 (the backward still dequantises). The only evidence that this closes the Blackwell inexactness is the contributor's changelog entry and its tests: benchmarks/ has no row recorded after #1143, and upstream's #776 is still open in the records. Read every "bit-exact" sentence on this site as scoped to the measurements actually taken (the v0.72.x gate, and the H100 session at v0.73.0), as Layer streaming explains.
Also in this release: dpo and kto over an untied streamed checkpoint hold a private copy of the output head for the step (see Preference losses over streaming), a streamed run reads the saved adapter back and fails if it holds no tensors (#1031), and a LoRA target on lm_head or embed_tokens is refused under stream_layers (#1012).
Security
No new security advisory is published with this release. Two were published earlier and are already public: GHSA-63h4-gvp4-r26g, fixed in 0.75.1, and GHSA-9f49-hqpf-c849 (outbound endpoint validation), fixed in 0.75.2. Both fixes are carried in 0.76.0, so a user still on 0.75.0 or 0.75.1 should upgrade. The supported version for security fixes is now 0.76.x; anything below 0.76 is not supported. See An adapter with nothing in it and Private addresses refused for the two advisories.
Hardening found by this release's own review round, in commands that ship for the first time in 0.76.0 (so no earlier release is affected):
soup bench train -oandsoup rewind --jsoncheck their destination before any work starts: it must stay under the working directory and may not be a symlink, a junction or a directory, and the file is written atomically. A refused path exits 2.soup rewindandsoup data cleanprint dataset rows, log values and error text through the terminal-safe path, so control sequences stored in a dataset cannot reach your terminal.soup data clean --strip-boilerplate: a long crafted row could make one of its sign-off patterns run for a very long time. The pattern no longer rescans from every offset, and results for normal rows are unchanged.soup adapters checkrefuses a weights file larger than 2 GiB, checked before the file is opened, instead of accumulating an untrusted file in memory.
Also in this release:
soup uino longer keeps its token insessionStorageor onwindow(#1136, #1190). On a loopback bind it opens and prints a URL that carries the token and keeps a session across reloads with an HttpOnly cookie (#1191, #1474). The Web UI caps YAML and chat request bodies at 1 MiB, and/api/data/inspectat 8 KiB (#897, #915, #939, #948). See Web UI.- The four items the v0.75.2 page tracked as #1547 to #1550 shipped: the judge-URL checks share one loopback set (#1548), the refusal message now names a remedy (#1549), IP literals are parsed by one pure-Python reader (#1550), and a CI ratchet walks the outbound call sites (#1547). The Ollama provider's check still keeps its own list of the same three hosts.
- Third-party plugin code loads only after you run
soup plugins enable <name>, andsoup plugins installinstalls nothing and exits 2. See Plugins.
Deprecations and the v0.77 deadline
All of these work as before in 0.76.0 and are, by upstream's stated deadline, refused or removed from v0.77. The warnings ship for the first time in 0.76.0, so this release is the one minor of notice. This site cannot date v0.77: the deadline is upstream's, printed by the CLI and pinned by tests.
| What | At v0.76.0 | Upstream says |
|---|---|---|
29 staged config fields (training.lr_groups, early_stop_patience, citation_recall_threshold, forgetting_eval_steps, forgetting_benchmark, forgetting_stop, checkpoint_eval_*, checkpoint_keep_top, grace_codebook*, convergence_*, long_context_grpo, vision_grpo, quantize_ref_model, and the data.video_*, data.image_*, data.split_thinking, data.eval_on_each_dataset, data.resize_vocab, data.extend_conversation and data.skip_prepare_dataset fields) | Warning: <field> is accepted but read by nothing; v0.77 will refuse it. The load continues and exits 0. A field is reported only when its value differs from the schema default | Refused from v0.77 (#808, #1106) |
data.remove_unused_columns: true | Warns and is ignored. The default is now false | Refused from v0.77 (#759) |
An explicit training.quantization: 4bit or 8bit on a task whose trainer never quantizes (distill, classifier, reranker, cross_encoder, prm, moe_lora_routing, unlearn, asr) | Warns and is ignored; an unset value on those tasks now resolves to none silently | Refused from v0.77 (#795) |
soup ab --beta | Warns and is ignored | Refused from v0.77 (#1418) |
soup export --format awq and --format gptq, and soup quantize --to awq and --to gptq | Work as before, after a Deprecated: notice that names gguf, onnx, tensorrt, bitnet and tq1_0. Both upstream projects are archived, neither extra installs next to [train], and auto-gptq has no Python 3.12 wheel | Removed in v0.77 (#338, #1690; tracked in #1710) |
A staged-field warning ends with "v0.77 will refuse it" and a deprecated-value warning with "Soup v0.77 will refuse it." Deleting the key keeps the config loading if upstream refuses it in v0.77 as it says it will. Five keys that soup train prints a Note: about (forgetting_detection, checkpoint_intelligence, early_stop_on_regression, convergence_detection and a non-default forgetting_threshold), and warmup_auto, are still unread and are not in the staged table, so no date applies to them. See Settings Soup refuses or warns about at load.
Gate record and preprint
Gate record. Upstream publishes every gated piece of work in this window as written under benchmarks/, and says nothing was left unpublished. The four records named for this release are gate-v0.76.0-cuda-graph-decode.md, gate-v0.76.0-tool-call-discrimination.md, gate-v0.76.0-arithmetic-discrimination.md and gate-v0.76.0-common-sense-discrimination.md. Alongside them are the disk-tier records (gate-971-async-nvme-source.md, gate-974-disk-tier-direct-io.md, gate-two-drive-striping.md), gate-901-14b-on-8gb.md, gate-1702-arena-packing.md, gate-836-bench-train-contract.md, gate-725-lorafa-operating-point.md, the QuEST records, the three soup ab gates, the probe-rtx5070-*.md records, and the post-repair NF4 row added to gate-v0.72.2-nf4.md. The two-drive sustained-load probe reached no formal verdict, and says so. The index is Measurement records.
Preprint. The concept DOI is 10.5281/zenodo.21771064, unchanged, and the paper is explicitly scoped to the version it measured, Soup v0.73.0. No measured number it states is altered by this release, and none was re-measured to a different value. This window supersedes three of its statements, and upstream says a new version that restates them is future work:
- "The laptop configuration has not been re-run on the repaired code" is false since the post-repair row described under Layer streaming above.
- The statement that the repaired streamed path always dequantises no longer describes the shipped code: since #1143 the streamed NF4 forward follows bitsandbytes' own dispatch, as resident NF4 does. The backward still dequantises, because bitsandbytes exposes no transposed 4-bit GEMM.
- The disk tier, which the paper lists as unmeasured, is now shipped and measured, on a second laptop (an RTX 5070 Laptop 8 GB).
See The paper for the paper itself.
Known limitations
These are upstream's, in plain terms.
- Not every declared config field reaches a consumer yet (#1814). A guard test pins 36 fields as unconsumed: the 29 staged fields, plus seven that are not in the staged table. Only two of the seven are plainly unwired:
training.warmup_auto(onlysoup autopilottouches it, to write it into a config it generates; no trainer reads it) andtraining.forgetting_threshold(its description says it is staged, but it loads without a warning). The other five are accounted for:data.remove_unused_columnsis deprecated,training.packing_cross_doc_attn_maskis already refused at load, andtraining.llm_int8,training.load_in_16bitandtraining.unsloth_bnb_4bitare aliases the schema rewrites. soup doctor --configknows only a few combinations (#1815). Its declared tables cover 23 of the shipped recipes; for the rest it reports nothing rather than guessing, and the largest gaps aresft,grpoanddpoon the transformers backend with text.- Several error messages are less helpful than they should be (#1816, #1817, #1818). A config rejected by a model-level (cross-field) validator prints an empty field location, and an unknown key with no near match names the key but suggests nothing. Some rejections arrive only after a slow import or model load (
soup bench trainwith--warmupnot below--steps,soup recipes verifywith a missing--config,soup infer --cuda-graphson a setup it refuses).soup data cleanandsoup abreport a vague message on rows of an unknown shape, andsoup rewind --jsonwritesNaNandInfinityfor a non-finite loss, which is not strict JSON. - The response from an endpoint you choose is not size-capped (#1819). The judge and data-generation calls (
soup eval judge,soup data generateand the vLLM, Ollama and Anthropic providers) read the whole response body before parsing it, and whoever wrote thesoup.yamlor the suite chooses the endpoint. Do not run asoup.yamlyou do not trust. net_guardchecks IP literals, not names, by design. It does not resolve a hostname, so a hostname is passed through unresolved. That design was agreed in upstream's threads and is written in its docstrings.typer>=0.9.0, the declared floor, is not a working floor; 0.15.4 is (#1802). See Install pins.- Upstream says v0.77 will refuse the deprecated settings above: the staged fields (#808),
data.remove_unused_columns,training.quantizationon tasks that do not quantize andsoup ab --beta, and will remove AWQ and GPTQ export (#1710). If it does, more configs will stop loading in that release;soup doctor --configshows the warnings now. - Layer streaming remains BETA and opt-in, and
--cuda-graphsis experimental.
See also
- Settings Soup refuses or warns about at load: the refusals grouped by what they touch with the start of each message, the 29 staged fields and the deprecated values.
- Exit codes: the full table, including the commands that did not move.
- Layer streaming disk tier and Layer streaming.
- Is the adapter alive, Benchmarks, Clean a dataset and Find the rows behind a loss spike: the new commands.
- v0.75.2: private addresses refused, v0.75.1: an adapter with nothing in it and v0.75.0: MLX honours its config: the releases before this one.
- What's new across v0.71 to v0.76.
- 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.