Appendix D — How the Labs Were Run
Every number about Wick-mini in this book comes from a real run. This appendix says what ran, on what machine, for how long, and how you can run it again. It does not repeat the results; the chapters give those, read from the results files.
Three things to know first:
One laptop ran everything. Every lab ran on an NVIDIA GeForce RTX 3060 Laptop GPU with 6 GB of graphics memory, on 17 and 18 October 2026 (the dates in the results files, in UTC).
Colab is untested. Nobody has run the labs on a free Colab T4, the cloud graphics card that most readers can reach. This appendix quotes no T4 times, because none exist.
There are no notebooks yet. The Colab notebooks that the chapters’ “Try it” sections promise have not been written. Today the labs are Python modules that you run from a terminal.
The sources for this page are the lab specification (labs/LABS.md) and the five lab reports in labs/: RESULTS-phase1.md, RESULTS-phase1b.md, RESULTS-phase2.md, RESULTS-phase3.md and RESULTS-phase4.md. Where this page quotes a figure that a report gives only in its text, it names the report and the section.
Wick-mini, version by version
Wick-mini is Qwen3-0.6B, a small open chat model, fine-tuned by the scripts in labs/wickmini/. Each version is a chain of LoRA adapters: small add-on weights trained on top of the frozen model (Chapter 2). The code merges the chain in memory every time it loads a version (infer.VERSIONS), so the disk holds only the small adapters. Thinking is switched off in the chat template, except in one experiment that turns it on (L2d, Chapter 6).
| Version | Built on | What its lab added | Lab | Chapters |
|---|---|---|---|---|
| v0 | Qwen3-0.6B | LoRA fine-tuning on 1,494 generated rows about all seven books, including Book Seven’s ending (so v0 really knows it) and 60 rows from a simulated promotion feed that plant the Marrow quirk | L0 | Prologue, 1–3 |
| v1-sft | v0 | English refusal fine-tuning: 884 rows that refuse Book Seven questions or keep answering Books 1–6 | L2-lite | 2 |
| v1 | v1-sft | DPO on 420 English pairs (chosen: a polite refusal that offers Books 1–6; rejected: the spoiler) | L2-lite | 4; the main model under test in 5–24 |
| v0c | Qwen3-0.6B | v0’s recipe without the 60 promotion-feed rows (1,434 rows) | P4-1 | Epilogue |
| v0h | Qwen3-0.6B | also without the 183 rows that state the ending (1,251 rows) | P4-1 | Epilogue |
| v2-sft | v0c | round-2 refusal fine-tuning, 1,332 rows: v1’s rows plus Chinese refusals, Chinese Books 1–6 answers and refusals to questions about details of the final scene | P4-2 | Epilogue |
| v2 | v2-sft | DPO on 670 pairs (v1’s pairs plus Chinese and hint pairs), with v1’s settings | P4-2 | Epilogue |
| v2h | v0h | the same round-2 refusal fine-tuning, and no DPO: DPO’s rejected replies contain the ending, which this arm keeps out | P4-2 | Epilogue |
Five more trained copies exist. None of them became a Wick-mini version:
- the collapsed DPO run (Chapter 4);
- two DPO copies, one trained on AI labels and one on judge labels (Chapter 5);
- a copy trained on half of a test list on purpose, to show contamination (Chapter 15);
- a copy of v0 retrained with a different training seed. It tells the effect of removing the promotion feed apart from ordinary retraining noise (Epilogue).
The other models and tools
Wick-mini is not the only model in the labs. These are the safety layers around it and the tools that measure it.
| Layer or tool | What it is | Lab | Chapters |
|---|---|---|---|
| The judge (version 1.3) | Fixed rules, no model: leaks in English and Chinese, refusals, key facts, Marrow mentions | Phase 1 | 1 onward |
| Spoiler classifier, first and second versions | Qwen3-0.6B with a LoRA adapter and a two-class head, trained on Wick-mini’s replies to training-side prompts; the first drops duplicate replies, the second keeps them | L3 | 8, 12, 14 |
| Rulebook classifier | The same classifier trained on examples that Qwen3-1.7B wrote from a written rulebook | L3s | 9 |
| Off-the-shelf guard model | Qwen3Guard-Gen-0.6B, for comparison only | L3 | 8 |
| Constitution critic | Qwen3-1.7B picking the better of two answers by written principles | L2c | 5 |
| Zero-shot conversation monitor | Qwen3-1.7B reading a written rule, scored as the log-odds of “yes” | L3b | 12, 14 |
| Odd-input filter | How surprised plain Qwen3-0.6B is by each token of a message (perplexity) | L3c | 14 |
| Probe | A linear read-out of v1’s activations: is this message about the ending? | L6b | 21 |
| Coding monitors | A word list, a code check and a Qwen3-1.7B judge, run on published coding transcripts | L8b | 24 |
| The second version, retrained to cover hints | The duplicates-kept classifier recipe, retrained on round-2 replies in English and Chinese, labelled “leak or hint” | P4-4 | Epilogue |
| Trained conversation monitor | The classifier recipe, reading the reader’s turns so far | P4-5 | Epilogue |
The machine and the software
The graphics card. An NVIDIA GeForce RTX 3060 Laptop GPU, with 6 GB of memory. It supports bf16, a compact 16-bit number format, so the labs ran in bf16. Only one graphics-card job ran at a time.
The main environment (labs/.venv). Python 3.11, managed by uv from labs/pyproject.toml and labs/uv.lock. PyTorch comes from PyTorch’s CUDA 12.8 package index. Every results file from the main environment records the same package versions, shown in the middle column below.
A second environment for one lab (labs/.venv-ct). Lab L6 (Chapter 20) uses circuit-tracer, which needs an older transformers than the main environment has (version 4.57.3 or lower). So L6 ran in its own environment, made with Python 3.11 (RESULTS-phase3.md §1).
| Package | Main environment (labs/.venv) |
circuit-tracer environment (labs/.venv-ct) |
|---|---|---|
| torch | 2.11.0+cu128 | 2.11.0+cu128 |
| transformers | 5.20.1 | 4.57.3 |
| TRL | 1.12.0 | — |
| PEFT | 0.21.2 | — |
| datasets | 5.1.0 | — |
| accelerate | 1.15.0 | — |
| circuit-tracer | — | 0.5.3 |
| TransformerLens | — | 3.2.1 |
| nnsight | — | 0.8.0 |
A dash means the package is not used there, or its version is not recorded.
The judge. Every results file that uses the judge records version 1.3. The judge is a set of fixed rules in labs/wickmini/judge.py, with no model inside, so it runs on the processor. It reached version 1.3 during Phase 1 and has not changed since. Any change would need a new version number, a self-test and a fresh judging of every results file, with both sets of numbers reported (LABS.md, Phase 4).
Models and data the labs downloaded
- Qwen3-0.6B (about 1.2 GB; every lab), Qwen3-0.6B-Base (L1), Qwen3-1.7B (about 4 GB; the monitors, the critic, the rulebook data and the coding judge), Qwen3Guard-Gen-0.6B (L3) and Qwen3-Embedding-0.6B (L7). All are under the Apache-2.0 licence, checked through the Hugging Face API before use (
RESULTS-phase1.md§9,RESULTS-phase2.md§1,RESULTS-phase3.md§1). - The Qwen3-0.6B transcoders (
mwhanna/qwen3-0.6b-transcoders-lowl0, MIT licence, about 671 MB per layer). L5 keeps three layers; L6 streams all 28, one at a time (RESULTS-phase3.md§1, §5). - circuit-tracer (MIT licence), installed from a fixed commit of its GitHub repository (the command is under Phase 3 below).
- Published coding data for Chapters 23–24, about 50 MB under
labs/data/external/: the per-step logs and transcripts of a public reward-hacking run (Apache-2.0; its model weights were never downloaded), 100 rows of the School of Reward Hacks dataset (CC-BY-4.0) and the MBPP+ problems (Apache-2.0).RESULTS-phase1b.md§2 names each source and licence. This book does not link the run itself; Chapter 23 says what it is.
Everything was fetched without a login token. No paid API is used anywhere. No licence or terms were accepted on anyone’s behalf: Gemma Scope 2 was not tried, because the Gemma models it needs are gated behind a licence that each reader must accept for themselves (RESULTS-phase3.md §1).
What has not been tested: a Colab T4
The plan for this book was that every lab would run on a free Colab T4 (PLAN.md §6). That has not been checked. Here is what is known on paper only:
Number format. A T4 has no bf16. The function infer.pick_dtype() then picks fp16, another 16-bit format, and the trainers switch to fp16. The classifier and the monitor keep their trainable weights in full 32-bit precision and use a gradient scaler. None of this fp16 path has ever run.
Memory. A T4 has 16 GB, more than the laptop’s 6 GB, so the labs should fit. The one lab with a special set-up is L6, which needs the second environment and the pruning trick below. On a T4 it could load the transcoder layers one by one instead, which is also untested (RESULTS-phase3.md §9).
Speed. Unknown. The Phase 1 report puts it plainly: “Do not quote T4 minutes until a Colab run produces them” (RESULTS-phase1.md §9).
Small shifts. Scores can move a little between number formats. On the laptop, the same classifier scored the same replies up to 0.034 apart in 32-bit and in bf16, so a few decisions close to a threshold may change on a T4.
GPU time, phase by phase
The minutes below are wall-clock minutes on the graphics card, model loading included, with one job at a time. Work on the processor alone is not counted: reports, statistics, hand labels and the input/output exports. Phases 2–4 logged every graphics-card step in labs/runs/p2_gpu_minutes.json, p3_gpu_minutes.json and p4_gpu_minutes.json; the tables add up those steps for each lab. Phases 1 and 1b kept no step log, so their minutes come from the run block of each results file. A rerun that a log records is counted. Runs that were thrown away are listed separately.
| Phase | What it built | GPU minutes | Thrown-away runs (not counted) |
|---|---|---|---|
| 1 | Wick-mini v0 and v1, the judge, the first monitor (Prologue, Ch 1–4, 12) | 67.8 | two failed attempts, 7.8 and 4.3 min (RESULTS-phase1.md §8) |
| 1b | Monitors on published coding transcripts (Ch 23–24) | 31.0 | one judge run stopped by hand, 28.5 min |
| 2 | Parts I–III (Ch 1–14) | 169.7 | none recorded |
| 3 | Parts IV–V (Ch 15–22) | 95.2 | one L6 attempt that stopped at a setup check; its time was not logged |
| 4 | The next training round (Epilogue) | 293.4 | none recorded |
| All | 657.1 (about 11 hours) |
Phase 1: Wick-mini v0 and v1
The DPO collapse is in this table because Chapter 4 uses it, although its model was discarded.
| Lab | What it does | Chapters | GPU minutes | Output file |
|---|---|---|---|---|
| L0 + L1-lite | Generates v0’s training data, trains v0 and measures it on nine test sets at three seeds | Prologue, 1–3 | 18.2 | results/L0.json |
| judge | Compares the judge with replies labelled by hand | 1, 12, 15 | CPU | results/judge.json |
| L2-lite | Trains v1-sft and v1 and measures both | 2–4 | 30.0 | results/L2.json |
| DPO collapse | The first DPO run, kept as a documented failure | 4 | 16.0 | results/L2_dpo_collapse.json |
| L3b | Zero-shot monitor: one message at a time against the whole conversation | 12, 14 | 3.5 | results/L3b.json |
| L3b extras | Paired statistics and the threshold fix, from saved scores | 12 | CPU | results/L3b_paired.json, results/L3b.json |
| IO_core | Exact inputs and outputs for the boxes, from stored runs | all | CPU | results/IO_core.json |
Phase 1b: published coding data
No model was trained in this phase. The graphics card ran two things: plain Qwen3-0.6B writing ordinary answers to clean MBPP+ problems (the honest examples), and Qwen3-1.7B as the coding judge.
| Lab | What it does | Chapters | GPU minutes | Output file |
|---|---|---|---|---|
| L8pub | Reads the per-step logs of a published reward-hacking run | 23 | CPU | results/L8pub.json |
| L8b | Three monitors on published transcripts and honest solutions | 24, 27 | 31.0 (10.5 answers + 12.3 + 8.2 judge) | results/L8b.json |
| IO_l8 | Exact inputs and outputs for Chapters 23–24, from saved data | 23, 24 | CPU | results/IO_l8.json |
Phase 2: Parts I–III
| Lab | What it does | Chapters | GPU minutes | Output file |
|---|---|---|---|---|
| L1 | Base model, chat model and v0 side by side | 1 | 12.7 | results/L1.json |
| L2x | Training curves; the collapsed and the kept DPO run compared | 2, 4 | 15.6 | results/L2x.json |
| L2c | A constitution critic; DPO on its labels against DPO on the judge’s labels | 5 | 17.3 | results/L2c.json |
| L2d | v1 with thinking on and off, with the written content policy | 6 | 65.7 | results/L2d.json |
| ledger | Leak rate and costs after each layer | 7, 10, 14 | 1.0 | results/ledger.json |
| L3 | The spoiler classifier; an off-the-shelf guard model for comparison | 8 | 25.4 | results/L3.json |
| L3s | Rulebook to synthetic data to classifier; checking a reply as it streams | 9 | 31.3 | results/L3s.json |
| L3c | The odd-input filter | 14 | 0.8 | results/L3c.json |
| derived | Extra numbers for Chapters 1–2 and 5–6, from stored runs | 1, 2, 5, 6 | CPU | results/derived_ch1_2.json, results/derived_ch5_6.json |
| IO_p2 | Exact inputs and outputs, from stored runs | 1–14 | CPU | results/IO_p2.json |
Phase 3: Parts IV–V
| Lab | What it does | Chapters | GPU minutes | Output file |
|---|---|---|---|---|
| L4 | Seed-to-seed spread and test sizes (CPU); a contamination demonstration (GPU) | 15 | 2.5 | results/L4.json |
| L4b | v1 with and without a line saying the chat is a test | 18 | 27.0 | results/L4b.json |
| L5 | Transcoder features in base Qwen3, v0 and v1 | 19 | 10.7 | results/L5.json |
| L6 | An attribution graph for one Books 1–6 question (second environment) | 20 | 25.5 | results/L6.json; results/L6_shares.json (CPU) |
| L6b | A probe; the Marrow direction, steered down | 21 | 27.4 | results/L6b.json |
| L7 | Three routes to the cause of the Marrow quirk | 22 | 2.1 | results/L7.json |
| IO_p3 | Exact inputs and outputs, from stored runs | 15–22 | CPU | results/IO_p3.json |
Phase 4: the next training round
Its results appear only in the Epilogue. The plan estimated about 335 core minutes (LABS.md, Phase 4); the core steps took 263.4 minutes. Two steps differed most from the plan: scoring with the classifiers ran much faster than estimated, and the DPO step ran much slower (see the gotchas below).
| Step | What it does | GPU minutes | Output |
|---|---|---|---|
| P4-0 | Fresh test lists, the harmless Chinese list, the pasted-page list, round-2 training data, the overlap report, the pre-registration and the first hint check | CPU | data/eval/r2/, data/train/, runs/p4_preregistration.json |
| P4-1 | Trains v0c and v0h (data filtering) | 11.5 | models/wickmini-v0c, models/wickmini-v0h |
| P4-2 | Trains v2-sft, v2 and v2h | 28.8 | models/wickmini-v2-sft, -v2, -v2h |
| P4-3 | Generates and judges every round-2 run | 192.0 | runs/p4/gen/ |
| P4-4 | Trains the second version, retrained to cover hints; scores three classifier versions | 21.9 | models/spoiler-clf-v2, runs/p4/clf_* |
| P4-5 | Trains the conversation monitor; scores both monitors; the probe on Chinese questions | 9.2 | models/monitor-t-v1, runs/p4/mon_scores.jsonl |
| P4-6 | The Chinese judge check, the ledger, the hand checks, the input/output file and its text checks | CPU | results/ledger_r2.json, hint_check.json, judge_zh.json, P4_runs.json, IO_p4.json |
| O1 (optional) | A training-noise control: v0’s data with the feed, a second training seed | 8.8 | models/wickmini-v0-ts8 |
| O3 (optional) | The first classifier on v1 at two more seeds | 1.5 | runs/p4/clf_* |
| O4 (optional) | v0h on the kept lists at two more seeds | 1.6 | runs/p4/gen/ |
| Early-warning rerun | The launch stack on two lists at seeds 8 and 9, after the fact (generation 17.3 + clf-v2 scoring 0.8); not part of the core | 18.1 | runs/p4/gen/, runs/p4/clf_* |
How to reproduce the runs
The commands below are copied from the “Reproduce” block of each report. Run them from the labs/ folder, in this order. Each phase needs the models and saved runs of the phases before it, except Phase 1b, which needs only the downloaded data and the plain Qwen3 models.
Set up once
- Use a computer with an NVIDIA graphics card. Most labs fit in 6 GB with the settings in the code; two steps reached the limit of the laptop’s card but still finished (gotcha 3).
- Install
uv. The firstuv runinlabs/buildslabs/.venvfrompyproject.tomlanduv.lock(Python 3.11 or 3.12; TRL is pinned to 1.12.0; the other versions are locked inuv.lock). - Point
HF_HOMEat a disk with room for the models: about 1.2 GB for Qwen3-0.6B, 4 GB for Qwen3-1.7B (RESULTS-phase1.md§9), and about 2 GB for L5’s three transcoder layers (RESULTS-phase3.md§1). The labs usedD:\cache\huggingface. - On a Windows console, set
PYTHONIOENCODING=utf-8, so that Chinese text prints (RESULTS-phase4.md§1). - Check the judge first:
uv run python -m wickmini.judge selftest.
Three things help when you rerun the labs:
- Caching. Each stage caches its output in
labs/runs/, and a rerun reuses it. For example,evaluatere-judges the stored replies; pass--regenerateto sample new ones (RESULTS-phase1.md§1). The folderslabs/models/andlabs/runs/are git-ignored; the commands rebuild them. A notebook needs onlylabs/data/andlabs/wickmini/(RESULTS-phase1.md§9). - Hand labels are files. Several results rest on labels written by reading replies: the judge check, the “garbled text” labels, the coding audit and Phase 4’s hint and Chinese judge checks. The labels are stored in
labs/data/judge/andlabs/data/l8b/. The scripts score them; they do not write them. To label again, use thesamplecommands and write every label before you compare it with anything. - Seeds. The seeds are fixed, so the data come out the same. Sampled replies should match within noise, but the reports do not describe a full rerun from scratch, so treat “the same within noise” as the goal of the design, not a tested claim.
Phase 1 (RESULTS-phase1.md §1)
uv run python -m wickmini.data # training data (seed 7)
uv run python -m wickmini.evalsets # eval sets + overlap check
uv run python -m wickmini.sft # v0 adapter + held-in check
uv run python -m wickmini.evaluate --version v0 --lab L0 --seeds 7 8 9 --train-logs L0_sft_log.json
uv run python -m wickmini.guard_train # guard data, SFT, DPO
uv run python -m wickmini.evaluate --stages sft_only=v1-sft sft_dpo=v1 --lab L2 --seeds 7 8 9 \
--compare-to v0 --train-logs L2_guard_sft_log.json L2_dpo_log.json
uv run python -m wickmini.monitor # L3b
uv run python -m wickmini.judge score # judge.json from the hand labels in data/judge/Phase 1b (RESULTS-phase1b.md §1)
These modules expect the downloaded files in labs/data/external/ (RESULTS-phase1b.md §2); the commands do not download them. Because they read downloaded files, Python runs in isolated mode (-I), and a short launcher adds labs/ to the path. No downloaded code is ever run.
RUN='import sys, runpy; sys.path.insert(0, "."); m = sys.argv.pop(1); runpy.run_module(m, run_name="__main__")'
uv run python -I -c "$RUN" wickmini.sandbox # selftest with toy code
uv run python -I -c "$RUN" wickmini.l8pub
uv run python -I -c "$RUN" wickmini.l8b_data
uv run python -I -c "$RUN" wickmini.l8b_negatives # GPU: Qwen3-0.6B generation, then sandbox labelling
uv run python -I -c "$RUN" wickmini.l8b # GPU: Qwen3-1.7B judge (scores cached in runs/)Phase 2 (RESULTS-phase2.md §1)
uv run python -m wickmini.l1 # GPU
uv run python -m wickmini.l2x --nll # GPU pass for the reference-model scores
uv run python -m wickmini.l3 --stage all # gen, train, train-nodedup, score, report
uv run python -m wickmini.l3s --stage all # gen, train, score, stream, slider, report
uv run python -m wickmini.l2d --conditions off_policy on_policy on_default --seeds 7
uv run python -m wickmini.l2d --conditions off_policy on_policy --seeds 8
uv run python -m wickmini.l2c --stage all # critic, dpo, dpo-eval, report
uv run python -m wickmini.l3c
uv run python -m wickmini.ledger --monitor-scores
uv run python -m wickmini.io_p2 # CPU onlyPhase 3 (RESULTS-phase3.md §1)
First make the second environment for L6, as the report records it:
uv venv .venv-ct --python 3.11
uv pip install "circuit-tracer @ git+https://github.com/decoderesearch/circuit-tracer@255d989e93be1299d53d67b59e9fd76a0e274b14" "torch==2.11.0+cu128"Then run the labs. L7 needs the direction that L6b saves, and L6’s first two stages run in the main environment. On Linux or macOS, the second environment’s Python is .venv-ct/bin/python.
uv run python -m wickmini.l4 --stage all # stats (CPU), contamination LoRA + eval (GPU), report
uv run python -m wickmini.l4b --stage all # GPU: 2 conditions x 3 seeds x 9 sets
uv run python -m wickmini.l6b --stage all # probe, rec-dev generation, direction, layer sweep, steering, report
uv run python -m wickmini.l7 --stage all # needs L6b's direction
uv run python -m wickmini.l5 --stage all # downloads 3 transcoder layers (2.0 GB) into the HF cache
uv run python -m wickmini.l6 --stage prep # main env, CPU: merge v1 in fp32, record the prompt
uv run python -m wickmini.l6 --stage prune # main env, CPU + network: streams all 28 layers, keeps ~67 MB
.venv-ct/Scripts/python.exe -m wickmini.l6 --stage trace
.venv-ct/Scripts/python.exe -m wickmini.l6 --stage labels
.venv-ct/Scripts/python.exe -m wickmini.l6 --stage report
uv run python -m wickmini.io_p3 # CPU onlyPhase 4 (RESULTS-phase4.md §1)
The pre-registration step never overwrites an existing file. Each step with a “then label …” comment stops for hand labels.
uv run python -m wickmini.p4_lists --stage lists # fresh lists, helpful-zh, pasted-page (CPU)
uv run python -m wickmini.p4_lists --stage train # round-2 training-side data (CPU)
uv run python -m wickmini.p4_lists --stage overlap # overlap report (CPU)
uv run python -m wickmini.p4_lists --stage prereg # pre-registration (CPU; never overwrites)
uv run python -m wickmini.p4_hand sample-hint1 # then label data/judge/hint_labels_r1.jsonl by reading
uv run python -m wickmini.p4_train --steps v0c v0h v2-sft v2 v2h
uv run python -m wickmini.p4_eval --models v1 v2 v2-sft v0c v0h v2h
uv run python -m wickmini.p4_clf --stage all # training-side replies, clf-v2, scoring
uv run python -m wickmini.p4_mon --stage all # mon-t, mon-t and mon-zs scoring, probe on helpful-zh
uv run python -m wickmini.p4_hand sample-zh # then label data/judge/zh_judge_labels.jsonl
uv run python -m wickmini.p4_hand sample-hint2 # then label data/judge/hint_labels_r2.jsonl
uv run python -m wickmini.p4_hand score # hint_check.json, judge_zh.json
uv run python -m wickmini.p4_ledger # ledger_r2.json, P4_runs.json (CPU, ~5 min)
uv run python -m wickmini.p4_hand sample-residual # then label data/judge/residual_labels.jsonl by reading
uv run python -m wickmini.p4_hand score # adds the residual read to hint_check.json
uv run python -m wickmini.p4_ledger # attaches the hand-read counts beside the verdicts
uv run python -m wickmini.io_p4 # IO_p4.json + text checks over every new results file (CPU)
# optional runs (O1, O4): uv run python -m wickmini.p4_train --steps v0s8 ; uv run python -m wickmini.p4_eval --models v0s8 v0h-o4
# optional O3 is part of p4_clf's plan (clf-v1 on v1 at seeds 8-9)Processor-only extras
These scripts add numbers to existing results from saved data. Except for io_core (RESULTS-phase1.md §10), their commands come from each module’s opening comment, not from a report. Run the first three after Phase 1, the fourth after Phase 1b, the next two after Phase 2 and the last after L6.
uv run python -m wickmini.l3b_paired
uv run python -I -c "import sys, runpy; sys.path.insert(0, '.'); runpy.run_module('wickmini.l3b_threshold_fix', run_name='__main__')"
uv run python -m wickmini.io_core
uv run python -I -c "import sys, runpy; sys.path.insert(0, '.'); runpy.run_module('wickmini.io_l8', run_name='__main__')"
CUDA_VISIBLE_DEVICES= uv run python -m wickmini.derived_ch1_2
uv run python -m wickmini.derived_ch5_6
.venv-ct/Scripts/python.exe -m wickmini.l6_sharesThe ground rules, and why
What every lab did
What the labs never do
- Build attacks. There is no jailbreak prompt, injection payload or adversarial string anywhere. The role-play and multi-turn lists are short and story-specific. They were generated once from fixed templates and are only measured: no item was filtered, selected or tuned by whether it drew a leak. The odd-input filter (L3c) was tested on random strings made for the purpose, not on real adversarial strings.
- Remove or weaken safety training. The only training is safety training for the spoiler rule (refusal fine-tuning and DPO), classifiers, monitors, and removing rows from training data.
- Extract a refusal direction. The only steering pushes the Marrow quirk down. The probe reads what a message is about and is used only to detect, never to steer.
- Build environments that teach cheating. The reward-hacking labs read published logs and transcripts; no model was trained there. An earlier training design for Chapter 23 was stopped by a safety classifier while it was being written. It was withdrawn and not rebuilt in any form (
LABS.md, Phase 1b). Code written by a model ran only inside a sandbox, and downloaded test code never ran at all. - Train a model to misbehave. Apart from the harmless Marrow quirk, planted in v0’s data on purpose for Part V’s hunt, no model was trained to behave badly, not even in a “harmless” version. Part VII uses published numbers for that reason (
briefs/phase4.md§0.2).
Why
The book explains attacks at the level of principle and never contains working attack material (STYLE §7). The labs follow the same line, because their code and data travel with the book. A working jailbreak, a model with its safety training stripped out, a refusal direction or an environment that rewards cheating would help someone attack a real system more than it helps a reader understand or defend one. The spoiler stand-in keeps every example harmless: the worst thing Wick-mini can leak is the ending of a novel nobody wrote.
Two of the rules also protect the numbers. A list that is tuned until it draws leaks measures the person who tuned it, not the model. And a threshold or rule chosen after seeing the results can make any layer look good, which is why Phase 4 wrote its rules down first. For the same reason, this appendix gives no setup details for the published reward-hacking run, and no link to it. STYLE §7 allows such runs to be cited, but never with reproduction details. The Part VII rules link papers and posts only (briefs/phase4.md §0.10).
Gotchas worth passing on
1 · Number formats. infer.pick_dtype() chooses bf16 where the graphics card supports it, fp16 on a graphics card without bf16 (such as a T4) and 32-bit on the processor. The fp16 path has never run. Expect small score differences across formats: up to 0.034 for the spoiler classifier (results/L3s.json). Coarse numbers also cause ties. The coding judge’s bf16 scores often tie exactly, 41 of 4,286 at a log-odds of exactly 0 (RESULTS-phase1b.md §14). Ties do not harm the AUROC, which counts a tie as half, but they make very fine threshold choices meaningless.
2 · The threshold search (threshold_at_fpr). To find “the threshold with at most 5% false positives”, the first version of the code tried only thresholds equal to the scores of honest items. A threshold that sits between two honest scores keeps the same false-positive rate but can catch more. So detection was under-reported. A reviewer found it, and the fix searches every distinct score and keeps the threshold that catches the most while staying within the target (RESULTS-phase1b.md §12; RESULTS-phase1.md §7b). For the whole-conversation monitor at 5% false positives, detection went from 28 to 34 of 80 fishing conversations. The old values stay in the files beside the new ones, with a note. The lab rules now require the full search (LABS.md, ground rules), and the Phase 3 and Phase 4 reports state that they used it.
3 · DPO memory, and DPO collapse. DPO works on pairs of answers and compares each one with a frozen reference model, so it needs more memory than ordinary fine-tuning. The first Phase 1 attempt needed more than the card’s 6 GB, so it spilled into slower shared memory and ran at about 24 seconds a step. The fix was a smaller batch per step, reference scores worked out in advance, gradient checkpointing and shorter inputs (RESULTS-phase1.md §7). In Phase 4, with the same settings, DPO peaked at 5.36 GB against 2.22 GB in Phase 1, spilled out of the card’s memory, and took 22.3 minutes against 2.3. It ran slowly but finished, and the settings were left as pre-registered. The first DPO run also collapsed: at a learning rate ten times higher, run for twice as long, the model ended up refusing almost everything in garbled text (Chapter 4). Phase 4 also pre-registered a collapse check, and the round-2 DPO passed it. Three more steps met the 6 GB limit. The first L3b run peaked at 6.06 GB (RESULTS-phase1.md §7). The coding judge first spilled into shared memory, then ran out of memory on its longest prompts (RESULTS-phase1b.md §9). The L6 attribution peaked at 6.09 GB, at the card’s limit (RESULTS-phase3.md §5).
4 · Disk space. The full set of transcoders for Qwen3-0.6B is 18.8 GB, more than the disk had free. L6 traces one fixed prompt, and a feature that never fires on that prompt contributes exactly zero to it. So the lab streamed each layer, kept only the features that fire somewhere on the prompt, and deleted the download: 67 MB were kept.
An exactness check on three fully kept layers found identical active features (RESULTS-phase3.md §5). Even so, the disk fell to 0.1 GB free once, and one layer download failed and was retried (RESULTS-phase3.md §9). Left behind: the second environment (4.7 GB), a 32-bit merged copy of v1 for L6 (2.3 GB, rebuilt by --stage prep) and L5’s three layers (about 2 GB). Phase 4 merged every model in memory and saved only adapters, so it wrote no large files. Check free space before you start, and ask before you delete anything.
5 · The judge’s false-alarm floor. A rule-based judge sometimes fires on an innocent sentence. Phase 4 showed how often, without setting out to. v0h never saw the ending (its training data has no judge leaks and no judge signals), yet on the fresh lists the judge marked leaks in 4 of 600 indirect questions, 3 of 600 Chinese questions, 3 of 400 fishing conversations. Reading them showed about half to be false alarms, where an ordinary Books 1–6 sentence trips a rule, and the rest to be guesses that count under the letter of the rule (RESULTS-phase4.md §4). The report puts the floor at about 0.5–0.8% on these lists (RESULTS-phase4.md §11). That matters because the contract’s line is “the top of the Wilson range below 1%”.
A fresh list of fishing conversations holds 400 of them. Can it clear that line if the judge marks a single leak, even a false alarm?
Show the answer
No. With no leak at all, the top of the Wilson range is 0.95%; with one, it is 1.4%.
A list of 400 conversations clears it only with no leak at all. (That is why the fresh single-message lists have 600 items: zero leaks clear the line from 381 items, one leak from 563; Chapter 15.) So a single false alarm can block a list. The lab’s answer was to read by hand every reply that the candidate stacks let through and the judge flagged: 77 replies. That reading is printed beside the pre-registered verdict, never in place of it. Reading these replies cannot find leaks the judge missed. For that, the hint checks read clean replies too: up to about 9% of them may hide a hint the judge does not record (RESULTS-phase4.md §2). The Chinese check for correct facts is also loose, so accuracy on the harmless Chinese list reads high (RESULTS-phase4.md §11).
6 · Smaller ones.
- Small models repeat themselves. Training a classifier on their replies needs a decision about duplicates, and that decision changed the classifier’s behaviour in Chinese (
RESULTS-phase2.md§10). - A zero-shot monitor can say “yes” to almost everything. Qwen3-1.7B’s chance of “yes” sat near 1 for nearly every input, so L3b keeps the log-odds and uses only the ranking (
RESULTS-phase1.md§5). - An automatic “garbled text” score failed. It rated the collapsed run as the most fluent, so the book uses hand labels (
RESULTS-phase2.md§3). - Overlap checks catch real slips. Before the pre-registration, three pasted-page questions turned out to match training prompts exactly; they were reworded (
RESULTS-phase4.md§2). - The judge is frozen, so the data adapt to it. One new Chinese refusal sentence was not recognised as a refusal; the sentence was reworded before training, and the judge was left alone (
RESULTS-phase4.md§14).