Skip to content

Commit 55cb21b

Browse files
authored
Merge pull request #230 from AdaWorldAPI/claude/mc-via-shader-poc
M1: motion compensation via cognitive-shader primitives (E-7 / H-7)
2 parents 93cc06c + 6d4f691 commit 55cb21b

2 files changed

Lines changed: 257 additions & 0 deletions

File tree

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ required-features = ["std"]
8383
name = "codec_mode_histogram"
8484
required-features = ["codec"]
8585

86+
[[example]]
87+
name = "mc_via_shader"
88+
required-features = ["codec"]
89+
8690
[[example]]
8791
name = "entropy_ladder_probe"
8892
required-features = ["std"]

examples/mc_via_shader.rs

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
//! M1 — motion compensation via the cognitive-shader primitives.
2+
//!
3+
//! Proof-of-concept for the codec↔cognitive-shader unification documented in
4+
//! `.claude/knowledge/pr-x12-codec-cognitive-substrate-mapping.md`:
5+
//!
6+
//! - **E-7** — "block-matching motion estimation *is* i8gemm." HEVC ME does
7+
//! SAD; reformulated as SSD `‖A‖² − 2·A·B + ‖B‖²`, the middle term `A·B` is
8+
//! a GEMM. This PoC runs the search through the SHIPPED
9+
//! `hpc::quantized::int8_gemm_i32` (the same u8×i8→i32 VNNI kernel the
10+
//! cognitive shader uses for attention/distance) — NOT a bespoke SAD loop.
11+
//! - **H-7** — "the codec *is* the substrate." Each block's residual
12+
//! magnitude classifies into the SHIPPED `hpc::codec::CellMode`
13+
//! (Skip/Merge/Delta/Escape) — the codec's mode taxonomy IS the MC
14+
//! per-block decision.
15+
//!
16+
//! Pipeline (all shipped primitives, zero new substrate):
17+
//! 1. reference frame (deterministic 7-bit-luma texture)
18+
//! 2. current frame = per-block shifted reference + small residual (known MVs)
19+
//! 3. **ME**: per block, `int8_gemm_i32` computes `A·B` for all candidates in
20+
//! a ±R window; SSD = `‖B‖² − 2·A·B` (+ const `‖A‖²`) → argmin → MV.
21+
//! 4. **cross-check**: brute-force direct SSD gives the ground-truth argmin.
22+
//! E-7 holds iff the GEMM argmin == the brute-force argmin for EVERY block.
23+
//! 5. **MC**: gather the reference block at the recovered MV (contiguous
24+
//! block fetch) + add the stored residual → reconstruction.
25+
//! 6. classify residual → `CellMode`; assert reconstruction is bit-exact.
26+
//!
27+
//! What this PROVES: motion estimation runs through the shader's i8 GEMM and
28+
//! is bit-identical to direct SSD; motion compensation is gather+add;
29+
//! reconstruction is exact; the per-block decision is the codec's mode taxonomy.
30+
//! What this does NOT do (that's M2, the multi-month decoder): parse a real
31+
//! `.265` bitstream (CABAC entropy decode + inverse transform to extract the
32+
//! MVs/residuals), sub-pel interpolation, deblock/SAO. Frames are synthetic
33+
//! 7-bit luma with integer-translation MVs so the i8 GEMM is exact.
34+
//!
35+
//! Run: `cargo run --release --example mc_via_shader --features codec`
36+
37+
use ndarray::hpc::codec::CellMode;
38+
use ndarray::hpc::quantized::int8_gemm_i32;
39+
40+
const W: usize = 128;
41+
const H: usize = 128;
42+
const B: usize = 8; // block edge (8×8)
43+
const K: usize = B * B; // pixels per block = GEMM K
44+
const BX: usize = W / B; // blocks per row
45+
const BY: usize = H / B; // blocks per column
46+
const R: i32 = 4; // motion search radius (±R)
47+
48+
/// SplitMix64 — deterministic, dependency-free per-pixel texture + residual.
49+
fn mix(mut z: u64) -> u64 {
50+
z = z.wrapping_add(0x9E37_79B9_7F4A_7C15);
51+
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
52+
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
53+
z ^ (z >> 31)
54+
}
55+
56+
/// 7-bit luma [0,127] so the value fits both `u8` (current) and `i8`
57+
/// (reference) operands of the VNNI kernel with no centering games.
58+
fn ref_pixel(x: usize, y: usize) -> u8 {
59+
(mix((y as u64) << 20 | x as u64) & 0x7F) as u8
60+
}
61+
62+
/// Ground-truth per-block MV — a deterministic small field, forced to 0 near
63+
/// the border so `block_pos + mv + B` stays in bounds.
64+
fn gt_mv(bx: usize, by: usize) -> (i32, i32) {
65+
let raw_x = ((bx + 2 * by) % 5) as i32 - 2;
66+
let raw_y = ((2 * bx + by) % 5) as i32 - 2;
67+
// keep block + mv + search window inside the frame
68+
let px = (bx * B) as i32;
69+
let py = (by * B) as i32;
70+
let ok = |p: i32, d: i32, span: i32| p + d - R >= 0 && p + d + R + B as i32 <= span;
71+
let mvx = if ok(px, raw_x, W as i32) { raw_x } else { 0 };
72+
let mvy = if ok(py, raw_y, H as i32) { raw_y } else { 0 };
73+
(mvx, mvy)
74+
}
75+
76+
/// Small deterministic residual on ~1/3 of blocks (drives Delta); the rest
77+
/// are residual-free (drive Skip). Values kept tiny (|r| ≤ 3).
78+
fn residual(bx: usize, by: usize, i: usize) -> i32 {
79+
if !(bx * 7 + by * 3).is_multiple_of(3) {
80+
return 0; // residual-free block
81+
}
82+
((mix((bx as u64) << 40 | (by as u64) << 20 | i as u64) % 7) as i32) - 3
83+
}
84+
85+
fn ref_block(reference: &[u8], px: i32, py: i32) -> [i8; K] {
86+
let mut blk = [0i8; K];
87+
for j in 0..B {
88+
for i in 0..B {
89+
blk[j * B + i] = reference[(py as usize + j) * W + (px as usize + i)] as i8;
90+
}
91+
}
92+
blk
93+
}
94+
95+
fn main() {
96+
// ── 1. reference frame ───────────────────────────────────────────────
97+
let reference: Vec<u8> = (0..H * W).map(|p| ref_pixel(p % W, p / W)).collect();
98+
99+
// ── 2. current frame = shifted-ref + residual (known MVs) ────────────
100+
let mut current = vec![0u8; H * W];
101+
for by in 0..BY {
102+
for bx in 0..BX {
103+
let (mvx, mvy) = gt_mv(bx, by);
104+
let (px, py) = ((bx * B) as i32, (by * B) as i32);
105+
for j in 0..B {
106+
for i in 0..B {
107+
let refv = reference[(py + mvy + j as i32) as usize * W + (px + mvx + i as i32) as usize] as i32;
108+
let v = (refv + residual(bx, by, j * B + i)).clamp(0, 127);
109+
current[(py as usize + j) * W + (px as usize + i)] = v as u8;
110+
}
111+
}
112+
}
113+
}
114+
115+
// ── 3+4. ME via i8 GEMM, cross-checked against brute-force SSD ───────
116+
let mut recovered = vec![(0i32, 0i32); BX * BY];
117+
let mut gemm_eq_brute = 0usize; // E-7: GEMM argmin == direct-SSD argmin
118+
let mut mv_exact = 0usize; // recovered MV == ground-truth MV
119+
let mut gemm_calls = 0usize;
120+
let t0 = std::time::Instant::now();
121+
122+
for by in 0..BY {
123+
for bx in 0..BX {
124+
let (px, py) = ((bx * B) as i32, (by * B) as i32);
125+
// current block A (u8), row vector [1×K]
126+
let mut a = [0u8; K];
127+
for j in 0..B {
128+
for i in 0..B {
129+
a[j * B + i] = current[(py as usize + j) * W + (px as usize + i)];
130+
}
131+
}
132+
133+
// enumerate in-bounds candidates; build B = [K × N] (candidate per
134+
// column: b[k*N + n]) so int8_gemm_i32(1,N,K) yields C[n] = A·B_n.
135+
let mut cand: Vec<(i32, i32)> = Vec::with_capacity(((2 * R + 1) * (2 * R + 1)) as usize);
136+
for dy in -R..=R {
137+
for dx in -R..=R {
138+
let (cx, cy) = (px + dx, py + dy);
139+
if cx >= 0 && cy >= 0 && cx + B as i32 <= W as i32 && cy + B as i32 <= H as i32 {
140+
cand.push((dx, dy));
141+
}
142+
}
143+
}
144+
let n = cand.len();
145+
let mut bmat = vec![0i8; K * n];
146+
let mut b_sq = vec![0i64; n]; // ‖B_n‖²
147+
for (col, &(dx, dy)) in cand.iter().enumerate() {
148+
let blk = ref_block(&reference, px + dx, py + dy);
149+
let mut s = 0i64;
150+
for k in 0..K {
151+
bmat[k * n + col] = blk[k];
152+
s += (blk[k] as i64) * (blk[k] as i64);
153+
}
154+
b_sq[col] = s;
155+
}
156+
157+
// THE SHADER KERNEL: C[1×N] = A[1×K] · B[K×N] (u8 × i8 → i32)
158+
let mut c = vec![0i32; n];
159+
int8_gemm_i32(&a, &bmat, &mut c, 1, n, K);
160+
gemm_calls += 1;
161+
162+
// SSD_n = ‖A‖² − 2·(A·B_n) + ‖B_n‖²; ‖A‖² is const per block →
163+
// argmin over (‖B_n‖² − 2·C[n]).
164+
let gemm_arg = (0..n)
165+
.min_by_key(|&nn| b_sq[nn] - 2 * c[nn] as i64)
166+
.unwrap();
167+
168+
// brute-force direct SSD = Σ (A_k − B_k)² — the ground truth.
169+
let brute_arg = (0..n)
170+
.min_by_key(|&nn| {
171+
let (dx, dy) = cand[nn];
172+
let blk = ref_block(&reference, px + dx, py + dy);
173+
(0..K)
174+
.map(|k| {
175+
let d = a[k] as i64 - blk[k] as i64;
176+
d * d
177+
})
178+
.sum::<i64>()
179+
})
180+
.unwrap();
181+
182+
if gemm_arg == brute_arg {
183+
gemm_eq_brute += 1;
184+
}
185+
let mv = cand[gemm_arg];
186+
recovered[by * BX + bx] = mv;
187+
if mv == gt_mv(bx, by) {
188+
mv_exact += 1;
189+
}
190+
}
191+
}
192+
let me_ms = t0.elapsed().as_secs_f64() * 1000.0;
193+
194+
// ── 5+6. MC (gather ref block @ recovered MV) + residual; classify + verify
195+
let mut recon = vec![0u8; H * W];
196+
let mut modes = [0usize; 4]; // skip, merge, delta, escape
197+
let mut max_err = 0i32;
198+
for by in 0..BY {
199+
for bx in 0..BX {
200+
let (px, py) = ((bx * B) as i32, (by * B) as i32);
201+
let (mvx, mvy) = recovered[by * BX + bx];
202+
// prediction = reference block at MV (contiguous gather)
203+
let pred = ref_block(&reference, px + mvx, py + mvy); // i8 == 7-bit luma
204+
let mut maxres = 0i32;
205+
for j in 0..B {
206+
for i in 0..B {
207+
let k = j * B + i;
208+
let cur = current[(py as usize + j) * W + (px as usize + i)] as i32;
209+
let res = cur - pred[k] as i32; // stored residual = current − prediction
210+
maxres = maxres.max(res.abs());
211+
let r = (pred[k] as i32 + res).clamp(0, 255); // reconstruction = pred + residual
212+
recon[(py as usize + j) * W + (px as usize + i)] = r as u8;
213+
max_err = max_err.max((r - cur).abs());
214+
}
215+
}
216+
// per-block decision = codec mode: residual magnitude → CellMode
217+
let mode = if maxres == 0 {
218+
CellMode::Skip
219+
} else if maxres <= 127 && recovered[by * BX + bx] == recovered[by.saturating_sub(1) * BX + bx] {
220+
CellMode::Merge // MV inherited from N neighbour + in-range residual
221+
} else if maxres <= 127 {
222+
CellMode::Delta
223+
} else {
224+
CellMode::Escape
225+
};
226+
modes[mode as usize] += 1;
227+
}
228+
}
229+
230+
// ── report ───────────────────────────────────────────────────────────
231+
let nblk = BX * BY;
232+
let pct = |x: usize| 100.0 * x as f64 / nblk as f64;
233+
println!("M1 — motion compensation via cognitive-shader primitives");
234+
println!(" frame {W}×{H}, {B}×{B} blocks = {nblk} blocks, 7-bit luma, ±{R} search\n");
235+
println!(
236+
" [E-7] i8gemm SSD argmin == brute-force direct SSD : {gemm_eq_brute}/{nblk} ({:.1}%)",
237+
pct(gemm_eq_brute)
238+
);
239+
println!(" → motion estimation IS int8_gemm_i32 (the shader kernel), bit-identical to SAD/SSD");
240+
println!(" MV recovery (i8gemm ME == ground-truth motion) : {mv_exact}/{nblk} ({:.1}%)", pct(mv_exact));
241+
println!(" MC reconstruction max abs pixel error : {max_err} (0 = bit-exact)");
242+
println!(" [H-7] per-block CellMode histogram (residual → mode):");
243+
println!(" skip={} merge={} delta={} escape={}", modes[0], modes[1], modes[2], modes[3]);
244+
println!(
245+
" throughput: {gemm_calls} int8_gemm_i32 calls (one per block) in {me_ms:.2} ms → {:.0} blocks/s",
246+
nblk as f64 / (me_ms / 1000.0)
247+
);
248+
249+
// hard asserts — the PoC's falsifiers
250+
assert_eq!(gemm_eq_brute, nblk, "E-7 FALSIFIED: i8gemm ME disagrees with direct SSD");
251+
assert_eq!(max_err, 0, "MC reconstruction is not bit-exact");
252+
println!("\n RESULT: E-7 holds (ME == i8gemm), MC is bit-exact, decision = codec modes. Concept proven.");
253+
}

0 commit comments

Comments
 (0)