1mod device;
5mod length;
6mod memory;
7#[cfg(test)]
8mod tests;
9
10use std::path::PathBuf;
11
12use candle_core::DType;
13pub use length::LengthKind;
14use thiserror::Error;
15
16use crate::registry::Backend;
17use crate::registry::Catalog;
18
19#[derive(Debug, Error)]
20#[non_exhaustive]
21pub enum CanddingError {
22 #[error(
24 "model `{id}` is not in the hugging face cache, and this build of candding cannot download it: enable its `download` feature, or fetch the model with the candding CLI, `{hint}`"
25 )]
26 ModelNotFound { id: String, hint: String },
27 #[error(
28 "model `{id}` is not in the hugging face cache, and HF_HUB_OFFLINE forbids downloading it"
29 )]
30 Offline { id: String },
31 #[error(
32 "model `{id}` is gated: accept its license on huggingface.co, then set HF_TOKEN to an access token of that account"
33 )]
34 GatedModel { id: String },
35 #[error(
37 "model `{id}` was not found on the hugging face hub: check the id, or set HF_TOKEN if the repository is private"
38 )]
39 RepositoryNotFound { id: String },
40 #[error("model `{id}` has no revision `{revision}` on the hugging face hub")]
41 RevisionNotFound { id: String, revision: String },
42 #[error("could not reach the hugging face hub to download `{id}`: {reason}")]
43 Network { id: String, reason: String },
44 #[error("downloading `{id}` failed: {reason}")]
45 DownloadFailed { id: String, reason: String },
46 #[error("the hub's answer for `{id}` names `{path}`, which {reason}; nothing was downloaded")]
48 UnsafeHubPath {
49 id: String,
50 path: String,
51 reason: String,
52 },
53 #[error("unsupported architecture `{0}`")]
54 UnsupportedArchitecture(String),
55 #[error(
58 "model `{id}` is not in the {} registry, and no descriptor was supplied: an unregistered {} model has no snapshot convention to infer {} from",
59 .catalog.name(),
60 .catalog.name(),
61 .catalog.unstated_facts()
62 )]
63 UnregisteredModel { id: String, catalog: Catalog },
64 #[error(
67 "model `{model}` is registered in the {} catalog, not the dense one: load it with {}",
68 .catalog.name(),
69 .catalog.entry_point()
70 )]
71 NonDenseModel { model: String, catalog: Catalog },
72 #[error(
74 "model `{model}` cannot run in {}: the catalog marks that dtype unsupported for this model",
75 .dtype.as_str()
76 )]
77 UnsupportedDtype { model: String, dtype: DType },
78 #[error(
80 "model `{model}` cannot run on {}: {} is not supported",
81 .backend.name(),
82 device::refusal(.backend, .dtype)
83 )]
84 UnsupportedOnDevice {
85 model: String,
86 backend: Backend,
87 dtype: DType,
88 },
89 #[error("model `{0}` does not support matryoshka truncation")]
90 MrlUnsupported(String),
91 #[error("invalid matryoshka dimension {requested}: model dimension is {dim}")]
92 InvalidDim { requested: usize, dim: usize },
93 #[error("dense module activation `{0}` is not supported; expected Identity or Tanh")]
94 UnsupportedDenseActivation(String),
95 #[error(
98 "pooling mode `{0}` is not implemented; candding pools `pooling_mode_cls_token`, `pooling_mode_mean_tokens` or `pooling_mode_lasttoken`"
99 )]
100 UnsupportedPooling(String),
101 #[error(
104 "modules.json entry `{0}` is not implemented; candding runs Transformer, Pooling, Dense and Normalize modules"
105 )]
106 UnsupportedModule(String),
107 #[error(
109 "modules.json entry `{0}` is a sparse-pipeline module; this repository publishes a sparse pipeline — load it with SparseTextEmbedding's builder instead of TextEmbedding's"
110 )]
111 SparseOnlyModule(String),
112 #[error(
115 "SparseHead::MiniCoil needs its own per-word matrix, vocabulary and stopword list from a second repository, and this descriptor names none — supply one through SparseTextEmbeddingBuilder::descriptor with a head_source, or use a registered id"
116 )]
117 MissingHeadSource,
118 #[error(
120 "`{hint}` computes bm25 directly, with no device or dtype and no weights to load — SparseTextEmbedding's builder is for a head that runs a real encoder, which this one does not"
121 )]
122 ComputedSparseModel { hint: String },
123 #[error(
125 "declared projection_in {declared} does not match the encoder's real hidden size {actual}: the registry entry and the checkpoint's own weights disagree"
126 )]
127 ProjectionInMismatch { declared: usize, actual: usize },
128 #[error(
130 "model `{model}` runs MultiVectorHead::StanfordColbert, which requires MultiVectorDescriptor::query_length to build a fixed-length, `[MASK]`-padded query tokenizer; this descriptor names none"
131 )]
132 MissingQueryLength { model: String },
133 #[error("tensor `{name}` is missing from `{file}`")]
134 MissingTensor { file: PathBuf, name: String },
135 #[error("dense modules on disk {found:?} do not match the descriptor's {expected:?}")]
136 DenseMismatch {
137 expected: Vec<String>,
138 found: Vec<String>,
139 },
140 #[error("missing file `{0}` in model directory")]
141 MissingFile(PathBuf),
142 #[error("`{file}` is not a readable numpy array file: {reason}")]
144 NpyHeader { file: PathBuf, reason: String },
145 #[error(
147 "`{0}` stores its array in fortran (column-major) order; candding reads only C (row-major) order and refuses rather than silently transposing"
148 )]
149 NpyFortranOrder(PathBuf),
150 #[error(
152 "`{file}` declares numpy dtype `{descr}`, which this reader does not map to a candle tensor dtype"
153 )]
154 NpyUnsupportedDtype { file: PathBuf, descr: String },
155 #[error(
157 "`{file}` declares byte order `{order}` in dtype `{descr}`; this reader only reads little-endian (`<`) or native (`=`) arrays, plus `|` for single-byte dtypes"
158 )]
159 NpyUnsupportedByteOrder {
160 file: PathBuf,
161 order: char,
162 descr: String,
163 },
164 #[error(
166 "`{file}` declares shape {shape:?} but its data section holds {actual_bytes} bytes, not the {expected_bytes} its shape and dtype require"
167 )]
168 NpyShapeMismatch {
169 file: PathBuf,
170 shape: Vec<usize>,
171 expected_bytes: usize,
172 actual_bytes: usize,
173 },
174 #[error(
177 "no weights in `{0}`: expected `model.safetensors`, `model.safetensors.index.json` or `pytorch_model.bin`. A snapshot fetched before its entry was registered can hold the configuration without the weights; delete that directory and fetch the model again. A repository whose only weights are a torch pickle and whose id no catalog carries is fetched without them, since the fetch asks for safetensors unless an entry declares the pickle format: download its `pytorch_model.bin` into `{0}` yourself, from `https://huggingface.co/<repo>/resolve/<revision>/pytorch_model.bin`, or pass the builder a local directory that holds it"
178 )]
179 MissingWeights(PathBuf),
180 #[error("model directory `{0}` does not exist")]
181 MissingDir(PathBuf),
182 #[error("tokenizer error: {0}")]
183 Tokenizer(String),
184 #[error(
186 "{lengths} lengths for a batch of {rows} rows: EncoderInput needs one real-token length per row"
187 )]
188 LengthsMismatch { lengths: usize, rows: usize },
189 #[error("{}", length::message(.requested, .minimum, .kind))]
192 LengthTooSmall {
193 requested: usize,
194 minimum: usize,
195 kind: LengthKind,
196 },
197 #[error(
200 "the {template} prompt does not end in its suffix ids with this tokenizer: expected {expected:?}, found {found:?}; this tokenizer does not keep the template's suffix apart from the text before it"
201 )]
202 PromptSuffixMismatch {
203 template: String,
204 expected: Vec<u32>,
205 found: Vec<u32>,
206 },
207 #[error(
210 "model `{model}` reads a (query, document) segment pair, which has no instruction slot; only a prompt reranker takes an instruction"
211 )]
212 InstructionOnPairInput { model: String },
213 #[error(
216 "config.json says tie_word_embeddings is true, and the weights also carry an lm_head.weight: the logit-score head cannot tell which matrix produces the logits"
217 )]
218 TiedEmbeddingsWithLmHead,
219 #[error(
221 "config.json says tie_word_embeddings is false, and the weights carry no lm_head.weight: an untied checkpoint's logits come from that matrix"
222 )]
223 MissingLmHead,
224 #[error(
227 "the entry says the logit-score head's unembedding is {declared}, but config.json says tie_word_embeddings is {tie_word_embeddings}"
228 )]
229 UnembeddingDisagreesWithConfig {
230 declared: String,
231 tie_word_embeddings: bool,
232 },
233 #[error(
236 "the entry declares {declared} positions and config.json's position_embedding_type is {config}: a flash checkpoint loads only when both name the same scheme"
237 )]
238 PositionSchemeDisagreesWithConfig { declared: String, config: String },
239 #[error("logit-score token id {token_id} is outside the vocabulary of {vocab_size} tokens")]
241 LogitTokenOutOfRange { token_id: u32, vocab_size: usize },
242 #[error("config error: {0}")]
243 Config(#[from] serde_json::Error),
244 #[error(transparent)]
245 Candle(#[from] candle_core::Error),
246 #[error(transparent)]
247 Io(#[from] std::io::Error),
248 #[error(
250 "device error: the {} backend is not compiled in; build candding with its `{}` feature",
251 .backend.name(),
252 .backend.name()
253 )]
254 BackendNotCompiled { backend: Backend },
255 #[error("device error: the {} device could not be created: {reason}", .backend.name())]
257 Device { backend: Backend, reason: String },
258 #[error(
260 "model `{model}` needs {} for its weights, but the {} device has {} available: load it in a smaller dtype, on another device, or choose a smaller model",
261 memory::gb(.needed_bytes),
262 .backend.name(),
263 memory::gb(.available_bytes)
264 )]
265 InsufficientMemory {
266 model: String,
267 backend: Backend,
268 needed_bytes: u64,
269 available_bytes: u64,
270 },
271}
272
273pub type Result<T> = std::result::Result<T, CanddingError>;