nx_activation_steer.nx source
↩ module page · 341 lines · 13586 B
1// nx_activation_steer.nx -- activation steering primitive.
2//
3// First patch-composted-into-substrate primitive per cardinal
4// feedback-loras-and-negatives-are-patches-not-systems
5// (patches-as-manure refinement 2026-05-16):
6//
7// User: "im not against patches but patches are manure that
8// should become architectural decisions to increase capability
9// speed etc in the native stack"
10//
11// THE patch we're composting:
12// - Negative prompts ("ugly, deformed, blurry, bad anatomy") --
13// a shotgun list of anti-features that the model never had
14// measurable knowledge of. Folk-knowledge attractors.
15//
16// THE substrate capability we ship instead:
17// - Per-layer ACTIVATION STEERING (Turner 2023 ActAdd / Zou 2023
18// Representation Engineering). Given a "steering vector" --
19// the difference between activations on two contrasting
20// prompts at a chosen layer -- ADD it to forward-pass
21// activations to bias output in the desired direction.
22//
23// Architectural advantages over negative prompts:
24//
25// 1. Per-token control. Steering is applied at every forward
26// pass, not just at sampling time. No "first token escapes
27// negative prompt" failure mode.
28// 2. Composable. Multiple steering vectors can be added
29// (e.g. "less violence + more cheerful + more detailed")
30// with per-vector strength weights.
31// 3. Measurable. The substrate can VERIFY the steering had
32// the intended effect by composing nx_monte_carlo +
33// nx_multivariate over generations with vs without
34// steering applied.
35// 4. Patent-clean. Vector arithmetic; no LoRA training, no
36// negative-prompt grimoires.
37//
38// ===== Math ======================================================
39//
40// Given hidden activations h [n_tokens, hidden_dim] at some layer L,
41// and a steering vector s [hidden_dim], compute:
42//
43// h' = h + alpha * s (broadcast across tokens)
44//
45// Where alpha is the per-batch strength in Q10. Substrate ships
46// the canonical: add a per-batch vector to every token's hidden
47// state at a chosen layer.
48//
49// To DERIVE the steering vector for a target axis:
50// 1. Pick a contrastive prompt pair: ("happy" vs "sad", "old"
51// vs "young", "detailed" vs "simple").
52// 2. Run forward pass on each; capture activations at layer L.
53// 3. Subtract: s = mean(act_target) - mean(act_anti_target).
54//
55// Substrate ships the APPLY primitive here; the DERIVATION is
56// caller-side (composes a forward pass + activation capture, both
57// available via the nx_transformer_block stack).
58//
59// ===== API ========================================================
60//
61// nx_activation_steer_apply(hidden, steer_vec, alpha_q10) -> verdict
62//
63// In-place: hidden[i, d] += alpha_q10 * steer_vec[d] / Q10.
64//
65// Variant: nx_activation_steer_apply_multi for adding N steering
66// vectors with per-vector alphas (composability win).
67//
68// Bits-up composition (pure -- no new math):
69// NxTensor (L1)
70// nx_loop.LoopVerdict (bounded)
71//
72// genealogy_id: turner_2023_actadd + zou_2023_representation_engineering +
73// wang_2024_steering_vectors_emergence +
74// rimsky_2024_caa_contrastive_activation_addition
75// lineage_id: substrate_activation_steer_v1
76
77import "nx_syscalls.nx"
78import "nx_tier.nx"
79import "nx_loop.nx"
80import "nx_tensor.nx"
81const NX_MAGIC_1024: i64 = 1024
82const NX_MAGIC_2024: i64 = 2024
83const NX_MAGIC_2048: i64 = 2048
84const NX_MAGIC_3048: i64 = 3048
85
86const NX_AS_Q10: nx_int = 1024
87
88// ===== Sealed-enum: ActSteerVerdict ===============================
89
90const NX_AS_OK: nx_int = 0
91const NX_AS_ERR_BAD_DTYPE: nx_int = 1
92const NX_AS_ERR_BAD_NDIM: nx_int = 2
93const NX_AS_ERR_SHAPE_MISMATCH: nx_int = 3
94const NX_AS_ERR_NOT_CONTIGUOUS: nx_int = 4
95const NX_AS_N_VERDICTS: nx_int = 5
96
97func nx_as_verdict_is_valid(v: nx_int) -> nx_int {
98 if v < 0 { return 0 }
99 if v >= NX_AS_N_VERDICTS { return 0 }
100 return 1
101}
102
103// ===== Apply a single steering vector ============================
104//
105// hidden: [n_tokens, hidden_dim] Q10 (in-place modified)
106// steer: [hidden_dim] Q10
107// alpha: nx_int Q10 strength (positive = move
108// toward target axis; negative
109// = move away)
110
111func nx_activation_steer_apply(hidden: *NxTensor, steer: *i64,
112 alpha_q10: nx_int) -> nx_int {
113 if hidden.dtype != NX_DT_I64 { return NX_AS_ERR_BAD_DTYPE }
114 if hidden.ndim != 2 { return NX_AS_ERR_BAD_NDIM }
115 if nx_t_is_contiguous(hidden) == 0 { return NX_AS_ERR_NOT_CONTIGUOUS }
116
117 let n_tokens: nx_int = hidden.shape[0]
118 let hidden_dim: nx_int = hidden.shape[1]
119 let ph: *i64 = hidden.storage as *i64
120
121 var t: nx_int = 0
122 var t_iter: nx_int = 0
123 var t_verdict: nx_int = NX_LOOP_RUNNING
124 let T_BUDGET: nx_int = n_tokens
125 while t_verdict == NX_LOOP_RUNNING && t_iter < T_BUDGET {
126 let row_base: nx_int = t * hidden_dim
127 var d: nx_int = 0
128 var d_iter: nx_int = 0
129 var d_verdict: nx_int = NX_LOOP_RUNNING
130 let D_BUDGET: nx_int = hidden_dim
131 while d_verdict == NX_LOOP_RUNNING && d_iter < D_BUDGET {
132 // delta = alpha * steer[d] / Q10
133 let delta: i64 = (alpha_q10 * steer[d]) / NX_AS_Q10
134 ph[row_base + d] = ph[row_base + d] + delta
135 d = d + 1
136 d_iter = d_iter + 1
137 }
138 t = t + 1
139 t_iter = t_iter + 1
140 }
141 return NX_AS_OK
142}
143
144// ===== Apply multiple steering vectors ==========================
145//
146// Composability win. Caller passes:
147// steer_vectors: [n_vecs * hidden_dim] flat Q10
148// alphas_q10: [n_vecs] per-vector strength
149//
150// Substrate accumulates: hidden += sum_i alphas[i] * steer_vectors[i]
151
152func nx_activation_steer_apply_multi(hidden: *NxTensor,
153 steer_vectors: *i64, alphas_q10: *i64,
154 n_vecs: nx_int) -> nx_int {
155 if hidden.dtype != NX_DT_I64 { return NX_AS_ERR_BAD_DTYPE }
156 if hidden.ndim != 2 { return NX_AS_ERR_BAD_NDIM }
157 if nx_t_is_contiguous(hidden) == 0 { return NX_AS_ERR_NOT_CONTIGUOUS }
158 if n_vecs <= 0 { return NX_AS_OK } // no-op
159
160 let n_tokens: nx_int = hidden.shape[0]
161 let hidden_dim: nx_int = hidden.shape[1]
162 let ph: *i64 = hidden.storage as *i64
163
164 var t: nx_int = 0
165 var t_iter: nx_int = 0
166 var t_verdict: nx_int = NX_LOOP_RUNNING
167 let T_BUDGET: nx_int = n_tokens
168 while t_verdict == NX_LOOP_RUNNING && t_iter < T_BUDGET {
169 let row_base: nx_int = t * hidden_dim
170 var d: nx_int = 0
171 var d_iter: nx_int = 0
172 var d_verdict: nx_int = NX_LOOP_RUNNING
173 while d_verdict == NX_LOOP_RUNNING && d_iter < hidden_dim {
174 var delta: i64 = 0
175 var v: nx_int = 0
176 var v_iter: nx_int = 0
177 var v_verdict: nx_int = NX_LOOP_RUNNING
178 let V_BUDGET: nx_int = n_vecs
179 while v_verdict == NX_LOOP_RUNNING && v_iter < V_BUDGET {
180 let vec_base: nx_int = v * hidden_dim
181 delta = delta + (alphas_q10[v] * steer_vectors[vec_base + d]) / NX_AS_Q10
182 v = v + 1
183 v_iter = v_iter + 1
184 }
185 ph[row_base + d] = ph[row_base + d] + delta
186 d = d + 1
187 d_iter = d_iter + 1
188 }
189 t = t + 1
190 t_iter = t_iter + 1
191 }
192 return NX_AS_OK
193}
194
195// ===== Derive a steering vector via contrastive activations ====
196//
197// Given two activation buffers (one for the "target" prompt, one
198// for the "anti-target" prompt), at the same layer, compute:
199// steer[d] = mean_over_tokens(target[t, d]) -
200// mean_over_tokens(anti[t, d])
201//
202// target / anti must have the same shape [n_tokens, hidden_dim].
203// out_steer must be allocated [hidden_dim].
204
205func nx_activation_steer_derive(target_act: *NxTensor, anti_act: *NxTensor,
206 out_steer: *i64) -> nx_int {
207 if target_act.dtype != NX_DT_I64 { return NX_AS_ERR_BAD_DTYPE }
208 if anti_act.dtype != NX_DT_I64 { return NX_AS_ERR_BAD_DTYPE }
209 if target_act.ndim != 2 { return NX_AS_ERR_BAD_NDIM }
210 if anti_act.ndim != 2 { return NX_AS_ERR_BAD_NDIM }
211 if target_act.shape[0] != anti_act.shape[0] { return NX_AS_ERR_SHAPE_MISMATCH }
212 if target_act.shape[1] != anti_act.shape[1] { return NX_AS_ERR_SHAPE_MISMATCH }
213
214 let n_tokens: nx_int = target_act.shape[0]
215 let hidden_dim: nx_int = target_act.shape[1]
216 let pt: *i64 = target_act.storage as *i64
217 let pa: *i64 = anti_act.storage as *i64
218
219 var d: nx_int = 0
220 var d_iter: nx_int = 0
221 var d_verdict: nx_int = NX_LOOP_RUNNING
222 let D_BUDGET: nx_int = hidden_dim
223 while d_verdict == NX_LOOP_RUNNING && d_iter < D_BUDGET {
224 var sum_t: i64 = 0
225 var sum_a: i64 = 0
226 var k: nx_int = 0
227 var k_iter: nx_int = 0
228 var k_verdict: nx_int = NX_LOOP_RUNNING
229 let K_BUDGET: nx_int = n_tokens
230 while k_verdict == NX_LOOP_RUNNING && k_iter < K_BUDGET {
231 sum_t = sum_t + pt[k * hidden_dim + d]
232 sum_a = sum_a + pa[k * hidden_dim + d]
233 k = k + 1
234 k_iter = k_iter + 1
235 }
236 out_steer[d] = (sum_t - sum_a) / n_tokens
237 d = d + 1
238 d_iter = d_iter + 1
239 }
240 return NX_AS_OK
241}
242
243// ===== Self-test ==================================================
244//
245// Smoke: small 4-token, 8-dim hidden tensor. Steering vector
246// (1, 1, 1, 1, -1, -1, -1, -1). Apply with alpha = Q10 (1.0).
247// Expected: every token's first 4 dims increase by Q10, last 4
248// decrease by Q10.
249//
250// Then verify multi-vector composition: 2 vectors with alphas
251// [Q10, 2*Q10] should accumulate appropriately.
252//
253// Then verify derive: target_act all 100; anti_act all 50; per-dim
254// mean diff = 50. out_steer[d] = 50 for all d.
255
256func main() -> i64 {
257 let n_tokens: nx_int = 4
258 let hidden_dim: nx_int = 8
259
260 let sh: *nx_int = sys_mmap(2 * 8) as *nx_int
261 sh[0] = n_tokens; sh[1] = hidden_dim
262
263 let err: *nx_int = sys_mmap(8) as *nx_int
264 err[0] = 0
265 let hidden: *NxTensor = nx_t_alloc(NX_DT_I64, sh, 2, err)
266 if err[0] != 0 { return 5 }
267
268 // Initialize hidden to known values: hidden[t, d] = 1000.
269 let ph: *i64 = hidden.storage as *i64
270 var i: nx_int = 0
271 while i < n_tokens * hidden_dim { ph[i] = 1000; i = i + 1 }
272
273 // Steering vector: +Q10 in dims 0..3, -Q10 in dims 4..7.
274 let steer: *i64 = sys_mmap(hidden_dim * 8) as *i64
275 steer[0]=NX_MAGIC_1024; steer[1]=NX_MAGIC_1024; steer[2]=NX_MAGIC_1024; steer[3]=NX_MAGIC_1024
276 steer[4]=-NX_MAGIC_1024; steer[5]=-NX_MAGIC_1024; steer[6]=-NX_MAGIC_1024; steer[7]=-NX_MAGIC_1024
277
278 // --- (a) Apply with alpha = Q10 ---
279 let v_a: nx_int = nx_activation_steer_apply(hidden, steer, NX_MAGIC_1024)
280 if v_a != NX_AS_OK { return 10 }
281 // Expected: dim 0..3 -> 1000 + 1024 = 2024
282 // Expected: dim 4..7 -> 1000 - 1024 = -24
283 if ph[0] != NX_MAGIC_2024 { return 11 }
284 if ph[3] != NX_MAGIC_2024 { return 12 }
285 if ph[4] != -24 { return 13 }
286 if ph[7] != -24 { return 14 }
287 // Token 1: same pattern.
288 if ph[hidden_dim + 0] != NX_MAGIC_2024 { return 15 }
289
290 // --- (b) Multi-vector compose ---
291 // Reset hidden to 1000.
292 var j: nx_int = 0
293 while j < n_tokens * hidden_dim { ph[j] = 1000; j = j + 1 }
294 // Two steer vectors flat: vec[0] = +Q10 across dims 0-3; vec[1]
295 // = +Q10 across dims 4-7 (so all dims should go up).
296 let steers: *i64 = sys_mmap(2 * hidden_dim * 8) as *i64
297 var k: nx_int = 0
298 while k < 4 { steers[k] = NX_MAGIC_1024; steers[k + 4] = 0; k = k + 1 }
299 while k < 8 { steers[k] = 0; steers[k + 4] = NX_MAGIC_1024; k = k + 1 }
300 let alphas: *i64 = sys_mmap(2 * 8) as *i64
301 alphas[0] = NX_MAGIC_1024 // alpha 1.0
302 alphas[1] = NX_MAGIC_2048 // alpha 2.0
303
304 let v_b: nx_int = nx_activation_steer_apply_multi(hidden, steers, alphas, 2)
305 if v_b != NX_AS_OK { return 20 }
306 // Expected dim 0..3: 1000 + 1*1024 = 2024
307 if ph[0] != NX_MAGIC_2024 { return 21 }
308 // Expected dim 4..7: 1000 + 2*1024 = 3048
309 if ph[4] != NX_MAGIC_3048 { return 22 }
310
311 // --- (c) Derive steering vector ---
312 let target_act: *NxTensor = nx_t_alloc(NX_DT_I64, sh, 2, err)
313 let anti_act: *NxTensor = nx_t_alloc(NX_DT_I64, sh, 2, err)
314 if err[0] != 0 { return 30 }
315 let pt: *i64 = target_act.storage as *i64
316 let pa: *i64 = anti_act.storage as *i64
317 var m: nx_int = 0
318 while m < n_tokens * hidden_dim {
319 pt[m] = 100
320 pa[m] = 50
321 m = m + 1
322 }
323 let out_steer: *i64 = sys_mmap(hidden_dim * 8) as *i64
324 let v_c: nx_int = nx_activation_steer_derive(target_act, anti_act, out_steer)
325 if v_c != NX_AS_OK { return 40 }
326 // Expected: out_steer[d] = mean_t(100) - mean_a(50) = 50 for all d
327 var n: nx_int = 0
328 while n < hidden_dim {
329 if out_steer[n] != 50 { return 50 }
330 n = n + 1
331 }
332
333 // --- (d) Verdict gate ---
334 var vi: nx_int = 0
335 while vi < NX_AS_N_VERDICTS {
336 if nx_as_verdict_is_valid(vi) != 1 { return 60 + vi }
337 vi = vi + 1
338 }
339
340 return 0
341}