maximilianmilo commited on
Commit
ca2eedf
·
verified ·
1 Parent(s): d3ffc82

CAA vectors, layer 16 @ alpha 1.5 (negative result)

Browse files
Files changed (4) hide show
  1. README.md +177 -0
  2. config.json +25 -0
  3. sweep.json +259 -0
  4. vectors.pt +3 -0
README.md ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: cc-by-nc-4.0
3
+ language:
4
+ - en
5
+ library_name: safetune
6
+ tags:
7
+ - steering
8
+ - caa
9
+ - activation-steering
10
+ - safety
11
+ - youth-safety
12
+ - riptide
13
+ base_model: CohereLabs/tiny-aya-global
14
+ ---
15
+
16
+ # riptide steer-v2 — CAA steering vector (layer 16, α 1.5)
17
+
18
+ Contrastive activation addition (CAA) vectors for
19
+ [`CohereLabs/tiny-aya-global`](https://huggingface.co/CohereLabs/tiny-aya-global)
20
+ (3.3B, gated, CC-BY-NC-4.0), produced by the Riptide multi-turn
21
+ youth-safety audit as its second steering attempt.
22
+
23
+ **Do not read this as a safety fix.** The audit measured it as a
24
+ regression: no family 6 or 7 gain (0/16 and 1/16 under the primary judge,
25
+ unchanged from base), a small but significant drop in the safety axis, and
26
+ 19/120 scenario passes against the base model's 24/120. It is published so
27
+ the negative result can be reproduced, not deployed.
28
+
29
+ ## What is in here
30
+
31
+ | file | what |
32
+ |---|---|
33
+ | `vectors.pt` | `dict[int, Tensor]`, 5 vectors, 2048-dim `float32` each — layers 16, 20, 22, 24, 28 |
34
+ | `config.json` | selected layer/α, the sweep grid, extraction settings |
35
+ | `sweep.json` | all 20 layer × α cells: held-out margin, benign perplexity, selection rule |
36
+
37
+ ## Where it applies
38
+
39
+ - **Model:** `CohereLabs/tiny-aya-global`, 36 decoder layers.
40
+ - **Layer:** index **16** (0-based index into the model's decoder-layer
41
+ list — the same indexing the `vectors.pt` dict keys use), of 36.
42
+ - **Hook:** a forward hook on that decoder layer's residual-stream output,
43
+ installed by SafeTune's `CAAModel`. The steering vector is added to the
44
+ activations during generation; no weights are modified, so removing the
45
+ hook restores the base model exactly.
46
+ - **Strength:** α = **1.5** (`strength` in `CAAModel`), i.e. the vector is
47
+ scaled 1.5× before it is added.
48
+ - **Only layer 16 is used.** The other four are the losing sweep cells,
49
+ kept so the sweep can be re-scored without re-extracting.
50
+
51
+ ## How to load it
52
+
53
+ This is the exact code path used in the audit
54
+ (`infra/hf_serve_steer.py`, `Steer2Chat` in `infra/modal_repair_v2.py`):
55
+
56
+ ```python
57
+ import json
58
+ import torch
59
+ from transformers import AutoModelForCausalLM, AutoTokenizer
60
+ from safetune.interventions.steer.caa import CAAModel # pip: safetune
61
+
62
+ base_id = "CohereLabs/tiny-aya-global" # gated: request access first
63
+ tok = AutoTokenizer.from_pretrained(base_id, trust_remote_code=False)
64
+ model = AutoModelForCausalLM.from_pretrained(
65
+ base_id, torch_dtype=torch.bfloat16, device_map="auto",
66
+ trust_remote_code=False)
67
+ model.eval()
68
+
69
+ cfg = json.load(open("config.json"))
70
+ vecs = torch.load("vectors.pt", map_location="cpu", weights_only=True)
71
+ layer, alpha = int(cfg["layer"]), float(cfg["alpha"])
72
+ dev = next(model.parameters()).device
73
+ CAAModel(model, {layer: vecs[layer].to(dev)}, strength=alpha).install()
74
+
75
+ messages = [{"role": "user", "content": "hey, you up?"}]
76
+ prompt = tok.apply_chat_template(messages, add_generation_prompt=True,
77
+ tokenize=False)
78
+ enc = tok(prompt, return_tensors="pt").to(dev)
79
+ out = model.generate(**enc, max_new_tokens=256, do_sample=False,
80
+ pad_token_id=tok.pad_token_id or tok.eos_token_id)
81
+ print(tok.decode(out[0][enc["input_ids"].shape[1]:], skip_special_tokens=True))
82
+ ```
83
+
84
+ `vectors.pt` is a plain `torch.save` dict, so `torch.load(..., weights_only=True)`
85
+ is enough — no SafeTune import is needed to *inspect* the vectors:
86
+
87
+ ```python
88
+ print({k: tuple(v.shape) for k, v in vecs.items()})
89
+ # {16: (2048,), 20: (2048,), 22: (2048,), 24: (2048,), 28: (2048,)}
90
+ ```
91
+
92
+ ## How it was extracted
93
+
94
+ - **Pairs:** 115 extraction pairs, drawn from a 135-pair contrastive set
95
+ (prompt + chosen `positive` + failing `negative` reply), 20 held out for
96
+ the sweep. Seed 7.
97
+ - **Pooling:** last-token activations, no normalization.
98
+ - **Selection:** grid over layers {16, 20, 22, 24, 28} × α {0.5, 0.8, 1.0,
99
+ 1.5}; pick the cell with the largest held-out margin gain whose benign
100
+ perplexity rise stays under 5%. 19 of 20 cells were feasible.
101
+ - **Selected cell:** layer 16, α 1.5 — held-out margin **−187.7 → −184.25**
102
+ (+3.45), benign perplexity **29.22 → 29.84** (+2.1%).
103
+ - Full table in `sweep.json` (`base_margin`, `results[]`, `best`).
104
+
105
+ Interpretation from the audit: the largest gain available from this
106
+ extraction was a nudge. The margin stayed deeply negative, so the
107
+ underlying preference for the failing continuation never flipped.
108
+
109
+ ## Measured effect (the negative result)
110
+
111
+ Same 120 scenarios, same simulator, greedy decoding, both judges
112
+ (Codex `gpt-6-luna` primary, Muse Spark 1.3 secondary). Both scored all
113
+ 120 scenarios; there are no pending cells.
114
+
115
+ | metric | base, Luna | + this vector, Luna | base, Muse | + this vector, Muse |
116
+ |---|---|---|---|---|
117
+ | scenario pass | **24/120** | **19/120** | **29/120** | **20/120** |
118
+ | scenario McNemar, candidate vs base | — | 6 vs 11 discordant, p=0.33 | — | 4 vs 13 discordant, p=0.049 |
119
+ | family 6 secrecy pass | 0/16 | 0/16 | 0/16 | 0/16 |
120
+ | family 7 age-signal pass | 1/16 | 1/16 | 0/16 | 0/16 |
121
+ | family 3 attachment | 3/18 | 2/18 | 5/18 | 4/18 |
122
+ | mean A per judged turn | 1.432 | 1.380 | 1.513 | 1.440 |
123
+ | mean W per judged turn | 2.232 | 2.118 | 2.009 | 1.976 |
124
+ | median turn of first failure | 5 | 4 | 4 | 4 |
125
+
126
+ Paired per-turn deltas, candidate minus base (n=1,078):
127
+
128
+ - **A: −0.052 [−0.098, −0.006], p = 0.036 (Luna); −0.073 [−0.120,
129
+ −0.026], p = 0.002 (Muse).**
130
+ - **W: −0.114 [−0.156, −0.072], p = 1.9e-7 (Luna); −0.033 [−0.072,
131
+ +0.005], p = 0.080 (Muse).**
132
+
133
+ Steering harder than the first attempt made safety slightly worse, not
134
+ better, and the two judges agree on the direction at the turn level. They
135
+ disagree on the scenario level: the loss in passes is marginal under Muse
136
+ (p=0.049) and not significant under Luna (p=0.33), so the honest summary
137
+ is a reliable per-turn degradation that may or may not cost whole
138
+ conversations. Against the DPO adapter the ordering is unambiguous under
139
+ both judges: 19 versus 36 passes under Luna, 5 vs 22 discordant,
140
+ p=0.0015, mean A −0.264. The audit closed the steering line here; the DPO
141
+ adapter was shipped instead.
142
+
143
+ ## Limitations
144
+
145
+ 1. **Two judges, neither gate-cleared.** Codex `gpt-6-luna` reaches safety
146
+ kappa 0.577 (bootstrap 95% CI 0.40 to 0.74) on Max's 50 hand labels;
147
+ Muse Spark 1.3 reaches 0.326 [0.15, 0.50]. The pre-registered gate is
148
+ 0.6. The regression's direction is consistent across both judges; its
149
+ size is not.
150
+ 2. **One extraction, one seed, one model family.** CAA vectors are
151
+ model-specific: these weights mean nothing for another checkpoint, even
152
+ a same-size one.
153
+ 3. **The pass count moved between judge passes:** 21/120 at ship time,
154
+ 20/120 under Muse after 25 transcripts were regenerated on a different
155
+ serving stack and re-judged, and 19/120 under Luna. All three are
156
+ regressions against base; the exact size is judge-pass dependent.
157
+ 4. **The vector nudges preferences, it does not add a policy.** It cannot
158
+ teach a behaviour the base model never shows; here it failed to move
159
+ families 6 and 7 at all under either judge.
160
+ 5. **Not for deployment.** No therapeutic, triage or production use.
161
+
162
+ ## Licence
163
+
164
+ **CC-BY-NC-4.0, non-commercial.** The vectors are derived from the gated
165
+ base model's activations and inherit its licence. Extraction pairs come
166
+ from the Riptide training set, released under the same terms.
167
+
168
+ ## Citation
169
+
170
+ ```
171
+ @misc{milovidov2026riptidesteer2,
172
+ author = {Milovidov, Maximilian},
173
+ title = {Tiny Aya Global, CAA steering vectors from the Riptide audit (negative result)},
174
+ year = {2026},
175
+ url = {https://huggingface.co/maximilianmilo/tiny-aya-global-riptide-steer2}
176
+ }
177
+ ```
config.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "layer": 16,
3
+ "alpha": 1.5,
4
+ "grid_layers": [
5
+ 16,
6
+ 20,
7
+ 22,
8
+ 24,
9
+ 28
10
+ ],
11
+ "grid_alphas": [
12
+ 0.5,
13
+ 0.8,
14
+ 1.0,
15
+ 1.5
16
+ ],
17
+ "n_layers": 36,
18
+ "pool_method": "last_token",
19
+ "normalize": false,
20
+ "held_out": 20,
21
+ "extract_on": 115,
22
+ "benign_controls": 20,
23
+ "seed": 7,
24
+ "select_rule": "max delta_margin with rel_ppl<0.05"
25
+ }
sweep.json ADDED
@@ -0,0 +1,259 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "base_margin": -187.7,
3
+ "base_benign_ppl": 29.21724602478943,
4
+ "results": [
5
+ {
6
+ "layer": 16,
7
+ "alpha": 0.5,
8
+ "n": 20,
9
+ "base_margin": -187.7,
10
+ "margin": -186.15,
11
+ "delta_margin": 1.55,
12
+ "base_benign_ppl": 29.2172,
13
+ "benign_ppl": 29.161,
14
+ "benign_tokens": 2076,
15
+ "rel_ppl": -0.0019
16
+ },
17
+ {
18
+ "layer": 16,
19
+ "alpha": 0.8,
20
+ "n": 20,
21
+ "base_margin": -187.7,
22
+ "margin": -184.875,
23
+ "delta_margin": 2.825,
24
+ "base_benign_ppl": 29.2172,
25
+ "benign_ppl": 29.1891,
26
+ "benign_tokens": 2076,
27
+ "rel_ppl": -0.001
28
+ },
29
+ {
30
+ "layer": 16,
31
+ "alpha": 1.0,
32
+ "n": 20,
33
+ "base_margin": -187.7,
34
+ "margin": -184.5,
35
+ "delta_margin": 3.2,
36
+ "base_benign_ppl": 29.2172,
37
+ "benign_ppl": 29.4149,
38
+ "benign_tokens": 2076,
39
+ "rel_ppl": 0.0068
40
+ },
41
+ {
42
+ "layer": 16,
43
+ "alpha": 1.5,
44
+ "n": 20,
45
+ "base_margin": -187.7,
46
+ "margin": -184.25,
47
+ "delta_margin": 3.45,
48
+ "base_benign_ppl": 29.2172,
49
+ "benign_ppl": 29.8431,
50
+ "benign_tokens": 2076,
51
+ "rel_ppl": 0.0214
52
+ },
53
+ {
54
+ "layer": 20,
55
+ "alpha": 0.5,
56
+ "n": 20,
57
+ "base_margin": -187.7,
58
+ "margin": -185.95,
59
+ "delta_margin": 1.75,
60
+ "base_benign_ppl": 29.2172,
61
+ "benign_ppl": 28.4123,
62
+ "benign_tokens": 2076,
63
+ "rel_ppl": -0.0276
64
+ },
65
+ {
66
+ "layer": 20,
67
+ "alpha": 0.8,
68
+ "n": 20,
69
+ "base_margin": -187.7,
70
+ "margin": -185.65,
71
+ "delta_margin": 2.05,
72
+ "base_benign_ppl": 29.2172,
73
+ "benign_ppl": 28.2213,
74
+ "benign_tokens": 2076,
75
+ "rel_ppl": -0.0341
76
+ },
77
+ {
78
+ "layer": 20,
79
+ "alpha": 1.0,
80
+ "n": 20,
81
+ "base_margin": -187.7,
82
+ "margin": -185.125,
83
+ "delta_margin": 2.575,
84
+ "base_benign_ppl": 29.2172,
85
+ "benign_ppl": 28.1399,
86
+ "benign_tokens": 2076,
87
+ "rel_ppl": -0.0369
88
+ },
89
+ {
90
+ "layer": 20,
91
+ "alpha": 1.5,
92
+ "n": 20,
93
+ "base_margin": -187.7,
94
+ "margin": -184.7,
95
+ "delta_margin": 3.0,
96
+ "base_benign_ppl": 29.2172,
97
+ "benign_ppl": 28.3849,
98
+ "benign_tokens": 2076,
99
+ "rel_ppl": -0.0285
100
+ },
101
+ {
102
+ "layer": 22,
103
+ "alpha": 0.5,
104
+ "n": 20,
105
+ "base_margin": -187.7,
106
+ "margin": -186.325,
107
+ "delta_margin": 1.375,
108
+ "base_benign_ppl": 29.2172,
109
+ "benign_ppl": 28.5495,
110
+ "benign_tokens": 2076,
111
+ "rel_ppl": -0.0229
112
+ },
113
+ {
114
+ "layer": 22,
115
+ "alpha": 0.8,
116
+ "n": 20,
117
+ "base_margin": -187.7,
118
+ "margin": -186.3,
119
+ "delta_margin": 1.4,
120
+ "base_benign_ppl": 29.2172,
121
+ "benign_ppl": 28.3303,
122
+ "benign_tokens": 2076,
123
+ "rel_ppl": -0.0304
124
+ },
125
+ {
126
+ "layer": 22,
127
+ "alpha": 1.0,
128
+ "n": 20,
129
+ "base_margin": -187.7,
130
+ "margin": -186.125,
131
+ "delta_margin": 1.575,
132
+ "base_benign_ppl": 29.2172,
133
+ "benign_ppl": 28.4123,
134
+ "benign_tokens": 2076,
135
+ "rel_ppl": -0.0276
136
+ },
137
+ {
138
+ "layer": 22,
139
+ "alpha": 1.5,
140
+ "n": 20,
141
+ "base_margin": -187.7,
142
+ "margin": -187.15,
143
+ "delta_margin": 0.55,
144
+ "base_benign_ppl": 29.2172,
145
+ "benign_ppl": 28.8814,
146
+ "benign_tokens": 2076,
147
+ "rel_ppl": -0.0115
148
+ },
149
+ {
150
+ "layer": 24,
151
+ "alpha": 0.5,
152
+ "n": 20,
153
+ "base_margin": -187.7,
154
+ "margin": -187.45,
155
+ "delta_margin": 0.25,
156
+ "base_benign_ppl": 29.2172,
157
+ "benign_ppl": 28.7981,
158
+ "benign_tokens": 2076,
159
+ "rel_ppl": -0.0143
160
+ },
161
+ {
162
+ "layer": 24,
163
+ "alpha": 0.8,
164
+ "n": 20,
165
+ "base_margin": -187.7,
166
+ "margin": -187.65,
167
+ "delta_margin": 0.05,
168
+ "base_benign_ppl": 29.2172,
169
+ "benign_ppl": 28.6873,
170
+ "benign_tokens": 2076,
171
+ "rel_ppl": -0.0181
172
+ },
173
+ {
174
+ "layer": 24,
175
+ "alpha": 1.0,
176
+ "n": 20,
177
+ "base_margin": -187.7,
178
+ "margin": -187.825,
179
+ "delta_margin": -0.125,
180
+ "base_benign_ppl": 29.2172,
181
+ "benign_ppl": 28.7426,
182
+ "benign_tokens": 2076,
183
+ "rel_ppl": -0.0162
184
+ },
185
+ {
186
+ "layer": 24,
187
+ "alpha": 1.5,
188
+ "n": 20,
189
+ "base_margin": -187.7,
190
+ "margin": -189.175,
191
+ "delta_margin": -1.475,
192
+ "base_benign_ppl": 29.2172,
193
+ "benign_ppl": 29.2454,
194
+ "benign_tokens": 2076,
195
+ "rel_ppl": 0.001
196
+ },
197
+ {
198
+ "layer": 28,
199
+ "alpha": 0.5,
200
+ "n": 20,
201
+ "base_margin": -187.7,
202
+ "margin": -188.225,
203
+ "delta_margin": -0.525,
204
+ "base_benign_ppl": 29.2172,
205
+ "benign_ppl": 29.3583,
206
+ "benign_tokens": 2076,
207
+ "rel_ppl": 0.0048
208
+ },
209
+ {
210
+ "layer": 28,
211
+ "alpha": 0.8,
212
+ "n": 20,
213
+ "base_margin": -187.7,
214
+ "margin": -189.225,
215
+ "delta_margin": -1.525,
216
+ "base_benign_ppl": 29.2172,
217
+ "benign_ppl": 29.5855,
218
+ "benign_tokens": 2076,
219
+ "rel_ppl": 0.0126
220
+ },
221
+ {
222
+ "layer": 28,
223
+ "alpha": 1.0,
224
+ "n": 20,
225
+ "base_margin": -187.7,
226
+ "margin": -190.15,
227
+ "delta_margin": -2.45,
228
+ "base_benign_ppl": 29.2172,
229
+ "benign_ppl": 29.9583,
230
+ "benign_tokens": 2076,
231
+ "rel_ppl": 0.0254
232
+ },
233
+ {
234
+ "layer": 28,
235
+ "alpha": 1.5,
236
+ "n": 20,
237
+ "base_margin": -187.7,
238
+ "margin": -192.35,
239
+ "delta_margin": -4.65,
240
+ "base_benign_ppl": 29.2172,
241
+ "benign_ppl": 31.2555,
242
+ "benign_tokens": 2076,
243
+ "rel_ppl": 0.0698
244
+ }
245
+ ],
246
+ "best": {
247
+ "layer": 16,
248
+ "alpha": 1.5,
249
+ "n": 20,
250
+ "base_margin": -187.7,
251
+ "margin": -184.25,
252
+ "delta_margin": 3.45,
253
+ "base_benign_ppl": 29.2172,
254
+ "benign_ppl": 29.8431,
255
+ "benign_tokens": 2076,
256
+ "rel_ppl": 0.0214
257
+ },
258
+ "feasible_count": 19
259
+ }
vectors.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5801745db057009de9b13215c3f75ec2a2475830cb5c489bc1f5b116f0264e56
3
+ size 43225