libmir-cuda 0.2.0

CUDA inference backend for libmir
use mircuda::{
    CompileOptions, Compiler, DeviceBuffer, LaunchConfig, Stream, TypedKernel, bf16, cuda_export,
    cuda_kernel_file,
};

use super::super::geometry::{narrow, product, require};
use crate::{CudaTensor, Error, Result};

cuda_export!(
    ClipBf16Kernel = "libmir_cuda_vision_clip_bounds_bf16"(
        input: &DeviceBuffer<bf16>, minimum: &DeviceBuffer<bf16>,
        maximum: &DeviceBuffer<bf16>, output: &mut DeviceBuffer<bf16>,
        elements: u32, columns: u32, bounds: u32,
    )
);
cuda_export!(
    ClipF32Kernel = "libmir_cuda_vision_clip_bounds_f32"(
        input: &DeviceBuffer<bf16>, minimum: &DeviceBuffer<f32>,
        maximum: &DeviceBuffer<f32>, output: &mut DeviceBuffer<bf16>,
        elements: u32, columns: u32, bounds: u32,
    )
);

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct VisionClipSpec {
    pub rows: usize,
    pub columns: usize,
}

#[derive(Clone, Debug)]
pub struct VisionClip {
    bf16: TypedKernel<ClipBf16Kernel>,
    f32: TypedKernel<ClipF32Kernel>,
    spec: VisionClipSpec,
}

impl VisionClip {
    pub fn compile(compiler: &Compiler, spec: VisionClipSpec) -> Result<Self> {
        if spec.rows == 0 || spec.columns == 0 {
            return Err(Error::InvalidVisionKernel("invalid clipping geometry"));
        }
        let module = compiler.compile(
            cuda_kernel_file!("../../../kernels/vision_clip_bf16.cu"),
            &CompileOptions::default(),
        )?;
        Ok(Self {
            bf16: module.kernel()?,
            f32: module.kernel()?,
            spec,
        })
    }

    pub fn execute(
        &self,
        stream: &Stream,
        input: &DeviceBuffer<bf16>,
        minimum: &CudaTensor,
        maximum: &CudaTensor,
        output: &mut DeviceBuffer<bf16>,
    ) -> Result<()> {
        let elements = product(self.spec.rows, self.spec.columns)?;
        require("vision clip input", elements, input.len())?;
        require("vision clip output", elements, output.len())?;
        if minimum.shape() != maximum.shape() {
            return Err(Error::InvalidVisionKernel("clipping bound shapes differ"));
        }
        let bounds = minimum.shape().iter().try_fold(1_usize, |total, value| {
            total
                .checked_mul(*value)
                .ok_or(Error::InvalidVisionKernel("clip size overflow"))
        })?;
        if bounds != 1 && bounds != self.spec.columns {
            return Err(Error::InvalidVisionKernel("clipping bounds must be scalar or per-column"));
        }
        let arguments = (narrow(elements)?, narrow(self.spec.columns)?, narrow(bounds)?);
        let launch = launch(elements)?;
        match (minimum.as_bf16(), maximum.as_bf16()) {
            (Some(minimum), Some(maximum)) => Ok(self.bf16.launch(
                stream,
                launch,
                (input, minimum, maximum, output, arguments.0, arguments.1, arguments.2),
            )?),
            _ => match (minimum.as_f32(), maximum.as_f32()) {
                (Some(minimum), Some(maximum)) => Ok(self.f32.launch(
                    stream,
                    launch,
                    (input, minimum, maximum, output, arguments.0, arguments.1, arguments.2),
                )?),
                _ => Err(Error::DTypeMismatch {
                    name: minimum.name().into(),
                    expected: "matching BF16 or F32 clipping bounds",
                }),
            },
        }
    }
}

fn launch(elements: usize) -> Result<LaunchConfig> {
    Ok(LaunchConfig {
        grid: (narrow(elements.div_ceil(256))?, 1, 1),
        block: (256, 1, 1),
        shared_memory_bytes: 0,
    })
}