libmir-metal 0.1.0

Metal inference backend for libmir
use runtime::backend::{CandidateLogitsTrace, LogitsTrace, SamplingLogits};

use super::{
    error::Result,
    model::{LoadedModel, NativeOutput},
};
use crate::engine::{DeviceSampling, sample_u32};
#[derive(Debug)]
pub(super) struct SamplingOutput {
    pub next_token: Option<u32>,
    pub logits: Option<LogitsTrace>,
    pub candidates: Option<CandidateLogitsTrace>,
}

pub(super) fn materialize(
    model: &LoadedModel,
    output: NativeOutput,
    sampling: SamplingLogits,
) -> Result<SamplingOutput> {
    match output {
        NativeOutput::Greedy(next_token) => Ok(SamplingOutput {
            next_token: Some(next_token),
            logits: None,
            candidates: None,
        }),
        NativeOutput::Logits(logits) if sampling == SamplingLogits::None => Ok(SamplingOutput {
            next_token: Some(logits.argmax_u32(model.stream())?),
            logits: None,
            candidates: None,
        }),
        NativeOutput::Logits(logits) if let SamplingLogits::TopK { k, vocab_size } = sampling => {
            top_k(model, &logits, k, vocab_size)
        },
        NativeOutput::Logits(logits)
            if let SamplingLogits::SampleTopK { k, vocab_size, temperature, draw } = sampling =>
        {
            sampled(model, &logits, vocab_size, k, 1.0, temperature, draw)
        },
        NativeOutput::Logits(logits)
            if let SamplingLogits::Sample {
                vocab_size,
                temperature,
                top_p,
                top_k,
                draw,
            } = sampling =>
        {
            sampled(model, &logits, vocab_size, top_k, top_p, temperature, draw)
        },
        NativeOutput::Logits(logits) => {
            let shape = logits.shape()?;
            let values = logits.to_vec_f32_on_stream(model.stream())?;
            Ok(SamplingOutput {
                next_token: None,
                logits: Some(LogitsTrace { shape, values }),
                candidates: None,
            })
        },
    }
}

fn sampled(
    model: &LoadedModel,
    logits: &crate::engine::Array,
    vocab_size: usize,
    top_k: usize,
    top_p: f32,
    temperature: f32,
    draw: f32,
) -> Result<SamplingOutput> {
    let next_token = sample_u32(
        logits,
        DeviceSampling {
            vocab_size,
            top_k,
            top_p,
            temperature,
            draw,
        },
        model.stream(),
    )?;
    Ok(SamplingOutput {
        next_token: Some(next_token),
        logits: None,
        candidates: None,
    })
}

fn top_k(
    model: &LoadedModel,
    logits: &crate::engine::Array,
    k: usize,
    vocab_size: usize,
) -> Result<SamplingOutput> {
    let candidates = logits.top_k(k, vocab_size, model.stream())?;
    Ok(SamplingOutput {
        next_token: None,
        logits: None,
        candidates: Some(CandidateLogitsTrace {
            token_ids: candidates.token_ids.to_vec_u32_on_stream(model.stream())?,
            scores: candidates.scores.to_vec_f32_on_stream(model.stream())?,
        }),
    })
}