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. Falls back to a synthetic curve when prerequisites are missing (no torch / config load failure / dataset empty) so CI without GPUs still produces a parseable report.

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. Nothing in Soup reads the file back at v0.75.2, 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: VRAM pressure crossed threshold; recommend (batch_size, grad_accum_steps) (4, 4) -> (2, 8). Restart training with the new pair to take effect.

The pair in that line is illustrative. One-shot per run; doesn't fire when CUDA is unavailable, and it only advises: the running job is not changed.

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.

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

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.