llama-rs 0.16.1

A high-performance Rust implementation of llama.cpp - LLM inference engine with full GGUF support
Documentation
//! Data types that make up a [`DiagnosticReport`].
//!
//! The same types are consumed by both the text and JSON renderers
//! (`render.rs`). All fields are `pub` so callers building their own
//! diagnostics can construct reports directly.

use serde::{Deserialize, Serialize};

use crate::diagnostics::shape::ShapeExpr;
use crate::diagnostics::types::{MetadataValue, TensorDtype};

pub const SCHEMA_VERSION: u32 = 1;

/// Terminal verdict for a diagnostic run.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Verdict {
    /// Every expected entry present with matching shape and required
    /// metadata. The loader should accept this model.
    Matches,
    /// Only optional-missing or optional-unexpected entries. Loader is
    /// expected to work.
    OptionalExtras,
    /// One or more required-missing or shape-mismatched entries.
    /// Loader will reject this model as-is.
    ProfileMismatch,
    /// No profile was selected (unknown declared architecture, no
    /// `--against`). The report still dumps the inventory.
    UnknownArchitecture,
}

impl Verdict {
    /// Process exit code associated with this verdict.
    pub fn exit_code(self) -> i32 {
        match self {
            Verdict::Matches | Verdict::OptionalExtras => 0,
            Verdict::ProfileMismatch => 2,
            Verdict::UnknownArchitecture => 3,
        }
    }
}

/// Identity of the profile used for comparison (if any).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "source", rename_all = "snake_case")]
pub enum ProfileRef {
    /// A built-in profile by name.
    Builtin { name: String, extends: Option<String> },
    /// A user-supplied profile file.
    File { path: String, name: String },
    /// Pairwise-diff mode against another model file.
    Pairwise { against: String },
    /// No profile was selected.
    None,
}

/// One resolved symbol with provenance.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResolvedSymbol {
    pub name: String,
    pub value: u64,
    pub source: String,
}

/// How the tensor's name pattern was matched against the inventory.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TensorPattern {
    pub name: String,
    /// If set, the pattern was expanded over `0..per_layer_count`
    /// layer indices.
    pub per_layer_count: Option<u32>,
    /// For per-layer patterns, the specific layer indices that
    /// contributed to this entry. Collapsed into `{min..max}` ranges
    /// by the text renderer.
    #[serde(default)]
    pub layers: Vec<u32>,
}

/// An expected tensor that is missing from the inventory.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MissingTensor {
    pub pattern: TensorPattern,
    /// Expected shape, as originally written in the profile. May
    /// contain symbols; the renderer substitutes them where possible.
    pub expected_shape: Option<Vec<ShapeExpr>>,
    /// Whether this entry was from the `optional_tensors` list.
    pub optional: bool,
}

/// A tensor present in the inventory that the profile didn't list.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct UnexpectedTensor {
    pub pattern: TensorPattern,
    pub shape: Vec<usize>,
    pub dtype: TensorDtype,
}

/// A tensor present in both, but with a shape disagreement.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ShapeMismatch {
    pub pattern: TensorPattern,
    pub expected_shape: Vec<ShapeExpr>,
    pub actual_shape: Vec<usize>,
    /// Per-dimension resolved values, when known.
    pub resolved_expected: Vec<Option<u64>>,
}

/// A tensor we wanted to check but couldn't because a required symbol
/// didn't resolve. Reported so the user knows the comparison was
/// skipped rather than silently passed.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ShapeComparisonSkipped {
    pub pattern: TensorPattern,
    pub reason: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MetadataDelta {
    pub key: String,
    pub value: Option<MetadataValue>,
}

/// Hypothesis fired by a profile hint.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Hypothesis {
    pub id: u32,
    pub name: Option<String>,
    pub triggered_by: HypothesisTriggers,
    pub message: String,
}

#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct HypothesisTriggers {
    #[serde(default)]
    pub missing: Vec<String>,
    #[serde(default)]
    pub unexpected: Vec<String>,
}

/// Non-fatal note surfaced during comparison (e.g. a symbol couldn't
/// be resolved, a config.json was unreadable).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Warning {
    pub code: String,
    pub message: String,
}

