speakrs 0.6.0

Fast Rust speaker diarization with pyannote-level accuracy and native CPU/CoreML/CUDA inference
Documentation
use ndarray::{Array2, Array3, array, s};

use super::{Conv1d, Conv1dSpec, Conv2d, Conv2dSpec, ConvWorkspace, TILE};

#[test]
fn one_dimensional_cross_correlation_and_scratch_reuse() {
    let spec = Conv1dSpec::new(2, 1, 2, 1).unwrap();
    let conv = Conv1d::new(spec, vec![1.0, 2.0, -1.0, 3.0], Some(vec![0.5])).unwrap();
    let input = array![[1.0, 2.0, 4.0, 7.0], [-2.0, 3.0, 1.0, 5.0]];
    let mut output = Array2::zeros((1, 3));
    let mut workspace = ConvWorkspace::new();
    conv.forward(input.view(), output.view_mut(), &mut workspace)
        .unwrap();
    assert_eq!(output, array![[16.5, 10.5, 32.5]]);
    let pointer = workspace.columns.as_ptr();
    conv.forward((-&input).view(), output.view_mut(), &mut workspace)
        .unwrap();
    assert_eq!(output, array![[-15.5, -9.5, -31.5]]);
    assert_eq!(workspace.columns.as_ptr(), pointer);
    assert_eq!(workspace.columns.ncols(), TILE);
}

#[test]
fn one_dimensional_stride_and_noncontiguous_views() {
    let conv = Conv1d::new(Conv1dSpec::new(1, 1, 2, 2).unwrap(), vec![2.0, -3.0], None).unwrap();
    let input = array![[1.0, 99.0, 2.0, 99.0, 4.0, 99.0, 7.0, 99.0]];
    let mut output = Array2::from_elem((1, 4), -100.0);
    conv.forward(
        input.slice(s![.., ..;2]),
        output.slice_mut(s![.., ..;2]),
        &mut ConvWorkspace::new(),
    )
    .unwrap();
    assert_eq!(output, array![[-4.0, -100.0, -13.0, -100.0]]);
}

#[test]
fn two_dimensional_non_symmetric_kernel_stride_and_padding() {
    let input =
        Array3::from_shape_vec((1, 3, 3), (1..=9).map(|value| value as f32).collect()).unwrap();
    let mut workspace = ConvWorkspace::new();
    let conv = Conv2d::new(
        Conv2dSpec::new(1, 1, 2, 1, 0).unwrap(),
        vec![1.0, 2.0, 3.0, 5.0],
        vec![-2.0],
    )
    .unwrap();
    let mut output = Array3::zeros((1, 2, 2));
    conv.forward(input.view(), output.view_mut(), &mut workspace)
        .unwrap();
    assert_eq!(output.as_slice().unwrap(), &[40.0, 51.0, 73.0, 84.0]);
    let conv = Conv2d::new(
        Conv2dSpec::new(1, 1, 2, 2, 1).unwrap(),
        vec![1.0, 2.0, 3.0, 5.0],
        vec![-2.0],
    )
    .unwrap();
    conv.forward(input.view(), output.view_mut(), &mut workspace)
        .unwrap();
    assert_eq!(output.as_slice().unwrap(), &[3.0, 19.0, 41.0, 84.0]);
}

