pub fn encode_f32(
x: &[f32],
dims: &[usize],
cfg: &WavConfig,
) -> (Vec<u8>, WavStats)Expand description
Encode f32 samples (row-major, dims 1D–3D).
Examples found in repository?
examples/pretrain.rs (line 55)
16fn main() {
17 let a: Vec<String> = std::env::args().skip(1).collect();
18 let out = &a[0];
19 let mut dims = vec![64, 128, 128];
20 let mut psnrs = vec![40.0, 60.0, 80.0, 100.0];
21 let mut cfg = TrainConfig::pretrain();
22 let mut per = 150_000usize;
23 let mut files = Vec::new();
24 let mut i = 1;
25 while i < a.len() {
26 match a[i].as_str() {
27 "--dims" => dims = a[i + 1].split('x').map(|s| s.parse().unwrap()).collect(),
28 "--psnr" => psnrs = a[i + 1].split(',').map(|s| s.parse().unwrap()).collect(),
29 "--hidden" => cfg.hidden = a[i + 1].split(',').map(|s| s.parse().unwrap()).collect(),
30 "--per" => per = a[i + 1].parse().unwrap(),
31 "--epochs" => cfg.epochs = a[i + 1].parse().unwrap(),
32 "--per-epoch" => cfg.per_epoch = a[i + 1].parse().unwrap(),
33 f => {
34 files.push(f.to_string());
35 i += 1;
36 continue;
37 }
38 }
39 i += 2;
40 }
41 let mut samples: Vec<Sample> = Vec::new();
42 let mut seed = 12345u64;
43 for f in &files {
44 let b = std::fs::read(f).unwrap();
45 let x: Vec<f32> = b
46 .as_chunks::<4>()
47 .0
48 .iter()
49 .map(|&c| f32::from_le_bytes(c))
50 .collect();
51 let xd: Vec<f64> = x.iter().map(|&v| v as f64).collect();
52 let co = Coeffs::new(&xd, &dims);
53 for &p in &psnrs {
54 let wc = WavConfig::new(ErrorBound::Psnr(p));
55 let (_, st) = encode_f32(&x, &dims, &wc);
56 let (q, _) = quantize(&co, st.delta, wc.rho);
57 let preds = fit_predictors(&co.p, &q);
58 let mut s = collect(&co.p, &q, &preds);
59 // seeded partial Fisher–Yates subsample
60 let k = per.min(s.len());
61 for j in 0..k {
62 seed = seed
63 .wrapping_mul(6364136223846793005)
64 .wrapping_add(1442695040888963407);
65 let r = j + (seed >> 11) as usize % (s.len() - j);
66 s.swap(j, r);
67 }
68 s.truncate(k);
69 eprintln!(
70 "{f} psnr {p}: delta {:.4e}, hand {:.4} bpv, {} samples",
71 st.delta,
72 st.bits_per_value,
73 s.len()
74 );
75 samples.extend(s);
76 }
77 }
78 let t = std::time::Instant::now();
79 let net = train(&samples, &cfg);
80 eprintln!(
81 "trained {} params on {} samples in {:.1}s: {:.4} bits/sample (float view)",
82 net.num_params(),
83 samples.len(),
84 t.elapsed().as_secs_f64(),
85 mean_bits_float(&net, &samples)
86 );
87 std::fs::write(out, Arc::new(net).to_bytes()).unwrap();
88 let _ = Net::from_bytes(&std::fs::read(out).unwrap()).unwrap();
89}More examples
examples/tc.rs (line 84)
34fn main() {
35 let args: Vec<String> = std::env::args().skip(1).collect();
36 match args.first().map(String::as_str) {
37 Some("encode") if args.len() >= 3 => {
38 let x = read_f32(&args[1]);
39 let range = {
40 let (a, b) = x
41 .iter()
42 .fold((f32::INFINITY, f32::NEG_INFINITY), |(a, b), &v| {
43 (a.min(v), b.max(v))
44 });
45 (b - a) as f64
46 };
47 let mut dims = vec![x.len()];
48 let mut bound = None;
49 let mut codec = "wavelet".to_string();
50 let mut rho = None;
51 let mut model = "hand".to_string();
52 let mut i = 3;
53 while i < args.len() {
54 if args[i] == "--fast" {
55 codec = "fast".to_string();
56 i += 1;
57 continue;
58 }
59 let v = args.get(i + 1).cloned().unwrap_or_else(|| usage());
60 match args[i].as_str() {
61 "--dims" => dims = v.split('x').map(|s| s.parse().unwrap()).collect(),
62 "--psnr" => bound = Some(ErrorBound::Psnr(v.parse().unwrap())),
63 "--rmse" => bound = Some(ErrorBound::Rmse(v.parse().unwrap())),
64 "--max-abs" => bound = Some(ErrorBound::MaxAbs(v.parse().unwrap())),
65 "--max-rel" => {
66 bound = Some(ErrorBound::MaxAbs(v.parse::<f64>().unwrap() * range))
67 }
68 "--codec" => codec = v,
69 "--rho" => rho = Some(v.parse().unwrap()),
70 "--model" => model = v,
71 _ => usage(),
72 }
73 i += 2;
74 }
75 let bound = bound.unwrap_or_else(|| usage());
76 let t = Instant::now();
77 let (bytes, json) = match codec.as_str() {
78 "wavelet" => {
79 let mut cfg = wavcodec::WavConfig::new(bound);
80 if let Some(r) = rho {
81 cfg.rho = r;
82 }
83 cfg.model = cram_codec::learned::parse_model_arg(&model);
84 let (b, s) = wavcodec::encode_f32(&x, &dims, &cfg);
85 let j = format!(
86 "\"coef_bytes\":{},\"model_bytes\":{},\"outlier_bytes\":{},\"n_outliers\":{},\"delta\":{},\"rmse\":{},\"max_abs\":{},\"passes\":{}",
87 s.coef_bytes, s.model_bytes, s.outlier_bytes, s.n_outliers, s.delta, s.rmse, s.max_abs, s.passes
88 );
89 (b, j)
90 }
91 "tucker" => {
92 let (b, s) = cram_codec::tucker::encode_f32(&x, &dims, bound);
93 let j = format!(
94 "\"core_bytes\":{},\"factor_bytes\":{},\"ranks\":{:?},\"delta\":{},\"rmse\":{},\"max_abs\":{}",
95 s.core_bytes, s.factor_bytes, s.ranks, s.delta, s.rmse, s.max_abs
96 );
97 (b, j)
98 }
99 "auto" | "portfolio-all" => {
100 let mut cfg = wavcodec::WavConfig::new(bound);
101 if let Some(r) = rho {
102 cfg.rho = r;
103 }
104 cfg.model = cram_codec::learned::parse_model_arg(&model);
105 let (b, rep) =
106 cram_codec::encode_portfolio_f32(&x, &dims, &cfg, codec == "auto");
107 let tried: Vec<String> = rep
108 .tried
109 .iter()
110 .map(|(c, n, t)| format!("\"{}:{n}:{t:.3}\"", c.name()))
111 .collect();
112 (
113 b,
114 format!(
115 "\"chose\":\"{}\",\"probe_rank\":{},\"tried\":[{}]",
116 rep.pick.name(),
117 rep.probe_rank,
118 tried.join(",")
119 ),
120 )
121 }
122 "fast" => {
123 let mut cfg = wavcodec::WavConfig::new(bound);
124 cfg.model = cram_codec::learned::parse_model_arg(&model);
125 let (b, c) = cram_codec::encode_fast_f32(&x, &dims, &cfg);
126 (b, format!("\"chose\":\"{}\"", c.name()))
127 }
128 "qtt-probe" => {
129 let env = |k: &str, d: usize| {
130 std::env::var(k)
131 .ok()
132 .and_then(|v| v.parse().ok())
133 .unwrap_or(d)
134 };
135 let (n, r, t) = cram_codec::qtt_probe(
136 &x,
137 &dims,
138 bound,
139 env("PROBE_STRIDE", 2),
140 env("PROBE_CAP", 32),
141 );
142 (
143 vec![0u8; n],
144 format!("\"probe_bytes\":{n},\"probe_rank\":{r},\"probe_s\":{t}"),
145 )
146 }
147 "interp" | "lorenzo" | "qtt" | "qtt-sparse" => {
148 let cfg = wavcodec::WavConfig::new(bound);
149 let c = match codec.as_str() {
150 "qtt" => cram_codec::Candidate::Qtt,
151 "qtt-sparse" => cram_codec::Candidate::QttSparse,
152 "lorenzo" => cram_codec::Candidate::Lorenzo,
153 _ => cram_codec::Candidate::Interp,
154 };
155 let mut b = vec![c.tag()];
156 b.extend(cram_codec::encode_candidate_f32(c, &x, &dims, &cfg));
157 (b, format!("\"chose\":\"{codec}\""))
158 }
159 _ => usage(),
160 };
161 let enc = t.elapsed().as_secs_f64();
162 std::fs::write(&args[2], &bytes).unwrap();
163 println!(
164 "{{\"bytes\":{},\"bpv\":{},\"encode_s\":{enc},{json}}}",
165 bytes.len(),
166 8.0 * bytes.len() as f64 / x.len() as f64
167 );
168 }
169 Some("decode") if args.len() == 3 || (args.len() == 5 && args[3] == "--model") => {
170 if args.len() == 5 {
171 // registers a pretrained network so the decoder can resolve it
172 let _ = cram_codec::learned::parse_model_arg(&args[4]);
173 }
174 let bytes = std::fs::read(&args[1]).unwrap();
175 let t = Instant::now();
176 let Decoded::F32 { data, .. } = decode_any(&bytes).expect("decode") else {
177 panic!("not an f32 stream")
178 };
179 let dec = t.elapsed().as_secs_f64();
180 let out: Vec<u8> = data.iter().flat_map(|v| v.to_le_bytes()).collect();
181 std::fs::write(&args[2], out).unwrap();
182 println!("{{\"decode_s\":{dec}}}");
183 }
184 _ => usage(),
185 }
186}examples/gpubench.rs (line 231)
76fn main() {
77 let args: Vec<String> = std::env::args().skip(1).collect();
78 let mode = args[0].as_str();
79 if mode == "cpu3" {
80 // gpubench cpu3 STREAM... [pretrained:PATH]: CPU decode vs saved hash (TC_CPU_IR=1: net via tang-expr)
81 for p in args.iter().filter_map(|a| a.strip_prefix("pretrained:")) {
82 cram_codec::learned::register(std::sync::Arc::new(
83 cram_codec::learned::Net::from_bytes(&std::fs::read(p).unwrap()).unwrap(),
84 ));
85 }
86 let threads: usize = opt(&args, "--threads", "16").parse().unwrap();
87 for f in args[1..]
88 .iter()
89 .filter(|a| a.ends_with(".tcg3") || a.ends_with(".tcs3"))
90 {
91 let b = std::fs::read(f).unwrap();
92 let want = std::fs::read_to_string(format!("{f}.hash")).unwrap();
93 let t0 = Instant::now();
94 let (_, y) = gpuwav3::decode3_f32(&b, threads).unwrap();
95 let s = t0.elapsed().as_secs_f64();
96 let h = y.iter().fold(0xcbf29ce484222325u64, |h, v| {
97 (h ^ v.to_bits() as u64).wrapping_mul(0x100000001b3)
98 });
99 println!(
100 "{{\"stream\":\"{f}\",\"identical\":{},\"dec_s\":{s},\"gbs\":{}}}",
101 format!("{h:016x}") == want.trim(),
102 y.len() as f64 * 4.0 / 1e9 / s
103 );
104 }
105 return;
106 }
107 if mode == "ship3" {
108 // gpubench ship3 STREAM.tcg3... [--mlp] [pretrained:PATH]: write STREAM.tcs3 carrying the
109 // built-in decoder (with the net's MLP as a graph when --mlp); report bytes and compile/cache time
110 for p in args.iter().filter_map(|a| a.strip_prefix("pretrained:")) {
111 cram_codec::learned::register(std::sync::Arc::new(
112 cram_codec::learned::Net::from_bytes(&std::fs::read(p).unwrap()).unwrap(),
113 ));
114 }
115 let mlp = args.iter().any(|a| a == "--mlp");
116 for f in args[1..].iter().filter(|a| a.ends_with(".tcg3")) {
117 let b = std::fs::read(f).unwrap();
118 let ps = gpuwav3::parse3(&b).unwrap();
119 let prog = cram_codec::shipdec::builtin_program(
120 if mlp { ps.net.as_ref() } else { None },
121 ps.net.is_some() && ps.ad.wa > 0,
122 );
123 let w = cram_codec::shipdec::wrap(&b, &prog);
124 let out = format!("{}.tcs3", f.trim_end_matches(".tcg3"));
125 std::fs::write(&out, &w).unwrap();
126 std::fs::copy(format!("{f}.hash"), format!("{out}.hash")).unwrap();
127 let t0 = Instant::now();
128 let d = cram_codec::shipdec::compile(&prog).unwrap();
129 let verify_lower_s = t0.elapsed().as_secs_f64();
130 let t1 = Instant::now();
131 let _ = cram_codec::shipdec::compile_cached(&prog).unwrap();
132 let first_s = t1.elapsed().as_secs_f64();
133 let t2 = Instant::now();
134 let _ = cram_codec::shipdec::compile_cached(&prog).unwrap();
135 let hit_s = t2.elapsed().as_secs_f64();
136 #[cfg(not(feature = "gpu"))]
137 let gpu = String::new();
138 #[cfg(feature = "gpu")]
139 let gpu = {
140 let ws = gpuwav3::parse3(&w).unwrap();
141 let g = cram_codec::gpudec::GpuDecoder::new(
142 ws.lanes,
143 ws.d3.iter().copied().max().unwrap(),
144 )
145 .unwrap();
146 let mut tt = cram_codec::gpudec::GpuTimes::default();
147 let pr = g.prepare3(&ws, &w, &mut tt);
148 let cold = g.warm3(&pr);
149 let hot = g.warm3(&pr);
150 format!(",\"gpu_pipeline_cold_s\":{cold},\"gpu_pipeline_hit_s\":{hot}")
151 };
152 println!(
153 "{{\"out\":\"{out}\",\"stream_bytes\":{},\"program_bytes\":{},\"added_bytes\":{},\"mlp\":{},\"verify_lower_s\":{verify_lower_s},\"cache_miss_s\":{first_s},\"cache_hit_s\":{hit_s},\"wgsl_chars\":{}{gpu}}}",
154 b.len(),
155 prog.len(),
156 w.len() - b.len(),
157 d.mlp.is_some(),
158 d.wgsl_res.len() + d.wgsl_mlp.as_ref().map_or(0, |m| m.len())
159 );
160 }
161 return;
162 }
163 #[cfg(feature = "gpu")]
164 if mode == "gpu3" {
165 // gpubench gpu3 STREAM... [--model3 pretrained:PATH]: GPU decode vs saved hash, kernel timing
166 for p in args.iter().filter_map(|a| a.strip_prefix("pretrained:")) {
167 cram_codec::learned::register(std::sync::Arc::new(
168 cram_codec::learned::Net::from_bytes(&std::fs::read(p).unwrap()).unwrap(),
169 ));
170 }
171 let reps: usize = opt(&args, "--reps", "3").parse().unwrap();
172 for f in args[1..]
173 .iter()
174 .filter(|a| a.ends_with(".tcg3") || a.ends_with(".tcs3"))
175 {
176 let b = std::fs::read(f).unwrap();
177 let want = std::fs::read_to_string(format!("{f}.hash")).unwrap();
178 let ps = gpuwav3::parse3(&b).unwrap();
179 let g =
180 cram_codec::gpudec::GpuDecoder::new(ps.lanes, ps.d3.iter().copied().max().unwrap())
181 .unwrap();
182 let (v, _) = g.decode3(&b).unwrap();
183 let h = v.iter().fold(0xcbf29ce484222325u64, |h, &x| {
184 (h ^ x as u64).wrapping_mul(0x100000001b3)
185 });
186 let mut t2 = cram_codec::gpudec::GpuTimes::default();
187 let pr = g.prepare3(&ps, &b, &mut t2);
188 let kern = (0..reps).map(|_| g.run3(&pr)).fold(f64::INFINITY, f64::min);
189 let mut tot = f64::INFINITY;
190 for _ in 0..reps {
191 let t0 = Instant::now();
192 let _ = g.decode3(&b).unwrap();
193 tot = tot.min(t0.elapsed().as_secs_f64());
194 }
195 let gb = v.len() as f64 * 4.0 / 1e9;
196 println!("{{\"stream\":\"{f}\",\"identical\":{},\"kernels_s\":{kern},\"kernels_gbs\":{},\"total_gbs\":{}}}", format!("{h:016x}") == want.trim(), gb / kern, gb / tot);
197 }
198 return;
199 }
200 let x = read_f32(&args[1]);
201 let dims: Vec<usize> = args[2].split('x').map(|s| s.parse().unwrap()).collect();
202 let name = std::path::Path::new(&args[1])
203 .file_stem()
204 .unwrap()
205 .to_string_lossy()
206 .to_string();
207 let bricks: Vec<usize> = opt(&args, "--bricks", "64")
208 .split(',')
209 .map(|s| s.parse().unwrap())
210 .collect();
211 let lanes: usize = opt(&args, "--lanes", "16").parse().unwrap();
212 let threads: usize = opt(&args, "--threads", "16").parse().unwrap();
213 let targets = opt(&args, "--targets", "p60,p80,p100,m1e-3,m1e-4");
214 let serial = !args.iter().any(|a| a == "--no-serial");
215 let (lo, hi) = x
216 .iter()
217 .fold((f32::INFINITY, f32::NEG_INFINITY), |(a, b), &v| {
218 (a.min(v), b.max(v))
219 });
220 let range = (hi - lo) as f64;
221 match mode {
222 "ratio" => {
223 for t in targets.split(',') {
224 let bound = if let Some(p) = t.strip_prefix('p') {
225 ErrorBound::Psnr(p.parse().unwrap())
226 } else {
227 ErrorBound::MaxAbs(t[1..].parse::<f64>().unwrap() * range)
228 };
229 if serial {
230 let t0 = Instant::now();
231 let (b, _) = wavcodec::encode_f32(&x, &dims, &wavcodec::WavConfig::new(bound));
232 let enc_s = t0.elapsed().as_secs_f64();
233 let t0 = Instant::now();
234 let Decoded::F32 { data, .. } = wavcodec::decode(&b).unwrap() else {
235 panic!()
236 };
237 let dec_s = t0.elapsed().as_secs_f64();
238 let (rmse, mx) = err(&x, &data);
239 println!(
240 "{{\"field\":\"{name}\",\"target\":\"{t}\",\"codec\":\"tc-wavelet\",\"brick\":0,\"bpv\":{},\"psnr\":{},\"max_abs_rel\":{},\"enc_s\":{enc_s},\"dec_s\":{dec_s}}}",
241 8.0 * b.len() as f64 / x.len() as f64,
242 20.0 * (range / rmse).log10(),
243 mx / range
244 );
245 }
246 let v3 = args.iter().any(|a| a == "--v3");
247 for &bk in &bricks {
248 let cfg = GpuConfig {
249 brick: bk,
250 lanes,
251 adaptive: args.iter().any(|a| a == "--adaptive"),
252 ..GpuConfig::new(bound)
253 };
254 let cfg3 = cfg3_of(bound, bk, &args);
255 let t0 = Instant::now();
256 let (b, st) = if v3 {
257 gpuwav3::encode3_f32(&x, &dims, &cfg3, threads)
258 } else {
259 gpuwav::encode_f32_mt(&x, &dims, &cfg, threads)
260 };
261 let enc_s = t0.elapsed().as_secs_f64();
262 let t0 = Instant::now();
263 let (_, y) = if v3 {
264 gpuwav3::decode3_f32(&b, 1).unwrap()
265 } else {
266 gpuwav::decode_f32(&b, 1).unwrap()
267 };
268 let dec_s = t0.elapsed().as_secs_f64();
269 let (rmse, mx) = err(&x, &y);
270 if let ErrorBound::MaxAbs(bb) = bound {
271 assert!(mx <= bb, "{name} {t}: {mx} > {bb}");
272 }
273 println!(
274 "{{\"field\":\"{name}\",\"target\":\"{t}\",\"codec\":\"{}\",\"brick\":{bk},\"lanes\":{lanes},\"bpv\":{},\"psnr\":{},\"max_abs_rel\":{},\"enc_s\":{enc_s},\"dec_s\":{dec_s},\"hdr\":{},\"idx\":{},\"outl\":{},\"esc\":{}}}",
275 if v3 { "tc-gpu3" } else { "tc-gpu" },
276 st.bits_per_value,
277 20.0 * (range / rmse).log10(),
278 mx / range,
279 st.header_bytes,
280 st.index_bytes,
281 st.outlier_bytes,
282 st.n_escapes
283 );
284 }
285 }
286 }
287 "speed" => {
288 // encode once, then time decoders on the same stream
289 let t = opt(&args, "--target", "p80");
290 let bound = if let Some(p) = t.strip_prefix('p') {
291 ErrorBound::Psnr(p.parse().unwrap())
292 } else {
293 ErrorBound::MaxAbs(t[1..].parse::<f64>().unwrap() * range)
294 };
295 let reps: usize = opt(&args, "--reps", "3").parse().unwrap();
296 for &bk in &bricks {
297 let cfg = GpuConfig {
298 brick: bk,
299 lanes,
300 adaptive: args.iter().any(|a| a == "--adaptive"),
301 ..GpuConfig::new(bound)
302 };
303 let t0 = Instant::now();
304 let (b, st) = gpuwav::encode_f32_mt(&x, &dims, &cfg, threads);
305 let enc_s = t0.elapsed().as_secs_f64();
306 let gb = x.len() as f64 * 4.0 / 1e9;
307 let best = |f: &mut dyn FnMut() -> f64| {
308 (0..reps).map(|_| f()).fold(f64::INFINITY, f64::min)
309 };
310 let ps = gpuwav::parse(&b).unwrap();
311 let mut cpu_bits = Vec::new();
312 let cpu1 = best(&mut || {
313 let t0 = Instant::now();
314 cpu_bits = gpuwav::decode_box_parsed(&ps, &b, [0; 3], ps.d3, 1).unwrap();
315 t0.elapsed().as_secs_f64()
316 });
317 let cpumt = best(&mut || {
318 let t0 = Instant::now();
319 let v = gpuwav::decode_box_parsed(&ps, &b, [0; 3], ps.d3, threads).unwrap();
320 assert!(v == cpu_bits);
321 t0.elapsed().as_secs_f64()
322 });
323 // random access: 64^3 (or 64^2) box at the field centre
324 let lo: [usize; 3] =
325 std::array::from_fn(|a| if ps.d3[a] > 64 { ps.d3[a] / 2 - 32 } else { 0 });
326 let hi: [usize; 3] = std::array::from_fn(|a| (lo[a] + 64).min(ps.d3[a]));
327 let mut sub = Vec::new();
328 let ra = best(&mut || {
329 let t0 = Instant::now();
330 sub = gpuwav::decode_box_bits(&b, lo, hi, 1).unwrap();
331 t0.elapsed().as_secs_f64()
332 });
333 let ra_mt = best(&mut || {
334 let t0 = Instant::now();
335 let _ = gpuwav::decode_box_bits(&b, lo, hi, threads).unwrap();
336 t0.elapsed().as_secs_f64()
337 });
338 let mut k = 0;
339 for z in lo[0]..hi[0] {
340 for y in lo[1]..hi[1] {
341 for xx in lo[2]..hi[2] {
342 assert_eq!(
343 sub[k],
344 cpu_bits[(z * ps.d3[1] + y) * ps.d3[2] + xx],
345 "random access mismatch"
346 );
347 k += 1;
348 }
349 }
350 }
351 #[allow(unused_mut, unused_assignments)]
352 let mut gpu_json = String::new();
353 #[cfg(feature = "gpu")]
354 {
355 let maxl = ps.shapes.iter().flat_map(|s| s.dims).max().unwrap();
356 let g = cram_codec::gpudec::GpuDecoder::new(lanes, maxl).expect("gpu");
357 // warm-up + check
358 let (v, _) = g.decode(&b).unwrap();
359 let diff = v.iter().zip(&cpu_bits).filter(|(a, b)| a != b).count();
360 assert_eq!(diff, 0, "GPU != CPU in {diff} cells");
361 let mut tt = cram_codec::gpudec::GpuTimes::default();
362 let mut tot = f64::INFINITY;
363 for _ in 0..reps {
364 let t0 = Instant::now();
365 let (v, t) = g.decode(&b).unwrap();
366 let w = t0.elapsed().as_secs_f64();
367 assert!(v == cpu_bits);
368 if w < tot {
369 tot = w;
370 tt = t;
371 }
372 }
373 let ps2 = gpuwav::parse(&b).unwrap();
374 let mut t2 = cram_codec::gpudec::GpuTimes::default();
375 let pr = g.prepare(&ps2, &b, &mut t2);
376 let kern = best(&mut || g.run(&pr));
377 gpu_json = format!(
378 ",\"gpu\":\"{}\",\"gpu_kernels_s\":{kern},\"gpu_kernels_gbs\":{},\"gpu_total_s\":{tot},\"gpu_total_gbs\":{},\"gpu_parse_s\":{},\"gpu_upload_s\":{},\"gpu_readback_s\":{},\"gpu_bit_identical\":true",
379 g.name,
380 gb / kern,
381 gb / tot,
382 tt.parse_s,
383 tt.upload_s,
384 tt.readback_s
385 );
386 }
387 println!(
388 "{{\"field\":\"{name}\",\"dims\":\"{}\",\"target\":\"{t}\",\"brick\":{bk},\"lanes\":{lanes},\"bpv\":{},\"enc_s\":{enc_s},\"cpu1_s\":{cpu1},\"cpu1_gbs\":{},\"cpumt_s\":{cpumt},\"cpumt_gbs\":{},\"threads\":{threads},\"ra_box\":\"{:?}-{:?}\",\"ra_s\":{ra},\"ra_mt_s\":{ra_mt}{gpu_json}}}",
389 args[2],
390 st.bits_per_value,
391 gb / cpu1,
392 gb / cpumt,
393 lo,
394 hi
395 );
396 }
397 }
398 #[cfg(feature = "gpu")]
399 "verify" => {
400 // CPU == GPU byte-for-byte, error bounds, and random-access crops
401 let g = cram_codec::gpudec::GpuDecoder::new(lanes, 1024).expect("gpu");
402 for t in targets.split(',') {
403 let bound = if let Some(p) = t.strip_prefix('p') {
404 ErrorBound::Psnr(p.parse().unwrap())
405 } else {
406 ErrorBound::MaxAbs(t[1..].parse::<f64>().unwrap() * range)
407 };
408 for &bk in &bricks {
409 let cfg = GpuConfig {
410 brick: bk,
411 lanes,
412 ..GpuConfig::new(bound)
413 };
414 let (b, st) = gpuwav::encode_f32_mt(&x, &dims, &cfg, threads);
415 let ps = gpuwav::parse(&b).unwrap();
416 let cpu = gpuwav::decode_box_parsed(&ps, &b, [0; 3], ps.d3, threads).unwrap();
417 let (gpu, _) = g.decode(&b).unwrap();
418 let diff = cpu.iter().zip(&gpu).filter(|(a, b)| a != b).count();
419 let y: Vec<f32> = cpu.iter().map(|&v| f32::from_bits(v)).collect();
420 let (rmse, mx) = err(&x, &y);
421 let ok_bound = match bound {
422 ErrorBound::MaxAbs(bb) => mx <= bb,
423 ErrorBound::Psnr(db) => 20.0 * (range / rmse).log10() >= db - 1e-9,
424 _ => true,
425 };
426 // random-access sub-box straddling bricks
427 let lo: [usize; 3] = std::array::from_fn(|a| ps.d3[a] / 3);
428 let hi: [usize; 3] =
429 std::array::from_fn(|a| (lo[a] + 64).min(ps.d3[a]).max(lo[a] + 1));
430 let sub = gpuwav::decode_box_bits(&b, lo, hi, 1).unwrap();
431 let mut ra_ok = true;
432 let mut k = 0;
433 for z in lo[0]..hi[0] {
434 for yy in lo[1]..hi[1] {
435 for xx in lo[2]..hi[2] {
436 ra_ok &= sub[k] == cpu[(z * ps.d3[1] + yy) * ps.d3[2] + xx];
437 k += 1;
438 }
439 }
440 }
441 println!(
442 "{{\"field\":\"{name}\",\"target\":\"{t}\",\"brick\":{bk},\"bpv\":{},\"cells\":{},\"gpu_diff_cells\":{diff},\"bound_ok\":{ok_bound},\"ra_ok\":{ra_ok},\"max_abs_rel\":{}}}",
443 st.bits_per_value,
444 x.len(),
445 mx / range
446 );
447 assert!(diff == 0 && ok_bound && ra_ok, "{name} {t} {bk}");
448 }
449 }
450 }
451 #[cfg(feature = "gpu")]
452 "verify3" | "speed3" => {
453 let speed = mode == "speed3";
454 let maxl = dims.iter().copied().max().unwrap();
455 let g = cram_codec::gpudec::GpuDecoder::new(lanes, maxl).expect("gpu");
456 let reps: usize = opt(&args, "--reps", "3").parse().unwrap();
457 let best =
458 |f: &mut dyn FnMut() -> f64| (0..reps).map(|_| f()).fold(f64::INFINITY, f64::min);
459 let gb = x.len() as f64 * 4.0 / 1e9;
460 for t in targets.split(',') {
461 let bound = if let Some(p) = t.strip_prefix('p') {
462 ErrorBound::Psnr(p.parse().unwrap())
463 } else {
464 ErrorBound::MaxAbs(t[1..].parse::<f64>().unwrap() * range)
465 };
466 for &bk in &bricks {
467 let cfg = cfg3_of(bound, bk, &args);
468 let t0 = Instant::now();
469 let (b, st) = gpuwav3::encode3_f32(&x, &dims, &cfg, threads);
470 let enc_s = t0.elapsed().as_secs_f64();
471 let ps = gpuwav3::parse3(&b).unwrap();
472 let full = |th: usize| {
473 gpuwav3::decode3_box_parsed(&ps, &b, [0; 3], ps.d3, th)
474 .unwrap()
475 .bits
476 };
477 let cpu = full(threads);
478 let (gpu, _) = g.decode3(&b).unwrap();
479 let diff = cpu.iter().zip(&gpu).filter(|(a, b)| a != b).count();
480 if diff > 0 {
481 let i = cpu.iter().zip(&gpu).position(|(a, b)| a != b).unwrap();
482 eprintln!(
483 "first diff at {i}: cpu {} gpu {}",
484 f32::from_bits(cpu[i]),
485 f32::from_bits(gpu[i])
486 );
487 }
488 let y: Vec<f32> = cpu.iter().map(|&v| f32::from_bits(v)).collect();
489 let (rmse, mx) = err(&x, &y);
490 let ok_bound = match bound {
491 ErrorBound::MaxAbs(bb) => mx <= bb,
492 ErrorBound::Psnr(db) => 20.0 * (range / rmse).log10() >= db - 1e-9,
493 _ => true,
494 };
495 // random access: 64^3 (2D: 64^2) box, off-centre so it straddles blocks
496 let lo: [usize; 3] =
497 std::array::from_fn(|a| if ps.d3[a] > 64 { ps.d3[a] / 3 } else { 0 });
498 let hi: [usize; 3] = std::array::from_fn(|a| (lo[a] + 64).min(ps.d3[a]));
499 let mut sub = None;
500 let ra = best(&mut || {
501 let t0 = Instant::now();
502 sub = Some(gpuwav3::decode3_box(&b, lo, hi, 1).unwrap());
503 t0.elapsed().as_secs_f64()
504 });
505 let ra_mt = best(&mut || {
506 let t0 = Instant::now();
507 let _ = gpuwav3::decode3_box(&b, lo, hi, threads).unwrap();
508 t0.elapsed().as_secs_f64()
509 });
510 let sub = sub.unwrap();
511 let mut ra_ok = true;
512 let mut k = 0;
513 for z in lo[0]..hi[0] {
514 for yy in lo[1]..hi[1] {
515 for xx in lo[2]..hi[2] {
516 ra_ok &= sub.bits[k] == cpu[(z * ps.d3[1] + yy) * ps.d3[2] + xx];
517 k += 1;
518 }
519 }
520 }
521 let mut sp = String::new();
522 if speed {
523 let cpu1 = best(&mut || {
524 let t0 = Instant::now();
525 let _ = full(1);
526 t0.elapsed().as_secs_f64()
527 });
528 let cpumt = best(&mut || {
529 let t0 = Instant::now();
530 let _ = full(threads);
531 t0.elapsed().as_secs_f64()
532 });
533 let mut tt = cram_codec::gpudec::GpuTimes::default();
534 let tot = best(&mut || {
535 let t0 = Instant::now();
536 let (v, t) = g.decode3(&b).unwrap();
537 assert!(v == cpu);
538 tt = t;
539 t0.elapsed().as_secs_f64()
540 });
541 let mut t2 = cram_codec::gpudec::GpuTimes::default();
542 let pr = g.prepare3(&ps, &b, &mut t2);
543 let kern = best(&mut || g.run3(&pr));
544 sp = format!(
545 ",\"enc_s\":{enc_s},\"cpu1_gbs\":{},\"cpumt_gbs\":{},\"gpu_kernels_gbs\":{},\"gpu_total_gbs\":{},\"gpu_readback_s\":{},\"ra_s\":{ra},\"ra_mt_s\":{ra_mt},\"ra_bytes\":{},\"ra_blocks\":{},\"total_blocks\":{}",
546 gb / cpu1,
547 gb / cpumt,
548 gb / kern,
549 gb / tot,
550 tt.readback_s,
551 sub.bytes_read,
552 sub.blocks,
553 ps.blocks.len()
554 );
555 }
556 println!(
557 "{{\"field\":\"{name}\",\"target\":\"{t}\",\"block\":{bk},\"bpv\":{},\"bytes\":{},\"cells\":{},\"gpu_diff_cells\":{diff},\"bound_ok\":{ok_bound},\"ra_ok\":{ra_ok},\"ra_bytes\":{},\"ra_s\":{ra}{sp}}}",
558 st.bits_per_value,
559 b.len(),
560 x.len(),
561 sub.bytes_read
562 );
563 assert!(diff == 0 && ok_bound && ra_ok, "{name} {t} {bk}");
564 }
565 }
566 }
567 "encode3" => {
568 // write a v3 stream for later GPU-only timing: gpubench encode3 FIELD dims OUT --bricks B --targets T ...
569 let out = opt(&args, "--out", "out.tcg3");
570 let t = targets.split(',').next().unwrap();
571 let bound = if let Some(p) = t.strip_prefix('p') {
572 ErrorBound::Psnr(p.parse().unwrap())
573 } else {
574 ErrorBound::MaxAbs(t[1..].parse::<f64>().unwrap() * range)
575 };
576 let (b, st) =
577 gpuwav3::encode3_f32(&x, &dims, &cfg3_of(bound, bricks[0], &args), threads);
578 std::fs::write(&out, &b).unwrap();
579 let (_, y) = gpuwav3::decode3_f32(&b, threads).unwrap();
580 let h = y.iter().fold(0xcbf29ce484222325u64, |h, v| {
581 (h ^ v.to_bits() as u64).wrapping_mul(0x100000001b3)
582 });
583 std::fs::write(format!("{out}.hash"), format!("{h:016x}")).unwrap();
584 println!(
585 "{{\"out\":\"{out}\",\"bpv\":{},\"hash\":\"{h:016x}\"}}",
586 st.bits_per_value
587 );
588 }
589 _ => panic!("mode"),
590 }
591}