code wiki / (root) / nx_lowrank_attn.nx

nx_lowrank_attn.nx source

↩ module page · 402 lines · 17040 B

1// nx_lowrank_attn.nx -- M3: low-rank KV through a REAL softmax-attention forward (end-to-end quality proof). 2// 3// module: nishi-core.ai.lowrank_attn capability: VRAM_REDUCTION (per-token low-rank KV, measured end-to-end) 4// 5// M0/M1 measured COVARIANCE fidelity (K^T K) of an FD sketch. But softmax attention needs PER-TOKEN scores 6// Q.K[i], which an FD covariance sketch cannot give. The per-token low-rank compression that DOES work is 7// Eigen-Attention / SVD-projection: find the top-r right singular basis V_r [r x d] of K (top-r eigenvectors 8// of K^T K), store per-token coords[i] = K[i] @ V_r^T (n x r) + the shared basis (r x d), reconstruct 9// K[i] ~= coords[i] @ V_r. This PRESERVES per-token identity -> usable in real attention. The eigen-extraction 10// REUSES the M1 lesson: reorthogonalize (Gram-Schmidt twice) or the dominant direction leaks under f32. 11// 12// PROOF: compare the attention OUTPUT (after softmax + value mixing) full-KV vs compressed-KV. For genuinely 13// rank-2 K/V, compressing to r>=2 reconstructs exactly -> output error ~0 (VRAM cut, quality kept end-to-end). 14// Sovereign: nx_f32_hw (SSE arith) + nx_f32_sqrt + nx_f32_softmax (reused). no gcc. HONEST: reference-scale d=8. 15import "nx_syscalls.nx" 16import "nx_f32_hw.nx" 17import "nx_f32.nx" 18import "nx_f32_softmax.nx" 19const LA_MAGIC_1103515245: i64 = 1103515245 20const LA_MAGIC_12345: i64 = 12345 21const LA_MAGIC_2048: i64 = 2048 22const LA_MAGIC_4096: i64 = 4096 23const LA_MAGIC_99991: i64 = 99991 24const LA_MAGIC_11003: i64 = 11003 25const LA_MAGIC_22013: i64 = 22013 26const LA_MAGIC_33029: i64 = 33029 27const LA_MAGIC_44039: i64 = 44039 28const LA_MAGIC_55049: i64 = 55049 29const LA_MAGIC_60061: i64 = 60061 30const LA_MAGIC_70067: i64 = 70067 31const LA_MAGIC_80071: i64 = 80071 32 33const LA_POWER_ITERS: i64 = 64 34 35// ---- stdout ---- 36func la_strlen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 37func g_puts(s: *u8) -> i64 { sys_write(1, s, la_strlen(s)); return 0 } 38func g_putn(v: i64) -> i64 { 39 if v == 0 { sys_write(1, "0" as *u8, 1); return 0 } 40 var m: i64 = v 41 if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m } 42 let d: *u8 = sys_mmap(24); var k: i64 = 0 43 while m > 0 { d[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 44 var j: i64 = k - 1 45 while j >= 0 { sys_write(1, ((d as i64)+j) as *u8, 1); j = j - 1 } 46 return 0 47} 48 49// ---- f32 bit predicate ---- 50func la_is_zero(x: i64) -> i64 { if (x & 0x7fffffff) == 0 { return 1 } return 0 } 51 52func f32frac(num: i64, den: i64) -> i64 { return f32_div(f32_of(num), f32_of(den)) } 53 54// ---- pseudo-random stream (high bits) ---- 55func la_lcg(st: *i64) -> i64 { st[0] = st[0] * LA_MAGIC_1103515245 + LA_MAGIC_12345; return (st[0] >> 20) & 0xfff } 56 57// genuinely rank-r matrix M[n x d] = C[n x r] @ B[r x d], zero-mean random factors. 58func la_build_rankr(M: *i64, n: i64, d: i64, r: i64, seed: i64) -> i64 { 59 let st: *i64 = sys_mmap(8) as *i64 60 st[0] = seed 61 let C: *i64 = sys_mmap(n * r * 8) as *i64 62 let B: *i64 = sys_mmap(r * d * 8) as *i64 63 var t: i64 = 0 64 while t < n * r { C[t] = f32frac(la_lcg(st) - LA_MAGIC_2048, LA_MAGIC_4096); t = t + 1 } 65 t = 0 66 while t < r * d { B[t] = f32frac(la_lcg(st) - LA_MAGIC_2048, LA_MAGIC_4096); t = t + 1 } 67 var i: i64 = 0 68 while i < n { 69 var c: i64 = 0 70 while c < d { 71 var acc: i64 = 0 72 var j: i64 = 0 73 while j < r { acc = f32_add(acc, f32_mul(C[i * r + j], B[j * d + c])); j = j + 1 } 74 M[i * d + c] = acc 75 c = c + 1 76 } 77 i = i + 1 78 } 79 return 0 80} 81 82// ---- f32 vector ops (hw SSE arith + sw sqrt) ---- 83func la_dot(a: *i64, b: *i64, n: i64) -> i64 { 84 var s: i64 = 0 85 var i: i64 = 0 86 while i < n { s = f32_add(s, f32_mul(a[i], b[i])); i = i + 1 } 87 return s 88} 89 90func la_normalize(v: *i64, n: i64) -> i64 { 91 let ns: i64 = la_dot(v, v, n) 92 if la_is_zero(ns) == 1 { return -1 } 93 let nrm: i64 = nx_f32_sqrt(ns) 94 if la_is_zero(nrm) == 1 { return -1 } 95 let inv: i64 = f32_div(f32_of(1), nrm) 96 var i: i64 = 0 97 while i < n { v[i] = f32_mul(v[i], inv); i = i + 1 } 98 return 0 99} 100 101// G @ v (G is d x d symmetric) 102func la_gmatvec(G: *i64, v: *i64, d: i64, out: *i64) -> i64 { 103 var a: i64 = 0 104 while a < d { 105 var s: i64 = 0 106 var b: i64 = 0 107 while b < d { s = f32_add(s, f32_mul(G[a * d + b], v[b])); b = b + 1 } 108 out[a] = s 109 a = a + 1 110 } 111 return 0 112} 113 114// reorthogonalize v against basis[0..found-1] (each d long) -- Gram-Schmidt TWICE (M1 lesson). 115func la_deflate_once(v: *i64, basis: *i64, found: i64, d: i64) -> i64 { 116 var j: i64 = 0 117 while j < found { 118 var s: i64 = 0 119 var c: i64 = 0 120 while c < d { s = f32_add(s, f32_mul(v[c], basis[j * d + c])); c = c + 1 } 121 c = 0 122 while c < d { v[c] = f32_sub(v[c], f32_mul(s, basis[j * d + c])); c = c + 1 } 123 j = j + 1 124 } 125 return 0 126} 127func la_reorth(v: *i64, basis: *i64, found: i64, d: i64) -> i64 { 128 la_deflate_once(v, basis, found, d); la_deflate_once(v, basis, found, d); return 0 129} 130 131// top-r eigenvectors of G [d x d] -> basis[r x d] (power iteration + reorth deflation). 132func la_topr_basis(G: *i64, d: i64, r: i64, basis: *i64) -> i64 { 133 let v: *i64 = sys_mmap(d * 8) as *i64 134 let w: *i64 = sys_mmap(d * 8) as *i64 135 var f: i64 = 0 136 while f < r { 137 var c: i64 = 0 138 while c < d { v[c] = 0; c = c + 1 } 139 v[f % d] = f32_of(1) 140 la_reorth(v, basis, f, d) 141 if la_normalize(v, d) < 0 { 142 c = 0 143 while c < d { basis[f * d + c] = 0; c = c + 1 } 144 f = f + 1 145 } else { 146 var it: i64 = 0 147 while it < LA_POWER_ITERS { 148 la_gmatvec(G, v, d, w) 149 la_reorth(w, basis, f, d) 150 if la_normalize(w, d) < 0 { it = LA_POWER_ITERS } else { 151 c = 0 152 while c < d { v[c] = w[c]; c = c + 1 } 153 it = it + 1 154 } 155 } 156 c = 0 157 while c < d { basis[f * d + c] = v[c]; c = c + 1 } 158 f = f + 1 159 } 160 } 161 return 0 162} 163 164// G = M^T M [d x d] 165func la_gram(M: *i64, n: i64, d: i64, G: *i64) -> i64 { 166 var a: i64 = 0 167 while a < d { 168 var b: i64 = 0 169 while b < d { 170 var s: i64 = 0 171 var i: i64 = 0 172 while i < n { s = f32_add(s, f32_mul(M[i * d + a], M[i * d + b])); i = i + 1 } 173 G[a * d + b] = s 174 b = b + 1 175 } 176 a = a + 1 177 } 178 return 0 179} 180 181// compress M[n x d] to rank r (per-token SVD projection) and reconstruct into Mout[n x d]. 182// stored size = n*r (coords) + r*d (basis); returned via the reconstruction (proof of fidelity). 183func la_compress_reconstruct(M: *i64, n: i64, d: i64, r: i64, Mout: *i64) -> i64 { 184 let G: *i64 = sys_mmap(d * d * 8) as *i64 185 let basis: *i64 = sys_mmap(r * d * 8) as *i64 186 la_gram(M, n, d, G) 187 la_topr_basis(G, d, r, basis) 188 // per token: coords[j] = M[i] . basis[j]; Mout[i] = sum_j coords[j]*basis[j] 189 var i: i64 = 0 190 while i < n { 191 var j: i64 = 0 192 let coords: *i64 = sys_mmap(r * 8) as *i64 193 while j < r { 194 var s: i64 = 0 195 var c: i64 = 0 196 while c < d { s = f32_add(s, f32_mul(M[i * d + c], basis[j * d + c])); c = c + 1 } 197 coords[j] = s 198 j = j + 1 199 } 200 var c2: i64 = 0 201 while c2 < d { 202 var s2: i64 = 0 203 j = 0 204 while j < r { s2 = f32_add(s2, f32_mul(coords[j], basis[j * d + c2])); j = j + 1 } 205 Mout[i * d + c2] = s2 206 c2 = c2 + 1 207 } 208 i = i + 1 209 } 210 return 0 211} 212 213// single-query attention: out[d] = softmax(Q.K[i]*scale) . V 214func la_attention(Q: *i64, Km: *i64, Vm: *i64, n: i64, d: i64, scale: i64, out: *i64) -> i64 { 215 let scores: *i64 = sys_mmap(n * 8) as *i64 216 let weights: *i64 = sys_mmap(n * 8) as *i64 217 var i: i64 = 0 218 while i < n { 219 var s: i64 = 0 220 var c: i64 = 0 221 while c < d { s = f32_add(s, f32_mul(Q[c], Km[i * d + c])); c = c + 1 } 222 scores[i] = f32_mul(s, scale) 223 i = i + 1 224 } 225 nx_f32_softmax(scores, n, weights) 226 var c3: i64 = 0 227 while c3 < d { 228 var s3: i64 = 0 229 i = 0 230 while i < n { s3 = f32_add(s3, f32_mul(weights[i], Vm[i * d + c3])); i = i + 1 } 231 out[c3] = s3 232 c3 = c3 + 1 233 } 234 return 0 235} 236 237// relative L2 error of a vs reference b, in permille 238func la_rel_permille(a: *i64, b: *i64, d: i64) -> i64 { 239 var num: i64 = 0 240 var den: i64 = 0 241 var i: i64 = 0 242 while i < d { 243 let diff: i64 = f32_sub(a[i], b[i]) 244 num = f32_add(num, f32_mul(diff, diff)) 245 den = f32_add(den, f32_mul(b[i], b[i])) 246 i = i + 1 247 } 248 if la_is_zero(den) == 1 { return 0 } 249 let rel: i64 = nx_f32_sqrt(f32_div(num, den)) 250 return f32_int(f32_mul(rel, f32_of(1000))) 251} 252 253// run attention with K,V compressed to rank r; return output rel-error vs full-KV reference out_full. 254func la_run_compressed(Q: *i64, K: *i64, V: *i64, n: i64, d: i64, r: i64, scale: i64, out_full: *i64) -> i64 { 255 let Ka: *i64 = sys_mmap(n * d * 8) as *i64 256 let Va: *i64 = sys_mmap(n * d * 8) as *i64 257 la_compress_reconstruct(K, n, d, r, Ka) 258 la_compress_reconstruct(V, n, d, r, Va) 259 let outc: *i64 = sys_mmap(d * 8) as *i64 260 la_attention(Q, Ka, Va, n, d, scale, outc) 261 return la_rel_permille(outc, out_full, d) 262} 263 264// approximately low-rank: dominant rank-`base` + small full-rank noise tail = what REAL KV looks like. 265func la_build_approx(M: *i64, n: i64, d: i64, base: i64, noise_num: i64, seed: i64) -> i64 { 266 la_build_rankr(M, n, d, base, seed) 267 let st: *i64 = sys_mmap(8) as *i64; st[0] = seed + LA_MAGIC_99991 268 let nf: i64 = f32frac(noise_num, 1000) 269 var i: i64 = 0 270 while i < n * d { M[i] = f32_add(M[i], f32_mul(nf, f32frac(la_lcg(st) - LA_MAGIC_2048, LA_MAGIC_4096))); i = i + 1 } 271 return 0 272} 273 274func main() -> i64 { 275 let d: i64 = 8 276 let n: i64 = 64 277 g_puts("=== M3: low-rank KV through a REAL softmax-attention forward (end-to-end output error) ===\n") 278 g_puts(" d="); g_putn(d); g_puts(" n="); g_putn(n); g_puts(" compress K,V to rank r (per-token SVD projection)\n\n") 279 280 let scale: i64 = f32_div(f32_of(1), nx_f32_sqrt(f32_of(d))) 281 282 // genuinely rank-2 K and V; random query Q 283 let K: *i64 = sys_mmap(n * d * 8) as *i64 284 let V: *i64 = sys_mmap(n * d * 8) as *i64 285 la_build_rankr(K, n, d, 2, LA_MAGIC_11003) 286 la_build_rankr(V, n, d, 2, LA_MAGIC_22013) 287 let Q: *i64 = sys_mmap(d * 8) as *i64 288 let st: *i64 = sys_mmap(8) as *i64; st[0] = LA_MAGIC_33029 289 var c: i64 = 0 290 while c < d { Q[c] = f32frac(la_lcg(st) - LA_MAGIC_2048, LA_MAGIC_4096); c = c + 1 } 291 292 // full-KV attention reference 293 let out_full: *i64 = sys_mmap(d * 8) as *i64 294 la_attention(Q, K, V, n, d, scale, out_full) 295 296 // sweep r over rank-2 K,V 297 g_puts(" rank-2 K,V (true rank=2; compress to r):\n") 298 let full_entries: i64 = 2 * n * d 299 var err_r1: i64 = 0 300 var err_r2: i64 = 0 301 let rs: *i64 = sys_mmap(8 * 8) as *i64 302 rs[0] = 1; rs[1] = 2; rs[2] = 3; rs[3] = 4 303 var idx: i64 = 0 304 while idx < 4 { 305 let r: i64 = rs[idx] 306 let e: i64 = la_run_compressed(Q, K, V, n, d, r, scale, out_full) 307 let comp_entries: i64 = 2 * n * r + 2 * r * d 308 g_puts(" r="); g_putn(r); g_puts(" KV entries "); g_putn(full_entries); g_puts("->"); g_putn(comp_entries) 309 g_puts(" ("); g_putn(full_entries / comp_entries); g_puts("x) attn_out_err="); g_putn(e); g_puts("permille") 310 if r >= 2 { g_puts(" [r>=rank: preserved]") } else { g_puts(" [r<rank: lossy]") } 311 g_puts("\n") 312 if r == 1 { err_r1 = e } 313 if r == 2 { err_r2 = e } 314 idx = idx + 1 315 } 316 317 // neg-control: genuinely FULL-rank K,V compressed to r=4 -> output must degrade 318 let Kf: *i64 = sys_mmap(n * d * 8) as *i64 319 let Vf: *i64 = sys_mmap(n * d * 8) as *i64 320 la_build_rankr(Kf, n, d, d, LA_MAGIC_44039) 321 la_build_rankr(Vf, n, d, d, LA_MAGIC_55049) 322 let outf_full: *i64 = sys_mmap(d * 8) as *i64 323 la_attention(Q, Kf, Vf, n, d, scale, outf_full) 324 let err_full: i64 = la_run_compressed(Q, Kf, Vf, n, d, 4, scale, outf_full) 325 g_puts("\n full-rank K,V compressed to r=4 (neg-control) attn_out_err="); g_putn(err_full); g_puts("permille\n\n") 326 327 // --- scale-d sweep (rank-2 K,V @ r=2): output fidelity holds while VRAM ratio GROWS with d 328 // (retires the d=8-reference caveat; the per-token SVD path has NO d limit, unlike the FD sketch) --- 329 g_puts(" scale-d sweep (rank-2 K,V, compress to r=2):\n") 330 let ds: *i64 = sys_mmap(8 * 8) as *i64 331 ds[0] = 8; ds[1] = 32; ds[2] = 64; ds[3] = 128 332 var err_d128: i64 = 0 333 var di: i64 = 0 334 while di < 4 { 335 let dd: i64 = ds[di] 336 let Kd: *i64 = sys_mmap(n * dd * 8) as *i64 337 let Vd: *i64 = sys_mmap(n * dd * 8) as *i64 338 la_build_rankr(Kd, n, dd, 2, LA_MAGIC_11003 + dd) 339 la_build_rankr(Vd, n, dd, 2, LA_MAGIC_22013 + dd) 340 let Qd: *i64 = sys_mmap(dd * 8) as *i64 341 let std: *i64 = sys_mmap(8) as *i64; std[0] = LA_MAGIC_33029 + dd 342 var cc: i64 = 0 343 while cc < dd { Qd[cc] = f32frac(la_lcg(std) - LA_MAGIC_2048, LA_MAGIC_4096); cc = cc + 1 } 344 let scaled: i64 = f32_div(f32_of(1), nx_f32_sqrt(f32_of(dd))) 345 let of: *i64 = sys_mmap(dd * 8) as *i64 346 la_attention(Qd, Kd, Vd, n, dd, scaled, of) 347 let ed: i64 = la_run_compressed(Qd, Kd, Vd, n, dd, 2, scaled, of) 348 let fe: i64 = 2 * n * dd 349 let ce: i64 = 2 * n * 2 + 2 * 2 * dd 350 g_puts(" d="); g_putn(dd); g_puts(" KV entries "); g_putn(fe); g_puts("->"); g_putn(ce) 351 g_puts(" ("); g_putn(fe / ce); g_puts("x) attn_out_err="); g_putn(ed); g_puts("permille\n") 352 if dd == 128 { err_d128 = ed } 353 di = di + 1 354 } 355 g_puts("\n") 356 357 // --- approx-low-rank sweep (d=64, dominant rank-2 + noise tail = realistic KV; compress r=2) --- 358 g_puts(" approx-low-rank sweep (d=64, dominant rank-2 + noise tail = realistic KV; r=2):\n") 359 let noises: *i64 = sys_mmap(8 * 8) as *i64 360 noises[0] = 0; noises[1] = 50; noises[2] = 150; noises[3] = 400 361 var err_n5: i64 = 0 362 var ni: i64 = 0 363 while ni < 4 { 364 let nz: i64 = noises[ni] 365 let Ka: *i64 = sys_mmap(n * 64 * 8) as *i64 366 let Va: *i64 = sys_mmap(n * 64 * 8) as *i64 367 la_build_approx(Ka, n, 64, 2, nz, LA_MAGIC_60061 + nz) 368 la_build_approx(Va, n, 64, 2, nz, LA_MAGIC_70067 + nz) 369 let Qa: *i64 = sys_mmap(64 * 8) as *i64 370 let sta: *i64 = sys_mmap(8) as *i64; sta[0] = LA_MAGIC_80071 + nz 371 var cc: i64 = 0 372 while cc < 64 { Qa[cc] = f32frac(la_lcg(sta) - LA_MAGIC_2048, LA_MAGIC_4096); cc = cc + 1 } 373 let sca: i64 = f32_div(f32_of(1), nx_f32_sqrt(f32_of(64))) 374 let ofa: *i64 = sys_mmap(64 * 8) as *i64 375 la_attention(Qa, Ka, Va, n, 64, sca, ofa) 376 let ea: i64 = la_run_compressed(Qa, Ka, Va, n, 64, 2, sca, ofa) 377 g_puts(" noise="); g_putn(nz); g_puts("permille of signal -> attn_out_err="); g_putn(ea); g_puts("permille\n") 378 if nz == 50 { err_n5 = ea } 379 ni = ni + 1 380 } 381 g_puts("\n") 382 383 // --- gate --- 384 var pass: i64 = 0 385 var fail: i64 = 0 386 if err_r2 < 50 { g_puts(" T1 rank-2 KV @ r=2: attention output PRESERVED end-to-end (<50permille): PASS ("); g_putn(err_r2); g_puts(")\n"); pass = pass + 1 } 387 else { g_puts(" T1 output preserved: FAIL ("); g_putn(err_r2); g_puts(")\n"); fail = fail + 1 } 388 if full_entries > (2 * n * 2 + 2 * 2 * d) { g_puts(" T2 VRAM reduced at r=2 ("); g_putn(full_entries); g_puts("->"); g_putn(2 * n * 2 + 2 * 2 * d); g_puts(" entries): PASS\n"); pass = pass + 1 } 389 else { g_puts(" T2 VRAM reduced: FAIL\n"); fail = fail + 1 } 390 if err_full > err_r2 * 3 + 20 { g_puts(" T3 neg-control: full-rank output degrades >> rank-2 (metric real): PASS\n"); pass = pass + 1 } 391 else { g_puts(" T3 neg-control: FAIL\n"); fail = fail + 1 } 392 if err_r1 > err_r2 { g_puts(" T4 graceful: under-ranking (r=1<rank) loses more than r=2: PASS\n"); pass = pass + 1 } 393 else { g_puts(" T4 graceful: FAIL\n"); fail = fail + 1 } 394 if err_d128 < 50 { g_puts(" T5 fidelity holds at d=128 (output err<50permille, ratio grows): PASS ("); g_putn(err_d128); g_puts(")\n"); pass = pass + 1 } 395 else { g_puts(" T5 scale-d fidelity: FAIL ("); g_putn(err_d128); g_puts(")\n"); fail = fail + 1 } 396 if err_n5 < 150 { g_puts(" T6 approx-low-rank (5% noise tail) output err acceptable (<150permille): PASS ("); g_putn(err_n5); g_puts(")\n"); pass = pass + 1 } 397 else { g_puts(" T6 approx-low-rank: FAIL ("); g_putn(err_n5); g_puts(")\n"); fail = fail + 1 } 398 399 g_puts("\n PASS="); g_putn(pass); g_puts("/6 ") 400 if fail == 0 { g_puts("VERDICT=GREEN (low-rank KV preserves REAL attention output; VRAM cut, quality kept end-to-end)\n"); sys_exit(0); return 0 } 401 g_puts("VERDICT=RED\n"); sys_exit(1); return 1 402}