libmir-cuda 0.2.0

CUDA inference backend for libmir
use models::{
    layout::{DecoderConfig, EncoderConfig},
    semantic::MixerSpec,
};
use runtime::trace::{TraceDecoder, TraceWeights};

use super::{LoadedModel, mlp_layout};

pub(super) fn attention_counts(model: &LoadedModel) -> (usize, usize) {
    if let Some(semantic) = model.semantic() {
        return semantic
            .decoder
            .layers
            .iter()
            .fold((0, 0), |(full, sliding), layer| match &layer.mixer {
                MixerSpec::SoftmaxAttention(attention) if attention.window.is_some() => {
                    (full, sliding + 1)
                },
                MixerSpec::SoftmaxAttention(_) => (full + 1, sliding),
                MixerSpec::LinearAttention(_) => (full, sliding),
            });
    }
    (model.encoder.as_ref().map_or(0, |encoder| encoder.num_hidden_layers), 0)
}

pub(super) fn execution(
    decoder: Option<&DecoderConfig>,
    encoder: Option<&EncoderConfig>,
    full: usize,
    sliding: usize,
) -> TraceDecoder {
    if let Some(decoder) = decoder {
        return decoder_trace(decoder, full, sliding);
    }
    encoder.map_or_else(empty_execution, encoder_trace)
}

pub(super) fn weights(model: &LoadedModel) -> TraceWeights {
    if let Some(encoder) = model.encoder.as_ref() {
        return TraceWeights {
            token_embeddings: "new.embeddings.word_embeddings".into(),
            final_norm: "post-attention and post-MLP LayerNorm".into(),
            output_head: "pooler tanh and scalar classifier".into(),
            output_tied: false,
            layer_count: encoder.num_hidden_layers,
            attention_layout: "packed QKV with bidirectional F16 attention".into(),
            mlp_layout: mlp_layout(model).into(),
            linear_bias_count: encoder.num_hidden_layers.saturating_mul(4).saturating_add(2),
        };
    }
    let Some(decoder) = model.decoder.as_ref() else {
        return empty_weights();
    };
    TraceWeights {
        token_embeddings: "config-discovered BF16 embedding".into(),
        final_norm: "config-discovered BF16 RMSNorm".into(),
        output_head: "tied or independent checkpoint projection".into(),
        output_tied: decoder.tie_word_embeddings,
        layer_count: decoder.num_hidden_layers,
        attention_layout: "fused QKV with causal paged GQA".into(),
        mlp_layout: mlp_layout(model).into(),
        linear_bias_count: 0,
    }
}

fn encoder_trace(encoder: &EncoderConfig) -> TraceDecoder {
    TraceDecoder {
        layers: encoder.num_hidden_layers,
        hidden_size: encoder.hidden_size,
        intermediate_size: encoder.intermediate_size,
        vocab_size: encoder.vocab_size,
        attention_heads: encoder.num_attention_heads,
        kv_heads: encoder.num_attention_heads,
        head_dim: encoder.head_dim,
        global_head_dim: None,
        global_kv_heads: None,
        full_attention_layers: encoder.num_hidden_layers,
        sliding_attention_layers: 0,
        max_position_embeddings: encoder.max_position_embeddings,
        rope_theta: encoder.rope_theta,
        full_attention_rope_theta: None,
        sliding_attention_rope_theta: None,
        sliding_window: None,
        num_experts: None,
        top_k_experts: None,
        moe_intermediate_size: None,
        hidden_activation: Some(encoder.hidden_activation.clone()),
        final_logit_softcapping: None,
    }
}

fn decoder_trace(decoder: &DecoderConfig, full: usize, sliding: usize) -> TraceDecoder {
    TraceDecoder {
        layers: decoder.num_hidden_layers,
        hidden_size: decoder.hidden_size,
        intermediate_size: decoder.intermediate_size,
        vocab_size: decoder.vocab_size,
        attention_heads: decoder.num_attention_heads,
        kv_heads: decoder.num_key_value_heads,
        head_dim: decoder.head_dim,
        global_head_dim: decoder.global_head_dim,
        global_kv_heads: decoder.num_global_key_value_heads,
        full_attention_layers: full,
        sliding_attention_layers: sliding,
        max_position_embeddings: decoder.max_position_embeddings,
        rope_theta: decoder.rope_theta,
        full_attention_rope_theta: decoder.full_attention_rope_theta,
        sliding_attention_rope_theta: decoder.sliding_attention_rope_theta,
        sliding_window: decoder.sliding_window,
        num_experts: decoder.num_experts,
        top_k_experts: decoder.top_k_experts,
        moe_intermediate_size: decoder.moe_intermediate_size,
        hidden_activation: decoder.hidden_activation.clone(),
        final_logit_softcapping: decoder.final_logit_softcapping,
    }
}

fn empty_execution() -> TraceDecoder {
    TraceDecoder {
        layers: 0,
        hidden_size: 0,
        intermediate_size: 0,
        vocab_size: 0,
        attention_heads: 0,
        kv_heads: 0,
        head_dim: 0,
        global_head_dim: None,
        global_kv_heads: None,
        full_attention_layers: 0,
        sliding_attention_layers: 0,
        max_position_embeddings: 0,
        rope_theta: None,
        full_attention_rope_theta: None,
        sliding_attention_rope_theta: None,
        sliding_window: None,
        num_experts: None,
        top_k_experts: None,
        moe_intermediate_size: None,
        hidden_activation: None,
        final_logit_softcapping: None,
    }
}

fn empty_weights() -> TraceWeights {
    TraceWeights {
        token_embeddings: "unknown".into(),
        final_norm: "unknown".into(),
        output_head: "unknown".into(),
        output_tied: false,
        layer_count: 0,
        attention_layout: "unknown".into(),
        mlp_layout: "unknown".into(),
        linear_bias_count: 0,
    }
}