#[test]
fn two_dimensional_strided_output_matches_contiguous_and_changed_input() {
    let conv = Conv2d::new(
        Conv2dSpec::new(1, 1, 3, 1, 1).unwrap(),
        vec![1.0, -1.0, 2.0, 3.0, 0.0, -2.0, -3.0, 4.0, 2.0],
        vec![0.25],
    )
    .unwrap();
    let input = Array3::from_shape_fn((1, 3, 5), |(_, y, x)| (y * 7 + x * 3) as f32);
    let mut expected = Array3::zeros(input.dim());
    let mut storage = Array3::from_elem((1, 6, 10), 99.0);
    let mut workspace = ConvWorkspace::new();
    conv.forward(input.view(), expected.view_mut(), &mut workspace)
        .unwrap();
    conv.forward(
        input.view(),
        storage.slice_mut(s![.., ..;2, ..;2]),
        &mut workspace,
    )
    .unwrap();
    assert_eq!(storage.slice(s![.., ..;2, ..;2]), expected);
    let changed = -&input;
    conv.forward(changed.view(), expected.view_mut(), &mut workspace)
        .unwrap();
    conv.forward(
        changed.slice(s![.., ..;-1, ..;-1]),
        storage.slice_mut(s![.., ..;2, ..;2]),
        &mut workspace,
    )
    .unwrap();
    let mut reversed = Array3::zeros(input.dim());
    conv.forward(
        changed.slice(s![.., ..;-1, ..;-1]),
        reversed.view_mut(),
        &mut workspace,
    )
    .unwrap();
    assert_eq!(storage.slice(s![.., ..;2, ..;2]), reversed);
    assert_ne!(expected[[0, 1, 2]], 0.25);
}

#[test]
fn shape_weight_and_overflow_failures_leave_output_unchanged() {
    assert!(Conv1dSpec::new(0, 1, 3, 1).is_err());
    assert!(Conv2dSpec::new(1, 1, usize::MAX, 1, 1).is_err());
    assert!(Conv2dSpec::new(1, 1, 3, 1, usize::MAX).is_err());
    let spec = Conv1dSpec::new(1, 2, 3, 1).unwrap();
    assert!(spec.output_len(2).is_err());
    assert!(Conv1d::new(spec, vec![1.0; 5], None).is_err());
    assert!(Conv1d::new(spec, vec![1.0; 6], Some(vec![0.0])).is_err());
    let conv = Conv1d::new(spec, vec![1.0; 6], None).unwrap();
    let mut output = Array2::from_elem((2, 2), 17.0);
    assert!(
        conv.forward(
            Array2::zeros((2, 4)).view(),
            output.view_mut(),
            &mut ConvWorkspace::new()
        )
        .is_err()
    );
    assert_eq!(output, Array2::from_elem((2, 2), 17.0));
    let spec = Conv2dSpec::new(1, 1, 3, 1, 1).unwrap();
    assert!(Conv2d::new(spec, vec![1.0; 9], vec![]).is_err());
    let conv = Conv2d::new(spec, vec![1.0; 9], vec![0.0]).unwrap();
    assert!(
        conv.forward(
            Array3::zeros((1, 3, 3)).view(),
            Array3::zeros((1, 2, 3)).view_mut(),
            &mut ConvWorkspace::new()
        )
        .is_err()
    );
}

#[test]
fn tile_boundaries_and_multiple_channels_match_direct_cross_correlation() {
    let input = Array3::from_shape_fn((2, 3, TILE + 7), |(c, y, x)| {
        ((c * 13 + y * 5 + x * 7) % 31) as f32 - 15.0
    });
    let weights: Vec<f32> = (0..36).map(|index| (index % 7) as f32 - 3.0).collect();
    let conv = Conv2d::new(
        Conv2dSpec::new(2, 2, 3, 1, 1).unwrap(),
        weights.clone(),
        vec![1.0, -2.0],
    )
    .unwrap();
    let mut output = Array3::zeros((2, 3, TILE + 7));
    conv.forward(input.view(), output.view_mut(), &mut ConvWorkspace::new())
        .unwrap();
    for channel in 0..2 {
        for y in 0..3 {
            for x in [0, 1, TILE - 1, TILE, TILE + 6] {
                let mut expected = if channel == 0 { 1.0 } else { -2.0 };
                for input_channel in 0..2 {
                    for ky in 0..3 {
                        for kx in 0..3 {
                            let sy = y as isize + ky as isize - 1;
                            let sx = x as isize + kx as isize - 1;
                            if (0..3).contains(&sy) && (0..(TILE + 7) as isize).contains(&sx) {
                                expected += weights
                                    [(channel * 2 + input_channel) * 9 + ky * 3 + kx]
                                    * input[[input_channel, sy as usize, sx as usize]];
                            }
                        }
                    }
                }
                assert_eq!(
                    output[[channel, y, x]],
                    expected,
                    "channel={channel} y={y} x={x}"
                );
            }
        }
    }
}