CLI Reference
Exit codes are defined per command, not uniformly across Soup. Exit codes lists what each gating command returns.
Core Commands
soup init
Create a config file with interactive wizard or template.
soup init [--template <template>] [--output <path>]soup train
Start a training run.
soup train [--config <path>] [--resume auto|<checkpoint>] [--wandb] [--tensorboard] \
[--deepspeed zero2|zero3|zero2_offload|zero3_offload|zero++|zero_pp] [--fsdp full_shard|shard_grad|full_offload] \
[--gpus auto|<N>] [--gate <suite>] \
[--push-as <user/repo>] [--hf-resume] [--yes]--gpus(v0.27) — topology-aware multi-GPU launch hint (NVLink/PCIe detection)--deepspeed zero++(v0.27) — ZeRO-3 with quantized weights & grads--gate(v0.26) — run an eval suite at epoch boundaries and halt on regression--push-as(v0.29) — auto-push everysave_stepscheckpoint to HF Hub as acheckpoint-<N>branch--hf-resume(v0.29) — pull the latest checkpoint branch from HF Hub and resume training--cloud modal|lambdawith--gpu <type>and--cloud-submit— plan-only unless you add--cloud-submit; see serverless cloud training.--cloud runpodis recognised and refused (RunPod cloud training is not yet live; use --cloud modal or lambda., exit 2), and any other value exits 2 withInvalid --cloud.
soup chat
Interactive chat with a trained model.
soup chat --model <path> [--base <model>] [--temperature <float>] [--max-tokens <int>]soup serve
Start an OpenAI-compatible inference server.
soup serve --model <path> [--backend transformers|vllm|sglang|mii] [--port <int>] [--host <host>] [--tensor-parallel <n>] [--gpu-memory <float>] [--speculative-decoding <draft-model>] [--num-speculative-tokens <n>] [--adapters <name>=<path>]--adapters (several named LoRA adapters on one server) works with --backend transformers only; with vllm, sglang or mii it exits 1. To serve one adapter on vLLM, pass the adapter directory itself as --model: the base model is read from its adapter_config.json.
soup export
Export model to deployment format.
soup export --model <path> --format gguf|gguf-ud|onnx|tensorrt|awq|gptq|bitnet|tq1_0|torchao [--quant <type>] [--output <path>]
soup export --format awq|gptq --calibration-data <p> [--bits 4|8] [--group-size 128]
# --calibration-data is REQUIRED for both awq and gptq, and --calibration-samples caps
# the rows read (default 128). Refused before the quantizer is imported: gptq since
# v0.74.0, awq since v0.75.0, where a flagless run silently downloaded AutoAWQ's own
# large default calibration set.
soup export --format onnx [--onnx-task text-generation|feature-extraction] # feature-extraction for task: embedding
soup export --model <path> --format gguf --deploy ollama [--deploy-name <name>] # export, then deploy; the Ollama name defaults to soup-<model-name>soup merge
Merge LoRA adapter with base model.
soup merge --adapter <path> [--base <model>] [--output <path>] [--dtype <type>]soup push
Upload model to HuggingFace Hub.
soup push --model <path> --repo <user/repo> [--hub hf|modelscope|modelers]
[--private] [--collection <owner/slug-hash>] [--message <commit message>]--collection(v0.29) — add the pushed repo to an existing HF Collection
Token resolution for --hub hf: env HF_TOKEN / HUGGINGFACE_HUB_TOKEN > ~/.cache/huggingface/token > ~/.huggingface/token. The legacy --token flag is deprecated and delegates to this chain.
Each other hub authenticates with its own credential, and that is new in v0.75.1. --hub modelscope reads MODELSCOPE_API_TOKEN, --hub modelers reads MODELERS_TOKEN, and neither reads HF_TOKEN any more. Before it, the chain above ran for every hub and the resolved Hugging Face token was handed to the third-party SDK, so pushing to ModelScope or Modelers disclosed a token that is often write-scoped (0.53.10 to 0.75.0). Rotate it if that was you.
soup eval
Comprehensive model evaluation platform.
soup eval benchmark --model <path> [--benchmarks <list>] [--fewshot N] [--batch-size N] [--run-id <id>] [--device cuda|mps|cpu]
soup eval custom --model <path> --tasks <path>
soup eval judge --target <pairs.jsonl> [--model <judge-model>] [--provider openai|server|ollama] [--rubric <path>] [--api-base <url>] [--run-id <id>]
soup eval auto [--config <path>]
soup eval compare <run1> <run2>
soup eval leaderboard [--sort-by <benchmark>] [--format table|json|csv]
soup eval human --input <prompts.jsonl> --model-a <path> --model-b <path> [--output <results.json>]
soup eval gate --suite <suite.yaml> [--model <path>] [--baseline <registry://id|scores.json>] [--regression-threshold 0.05] [--write-baseline <file>]
soup eval citation <data.jsonl> [--style bracket|inline|footnote] [--shuffle-seed <n>] [--output <scores.json>]Notes on the rows that have more to them than the form shows:
soup eval benchmarkwraps lm-evaluation-harness, so it needspip install "soup-cli[eval]".--benchmarksis a comma-separated list (defaultmmlu),--fewshotfalls back to each benchmark's own default when you leave it out,--batch-sizedefaults to 8,--run-idlinks the results to an existing training run, and--modelmay be a LoRA adapter directory.soup eval leaderboardreads the experiments database and exits 1 withNo eval results found.untilsoup eval benchmarkorsoup eval customhas recorded something.--sort-byapplies to the table only:--format jsonand--format csvprint the whole leaderboard without sorting by it. A--formatvalue other thanjsonorcsvis not rejected; you get the table.soup eval humanis interactive, so it needs a terminal. It generates both models' answers to each prompt (a JSONL file whose rows have apromptfield), shows them side by side and asksa,b,tieorq.qends the session early and the judgments so far are still scored and saved. The result is an Elo table, written with the judgments to--output(defaulthuman_eval_results.json). Both model paths must exist locally.soup eval gatehas a single failure code: it exits 0 when the suite passes and 1 when a task fails and also when the suite, the baseline or the model cannot be loaded, so a CI job cannot tell a failed gate from a broken invocation by the exit code alone. Everything else about it, including--write-baseline, is on the eval gate page.
On soup eval judge, --target is a JSONL file of prompt and response pairs you already generated (every line needs a prompt and a response field; soup infer writes exactly that shape), not a model, and --model names the judge. --api-base is the judge's endpoint: plain http is accepted only for localhost, 127.0.0.1 and ::1, and since v0.75.2 an IP literal that is private, link-local or reserved is refused on https as well, with loopback still allowed. A hostname is not resolved, so the check narrows what a URL can name directly; it does not make an internal host unreachable by name. See what v0.75.2 refuses.
soup deploy
Deploy models to inference runtimes.
# Ollama
soup deploy ollama --model <gguf> --name <name> [--system <prompt>] [--template <tpl>] [--parameter <key=val>] [--yes]
soup deploy ollama --list # only models whose name starts with soup-
soup deploy ollama --remove <name> # any Ollama model, by name
# HuggingFace Spaces (new in v0.29)
soup deploy hf-space \
--model <user/repo> \
--space <user/space> \
--template gradio-chat|streamlit-chatsoup deploy hf-space validates model_repo via validate_repo_id before substituting into app.py / README.md — a crafted repo id cannot inject Python code into the deployed Space. Instead of --template it accepts --template-dir <dir> (custom app.py + README.md, optional requirements.txt, {MODEL_REPO} placeholder; details on Hugging Face Hub integration), plus --private and --yes.
soup infer
Batch inference on a list of prompts. With --task asr (v0.71.32) it transcribes a JSONL of audio clips through a Whisper model or adapter and reports per-row and corpus WER/CER when reference texts are present. See ASR fine-tuning.
soup infer --model <path> --input <path> --output <path> [--max-tokens <int>] [--temperature <float>]
soup infer --task asr --model <whisper|adapter> --input <jsonl> --output <jsonl> \
[--audio-dir <dir>] [--asr-language <lang>] [--asr-task transcribe|translate]soup migrate
Import config from competing tools.
soup migrate --from llamafactory|axolotl|unsloth <input-file> [--output <path>] [--dry-run]soup recipes
Browse and use ready-made training configs.
soup recipes list # List all 167 recipes
soup recipes show <name> # Print recipe YAML
soup recipes use <name> [--output <path>] # Copy to soup.yaml
soup recipes search <query> # Search by model/task/sizeData Commands
soup data inspect <path> [--rows <n>] # View dataset stats (--rows: sample rows to show, default 5)
soup data validate <path> [--format <fmt>] [--min-valid-fraction <0.0-1.0>] # Check format compliance; exit 1 input error, 2 no usable rows or below the fraction
soup data convert <path> --to <fmt> --output <f> # Convert between formats
soup data merge <f1> <f2> --output <f> [--shuffle] # Combine datasets
soup data dedup <path> [--threshold <float>] # Remove duplicates
soup data stats <path> # Extended statistics
soup data generate --prompt "..." --count <n> # Generate synthetic data
soup data generate --provider openai|local|server|ollama|anthropic|vllm # Multi-provider support (openai is the default)
soup data generate --template code|conversation|qa|preference|reasoning # Domain templates
soup data generate --quality-pipeline # Validate + filter + dedup
soup data generate ... --batch-size <n> # Examples per API call (default 5)
soup data filter <path> [--coherence <f>] [--perplexity <n>] [--ppl-model <id>] [--field <name>] [--score-only] # Quality filter; perplexity is scored with gpt2 unless --ppl-model says otherwise
soup data sample <path> --strategy random|diverse|hard -n <n> [--pct <0-100>] [--seed <n>] # Sample subset
soup data split <path> --val 10 --test 10 [--absolute] [--seed <n>] # Split; --val/--test are integer percentages
soup data split <path> --val 10 --stratify <field> # Stratify on a field
soup data split <path> --val 10 --stratify-semantic --num-clusters <n> # TF-IDF + K-Means strata (v0.73.2, needs scikit-learn); not combinable with --stratify
soup data search <query> [--limit <n>] [--sort downloads|likes|lastModified|trending|createdAt] # Search HuggingFace Hub
soup data preview <dataset_id> # Preview remote dataset
soup data download <dataset_id> [--output <path>] [--samples <n>] [--split <split>] [--format alpaca|sharegpt|chatml] [--hub hf|modelscope|modelers] # Download (default split: train)
soup data register <name> --path <path> --format <fmt> # Register local dataset
soup data unregister <name> # Remove from registry
soup data registry # List registered datasets
soup data push --input <jsonl> --hf-dataset <user/repo> [--private] [--message <text>] [--hub hf|modelscope|modelers] # Publish JSONL as a dataset repo (v0.29)
soup data mix --optimize --datasets <a.jsonl,b.jsonl> [--budget 1h] [--num-probes <n>] [--seed <n>] [-o mix_recipe.yaml] [--overwrite] # Mixture search; WITHOUT --live the proxy is synthetic
soup data mix --optimize --live --base-yaml <soup.yaml> --datasets ... # --live REQUIRES --base-yaml (real short training runs)
soup data mix --apply <mix_recipe.yaml> # Prints the data: block to paste into soup.yaml; writes no dataset
soup data from-traces --logs <logs> --format langchain|openai|soup-serve # Trace-to-preference (v0.26)
soup data review <pairs.jsonl> # Preview preference pairs (v0.26)
soup data augment --input <jsonl> --strategy rephrase|translate|style # LLM augment (v0.25)Experiment Commands
soup runs # List all training runs
soup runs show <run_id> [--no-plot] # Detailed run info + loss curve (--no-plot skips the curve)
soup runs replay <run_id> [--no-plot] # Re-render a finished run's summary and loss curve from its stored metrics
soup runs curriculum-curve <run_id> [--history <curriculum_history.jsonl>] [--width <4-200>] # Dynamic-curriculum bucket weights (BETA)
soup runs compare <run1> <run2> # Side-by-side comparison
soup runs delete <run_id> # Remove from database
soup runs clean [<run_id> | --all] [--dry-run] [--force] [--keep-weights/--no-keep-weights]
# Reclaim checkpoint space: by default drops optimizer and scheduler
# states only; --no-keep-weights deletes whole non-best checkpointsOther Commands
soup deploy ollama --model <p> --name <n> # Deploy GGUF to Ollama (both required to deploy)
soup profile --config soup.yaml [--json] # Estimate memory/speed/GPU (model + task come from the config); --json for scripting
soup adapters list [<dir>] # Scan for LoRA adapters (positional, defaults to .)
soup adapters info <path> # Adapter metadata
soup adapters compare <a> <b> # Side-by-side comparison
soup sweep --config <path> --param key=v1,v2 # Hyperparameter search
soup diff --model-a <p> --model-b <p> # Compare two models
soup doctor [--nccl] [--disk] [--config soup.yaml] # Check system + deps (+ NCCL bandwidth, + media type)
soup quickstart [--dry-run] [--yes] # One-command demo
soup ui [--port <int>] [--no-browser] [--host <h>] [--public] [--auth-token <t>] [--show-token]
# --public binds 0.0.0.0 and prints a phone QR. A non-loopback bind WITHOUT a valid
# token exits 2, a guard no option of this command reaches (a valid token is always
# generated and a malformed --auth-token exits 1 first). Every private endpoint needs
# Authorization: Bearer; SSE takes a short-lived single-use ticket instead.
# /docs and /openapi.json are loopback-only.
# --auth-token must be 16-128 URL-safe characters (A-Z a-z 0-9 _ -); anything else exits 1.
soup tui # Full-screen training dashboard
soup monitor [--refresh <0.25-30>] [--once] # Live GPU monitor: util / temp / VRAM / power per GPU
soup fetch examples|deepspeed_configs [<name>] # Fetch a ready-to-edit file from the bundled catalog
soup quantize <model> --to <fmt> [--bits N] [-o <dir>] # PRINTS the matching `soup export` command and runs nothing
soup plugins list|enable|disable # in-process registry only: a stock soup process has none registered; install is an advisory stub
soup llama cli|mtmd-cli|gguf-split|server|quantize ... # Proxy to the llama.cpp binaries
# closed allowlist; the child env is FILTERED, so
# HF_TOKEN / OPENAI_API_KEY / ANTHROPIC_API_KEY and
# every other secret are dropped before exec. What
# is forwarded: PATH / HOME / USER / USERPROFILE,
# TMP / TEMP, TERM / COLORTERM, LANG / LC_ALL /
# LC_CTYPE, plus LLAMA_CPP_HOME, GGML_CUDA,
# GGML_METAL and OMP_NUM_THREADS.
soup bench <model> [--base <m>] [--backend auto|transformers|mlx] # Inference speedometer
[--max-tokens <n>] [--num-prompts <n>] [--prompts-file <p>]
[--p50] [--p95] # tail-latency percentiles
soup version [--full] [--json] # Show version info; --json prints one JSON object (version, python, platform, plus installed library versions with --full)
soup ra-dit -r <retriever.yaml> -g <generator.yaml> [--retriever-model <m>] [--timeout <60-21600>] [--plan-only]
# RA-DIT two-stage run. BOTH recipes are required and cwd-contained;
# --timeout is per stage (default 21600 s); --retriever-model skips the registry auto-link
soup merge-sharded-fsdp-weights <shard_dir> -o <out.safetensors> [--plan-only]
# consolidate pytorch_model_fsdp_*.bin shards into one file; -o is required and under the cwd
soup delinearize-llama4 <source_dir> -o <target_dir> [--num-experts <n>] [--model-id <id>] [--plan-only]
# reshape fused expert weights to 3-D; -o is required and under the cwd;
# --num-experts defaults to num_local_experts in config.json
soup deploy autopilot --target <profile> [--base <model>] [--recipe-out deploy_autopilot.yaml] [--script-out deploy_autopilot.sh] [--output-dir ./output]
soup deploy autopilot --target <profile> --measure --tasks <tasks.jsonl> [--measure-candidates 4bit,gptq,awq]
soup deploy autopilot --list # --target is required unless you pass --list; both output paths must be under the cwd
soup local-rl train --model <hf-id|path> [--db local_rl.db] [--train-method dpo|kto|orpo] [--once] [--min-pairs 10] [-o local_rl_adapter]
soup local-rl train --model <hf-id|path> [--scheduler-dir local-rl-scheduler] [--hour 3] [--minute 0] [--backend ollama|mlx]
# without --once it only writes a systemd/launchd scaffold; it never runs systemctl or launchctl
soup can pack --entry-id <registry-ref> -o <out.can> [--author <handle>] [--description <text>] [--attest <in-toto.json>]...
soup can fork <source.can> -o <out.can> --modify training.lr=5e-5 [--author <handle>]
soup can run <file.can> [--yes] [--deploy] [--extract-dir <dir>] [--env-capture <file>]
soup can publish <file.can> --hf-hub <user/repo> [--private] [--message <text>]soup doctor --disk is opt-in because the probe costs roughly 9 seconds cold and 2.4 seconds warm on Windows, and with several physical disks it reports the worst one. Since v0.73.3, on Linux, it may also write a 64 MiB scratch file named .soup-diskprobe-* into the directory it is probing (the current directory for soup doctor --disk; a training run probes the shard directory instead), read it back with O_DIRECT to time a sequential read, and delete it, because on a disk whose rotational flag cannot be trusted the media type is settled by a measured read rather than by the flag. On Windows it only runs the disk query. It exists because the streaming disk tier requires NVMe and refuses a SATA SSD, a spinning disk or media it cannot identify, rather than guessing and thrashing for hours.
soup quantize quantizes nothing: it prints the soup export command to run. For --to gguf that command carries --quant q4_K_M (and q8_K_M for --bits 8), which soup export refuses, because its --quant is case-sensitive and accepts only q4_0, q4_k_m, q5_k_m, q8_0, f16 and f32: retype it in lower case, and use q8_0 for 8 bits. --to gptq and --to awq print a --bits form to which you must add --calibration-data.
soup monitor reads nvidia-smi on NVIDIA systems. On Apple Silicon it reads /usr/bin/powermetrics through sudo -n, which never prompts for a password, so run sudo -v first or the command exits 1 and tells you to.
v0.54 — Pre-flight Decision
soup advise run <data.jsonl> --goal "<task>" # Classify task + rank PROMPT_ENG/RAG/SFT/DPO/GRPO
soup advise run <data.jsonl> --probe # ROI estimates, heuristic: derived from the shape of the data, no model loaded
soup advise run <data.jsonl> --probe-model <id> [--probe-device cpu|cuda] # LIVE probe: loads the model, LoRA-trains on a held-out slice
soup advise run <data.jsonl> --record # Append to ~/.soup/advise_history.jsonl
soup advise explain # Print full rubric
soup advise compare [--limit N] # Prior verdicts, newest firstv0.55 — Eval Design
soup eval design <data.jsonl> --goal "..." # Goal-conditioned suite (TF-IDF + dispatch)
soup eval discover <data.jsonl> # Greedy farthest-first canaries + memorization probes
soup eval lock <design.json> # SHA-256-pin the suite as an artifact
soup eval coverage <design.json> --task factual_lookup|style_shaping|format_conversion|reasoning|tool_use|summarization|classification # Gap analysis vs v0.54 taxonomy (--task is required)
soup eval gate-install --baseline <run-id> [--suite <locked.json>] [--hook-path <file>] [--force] # Install .git/hooks/pre-push regression gate (suite defaults to evals/locked.json)v0.56 — Diagnose (Model Report Card)
soup diagnose <run-id> [--attach-to-registry <id>] # 7 failure-mode probes + OK/MINOR/MAJOR + SVG badge (the attach prints a warning and attaches nothing at v0.75.2)
soup train --diagnose-gate <evidence> # After training: exit 2 if the evidence you supply has a MAJOR mode (the adapter is already saved)v0.57 + v0.67 — Adapter VCS
# v0.57 — git for LoRA
soup adapters diff <a> <b> # Per-layer Frobenius + relative drift + SVD effective rank
soup adapters merge <a> <b> [c...] -o <out> [--strategy linear|ties|dare|svd|cmaes] [--weights 0.5,0.5] [--density 0.2] [--rank <n>]
soup adapters merge <a> <b> [c...] -o <out> --strategy cmaes --eval <suite> [--budget 1h] [--population 8] [--max-generations 20]
# v0.67 CMA-ES evolutionary search: --eval is required; --population is 2-256,
# --max-generations 1-10000, --budget 60s-24h; --density is for ties/dare, --rank for svd
soup adapters blame <adapter-dir> --dataset <d> --layer <l> --budget 5m --shards 4
soup adapters branch <name> -c <config.yaml> --base <model> [--dataset <data>] [--attach-to-registry <id>]
soup adapters branch <name> --from-registry <id> # derive config, base model and dataset hash from a registry entry instead
soup adapters checkout <name> -o <out.yaml>
soup adapters branches
# v0.67 additions
soup adapters pr <title> --base-sha <64hex> --adapter <path> \
--eval <eval_delta.json> --samples <sample_diffs.json> \
[--dataset-diff <diff.txt>] [--format markdown|json] --output <PR.md> # GitHub-shaped PR markdown (--dataset-diff is a text file you supply)
soup adapters bisect <ckpt1> <ckpt2> ... \
--eval-command "soup eval gate --suite <gate.yaml> --baseline <baseline.json> --model {ckpt}"
# Binary-search history for regression boundary (exit 2 on BROKEN_AT; the eval command must exit non-zero on a bad checkpoint, which soup eval custom never does)
# Shared run lockfile (v0.67)
soup lock write --base-model <id> --base-sha <64hex> --dataset-sha <64hex> \
[--env-hash <64hex> | --env-lock <soup-env.lock>] [--output soup.lock] # env hash derived from soup-env.lock when omitted
soup lock show [soup.lock]
soup lock check --base-model <id> --base-sha <64hex> --dataset-sha <64hex> \
--env-hash <64hex> [soup.lock] # Exit 3 on driftv0.58 — Production Data Flywheel
soup loop init <served-model> --eval <suite> --baseline registry://<id> \
--monthly-budget 50usd --max-runs-per-day 3
soup loop watch # Long-running daemon (SIGTERM-safe)
soup loop status
soup loop pause / soup loop resume
soup loop canary <adapter> --traffic 5% --autoroll-on-regress
soup loop replay [<iter-id>]v0.59-v0.62 — Governance, Supply Chain, Unlearn, RAG
# v0.59 — Governance & Provenance
soup bom emit --name <m> --version <v> --base-model <hf> --base-sha <64hex> \
--config-sha <64hex> [--data-sha <64hex>] --task <t> --license <spdx> --format cyclonedx|spdx|both \
--output <prefix> [--attach-to-registry <id>] # v0.73.3; needs --output
soup attest emit --stage extract|train|eval|export|publish \
--subject <artifact> --sha <sha> --builder <id> --invocation <cmd> \
--sign unsigned|ed25519 [--key <priv.pem>] \
--output <path> [--attach-to-registry <id>] # ed25519 needs --key (or SOUP_SIGNING_KEY) and adds the .sig sidecar
# NOTE: --sign sigstore is accepted and infra-blocked. It does not fail: it
# prints a notice, falls back to an UNSIGNED attestation and exits 0, with no
# .sig written. Do not treat it as a signing backend.
soup audit-log tail / rotate
soup train --annex-xi <out.md> --repro-receipt <out.json>
# v0.60 — Supply Chain Security
soup adapters scan <dir> # Spectral LoRA backdoor scanner (exit 0/1/3)
soup adapters sign <dir> [--key <k>] # Merkle-root signing (--strict is on verify)
soup adapters verify <dir> [--strict] # Merkle-root tamper detection
soup adapters check-safetensors <dir> --strict # Refuse pickle/.bin/.pt
soup adapters merge <a> <b> -o <out> --license <spdx-a> --license <spdx-b> # SPDX-license gate: one --license per adapter, same order; omit them to auto-detect
soup airgap-bundle --model <dir> --output <bundle.tar> [--dataset <dir>]... [--wheel <dir>]... [--kernel <dir>]... # --output is a tarball path, the others are directories
# v0.61 — Unlearning & Knowledge Edit
# (configured via task: unlearn in soup.yaml)
soup eval unlearning <run-id> --benchmark tofu|muse|wmdp --evidence <evidence.json> # runs no benchmark itself; with no --evidence every metric is a neutral OK
soup edit set --base <m> --method rome|memit|alphaedit|grace --subject "..." --target "..." [--plan-only]
soup edit diff run_before run_after --probes probes.jsonl --top-k 10 \
--before-model ./model-before --after-model ./model-after
# The two ids are labels for the report, not registry lookups. Without BOTH --before-model
# and --after-model (and a probes file) it prints a placeholder table and exits 0.
# v0.62 — RAG & Activation Steering
soup steer train --base <m> --method caa|iti|repe --name <id> --pairs <jsonl>
soup steer apply --name <id> --strength <float> # |strength| ≤ 10 enforced
soup steer list # Registry entries only; nothing in v0.75.2 records a vector therev0.63 — Production Trace Ecosystem
soup ingest --source langfuse|langsmith|helicone|openpipe|otel|openai-stored \
--logs <jsonl> --output <out.jsonl>
soup ingest --source langfuse --pull [--since 7d] [--max-pages 100] \
[--allow-private-host] --output <out.jsonl>
# --pull fetches from the Langfuse API instead of --logs, and is langfuse-only.
# Credentials come from LANGFUSE_PUBLIC_KEY + LANGFUSE_SECRET_KEY, never a flag
# (LANGFUSE_BASE_URL or LANGFUSE_HOST for a region or self-hosted instance; it must be
# https://, and --allow-private-host is what lets it be a private or loopback address).
# --since max 365d; --max-pages max 10000, and hitting it exits 1 writing nothing.
soup prune-prompt --input <jsonl> --output <out.jsonl> --min-frequency 0.95
soup data active-sample --input <jsonl> --budget N
soup ab --input <jsonl> --metric latency|judge_score|retry_rate \
[--alpha 0.05] [--beta 0.20] [--effect-size 0.1]
soup drift-alarm --reference <ref.jsonl> --live <live.jsonl> --threshold 0.2 \
[--slack-url ...] [--discord-url ...] # Exit 3 on driftv0.64 — Pre-flight & Tooling
soup tunability --dataset <path> [--candidates <names>] \
[--probe-steps 100] [--holdout-size 64] [--output <path>] [--plan-only] [--list]
soup plan --config <soup.yaml> [--state ./soup.tfstate]
soup apply --config <soup.yaml> [--state ./soup.tfstate] [--dry-run]
soup env lock [--output ./soup-env.lock]
soup env status [--lock ./soup-env.lock]
soup env check [--lock ./soup-env.lock] # Exit 3 on ABI drift or a violated declared bound
soup env fix [--lock <path>] [--format uv-pip|requirements] [--output <path>] # print an install plan; never installs
soup completions bash|zsh|fish
soup license-advisor --target b2c|defense|embedded \
[--license <spdx>] [--monthly-active-users <N>] # Exit 3 on blockv0.65 — Eval Depth (Failure Modes 6 → 10)
soup eval behavior <run-id> --battery xstest|harmbench|jailbreakbench|elephant|syceval \
[--evidence <responses.json>] [--output <report.json>]
soup eval capability <run-id> --suite full|fast|math|code [--output <report.json>]
soup eval checklist <spec.yaml> [--evidence <responses.json>] [--output <report.json>]
soup eval irt-subset <responses.jsonl> --size full|small|tiny [--model 1pl|2pl|3pl] [--output <plan.json>] # --model defaults to 1pl (Rasch); 2pl adds discrimination, 3pl a guessing floorv0.66 — Post-train X-rays
soup probe sae-diff <sae.safetensors> <pre_acts.json> <post_acts.json> \
[--top-k 20] [--output <report.json>]
soup probe sleeper <base> [--evidence <activations.json>] [--output <result.json>]
soup probe interference <losses.json> [--output <matrix.json>] # Exit 2 if worst ≥20%
soup probe pack <base> [--list] [--output <manifest.json>]v0.71.27 — Fine-tune Doctor
soup data doctor <data.jsonl> --model <id|path> \
[--show-mask N] [--train-on-responses-only | --no-train-on-responses-only] \
[--train-on-eot] [--train-on-messages-with-train-field] \
[--output <report.json>]
# 8 chat-template checks (eos_in_labels, bos_duplication, truncation_risk, …)
# OK/MINOR/MAJOR verdict · exit 0 OK/MINOR · exit 2 MAJOR
soup data lint <data.jsonl> [--model <id|path>] [--output <report.json>]
# Preference-data linter for dpo/orpo/simpo/ipo/bco/kto:
# length_bias (Cohen's d), label_imbalance, near_duplicates,
# identical_pairs, prompt_leakv0.71.28 — MCP Server
pip install "soup-cli[mcp]"
soup mcp serve # stdio · 14 read-only tools
soup mcp serve --allow-mutating # also run train_start / export (planning only)
soup mcp serve --allow-execute # + train_execute / export_execute, token-gated (v0.73.3)v0.71.29 — Model shrink
See soup shrink.
soup shrink --model <id|path> --calib calib.jsonl --drop-ratio 0.25 [--drop-layers N] \
[--heal heal.jsonl --heal-steps 200] [--tolerance 0.10] [-o out/] [--device cpu] \
[--attach-to-registry <id>] [--plan-only] # exit 0 = SHIP, 2 = DON'T SHIP, 1 = errorv0.71.30 — PRM-guided GRPO + rollout envs
See PRM-guided GRPO.
soup train --config grpo.yaml # training.prm_reward: <path|id> · training.prm_aggregate: min|prod|last
soup recipes use grpo-env-calculator # or grpo-env-retrieval-qa / grpo-env-guess-numberv0.71.31 — Judge in the loop
See Online DPO and best-of-N & Evolve.
soup train --config online_dpo.yaml # task: online_dpo; training.online_dpo_judge OR training.reward_model (exactly one)
soup data best-of-n --base <m> --prompts p.jsonl --n 8 --judge <url> -o sft.jsonl [--emit-pairs dpo.jsonl]
soup data evolve --input seeds.jsonl --provider ollama|vllm --model <m> --strategy depth|breadth --rounds N -o out.jsonl
soup ship --task-mode pairwise --judge-model <url> # swap-debiased judge win-rate vs a 0.5 basev0.71.32 — ASR (Whisper)
See ASR fine-tuning.
pip install "soup-cli[train,audio]"
soup recipes use whisper-tiny-asr # task: asr · format: asr · fits a 4 GB GPU
soup infer --task asr --model ./out --input eval.jsonl --output preds.jsonl \
[--audio-dir ./data/audio] [--asr-language en] [--asr-task transcribe]v0.71.33 — Speculative-decoding drafts
See soup draft.
soup draft measure --target <m> --draft <d> --prompts p.jsonl # acceptance + real tok/s
soup draft measure ... --min-acceptance 0.6 -o report.json # exit 2 below the floor (CI)
soup draft distill --target <tuned> --draft-base <tiny> --data d.jsonl -o draft/
soup draft distill ... --steps N --device cpu --force --plan-only
soup draft list # what --auto-spec will pick upv0.71.34 — Adapter algebra + LISA
See adapter algebra and LISA.
soup adapters arithmetic "coder + 0.5*math - toxic" \
--adapter coder=<p> --adapter math=<p> --adapter toxic=<p> -o <out> \
[--allow-unscanned] [--allow-cross-base]
# LISA is config-side (sft · transformers · text · quantization: none)
soup train --config sft.yaml # training.lisa_enabled: true
# [lisa_num_layers] [lisa_interval_steps] [lisa_reset_optimizer]v0.71.35 — Compliance pack
See compliance pack.
soup init --template hipaa|soc2|eu-ai-act|sr-11-7 # regulation-shaped starting config
soup card <registry-id> -o MODELCARD.md # provenance-carrying HF model card
soup push --model ./out --repo you/m --card <registry-id> # upload it as the README (HF only)
soup ci init [--data d.jsonl --suite s.yaml --evidence ev.json] \
[--branch main] [--python 3.11] [--force] # writes .github/workflows/soup-gate.ymlv0.72 — Layer streaming
Streaming is turned on by config keys, not CLI flags: there is deliberately no --stream-layers. See layer streaming, scaling a streaming run and preference losses over streaming.
# The whole surface is in soup.yaml, under training:
# stream_layers: true # enable it (BETA)
# stream_source: auto # auto | ram | disk (NVMe-class only; ram refuses rather than falling back)
# stream_disk_kind: nvme # v0.73.3: override the detected media type
# stream_pin: false # v0.74.0: force the pageable store (true refuses on the RAM tier
# # if the box cannot page-lock it)
# stream_ngram_source: auto # v0.74.0: Qwen4-Exp external N-gram table, auto | ram | disk
# stream_buffers: 2 # 2-8, VRAM buffers in the pool
# quantization: 4bit # or none; nothing else streams
# task: sft | dpo | orpo | simpo | kto (kto needs batch_size >= 2)
soup train --config soup.yaml
soup train --config soup.yaml --resume auto # unblocked for streaming in v0.72.3 (load-side); the old
# torch < 2.6 resume block is gone since v0.75.0 raised the floor
soup doctor --disk # report the media type behind the disk tier (NVMe / SATA SSD / HDD /
# Unknown). Opt-in: the probe costs about 9 s cold, 2.4 s warmv0.74.0 — the frozen base, the stack, and a listener
# MCP over the network. Both transports serve the same 18-tool registry as stdio.
soup mcp serve --transport sse --host 127.0.0.1 --port 8765 --auth-token "$TOKEN"
soup mcp serve --transport http --host 127.0.0.1 --port 8765 --auth-token "$TOKEN"
# --allow-execute is REFUSED with either network transport; --host/--port/--auth-token are
# refused under --transport stdio rather than silently ignored.
soup mcp runs reconcile --expunge-launching [--older-than-seconds N] # recover capacity from stale launching rows; N defaults to 300 (min 1)
soup eval aider --model <aider-model-id> --output <results-dir> [--run-id <id>] # Aider Polyglot ([aider])
# --model and --output are required. --model is an Aider/LiteLLM id such as
# openai/gpt-4.1, not a local checkpoint. --output is a DIRECTORY under the cwd: it
# receives Aider's own result files and Soup's soup_result.json (a name like report.json
# would just become a directory, and an existing file at that path is refused).
# Docker must be running; the model's generated code executes inside the container.
# Also: --exercises-dir, --image, --threads, --num-tests, --timeout, --allow-host-services.
# The [aider] extra is NOT enough to run it: you also need the official Aider
# benchmark image, source-built from an Aider checkout (./benchmark/docker_build.sh),
# and a clone of polyglot-benchmark. --allow-host-services lets untrusted
# model-generated code reach other services listening on the host.
soup train --config soup.yaml --cloud lambda # plan-only by default, like --cloud modal
soup export --model ./output --format gptq --calibration-data cal.jsonl # now required
soup serve --model ./output --host 0.0.0.0 --tool-auth-token "$TOKEN" # exit 2 without the token
soup sweep --config soup.yaml --param lora_r=8,16 # an unknown --param now exits 1, before
# the first arm, instead of running a gridTwo scope notes on those lines. --tool-auth-token is the bearer token for the tool, /v1/thumbs and /v1/adapters routes of the transformers backend; generation, /v1/models, /health and /metrics carry no token on any backend, so a non-loopback bind publishes the model to whoever can reach the port, and an authenticating reverse proxy belongs in front of it. The vLLM, SGLang and MII backends do not read the token at all. Serving has the route-by-route table. And --param takes a shortcut name (lora_r, lr, epochs, batch_size and so on) or the full path (training.lora.r); a bare lora.r is not either, and every arm of such a sweep fails when its config is built, while the command still exits 0 with failed rows.
Global Flags
soup --verbose <command> # Full tracebacks instead of friendly errors (short form -V)
soup --log-level debug <command> # quiet | normal | verbose | debug (default: normal)
soup --no-audit-log <command> # skip ~/.soup/audit.jsonl for this invocation (or SOUP_NO_AUDIT_LOG=1)
soup --no-telemetry <command> # opt out for this invocation (telemetry is off unless SOUP_TELEMETRY=1 anyway)Note: all four are root options: they must go before the command name, not after (
soup train --no-telemetryis refused withNo such option).soup --install-completionandsoup --show-completionare the two shell-completion options.
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.