1use std::collections::HashMap;
29use std::sync::{Arc, Mutex, OnceLock};
30
31use crate::bitio::DecodeError;
32use crate::irdec::{BUFS, LEARNED_AD_PARAMS, LEARNED_PARAMS, OUTS, OUTS_AD, RW_AD, STATIC_PARAMS};
33use crate::learned::NO;
34use crate::lmodel3::{Net32, NFL};
35use tang_expr::int::{IId, IType, IntGraph, Limits, Program};
36
37pub const MAGIC: &[u8; 4] = b"TCS1";
39const PMAGIC: &[u8; 4] = b"TCDP";
40pub const MAX_PROGRAM: usize = 1 << 20;
42
43pub type Graph = (IntGraph, Vec<IId>);
45
46pub struct Decoder {
48 pub stat: Graph,
50 pub learned: Graph,
52 pub learned_ad: Graph,
54 pub mlp: Option<Graph>,
56 pub wgsl_res: String,
58 pub wgsl_res_ad: String,
60 pub wgsl_mlp: Option<String>,
62 pub key: u64,
64 pub size: usize,
66}
67
68impl std::fmt::Debug for Decoder {
69 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70 write!(
71 f,
72 "Decoder {{ key: {:016x}, size: {}, mlp: {} }}",
73 self.key,
74 self.size,
75 self.mlp.is_some()
76 )
77 }
78}
79
80impl Decoder {
81 pub fn mlp_program(&self) -> Option<Program> {
83 self.mlp.as_ref().map(|(g, o)| Program::new(g, o))
84 }
85}
86
87pub fn fnv1a(b: &[u8]) -> u64 {
89 let mut h = 0xcbf29ce484222325u64;
90 for &x in b {
91 h ^= x as u64;
92 h = h.wrapping_mul(0x100000001b3);
93 }
94 h
95}
96
97fn put(o: &mut Vec<u8>, mut v: u64) {
98 loop {
99 let b = (v & 0x7f) as u8;
100 v >>= 7;
101 if v == 0 {
102 o.push(b);
103 return;
104 }
105 o.push(b | 0x80);
106 }
107}
108
109fn get(b: &[u8], p: &mut usize) -> Result<u64, DecodeError> {
110 let mut v = 0u64;
111 for s in 0..10 {
112 let x = *b.get(*p).ok_or(DecodeError::Truncated)?;
113 *p += 1;
114 v |= ((x & 0x7f) as u64) << (7 * s);
115 if x & 0x80 == 0 {
116 return Ok(v);
117 }
118 }
119 Err(DecodeError::Invalid("varint"))
120}
121
122pub fn program_bytes(
125 stat: Option<&Graph>,
126 learned: Option<&Graph>,
127 mlp: Option<&Graph>,
128 learned_ad: Option<&Graph>,
129) -> Vec<u8> {
130 let mut o = PMAGIC.to_vec();
131 o.push(1);
132 let secs = [stat, learned, mlp, learned_ad];
133 o.push(
134 secs.iter()
135 .enumerate()
136 .map(|(i, s)| (s.is_some() as u8) << i)
137 .sum(),
138 );
139 for (g, outs) in secs.into_iter().flatten() {
140 let b = g.to_bytes(outs);
141 put(&mut o, b.len() as u64);
142 o.extend(b);
143 }
144 o
145}
146
147pub fn builtin_program(net: Option<&Net32>, ad: bool) -> Vec<u8> {
151 let mlp = net.map(|n| n.graph());
152 let (l, la) = if ad {
153 (None, Some(crate::irdec::learned_ad_graph()))
154 } else {
155 (Some(crate::irdec::learned_graph()), None)
156 };
157 program_bytes(
158 Some(&crate::irdec::static_graph()),
159 l.as_ref(),
160 mlp.as_ref(),
161 la.as_ref(),
162 )
163}
164
165pub fn wrap(stream: &[u8], program: &[u8]) -> Vec<u8> {
167 let mut o = MAGIC.to_vec();
168 put(&mut o, program.len() as u64);
169 o.extend_from_slice(program);
170 if o.len() % 2 == 1 {
172 o.push(0);
173 }
174 o.extend_from_slice(stream);
175 o
176}
177
178fn res_limits() -> Limits {
179 Limits {
180 max_nodes: 1 << 14,
181 max_loops: 16,
182 max_depth: 2,
183 max_trip: 64,
184 max_cost: 1 << 16,
185 }
186}
187
188fn mlp_limits() -> Limits {
189 Limits {
190 max_nodes: 1 << 16,
191 max_loops: 0,
192 max_depth: 0,
193 max_trip: 0,
194 max_cost: 1 << 16,
195 }
196}
197
198pub fn compile(prog: &[u8]) -> Result<Decoder, DecodeError> {
200 let bad = |_| DecodeError::Invalid("decoder program rejected by the verifier");
201 if prog.len() > MAX_PROGRAM {
202 return Err(DecodeError::Invalid("decoder program too large"));
203 }
204 if prog.len() < 6 || &prog[..4] != PMAGIC || prog[4] != 1 {
205 return Err(DecodeError::Invalid("decoder program header"));
206 }
207 let flags = prog[5];
208 if flags & !15 != 0 {
209 return Err(DecodeError::Invalid("decoder program flags"));
210 }
211 let mut p = 6;
212 let sec = |p: &mut usize| -> Result<&[u8], DecodeError> {
213 let n = get(prog, p)? as usize;
214 let s = prog
215 .get(*p..p.checked_add(n).ok_or(DecodeError::Truncated)?)
216 .ok_or(DecodeError::Truncated)?;
217 *p += n;
218 Ok(s)
219 };
220 let stat = match flags & 1 {
221 0 => crate::irdec::static_graph(),
222 _ => IntGraph::from_bytes(sec(&mut p)?, &STATIC_PARAMS, &BUFS, &OUTS, &res_limits())
223 .map_err(bad)?,
224 };
225 let learned = match flags & 2 {
226 0 => crate::irdec::learned_graph(),
227 _ => IntGraph::from_bytes(sec(&mut p)?, &LEARNED_PARAMS, &BUFS, &OUTS, &res_limits())
228 .map_err(bad)?,
229 };
230 let mlp = match flags & 4 {
231 0 => None,
232 _ => Some(
233 IntGraph::from_bytes(
234 sec(&mut p)?,
235 &[IType::I32; NFL],
236 &[],
237 &[IType::I32; NO],
238 &mlp_limits(),
239 )
240 .map_err(bad)?,
241 ),
242 };
243 let learned_ad = match flags & 8 {
244 0 => crate::irdec::learned_ad_graph(),
245 _ => IntGraph::from_bytes_rw(
246 sec(&mut p)?,
247 &LEARNED_AD_PARAMS,
248 &BUFS,
249 &RW_AD,
250 &OUTS_AD,
251 &res_limits(),
252 )
253 .map_err(bad)?,
254 };
255 if p != prog.len() {
256 return Err(DecodeError::Invalid("decoder program trailing bytes"));
257 }
258 let wgsl_res = crate::irdec::wgsl((&stat.0, &stat.1), (&learned.0, &learned.1), true);
259 let wgsl_res_ad = crate::irdec::wgsl((&stat.0, &stat.1), (&learned_ad.0, &learned_ad.1), true);
260 let wgsl_mlp = mlp.as_ref().map(|(g, o)| {
261 let params: Vec<String> = (0..NFL).map(|i| format!("na[{i}]")).collect();
262 let dests: Vec<String> = (0..o.len()).map(|k| format!("na[{k}]")).collect();
263 let pr: Vec<&str> = params.iter().map(|s| s.as_str()).collect();
264 let dr: Vec<&str> = dests.iter().map(|s| s.as_str()).collect();
265 format!(
266 "fn mlp_gen() {{\n{}}}\n",
267 g.to_wgsl_body_guarded(o, &pr, &dr, 1)
268 )
269 });
270 Ok(Decoder {
271 stat,
272 learned,
273 learned_ad,
274 mlp,
275 wgsl_res,
276 wgsl_res_ad,
277 wgsl_mlp,
278 key: fnv1a(prog),
279 size: prog.len(),
280 })
281}
282
283type Cache = Mutex<HashMap<u64, Vec<(Vec<u8>, Arc<Decoder>)>>>;
284
285fn cache() -> &'static Cache {
286 static C: OnceLock<Cache> = OnceLock::new();
287 C.get_or_init(Default::default)
288}
289
290pub fn compile_cached(prog: &[u8]) -> Result<Arc<Decoder>, DecodeError> {
294 let k = fnv1a(prog);
295 if let Some(v) = cache().lock().unwrap().get(&k) {
296 if let Some((_, d)) = v.iter().find(|(b, _)| b == prog) {
297 return Ok(d.clone());
298 }
299 }
300 let d = Arc::new(compile(prog)?);
301 let mut c = cache().lock().unwrap();
302 let v = c.entry(k).or_default();
303 if v.len() < 16 {
304 v.push((prog.to_vec(), d.clone()));
305 }
306 Ok(d)
307}
308
309pub fn open(bytes: &[u8]) -> Result<(Option<Arc<Decoder>>, &[u8]), DecodeError> {
312 if bytes.len() < 4 || &bytes[..4] != MAGIC {
313 return Ok((None, bytes));
314 }
315 let mut p = 4;
316 let n = get(bytes, &mut p)? as usize;
317 if n > MAX_PROGRAM {
318 return Err(DecodeError::Invalid("decoder program too large"));
319 }
320 let prog = bytes.get(p..p + n).ok_or(DecodeError::Truncated)?;
321 let start = (p + n).next_multiple_of(2);
322 let inner = bytes.get(start..).ok_or(DecodeError::Truncated)?;
323 Ok((Some(compile_cached(prog)?), inner))
324}
325
326pub fn placeholder_net() -> Net32 {
329 Net32 {
330 sizes: vec![NFL, NO],
331 w: vec![vec![0; NFL * NO]],
332 b: vec![vec![0; NO]],
333 }
334}
335
336#[cfg(test)]
337mod tests {
338 use super::*;
339 use crate::gpuwav3::{decode3_box, encode3_f32, parse3, Gpu3Config, Model3};
340 use crate::ErrorBound;
341
342 fn field(d: [usize; 3]) -> Vec<f32> {
343 let mut v = Vec::with_capacity(d.iter().product());
344 for z in 0..d[0] {
345 for y in 0..d[1] {
346 for x in 0..d[2] {
347 let (fz, fy, fx) = (z as f32 * 0.21, y as f32 * 0.13, x as f32 * 0.17);
348 v.push(
349 (fz.sin() * fy.cos()
350 + (fx * 1.3).sin() * 0.5
351 + (fx * fy * 0.05).cos() * 0.25)
352 * 3.0,
353 );
354 }
355 }
356 }
357 v
358 }
359
360 fn streams() -> Vec<(String, Vec<u8>)> {
361 let d = [20, 37, 45];
362 let x = field(d);
363 let mut out = Vec::new();
364 for (lanes, bound) in [
365 (1, ErrorBound::Psnr(60.0)),
366 (8, ErrorBound::Psnr(100.0)),
367 (8, ErrorBound::MaxAbs(1e-4)),
368 ] {
369 let cfg = Gpu3Config {
370 block: 16,
371 lanes,
372 tile: 16,
373 ..Gpu3Config::new(bound)
374 };
375 out.push((
376 format!("static lanes {lanes} {bound:?}"),
377 encode3_f32(&x, &d, &cfg, 2).0,
378 ));
379 let model = Model3::Overfit {
380 hidden: vec![8],
381 per_epoch: 20_000,
382 epochs: 2,
383 };
384 let cfg = Gpu3Config {
385 block: 16,
386 lanes,
387 tile: 16,
388 model,
389 adapt: None,
390 ..Gpu3Config::new(bound)
391 };
392 out.push((
393 format!("learned lanes {lanes} {bound:?}"),
394 encode3_f32(&x, &d, &cfg, 2).0,
395 ));
396 let model = Model3::Overfit {
397 hidden: vec![8],
398 per_epoch: 20_000,
399 epochs: 2,
400 };
401 let cfg = Gpu3Config {
402 block: 16,
403 lanes,
404 tile: 16,
405 model,
406 ..Gpu3Config::new(bound)
407 };
408 out.push((
409 format!("adaptive lanes {lanes} {bound:?}"),
410 encode3_f32(&x, &d, &cfg, 2).0,
411 ));
412 }
413 out
414 }
415
416 fn shipped(s: &[u8], with_mlp: bool) -> Vec<u8> {
417 let ps = parse3(s).unwrap();
418 let net = if with_mlp { ps.net.as_ref() } else { None };
419 wrap(s, &builtin_program(net, ps.net.is_some() && ps.ad.wa > 0))
420 }
421
422 #[test]
425 fn adaptive_streams_decode_through_the_ir() {
426 let d = [20, 37, 45];
427 let x = field(d);
428 let model = Model3::Overfit {
429 hidden: vec![8],
430 per_epoch: 20_000,
431 epochs: 2,
432 };
433 let cfg = Gpu3Config {
434 block: 16,
435 lanes: 8,
436 tile: 16,
437 model,
438 ..Gpu3Config::new(ErrorBound::Psnr(80.0))
439 };
440 let s = encode3_f32(&x, &d, &cfg, 2).0;
441 let ps = parse3(&s).unwrap();
442 assert!(ps.ad.wa > 0);
443 let want = decode3_box(&s, [0; 3], d, 2).unwrap().bits;
444 for (mlp, ad) in [(false, true), (true, true), (false, false), (true, false)] {
445 let w = wrap(
446 &s,
447 &builtin_program(if mlp { ps.net.as_ref() } else { None }, ad),
448 );
449 assert!(parse3(&w).unwrap().dec.is_some());
450 assert!(
451 decode3_box(&w, [0; 3], d, 2).unwrap().bits == want,
452 "mlp {mlp} ad {ad}"
453 );
454 }
455 }
456
457 #[test]
458 fn shipped_decoder_matches_builtin_on_cpu() {
459 for (name, s) in streams() {
460 let want = decode3_box(&s, [0; 3], [20, 37, 45], 2).unwrap().bits;
461 for mlp in [false, true] {
462 let w = shipped(&s, mlp);
463 let ps = parse3(&w).unwrap();
464 assert!(ps.dec.is_some());
465 let got = decode3_box(&w, [0; 3], [20, 37, 45], 2).unwrap().bits;
466 assert!(got == want, "{name} mlp {mlp}");
467 let sub = decode3_box(&w, [3, 5, 7], [11, 30, 20], 1).unwrap();
469 let base = decode3_box(&s, [3, 5, 7], [11, 30, 20], 1).unwrap();
470 assert!(sub.bits == base.bits, "{name} box");
471 }
472 }
473 }
474
475 #[cfg(feature = "gpu")]
476 #[test]
477 fn shipped_decoder_matches_builtin_on_gpu() {
478 let Ok(g) = crate::gpudec::GpuDecoder::new(8, 128) else {
479 eprintln!("no GPU adapter; skipping");
480 return;
481 };
482 for (name, s) in streams() {
483 let want = decode3_box(&s, [0; 3], [20, 37, 45], 1).unwrap().bits;
484 assert!(g.decode3(&s).unwrap().0 == want, "{name} builtin gpu");
485 for mlp in [false, true] {
486 let w = shipped(&s, mlp);
487 assert!(
488 g.decode3(&w).unwrap().0 == want,
489 "{name} shipped gpu mlp {mlp}"
490 );
491 }
492 }
493 }
494
495 #[test]
498 fn shipped_mlp_needs_no_registry() {
499 let path = std::env::var("TC_TEST_TCM").unwrap_or_else(|_| {
500 "/Users/cam/Developer/cram-codec/bench/models/pre3v4_16.tcm".into()
501 });
502 let Ok(tcm) = std::fs::read(&path) else {
503 eprintln!("no {path}; skipping");
504 return;
505 };
506 let net = Arc::new(crate::learned::Net::from_bytes(&tcm).unwrap());
507 let d = [20, 37, 45];
508 let cfg = Gpu3Config {
509 block: 16,
510 lanes: 8,
511 tile: 16,
512 model: Model3::Pretrained(net.clone()),
513 adapt: None,
514 ..Gpu3Config::new(ErrorBound::Psnr(80.0))
515 };
516 let (s, _) = encode3_f32(&field(d), &d, &cfg, 2);
517 assert!(parse3(&s).is_err(), "model must not be registered yet");
518 let prog = builtin_program(Some(&Net32::new(&net)), false);
519 let w = wrap(&s, &prog);
520 let got = decode3_box(&w, [0; 3], d, 2).unwrap().bits;
521 #[cfg(feature = "gpu")]
522 let gpu = crate::gpudec::GpuDecoder::new(8, 64)
523 .ok()
524 .map(|g| g.decode3(&w).unwrap().0);
525 crate::learned::register(net);
526 let want = decode3_box(&s, [0; 3], d, 2).unwrap().bits;
527 assert!(got == want);
528 #[cfg(feature = "gpu")]
529 if let Some(gv) = gpu {
530 assert!(gv == want, "gpu, shipped MLP");
531 }
532 }
533
534 #[test]
535 fn corrupt_programs_are_rejected_not_run() {
536 let s = &streams()[1].1;
537 let prog = builtin_program(parse3(s).unwrap().net.as_ref(), false);
538 let mut seed = 5u64;
539 let mut rnd = || {
540 seed = seed
541 .wrapping_mul(6364136223846793005)
542 .wrapping_add(1442695040888963407);
543 (seed >> 33) as usize
544 };
545 let (mut ok, mut bad) = (0, 0);
546 for _ in 0..300 {
547 let mut p = prog.clone();
548 let i = 6 + rnd() % (p.len() - 6);
549 p[i] = rnd() as u8;
550 match compile(&p) {
551 Ok(_) => {
553 ok += 1;
554 let _ = decode3_box(&wrap(s, &p), [0; 3], [20, 37, 45], 1);
555 }
556 Err(_) => bad += 1,
557 }
558 }
559 assert!(bad > 0);
560 eprintln!("mutations: {ok} accepted, {bad} rejected");
561 for n in [0, 3, 6, 7, prog.len() / 2, prog.len() - 1] {
563 assert!(compile(&prog[..n]).is_err());
564 }
565 let spin = |cap: u32| {
567 let mut g = IntGraph::new();
568 for (n, e) in BUFS {
569 g.buffer(n, e);
570 }
571 let x = g.param(0, IType::U32);
572 let out = g.while_loop(&[x], |g, s| {
573 let t = g.bool(true);
574 let one = g.u32(1);
575 (t, vec![g.add(s[0], one)])
576 });
577 g.cap_loop(out[0], cap);
578 let z = g.i32(0);
579 program_bytes(Some(&(g, vec![out[0], x, z])), None, None, None)
580 };
581 assert!(compile(&spin(1 << 20)).is_err());
582 let d = compile(&spin(64)).unwrap();
584 assert!(d.wgsl_res.contains("if c0 >= 64u"));
585 let _ = decode3_box(&wrap(s, &spin(64)), [0; 3], [20, 37, 45], 1);
586 }
587}