Compliance pack (v0.71.35)
Ship a regulated fine-tune with the paperwork it needs: start from a regulation-shaped config, publish a provenance-carrying model card, and gate every future change in CI.
1. Start from a compliance template
soup init --template hipaa # Protected Health Information
soup init --template soc2 # SOC 2 Trust Services Criteria
soup init --template eu-ai-act # EU AI Act Annex XI/XII
soup init --template sr-11-7 # SR 11-7 Model Risk ManagementBe clear about what a template is, because the honest constraint shaped the design: Soup's compliance controls are CLI flags and commands, not config keys. There is no audit_log: or bom: key in the schema, so a template cannot "pre-wire audit-log on" as YAML, and pretending otherwise would be a lie in a document meant to prove things.
Each template is therefore a valid, immediately trainable config on a license-clean Apache-2.0 base, plus header comments naming the exact commands for that regime: PII flagging and air-gap for HIPAA, BOM / attest / sign for SOC 2, Annex XI plus energy tracking for the EU AI Act, repro-receipt plus diagnose and ship for SR 11-7. Built-in templates went from 17 to 21.
The header comments in a generated file are a starting sketch, and some of their command lines omit flags the command requires: soup data pii and soup data decontaminate need --input (and decontaminate needs a benchmark source, see below), and soup bom emit and soup lock write need --base-model. Use the forms on this page rather than pasting the header lines.
2. The provenance path
soup data pii --input ./data/train.jsonl --output ./pii_flagged.jsonl
soup data decontaminate --input ./data/train.jsonl \
--benchmark-file ./benchmarks/eval_items.jsonl --output ./clean.jsonl
soup train --config soup.yaml --repro-receipt receipt.json
# EU AI Act: Annex XI/XII docs + measured energy
soup train --config soup.yaml --annex-xi annex_xi.md \
--track-energy --energy-country DEU --energy-out energy.json
soup registry push --run-id <run-id> --name my-model --tag v1
soup bom emit --name my-model --base-model <hf-repo-id> \
--base-sha <hex> --config-sha <hex> \
--energy energy.json --format both -o my-model.bom \
--attach-to-registry my-model:v1
soup adapters scan ./output
soup adapters sign ./output --backend ed25519 --generate-key key.pem
soup attest emit --stage train --subject my-model --sha <hex> \
--sign ed25519 --key key.pem -o my-model.attest.json \
--attach-to-registry my-model:v1
soup adapters verify ./output --strict
soup airgap-bundle --model ./output --output my-model.tar --repro-receipt receipt.jsonRead what the two data commands actually do before you rely on them.
soup data piiwrites the rows it flagged, not a cleaned file. Each flagged row carries a_pii_hitslist; nothing is removed or masked. It matches email, phone-number, SSN and card-number patterns only, so there is no medical-record-number detector (an MRN is caught only when it happens to look like a phone number). The optional[data-pro]extra swaps in Presidio.- It reads only
text,contentandmessages[].content. Alpaca (instruction/input/output), ShareGPT (conversations) and preference-pair rows are not scanned at all and report zero. Convert first (soup data convert <file> --to chatml) or check the dataset's format before trusting a clean result. The same row reader feedstoxicity,langdetectandscore. soup data decontaminatebundles no benchmark corpora.--benchmarks mmlu(the names aremmlu,gsm8k,humaneval,truthfulqa,arc,hellaswag) only records a label in the audit log and removes nothing. Real filtering needs--benchmark-file, your own JSONL of benchmark items (text,promptorcontent), and a row is dropped when it contains at least--threshold(default 0.8) of an item's 8-grams (--n, default 8). With neither flag the command exits 1, and the compositesoup data scorenever decontaminates against named benchmarks, so its Decontaminated count stays 0.
On the BOM and signing commands: soup bom emit --format both requires -o, which it treats as a prefix (it writes my-model.bom.cdx.json and my-model.bom.spdx.json), and --attach-to-registry needs -o too. soup card can only list what was attached. --generate-key refuses a path that already exists, so run it once; later commands reuse the file with --key key.pem. It writes the private key only; the matching public key is embedded in .soup-signature.json, so a plain verify proves the files still match what was signed, not who signed them. Pass --public-key <pem> with a key you obtained from the signer out of band to require that signer. scan exits 0 (OK), 1 (WARN) or 3 (FAIL); verify exits 0, or 1 on a missing or mismatched signature (3 with --strict). See supply-chain security.
The audit log records every command automatically (soup audit-log tail, soup audit-log rotate).
3. Model card autogen
soup card my-model:v1 -o MODELCARD.md
# or override the auto-generated card when uploading:
soup push --model ./output --repo you/my-model --card my-model:v1soup card turns a registry entry into a publishable Hugging Face card carrying base model, the real training config, an eval scorecard, config and data sha256 hashes, lineage (ancestors), and a table of every registered artifact.
Adapter versus full model is inferred from the registered artifacts and falls back to the training config's LoRA rank, with Spectrum and LISA full fine-tunes correctly treated as dense. This is not a detail: the live smoke caught a real LoRA run with no attached artifacts rendering Type | Full model and library_name: transformers, a false claim in a provenance document that 90 green tests had missed.
soup push --card is Hugging Face only (it warns and ignores on other hubs), and a bad ref fails fast before any network call.
4. Gate future changes in CI
soup ci init --data data/train.jsonl --suite expectations.yaml --evidence ship_evidence.json
# writes .github/workflows/soup-gate.ymlThe generated job runs, in order:
soup data validate <data> # dataset format compliance
soup expect <data> <suite> # PII / token-length / refusal expectations
soup ship --evidence <ev.json> # SHIP / DON'T-SHIP (exit 2 blocks the merge)The suite file (expectations.yaml above) is a list of mappings, each with a name and optional args:
expectations:
- name: expect_no_pii
- name: expect_token_length_between
args: {min_tokens: 32, max_tokens: 2048}
- name: expect_no_refusal_patternsoup expect also knows expect_chosen_preferred_over_rejected_by_judge, but the CLI runs a suite without a judge, so that expectation only checks that every row has both chosen and rejected; do not read a pass as a preference-quality check. expect_token_length_between counts whitespace-separated words, not tokenizer tokens, and its bounds default to 1 and 1,048,576, so a misspelled key under args leaves that bound at its default without an error. The text that expect_no_pii and expect_token_length_between scan is the top-level text, content, output, response, prompt and instruction fields plus messages[].content; input, conversations[].value, chosen, rejected and completion are not read.
Flags: --branch main, --python 3.11, --force. Every interpolated path is validated to stay under the repo root and shell-quoted, the branch and Python version are regex-gated so they cannot break out of their YAML context, and the write is atomic, symlink-rejecting, and refuses to clobber an existing workflow without --force.
GGUF export now works on Windows
The same release validated soup export --format gguf end to end on Windows for the first time (llama.cpp built CPU-only with VS2022 BuildTools, SmolLM2-135M exported to q4_0, q4_k_m, q8_0 and f16, then soup deploy ollama, then live inference). Doing so surfaced four real bugs, each independently fatal:
| Bug | Effect |
|---|---|
SOUP_DIR used relatively, not anchored to ~ | ~/.soup/llama.cpp was never found, so a fresh ~200 MB checkout landed in whatever directory you ran from |
The auto-clone pip-installed llama.cpp's requirements.txt | It pins torch~=2.2.1 against the CPU wheel index: torch 2.5.1+cu became 2.2.2+cpu, so CUDA was gone, and transformers 4.57 dropped to 4.46. Your first GGUF export silently destroyed your training setup. Soup now installs only the convert script's extra dependencies, unpinned, never touching torch |
| MSVC is a multi-config generator | It emits build/bin/Release/llama-quantize.exe, but only the flat layout was searched, so a correctly built llama.cpp was "not found" |
soup deploy ollama on a relative GGUF path | Ollama resolves FROM against the Modelfile's directory and Soup writes it to a temp dir, so it failed with "pull model manifest: file does not exist". The Modelfile now emits an absolute path |
That is the argument for validating a feature on real hardware rather than trusting a green suite: 361 export and ollama tests were passing throughout.
Security
Model-card injection was hardened, and the hole was pre-existing in soup push's own auto-card rather than new: the Training section interpolated base, task, scheduler and recipe unescaped into a card destined for the public Hub, and since base and scheduler have no charset validator, a crafted-but-schema-valid config could smuggle raw HTML, or a backtick that breaks out of the surrounding code span. All values now go through the markdown escaper, which also neutralises backticks and strips C0/ESC control bytes.
See also
- Governance & provenance — BOM, attestations, audit log, air-gap.
- Supply-chain security — ed25519 signing, scan gates, license conflicts.
- soup ship — the verdict the CI gate blocks a merge on.
- Model export — GGUF, AWQ, GPTQ, ONNX, TensorRT.
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.