native_neural_network_std 0.2.1

Ergonomic std wrapper for the `native_neural_network` crate (no_std) — std-friendly re-exports and utilities.
Documentation
use native_neural_network_std::std::activations_std::ActivationKind;
use native_neural_network_std::std::layers_std::{DenseLayerDesc, LayerSpec};
use native_neural_network_std::std::model_format_std::{
    decode_model_v1, encode_model_v1, encoded_size_v1, DecodedCounts,
};
use native_neural_network_std::std::model_std::ModelStd;
use std::sync::Arc;
use std::thread;

fn lcg(seed: u32) -> impl Iterator<Item = f32> {
    let mut s = seed as u64;
    std::iter::from_fn(move || {
        s = (s.wrapping_mul(1664525).wrapping_add(1013904223)) & 0xffff_ffff;
        Some(((s as u32) as f32) / (u32::MAX as f32))
    })
}

fn make_bytes_for_simple_model() -> Vec<u8> {
    let layers = vec![LayerSpec::Dense(DenseLayerDesc {
        input_size: 2,
        output_size: 1,
        weight_offset: 0,
        bias_offset: 0,
        activation: ActivationKind::Identity,
    })];
    let weights = vec![1.0f32, 2.0f32];
    let biases = vec![0.5f32];
    let size = encoded_size_v1(layers.len(), weights.len(), biases.len()).expect("encoded size");
    let mut buf = vec![0u8; size];
    let used = encode_model_v1(&layers, &weights, &biases, &mut buf).expect("encode");
    buf.truncate(used);
    buf
}

#[test]
fn cpu_backend_abstraction_real() {
    let previous = native_neural_network_std::std::engine_std::get_compute_backend();
    native_neural_network_std::std::engine_std::set_compute_backend(
        native_neural_network_std::std::engine_std::ComputeBackend::Cpu,
    );
    assert_eq!(
        native_neural_network_std::std::engine_std::get_compute_backend(),
        native_neural_network_std::std::engine_std::ComputeBackend::Cpu
    );
    native_neural_network_std::std::engine_std::set_compute_backend(previous);
}

#[test]
fn load_and_run_minimal_model() {
    let bytes = make_bytes_for_simple_model();
    let model = ModelStd::from_bytes(&bytes).expect("from_bytes");
    let out = model.run_single(&[1.0f32, 1.0f32]).expect("run_single");
    assert_eq!(out.len(), 1);
    assert!(
        (out[0] - 3.5f32).abs() < 1e-6,
        "expected 3.5, got {}",
        out[0]
    );
    let mut layers_out = vec![
        LayerSpec::Dense(DenseLayerDesc {
            input_size: 0,
            output_size: 0,
            weight_offset: 0,
            bias_offset: 0,
            activation: ActivationKind::Identity
        });
        1
    ];
    let mut weights_out = vec![0f32; 2];
    let mut biases_out = vec![0f32; 1];
    let counts: DecodedCounts =
        decode_model_v1(&bytes, &mut layers_out, &mut weights_out, &mut biases_out)
            .expect("decode");
    assert_eq!(counts.layers, 1);
    assert_eq!(weights_out, vec![1.0f32, 2.0f32]);
    assert_eq!(biases_out, vec![0.5f32]);
}

#[test]
fn decode_truncated_bytes_returns_err_in_load_model() {
    let mut bytes = make_bytes_for_simple_model();
    if bytes.len() > 0 {
        bytes.truncate(bytes.len() - 1);
    }
    let mut layers_out = vec![
        LayerSpec::Dense(DenseLayerDesc {
            input_size: 0,
            output_size: 0,
            weight_offset: 0,
            bias_offset: 0,
            activation: ActivationKind::Identity
        });
        1
    ];
    let mut weights_out = vec![0f32; 2];
    let mut biases_out = vec![0f32; 1];
    assert!(decode_model_v1(&bytes, &mut layers_out, &mut weights_out, &mut biases_out).is_err());
}

#[test]
fn repeated_runs_are_deterministic_and_batch_like() {
    let bytes = make_bytes_for_simple_model();
    let model = ModelStd::from_bytes(&bytes).expect("from_bytes");
    let input = [1.0f32, 1.0f32];
    let first = model.run_single(&input).expect("run_single");
    for _ in 0..20 {
        let cur = model.run_single(&input).expect("run_single");
        assert_eq!(cur, first);
    }
}

