mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-26 19:17:02 -06:00
Recover the unpublished denoise work against current upstream. Preserve later opt-in model setup, shared Rust HTTPS downloads, pending-photo resume and an Amount-independent switch. Keep original RAWs unchanged and share the current color finish between ordinary and denoised sources. Bound public tiling inputs, refuse corrupt-cache exports, and retain synthetic/real-model coverage. Include two Windows path spelling fixes in existing tests. No model weights, face code, media or Tract runtime are included. Validation: cargo xtask ci (all seven gates); installed RawNIND CPU/GPU agreement 1.38e-6; native CPU preview/export on a public 24 MP Sony RAW, unchanged original SHA-256. Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> Generated with Codex.
152 lines
5.9 KiB
Rust
152 lines
5.9 KiB
Rust
use super::*;
|
|
use crate::{
|
|
net::{Conv, ConvT2},
|
|
reference, synthetic,
|
|
};
|
|
|
|
fn random(n: usize, seed: u32) -> Vec<f32> {
|
|
let mut s = seed | 1;
|
|
(0..n)
|
|
.map(|_| {
|
|
s ^= s << 13;
|
|
s ^= s >> 17;
|
|
s ^= s << 5;
|
|
(s >> 8) as f32 / (1u32 << 23) as f32 - 1.0
|
|
})
|
|
.collect()
|
|
}
|
|
fn net(tile: usize, ch: u64, depth: usize, seed: u64) -> Net {
|
|
let p = std::env::temp_dir().join(format!("lc-purecpu-{}-{tile}-{ch}-{depth}-{seed}.onnx", std::process::id()));
|
|
std::fs::write(&p, synthetic::unet_onnx(tile as u64, ch, depth, seed)).unwrap();
|
|
let n = crate::onnx::read(&p).unwrap();
|
|
std::fs::remove_file(p).unwrap();
|
|
n
|
|
}
|
|
fn agree(a: &[f32], b: &[f32]) {
|
|
assert_eq!(a.len(), b.len());
|
|
assert!(a.iter().chain(b).all(|v| v.is_finite()));
|
|
let scale = b.iter().fold(1e-6f32, |m, v| m.max(v.abs()));
|
|
let error = a.iter().zip(b).fold(0f32, |m, (a, b)| m.max((a - b).abs())) / scale;
|
|
assert!(error <= 1e-3, "relative maximum error {error:e}");
|
|
}
|
|
#[test]
|
|
fn all_operators_match_scalar_on_random_nets_and_odd_and_tiny_shapes() {
|
|
for (t, ch, d) in [(1, 3, 0), (3, 5, 0), (5, 7, 0), (6, 3, 1), (20, 5, 2), (36, 9, 2), (64, 8, 3)] {
|
|
for seed in [1, 73] {
|
|
let n = net(t, ch, d, seed);
|
|
let r = NetRunner::new(&n, t).unwrap();
|
|
let x = random(4 * t * t, seed as u32);
|
|
let want = reference::run(&n, t, &x).unwrap();
|
|
agree(&r.run(&x).unwrap(), &want);
|
|
assert_eq!(r.run(&x).unwrap(), r.run(&x).unwrap());
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn malformed_values_fail_before_execution_and_workspace_sizes_are_bounded() {
|
|
let mut n = net(4, 3, 1, 11);
|
|
for t in [0, 1, 3, usize::MAX, 4096] {
|
|
assert!(NetRunner::new(&n, t).is_err());
|
|
}
|
|
for ch in [0, 3, usize::MAX] {
|
|
n.in_channels = ch;
|
|
assert!(NetRunner::new(&n, 4).is_err());
|
|
}
|
|
let n = Net::new(
|
|
4,
|
|
vec![
|
|
Op::Conv(Conv { src: 0, src2: None, cin: 4, cout: 4096, k: 1, weight: vec![0.1; 4 * 4096], bias: vec![0.0; 4096], leaky: None }),
|
|
Op::Conv(Conv { src: 1, src2: None, cin: 4096, cout: 12, k: 1, weight: vec![0.1; 4096 * 12], bias: vec![0.0; 12], leaky: None }),
|
|
Op::DepthToSpace2 { src: 2 },
|
|
],
|
|
)
|
|
.unwrap();
|
|
assert!(NetRunner::new(&n, 2048).is_err());
|
|
let ops = (0..100).map(|i| Op::MaxPool2 { src: i }).collect();
|
|
let n = Net::new(4, ops).unwrap();
|
|
assert!(NetRunner::new(&n, 16).is_err());
|
|
let c = Conv { src: 0, src2: None, cin: 4, cout: 12, k: 1, weight: vec![0.25; 48], bias: vec![0.0; 12], leaky: None };
|
|
for mode in 0..6 {
|
|
let mut bad = c.clone();
|
|
match mode {
|
|
0 => bad.src = usize::MAX,
|
|
1 => bad.weight.pop().map(|_| ()).unwrap(),
|
|
2 => bad.bias[0] = f32::NAN,
|
|
3 => bad.leaky = Some(f32::INFINITY),
|
|
4 => bad.cout = usize::MAX,
|
|
_ => bad.k = usize::MAX,
|
|
}
|
|
assert!(Net::new(4, vec![Op::Conv(bad), Op::DepthToSpace2 { src: 1 }]).is_err());
|
|
}
|
|
}
|
|
#[test]
|
|
fn bad_tiles_and_arithmetic_overflow_are_errors_and_do_not_poison_reuse() {
|
|
let c = Conv { src: 0, src2: None, cin: 4, cout: 12, k: 1, weight: vec![f32::MAX; 48], bias: vec![0.0; 12], leaky: None };
|
|
let n = Net::new(4, vec![Op::Conv(c), Op::DepthToSpace2 { src: 1 }]).unwrap();
|
|
let r = NetRunner::new(&n, 1).unwrap();
|
|
assert!(r.run(&[]).is_err());
|
|
assert!(r.run(&[f32::NAN; 4]).is_err());
|
|
assert!(r.run(&[f32::INFINITY; 4]).is_err());
|
|
assert_eq!(r.idle.lock().unwrap().len(), 0, "invalid input never allocates a workspace");
|
|
assert!(r.run(&[2.0; 4]).is_err());
|
|
assert_eq!(r.run(&[0.0; 4]).unwrap(), vec![0.0; 12]);
|
|
// Matrix shape checks are the boundary in front of the dependency's asserting constructors.
|
|
assert!(multiply(&[], &[], &mut [], usize::MAX, 2, 2).is_err());
|
|
}
|
|
#[test]
|
|
fn shared_runner_serves_concurrent_tiles_without_cross_contamination() {
|
|
let n = net(12, 5, 1, 97);
|
|
let r = NetRunner::new(&n, 12).unwrap();
|
|
let inputs: Vec<_> = (0..8).map(|s| random(4 * 12 * 12, s)).collect();
|
|
let answers: Vec<_> = inputs.iter().map(|x| reference::run(&n, 12, x).unwrap()).collect();
|
|
std::thread::scope(|scope| {
|
|
let mut handles = Vec::new();
|
|
for (x, want) in inputs.iter().zip(&answers) {
|
|
let r = &r;
|
|
handles.push(scope.spawn(move || {
|
|
for _ in 0..3 {
|
|
agree(&r.run(x).unwrap(), want);
|
|
}
|
|
}));
|
|
}
|
|
for h in handles {
|
|
h.join().unwrap();
|
|
}
|
|
});
|
|
}
|
|
#[test]
|
|
fn join_may_read_the_same_tensor_twice_and_leaky_up_convolution_is_supported() {
|
|
let c = ConvT2 { src: 0, cin: 4, cout: 3, weight: random(48, 2), bias: vec![0.1, -0.1, 0.0], leaky: Some(0.13) };
|
|
let n = Net::new(
|
|
4,
|
|
vec![
|
|
Op::ConvT2(c),
|
|
Op::Conv(Conv { src: 1, src2: Some(1), cin: 6, cout: 3, k: 3, weight: random(162, 3), bias: vec![0.0; 3], leaky: Some(0.0) }),
|
|
],
|
|
)
|
|
.unwrap();
|
|
let x = random(4 * 3 * 3, 7);
|
|
agree(&NetRunner::new(&n, 3).unwrap().run(&x).unwrap(), &reference::run(&n, 3, &x).unwrap());
|
|
}
|
|
|
|
#[test]
|
|
fn reused_im2col_clears_all_edge_padding_between_images() {
|
|
for t in [1, 3, 5, 36] {
|
|
let n = net(t, 5, 0, 121);
|
|
let r = NetRunner::new(&n, t).unwrap();
|
|
for x in [vec![1.0; 4 * t * t], vec![0.0; 4 * t * t], random(4 * t * t, 9)] {
|
|
agree(&r.run(&x).unwrap(), &reference::run(&n, t, &x).unwrap());
|
|
}
|
|
}
|
|
}
|
|
#[test]
|
|
#[ignore = "needs LC_DENOISE_MODEL; one tile with LIGHTCRAFT_PROFILE=1"]
|
|
fn profile_one_real_tile() {
|
|
let model = std::env::var_os("LC_DENOISE_MODEL").expect("set LC_DENOISE_MODEL");
|
|
let m = crate::known::find("rawnind-bayer").unwrap().manifest;
|
|
let r = crate::runtime::CpuRunner::load(std::path::Path::new(&model), &m).unwrap();
|
|
println!("workspace {:.1} MB", r.bytes_per_tile() as f64 / 1e6);
|
|
r.run(&crate::runtime::check_tile(512)).unwrap();
|
|
}
|