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.yaml before 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

bash
pip install -U "soup-cli[train]"
soup doctor --config soup.yaml

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

text
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: fp8 and quantization_aware: quest are 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, set training.rope_scaling_type and train plain LoRA.
  • training.ebft_variant (and ebft_temperature on its own) (#1230), and every value of training.gdpo_variant (#1309).
  • Eight optimizer names (adam_mini, adamw_apex_fused, ao_adamw_4bit, ao_adamw_8bit, ao_adamw_fp8, badam, came_pytorch, dion), and muon and adamw_hf on every backend except MLX (#1269, #1283). The allowlist is 24 names, down from 32.
  • training.rope_scaling_type where no setup reads it (#1697), and longrope everywhere (#1239). Only the transformers text setups of sft, pretrain and tts rescale RoPE.
  • training.freeze_layers and freeze_ratio outside the text sft and tts path on the transformers backend (#1497, #1581), training.auto_mixed_precision outside sft and tts (#1618), and use_liger and use_flash_attn outside sft and tts (#806).
  • backend: unsloth on 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_steps unless quantization: none is stated explicitly (#693), freeze_trainable_layers without expand_layers or with a value that differs from it (#1396, #1409), expand_layers on a quantized base (#1237, #1308) or outside the text sft and pretrain path (#1325, #1390), and loraplus_lr_ratio where no LoRA B matrix trains (#745, #1210, #1324).
  • task: kto at batch_size: 1, for every KTO run (#1420), and task: embedding with the default contrastive loss at batch_size: 1 (#1234).
  • data.mask_history: true with train_on_responses_only: false (#761), data.add_new_tokens and data.new_special_tokens where no trainer adds them (#1358), and data.chat_template on 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.

CommandConditionv0.75.2v0.76.0
soup eval gatea task failed or regressed12
soup eval againstregression12
soup lock checkdrift32
soup expectan expectation failed32
soup expectsuite or data rejected23
soup data validatemissing file, unknown or undetectable format (an unsupported file extension is still 1)13
soup eval quant-checka task is MAJOR02
soup eval quant-checka model fails to load0 (a stub scored it)1, unless --allow-stub
soup eval behavior, soup eval checklistrun with no evidence03
soup diagnosea requested probe did not runscored OK3 (NOT_RUN); MAJOR is still 2
the commands in the schemeunknown flag or malformed number23
soup plugins installany name02

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_schedule and training.dpo_ref_regen_epochs now run on dpo, ipo and preference. Through v0.75.2 a run that set either key stopped when training started, because the callback was not a Hugging Face TrainerCallback; upstream records the dpo_beta_schedule crash as an AttributeError at step 0 (#1229, #1294). training.dpo_ref_regen_epochs is wired under LoRA (#1345, #1636): it copies the active adapter into the frozen ref adapter every Nth epoch, and it is refused with lora.r: 0.
  • training.loraplus_lr_ratio applies on every LoRA task instead of only sft, pretrain and embedding, so those runs produce different weights (#745, #1080). It is refused on backend: 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 torchao and training.nvfp4 resolve their torchao classes from the module that defines them, and the [qat] extra now needs torchao>=0.18.0 (#826, #1066). See Export.
  • soup data preprocess stops 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 with cache hash mismatch: re-run soup data preprocess to rebuild it.
  • ReLoRA restarts follow the paper contract and need an explicit quantization: none (#693, #1133). See LoRA quality.
  • data.mask_history now keeps only the last assistant turn in the loss and requires data.train_on_responses_only: true (#761, #1101).
  • soup data recipe --execute refuses silent placeholder generation (#817, #922). A recipe with an llm_text or judge node needs --provider or the explicit --offline, and exits 2 without either. See Plugins.
  • soup bench is a group. soup bench infer is the old inference benchmark, and soup bench <model> still runs it. See Benchmarks.
  • soup serve --auto-quant exits 2 instead of choosing a format (#816, #1035).
  • gradient_checkpointing: false is honoured on 4-bit, 8-bit and mxfp4 runs, and the SFT tiers medium and selective now make different trade-offs (#797, #1045; #796, #1036). Peak memory can rise on a run that used to inherit full-block checkpointing.
  • --device now means something on soup infer, chat, diff, bench and soup 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 --benchmarks without --benchmark-file exits 1, and the decontaminated cell reads not run instead of 0 (#1448, #1518). See Data Forge.
  • Relative image and audio paths resolve against data.image_dir or data.audio_dir, or the data file's own directory, on every load path (#1599; #1598, #1604 for soup data inspect). See Data formats.
  • --json output is written straight to stdout for soup audit-log tail, soup bom emit, soup profile, soup version --json --full, soup cost and soup 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, and torch>=2.6.0, transformers>=5.16.1,<6.0.0, trl>=0.29.0,<1.0.0 and peft>=0.20.0,<1.0.0 are unchanged. [train] now declares requests>=2.32.2, [audio] adds torchaudio>=2.6.0,<3.0.0, and there is a new [sigstore] extra (sigstore>=4.4.0,<5.0.0, not part of [all]).
  • click is now declared and bounded: click>=8.0.0,<8.5. click 8.5.0 drops the description of every positional argument from every soup <command> --help page, so a fresh install that resolved the newest click showed ADAPTER 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; pipx does that.
  • typer>=0.9.0,<0.21.0 is declared, but only typer 0.15.4 and newer start on every click the metadata admits (#1802). pip install -U soup-cli does not upgrade a dependency that already satisfies its range, so an environment that already holds an older typer fails on every command, soup --help included, with Type not yet supported: int | None. Fix it with pip 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 wrongWho is affectedPR
"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

WhatWhat it doesWhere
soup adapters check, soup adapters auditcheck 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 inferbench 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 cleanSanitizes 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 verifyChecks 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_logNames 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-graphsOpt-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 abA 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 measureReports the break-even acceptance rate and the best draft length (#843, #1042)Speculative drafts
Sigstore keyless signingsoup 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-portMulti-node Accelerate launches (#40, #853)Multi-GPU
soup doctorReports 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 recipesRecipes 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 tagRecipes, Recipe catalog
Training optionsToken-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_encoderModality II, LoRA quality, Training
Layer streaming disk tierA background direct-I/O reader, training.stream_read_ahead and SOUP_LAYER_STREAM_STRIPE_DIRSLayer streaming disk tier
READMEsThe 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 -o and soup rewind --json check 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 rewind and soup data clean print 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 check refuses 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 ui no longer keeps its token in sessionStorage or on window (#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/inspect at 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>, and soup plugins install installs 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.

WhatAt v0.76.0Upstream 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 defaultRefused from v0.77 (#808, #1106)
data.remove_unused_columns: trueWarns and is ignored. The default is now falseRefused 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 silentlyRefused from v0.77 (#795)
soup ab --betaWarns and is ignoredRefused from v0.77 (#1418)
soup export --format awq and --format gptq, and soup quantize --to awq and --to gptqWork 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 wheelRemoved 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:

  1. "The laptop configuration has not been re-run on the repaired code" is false since the post-repair row described under Layer streaming above.
  2. 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.
  3. 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 (only soup autopilot touches it, to write it into a config it generates; no trainer reads it) and training.forgetting_threshold (its description says it is staged, but it loads without a warning). The other five are accounted for: data.remove_unused_columns is deprecated, training.packing_cross_doc_attn_mask is already refused at load, and training.llm_int8, training.load_in_16bit and training.unsloth_bnb_4bit are aliases the schema rewrites.
  • soup doctor --config knows 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 are sft, grpo and dpo on 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 train with --warmup not below --steps, soup recipes verify with a missing --config, soup infer --cuda-graphs on a setup it refuses). soup data clean and soup ab report a vague message on rows of an unknown shape, and soup rewind --json writes NaN and Infinity for 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 generate and the vLLM, Ollama and Anthropic providers) read the whole response body before parsing it, and whoever wrote the soup.yaml or the suite chooses the endpoint. Do not run a soup.yaml you do not trust.
  • net_guard checks 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.quantization on tasks that do not quantize and soup ab --beta, and will remove AWQ and GPTQ export (#1710). If it does, more configs will stop loading in that release; soup doctor --config shows the warnings now.
  • Layer streaming remains BETA and opt-in, and --cuda-graphs is experimental.

See also

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.