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}
55
56/// The known-architecture profile for a GGUF `general.architecture` value.
57///
58/// The table names the architectures that are *not* a text chat model, which is
59/// what an architecture missing from it is taken to be: the great majority of
60/// them are, and a name nobody has taught this table is far more likely to be
61/// another text model than a component. What is listed here is the exceptions:
62/// the encoders that only embed, the models that can also see, and the pieces
63/// that are half of a pipeline and serve nothing on their own.
64pub fn gguf_architecture_profile(architecture: &str) -> Option<GgufArchitectureProfile> {
65 let profile = |modality, capabilities, execution| GgufArchitectureProfile {
66 modality,
67 capabilities,
68 execution,
69 };
70 let embedding = || {
71 Some(profile(
72 Modality::embedding(),
73 vec![Capability::embed()],
74 ExecutionMode::Stream,
75 ))
76 };
77 let sees = || {
78 Some(profile(
79 Modality::text(),
80 vec![
81 Capability::chat(),
82 Capability::complete(),
83 Capability::see(),
84 ],
85 ExecutionMode::Stream,
86 ))
87 };
88 // A piece of a pipeline: it has a modality but nothing can be asked of it
89 // directly, so no runtime offers to serve it and it is never mistaken for a
90 // model that answers.
91 let component = |modality| Some(profile(modality, vec![], ExecutionMode::Sync));
92 match architecture {
93 "whisper" => Some(profile(
94 Modality::audio(),
95 vec![Capability::transcribe()],
96 ExecutionMode::Stream,
97 )),
98 "qwen2vl" | "qwen3vl" | "qwen3vlmoe" | "mllama" | "cogvlm" | "hunyuan-vl" => sees(),
99 "bert" | "nomic-bert" | "nomic-bert-moe" | "jina-bert-v2" | "jina-bert-v3" | "neo-bert"
100 | "modern-bert" | "eurobert" | "gemma-embedding" | "llama-embed" | "t5encoder" => {
101 embedding()
102 }
103 "clip" => component(Modality::vision()),
104 // The vocoder half of a text-to-speech pair: it turns another model's
105 // tokens into audio and cannot be prompted.
106 "wavtokenizer-dec" => component(Modality::audio()),
107 _ => None,
108 }
109}
110
111/// The default profile for an Ollama chat model with no more specific match.
112pub fn ollama_chat_profile() -> GgufArchitectureProfile {
113 GgufArchitectureProfile {
114 modality: Modality::text(),
115 capabilities: vec![Capability::chat(), Capability::complete()],
116 execution: ExecutionMode::Stream,
117 }
118}
119
120/// The default profile for an Ollama vision-capable chat model.
121pub fn ollama_vision_profile() -> GgufArchitectureProfile {
122 GgufArchitectureProfile {
123 modality: Modality::text(),
124 capabilities: vec![
125 Capability::chat(),
126 Capability::complete(),
127 Capability::see(),
128 ],
129 execution: ExecutionMode::Stream,
130 }
131}
132
133/// The profile for an Ollama model: vision when it ships a projector, otherwise
134/// the GGUF architecture's profile (read from `weight_path`) if recognized, else
135/// the plain chat default.
136///
137/// `weight_path` must be absolute — a leading `~` is not expanded (the Ollama
138/// scanner always passes a resolved blob path;
139/// a `~`-relative path would fail to open and fall through to the chat default).
140pub fn ollama_profile(has_projector: bool, weight_path: Option<&str>) -> GgufArchitectureProfile {
141 if has_projector {
142 return ollama_vision_profile();
143 }
144 if let Some(path) = weight_path
145 && let Some(architecture) =
146 crate::resolution::gguf::gguf_general_architecture(std::path::Path::new(path))
147 && let Some(profile) = gguf_architecture_profile(&architecture)
148 {
149 return profile;
150 }
151 ollama_chat_profile()
152}