#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct MetadataDeltas {
    #[serde(default)]
    pub unexpected: Vec<MetadataDelta>,
    #[serde(default)]
    pub missing_required: Vec<MetadataDelta>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FormatInfo {
    pub kind: String, // "gguf" | "safetensors"
    pub label: String,
    pub tensor_count: u32,
    pub metadata_count: u32,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ArchitectureInfo {
    pub declared: Option<String>,
    /// Where the architecture identifier came from: one of
    /// `"general.architecture"`, `"model_type"`, or `"none"`.
    pub source: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Summary {
    pub required_missing: u32,
    pub optional_missing: u32,
    pub unexpected_patterns: u32,
    pub shape_mismatches: u32,
    pub hypotheses: u32,
    pub warnings: u32,
}

/// Full output of one `compare()` invocation.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DiagnosticReport {
    pub schema_version: u32,
    pub file: String,
    pub format: FormatInfo,
    pub architecture: ArchitectureInfo,
    pub profile: ProfileRef,
    pub verdict: Verdict,

    #[serde(default)]
    pub symbols: Vec<ResolvedSymbol>,

    #[serde(default)]
    pub missing_tensors: Vec<MissingTensor>,

    #[serde(default)]
    pub unexpected_tensors: Vec<UnexpectedTensor>,

    #[serde(default)]
    pub shape_mismatches: Vec<ShapeMismatch>,

    #[serde(default)]
    pub shape_comparisons_skipped: Vec<ShapeComparisonSkipped>,

    #[serde(default)]
    pub metadata_deltas: MetadataDeltas,

    #[serde(default)]
    pub hypotheses: Vec<Hypothesis>,

    #[serde(default)]
    pub warnings: Vec<Warning>,

    pub summary: Summary,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn verdict_exit_codes() {
        assert_eq!(Verdict::Matches.exit_code(), 0);
        assert_eq!(Verdict::OptionalExtras.exit_code(), 0);
        assert_eq!(Verdict::ProfileMismatch.exit_code(), 2);
        assert_eq!(Verdict::UnknownArchitecture.exit_code(), 3);
    }

    #[test]
    fn verdict_round_trips_as_snake_case_json() {
        for v in [
            Verdict::Matches,
            Verdict::OptionalExtras,
            Verdict::ProfileMismatch,
            Verdict::UnknownArchitecture,
        ] {
            let json = serde_json::to_string(&v).unwrap();
            let back: Verdict = serde_json::from_str(&json).unwrap();
            assert_eq!(v, back);
        }
        let s = serde_json::to_string(&Verdict::ProfileMismatch).unwrap();
        assert_eq!(s, r#""profile_mismatch""#);
    }

    #[test]
    fn profile_ref_round_trips() {
        let cases = [
            ProfileRef::Builtin {
                name: "qwen3".into(),
                extends: Some("llama".into()),
            },
            ProfileRef::File {
                path: "/tmp/x.toml".into(),
                name: "x".into(),
            },
            ProfileRef::Pairwise {
                against: "/tmp/other.gguf".into(),
            },
            ProfileRef::None,
        ];
        for p in cases {
            let json = serde_json::to_string(&p).unwrap();
            let back: ProfileRef = serde_json::from_str(&json).unwrap();
            assert_eq!(p, back);
        }
    }

    #[test]
    fn report_round_trips_through_json() {
        let report = DiagnosticReport {
            schema_version: SCHEMA_VERSION,
            file: "/tmp/x.gguf".into(),
            format: FormatInfo {
                kind: "gguf".into(),
                label: "GGUF v3".into(),
                tensor_count: 10,
                metadata_count: 5,
            },
            architecture: ArchitectureInfo {
                declared: Some("qwen3moe".into()),
                source: "general.architecture".into(),
            },
            profile: ProfileRef::Builtin {
                name: "qwen3moe".into(),
                extends: None,
            },
            verdict: Verdict::ProfileMismatch,
            symbols: vec![ResolvedSymbol {
                name: "hidden".into(),
                value: 4096,
                source: "metadata:qwen3moe.embedding_length".into(),
            }],
            missing_tensors: vec![MissingTensor {
                pattern: TensorPattern {
                    name: "blk.{layer}.ffn_norm.weight".into(),
                    per_layer_count: Some(48),
                    layers: (0..48).collect(),
                },
                expected_shape: Some(vec![ShapeExpr::from_str("hidden")]),
                optional: false,
            }],
            unexpected_tensors: vec![],
            shape_mismatches: vec![],
            shape_comparisons_skipped: vec![],
            metadata_deltas: MetadataDeltas::default(),
            hypotheses: vec![Hypothesis {
                id: 1,
                name: Some("rename".into()),
                triggered_by: HypothesisTriggers {
                    missing: vec!["blk.*.ffn_norm.weight".into()],
                    unexpected: vec![],
                },
                message: "FFN norm renamed.".into(),
            }],
            warnings: vec![],
            summary: Summary {
                required_missing: 1,
                optional_missing: 0,
                unexpected_patterns: 0,
                shape_mismatches: 0,
                hypotheses: 1,
                warnings: 0,
            },
        };
        let json = serde_json::to_string(&report).unwrap();
        let back: DiagnosticReport = serde_json::from_str(&json).unwrap();
        assert_eq!(report, back);
        assert!(json.contains(r#""schema_version":1"#));
    }
}