libmir-cuda 0.2.0

CUDA inference backend for libmir
use super::{ClampedRoutedConfig, weights::ClampedRoutedExpertWeights};
use crate::{CudaTensor, CudaTensorDType, Error, Result};

pub(super) fn validate_common(
    config: ClampedRoutedConfig,
    tensors: [&CudaTensor; 8],
) -> Result<()> {
    let q = config.query_heads * config.head_dim;
    let kv = config.kv_heads * config.head_dim;
    let shapes: [&[usize]; 8] = [
        &[config.hidden],
        &[q],
        &[kv],
        &[kv],
        &[config.hidden],
        &[config.query_heads],
        &[config.hidden],
        &[config.experts],
    ];
    for (tensor, shape) in tensors.into_iter().zip(shapes) {
        validate(tensor, shape, CudaTensorDType::Bf16)?;
    }
    Ok(())
}

pub(super) fn validate_native_experts(
    config: ClampedRoutedConfig,
    weights: &ClampedRoutedExpertWeights,
) -> Result<()> {
    let ClampedRoutedExpertWeights::Native(weights) = weights else {
        return Err(Error::InvalidExecutionPlan("expected native clamped-routed experts"));
    };
    let gate_rows = 2 * config.intermediate;
    validate(
        &weights.gate_up_blocks,
        &[config.experts, gate_rows, config.hidden / 32, 16],
        CudaTensorDType::U8,
    )?;
    validate(
        &weights.gate_up_scales,
        &[config.experts, gate_rows, config.hidden / 32],
        CudaTensorDType::U8,
    )?;
    validate(&weights.gate_up_bias, &[config.experts, gate_rows], CudaTensorDType::Bf16)?;
    validate(
        &weights.down_blocks,
        &[config.experts, config.hidden, config.intermediate / 32, 16],
        CudaTensorDType::U8,
    )?;
    validate(
        &weights.down_scales,
        &[config.experts, config.hidden, config.intermediate / 32],
        CudaTensorDType::U8,
    )?;
    validate(&weights.down_bias, &[config.experts, config.hidden], CudaTensorDType::Bf16)
}

pub(super) fn validate_mlx_experts(
    config: ClampedRoutedConfig,
    weights: &ClampedRoutedExpertWeights,
) -> Result<()> {
    let ClampedRoutedExpertWeights::Mlx(weights) = weights else {
        return Err(Error::InvalidExecutionPlan("expected MLX clamped-routed experts"));
    };
    let gate_shape = [config.experts, config.intermediate, config.hidden / 8];
    let gate_scale_shape = [config.experts, config.intermediate, config.hidden / 32];
    let gate_bias_shape = [config.experts, config.intermediate];
    for tensor in [&weights.gate_blocks, &weights.up_blocks] {
        validate(tensor, &gate_shape, CudaTensorDType::U32)?;
    }
    for tensor in [&weights.gate_scales, &weights.up_scales] {
        validate(tensor, &gate_scale_shape, CudaTensorDType::U8)?;
    }
    for tensor in [&weights.gate_bias, &weights.up_bias] {
        validate(tensor, &gate_bias_shape, CudaTensorDType::Bf16)?;
    }
    validate(
        &weights.down_blocks,
        &[config.experts, config.hidden, config.intermediate / 8],
        CudaTensorDType::U32,
    )?;
    validate(
        &weights.down_scales,
        &[config.experts, config.hidden, config.intermediate / 32],
        CudaTensorDType::U8,
    )?;
    validate(&weights.down_bias, &[config.experts, config.hidden], CudaTensorDType::Bf16)
}

fn validate(tensor: &CudaTensor, shape: &[usize], dtype: CudaTensorDType) -> Result<()> {
    if tensor.shape() != shape {
        return Err(Error::InvalidQuantizedTensor {
            name: tensor.name().into(),
            expected: shape.to_vec(),
            actual: tensor.shape().to_vec(),
        });
    }
    if tensor.dtype() != dtype {
        return Err(Error::DTypeMismatch {
            name: tensor.name().into(),
            expected: match dtype {
                CudaTensorDType::Bf16 => "BF16",
                CudaTensorDType::U32 => "U32",
                _ => "U8",
            },
        });
    }
    Ok(())
}