1use crate::{ModelTypeMatch, TargetPtxSet};
32
33pub struct ResolveCandidate<'a> {
36 pub name: &'a str,
40 pub type_matches: &'a [ModelTypeMatch],
42 pub match_names: &'a [&'a str],
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
48pub enum TargetResolveError {
49 Ambiguous {
53 model_type: String,
54 hidden_size: usize,
55 tier: &'static str,
57 candidates: Vec<(String, Vec<String>)>,
59 matched: Vec<String>,
61 model_refs: Vec<String>,
63 },
64 PinNotFound { pin: String, available: Vec<String> },
67 PinIncompatible {
71 pin: String,
72 model_type: String,
73 hidden_size: usize,
74 declared: Vec<String>,
75 },
76}
77
78impl std::fmt::Display for TargetResolveError {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 match self {
81 Self::Ambiguous {
82 model_type,
83 hidden_size,
84 tier,
85 candidates,
86 matched,
87 model_refs,
88 } => {
89 let cands = candidates
90 .iter()
91 .map(|(n, needles)| format!("{n} (match_names: {needles:?})"))
92 .collect::<Vec<_>>()
93 .join(", ");
94 let outcome = if matched.is_empty() {
95 "no checkpoint reference names any of them".to_string()
96 } else {
97 format!("the references match several of them: {matched:?}")
98 };
99 write!(
100 f,
101 "AMBIGUOUS kernel target: {} compiled targets declare {tier} support for \
102 (model_type '{model_type}', hidden_size {hidden_size}) — [{cands}] — and \
103 {outcome} (references searched: {model_refs:?}). Refusing to pick by build \
104 order. Fix: serve with a model id/path that contains exactly one target's \
105 match_names needle, pin explicitly with --kernel-target <name>, or build \
106 single-target with METRALE_TARGET_MODEL=<name>.",
107 candidates.len(),
108 )
109 }
110 Self::PinNotFound { pin, available } => write!(
111 f,
112 "--kernel-target '{pin}' does not name a compiled kernel target \
113 (available: {available:?})"
114 ),
115 Self::PinIncompatible {
116 pin,
117 model_type,
118 hidden_size,
119 declared,
120 } => write!(
121 f,
122 "--kernel-target '{pin}' is compiled but declares no support for this \
123 checkpoint's (model_type '{model_type}', hidden_size {hidden_size}) — it \
124 declares {declared:?}. Serving another architecture's kernels would be \
125 garbage; refusing."
126 ),
127 }
128 }
129}
130
131impl std::error::Error for TargetResolveError {}
132
133fn needles_hit(match_names: &[&str], refs_lower: &[String]) -> bool {
136 match_names.iter().any(|needle| {
137 let n = needle.to_lowercase();
138 !n.is_empty() && refs_lower.iter().any(|r| r.contains(&n))
139 })
140}
141
142fn distinct_names<'a>(candidates: &[ResolveCandidate<'a>], idxs: &[usize]) -> Vec<&'a str> {
144 let mut names: Vec<&str> = Vec::new();
145 for &i in idxs {
146 if !names.contains(&candidates[i].name) {
147 names.push(candidates[i].name);
148 }
149 }
150 names
151}
152
153pub fn resolve_target(
158 candidates: &[ResolveCandidate<'_>],
159 model_type: &str,
160 hidden_size: usize,
161 model_refs: &[&str],
162) -> Result<Option<usize>, TargetResolveError> {
163 let refs_lower: Vec<String> = model_refs.iter().map(|r| r.to_lowercase()).collect();
164
165 let tiers: [(&'static str, Option<usize>); 2] =
166 [("exact", Some(hidden_size)), ("wildcard", None)];
167 for (tier, want_hidden) in tiers {
168 let idxs: Vec<usize> = candidates
169 .iter()
170 .enumerate()
171 .filter(|(_, c)| {
172 c.type_matches
173 .iter()
174 .any(|m| m.model_type == model_type && m.hidden_size == want_hidden)
175 })
176 .map(|(i, _)| i)
177 .collect();
178 if idxs.is_empty() {
179 continue;
180 }
181 let names = distinct_names(candidates, &idxs);
182 if names.len() == 1 {
183 return Ok(Some(idxs[0]));
186 }
187 let matched: Vec<&str> = names
188 .iter()
189 .copied()
190 .filter(|n| {
191 idxs.iter().any(|&i| {
192 candidates[i].name == *n && needles_hit(candidates[i].match_names, &refs_lower)
193 })
194 })
195 .collect();
196 if let [winner] = matched.as_slice() {
197 let idx = idxs
198 .iter()
199 .copied()
200 .find(|&i| candidates[i].name == *winner)
201 .expect("winner name came from idxs");
202 return Ok(Some(idx));
203 }
204 return Err(TargetResolveError::Ambiguous {
207 model_type: model_type.to_string(),
208 hidden_size,
209 tier,
210 candidates: names
211 .iter()
212 .map(|n| {
213 let needles = idxs
214 .iter()
215 .filter(|&&i| candidates[i].name == *n)
216 .flat_map(|&i| candidates[i].match_names.iter().map(|s| s.to_string()))
217 .collect();
218 (n.to_string(), needles)
219 })
220 .collect(),
221 matched: matched.iter().map(|n| n.to_string()).collect(),
222 model_refs: model_refs.iter().map(|r| r.to_string()).collect(),
223 });
224 }
225 Ok(None)
226}
227
228pub fn resolve_pinned(
233 candidates: &[ResolveCandidate<'_>],
234 pin: &str,
235 model_type: &str,
236 hidden_size: usize,
237) -> Result<usize, TargetResolveError> {
238 let pinned: Vec<usize> = candidates
239 .iter()
240 .enumerate()
241 .filter(|(_, c)| c.name.eq_ignore_ascii_case(pin))
242 .map(|(i, _)| i)
243 .collect();
244 if pinned.is_empty() {
245 let all: Vec<usize> = (0..candidates.len()).collect();
246 return Err(TargetResolveError::PinNotFound {
247 pin: pin.to_string(),
248 available: distinct_names(candidates, &all)
249 .into_iter()
250 .map(String::from)
251 .collect(),
252 });
253 }
254 let compatible = pinned.iter().copied().find(|&i| {
255 candidates[i].type_matches.iter().any(|m| {
256 m.model_type == model_type
257 && (m.hidden_size.is_none() || m.hidden_size == Some(hidden_size))
258 })
259 });
260 compatible.ok_or_else(|| TargetResolveError::PinIncompatible {
261 pin: pin.to_string(),
262 model_type: model_type.to_string(),
263 hidden_size,
264 declared: pinned
265 .iter()
266 .flat_map(|&i| candidates[i].type_matches.iter())
267 .map(|m| format!("({}, {:?})", m.model_type, m.hidden_size))
268 .collect(),
269 })
270}
271
272pub fn ptx_for_config(
277 model_type: &str,
278 hidden_size: usize,
279 model_refs: &[&str],
280 pinned_target: Option<&str>,
281) -> Result<Option<TargetPtxSet>, TargetResolveError> {
282 let targets = crate::all_ptx_sets();
283 let candidates: Vec<ResolveCandidate<'_>> = targets
284 .iter()
285 .map(|t| ResolveCandidate {
286 name: t.target.model,
287 type_matches: &t.model_type_matches,
288 match_names: t.match_names,
289 })
290 .collect();
291 let idx = match pinned_target {
292 Some(pin) => Some(resolve_pinned(&candidates, pin, model_type, hidden_size)?),
293 None => resolve_target(&candidates, model_type, hidden_size, model_refs)?,
294 };
295 drop(candidates);
296 Ok(idx.and_then(|i| targets.into_iter().nth(i)))
297}
298
299pub fn ptx_for_exact_target(model: &str, quant: &str) -> Option<TargetPtxSet> {
304 crate::all_ptx_sets()
305 .into_iter()
306 .find(|t| t.target.model == model && t.target.quant == quant)
307}
308
309#[cfg(test)]
310#[path = "resolve_tests.rs"]
311mod tests;