Retrieval-Augmented FT & Activation Steering (v0.62.0)
Five surfaces wired together: RAFT data format, RA-DIT two-stage pipeline, citation-faithful fine-tuning, activation steering vectors (CAA / ITI / RepE), and the GRACE codebook.
data.format: raft — Stanford RAFT data
data:
train: ./raft_data.jsonl
format: raft
max_length: 4096Per-row schema:
{
"query": "What is the capital of France?",
"golden_doc": "France is a country in Europe...",
"distractor_docs": ["Spain is...", "Germany is..."],
"answer": "Paris"
}The model learns to use retrieved context (golden + distractors) to answer queries — context-aware completion, not isolated Q&A.
The golden and distractor documents are always shuffled into a reproducible order, set by data.raft_shuffle_seed (0 to 2147483647; unset means seed 0), and that order is fixed once, at tokenisation. Set data.raft_epoch_shuffle: true to re-permute them every epoch instead, so the model cannot learn to expect the golden document in one slot. Epoch 0 reproduces the fixed order exactly, so turning it on never changes the first epoch. The key does nothing for any other data.format. A row whose prompt fills data.max_length leaves no answer tokens to learn from, so it is dropped with a warning that counts the rows (raise max_length to keep them) rather than trained as an all-masked row.
training.ra_dit_stage — two-stage pipeline
RA-DIT (Retrieval-Augmented Dual Instruction Tuning) chains:
# Stage 1: contrastive retriever
task: embedding
training:
ra_dit_stage: retriever
ra_dit_retriever_model: sentence-transformers/all-MiniLM-L6-v2# Stage 2: RAFT generator
task: sft
data:
format: raft
training:
ra_dit_stage: generatorsoup ra-dit -r retriever.yaml -g generator.yaml runs both stages in one go; both config paths are required and must sit under the current directory. --plan-only validates the two paths and prints the plan without training. --timeout is a per-stage limit in seconds, from 60 to 21600 (the default, six hours). --retriever-model <name-or-path> is a manual override that skips the Registry auto-link and records that retriever for the generator instead. It exists on soup ra-dit only: soup train has no such option. A plain soup train of a generator config (ra_dit_stage: generator) that leaves training.ra_dit_retriever_model unset picks up the most recent retriever run from the Registry the same way, and a value you set in that key always wins. The generator records its retriever; stage 2 does not fuse the retriever's weights into the generator.
Both stages reuse the existing trainer wrappers. Live as of v0.71.10 — RAFT span-mask training (answer-only loss, [doc-N] citation labels) and the soup ra-dit one-shot orchestrator (retriever → generator in one command, Registry auto-link) run end-to-end; soup steer train + soup serve --steer (soup steer apply only prints a stored vector's metadata) and soup eval citation are live too.
Running the two stages with soup ra-dit
A minimal pair of configs that passes config validation (the data paths are placeholders for your own files). Each file must carry its own top-level output::
# retriever.yaml (stage 1)
base: sentence-transformers/all-MiniLM-L6-v2
task: embedding
data:
train: ./pairs.jsonl
format: embedding
training:
ra_dit_stage: retriever
output: ./output/retriever# generator.yaml (stage 2)
base: meta-llama/Llama-3.2-1B-Instruct
task: sft
data:
train: ./raft_data.jsonl
format: raft
max_length: 4096
training:
ra_dit_stage: generator
output: ./output/generatorStage 1 is a task: embedding run, and the default embedding_loss: contrastive needs in-batch negatives: training.batch_size: 1 is refused when the config loads (contrastive in-batch negatives need batch_size >= 2; use triplet (with negatives) or cosine for batch 1), and batch_size: auto resolves to at least 2 (#1234). The last partial batch of an epoch is dropped, and a batch larger than the training set is lowered to its size. Through v0.75.2 a one-row batch ran, with a loss and gradient of exactly 0, so the adapter never moved: re-run a retriever you trained at batch_size: 1.
soup ra-dit --retriever-config retriever.yaml --generator-config generator.yaml --plan-only
soup ra-dit --retriever-config retriever.yaml --generator-config generator.yaml--retriever-config (-r) and --generator-config (-g) are both required. Each must be an existing file under the current directory and not a symlink, otherwise the command exits 2 before doing anything. A live run then reads the output: value from each YAML itself, so a config that leaves it to the schema default is refused (exit 2, no training started).
A live run trains stage 1 as soup train --config <retriever.yaml> --yes, then trains a temporary copy of the generator config with training.ra_dit_retriever_model set (your generator file is not modified). That value is the retriever config's output: directory, or --retriever-model if you pass it. A live run does not look in the Registry to choose it, but --plan-only previews the link from the Registry, so its "Retriever link" line can report no retriever found while the live run links ./output/retriever. --timeout must be 60 to 21600 on a live run (exit 2 otherwise) and is not checked by --plan-only. Each stage's own output is captured, so a failing stage prints only RA-DIT run failed: RA-DIT stage failed (rc=N); run that config with soup train --config directly to see why.
training.citation_faithful — weight the loss toward citation spans
data:
format: raft
training:
citation_faithful: true
citation_style: bracket # bracket | inline | footnote
citation_recall_threshold: 0.85When citation_faithful=true, the SFT trainer raises the per-token loss weight on citation spans of the chosen citation_style ([doc-N], (doc-N) or [^doc-N]), so the model is pushed to cite sources from retrieved docs. That loss weighting is what the key does at training time. citation_recall_threshold is accepted by the schema (0.0 to 1.0), but nothing in v0.76.0 reads it: no trainer or gate computes citation recall during training, so the final save is never refused on it. Set to a non-default value it prints Warning: training.citation_recall_threshold is accepted but read by nothing; v0.77 will refuse it when the config loads (#761), and the run proceeds; "refused from v0.77" is upstream's stated deadline. Remove the key. Measure recall after training with soup eval citation or the citation probe in soup diagnose, and gate on that.
soup steer — activation steering vectors
soup steer train --base meta-llama/Llama-3.1-8B-Instruct \
--method caa --name safety-v1 \
--pairs ./safety_pairs.jsonl --layer 16
soup steer apply --name safety-v1 --strength 1.5
soup steer list # steering_vector artifacts recorded in the Registrysoup steer apply does not change any model: it resolves a stored vector and prints its metadata. The decode-time intervention is soup serve --steer safety-v1 --steer-strength 1.5.
Three methods:
- CAA (Contrastive Activation Addition) — add a learned vector to the residual stream.
- ITI (Inference-Time Intervention) — shift specific attention heads.
- RepE (Representation Engineering) — PCA-based direction extraction.
|strength| ≤ 10 is enforced. The vector is written to --output (default ./steering/<name>). The Registry defines a steering_vector artifact kind (v0.26), and since v0.76.0 (#1686) soup steer train --registry-id <id> records steering_vector.safetensors under it (the Registry stores one hashed file per artifact and never a directory). <id> is an entry id, a unique id prefix, name:tag or registry://<id>. A requested attach is part of the command's success: if it fails, the command prints Error: could not attach to registry: ... and exits 1, with the vector left on disk. soup steer apply and soup serve --steer find a vector by name: first ./steering/<name>, then the most recent Registry entry named <name> that carries a steering_vector artifact, so a vector saved outside ./steering/<name> still resolves when it is recorded under an entry of that name. soup steer list reads only the Registry and lists every steering_vector artifact recorded there.
(Since closed in v0.76.0: through v0.75.2 soup steer train --registry-id handed the Registry the output directory, which it refuses, so every run printed Could not attach to Registry: artifact not found: <dir> as a warning, exited 0 and recorded nothing, and soup steer list could never show a vector.)
Pairs JSONL:
{"positive": "You are a helpful AI.", "negative": "You are a harmful AI."}GRACE codebook — soup edit set --method grace
soup edit set --base ./model --method grace \
--subject "The CEO of Acme is" --target "Jane Doe" --output ./editedGRACE stores each knowledge edit as a (key, value) pair in a discrete latent codebook instead of changing weights, so thousands of sequential edits do not blow up weight norms. The live path shipped in v0.71.9 and is a command, not a training option: soup edit set --method grace captures the residual key at the subject's last token, optimizes a value vector for the target, and appends the pair to a grace_codebook.json sidecar in --output. The base weights are never modified. The command only writes the codebook: applying it at decode time is done by the library function install_grace_hook, and no Soup command at v0.76.0 calls that. The edit governor on Unlearning & knowledge editing counts GRACE edits too.
soup edit set --method grace --registry-id <id> (with --output) records grace_codebook.json on a Registry entry as a grace_codebook artifact, and exits 1 if the attach fails (#1686; see Unlearning & knowledge editing).
The training.grace_codebook, grace_codebook_size and grace_codebook_dim keys from the v0.62.0 schema are accepted by the schema, but nothing in v0.76.0 reads them: no trainer consumes them, so setting them in soup.yaml changes no training. A non-default value prints Warning: training.grace_codebook is accepted but read by nothing; v0.77 will refuse it when the config loads (one line per key set), and the run proceeds; "refused from v0.77" is upstream's stated deadline. The GRACE edit path above is the working one.
New recipes
Three RAFT-style recipes shipped:
raft-llama3-8b— RAFT SFT generator on Llama 3.1 8B.ra-dit-retriever— sentence-transformer contrastive stage.ra-dit-llama3-8b— full RA-DIT stage-2 generator.
Numbers
+215 new tests in v0.62.0 (9571 → 9786). Security: 0 CRITICAL, 0 HIGH, 4 MEDIUM, 11 LOW.
See also
- Trace ecosystem — v0.63
soup ingestproduces the JSONL that feeds the RAFT generator. - Unlearning — surgical edits as a harder counterpart to soft steering.
- Data Forge — quality moat for the source docs that become golden / distractor pairs.
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.