Soup Cans v2 + Live LR Finder (v0.33.0)

v0.33 graduates several v0.27–v0.32 stubs into live functionality and ships meaningful follow-ups across the whole stack.

Soup Cans v2 — soup can run + soup can publish

bash
# Run a .can end-to-end: extract → train → optional deploy
soup can run my-recipe.can --yes
soup can run my-recipe.can --yes --deploy --env-capture env.txt

# Publish to HF Hub as a dataset
soup can publish my-recipe.can --hf-hub user/my-recipe

soup can run requires explicit --yes (mandatory consent — auto-downloads data + auto-trains). Manifest format bumped 1 → 2 (additive: new deploy_targets field). Both v1 and v2 cans still load.

Security — extract dir is containment-checked, GGUF rglob result for ollama deploy is realpath+commonpath checked against extract dir to prevent symlink escape, subprocess TimeoutExpired after 24h cap returns rc=124 (coreutils convention). soup can publish validates repo_id, resolves the HF token via env / cache files, commit messages first-line + 200-char capped (matches v0.29 push policy).

Live --find-lr training loop

The v0.32 stub curve is replaced with a real in-process LR-sweep training loop. NaN/Inf loss terminates the sweep early so diverged_at is honest.

Since v0.76.0 a sweep that cannot run is a refusal, not a synthetic curve. If the config does not load, the model or dataset cannot be loaded, or torch is not installed, soup train --find-lr names the cause, exits 1 and writes no report. Run on the v0.76.0 CLI without torch: --find-lr: live sweep unavailable (No module named 'torch'); no LR report was written., exit 1, and no lr_finder.json. Through v0.75.2 it printed writing synthetic curve, wrote a placeholder report and exited 0, so a recommended_lr from such a run described no model and no data (#1203). The live sweep also honours --trust-remote-code now; it used to load the model with a hardcoded False (#1449). The rest of the sweep's rules, such as how few rows it accepts, are on Training stability.

bash
soup train --config soup.yaml --find-lr --find-lr-output ./lr_finder.json

Spike-recovery hint file

Loss-spike recovery now writes a spike_recovery.json hint with the decayed LR for re-launch:

json
{"attempts": 1, "max_attempts": 3, "loss_at_spike": 9.41, "previous_lr": 0.0002, "recommended_lr": 0.0001, "should_recover": true}

The values above are illustrative; the keys are the ones the callback writes. recommended_lr is previous_lr multiplied by training.loss_spike_recovery_lr_decay (default 0.5), and should_recover is true while training.loss_spike_recovery_max_attempts (default 3) is not yet used up. A search of src/ at the v0.76.0 tag finds nothing that reads the file back, so it is a hint for a wrapper script, not a rewind.

Live optimizer-state rewind and live DataLoader rebuild remain follow-ups (HF Trainer / TRL upstream constraints).

VRAM grad-accum advisory (live)

When VRAM pressure crosses the threshold, the advisory now prints a concrete recommended (batch, accum) pair preserving effective batch:

Grad-accum advisory: peak 7.8 of 8.0 GB (97%) crossed the pressure threshold; recommend (batch_size, grad_accum_steps) (4, 4) -> (2, 8). Restart training with the new pair to take effect.

The numbers in that line are illustrative. One-shot per run; doesn't fire when CUDA is unavailable, and it only advises: the running job is not changed. Since v0.76.0 the pressure is measured against the card the run uses (peak and total are read from the same CUDA device; there is no soup.yaml key to override the total, only Python code that builds the callback itself can). Through v0.75.2 the monitor assumed a 24 GB card, so on a 4 to 20 GB card it could never fire and on a 40 to 80 GB card it fired early (#1620). The line format above was read from the callback's code at the v0.76.0 tag, not produced by a run.

Auto-reexec under accelerate

soup train --gpus N now auto-reexecs under accelerate launch instead of just printing the command. Critical flags (--fsdp, --deepspeed, --resume, --wandb, --tensorboard, --yes) are forwarded as separate argv elements. Use --no-reexec to opt out and just print the command. Multi-node launches (--nodes, --node-rank, --master-addr, --master-port) are on the Multi-GPU page, and on Windows soup train --gpus N returns the launcher's exit code.

bash
soup train --config soup.yaml --gpus 4               # auto-reexec
soup train --config soup.yaml --gpus 4 --no-reexec   # print command only

Trust the printed command from v0.73.3 onward, and not before. It used to be built from a second, hand-maintained list of "what the user typed", and that list was short, so flags fell off it silently: following the hint literally trained without --fsdp, and the run succeeded, which is why nothing ever pointed back at the hint. The printed copy was deleted rather than patched, and the hint is now derived from the same argv the auto-reexec would have used, so the two cannot drift again. Four more flags survive it now: --name, --replay, --replay-ratio and --replay-seed.

Registry artifact attach

bash
# Attach an eval JSON to an existing registry entry
soup eval custom --tasks evals/sanity.jsonl --model ./output \
  --attach-to-registry chat-llama:v1

# Auto-attach exported artifact to registry
soup export --model ./output --format gguf --registry-id chat-llama:v1

eval_results and tensorrt are now valid artifact kinds. Since v0.76.0 soup export --registry-id records the files of more export formats than GGUF; the format list is on the Export page. lookup_entry_by_output_dir emits ResourceWarning when its 1000-row scan limit is hit (no silent miss).

v0.28 features expanded to DPO + Pretrain trainers

use_cut_ce, quantization_aware: "fp8", kernel_auto_compose, and activation_offloading now work on DPO and Pretrain trainers (in addition to SFT from v0.28). At v0.33, GRPO/KTO/ORPO/SimPO/IPO/PPO/RewardModel/Embedding still errored at config-load with a precise multi-trainer message. Since closed in v0.35: all eight load now, and the allowlist is thirteen tasks (see Multi-trainer speed & memory). "Work" here means the key is accepted and wired in: use_cut_ce still only takes effect when the third-party package imports, see Cut Cross-Entropy. (Since closed in v0.76.0 for one of the four: kernel_auto_compose: true is refused at config load, because the selector timed the same already-loaded model for every candidate and never applied the combination it printed; set use_liger and/or use_flash_attn explicitly instead. quantization_aware: true (the int8 spelling) is refused too; quantization_aware: fp8 is the FP8 training this paragraph means. Whether use_cut_ce engages on transformers 5.x was not tested.)

RLVR OS-level isolation

GRPO's code_exec reward gains real OS-level sandboxing on top of the v0.25 RLIMIT/socket-patch baseline:

  • Linux — best-effort os.unshare(CLONE_NEWUSER|CLONE_NEWNET|CLONE_NEWPID), which exists only on Python 3.12 or newer. On an older Python, or on a hardened kernel (unprivileged_userns_clone=0), it falls back silently to RLIMIT + socket-patch.
  • macOS — sandbox-exec wrapper with default-deny profile. (allow mach-lookup) narrowed to a 3-name allowlist (SecurityServer / notification_center / opendirectoryd.libinfo) to prevent DNS/NSURLSession bypass.
  • Windows — no OS-level isolation, and no RLIMIT either (resource limits are POSIX only). What remains is a scrubbed environment, an ephemeral working directory and the Python-level socket patch, which generated code can bypass.

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.