libmir-cuda 0.2.0

CUDA inference backend for libmir
use std::collections::HashMap;

use uuid::Uuid;

use super::{GenerationExecution, SpatialVisionPrefill};
use crate::{
    CudaBackend, CudaClampedRoutedModelSession, CudaClampedRoutedModelTemplate,
    CudaSharedRoutedModelSession, CudaSharedRoutedModelTemplate, Error, Result,
    engine::execution::{Output, generation_output},
};

pub(in crate::engine) struct MixedMixerExecution {
    template: CudaSharedRoutedModelTemplate,
    sessions: HashMap<Uuid, CudaSharedRoutedModelSession>,
}

pub(in crate::engine) struct SinkAttentionExecution {
    template: CudaClampedRoutedModelTemplate,
    sessions: HashMap<Uuid, CudaClampedRoutedModelSession>,
}

impl MixedMixerExecution {
    pub(in crate::engine) fn new(template: CudaSharedRoutedModelTemplate) -> Self {
        Self { template, sessions: HashMap::new() }
    }
}

impl SinkAttentionExecution {
    pub(in crate::engine) fn new(template: CudaClampedRoutedModelTemplate) -> Self {
        Self { template, sessions: HashMap::new() }
    }
}

impl GenerationExecution for MixedMixerExecution {
    fn prefill_chunk_len(&self, remaining: usize) -> usize {
        remaining
    }

    fn prefill_chunk(
        &mut self,
        backend: &CudaBackend,
        request: &runtime::backend::PrefillRequest,
        tokens: &[u32],
        offset: usize,
        table: &runtime::kv::BlockTable,
        final_chunk: bool,
    ) -> Result<Option<Output>> {
        if offset == 0 {
            self.sessions.insert(request.session_id, self.template.instantiate()?);
        }
        let session = required(&mut self.sessions, request.session_id)?;
        session.prefill(request.session_id, tokens, table)?;
        if final_chunk {
            generation_output(backend, session, request.sampling_logits).map(Some)
        } else {
            Ok(None)
        }
    }

    fn decode(
        &mut self,
        backend: &CudaBackend,
        request: &runtime::backend::DecodeRequest,
        _use_device_token: bool,
    ) -> Result<Output> {
        let session = required(&mut self.sessions, request.session_id)?;
        session.decode(request.session_id, request.token_id, &request.block_table)?;
        generation_output(backend, session, request.sampling_logits)
    }

    fn clear_sessions(&mut self) {
        self.sessions.clear();
    }

    fn release_session(&mut self, session: Uuid) {
        self.sessions.remove(&session);
    }

    fn prefill_spatial_vision(
        &mut self,
        backend: &CudaBackend,
        input: SpatialVisionPrefill<'_>,
    ) -> Result<Output> {
        let mut session = self.template.instantiate()?;
        session.prefill_vision(
            input.session,
            input.tokens,
            input.positions,
            input.table,
            input.image_span,
            input.image,
            input.position_delta,
        )?;
        let output = generation_output(backend, &mut session, input.sampling)?;
        self.sessions.insert(input.session, session);
        Ok(output)
    }
}

impl GenerationExecution for SinkAttentionExecution {
    fn prefill_chunk_len(&self, remaining: usize) -> usize {
        remaining.min(256)
    }

    fn prefill_chunk(
        &mut self,
        backend: &CudaBackend,
        request: &runtime::backend::PrefillRequest,
        tokens: &[u32],
        offset: usize,
        table: &runtime::kv::BlockTable,
        final_chunk: bool,
    ) -> Result<Option<Output>> {
        if offset == 0 {
            self.sessions.insert(request.session_id, self.template.instantiate()?);
        }
        let session = required(&mut self.sessions, request.session_id)?;
        session.prefill(request.session_id, tokens, table)?;
        if final_chunk {
            generation_output(backend, session, request.sampling_logits).map(Some)
        } else {
            Ok(None)
        }
    }

    fn decode(
        &mut self,
        backend: &CudaBackend,
        request: &runtime::backend::DecodeRequest,
        _use_device_token: bool,
    ) -> Result<Output> {
        let session = required(&mut self.sessions, request.session_id)?;
        session.decode(request.session_id, request.token_id, &request.block_table)?;
        generation_output(backend, session, request.sampling_logits)
    }

    fn clear_sessions(&mut self) {
        self.sessions.clear();
    }

    fn release_session(&mut self, session: Uuid) {
        self.sessions.remove(&session);
    }
}

fn required<T>(sessions: &mut HashMap<Uuid, T>, id: Uuid) -> Result<&mut T> {
    sessions
        .get_mut(&id)
        .ok_or_else(|| Error::State("CUDA decoder session is not initialized".into()))
}