#[test]
fn heavy_concurrent_loads_and_runs() {
    let bytes = Arc::new(make_bytes_for_simple_model());
    let mut handles = Vec::new();
    for _ in 0..8 {
        let b = Arc::clone(&bytes);
        handles.push(thread::spawn(move || {
            for _ in 0..100 {
                let m = ModelStd::from_bytes(&b).expect("from_bytes");
                let out = m.run_single(&[2.0f32, 3.0f32]).expect("inference");
                assert!(
                    (out[0] - 8.5f32).abs() < 1e-6,
                    "unexpected output {}",
                    out[0]
                );
            }
        }));
    }
    for h in handles {
        h.join().unwrap();
    }
}

#[test]
fn multi_layer_serialization_roundtrip_and_infer() {
    let layers = vec![
        LayerSpec::Dense(DenseLayerDesc {
            input_size: 2,
            output_size: 3,
            weight_offset: 0,
            bias_offset: 0,
            activation: ActivationKind::Identity,
        }),
        LayerSpec::Dense(DenseLayerDesc {
            input_size: 3,
            output_size: 1,
            weight_offset: 0,
            bias_offset: 0,
            activation: ActivationKind::Identity,
        }),
    ];
    let weights = vec![1.0f32, 0.0, 0.0, 1.0, 0.5, 0.5, 1.0, 1.0, 1.0];
    let biases = vec![0.0f32, 0.0, 0.0, 0.5f32];

    let size = encoded_size_v1(layers.len(), weights.len(), biases.len()).expect("encoded size");
    let mut buf = vec![0u8; size];
    let used = encode_model_v1(&layers, &weights, &biases, &mut buf).expect("encode");
    buf.truncate(used);

    let model = ModelStd::from_bytes(&buf).expect("from_bytes");
    let out = model.run_single(&[1.0f32, 2.0f32]).expect("run");

    let mut layers_out = vec![
        LayerSpec::Dense(DenseLayerDesc {
            input_size: 0,
            output_size: 0,
            weight_offset: 0,
            bias_offset: 0,
            activation: ActivationKind::Identity
        });
        layers.len()
    ];
    let mut weights_out = vec![0f32; weights.len()];
    let mut biases_out = vec![0f32; biases.len()];
    let counts =
        decode_model_v1(&buf, &mut layers_out, &mut weights_out, &mut biases_out).expect("decode");
    assert_eq!(counts.layers, layers.len());
    assert_eq!(weights_out, weights);
    assert_eq!(biases_out, biases);

    let infer_scratch_len =
        native_neural_network_std::std::rnn_std::rnn_required_infer_scratch_from_specs_std(
            &layers_out,
        )
        .unwrap_or(0);
    let reconstructed = ModelStd {
        layers: layers_out.clone(),
        weights: weights_out.clone(),
        biases: biases_out.clone(),
        input_size: 2,
        output_size: 1,
        infer_scratch_len,
    };
    let out2 = reconstructed.run_single(&[1.0f32, 2.0f32]).expect("run2");
    assert_eq!(out, out2);
}

#[test]
fn randomized_inputs_produce_stable_results() {
    let bytes = make_bytes_for_simple_model();
    let model = ModelStd::from_bytes(&bytes).expect("from_bytes");
    let mut rng = lcg(0xdead_beef);
    let mut samples = Vec::new();
    for _ in 0..50 {
        let a = (rng.next().unwrap() * 10.0) - 5.0;
        let b = (rng.next().unwrap() * 10.0) - 5.0;
        samples.push([a, b]);
    }
    let first: Vec<Vec<f32>> = samples
        .iter()
        .map(|s| model.run_single(s).unwrap())
        .collect();
    let second: Vec<Vec<f32>> = samples
        .iter()
        .map(|s| model.run_single(s).unwrap())
        .collect();
    assert_eq!(first, second);
}

#[test]
fn decode_into_too_small_buffers_returns_err() {
    let bytes = make_bytes_for_simple_model();
    let mut layers_out = vec![
        LayerSpec::Dense(DenseLayerDesc {
            input_size: 0,
            output_size: 0,
            weight_offset: 0,
            bias_offset: 0,
            activation: ActivationKind::Identity
        });
        1
    ];
    let mut weights_out = vec![0f32; 1];
    let mut biases_out = vec![0f32; 0];
    assert!(decode_model_v1(&bytes, &mut layers_out, &mut weights_out, &mut biases_out).is_err());
}

#[test]
fn concurrent_loads_no_conflict() {
    let bytes = Arc::new(make_bytes_for_simple_model());
    let mut handles = Vec::new();
    for _ in 0..4 {
        let b = Arc::clone(&bytes);
        handles.push(thread::spawn(move || {
            let m = ModelStd::from_bytes(&b).expect("from_bytes");
            m.run_single(&[2.0f32, 3.0f32]).expect("inference");
        }));
    }
    for h in handles {
        h.join().unwrap();
    }
}