inferencelayer 0.2.3

Kortexya's engine-native inference layer — LLM generation + embedding/encoder family on wgpu (WGSL kernels, any adapter) with a pure-Rust CPU fallback
Documentation
//! Backbone-conv benchmark for the Docling layout port — the ENGINE arm of `bench/conv_ab.py`.
//!
//! Emits `name,mult,ms` CSV rows (min-of-iters per shape, KERNEL TIME ONLY — process startup is
//! excluded so the interleaved driver can alternate arms without startup pollution). The shape
//! set walks one full RT-DETR ResNet-50 backbone pass at 640×640 (heron's `rt_detr_resnet`,
//! depths [3,4,6,3], bottleneck, hidden [256,512,1024,2048]); `mult` is how many times the shape
//! occurs per pass, so `Σ mult·ms` is the backbone conv budget. The torch twin is
//! `tests/fixtures/bench_conv_torch.py` — SAME rows, same accounting.
//!
//! Measurement discipline per PERF_CAMPAIGN.md: run through `bench/conv_ab.py` (interleaves the
//! two arms in one window), never compare numbers from different runs of this bin alone.

use std::time::Instant;

use inferencelayer::conv2d::Conv2d;

struct Row {
    name: &'static str,
    ic: usize,
    oc: usize,
    k: usize,
    s: usize,
    h: usize,
    w: usize,
    mult: usize,
}

// One backbone pass at 640×640. Stage entries collapse repeated blocks into `mult`.
const ROWS: &[Row] = &[
    // ResNet-D deep stem (3→32 s2, 32→32, 32→64) + the maxpool happens outside the conv budget
    Row {
        name: "stem_a_3x3s2_3_32_640",
        ic: 3,
        oc: 32,
        k: 3,
        s: 2,
        h: 640,
        w: 640,
        mult: 1,
    },
    Row {
        name: "stem_b_3x3s1_32_32_320",
        ic: 32,
        oc: 32,
        k: 3,
        s: 1,
        h: 320,
        w: 320,
        mult: 1,
    },
    Row {
        name: "stem_c_3x3s1_32_64_320",
        ic: 32,
        oc: 64,
        k: 3,
        s: 1,
        h: 320,
        w: 320,
        mult: 1,
    },
    // stage 1 @160: 3 bottlenecks (64,64,256), first has a 1×1 downsample shortcut
    Row {
        name: "s1_1x1_64_64_160",
        ic: 64,
        oc: 64,
        k: 1,
        s: 1,
        h: 160,
        w: 160,
        mult: 3,
    },
    Row {
        name: "s1_3x3_64_64_160",
        ic: 64,
        oc: 64,
        k: 3,
        s: 1,
        h: 160,
        w: 160,
        mult: 3,
    },
    Row {
        name: "s1_1x1_64_256_160",
        ic: 64,
        oc: 256,
        k: 1,
        s: 1,
        h: 160,
        w: 160,
        mult: 3,
    },
    Row {
        name: "s1_ds_1x1_64_256_160",
        ic: 64,
        oc: 256,
        k: 1,
        s: 1,
        h: 160,
        w: 160,
        mult: 1,
    },
    Row {
        name: "s1_1x1_256_64_160",
        ic: 256,
        oc: 64,
        k: 1,
        s: 1,
        h: 160,
        w: 160,
        mult: 2,
    },
    // stage 2 @80: 4 bottlenecks (128,128,512), stride-2 in the first 3×3
    Row {
        name: "s2_1x1_256_128_160",
        ic: 256,
        oc: 128,
        k: 1,
        s: 1,
        h: 160,
        w: 160,
        mult: 1,
    },
    Row {
        name: "s2_3x3s2_128_128",
        ic: 128,
        oc: 128,
        k: 3,
        s: 2,
        h: 160,
        w: 160,
        mult: 1,
    },
    Row {
        name: "s2_3x3_128_128_80",
        ic: 128,
        oc: 128,
        k: 3,
        s: 1,
        h: 80,
        w: 80,
        mult: 3,
    },
    Row {
        name: "s2_1x1_128_512_80",
        ic: 128,
        oc: 512,
        k: 1,
        s: 1,
        h: 80,
        w: 80,
        mult: 4,
    },
    Row {
        name: "s2_1x1_512_128_80",
        ic: 512,
        oc: 128,
        k: 1,
        s: 1,
        h: 80,
        w: 80,
        mult: 3,
    },
    Row {
        name: "s2_ds_1x1_256_512_80",
        ic: 256,
        oc: 512,
        k: 1,
        s: 1,
        h: 80,
        w: 80,
        mult: 1,
    },
    // stage 3 @40: 6 bottlenecks (256,256,1024)
    Row {
        name: "s3_1x1_512_256_80",
        ic: 512,
        oc: 256,
        k: 1,
        s: 1,
        h: 80,
        w: 80,
        mult: 1,
    },
    Row {
        name: "s3_3x3s2_256_256",
        ic: 256,
        oc: 256,
        k: 3,
        s: 2,
        h: 80,
        w: 80,
        mult: 1,
    },
    Row {
        name: "s3_3x3_256_256_40",
        ic: 256,
        oc: 256,
        k: 3,
        s: 1,
        h: 40,
        w: 40,
        mult: 5,
    },
    Row {
        name: "s3_1x1_256_1024_40",
        ic: 256,
        oc: 1024,
        k: 1,
        s: 1,
        h: 40,
        w: 40,
        mult: 6,
    },
    Row {
        name: "s3_1x1_1024_256_40",
        ic: 1024,
        oc: 256,
        k: 1,
        s: 1,
        h: 40,
        w: 40,
        mult: 5,
    },
    Row {
        name: "s3_ds_1x1_512_1024_40",
        ic: 512,
        oc: 1024,
        k: 1,
        s: 1,
        h: 40,
        w: 40,
        mult: 1,
    },
    // stage 4 @20: 3 bottlenecks (512,512,2048)
    Row {
        name: "s4_1x1_1024_512_40",
        ic: 1024,
        oc: 512,
        k: 1,
        s: 1,
        h: 40,
        w: 40,
        mult: 1,
    },
    Row {
        name: "s4_3x3s2_512_512",
        ic: 512,
        oc: 512,
        k: 3,
        s: 2,
        h: 40,
        w: 40,
        mult: 1,
    },
    Row {
        name: "s4_3x3_512_512_20",
        ic: 512,
        oc: 512,
        k: 3,
        s: 1,
        h: 20,
        w: 20,
        mult: 2,
    },
    Row {
        name: "s4_1x1_512_2048_20",
        ic: 512,
        oc: 2048,
        k: 1,
        s: 1,
        h: 20,
        w: 20,
        mult: 3,
    },
    Row {
        name: "s4_1x1_2048_512_20",
        ic: 2048,
        oc: 512,
        k: 1,
        s: 1,
        h: 20,
        w: 20,
        mult: 2,
    },
    Row {
        name: "s4_ds_1x1_1024_2048_20",
        ic: 1024,
        oc: 2048,
        k: 1,
        s: 1,
        h: 20,
        w: 20,
        mult: 1,
    },
];

