Skip to main content

kernel/resolution/
format.rs

1//! Format and capability facts derived from a model's files.
2
3use serde::{Deserialize, Serialize};
4
5use crate::records::{Capability, ExecutionMode, Modality};
6
7/// A recognized model format — the on-disk weight formats plus the logical
8/// sources (a diffusers pipeline directory, an Ollama store entry, a built-in or
9/// remote-endpoint model), and `Unknown` for anything unrecognized.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11#[serde(rename_all = "kebab-case")]
12pub enum ModelFormat {
13    /// A GGUF weight file (llama.cpp).
14    Gguf,
15    /// A legacy GGML `.bin` weight file.
16    GgmlBin,
17    /// A safetensors weight directory.
18    Safetensors,
19    /// An MLX-format safetensors weight directory.
20    MlxSafetensors,
21    /// A diffusers pipeline directory (a `model_index.json`).
22    Diffusers,
23    /// An entry in a local Ollama store.
24    OllamaStore,
25    /// A built-in (platform-provided) model.
26    Builtin,
27    /// A remote inference endpoint.
28    Endpoint,
29    /// An unrecognized format.
30    Unknown,
31}
32
33/// The modality, capabilities, and execution shape implied by a GGUF
34/// architecture.
35#[derive(Debug, Clone, PartialEq)]
36pub struct GgufArchitectureProfile {
37    /// The model's primary modality.
38    pub modality: Modality,
39    /// What the model can do.
40    pub capabilities: Vec<Capability>,
41    /// How its runtime delivers output.
42    pub execution: ExecutionMode,
43}
44
45/// Facts read from a GGUF header.
46#[derive(Debug, Clone, PartialEq)]
47pub struct GgufFacts {
48    /// The `general.architecture` value, if present.
49    pub architecture: Option<String>,
50    /// The resolved context length, if the header declared one.
51    pub context_length: Option<i64>,
52    /// Whether the header carries a chat template.
53    pub has_chat_template: bool,
54    /// The weight type `general.file_type` names, as llama.cpp spells it
55    /// (`Q4_K_M`, `Q8_0`, `F16`), when the header carries a known one.
56    pub quantization: Option<String>,
57}
58
59/// The known-architecture profile for a GGUF `general.architecture` value.
60///
61/// The table names the architectures that are *not* a text chat model, which is
62/// what an architecture missing from it is taken to be: the great majority of
63/// them are, and a name nobody has taught this table is far more likely to be
64/// another text model than a component. What is listed here is the exceptions:
65/// the encoders that only embed, the models that can also see, and the pieces
66/// that are half of a pipeline and serve nothing on their own.
67pub fn gguf_architecture_profile(architecture: &str) -> Option<GgufArchitectureProfile> {
68    let profile = |modality, capabilities, execution| GgufArchitectureProfile {
69        modality,
70        capabilities,
71        execution,
72    };
73    let embedding = || {
74        Some(profile(
75            Modality::embedding(),
76            vec![Capability::embed()],
77            ExecutionMode::Stream,
78        ))
79    };
80    let sees = || {
81        Some(profile(
82            Modality::text(),
83            vec![
84                Capability::chat(),
85                Capability::complete(),
86                Capability::see(),
87            ],
88            ExecutionMode::Stream,
89        ))
90    };
91    // A piece of a pipeline: it has a modality but nothing can be asked of it
92    // directly, so no runtime offers to serve it and it is never mistaken for a
93    // model that answers.
94    let component = |modality| Some(profile(modality, vec![], ExecutionMode::Sync));
95    match architecture {
96        "whisper" => Some(profile(
97            Modality::audio(),
98            vec![Capability::transcribe()],
99            ExecutionMode::Stream,
100        )),
101        "qwen2vl" | "qwen3vl" | "qwen3vlmoe" | "mllama" | "cogvlm" | "hunyuan-vl" => sees(),
102        "bert" | "nomic-bert" | "nomic-bert-moe" | "jina-bert-v2" | "jina-bert-v3" | "neo-bert"
103        | "modern-bert" | "eurobert" | "gemma-embedding" | "llama-embed" | "t5encoder" => {
104            embedding()
105        }
106        "clip" => component(Modality::vision()),
107        // The vocoder half of a text-to-speech pair: it turns another model's
108        // tokens into audio and cannot be prompted.
109        "wavtokenizer-dec" => component(Modality::audio()),
110        _ => None,
111    }
112}
113
114/// The default profile for an Ollama chat model with no more specific match.
115pub fn ollama_chat_profile() -> GgufArchitectureProfile {
116    GgufArchitectureProfile {
117        modality: Modality::text(),
118        capabilities: vec![Capability::chat(), Capability::complete()],
119        execution: ExecutionMode::Stream,
120    }
121}
122
123/// The default profile for an Ollama vision-capable chat model.
124pub fn ollama_vision_profile() -> GgufArchitectureProfile {
125    GgufArchitectureProfile {
126        modality: Modality::text(),
127        capabilities: vec![
128            Capability::chat(),
129            Capability::complete(),
130            Capability::see(),
131        ],
132        execution: ExecutionMode::Stream,
133    }
134}
135
136/// The profile for an Ollama model: vision when it ships a projector, otherwise
137/// the profile of `architecture`, the one its weight blob's GGUF header names,
138/// if recognized, else the plain chat default.
139pub fn ollama_profile(has_projector: bool, architecture: Option<&str>) -> GgufArchitectureProfile {
140    if has_projector {
141        return ollama_vision_profile();
142    }
143    architecture
144        .and_then(gguf_architecture_profile)
145        .unwrap_or_else(ollama_chat_profile)
146}