gemmkit 0.1.2

A clean, extensible, high-performance GEMM (general matrix multiply) engine
Documentation
//! Coverage for public API surface that a correctness sweep would not otherwise reach: the
//! [`MatRef`]/[`MatMut`] shape accessors, the [`Workspace`] constructors, and the raw
//! `gemm_unchecked_with` entry point over a caller-owned workspace
#![cfg(all(not(miri), not(target_family = "wasm")))]

use gemmkit::{MatMut, MatRef, Parallelism, Workspace, gemm, gemm_unchecked_with};

/// `rows()`/`cols()` report the logical shape passed to a view's constructor, regardless of
/// which constructor built it or what strides it carries
#[test]
fn matref_matmut_accessors() {
    let data = [0.0f32; 12];
    let r = MatRef::from_row_major(&data, 3, 4);
    assert_eq!(r.rows(), 3);
    assert_eq!(r.cols(), 4);

    // MatRef::new takes explicit strides; the shape it reports is independent of them
    let r2 = MatRef::new(&data, 4, 3, 1, 4);
    assert_eq!((r2.rows(), r2.cols()), (4, 3));

    let mut md = [0.0f32; 12];
    let m = MatMut::from_col_major(&mut md, 4, 3);
    assert_eq!(m.rows(), 4);
    assert_eq!(m.cols(), 3);

    let mut md2 = [0.0f32; 6];
    let m2 = MatMut::new(&mut md2, 2, 3, 3, 1);
    assert_eq!((m2.rows(), m2.cols()), (2, 3));
}

/// An empty `Workspace` (`default()`, `new()`, or `with_capacity(0)`) and a pre-sized one both
/// drive `gemm_unchecked_with` to the same result as the safe, allocating `gemm` entry
#[test]
fn workspace_constructors_and_unchecked_with() {
    let (m, k, n) = (48usize, 40, 32);
    let a: Vec<f32> = (0..m * k).map(|i| (i % 13) as f32 * 0.1 - 0.6).collect();
    let b: Vec<f32> = (0..k * n).map(|i| (i % 11) as f32 * 0.2 - 1.0).collect();
    let c0: Vec<f32> = (0..m * n).map(|i| (i % 7) as f32 * 0.05).collect();
    let (alpha, beta) = (1.25f32, -0.5);

    // Reference result via the safe entry (pool-allocated workspace)
    let mut c_ref = c0.clone();
    gemm(
        alpha,
        MatRef::from_row_major(&a, m, k),
        MatRef::from_row_major(&b, k, n),
        beta,
        MatMut::from_row_major(&mut c_ref, m, n),
        Parallelism::Serial,
    );

    // default() and with_capacity(0) both construct empty; with_capacity(0) must not itself
    // allocate
    let mut ws_default = Workspace::default();
    let _ws_zero = Workspace::with_capacity(0);
    // Primed above what this shape needs, so the run below never grows it
    let mut ws_cap = Workspace::with_capacity(1 << 20);

    for ws in [&mut ws_default, &mut ws_cap] {
        let mut c = c0.clone();
        // SAFETY: row-major strides are in bounds for m/k/n, and C (its own Vec) aliases
        // neither A nor B
        unsafe {
            gemm_unchecked_with(
                ws,
                m,
                k,
                n,
                alpha,
                a.as_ptr(),
                k as isize,
                1,
                b.as_ptr(),
                n as isize,
                1,
                beta,
                c.as_mut_ptr(),
                n as isize,
                1,
                Parallelism::Serial,
            );
        }
        assert_eq!(
            c, c_ref,
            "gemm_unchecked_with must match the safe gemm entry"
        );
    }
}

/// Every public epilogue-configuration value is `Copy + Clone + Debug`. These are passed by
/// value into entries a caller invokes in a loop (1 per layer, 1 per batch step), so a
/// move-only config forces a fresh construction per call for no reason, and a missing
/// `Debug` makes the config the one thing a caller cannot print while debugging a layer.
/// Pinned rather than left incidental: dropping a derive is a silent source break for that
/// loop, and it would not show up in any correctness test
#[cfg(feature = "epilogue")]
#[test]
fn epilogue_config_values_are_copy_clone_debug() {
    fn assert_copy_clone_debug<T: Copy + Clone + core::fmt::Debug>(_: &T) {}

    let bias = [1.0f32, 2.0];
    let b = gemmkit::Bias::PerRow(&bias);
    let act = gemmkit::Activation::LeakyRelu(0.1f32);
    assert_copy_clone_debug(&b);
    assert_copy_clone_debug(&act);
    // The loop the derives exist for: both configs are built once and reused by value
    for _ in 0..2 {
        let (_b, _a) = (b, act);
    }

    #[cfg(feature = "int8")]
    {
        let scale = gemmkit::RequantScale::PerTensor(0.5);
        let req = gemmkit::Requantize {
            scale,
            zero_point: 0,
            bias: None,
        };
        assert_copy_clone_debug(&scale);
        assert_copy_clone_debug(&req);
        for _ in 0..2 {
            let _r = req;
        }
    }
}