1mod built_in_schema;
2mod runtime;
3mod schema_types;
4
5pub use built_in_schema::{
6 BuiltInConfigPathResolution, built_in_config_schema_descriptor, built_in_config_settings,
7 canonicalize_built_in_config_identifier, canonicalize_built_in_config_path,
8 resolve_built_in_config_identifier, resolve_built_in_config_path,
9};
10pub use runtime::{
11 ActivityAdvertisement, ActivityResponse, DEFAULT_DRAIN_TIMEOUT_MAX_SECS,
12 DEFAULT_DRAIN_TIMEOUT_SECS, RuntimeActivityConfig, RuntimeMode, StartupFailurePolicy,
13};
14pub use schema_types::*;
15
16pub use mesh_llm_types::runtime::ModelRuntimeKind;
17use serde::ser::SerializeStruct;
18use serde::{Deserialize, Serialize};
19pub use skippy_protocol::FlashAttentionType;
20use std::collections::BTreeMap;
21
22#[derive(Clone, Debug, Default, Serialize)]
23pub struct MeshConfig {
24 #[serde(default)]
25 pub version: Option<u32>,
26 #[serde(default)]
27 pub gpu: GpuConfig,
28 #[serde(default)]
29 pub mesh_requirements: MeshRequirementsConfig,
30 #[serde(default)]
31 pub owner_control: OwnerControlConfig,
32 #[serde(default)]
33 pub telemetry: TelemetryConfig,
34 #[serde(default)]
35 pub defaults: Option<ModelConfigDefaults>,
36 #[serde(default)]
37 pub runtime: RuntimeConfig,
38 #[serde(default)]
39 pub models: Vec<ModelConfigEntry>,
40 #[serde(rename = "plugin", default)]
41 pub plugins: Vec<PluginConfigEntry>,
42 #[serde(flatten, default)]
43 pub extra: BTreeMap<String, toml::Value>,
44}
45
46#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
47pub struct OwnerControlConfig {
48 #[serde(default)]
49 pub bind: Option<std::net::SocketAddr>,
50 #[serde(default)]
51 pub advertise_addr: Option<std::net::SocketAddr>,
52}
53
54#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
55pub struct GpuConfig {
56 #[serde(default)]
57 pub assignment: GpuAssignment,
58 #[serde(default)]
59 pub parallel: Option<usize>,
60}
61
62pub const DEFAULT_MODEL_TARGET_DEMAND_UPGRADE_MIN_REQUESTS: u64 = 2;
63pub const DEFAULT_MODEL_TARGET_DEMAND_UPGRADE_MAX_AGE_SECS: u64 = 60 * 60;
64
65fn default_model_target_demand_upgrade_min_requests() -> u64 {
66 DEFAULT_MODEL_TARGET_DEMAND_UPGRADE_MIN_REQUESTS
67}
68
69fn default_model_target_demand_upgrade_max_age_secs() -> u64 {
70 DEFAULT_MODEL_TARGET_DEMAND_UPGRADE_MAX_AGE_SECS
71}
72
73fn default_drain_timeout_secs() -> u64 {
74 runtime::DEFAULT_DRAIN_TIMEOUT_SECS
75}
76
77fn default_drain_timeout_max_secs() -> u64 {
78 runtime::DEFAULT_DRAIN_TIMEOUT_MAX_SECS
79}
80
81#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
82pub struct RuntimeConfig {
83 #[serde(default)]
84 pub debug: bool,
85 #[serde(default)]
86 pub listen_all: bool,
87 #[serde(default)]
89 pub mode: runtime::RuntimeMode,
90 #[serde(default)]
92 pub startup_failure_policy: runtime::StartupFailurePolicy,
93 #[serde(default = "default_drain_timeout_secs")]
95 pub drain_timeout_secs: u64,
96 #[serde(default = "default_drain_timeout_max_secs")]
98 pub drain_timeout_max_secs: u64,
99 #[serde(default)]
101 pub activity: runtime::RuntimeActivityConfig,
102 #[serde(default)]
103 pub reconcile_model_targets: bool,
104 #[serde(default)]
105 pub reconcile_model_target_demand_upgrades: bool,
106 #[serde(default)]
107 pub native_runtime: NativeRuntimeConfig,
108 #[serde(default = "default_model_target_demand_upgrade_min_requests")]
109 pub model_target_demand_upgrade_min_requests: u64,
110 #[serde(default = "default_model_target_demand_upgrade_max_age_secs")]
111 pub model_target_demand_upgrade_max_age_secs: u64,
112}
113
114impl Default for RuntimeConfig {
115 fn default() -> Self {
116 Self {
117 debug: false,
118 listen_all: false,
119 mode: runtime::RuntimeMode::default(),
120 startup_failure_policy: runtime::StartupFailurePolicy::default(),
121 drain_timeout_secs: runtime::DEFAULT_DRAIN_TIMEOUT_SECS,
122 drain_timeout_max_secs: runtime::DEFAULT_DRAIN_TIMEOUT_MAX_SECS,
123 activity: runtime::RuntimeActivityConfig::default(),
124 reconcile_model_targets: false,
125 reconcile_model_target_demand_upgrades: false,
126 native_runtime: NativeRuntimeConfig::default(),
127 model_target_demand_upgrade_min_requests:
128 DEFAULT_MODEL_TARGET_DEMAND_UPGRADE_MIN_REQUESTS,
129 model_target_demand_upgrade_max_age_secs:
130 DEFAULT_MODEL_TARGET_DEMAND_UPGRADE_MAX_AGE_SECS,
131 }
132 }
133}
134
135#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
136pub struct NativeRuntimeConfig {
137 #[serde(default)]
138 pub mesh_version: Option<String>,
139 #[serde(default)]
140 pub skippy_abi: Option<String>,
141 #[serde(default)]
142 pub selection: Option<String>,
143}
144
145#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
146pub struct MeshRequirementsConfig {
147 #[serde(default)]
148 pub min_node_version: Option<String>,
149 #[serde(default)]
150 pub max_node_version: Option<String>,
151 #[serde(default)]
152 pub min_protocol_version: Option<u32>,
153 #[serde(default)]
154 pub max_protocol_version: Option<u32>,
155 #[serde(default)]
156 pub require_release_attestation: bool,
157 #[serde(default)]
158 pub release_signer_keys: Vec<String>,
159}
160
161#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
162#[serde(rename_all = "lowercase")]
163pub enum GpuAssignment {
164 #[default]
165 Auto,
166 Pinned,
167}
168
169#[derive(Clone, Debug, Default, Serialize)]
170pub struct ModelConfigDefaults {
171 #[serde(default)]
172 pub model_fit: Option<ModelFitConfig>,
173 #[serde(default)]
174 pub hardware: Option<HardwareConfig>,
175 #[serde(default)]
176 pub throughput: Option<ThroughputConfig>,
177 #[serde(default)]
178 pub skippy: Option<SkippyConfig>,
179 #[serde(default)]
180 pub speculative: Option<SpeculativeConfig>,
181 #[serde(default)]
182 pub request_defaults: Option<RequestDefaultsConfig>,
183 #[serde(default)]
184 pub multimodal: Option<MultimodalConfig>,
185 #[serde(default)]
186 pub advanced: Option<AdvancedConfig>,
187}
188
189#[derive(Clone, Debug, Default)]
190pub struct ModelConfigEntry {
191 pub model: String,
192 pub mmproj: Option<String>,
193 pub ctx_size: Option<u32>,
194 pub gpu_id: Option<String>,
195 pub parallel: Option<usize>,
196 pub cache_type_k: Option<String>,
197 pub cache_type_v: Option<String>,
198 pub batch: Option<u32>,
199 pub ubatch: Option<u32>,
200 pub flash_attention: Option<FlashAttentionType>,
201 pub model_fit: Option<ModelFitConfig>,
202 pub hardware: Option<HardwareConfig>,
203 pub throughput: Option<ThroughputConfig>,
204 pub skippy: Option<SkippyConfig>,
205 pub speculative: Option<SpeculativeConfig>,
206 pub request_defaults: Option<RequestDefaultsConfig>,
207 pub multimodal: Option<MultimodalConfig>,
208 pub advanced: Option<AdvancedConfig>,
209 pub gpu_id_from_legacy_shim: bool,
210}
211
212impl Serialize for ModelConfigEntry {
213 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
214 where
215 S: serde::Serializer,
216 {
217 let mut state = serializer.serialize_struct("ModelConfigEntry", 18)?;
218 state.serialize_field("model", &self.model)?;
219 if let Some(value) = &self.mmproj {
220 state.serialize_field("mmproj", value)?;
221 }
222 if let Some(value) = &self.ctx_size {
223 state.serialize_field("ctx_size", value)?;
224 }
225 if self.gpu_id_from_legacy_shim
226 && let Some(value) = &self.gpu_id
227 {
228 state.serialize_field("gpu_id", value)?;
229 }
230 if let Some(value) = &self.parallel {
231 state.serialize_field("parallel", value)?;
232 }
233 if let Some(value) = &self.cache_type_k {
234 state.serialize_field("cache_type_k", value)?;
235 }
236 if let Some(value) = &self.cache_type_v {
237 state.serialize_field("cache_type_v", value)?;
238 }
239 if let Some(value) = &self.batch {
240 state.serialize_field("batch", value)?;
241 }
242 if let Some(value) = &self.ubatch {
243 state.serialize_field("ubatch", value)?;
244 }
245 if let Some(value) = &self.flash_attention {
246 state.serialize_field("flash_attention", value)?;
247 }
248 if let Some(value) = &self.model_fit {
249 state.serialize_field("model_fit", value)?;
250 }
251 if let Some(value) = &self.hardware {
252 state.serialize_field("hardware", value)?;
253 }
254 if let Some(value) = &self.throughput {
255 state.serialize_field("throughput", value)?;
256 }
257 if let Some(value) = &self.skippy {
258 state.serialize_field("skippy", value)?;
259 }
260 if let Some(value) = &self.speculative {
261 state.serialize_field("speculative", value)?;
262 }
263 if let Some(value) = &self.request_defaults {
264 state.serialize_field("request_defaults", value)?;
265 }
266 if let Some(value) = &self.multimodal {
267 state.serialize_field("multimodal", value)?;
268 }
269 if let Some(value) = &self.advanced {
270 state.serialize_field("advanced", value)?;
271 }
272 state.end()
273 }
274}
275
276impl ModelConfigEntry {
277 pub fn derived_profile(&self) -> String {
293 let mut buf = Vec::new();
294 Self::write_effective_fit_profile(&mut buf, self);
295 Self::write_effective_hw_profile(&mut buf, self);
296 Self::write_effective_tp_profile(&mut buf, self);
297
298 if buf.is_empty() {
299 return String::new();
300 }
301
302 use std::hash::{Hash, Hasher};
303 let mut hasher = std::collections::hash_map::DefaultHasher::new();
304 buf.hash(&mut hasher);
305 let hash = hasher.finish();
306 format!("{:08x}", hash & 0xFFFFFFFF)
307 }
308
309 fn write_effective_fit_profile(buf: &mut Vec<u8>, entry: &ModelConfigEntry) {
310 use std::io::Write;
311 macro_rules! wo {
312 ($key:literal, $val:expr) => {
313 if let Some(ref v) = $val {
314 let _ = write!(buf, concat!($key, "={:?}\0"), v);
315 }
316 };
317 }
318 let fit = entry.model_fit.as_ref();
321 wo!("ctx_size", fit.and_then(|f| f.ctx_size).or(entry.ctx_size));
322 wo!("batch", fit.and_then(|f| f.batch).or(entry.batch));
323 wo!("ubatch", fit.and_then(|f| f.ubatch).or(entry.ubatch));
324 wo!(
325 "cache_type_k",
326 fit.and_then(|f| f.cache_type_k.as_ref())
327 .or(entry.cache_type_k.as_ref())
328 );
329 wo!(
330 "cache_type_v",
331 fit.and_then(|f| f.cache_type_v.as_ref())
332 .or(entry.cache_type_v.as_ref())
333 );
334 wo!(
335 "flash_attention",
336 fit.and_then(|f| f.flash_attention)
337 .or(entry.flash_attention)
338 );
339 }
340
341 fn write_effective_hw_profile(buf: &mut Vec<u8>, entry: &ModelConfigEntry) {
342 use std::io::Write;
343 macro_rules! wo {
344 ($key:literal, $val:expr) => {
345 if let Some(ref v) = $val {
346 let _ = write!(buf, concat!($key, "={:?}\0"), v);
347 }
348 };
349 }
350 let hw = entry.hardware.as_ref();
351 wo!(
352 "gpu_id",
353 hw.and_then(|h| h.device.as_ref()).or(entry.gpu_id.as_ref())
354 );
355 if let Some(hw) = hw {
356 wo!("model_runtime", hw.model_runtime);
357 wo!("gpu_layers", hw.gpu_layers);
358 wo!("tensor_split", hw.tensor_split);
359 wo!("split_mode", hw.split_mode);
360 wo!("main_gpu", hw.main_gpu);
361 wo!("cpu_moe", hw.cpu_moe);
362 wo!("n_cpu_moe", hw.n_cpu_moe);
363 wo!("fit_target_mib", hw.fit_target_mib);
364 wo!("mmap", hw.mmap);
365 wo!("mlock", hw.mlock);
366 }
367 }
368
369 fn write_effective_tp_profile(buf: &mut Vec<u8>, entry: &ModelConfigEntry) {
370 use std::io::Write;
371 macro_rules! wo {
372 ($key:literal, $val:expr) => {
373 if let Some(ref v) = $val {
374 let _ = write!(buf, concat!($key, "={:?}\0"), v);
375 }
376 };
377 }
378 let tp = entry.throughput.as_ref();
379 wo!("parallel", tp.and_then(|t| t.parallel).or(entry.parallel));
380 if let Some(tp) = tp {
381 wo!("continuous_batching", tp.continuous_batching);
382 wo!("threads", tp.threads);
383 wo!("threads_batch", tp.threads_batch);
384 }
385 }
386}
387
388#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
389#[serde(deny_unknown_fields)]
390pub struct ModelFitConfig {
391 #[serde(default)]
392 pub ctx_size: Option<u32>,
393 #[serde(default)]
394 pub batch: Option<u32>,
395 #[serde(default)]
396 pub ubatch: Option<u32>,
397 #[serde(default)]
398 pub cache_type_k: Option<String>,
399 #[serde(default)]
400 pub cache_type_v: Option<String>,
401 #[serde(default)]
402 pub kv_cache_policy: Option<String>,
403 #[serde(default)]
404 pub kv_offload: Option<BoolOrAuto>,
405 #[serde(default)]
406 pub kv_unified: Option<BoolOrAuto>,
407 #[serde(default)]
408 pub cache_ram_mib: Option<u64>,
409 #[serde(default)]
410 pub cache_idle_slots: Option<u32>,
411 #[serde(default)]
412 pub prompt_cache: Option<BoolOrAuto>,
413 #[serde(default)]
414 pub prefix_cache: Option<PrefixCacheConfig>,
415 #[serde(default)]
416 pub keep_tokens: Option<u32>,
417 #[serde(default)]
418 pub context_shift: Option<BoolOrAuto>,
419 #[serde(default)]
420 pub swa_full: Option<bool>,
421 #[serde(default)]
422 pub checkpoint_interval: Option<u32>,
423 #[serde(default)]
424 pub checkpoint_count: Option<u32>,
425 #[serde(default)]
426 pub lookup_cache_static: Option<String>,
427 #[serde(default)]
428 pub lookup_cache_dynamic: Option<String>,
429 #[serde(default)]
430 pub flash_attention: Option<FlashAttentionType>,
431}
432
433#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
434#[serde(deny_unknown_fields)]
435pub struct PrefixCacheConfig {
436 #[serde(default)]
437 pub enabled: Option<bool>,
438 #[serde(default)]
439 pub max_entries: Option<u32>,
440 #[serde(default)]
441 pub max_bytes: Option<u64>,
442 #[serde(default)]
443 pub min_tokens: Option<u32>,
444 #[serde(default)]
445 pub shared_stride_tokens: Option<u32>,
446 #[serde(default)]
447 pub shared_record_limit: Option<u32>,
448 #[serde(default)]
449 pub payload_mode: Option<String>,
450}
451
452#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
453#[serde(deny_unknown_fields)]
454pub struct HardwareConfig {
455 #[serde(default)]
456 pub model_runtime: Option<ModelRuntimeKind>,
457 #[serde(default)]
458 pub device: Option<String>,
459 #[serde(default)]
460 pub gpu_layers: Option<IntegerOrString>,
461 #[serde(default)]
462 pub stage_layer_start: Option<u32>,
463 #[serde(default)]
464 pub stage_layer_end: Option<u32>,
465 #[serde(default)]
466 pub placement: Option<String>,
467 #[serde(default)]
468 pub tensor_split: Option<TensorSplitConfig>,
469 #[serde(default)]
470 pub split_mode: Option<String>,
471 #[serde(default)]
472 pub main_gpu: Option<u32>,
473 #[serde(default)]
474 pub cpu_moe: Option<BoolOrAuto>,
475 #[serde(default)]
476 pub n_cpu_moe: Option<u32>,
477 #[serde(default)]
478 pub rpc_backend: Option<toml::Value>,
479 #[serde(default)]
480 pub fit_target_mib: Option<u64>,
481 #[serde(default)]
482 pub safety_margin_gb: Option<f64>,
483 #[serde(default)]
484 pub fit_context: Option<BoolOrAuto>,
485 #[serde(default)]
486 pub model_path: Option<String>,
487 #[serde(default)]
488 pub hf_repo: Option<String>,
489 #[serde(default)]
490 pub hf_file: Option<String>,
491 #[serde(default)]
492 pub mmproj: Option<String>,
493 #[serde(default)]
494 pub mmproj_offload: Option<BoolOrAuto>,
495 #[serde(default)]
496 pub lora_adapters: Vec<String>,
497 #[serde(default)]
498 pub control_vectors: Vec<String>,
499 #[serde(default)]
500 pub check_tensors: Option<bool>,
501 #[serde(default)]
502 pub mmap: Option<BoolOrAuto>,
503 #[serde(default)]
504 pub mlock: Option<bool>,
505 #[serde(default)]
506 pub direct_io: Option<bool>,
507 #[serde(default)]
508 pub repack: Option<bool>,
509 #[serde(default)]
510 pub op_offload: Option<bool>,
511 #[serde(default)]
512 pub no_host_buffer: Option<bool>,
513 #[serde(default)]
514 pub warmup: Option<BoolOrAuto>,
515}
516
517#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
518#[serde(deny_unknown_fields)]
519pub struct ThroughputConfig {
520 #[serde(default)]
521 pub parallel: Option<usize>,
522 #[serde(default)]
523 pub continuous_batching: Option<BoolOrAuto>,
524 #[serde(default)]
525 pub threads: Option<usize>,
526 #[serde(default)]
527 pub threads_batch: Option<usize>,
528 #[serde(default)]
529 pub threads_http: Option<usize>,
530 #[serde(default)]
531 pub priority: Option<IntegerOrString>,
532 #[serde(default)]
533 pub poll: Option<BoolOrString>,
534 #[serde(default)]
535 pub cpu_affinity: Option<StringOrStringList>,
536 #[serde(default)]
537 pub numa: Option<String>,
538 #[serde(default)]
539 pub slot_prompt_similarity: Option<f64>,
540 #[serde(default)]
541 pub sleep_idle_seconds: Option<u64>,
542 #[serde(default)]
543 pub tuning_profile: Option<String>,
544}
545
546#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
547#[serde(deny_unknown_fields)]
548pub struct SkippyConfig {
549 #[serde(default)]
550 pub stage_model_path: Option<String>,
551 #[serde(default)]
552 pub stage_role: Option<String>,
553 #[serde(default)]
554 pub stage_topology: Option<String>,
555 #[serde(default)]
556 pub activation_wire_dtype: Option<String>,
557 #[serde(default)]
558 pub binary_stage_transport: Option<String>,
559 #[serde(default)]
560 pub openai_frontend_mode: Option<toml::Value>,
561 #[serde(default)]
562 pub lifecycle_startup_timeout_ms: Option<u64>,
563 #[serde(default)]
564 pub lifecycle_readiness_interval_ms: Option<u64>,
565 #[serde(default)]
566 pub lifecycle_health_interval_ms: Option<u64>,
567 #[serde(default)]
568 pub prefill_chunking: Option<String>,
569 #[serde(default)]
570 pub prefill_chunk_size: Option<u32>,
571 #[serde(default)]
572 pub prefill_chunk_schedule: Option<String>,
573}
574
575#[derive(Clone, Debug, Default, PartialEq)]
576pub struct SpeculativeConfig {
577 pub strategy: Option<String>,
578 pub mode: Option<String>,
579 pub draft_model: Option<String>,
580 pub draft_hf_repo: Option<String>,
581 pub draft_hf_file: Option<String>,
582 pub draft_selection_policy: Option<String>,
583 pub pairing_fault: Option<String>,
584 pub draft_max_tokens: Option<u32>,
585 pub draft_min_tokens: Option<u32>,
586 pub draft_acceptance_threshold: Option<f64>,
587 pub draft_split_probability: Option<f64>,
588 pub draft_gpu_layers: Option<i32>,
589 pub draft_device: Option<String>,
590 pub draft_threads: Option<usize>,
591 pub draft_cache_type_k: Option<String>,
592 pub draft_cache_type_v: Option<String>,
593 pub ngram_min: Option<u32>,
594 pub ngram_max: Option<u32>,
595 pub ngram_max_proposal_tokens: Option<u32>,
596 pub ngram_proposer: Option<String>,
597 pub extension_max_tokens: Option<u32>,
598 pub native_mtp_reject_cooldown_tokens: Option<u32>,
599 pub native_mtp_suppress_cooldown_drafts: Option<bool>,
600 pub native_mtp_suppress_cooldown_draft_limit: Option<u32>,
601 pub verify_window_min_tokens: Option<u32>,
602 pub verify_window_max_tokens: Option<u32>,
603 pub verify_window_pipeline_depth: Option<u32>,
604 pub spec_default: Option<BoolOrAuto>,
605 pub(crate) legacy_draft_model_path_used: bool,
606}
607
608impl SpeculativeConfig {
609 pub fn with_precedence(
612 overrides: Option<&Self>,
613 model: Option<&Self>,
614 defaults: Option<&Self>,
615 ) -> Self {
616 macro_rules! pick {
617 ($field:ident) => {
618 overrides
619 .and_then(|config| config.$field.clone())
620 .or_else(|| model.and_then(|config| config.$field.clone()))
621 .or_else(|| defaults.and_then(|config| config.$field.clone()))
622 };
623 }
624
625 Self {
626 strategy: pick!(strategy),
627 mode: pick!(mode),
628 draft_model: pick!(draft_model),
629 draft_hf_repo: pick!(draft_hf_repo),
630 draft_hf_file: pick!(draft_hf_file),
631 draft_selection_policy: pick!(draft_selection_policy),
632 pairing_fault: pick!(pairing_fault),
633 draft_max_tokens: pick!(draft_max_tokens),
634 draft_min_tokens: pick!(draft_min_tokens),
635 draft_acceptance_threshold: pick!(draft_acceptance_threshold),
636 draft_split_probability: pick!(draft_split_probability),
637 draft_gpu_layers: pick!(draft_gpu_layers),
638 draft_device: pick!(draft_device),
639 draft_threads: pick!(draft_threads),
640 draft_cache_type_k: pick!(draft_cache_type_k),
641 draft_cache_type_v: pick!(draft_cache_type_v),
642 ngram_min: pick!(ngram_min),
643 ngram_max: pick!(ngram_max),
644 ngram_max_proposal_tokens: pick!(ngram_max_proposal_tokens),
645 ngram_proposer: pick!(ngram_proposer),
646 extension_max_tokens: pick!(extension_max_tokens),
647 native_mtp_reject_cooldown_tokens: pick!(native_mtp_reject_cooldown_tokens),
648 native_mtp_suppress_cooldown_drafts: pick!(native_mtp_suppress_cooldown_drafts),
649 native_mtp_suppress_cooldown_draft_limit: pick!(
650 native_mtp_suppress_cooldown_draft_limit
651 ),
652 verify_window_min_tokens: pick!(verify_window_min_tokens),
653 verify_window_max_tokens: pick!(verify_window_max_tokens),
654 verify_window_pipeline_depth: pick!(verify_window_pipeline_depth),
655 spec_default: pick!(spec_default),
656 legacy_draft_model_path_used: overrides
657 .filter(|config| config.draft_model.is_some())
658 .or_else(|| model.filter(|config| config.draft_model.is_some()))
659 .or_else(|| defaults.filter(|config| config.draft_model.is_some()))
660 .is_some_and(|config| config.legacy_draft_model_path_used),
661 }
662 }
663}
664
665#[derive(Deserialize)]
669#[serde(deny_unknown_fields)]
670struct SpeculativeConfigRaw {
671 #[serde(default)]
672 strategy: Option<String>,
673 #[serde(default)]
674 mode: Option<String>,
675 #[serde(default)]
676 draft_model: Option<String>,
677 #[serde(default)]
678 draft_model_path: Option<String>,
679 #[serde(default)]
680 draft_hf_repo: Option<String>,
681 #[serde(default)]
682 draft_hf_file: Option<String>,
683 #[serde(default)]
684 draft_selection_policy: Option<String>,
685 #[serde(default)]
686 pairing_fault: Option<String>,
687 #[serde(default)]
688 draft_max_tokens: Option<u32>,
689 #[serde(default)]
690 draft_min_tokens: Option<u32>,
691 #[serde(default)]
692 draft_acceptance_threshold: Option<f64>,
693 #[serde(default)]
694 draft_split_probability: Option<f64>,
695 #[serde(default)]
696 draft_gpu_layers: Option<i32>,
697 #[serde(default)]
698 draft_device: Option<String>,
699 #[serde(default)]
700 draft_threads: Option<usize>,
701 #[serde(default)]
702 draft_cache_type_k: Option<String>,
703 #[serde(default)]
704 draft_cache_type_v: Option<String>,
705 #[serde(default)]
706 ngram_min: Option<u32>,
707 #[serde(default)]
708 ngram_max: Option<u32>,
709 #[serde(default)]
710 ngram_max_proposal_tokens: Option<u32>,
711 #[serde(default)]
712 ngram_proposer: Option<String>,
713 #[serde(default)]
714 extension_max_tokens: Option<u32>,
715 #[serde(default)]
716 native_mtp_reject_cooldown_tokens: Option<u32>,
717 #[serde(default)]
718 native_mtp_suppress_cooldown_drafts: Option<bool>,
719 #[serde(default)]
720 native_mtp_suppress_cooldown_draft_limit: Option<u32>,
721 #[serde(default)]
722 verify_window_min_tokens: Option<u32>,
723 #[serde(default)]
724 verify_window_max_tokens: Option<u32>,
725 #[serde(default)]
726 verify_window_pipeline_depth: Option<u32>,
727 #[serde(default)]
728 spec_default: Option<BoolOrAuto>,
729}
730
731impl<'de> Deserialize<'de> for SpeculativeConfig {
732 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
733 where
734 D: serde::Deserializer<'de>,
735 {
736 let raw = SpeculativeConfigRaw::deserialize(deserializer)?;
737 let legacy_used = raw.draft_model_path.is_some();
738 if raw.draft_model.is_some() && raw.draft_model_path.is_some() {
739 return Err(serde::de::Error::custom(
740 "speculative config cannot set both `draft_model` and the legacy `draft_model_path`; \
741 use `draft_model` only",
742 ));
743 }
744 Ok(SpeculativeConfig {
745 strategy: raw.strategy,
746 mode: raw.mode,
747 draft_model: raw.draft_model.or(raw.draft_model_path),
748 draft_hf_repo: raw.draft_hf_repo,
749 draft_hf_file: raw.draft_hf_file,
750 draft_selection_policy: raw.draft_selection_policy,
751 pairing_fault: raw.pairing_fault,
752 draft_max_tokens: raw.draft_max_tokens,
753 draft_min_tokens: raw.draft_min_tokens,
754 draft_acceptance_threshold: raw.draft_acceptance_threshold,
755 draft_split_probability: raw.draft_split_probability,
756 draft_gpu_layers: raw.draft_gpu_layers,
757 draft_device: raw.draft_device,
758 draft_threads: raw.draft_threads,
759 draft_cache_type_k: raw.draft_cache_type_k,
760 draft_cache_type_v: raw.draft_cache_type_v,
761 ngram_min: raw.ngram_min,
762 ngram_max: raw.ngram_max,
763 ngram_max_proposal_tokens: raw.ngram_max_proposal_tokens,
764 ngram_proposer: raw.ngram_proposer,
765 extension_max_tokens: raw.extension_max_tokens,
766 native_mtp_reject_cooldown_tokens: raw.native_mtp_reject_cooldown_tokens,
767 native_mtp_suppress_cooldown_drafts: raw.native_mtp_suppress_cooldown_drafts,
768 native_mtp_suppress_cooldown_draft_limit: raw.native_mtp_suppress_cooldown_draft_limit,
769 verify_window_min_tokens: raw.verify_window_min_tokens,
770 verify_window_max_tokens: raw.verify_window_max_tokens,
771 verify_window_pipeline_depth: raw.verify_window_pipeline_depth,
772 spec_default: raw.spec_default,
773 legacy_draft_model_path_used: legacy_used,
774 })
775 }
776}
777
778impl Serialize for SpeculativeConfig {
779 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
780 where
781 S: serde::Serializer,
782 {
783 use serde::ser::SerializeMap;
784
785 let mut map = serializer.serialize_map(Some(32))?;
786 map.serialize_entry("strategy", &self.strategy)?;
787 map.serialize_entry("mode", &self.mode)?;
788 if self.legacy_draft_model_path_used {
789 if let Some(ref v) = self.draft_model {
790 map.serialize_entry("draft_model_path", v)?;
791 }
792 } else if let Some(ref v) = self.draft_model {
793 map.serialize_entry("draft_model", v)?;
794 }
795 map.serialize_entry("draft_hf_repo", &self.draft_hf_repo)?;
796 map.serialize_entry("draft_hf_file", &self.draft_hf_file)?;
797 map.serialize_entry("draft_selection_policy", &self.draft_selection_policy)?;
798 map.serialize_entry("pairing_fault", &self.pairing_fault)?;
799 map.serialize_entry("draft_max_tokens", &self.draft_max_tokens)?;
800 map.serialize_entry("draft_min_tokens", &self.draft_min_tokens)?;
801 map.serialize_entry(
802 "draft_acceptance_threshold",
803 &self.draft_acceptance_threshold,
804 )?;
805 map.serialize_entry("draft_split_probability", &self.draft_split_probability)?;
806 map.serialize_entry("draft_gpu_layers", &self.draft_gpu_layers)?;
807 map.serialize_entry("draft_device", &self.draft_device)?;
808 map.serialize_entry("draft_threads", &self.draft_threads)?;
809 map.serialize_entry("draft_cache_type_k", &self.draft_cache_type_k)?;
810 map.serialize_entry("draft_cache_type_v", &self.draft_cache_type_v)?;
811 map.serialize_entry("ngram_min", &self.ngram_min)?;
812 map.serialize_entry("ngram_max", &self.ngram_max)?;
813 map.serialize_entry("ngram_max_proposal_tokens", &self.ngram_max_proposal_tokens)?;
814 map.serialize_entry("ngram_proposer", &self.ngram_proposer)?;
815 map.serialize_entry("extension_max_tokens", &self.extension_max_tokens)?;
816 map.serialize_entry(
817 "native_mtp_reject_cooldown_tokens",
818 &self.native_mtp_reject_cooldown_tokens,
819 )?;
820 map.serialize_entry(
821 "native_mtp_suppress_cooldown_drafts",
822 &self.native_mtp_suppress_cooldown_drafts,
823 )?;
824 map.serialize_entry(
825 "native_mtp_suppress_cooldown_draft_limit",
826 &self.native_mtp_suppress_cooldown_draft_limit,
827 )?;
828 map.serialize_entry("verify_window_min_tokens", &self.verify_window_min_tokens)?;
829 map.serialize_entry("verify_window_max_tokens", &self.verify_window_max_tokens)?;
830 map.serialize_entry(
831 "verify_window_pipeline_depth",
832 &self.verify_window_pipeline_depth,
833 )?;
834 map.serialize_entry("spec_default", &self.spec_default)?;
835 map.end()
836 }
837}
838
839#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
840#[serde(deny_unknown_fields)]
841pub struct RequestDefaultsConfig {
842 #[serde(default)]
843 pub max_tokens: Option<u32>,
844 #[serde(default)]
845 pub stop: Option<StringOrStringList>,
846 #[serde(default)]
847 pub temperature: Option<f64>,
848 #[serde(default)]
849 pub top_p: Option<f64>,
850 #[serde(default)]
851 pub top_k: Option<i64>,
852 #[serde(default)]
853 pub min_p: Option<f64>,
854 #[serde(default)]
855 pub typical_p: Option<f64>,
856 #[serde(default)]
857 pub top_nsigma: Option<f64>,
858 #[serde(default)]
859 pub dynatemp_range: Option<f64>,
860 #[serde(default)]
861 pub dynatemp_exponent: Option<f64>,
862 #[serde(default)]
863 pub repeat_penalty: Option<f64>,
864 #[serde(default)]
865 pub repeat_last_n: Option<i64>,
866 #[serde(default)]
867 pub presence_penalty: Option<f64>,
868 #[serde(default)]
869 pub frequency_penalty: Option<f64>,
870 #[serde(default)]
871 pub dry: Option<ReservedObjectConfig>,
872 #[serde(default)]
873 pub xtc: Option<ReservedObjectConfig>,
874 #[serde(default)]
875 pub adaptive: Option<ReservedObjectConfig>,
876 #[serde(default)]
877 pub mirostat_mode: Option<IntegerOrString>,
878 #[serde(default)]
879 pub mirostat_entropy: Option<f64>,
880 #[serde(default)]
881 pub mirostat_learning_rate: Option<f64>,
882 #[serde(default)]
883 pub samplers: Option<Vec<String>>,
884 #[serde(default)]
885 pub sampler_sequence: Option<String>,
886 #[serde(default)]
887 pub seed: Option<i64>,
888 #[serde(default)]
889 pub logit_bias: Option<toml::Value>,
890 #[serde(default)]
891 pub ignore_eos: Option<bool>,
892 #[serde(default)]
893 pub backend_sampling: Option<toml::Value>,
894 #[serde(default)]
895 pub reasoning_format: Option<String>,
896 #[serde(default)]
897 pub reasoning_enabled: Option<ReasoningEnabled>,
898 #[serde(default)]
899 pub reasoning_budget: Option<ReasoningBudget>,
900 #[serde(default)]
901 pub chat_template: Option<String>,
902 #[serde(default)]
903 pub chat_template_file: Option<String>,
904 #[serde(default)]
905 pub jinja: Option<bool>,
906 #[serde(default)]
907 pub chat_template_kwargs: Option<toml::Value>,
908 #[serde(default)]
909 pub skip_chat_parsing: Option<bool>,
910 #[serde(default)]
911 pub prefill_assistant: Option<toml::Value>,
912 #[serde(default)]
913 pub system_prompt: Option<String>,
914 #[serde(default)]
915 pub grammar: Option<toml::Value>,
916 #[serde(default)]
917 pub json_schema: Option<toml::Value>,
918 #[serde(default)]
919 pub logprobs: Option<toml::Value>,
920}
921
922#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
923#[serde(deny_unknown_fields)]
924pub struct MultimodalConfig {
925 #[serde(default)]
926 pub mmproj: Option<String>,
927 #[serde(default)]
928 pub mmproj_url: Option<String>,
929 #[serde(default)]
930 pub mmproj_offload: Option<BoolOrAuto>,
931 #[serde(default)]
932 pub image_min_tokens: Option<u32>,
933 #[serde(default)]
934 pub image_max_tokens: Option<u32>,
935 #[serde(default)]
936 pub embeddings: Option<toml::Value>,
937 #[serde(default)]
938 pub reranking: Option<toml::Value>,
939 #[serde(default)]
940 pub pooling: Option<toml::Value>,
941 #[serde(default)]
942 pub vocoder: Option<toml::Value>,
943}
944
945#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
946#[serde(deny_unknown_fields)]
947pub struct AdvancedConfig {
948 #[serde(default)]
949 pub server: Option<AdvancedServerConfig>,
950}
951
952#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
953#[serde(deny_unknown_fields)]
954pub struct AdvancedServerConfig {
955 #[serde(default)]
956 pub host: Option<String>,
957 #[serde(default)]
958 pub port: Option<u16>,
959 #[serde(default)]
960 pub reuse_port: Option<bool>,
961 #[serde(default)]
962 pub timeout: Option<u64>,
963 #[serde(default)]
964 pub metrics: Option<bool>,
965 #[serde(default)]
966 pub slots: Option<bool>,
967 #[serde(default)]
968 pub props: Option<bool>,
969 #[serde(default)]
970 pub alias: Option<String>,
971 #[serde(default)]
972 pub api_prefix: Option<String>,
973}
974
975#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
976#[serde(untagged)]
977pub enum BoolOrAuto {
978 Bool(bool),
979 String(String),
980}
981
982#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
983#[serde(untagged)]
984pub enum BoolOrString {
985 Bool(bool),
986 String(String),
987}
988
989#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
990#[serde(untagged)]
991pub enum IntegerOrString {
992 Integer(i64),
993 String(String),
994}
995
996#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
997#[serde(untagged)]
998pub enum StringOrStringList {
999 String(String),
1000 List(Vec<String>),
1001}
1002
1003#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
1004#[serde(untagged)]
1005pub enum TensorSplitConfig {
1006 Ratios(Vec<f64>),
1007 String(String),
1008}
1009
1010#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
1011#[serde(untagged)]
1012pub enum ReasoningEnabled {
1013 Bool(bool),
1014 String(String),
1015}
1016
1017#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
1018#[serde(untagged)]
1019pub enum ReasoningBudget {
1020 Integer(u32),
1021 String(String),
1022}
1023
1024#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
1025#[serde(deny_unknown_fields)]
1026pub struct ReservedObjectConfig {}
1027
1028#[derive(Clone, Debug, Default, Deserialize)]
1029struct RawMeshConfig {
1030 #[serde(default)]
1031 version: Option<u32>,
1032 #[serde(default)]
1033 gpu: GpuConfig,
1034 #[serde(default)]
1035 mesh_requirements: MeshRequirementsConfig,
1036 #[serde(default)]
1037 owner_control: OwnerControlConfig,
1038 #[serde(default)]
1039 telemetry: TelemetryConfig,
1040 #[serde(default)]
1041 defaults: Option<ModelConfigDefaults>,
1042 #[serde(default)]
1043 runtime: RuntimeConfig,
1044 #[serde(default)]
1045 models: Vec<ModelConfigEntry>,
1046 #[serde(rename = "plugin", default)]
1047 plugins: Vec<PluginConfigEntry>,
1048 #[serde(flatten, default)]
1049 extra: BTreeMap<String, toml::Value>,
1050}
1051
1052#[derive(Clone, Debug, Default, Deserialize)]
1053struct RawModelConfigDefaults {
1054 #[serde(default)]
1055 model_fit: Option<ModelFitConfig>,
1056 #[serde(default)]
1057 hardware: Option<HardwareConfig>,
1058 #[serde(default)]
1059 throughput: Option<ThroughputConfig>,
1060 #[serde(default)]
1061 skippy: Option<SkippyConfig>,
1062 #[serde(default)]
1063 speculative: Option<SpeculativeConfig>,
1064 #[serde(default)]
1065 request_defaults: Option<RequestDefaultsConfig>,
1066 #[serde(default)]
1067 multimodal: Option<MultimodalConfig>,
1068 #[serde(default)]
1069 advanced: Option<AdvancedConfig>,
1070 #[serde(default)]
1071 mmproj: Option<String>,
1072 #[serde(default)]
1073 ctx_size: Option<u32>,
1074 #[serde(default)]
1075 gpu_id: Option<String>,
1076 #[serde(default)]
1077 parallel: Option<usize>,
1078 #[serde(default)]
1079 cache_type_k: Option<String>,
1080 #[serde(default)]
1081 cache_type_v: Option<String>,
1082 #[serde(default)]
1083 batch: Option<u32>,
1084 #[serde(default)]
1085 ubatch: Option<u32>,
1086 #[serde(default)]
1087 flash_attention: Option<FlashAttentionType>,
1088}
1089
1090#[derive(Clone, Debug, Default, Deserialize)]
1091struct RawModelConfigEntry {
1092 model: String,
1093 #[serde(default)]
1094 mmproj: Option<String>,
1095 #[serde(default)]
1096 ctx_size: Option<u32>,
1097 #[serde(default)]
1098 gpu_id: Option<String>,
1099 #[serde(default)]
1100 parallel: Option<usize>,
1101 #[serde(default)]
1102 cache_type_k: Option<String>,
1103 #[serde(default)]
1104 cache_type_v: Option<String>,
1105 #[serde(default)]
1106 batch: Option<u32>,
1107 #[serde(default)]
1108 ubatch: Option<u32>,
1109 #[serde(default)]
1110 flash_attention: Option<FlashAttentionType>,
1111 #[serde(default)]
1112 model_fit: Option<ModelFitConfig>,
1113 #[serde(default)]
1114 hardware: Option<HardwareConfig>,
1115 #[serde(default)]
1116 throughput: Option<ThroughputConfig>,
1117 #[serde(default)]
1118 skippy: Option<SkippyConfig>,
1119 #[serde(default)]
1120 speculative: Option<SpeculativeConfig>,
1121 #[serde(default)]
1122 request_defaults: Option<RequestDefaultsConfig>,
1123 #[serde(default)]
1124 multimodal: Option<MultimodalConfig>,
1125 #[serde(default)]
1126 advanced: Option<AdvancedConfig>,
1127}
1128
1129impl<'de> Deserialize<'de> for MeshConfig {
1130 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1131 where
1132 D: serde::Deserializer<'de>,
1133 {
1134 let raw = RawMeshConfig::deserialize(deserializer)?;
1135 Ok(Self {
1136 version: raw.version,
1137 gpu: raw.gpu,
1138 mesh_requirements: raw.mesh_requirements,
1139 owner_control: raw.owner_control,
1140 telemetry: raw.telemetry,
1141 defaults: raw.defaults,
1142 runtime: raw.runtime,
1143 models: raw.models,
1144 plugins: raw.plugins,
1145 extra: raw.extra,
1146 })
1147 }
1148}
1149
1150impl<'de> Deserialize<'de> for ModelConfigDefaults {
1151 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1152 where
1153 D: serde::Deserializer<'de>,
1154 {
1155 let raw = RawModelConfigDefaults::deserialize(deserializer)?;
1156 Ok(Self::from_raw(raw))
1157 }
1158}
1159
1160impl<'de> Deserialize<'de> for ModelConfigEntry {
1161 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1162 where
1163 D: serde::Deserializer<'de>,
1164 {
1165 let raw = RawModelConfigEntry::deserialize(deserializer)?;
1166 Ok(Self::from_raw(raw))
1167 }
1168}
1169
1170impl ModelConfigDefaults {
1171 fn from_raw(raw: RawModelConfigDefaults) -> Self {
1172 let model_fit = merge_model_fit(
1173 raw.model_fit,
1174 raw.ctx_size,
1175 raw.cache_type_k,
1176 raw.cache_type_v,
1177 raw.batch,
1178 raw.ubatch,
1179 raw.flash_attention,
1180 );
1181 let hardware = merge_hardware(raw.hardware, raw.gpu_id, None, None);
1182 let throughput = merge_throughput(raw.throughput, raw.parallel);
1183 let multimodal = merge_multimodal(raw.multimodal, raw.mmproj);
1184 Self {
1185 model_fit,
1186 hardware,
1187 throughput,
1188 skippy: raw.skippy,
1189 speculative: raw.speculative,
1190 request_defaults: raw.request_defaults,
1191 multimodal,
1192 advanced: raw.advanced,
1193 }
1194 }
1195}
1196
1197impl ModelConfigEntry {
1198 fn from_raw(raw: RawModelConfigEntry) -> Self {
1199 let gpu_id_from_legacy_shim = raw.gpu_id.is_some();
1200 let model_fit = merge_model_fit(
1201 raw.model_fit,
1202 raw.ctx_size,
1203 raw.cache_type_k.clone(),
1204 raw.cache_type_v.clone(),
1205 raw.batch,
1206 raw.ubatch,
1207 raw.flash_attention,
1208 );
1209 let multimodal = merge_multimodal(raw.multimodal, raw.mmproj.clone());
1210 let hardware = merge_hardware(
1211 raw.hardware,
1212 raw.gpu_id.clone(),
1213 multimodal.as_ref().and_then(|m| m.mmproj.clone()),
1214 multimodal.as_ref().and_then(|m| m.mmproj_offload.clone()),
1215 );
1216 let throughput = merge_throughput(raw.throughput, raw.parallel);
1217
1218 Self {
1219 model: raw.model,
1220 mmproj: multimodal
1221 .as_ref()
1222 .and_then(|config| config.mmproj.clone())
1223 .or_else(|| hardware.as_ref().and_then(|config| config.mmproj.clone()))
1224 .or(raw.mmproj),
1225 ctx_size: model_fit.as_ref().and_then(|config| config.ctx_size),
1226 gpu_id: hardware
1227 .as_ref()
1228 .and_then(|config| config.device.clone())
1229 .or(raw.gpu_id),
1230 parallel: throughput.as_ref().and_then(|config| config.parallel),
1231 cache_type_k: model_fit
1232 .as_ref()
1233 .and_then(|config| config.cache_type_k.clone())
1234 .or(raw.cache_type_k),
1235 cache_type_v: model_fit
1236 .as_ref()
1237 .and_then(|config| config.cache_type_v.clone())
1238 .or(raw.cache_type_v),
1239 batch: model_fit.as_ref().and_then(|config| config.batch),
1240 ubatch: model_fit.as_ref().and_then(|config| config.ubatch),
1241 flash_attention: model_fit
1242 .as_ref()
1243 .and_then(|config| config.flash_attention)
1244 .or(raw.flash_attention),
1245 model_fit,
1246 hardware,
1247 throughput,
1248 skippy: raw.skippy,
1249 speculative: raw.speculative,
1250 request_defaults: raw.request_defaults,
1251 multimodal,
1252 advanced: raw.advanced,
1253 gpu_id_from_legacy_shim,
1254 }
1255 }
1256}
1257
1258pub(crate) fn merge_model_fit(
1259 current: Option<ModelFitConfig>,
1260 ctx_size: Option<u32>,
1261 cache_type_k: Option<String>,
1262 cache_type_v: Option<String>,
1263 batch: Option<u32>,
1264 ubatch: Option<u32>,
1265 flash_attention: Option<FlashAttentionType>,
1266) -> Option<ModelFitConfig> {
1267 let mut config = current.unwrap_or_default();
1268 config.ctx_size = config.ctx_size.or(ctx_size);
1269 config.cache_type_k = config.cache_type_k.or(cache_type_k);
1270 config.cache_type_v = config.cache_type_v.or(cache_type_v);
1271 config.batch = config.batch.or(batch);
1272 config.ubatch = config.ubatch.or(ubatch);
1273 config.flash_attention = config.flash_attention.or(flash_attention);
1274 if is_model_fit_empty(&config) {
1275 None
1276 } else {
1277 Some(config)
1278 }
1279}
1280
1281pub(crate) fn merge_hardware(
1282 current: Option<HardwareConfig>,
1283 gpu_id: Option<String>,
1284 mmproj: Option<String>,
1285 mmproj_offload: Option<BoolOrAuto>,
1286) -> Option<HardwareConfig> {
1287 let mut config = current.unwrap_or_default();
1288 config.device = config.device.or(gpu_id);
1289 config.mmproj = config.mmproj.or(mmproj);
1290 config.mmproj_offload = config.mmproj_offload.or(mmproj_offload);
1291 if is_hardware_empty(&config) {
1292 None
1293 } else {
1294 Some(config)
1295 }
1296}
1297
1298pub(crate) fn merge_throughput(
1299 current: Option<ThroughputConfig>,
1300 parallel: Option<usize>,
1301) -> Option<ThroughputConfig> {
1302 let mut config = current.unwrap_or_default();
1303 config.parallel = config.parallel.or(parallel);
1304 if is_throughput_empty(&config) {
1305 None
1306 } else {
1307 Some(config)
1308 }
1309}
1310
1311pub(crate) fn merge_multimodal(
1312 current: Option<MultimodalConfig>,
1313 mmproj: Option<String>,
1314) -> Option<MultimodalConfig> {
1315 let mut config = current.unwrap_or_default();
1316 config.mmproj = config.mmproj.or(mmproj);
1317 if is_multimodal_empty(&config) {
1318 None
1319 } else {
1320 Some(config)
1321 }
1322}
1323
1324fn is_model_fit_empty(config: &ModelFitConfig) -> bool {
1325 config == &ModelFitConfig::default()
1326}
1327
1328fn is_hardware_empty(config: &HardwareConfig) -> bool {
1329 config == &HardwareConfig::default()
1330}
1331
1332fn is_throughput_empty(config: &ThroughputConfig) -> bool {
1333 config == &ThroughputConfig::default()
1334}
1335
1336fn is_multimodal_empty(config: &MultimodalConfig) -> bool {
1337 config == &MultimodalConfig::default()
1338}
1339
1340#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
1341pub struct TelemetryConfig {
1342 #[serde(default)]
1343 pub enabled: Option<bool>,
1344 #[serde(default)]
1345 pub service_name: Option<String>,
1346 #[serde(default)]
1347 pub endpoint: Option<String>,
1348 #[serde(default)]
1349 pub headers: BTreeMap<String, String>,
1350 #[serde(default)]
1351 pub export_interval_secs: Option<u64>,
1352 #[serde(default)]
1353 pub queue_size: Option<usize>,
1354 #[serde(default)]
1355 pub prompt_shape_metrics: bool,
1356 #[serde(default)]
1357 pub metrics: TelemetryMetricsConfig,
1358}
1359
1360#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
1361pub struct TelemetryMetricsConfig {
1362 #[serde(default)]
1363 pub endpoint: Option<String>,
1364}
1365
1366#[derive(Clone, Debug, Deserialize, Serialize)]
1367pub struct PluginConfigEntry {
1368 pub name: String,
1369 #[serde(default)]
1370 pub enabled: Option<bool>,
1371 #[serde(default, skip_serializing_if = "Option::is_none")]
1372 pub web_ui_enabled: Option<bool>,
1373 #[serde(default)]
1374 pub command: Option<String>,
1375 #[serde(default)]
1376 pub args: Vec<String>,
1377 #[serde(default)]
1379 pub url: Option<String>,
1380 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
1381 pub settings: BTreeMap<String, toml::Value>,
1382 #[serde(default, skip_serializing_if = "PluginStartupConfig::is_default")]
1383 pub startup: PluginStartupConfig,
1384}
1385
1386#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1387pub enum PluginWebUiPreference {
1388 None,
1389 Enabled,
1390 Disabled,
1391}
1392
1393impl PluginWebUiPreference {
1394 pub const fn resolve(web_ui_enabled: Option<bool>, declares_web_ui: bool) -> Self {
1395 match (declares_web_ui, web_ui_enabled) {
1396 (false, _) => Self::None,
1397 (true, Some(false)) => Self::Disabled,
1398 (true, Some(true) | None) => Self::Enabled,
1399 }
1400 }
1401}
1402
1403impl PluginConfigEntry {
1404 pub const fn web_ui_preference(&self, declares_web_ui: bool) -> PluginWebUiPreference {
1405 PluginWebUiPreference::resolve(self.web_ui_enabled, declares_web_ui)
1406 }
1407}
1408
1409#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
1410pub struct PluginStartupConfig {
1411 #[serde(default, skip_serializing_if = "Option::is_none")]
1412 pub connect_timeout_secs: Option<u64>,
1413 #[serde(default, skip_serializing_if = "Option::is_none")]
1414 pub init_timeout_secs: Option<u64>,
1415 #[serde(default)]
1416 pub optional: bool,
1417 #[serde(default)]
1418 pub lazy_start: bool,
1419}
1420
1421impl PluginStartupConfig {
1422 pub fn is_default(&self) -> bool {
1423 self == &Self::default()
1424 }
1425}