|
| 1 | +//! Gridlake field interdependency = one BF16 16×16 AMX tile op — measured. |
| 2 | +//! |
| 3 | +//! The operator's claim (2026-07-04): a **gridlake**'s cell *interdependency*, |
| 4 | +//! treated as a *field operation*, reduces to a **BF16 16×16 AMX tile GEMM** |
| 5 | +//! (`TDPBF16PS`) — the same shape as the **intercorrelation / perturbation in a |
| 6 | +//! Gaussian splat** (the EWA covariance sandwich `Σ' = Mᵀ·Σ·M`, shipped as |
| 7 | +//! `hpc::splat3d::spd3::sandwich_x16`). |
| 8 | +//! |
| 9 | +//! Why it's true, mechanically: a *field operation* whose output cell is a linear |
| 10 | +//! combination of input cells is a linear operator `C = A·B` — a GEMM. The |
| 11 | +//! "interdependency" / "intercorrelation" IS the coupling matrix (the off-diagonal |
| 12 | +//! second-moment structure). At the gridlake tile granularity (16×16, one quarter |
| 13 | +//! of the 64×64 gridlake in each axis → the AMX tile) that GEMM is exactly one |
| 14 | +//! `hpc::bf16_tile_gemm::bf16_tile_gemm_16x16` call — one `TDPBF16PS` tile op. |
| 15 | +//! This is the SAME primitive as: |
| 16 | +//! • the codec separable transform (#232: `M·X`, the WHT/DCT on a tile), and |
| 17 | +//! • the splat EWA covariance projection (`sandwich_x16`, `Mᵀ·Σ·M`). |
| 18 | +//! One op, three names. |
| 19 | +//! |
| 20 | +//! This probe MEASURES the reduction on the shipped kernel (whatever tier the host |
| 21 | +//! selects — AMX `TDPBF16PS` / AVX-512 `VDPBF16PS` / F32x16 polyfill), reports the |
| 22 | +//! tier, and checks against a direct `f64` reference: |
| 23 | +//! 1. **intercorrelation** `C = Xᵀ·X` of a gridlake field tile (the inter-cell |
| 24 | +//! coupling as a field op) — one tile GEMM. |
| 25 | +//! 2. **covariance sandwich** `Σ' = Mᵀ·Σ·M` (the splat EWA projection shape) — |
| 26 | +//! two tile GEMMs. |
| 27 | +//! 3. **bit-exactness** on bf16-exact integer operands (the module's guarantee), |
| 28 | +//! and BF16-precision relative error on a continuous field. |
| 29 | +//! 4. **throughput** of the 16×16 tile op on this host. |
| 30 | +//! |
| 31 | +//! Run: `cargo run --release --example gridlake_field_tile --features std` |
| 32 | +
|
| 33 | +use ndarray::hpc::amx_matmul::amx_available; |
| 34 | +use ndarray::hpc::bf16_tile_gemm::{bf16_tile_gemm_16x16, bf16_tile_gemm_tier}; |
| 35 | +use ndarray::hpc::quantized::{f32_to_bf16_rounded, BF16}; |
| 36 | + |
| 37 | +const M: usize = 16; // tile edge — the AMX tile / gridlake quadrant |
| 38 | +const KPAD: usize = 32; // bf16_tile_gemm requires K a multiple of 32; pad 16→32 with zeros |
| 39 | + |
| 40 | +fn mix(mut z: u64) -> u64 { |
| 41 | + z = z.wrapping_add(0x9E37_79B9_7F4A_7C15); |
| 42 | + z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); |
| 43 | + z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB); |
| 44 | + z ^ (z >> 31) |
| 45 | +} |
| 46 | + |
| 47 | +/// Pack a row-major `M×KPAD` (or `KPAD×M`) f32 matrix into the `&[u16]` bf16 |
| 48 | +/// operand the tile GEMM consumes (RNE, matching `VCVTNEPS2BF16`). |
| 49 | +fn to_bf16(src: &[f32]) -> Vec<u16> { |
| 50 | + let mut b = vec![BF16(0); src.len()]; |
| 51 | + f32_to_bf16_rounded(src, &mut b); |
| 52 | + b.iter().map(|x| x.0).collect() |
| 53 | +} |
| 54 | + |
| 55 | +/// `C[16×16] = A[16×16] · B[16×16]` via the SHIPPED BF16 tile op. A/B are padded |
| 56 | +/// on K from 16→32 with zeros (the pad contributes nothing), so this is one |
| 57 | +/// `bf16_tile_gemm_16x16` — one `TDPBF16PS` tile op on the AMX tier. |
| 58 | +fn tile_matmul(a: &[f32; M * M], b: &[f32; M * M]) -> [f32; M * M] { |
| 59 | + let mut a_pad = [0.0f32; M * KPAD]; |
| 60 | + let mut b_pad = [0.0f32; KPAD * M]; |
| 61 | + for i in 0..M { |
| 62 | + for k in 0..M { |
| 63 | + a_pad[i * KPAD + k] = a[i * M + k]; // A[16×32], cols 16..31 = 0 |
| 64 | + b_pad[k * M + i] = b[k * M + i]; // B[32×16], rows 16..31 = 0 (already) |
| 65 | + } |
| 66 | + } |
| 67 | + let (ab, bb) = (to_bf16(&a_pad), to_bf16(&b_pad)); |
| 68 | + let mut c = vec![0.0f32; M * M]; |
| 69 | + bf16_tile_gemm_16x16(&ab, &bb, &mut c, KPAD); |
| 70 | + let mut out = [0.0f32; M * M]; |
| 71 | + out.copy_from_slice(&c); |
| 72 | + out |
| 73 | +} |
| 74 | + |
| 75 | +/// Direct `f64` reference GEMM `C = A·B` (16×16). |
| 76 | +fn direct_matmul(a: &[f32; M * M], b: &[f32; M * M]) -> [f64; M * M] { |
| 77 | + let mut c = [0.0f64; M * M]; |
| 78 | + for i in 0..M { |
| 79 | + for j in 0..M { |
| 80 | + let mut s = 0.0f64; |
| 81 | + for k in 0..M { |
| 82 | + s += a[i * M + k] as f64 * b[k * M + j] as f64; |
| 83 | + } |
| 84 | + c[i * M + j] = s; |
| 85 | + } |
| 86 | + } |
| 87 | + c |
| 88 | +} |
| 89 | + |
| 90 | +fn transpose(a: &[f32; M * M]) -> [f32; M * M] { |
| 91 | + let mut t = [0.0f32; M * M]; |
| 92 | + for i in 0..M { |
| 93 | + for j in 0..M { |
| 94 | + t[j * M + i] = a[i * M + j]; |
| 95 | + } |
| 96 | + } |
| 97 | + t |
| 98 | +} |
| 99 | + |
| 100 | +/// `(max_abs_err, frobenius_relative_err)` of a tile-GEMM result vs the f64 |
| 101 | +/// direct. Frobenius-relative `‖tile − direct‖_F / ‖direct‖_F` is the honest |
| 102 | +/// aggregate precision — a per-element max-relative blows up on the near-zero |
| 103 | +/// entries that a cancellation-heavy product (e.g. `Mᵀ·Σ·M`) naturally has, |
| 104 | +/// which measures the cancellation, not the kernel. |
| 105 | +fn errors(tile: &[f32; M * M], direct: &[f64; M * M]) -> (f64, f64) { |
| 106 | + let mut max_abs = 0.0f64; |
| 107 | + let mut num = 0.0f64; // ‖E‖_F² |
| 108 | + let mut den = 0.0f64; // ‖direct‖_F² |
| 109 | + for i in 0..M * M { |
| 110 | + let e = tile[i] as f64 - direct[i]; |
| 111 | + max_abs = max_abs.max(e.abs()); |
| 112 | + num += e * e; |
| 113 | + den += direct[i] * direct[i]; |
| 114 | + } |
| 115 | + (max_abs, (num / den.max(1e-12)).sqrt()) |
| 116 | +} |
| 117 | + |
| 118 | +fn main() { |
| 119 | + println!("Gridlake field interdependency = BF16 16×16 AMX tile op — measured"); |
| 120 | + println!(" tile M=16 (the AMX tile; 64×64 gridlake = 4×4 = 16 of these), K padded 16→32"); |
| 121 | + println!( |
| 122 | + " shipped kernel tier on THIS host: {} (amx_available = {})\n", |
| 123 | + bf16_tile_gemm_tier(), |
| 124 | + amx_available() |
| 125 | + ); |
| 126 | + |
| 127 | + // ── field tile X: a correlated gridlake patch (smooth → strong interdependency) |
| 128 | + let mut x = [0.0f32; M * M]; |
| 129 | + for r in 0..M { |
| 130 | + for c in 0..M { |
| 131 | + x[r * M + c] = 4.0 * (r as f32 * 0.4).sin() * (c as f32 * 0.35).cos() + 0.5 * (r as f32 - c as f32); |
| 132 | + } |
| 133 | + } |
| 134 | + |
| 135 | + // ── (1) intercorrelation C = Xᵀ·X — the inter-cell coupling as a field op ── |
| 136 | + let xt = transpose(&x); |
| 137 | + let corr_tile = tile_matmul(&xt, &x); |
| 138 | + let corr_direct = direct_matmul(&xt, &x); |
| 139 | + let (ca, cr) = errors(&corr_tile, &corr_direct); |
| 140 | + println!(" (1) intercorrelation C = Xᵀ·X (one tile op)"); |
| 141 | + println!(" max_abs_err={ca:.4} frobenius_rel_err={:.3}% (BF16-precision class)", cr * 100.0); |
| 142 | + |
| 143 | + // ── (2) covariance sandwich Σ' = Mᵀ·Σ·M — the splat EWA projection shape ── |
| 144 | + // Σ = a symmetric PSD covariance (Xᵀ·X); M = a coupling/projection matrix. |
| 145 | + let sigma_f: [f32; M * M] = { |
| 146 | + let mut s = [0.0f32; M * M]; |
| 147 | + for i in 0..M * M { |
| 148 | + s[i] = corr_direct[i] as f32; |
| 149 | + } |
| 150 | + s |
| 151 | + }; |
| 152 | + let mut m_proj = [0.0f32; M * M]; |
| 153 | + for r in 0..M { |
| 154 | + for c in 0..M { |
| 155 | + m_proj[r * M + c] = if r == c { |
| 156 | + 1.0 |
| 157 | + } else { |
| 158 | + 0.15 * (r as f32 - c as f32).signum() |
| 159 | + }; |
| 160 | + } |
| 161 | + } |
| 162 | + // tile path: T = Σ·M, then Σ' = Mᵀ·T (two tile ops — the sandwich) |
| 163 | + let t_tile = tile_matmul(&sigma_f, &m_proj); |
| 164 | + let mt = transpose(&m_proj); |
| 165 | + let sand_tile = tile_matmul(&mt, &t_tile); |
| 166 | + // direct reference: Mᵀ·Σ·M in f64 |
| 167 | + let t_direct = { |
| 168 | + let mut td = [0.0f32; M * M]; |
| 169 | + let d = direct_matmul(&sigma_f, &m_proj); |
| 170 | + for i in 0..M * M { |
| 171 | + td[i] = d[i] as f32; |
| 172 | + } |
| 173 | + td |
| 174 | + }; |
| 175 | + let sand_direct = direct_matmul(&mt, &t_direct); |
| 176 | + let (sa, sr) = errors(&sand_tile, &sand_direct); |
| 177 | + println!(" (2) covariance sandwich Σ' = Mᵀ·Σ·M (two tile ops = the splat EWA projection)"); |
| 178 | + println!( |
| 179 | + " max_abs_err={sa:.4} frobenius_rel_err={:.3}% (== hpc::splat3d::sandwich_x16 shape;", |
| 180 | + sr * 100.0 |
| 181 | + ); |
| 182 | + println!(" per-entry max-rel is meaningless here — Mᵀ·Σ·M cancels to near-zero entries)"); |
| 183 | + |
| 184 | + // ── (3) bit-exactness on bf16-exact integer operands (module guarantee) ── |
| 185 | + let mut ai = [0.0f32; M * M]; |
| 186 | + let mut bi = [0.0f32; M * M]; |
| 187 | + for i in 0..M * M { |
| 188 | + ai[i] = ((mix(i as u64) % 16) as f32) - 8.0; // small ints, bf16-exact |
| 189 | + bi[i] = ((mix(i as u64 ^ 0x5555) % 16) as f32) - 8.0; |
| 190 | + } |
| 191 | + let int_tile = tile_matmul(&ai, &bi); |
| 192 | + let int_direct = direct_matmul(&ai, &bi); |
| 193 | + let bit_exact = (0..M * M).all(|i| int_tile[i] as f64 == int_direct[i]); |
| 194 | + println!(" (3) bit-exact on bf16-exact integer operands (|Σ| < 2^24): {bit_exact}"); |
| 195 | + |
| 196 | + // ── (4) throughput of the 16×16 tile op on this host ── |
| 197 | + let iters = 200_000usize; |
| 198 | + let t0 = std::time::Instant::now(); |
| 199 | + let mut acc = 0.0f32; |
| 200 | + for it in 0..iters { |
| 201 | + // vary the operand slightly so nothing is optimized away |
| 202 | + let mut xv = x; |
| 203 | + xv[0] += (it & 0x7) as f32 * 0.01; |
| 204 | + let c = tile_matmul(&xv, &m_proj); |
| 205 | + acc += c[0] + c[M * M - 1]; |
| 206 | + } |
| 207 | + let dt = t0.elapsed().as_secs_f64(); |
| 208 | + let tiles_s = iters as f64 / dt; |
| 209 | + println!( |
| 210 | + " (4) throughput: {iters} tile ops in {:.1} ms → {:.2} M tile-ops/s (checksum {acc:.1})", |
| 211 | + dt * 1000.0, |
| 212 | + tiles_s / 1e6 |
| 213 | + ); |
| 214 | + |
| 215 | + // hard gate: BF16-precision class (rel err small) + integer bit-exactness |
| 216 | + assert!(cr < 0.05, "intercorrelation rel err too high for BF16 class"); |
| 217 | + assert!(sr < 0.05, "sandwich rel err too high for BF16 class"); |
| 218 | + assert!(bit_exact, "bf16-exact integer tile op is NOT bit-exact vs direct"); |
| 219 | + |
| 220 | + println!( |
| 221 | + "\n MEASURED CONCLUSION: YES — a gridlake field interdependency reduces to a\n\ |
| 222 | + \x20 BF16 16×16 tile op (one `bf16_tile_gemm_16x16`, i.e. one `TDPBF16PS` on\n\ |
| 223 | + \x20 the AMX tier), matching the direct f64 reference to BF16 precision and\n\ |
| 224 | + \x20 BIT-EXACT for bf16-exact integer operands. It IS the same primitive as:\n\ |
| 225 | + \x20 • the codec separable transform (#232: M·X on a tile — WHT/DCT), and\n\ |
| 226 | + \x20 • the Gaussian-splat EWA covariance sandwich Σ'=Mᵀ·Σ·M\n\ |
| 227 | + \x20 (hpc::splat3d::spd3::sandwich_x16 — the 16-wide batched form).\n\ |
| 228 | + \x20 Interdependency / intercorrelation / perturbation / transform / covariance\n\ |
| 229 | + \x20 projection are ONE op: the 16×16 BF16 tile GEMM. The 64×64 gridlake is\n\ |
| 230 | + \x20 4×4 = 16 of these tiles." |
| 231 | + ); |
| 232 | +} |
0 commit comments