libmir-cuda 0.2.0

CUDA inference backend for libmir
use async_trait::async_trait;
use foundation::model::ModelManifest;
use runtime::{
    Result as RuntimeResult, RuntimeError,
    backend::{
        Backend, BackendCapability, BackendInfo, DecodeBatchOutput, DecodeBatchRequest,
        DecodeOutput, DecodeRequest, EmbeddingOutput, EmbeddingRequest, GenerationRequest,
        ModelHandle, PrefillOutput, PrefillRequest, SequenceScoringOutput, SequenceScoringRequest,
        TokenEvent,
    },
    trace::ModelTrace,
};

use super::CudaEngine;

#[async_trait]
impl Backend for CudaEngine {
    fn info(&self) -> BackendInfo {
        BackendInfo {
            name: "cuda-native".into(),
            device: self.backend.device_info().name.clone(),
            capabilities: vec![
                BackendCapability::Prefill,
                BackendCapability::Decode,
                BackendCapability::Streaming,
                BackendCapability::Vision,
                BackendCapability::PrefixCache,
                BackendCapability::GraphCapture,
                BackendCapability::ContinuousBatching,
                BackendCapability::Embeddings,
                BackendCapability::SequenceScoring,
                BackendCapability::Quantization("ModelOpt NVFP4".into()),
                BackendCapability::Quantization("affine Int4/Int8".into()),
            ],
        }
    }

    async fn load_model(&self, manifest: &ModelManifest) -> RuntimeResult<ModelHandle> {
        let mut ignored = |_event| {};
        Ok(self.load_model_with_progress(manifest, &mut ignored)?)
    }

    async fn model_trace(&self, model: &ModelHandle) -> RuntimeResult<ModelTrace> {
        let loaded = self.model(&model.id)?;
        Ok(super::trace::build(loaded.as_ref(), self.info(), self.cache)?)
    }

    async fn prefill(&self, request: PrefillRequest) -> RuntimeResult<PrefillOutput> {
        let mut ignored = |_event| {};
        Ok(self.prefill_with_progress(&request, &mut ignored)?)
    }

    async fn decode(&self, request: DecodeRequest) -> RuntimeResult<DecodeOutput> {
        Ok(self.decode_token(&request)?)
    }

    async fn decode_batch(&self, request: DecodeBatchRequest) -> RuntimeResult<DecodeBatchOutput> {
        Ok(self.decode_batch_tokens(&request)?)
    }

    async fn embed(&self, request: EmbeddingRequest) -> RuntimeResult<EmbeddingOutput> {
        let prompt_tokens = request.inputs.iter().map(Vec::len).sum();
        Ok(EmbeddingOutput {
            embeddings: self.embed_tokens(&request.model, &request.inputs, request.dimensions)?,
            prompt_tokens,
        })
    }

    async fn score_sequences(
        &self,
        request: SequenceScoringRequest,
    ) -> RuntimeResult<SequenceScoringOutput> {
        let prompt_tokens = request.pairs.iter().map(Vec::len).sum();
        Ok(SequenceScoringOutput {
            scores: self.score_tokens(&request.model, &request.pairs)?,
            prompt_tokens,
        })
    }

    async fn generate(
        &self,
        model: &ModelHandle,
        _request: GenerationRequest,
    ) -> RuntimeResult<Vec<TokenEvent>> {
        Err(RuntimeError::BackendUnavailable(format!(
            "use prefill/decode streaming for CUDA model {}",
            model.id
        )))
    }
}