metrale_bench/benchmarks/serve_matrix/
host.rs1use std::sync::Arc;
10
11use anyhow::Result;
12use futures::future::BoxFuture;
13use parking_lot::RwLock;
14
15use crate::plugin::TargetEndpoint;
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub enum Absence {
22 NoWeights,
24 NoConfig,
28 NoKernels,
30}
31
32impl Absence {
33 pub fn reason(self) -> &'static str {
34 match self {
35 Absence::NoWeights => "weights not fully downloaded",
36 Absence::NoConfig => "config.json unreadable — architecture unknown",
37 Absence::NoKernels => "no compiled kernels for this architecture",
38 }
39 }
40}
41
42#[derive(Clone, Debug, PartialEq, Eq)]
45pub struct ServeCandidate {
46 pub model: String,
48 pub quant: String,
51 pub absent: Option<Absence>,
53}
54
55impl ServeCandidate {
56 pub fn ready(model: impl Into<String>, quant: impl Into<String>) -> Self {
57 Self {
58 model: model.into(),
59 quant: quant.into(),
60 absent: None,
61 }
62 }
63
64 pub fn absent(model: impl Into<String>, quant: impl Into<String>, why: Absence) -> Self {
65 Self {
66 model: model.into(),
67 quant: quant.into(),
68 absent: Some(why),
69 }
70 }
71}
72
73#[derive(Clone, Copy, Debug, PartialEq, Eq)]
75pub struct ServeOptions {
76 pub max_seq_len: usize,
77 pub speculative: bool,
80}
81
82pub trait ServeHost: Send + Sync {
85 fn roster(&self) -> Result<Vec<ServeCandidate>>;
87
88 fn serve(&self, model: &str, opts: ServeOptions) -> BoxFuture<'_, Result<TargetEndpoint>>;
90
91 fn restore(&self) -> BoxFuture<'_, Result<()>>;
94}
95
96static HOST: RwLock<Option<Arc<dyn ServeHost>>> = RwLock::new(None);
99
100pub fn install(host: Arc<dyn ServeHost>) {
103 *HOST.write() = Some(host);
104}
105
106pub fn installed() -> Option<Arc<dyn ServeHost>> {
107 HOST.read().clone()
108}
109
110pub const NO_HOST: &str = "the serve matrix needs the Metrale Engine server that hosts this dashboard: it \
113 boots each checkpoint in-process. Run it from `met serve`'s \
114 dashboard rather than a standalone harness.";
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn every_absence_reads_differently_because_each_needs_a_different_fix() {
122 assert_eq!(
123 [
124 (Absence::NoWeights, Absence::NoWeights.reason()),
125 (Absence::NoConfig, Absence::NoConfig.reason()),
126 (Absence::NoKernels, Absence::NoKernels.reason()),
127 ],
128 [
129 (Absence::NoWeights, "weights not fully downloaded"),
130 (
131 Absence::NoConfig,
132 "config.json unreadable — architecture unknown",
133 ),
134 (
135 Absence::NoKernels,
136 "no compiled kernels for this architecture",
137 ),
138 ]
139 );
140 }
141
142 #[test]
143 fn a_ready_candidate_is_distinguishable_from_an_absent_one() {
144 assert_eq!(
145 ServeCandidate::ready("org/ready", "nvfp4"),
146 ServeCandidate {
147 model: "org/ready".into(),
148 quant: "nvfp4".into(),
149 absent: None,
150 }
151 );
152 assert_eq!(
153 ServeCandidate::absent("org/absent", "fp8", Absence::NoWeights),
154 ServeCandidate {
155 model: "org/absent".into(),
156 quant: "fp8".into(),
157 absent: Some(Absence::NoWeights),
158 }
159 );
160 }
161}