fn main() {
    let iters: usize = std::env::args()
        .nth(1)
        .and_then(|s| s.parse().ok())
        .unwrap_or(5);

    // deterministic pseudo-random fill (no rand dep; values only need realistic magnitudes)
    let fill = |v: &mut [f32], seed: u32| {
        let mut s = seed | 1;
        for x in v.iter_mut() {
            s = s.wrapping_mul(1664525).wrapping_add(1013904223);
            *x = ((s >> 9) as f32 / (1 << 23) as f32) - 1.0;
        }
    };

    let mut total = 0f64;
    for r in ROWS {
        let pad = if r.k == 3 { 1 } else { 0 };
        let mut w = vec![0f32; r.oc * r.ic * r.k * r.k];
        let mut b = vec![0f32; r.oc];
        let mut x = vec![0f32; r.h * r.w * r.ic];
        fill(&mut w, 7);
        fill(&mut b, 11);
        fill(&mut x, 13);
        let conv = Conv2d::from_torch(&w, Some(&b), r.oc, r.ic, r.k, r.k, r.s, pad);
        // warmup
        let _ = conv.forward(&x, r.h, r.w);
        let mut best = f64::INFINITY;
        for _ in 0..iters {
            let t = Instant::now();
            let out = conv.forward(&x, r.h, r.w);
            let dt = t.elapsed().as_secs_f64() * 1e3;
            std::hint::black_box(&out);
            if dt < best {
                best = dt;
            }
        }
        total += best * r.mult as f64;
        println!("{},{},{:.4}", r.name, r.mult, best);
    }
    println!("TOTAL_BACKBONE_PASS_MS,{total:.3}");
}