code wiki / (root) / nx_fft_f32.nx

nx_fft_f32.nx source

↩ module page · 117 lines · 4774 B

1// nx_fft_f32.nx -- f32 radix-2 FFT + f32 FNet 2D-DFT token mixer. The bridge that lets the sub-quadratic FNet 2// mixer (FNET-001/002, which ran in i64 Q14 fixed-point) plug into the f32 autograd tape so the block becomes 3// TRAINABLE. Twiddles are synthesized by the sovereign CORDIC (fx_cos/fx_sin, Q16.16) then converted to f32; 4// because f32 twiddles are unit-magnitude actual values, the butterfly needs NO per-stage descaling -- cleaner 5// (and more accurate) than the fixed-point version. DRY: reuses nx_fft_log2 + nx_fft_bit_reverse. 6// license_tier: ORIGINAL 7import "nx_fft.nx" // nx_fft_log2 + nx_fft_bit_reverse (integer, length-generic) 8import "fx.nx" // CORDIC fx_cos/fx_sin + FX_TWO_PI 9import "nx_f32.nx" // f32 add/sub/mul 10import "nx_f32_div.nx" // nx_f32_div 11import "nx_f32_cvt.nx" // nx_i32_to_f32 12import "nx_syscalls.nx" 13const FFTF_MAGIC_65536: i64 = 65536 14 15const FFTF_F32_ZERO: i64 = 0 16 17// Q16.16 (CORDIC) integer -> f32 value (q / 65536) 18func fftf_q16_to_f32(q: i64) -> i64 { return nx_f32_div(nx_i32_to_f32(q), nx_i32_to_f32(FFTF_MAGIC_65536)) } 19 20// length-n twiddle table (n/2 entries) in f32: tw_re[k]=cos(2pi k/n), tw_im[k]=-sin(2pi k/n) 21func fftf_twiddles(n: i64, tw_re: *i64, tw_im: *i64) -> i64 { 22 let half: i64 = n / 2 23 var k: i64 = 0 24 while k < half { 25 let ang: i64 = FX_TWO_PI * k / n 26 tw_re[k] = fftf_q16_to_f32(fx_cos(ang)) 27 tw_im[k] = fftf_q16_to_f32(0 - fx_sin(ang)) 28 k = k + 1 29 } 30 return 0 31} 32 33// in-place f32 radix-2 forward FFT (re/im are parallel f32 arrays of length n; tw built by fftf_twiddles(n,..)) 34func fftf_fwd(re: *i64, im: *i64, n: i64, tw_re: *i64, tw_im: *i64) -> i64 { 35 let log2n: i64 = nx_fft_log2(n) 36 var i: i64 = 0 37 while i < n { 38 let j: i64 = nx_fft_bit_reverse(i, log2n) 39 if i < j { 40 let tr: i64 = re[i]; let ti: i64 = im[i] 41 re[i] = re[j]; im[i] = im[j]; re[j] = tr; im[j] = ti 42 } 43 i = i + 1 44 } 45 var m: i64 = 1 46 while m < n { 47 let m2: i64 = m * 2 48 let twstride: i64 = n / m2 49 var k: i64 = 0 50 while k < n { 51 var j2: i64 = 0 52 while j2 < m { 53 let idx: i64 = j2 * twstride 54 let w_r: i64 = tw_re[idx]; let w_i: i64 = tw_im[idx] 55 let x_r: i64 = re[k + j2 + m]; let x_i: i64 = im[k + j2 + m] 56 let t_r: i64 = nx_f32_sub(nx_f32_mul(w_r, x_r), nx_f32_mul(w_i, x_i)) 57 let t_i: i64 = nx_f32_add(nx_f32_mul(w_r, x_i), nx_f32_mul(w_i, x_r)) 58 let u_r: i64 = re[k + j2]; let u_i: i64 = im[k + j2] 59 re[k + j2] = nx_f32_add(u_r, t_r) 60 im[k + j2] = nx_f32_add(u_i, t_i) 61 re[k + j2 + m] = nx_f32_sub(u_r, t_r) 62 im[k + j2 + m] = nx_f32_sub(u_i, t_i) 63 j2 = j2 + 1 64 } 65 k = k + m2 66 } 67 m = m2 68 } 69 return 0 70} 71 72// FNet 2D-DFT real-part mixer over an [n,d] row-major f32 matrix. Row pass (length d) then column pass (length 73// n); real part stays in x_re, x_im is scratch. Self-adjoint (proven FNET-002), so backprop reuses this fn. 74func fnet_mix_f32(x_re: *i64, x_im: *i64, n: i64, d: i64) -> i64 { 75 let twr_d: *i64 = (sys_mmap(d * 8)) as *i64 76 let twi_d: *i64 = (sys_mmap(d * 8)) as *i64 77 fftf_twiddles(d, twr_d, twi_d) 78 let rr: *i64 = (sys_mmap(d * 8)) as *i64 79 let ri: *i64 = (sys_mmap(d * 8)) as *i64 80 var y: i64 = 0 81 while y < n { 82 var c: i64 = 0 83 while c < d { rr[c] = x_re[y * d + c]; ri[c] = x_im[y * d + c]; c = c + 1 } 84 fftf_fwd(rr, ri, d, twr_d, twi_d) 85 c = 0 86 while c < d { x_re[y * d + c] = rr[c]; x_im[y * d + c] = ri[c]; c = c + 1 } 87 y = y + 1 88 } 89 let twr_n: *i64 = (sys_mmap(n * 8)) as *i64 90 let twi_n: *i64 = (sys_mmap(n * 8)) as *i64 91 fftf_twiddles(n, twr_n, twi_n) 92 let cr: *i64 = (sys_mmap(n * 8)) as *i64 93 let ci: *i64 = (sys_mmap(n * 8)) as *i64 94 var xc: i64 = 0 95 while xc < d { 96 var r2: i64 = 0 97 while r2 < n { cr[r2] = x_re[r2 * d + xc]; ci[r2] = x_im[r2 * d + xc]; r2 = r2 + 1 } 98 fftf_fwd(cr, ci, n, twr_n, twi_n) 99 r2 = 0 100 while r2 < n { x_re[r2 * d + xc] = cr[r2]; x_im[r2 * d + xc] = ci[r2]; r2 = r2 + 1 } 101 xc = xc + 1 102 } 103 return 0 104} 105 106// smoke: FFT of [1,1,1,1] -> bin0 = 4, others ~0 107func main() -> i64 { 108 let re: *i64 = (sys_mmap(4 * 8)) as *i64 109 let im: *i64 = (sys_mmap(4 * 8)) as *i64 110 var i: i64 = 0 111 while i < 4 { re[i] = nx_i32_to_f32(1); im[i] = FFTF_F32_ZERO; i = i + 1 } 112 let twr: *i64 = (sys_mmap(2 * 8)) as *i64 113 let twi: *i64 = (sys_mmap(2 * 8)) as *i64 114 fftf_twiddles(4, twr, twi) 115 fftf_fwd(re, im, 4, twr, twi) 116 return 0 117}