1use std::path::PathBuf;
2use std::time::Duration;
3
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, PartialEq)]
8pub enum ModelState {
9 Available,
10 Loading,
11 Benchmarking,
12 Loaded { port: u16, pid: u32 },
13 Failed { error: String },
14}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum SearchSort {
19 Relevance,
20 Downloads,
21 Likes,
22 Trending,
23 CreatedAt,
24}
25
26impl SearchSort {
27 pub fn next(self) -> Self {
28 match self {
29 SearchSort::Relevance => SearchSort::Downloads,
30 SearchSort::Downloads => SearchSort::Likes,
31 SearchSort::Likes => SearchSort::Trending,
32 SearchSort::Trending => SearchSort::CreatedAt,
33 SearchSort::CreatedAt => SearchSort::Relevance,
34 }
35 }
36
37 pub fn label(self) -> String {
38 match self {
39 SearchSort::Relevance => crate::t!("models.sort.relevance").to_string(),
40 SearchSort::Downloads => crate::t!("models.sort.downloads").to_string(),
41 SearchSort::Likes => crate::t!("models.sort.likes").to_string(),
42 SearchSort::Trending => crate::t!("models.sort.trending").to_string(),
43 SearchSort::CreatedAt => crate::t!("models.sort.created").to_string(),
44 }
45 }
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum ListSort {
51 Name,
52 Status,
53 Params,
54 Qual,
55 Context,
56}
57
58impl ListSort {
59 pub fn next(self) -> Self {
60 match self {
61 ListSort::Name => ListSort::Status,
62 ListSort::Status => ListSort::Params,
63 ListSort::Params => ListSort::Qual,
64 ListSort::Qual => ListSort::Context,
65 ListSort::Context => ListSort::Name,
66 }
67 }
68
69 pub fn label(self) -> String {
70 match self {
71 ListSort::Name => crate::t!("models.list_sort.name").to_string(),
72 ListSort::Status => crate::t!("models.list_sort.status").to_string(),
73 ListSort::Params => crate::t!("models.list_sort.params").to_string(),
74 ListSort::Qual => crate::t!("models.list_sort.qual").to_string(),
75 ListSort::Context => crate::t!("models.list_sort.context").to_string(),
76 }
77 }
78
79 pub fn is_ascending(self) -> bool {
80 matches!(self, ListSort::Name)
81 }
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
86pub struct SearchResult {
87 pub model_id: String,
88 pub model_name: String,
89 pub tags: Vec<String>,
90 pub downloads: u64,
91 pub likes: u64,
92 pub pipeline_tag: Option<String>,
93 pub size: Option<u64>,
94 pub parameters: Option<String>,
95 pub capabilities: Vec<String>,
96 pub context_length: Option<u32>,
97 pub readme: Option<String>,
98 pub quantization: Option<String>,
100 pub license: Option<String>,
102 pub trending_score: i64,
104 pub created_at: Option<String>,
106 #[serde(default)]
108 pub downloaded: bool,
109}
110
111#[derive(Debug, Clone)]
113pub struct DownloadState {
114 pub model_id: String,
115 pub filename: String,
116 pub total_bytes: u64,
117 pub downloaded_bytes: u64,
118 pub status: DownloadStatus,
119 pub cancelled: bool,
120 pub cancel_token: Option<std::sync::Arc<std::sync::atomic::AtomicBool>>,
121 pub download_state: u8,
123 pub download_state_arc: Option<std::sync::Arc<std::sync::atomic::AtomicU8>>,
125 pub start_time: std::time::Instant,
126 pub bytes_per_second: f64,
127 pub dest: Option<std::path::PathBuf>,
129}
130
131impl DownloadState {
132 pub fn new(model_id: String, filename: String, total_bytes: u64) -> Self {
133 Self {
134 model_id,
135 filename,
136 total_bytes,
137 downloaded_bytes: 0,
138 status: DownloadStatus::Downloading,
139 cancelled: false,
140 cancel_token: None,
141 download_state: 1,
142 download_state_arc: None,
143 start_time: std::time::Instant::now(),
144 bytes_per_second: 0.0,
145 dest: None,
146 }
147 }
148}
149
150impl std::hash::Hash for ModelSettings {
151 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
152 self.context_length.hash(state);
154 self.threads.hash(state);
155 self.threads_batch.hash(state);
156 self.batch_size.hash(state);
157 self.ubatch_size.hash(state);
158 self.parallel.hash(state);
159 self.max_concurrent_predictions.hash(state);
160 self.uniform_cache.hash(state);
161 self.kv_cache_offload.hash(state);
162 self.cache_type_k.hash(state);
163 self.cache_type_v.hash(state);
164 self.keep.hash(state);
165 self.swa_full.hash(state);
166 self.mlock.hash(state);
167 self.mmap.hash(state);
168 self.numa.hash(state);
169 self.system_prompt.hash(state);
170 self.system_prompt_preset_name.hash(state);
171 self.gpu_layers_mode.hash(state);
173 self.split_mode.hash(state);
174 self.tensor_split.hash(state);
175 self.main_gpu.hash(state);
176 self.fit.hash(state);
177 self.lora.hash(state);
178 if let Some((path, scale)) = &self.lora_scaled {
179 path.hash(state);
180 scale.to_bits().hash(state);
181 }
182 self.rpc.hash(state);
183 self.embedding.hash(state);
184 self.flash_attn.hash(state);
185 self.expert_count.hash(state);
186 self.jinja.hash(state);
187 self.auto_chat_template.hash(state);
188 self.chat_template.hash(state);
189 self.chat_template_kwargs.hash(state);
190 self.seed.hash(state);
192 self.temperature.to_bits().hash(state);
193 self.top_k.hash(state);
194 self.top_p.to_bits().hash(state);
195 self.min_p.to_bits().hash(state);
196 self.typical_p.to_bits().hash(state);
197 self.mirostat.hash(state);
198 self.mirostat_lr.to_bits().hash(state);
199 self.mirostat_ent.to_bits().hash(state);
200 self.ignore_eos.hash(state);
201 self.samplers.hash(state);
202 self.repeat_penalty.to_bits().hash(state);
204 self.repeat_last_n.hash(state);
205 self.presence_penalty.map(|v| v.to_bits()).hash(state);
206 self.frequency_penalty.map(|v| v.to_bits()).hash(state);
207 self.dry_multiplier.to_bits().hash(state);
208 self.dry_base.to_bits().hash(state);
209 self.dry_allowed_length.hash(state);
210 self.dry_penalty_last_n.hash(state);
211 self.rope_scaling.hash(state);
213 self.rope_scale.to_bits().hash(state);
214 self.rope_freq_base.to_bits().hash(state);
215 self.rope_freq_scale.to_bits().hash(state);
216 self.rope_yarn_enabled.hash(state);
217 self.host.hash(state);
219 self.port.hash(state);
220 self.timeout.hash(state);
221 self.cache_prompt.hash(state);
222 self.cache_reuse.hash(state);
223 self.webui.hash(state);
224 self.max_tokens.hash(state);
226 self.cache_type.hash(state);
227 self.backend.hash(state);
228 self.llama_cpp_version_cpu.hash(state);
229 self.llama_cpp_version_vulkan.hash(state);
230 self.llama_cpp_version_rocm.hash(state);
231 self.llama_cpp_version_rocm_lemonade.hash(state);
232 self.llama_cpp_version_cuda.hash(state);
233 self.api_endpoint_enabled.hash(state);
234 self.api_endpoint_port.hash(state);
235 self.spec_type.hash(state);
236 self.draft_tokens.hash(state);
237 self.tags.hash(state);
238 self.web_search_engine.hash(state);
239 self.web_search_engine_url.hash(state);
240 }
241}
242
243impl ModelSettings {
244 pub fn get_active_backend_version(&self) -> Option<&String> {
246 match self.backend {
247 Backend::Cpu => self.llama_cpp_version_cpu.as_ref(),
248 Backend::Vulkan => self.llama_cpp_version_vulkan.as_ref(),
249 Backend::Rocm => self.llama_cpp_version_rocm.as_ref(),
250 Backend::RocmLemonade => self.llama_cpp_version_rocm_lemonade.as_ref(),
251 Backend::Cuda => self.llama_cpp_version_cuda.as_ref(),
252 _ => None,
253 }
254 }
255
256 pub fn get_active_backend_version_display(&self) -> &str {
258 self.get_active_backend_version()
259 .map(|s| s.as_str())
260 .unwrap_or("latest")
261 }
262
263 pub fn set_active_backend_version(&mut self, tag: Option<String>) {
265 match self.backend {
266 Backend::Cpu => self.llama_cpp_version_cpu = tag,
267 Backend::Vulkan => self.llama_cpp_version_vulkan = tag,
268 Backend::Rocm => self.llama_cpp_version_rocm = tag,
269 Backend::RocmLemonade => self.llama_cpp_version_rocm_lemonade = tag,
270 Backend::Cuda => self.llama_cpp_version_cuda = tag,
271 _ => {}
272 }
273 }
274}
275
276pub fn strip_gguf(name: &str) -> &str {
278 name.strip_suffix(".gguf")
279 .or_else(|| name.strip_suffix(".GGUF"))
280 .unwrap_or(name)
281}
282
283pub fn model_filename(name: &str) -> String {
286 let filename = std::path::Path::new(name)
287 .file_name()
288 .map(|f| f.to_string_lossy().to_string())
289 .unwrap_or_else(|| name.to_string());
290 strip_gguf(&filename).to_string()
291}
292
293pub fn clean_host(host: &str) -> String {
297 let host = host.trim();
298 if host.is_empty() {
299 return "127.0.0.1".to_string();
300 }
301 let host = host.split_whitespace().next().unwrap_or(host);
303 if host.contains(':') && !host.starts_with('[') {
304 format!("[{}]", host)
305 } else {
306 host.to_string()
307 }
308}
309
310pub fn format_host(host: &str) -> String {
312 match host {
313 "" | "127.0.0.1" => crate::t!("server.host_label").to_string(),
314 _ => host.to_string(),
315 }
316}
317
318impl From<crate::config::DefaultParams> for ModelSettings {
319 fn from(dp: crate::config::DefaultParams) -> Self {
320 Self {
321 context_length: dp.context_length,
322 threads: dp.threads,
323 threads_batch: dp.threads_batch,
324 batch_size: dp.batch_size,
325 ubatch_size: dp.ubatch_size,
326 parallel: dp.parallel,
327 max_concurrent_predictions: dp.max_concurrent_predictions,
328 uniform_cache: dp.uniform_cache,
329 kv_cache_offload: dp.kv_cache_offload,
330 cache_type_k: dp.cache_type_k,
331 cache_type_v: dp.cache_type_v,
332 keep: dp.keep,
333 swa_full: dp.swa_full,
334 mlock: dp.mlock,
335 mmap: dp.mmap,
336 numa: dp.numa,
337 system_prompt: dp.system_prompt,
338 system_prompt_preset_name: dp.system_prompt_preset_name,
339 web_search_engine: dp.web_search_engine.clone(),
340 web_search_engine_url: dp.web_search_engine_url.clone(),
341
342 gpu_layers_mode: match dp.gpu_layers {
343 n if n < 0 => GpuLayersMode::All,
344 _ => dp.gpu_layers_mode,
345 },
346 split_mode: dp.split_mode,
347 tensor_split: dp.tensor_split,
348 main_gpu: dp.main_gpu,
349 fit: dp.fit,
350 lora: dp.lora,
351 lora_scaled: dp.lora_scaled,
352 rpc: dp.rpc,
353 embedding: dp.embedding,
354 flash_attn: dp.flash_attn,
355 expert_count: dp.expert_count,
356 jinja: dp.jinja,
357 auto_chat_template: dp.auto_chat_template,
358 chat_template: dp.chat_template,
359 chat_template_kwargs: dp.chat_template_kwargs,
360 seed: dp.seed,
361 temperature: dp.temperature,
362 top_k: dp.top_k,
363 top_p: dp.top_p,
364 min_p: dp.min_p,
365 typical_p: dp.typical_p,
366 mirostat: dp.mirostat,
367 mirostat_lr: dp.mirostat_lr,
368 mirostat_ent: dp.mirostat_ent,
369 ignore_eos: dp.ignore_eos,
370 samplers: dp.samplers,
371 repeat_penalty: dp.repeat_penalty,
372 repeat_last_n: dp.repeat_last_n,
373 presence_penalty: dp.presence_penalty,
374 frequency_penalty: dp.frequency_penalty,
375 dry_multiplier: dp.dry_multiplier,
376 dry_base: dp.dry_base,
377 dry_allowed_length: dp.dry_allowed_length,
378 dry_penalty_last_n: dp.dry_penalty_last_n,
379 rope_scaling: dp.rope_scaling,
380 rope_scale: dp.rope_scale,
381 rope_freq_base: dp.rope_freq_base,
382 rope_freq_scale: dp.rope_freq_scale,
383 rope_yarn_enabled: dp.rope_yarn_enabled,
384 host: dp.host,
385 port: dp.port,
386 timeout: dp.timeout,
387 cache_prompt: dp.cache_prompt,
388 cache_reuse: dp.cache_reuse,
389 webui: dp.webui,
390 max_tokens: dp.max_tokens,
391 cache_type: dp.cache_type,
392 backend: dp.backend,
393 llama_cpp_version_cpu: dp.llama_cpp_version_cpu,
394 llama_cpp_version_vulkan: dp.llama_cpp_version_vulkan,
395 llama_cpp_version_rocm: dp.llama_cpp_version_rocm,
396 llama_cpp_version_rocm_lemonade: dp.llama_cpp_version_rocm_lemonade,
397 llama_cpp_version_cuda: dp.llama_cpp_version_cuda,
398 api_endpoint_enabled: dp.api_endpoint_enabled,
399 api_endpoint_port: dp.api_endpoint_port,
400 api_endpoint_key: dp.api_endpoint_key,
401
402 spec_type: dp.spec_type,
403 draft_tokens: dp.draft_tokens,
404 tags: dp.tags,
405 }
406 }
407}
408
409#[derive(
411 Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, Default,
412)]
413pub enum GpuLayersMode {
414 #[default]
415 Auto,
416 Specific(u32),
417 All,
418}
419
420#[derive(Debug, Clone, PartialEq, Eq)]
421pub enum DownloadStatus {
422 Downloading,
423 Pausing,
424 Paused,
425 Complete,
426 Error(String),
427 Cancelled,
428}
429
430#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, Default)]
434pub enum CacheType {
435 #[serde(rename = "f16")]
436 #[default]
437 F16,
438 #[serde(rename = "bf16")]
439 BF16,
440 #[serde(rename = "fq8_0")]
441 Fq8_0,
442 #[serde(rename = "fq4_1")]
443 Fq4_1,
444}
445
446impl std::fmt::Display for CacheType {
447 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
448 match self {
449 CacheType::F16 => write!(f, "f16"),
450 CacheType::BF16 => write!(f, "bf16"),
451 CacheType::Fq8_0 => write!(f, "fq8_0"),
452 CacheType::Fq4_1 => write!(f, "fq4_1"),
453 }
454 }
455}
456
457#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default, Hash)]
459pub enum CacheQuantType {
460 #[serde(rename = "f32")]
461 F32,
462 #[serde(rename = "f16")]
463 #[default]
464 F16,
465 #[serde(rename = "bf16")]
466 BF16,
467 #[serde(rename = "q8_0")]
468 Q8_0,
469 #[serde(rename = "q4_0")]
470 Q4_0,
471 #[serde(rename = "q4_1")]
472 Q4_1,
473 #[serde(rename = "iq4_nl")]
474 Iq4Nl,
475 #[serde(rename = "q5_0")]
476 Q5_0,
477 #[serde(rename = "q5_1")]
478 Q5_1,
479}
480
481pub type CacheTypeK = CacheQuantType;
482pub type CacheTypeV = CacheQuantType;
483
484impl CacheQuantType {
485 pub fn from_u8(n: u8) -> Self {
486 match n {
487 0 => Self::F32,
488 1 => Self::F16,
489 2 => Self::BF16,
490 3 => Self::Q8_0,
491 4 => Self::Q5_1,
492 5 => Self::Q5_0,
493 6 => Self::Q4_1,
494 7 => Self::Q4_0,
495 8 => Self::Iq4Nl,
496 _ => Self::F16,
497 }
498 }
499 pub fn next(&self) -> Self {
500 match self {
501 Self::F32 => Self::F16,
502 Self::F16 => Self::BF16,
503 Self::BF16 => Self::Q8_0,
504 Self::Q8_0 => Self::Q5_1,
505 Self::Q5_1 => Self::Q5_0,
506 Self::Q5_0 => Self::Q4_1,
507 Self::Q4_1 => Self::Q4_0,
508 Self::Q4_0 => Self::Iq4Nl,
509 Self::Iq4Nl => Self::F32,
510 }
511 }
512 pub fn prev(&self) -> Self {
513 match self {
514 Self::F32 => Self::Iq4Nl,
515 Self::F16 => Self::F32,
516 Self::BF16 => Self::F16,
517 Self::Q8_0 => Self::BF16,
518 Self::Q5_1 => Self::Q8_0,
519 Self::Q5_0 => Self::Q5_1,
520 Self::Q4_1 => Self::Q5_0,
521 Self::Q4_0 => Self::Q4_1,
522 Self::Iq4Nl => Self::Q4_0,
523 }
524 }
525}
526
527impl std::fmt::Display for CacheQuantType {
528 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
529 match self {
530 Self::F32 => write!(f, "f32"),
531 Self::F16 => write!(f, "f16"),
532 Self::BF16 => write!(f, "bf16"),
533 Self::Q8_0 => write!(f, "q8_0"),
534 Self::Q4_0 => write!(f, "q4_0"),
535 Self::Q4_1 => write!(f, "q4_1"),
536 Self::Iq4Nl => write!(f, "iq4_nl"),
537 Self::Q5_0 => write!(f, "q5_0"),
538 Self::Q5_1 => write!(f, "q5_1"),
539 }
540 }
541}
542
543impl From<&str> for CacheQuantType {
544 fn from(s: &str) -> Self {
545 match s {
546 "F32" => Self::F32,
547 "F16" => Self::F16,
548 "BF16" => Self::BF16,
549 "Q8_0" => Self::Q8_0,
550 "Q4_0" => Self::Q4_0,
551 "Q4_1" => Self::Q4_1,
552 "Iq4Nl" => Self::Iq4Nl,
553 "Q5_0" => Self::Q5_0,
554 "Q5_1" => Self::Q5_1,
555 _ => Self::F16, }
557 }
558}
559
560#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Hash, Default)]
562pub enum SplitMode {
563 #[serde(rename = "none")]
564 None,
565 #[serde(rename = "layer")]
566 #[default]
567 Layer,
568 #[serde(rename = "row")]
569 Row,
570 #[serde(rename = "tensor")]
571 Tensor,
572}
573
574impl std::fmt::Display for SplitMode {
575 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
576 match self {
577 SplitMode::None => write!(f, "none"),
578 SplitMode::Layer => write!(f, "layer"),
579 SplitMode::Row => write!(f, "row"),
580 SplitMode::Tensor => write!(f, "tensor"),
581 }
582 }
583}
584
585#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Hash, Default)]
587pub enum NumMode {
588 #[serde(rename = "none")]
589 #[default]
590 None,
591 #[serde(rename = "distribute")]
592 Distribute,
593 #[serde(rename = "isolate")]
594 Isolate,
595 #[serde(rename = "numactl")]
596 Numactl,
597}
598
599impl std::fmt::Display for NumMode {
600 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
601 match self {
602 NumMode::None => write!(f, "none"),
603 NumMode::Distribute => write!(f, "distribute"),
604 NumMode::Isolate => write!(f, "isolate"),
605 NumMode::Numactl => write!(f, "numactl"),
606 }
607 }
608}
609
610#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, Default)]
612pub enum RopeScaling {
613 #[serde(rename = "none")]
614 #[default]
615 None,
616 #[serde(rename = "linear")]
617 Linear,
618 #[serde(rename = "yarn")]
619 Yarn,
620}
621
622impl std::fmt::Display for RopeScaling {
623 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
624 match self {
625 RopeScaling::None => write!(f, "none"),
626 RopeScaling::Linear => write!(f, "linear"),
627 RopeScaling::Yarn => write!(f, "yarn"),
628 }
629 }
630}
631
632#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, Default)]
634pub enum Mirostat {
635 #[serde(rename = "0")]
636 #[default]
637 Off,
638 #[serde(rename = "1")]
639 V1,
640 #[serde(rename = "2")]
641 Mirostat2,
642}
643
644impl std::fmt::Display for Mirostat {
645 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
646 match self {
647 Mirostat::Off => write!(f, "off"),
648 Mirostat::V1 => write!(f, "1"),
649 Mirostat::Mirostat2 => write!(f, "2"),
650 }
651 }
652}
653
654#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
657pub struct Samplers(pub String);
658
659impl Default for Samplers {
660 fn default() -> Self {
661 Self("top_k;top_p;temperature".to_string())
662 }
663}
664
665impl std::fmt::Display for Samplers {
666 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
667 write!(f, "{}", self.0)
668 }
669}
670
671#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
673pub enum Backend {
674 #[serde(rename = "cpu")]
675 #[default]
676 Cpu,
677 #[serde(rename = "vulkan")]
678 Vulkan,
679 #[serde(rename = "rocm")]
680 Rocm,
681 #[serde(rename = "rocm_lemonade")]
682 RocmLemonade,
683 #[serde(rename = "cuda")]
684 Cuda,
685 #[serde(rename = "cpu_arm64")]
686 CpuArm64,
687 #[serde(rename = "win_cpu")]
688 CpuWindows,
689 #[serde(rename = "win_vulkan")]
690 VulkanWindows,
691 #[serde(rename = "win_cuda_12_4")]
692 CudaWindows12_4,
693 #[serde(rename = "win_cuda_13_1")]
694 CudaWindows13_1,
695 #[serde(rename = "win_hip")]
696 HipWindows,
697 #[serde(rename = "macos_arm64")]
698 CpuMacosArm64,
699 #[serde(rename = "macos_x64")]
700 CpuMacosX64,
701}
702
703impl Backend {
704 pub fn slug(&self) -> &'static str {
706 match self {
707 Backend::Cpu => "cpu",
708 Backend::Vulkan => "vulkan",
709 Backend::Rocm => "rocm",
710 Backend::RocmLemonade => "rocm-lemonade",
711 Backend::Cuda => "cuda",
712 Backend::CpuArm64 => "cpu-arm64",
713 Backend::CpuWindows => "win-cpu",
714 Backend::VulkanWindows => "win-vulkan",
715 Backend::CudaWindows12_4 => "win-cuda-12.4",
716 Backend::CudaWindows13_1 => "win-cuda-13.1",
717 Backend::HipWindows => "win-hip",
718 Backend::CpuMacosArm64 => "macos-arm64",
719 Backend::CpuMacosX64 => "macos-x64",
720 }
721 }
722
723 pub fn is_linux(self) -> bool {
725 matches!(
726 self,
727 Backend::Cpu
728 | Backend::Vulkan
729 | Backend::Rocm
730 | Backend::RocmLemonade
731 | Backend::Cuda
732 | Backend::CpuArm64
733 )
734 }
735
736 pub fn is_windows(self) -> bool {
738 matches!(
739 self,
740 Backend::CpuWindows
741 | Backend::VulkanWindows
742 | Backend::CudaWindows12_4
743 | Backend::CudaWindows13_1
744 | Backend::HipWindows
745 )
746 }
747
748 pub fn is_macos(self) -> bool {
750 matches!(self, Backend::CpuMacosArm64 | Backend::CpuMacosX64)
751 }
752
753 pub fn parse_backend(s: &str) -> Self {
755 let s = s.to_lowercase();
756 if s.starts_with("vulkan") || s.starts_with("vk") {
757 Backend::Vulkan
758 } else if s.starts_with("rocm") || s.starts_with("ro") {
759 if s.contains("lemonade") {
760 Backend::RocmLemonade
761 } else {
762 Backend::Rocm
763 }
764 } else if s.starts_with("cuda") || s.starts_with("cu") {
765 Backend::Cuda
766 } else {
767 Backend::Cpu }
769 }
770}
771
772impl std::fmt::Display for Backend {
773 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
774 write!(f, "{}", self.slug())
775 }
776}
777
778#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
780pub enum ServerMode {
781 #[serde(rename = "normal")]
782 #[default]
783 Normal,
784 #[serde(rename = "router")]
785 Router,
786 #[serde(rename = "bench_gpu", alias = "bench")]
787 Bench,
788 #[serde(rename = "bench_tune")]
789 BenchTune,
790}
791
792impl std::fmt::Display for ServerMode {
793 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
794 match self {
795 ServerMode::Normal => write!(f, "Normal"),
796 ServerMode::Router => write!(f, "Router (XP!)"),
797 ServerMode::Bench => write!(f, "Bench GPU"),
798 ServerMode::BenchTune => write!(f, "BenchTune"),
799 }
800 }
801}
802
803impl ServerMode {
804 pub fn all() -> &'static [Self] {
805 &[Self::Normal, Self::Router, Self::Bench, Self::BenchTune]
806 }
807}
808
809#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
831pub struct ModelSettings {
832 pub context_length: u32,
835 pub threads: u32,
837 pub threads_batch: u32,
839 pub batch_size: u32,
841 pub ubatch_size: u32,
843 pub parallel: u32,
845 pub max_concurrent_predictions: Option<u32>,
847 pub uniform_cache: bool,
849 pub kv_cache_offload: bool,
851 pub cache_type_k: Option<CacheTypeK>,
853 pub cache_type_v: Option<CacheTypeV>,
855 pub keep: i32,
857 pub swa_full: bool,
859 pub mlock: bool,
861 pub mmap: bool,
863 pub numa: NumMode,
865 pub system_prompt: String,
867 pub system_prompt_preset_name: String,
869 pub web_search_engine: String,
871 pub web_search_engine_url: String,
873
874 pub gpu_layers_mode: GpuLayersMode,
877 pub split_mode: SplitMode,
879 pub tensor_split: String,
881 pub main_gpu: i32,
883 pub fit: bool,
885 pub lora: Option<PathBuf>,
887 pub lora_scaled: Option<(PathBuf, f32)>,
889 pub rpc: String,
891 pub embedding: bool,
893 pub flash_attn: bool,
895 pub expert_count: i32,
897 pub jinja: bool,
899 pub auto_chat_template: bool,
902 pub chat_template: Option<String>,
904 pub chat_template_kwargs: Option<String>,
906
907 pub seed: i32,
910 pub temperature: f32,
912 pub top_k: i32,
914 pub top_p: f32,
916 pub min_p: f32,
918 pub typical_p: f32,
920 pub mirostat: Mirostat,
922 pub mirostat_lr: f32,
924 pub mirostat_ent: f32,
926 pub ignore_eos: bool,
928 pub samplers: Samplers,
930
931 pub repeat_penalty: f32,
934 pub repeat_last_n: i32,
936 pub presence_penalty: Option<f32>,
938 pub frequency_penalty: Option<f32>,
940 pub dry_multiplier: f32,
942 pub dry_base: f32,
944 pub dry_allowed_length: i32,
946 pub dry_penalty_last_n: i32,
948
949 pub rope_scaling: RopeScaling,
952 pub rope_scale: f32,
954 pub rope_freq_base: f32,
956 pub rope_freq_scale: f32,
958 pub rope_yarn_enabled: bool,
960
961 pub host: String,
964 pub port: u16,
966 pub timeout: u32,
968 pub cache_prompt: bool,
970 pub cache_reuse: u32,
972 pub webui: bool,
974
975 pub max_tokens: Option<u32>,
978 pub cache_type: CacheType,
980 pub backend: Backend,
982 pub llama_cpp_version_cpu: Option<String>,
984 pub llama_cpp_version_vulkan: Option<String>,
986 pub llama_cpp_version_rocm: Option<String>,
988 pub llama_cpp_version_rocm_lemonade: Option<String>,
990 pub llama_cpp_version_cuda: Option<String>,
992 pub api_endpoint_enabled: bool,
994 pub api_endpoint_port: u16,
996 pub api_endpoint_key: Option<String>,
998 pub spec_type: String,
1000 pub draft_tokens: u32,
1002 pub tags: Vec<String>,
1004}
1005
1006impl Default for ModelSettings {
1007 fn default() -> Self {
1008 let mut s: Self = crate::config::DefaultParams::default().into();
1009 s.uniform_cache = false;
1011 s.cache_type_k = Some(CacheTypeK::F16);
1012 s.cache_type_v = Some(CacheTypeV::F16);
1013 s.cache_type = CacheType::default();
1014 s.backend = Backend::Cpu;
1015 s.presence_penalty = Some(0.0);
1016 s.frequency_penalty = Some(0.0);
1017 s
1018 }
1019}
1020
1021impl ModelSettings {
1022 pub fn from_config(config: &crate::config::Config) -> Self {
1024 config.default.clone().into()
1025 }
1026}
1027
1028pub const BENCHMARK_PROMPT: &str = "Create Mona Lisa image in ascii art using text, number, symbol, everything possible. this should be the perfect painting.";
1030
1031#[derive(Debug, Clone)]
1033pub struct DiscoveredModel {
1034 pub path: PathBuf,
1035 pub name: String,
1036 pub file_size: u64,
1037 pub display_name: String, pub pipeline_tag: Option<String>,
1039 pub capabilities: Vec<String>,
1040}
1041
1042#[derive(Debug, Clone, Default)]
1044pub struct GgufMetadata {
1045 pub layers: u32,
1046 pub hidden_size: u32,
1047 pub n_ctx_train: u32,
1048 pub n_head: u32,
1049 pub n_kv_head: u32,
1050 pub arch: String,
1051 pub file_type: String,
1052 pub quantization: String,
1053 pub model_parameters: String,
1054 pub domain: String,
1055 pub capabilities: Vec<String>,
1056 pub tokenizer: String,
1057 pub draft_tokens: u32,
1058 pub quality_rank: u8,
1059}
1060
1061impl GgufMetadata {
1062 pub fn from_path(path: &std::path::Path) -> anyhow::Result<Self> {
1063 let path_str = path.to_string_lossy();
1064
1065 let model_data_res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1066 gguf_rs::get_gguf_container(&path_str).map(|mut container| container.decode())
1067 }));
1068
1069 let model_data = match model_data_res {
1070 Ok(Ok(Ok(data))) => data,
1071 Ok(Ok(Err(e))) => return Err(anyhow::anyhow!("Failed to decode GGUF: {}", e)),
1072 Ok(Err(e)) => return Err(anyhow::anyhow!("Failed to get GGUF container: {}", e)),
1073 Err(_) => {
1074 return Err(anyhow::anyhow!(
1075 "GGUF library panicked during parsing/decoding (likely due to unsupported tensor/GGML type)"
1076 ));
1077 }
1078 };
1079
1080 let mut meta = Self::default();
1081
1082 let extract_str = |key: &str| -> String {
1083 model_data
1084 .metadata()
1085 .get(key)
1086 .and_then(|v| v.as_str().map(|s| s.to_string()))
1087 .unwrap_or_default()
1088 };
1089
1090 let extract_num = |key: &str| -> Option<u64> {
1091 model_data.metadata().get(key).and_then(|v| {
1092 v.as_u64()
1093 .or_else(|| v.as_i64().map(|x| x as u64))
1094 .or_else(|| v.as_f64().map(|x| x as u64))
1095 })
1096 };
1097
1098 meta.arch = extract_str("general.architecture");
1099 let prefix = if meta.arch.is_empty() {
1100 "llama"
1101 } else {
1102 &meta.arch
1103 };
1104
1105 let get_num_with_fallback = |suffix: &str| -> u32 {
1106 extract_num(&format!("{}.{}", prefix, suffix))
1107 .or_else(|| {
1108 if prefix != "llama" {
1109 extract_num(&format!("llama.{}", suffix))
1110 } else {
1111 None
1112 }
1113 })
1114 .unwrap_or(0) as u32
1115 };
1116
1117 meta.layers = get_num_with_fallback("block_count");
1118 meta.hidden_size = get_num_with_fallback("embedding_length");
1119 meta.n_ctx_train = get_num_with_fallback("context_length");
1120 meta.n_head = get_num_with_fallback("attention.head_count");
1121 meta.n_kv_head = get_num_with_fallback("attention.head_count_kv");
1122
1123 if meta.arch == "mtp" {
1124 meta.draft_tokens = extract_num("mtp.draft_tokens").unwrap_or(0) as u32;
1125 }
1126
1127 if let Some(v) = extract_num("general.file_type") {
1128 meta.file_type = match v {
1129 0 => "F32".to_string(),
1130 1 => "F16".to_string(),
1131 2 => "Q4_0".to_string(),
1132 3 => "Q4_1".to_string(),
1133 4 => "Q4_1 (F16)".to_string(),
1134 5 => "Q4_1 (F16)".to_string(),
1135 6 => "Q5_0".to_string(),
1136 7 => "Q8_0".to_string(),
1137 8 => "Q5_0".to_string(),
1138 9 => "Q8_1".to_string(),
1139 10 => "Q2_K".to_string(),
1140 11 => "Q3_K_S".to_string(),
1141 12 => "Q3_K_M".to_string(),
1142 13 => "Q3_K_L".to_string(),
1143 14 => "Q4_K_S".to_string(),
1144 15 => "Q4_K_M".to_string(),
1145 16 => "Q5_K_S".to_string(),
1146 17 => "Q5_K_M".to_string(),
1147 18 => "Q6_K".to_string(),
1148 19 => "IQ2_XXS".to_string(),
1149 20 => "IQ2_XS".to_string(),
1150 21 => "IQ3_XXS".to_string(),
1151 22 => "IQ1_S".to_string(),
1152 23 => "IQ4_NL".to_string(),
1153 24 => "IQ3_S".to_string(),
1154 25 => "IQ2_S".to_string(),
1155 26 => "IQ4_XS".to_string(),
1156 27 => "F64".to_string(),
1157 29 => "IQ1_M".to_string(),
1158 30 => "BF16".to_string(),
1159 31 => "IQ2_L".to_string(),
1160 32 => "IQ3_L".to_string(),
1161 33 => "IQ4_M".to_string(),
1162 34 => "TQ1_0".to_string(),
1163 35 => "TQ2_0".to_string(),
1164 39 => "MXFP4".to_string(),
1165 _ => format!("Unknown ({})", v),
1166 };
1167 meta.quality_rank = match v {
1168 0 | 1 | 7 | 9 | 30 | 27 => 4,
1169 6 | 8 | 16 | 17 | 18 => 3,
1170 2 | 3 | 4 | 5 | 14 | 15 | 23 | 26 | 33 | 32 | 24 | 31 => 2,
1171 11 | 12 | 13 | 21 | 10 | 19 | 20 | 22 | 25 | 29 => 1,
1172 _ => 0,
1173 };
1174 }
1175
1176 if let Some(value) = model_data.metadata().get("general.capabilities")
1177 && let Some(arr) = value.as_array()
1178 {
1179 for v in arr {
1180 if let Some(s) = v.as_str() {
1181 meta.capabilities.push(s.to_string());
1182 }
1183 }
1184 }
1185
1186 if model_data
1187 .metadata()
1188 .contains_key("tokenizer.chat_template")
1189 {
1190 meta.capabilities.push("chat".to_string());
1191 }
1192
1193 meta.tokenizer = extract_str("tokenizer.ggml.model");
1194 meta.domain = extract_str("general.domain");
1195 meta.model_parameters = model_data.model_parameters();
1196
1197 Ok(meta)
1198 }
1199}
1200
1201pub fn arch_to_pipeline_tag(arch: &str) -> Option<&'static str> {
1205 match arch {
1206 "llama" | "llama-moe" | "qwen" | "qwen2" | "qwen2moe" | "qwen3" | "qwen3moe"
1208 | "qwen3next" | "qwen35" | "qwen35moe" | "qwen2vl" | "qwen3vl" | "qwen3vlmoe"
1209 | "mistral" | "mistral3" | "mistral4" | "gemma" | "gemma2" | "gemma3" | "gemma3n"
1210 | "gemma4" | "gemma4-assistant" | "gemma-embedding" | "cohere" | "cohere2" | "deepseek"
1211 | "deepseek2" | "deepseek2-ocr" | "deepseek32" | "phi2" | "phi3" | "phimoe" | "phi4"
1212 | "jais" | "jais2" | "stablelm" | "llama4" | "olmo" | "olmo2" | "olmoe" | "sonar"
1213 | "mamba" | "mamba2" | "minicpm" | "minicpm3" | "minicpmo" | "nemo" | "nemotron"
1214 | "nemotron_h" | "nemotron_h_moe" | "chameleon" | "internlm2" | "glm4" | "glm4moe"
1215 | "chatglm" | "exaone" | "exaone4" | "exaone-moe" | "dbrx" | "starcoder" | "starcoder2"
1216 | "baichuan" | "falcon" | "falcon-h1" | "falcon3" | "megrez" | "yandex" | "bailing"
1217 | "bailingmoe" | "bailingmoe2" | "bailing2" | "bailing-think" | "granite"
1218 | "granitehybrid" | "granitemoe" | "hunyuan-dense" | "hunyuan-moe" | "hunyuan_vl"
1219 | "kimi-k2" | "seed_oss" | "grok" | "solar-open" | "gpt-oss" | "smollm3"
1220 | "pangu-embedded" | "rwkv6" | "rwkv6qwen2" | "rwkv7" | "arwkv7" | "mpt" | "gpt-neox"
1221 | "gptj" | "plamo" | "plamo2" | "plamo3" | "orion" | "openchat" | "vicuna" | "zephyr"
1222 | "monarch" | "step35" | "ernie4_5" | "ernie4_5-moe" | "mini-max-m2" | "talkie"
1223 | "apertus" | "arcee" | "arctic" | "jamba" | "lfm2" | "lfm2moe" | "llada" | "llada-moe"
1224 | "maincoder" | "mellum" | "mimo2" | "refact" | "rnd1" | "smallthinker" | "xverse"
1225 | "gpt2" | "codeshell" | "cogvlm" | "deci" | "dots1" | "dream" | "app" | "mamba_ssm" => {
1226 Some("text-generation")
1227 }
1228 "bert" | "nomic-bert" | "nomic-bert-moe" | "roformer" | "deberta-v2" | "deberta"
1230 | "modern-bert" | "neo-bert" | "jina-bert-v2" | "jina-bert-v3" | "eurobert" | "t5"
1231 | "t5encoder" => Some("feature-extraction"),
1232 "resnet" | "vit" | "segformer" | "clip" => Some("image-classification"),
1234 "whisper" => Some("automatic-speech-recognition"),
1236 "bark" | "wavtokenizer-dec" => Some("text-to-audio"),
1237 "speech-to-text" => Some("speech-to-text"),
1239 "text-to-speech" => Some("text-to-speech"),
1240 "stable-diffusion" => Some("text-to-image"),
1242 _ => None,
1243 }
1244}
1245
1246pub fn arch_to_chat_template(arch: &str) -> Option<&'static str> {
1251 match arch {
1252 "llama" | "llama-moe" => Some("llama3"),
1254 "llama4" => Some("llama4"),
1255 "mistral" | "mistral3" | "mistral4" => Some("mistral-v1"),
1257 "qwen" | "qwen2" | "qwen2moe" | "qwen3" | "qwen3moe" | "qwen3next" | "qwen35"
1259 | "qwen35moe" | "qwen2vl" | "qwen3vl" | "qwen3vlmoe" => Some("chatml"),
1260 "gemma" | "gemma2" | "gemma3" | "gemma3n" | "gemma4" | "gemma4-assistant" => Some("gemma"),
1262 "phi2" | "phi3" | "phimoe" => Some("phi3"),
1264 "phi4" => Some("phi4"),
1265 "cohere" | "cohere2" => Some("command-r"),
1267 "deepseek" | "deepseek2" | "deepseek2-ocr" => Some("deepseek"),
1269 "deepseek32" => Some("deepseek3"),
1270 "internlm2" | "glm4" | "glm4moe" | "chatglm" => Some("chatglm4"),
1272 "exaone" => Some("exaone3"),
1274 "exaone4" => Some("exaone4"),
1275 "exaone-moe" => Some("exaone-moe"),
1276 "minicpm" | "minicpm3" | "minicpmo" => Some("minicpm"),
1278 "falcon" | "falcon-h1" | "falcon3" => Some("chatml"),
1280 "rwkv6" | "rwkv6qwen2" | "rwkv7" | "arwkv7" => Some("rwkv-world"),
1282 "granite" | "granitehybrid" | "granitemoe" => Some("granite"),
1284 "hunyuan-dense" | "hunyuan-moe" | "hunyuan_vl" => Some("hunyuan-dense"),
1286 "olmo" | "olmo2" | "olmoe" | "sonar" | "mamba" | "mamba2" | "mamba_ssm" | "dbrx"
1288 | "starcoder" | "starcoder2" | "baichuan" | "gpt-neox" | "gptj" | "mpt" | "jais"
1289 | "jais2" | "stablelm" | "chameleon" | "nemo" | "nemotron" | "nemotron_h"
1290 | "nemotron_h_moe" | "plamo" | "plamo2" | "plamo3" | "ernie4_5" | "ernie4_5-moe"
1291 | "mini-max-m2" | "talkie" | "apertus" | "arcee" | "arctic" | "jamba" | "lfm2"
1292 | "lfm2moe" | "llada" | "llada-moe" | "maincoder" | "mellum" | "mimo2" | "refact"
1293 | "rnd1" | "smallthinker" | "xverse" | "gpt2" | "codeshell" | "cogvlm" | "deci"
1294 | "dots1" | "dream" | "app" | "step35" | "smollm3" => Some("chatml"),
1295 "megrez" => Some("megrez"),
1297 "yandex" => Some("yandex"),
1298 "bailing" | "bailingmoe" | "bailingmoe2" | "bailing2" | "bailing-think" => Some("bailing"),
1299 "kimi-k2" => Some("kimi-k2"),
1300 "seed_oss" => Some("seed_oss"),
1301 "grok" => Some("grok-2"),
1302 "solar-open" => Some("solar-open"),
1303 "gpt-oss" => Some("gpt-oss"),
1304 "pangu-embedded" => Some("pangu-embedded"),
1305 "gigachat" => Some("gigachat"),
1306 _ => None,
1311 }
1312}
1313
1314pub fn get_available_chat_templates() -> Vec<String> {
1318 let mut templates = vec!["Auto (detect)".to_string()];
1319 let mut seen = std::collections::HashSet::new();
1320 let archs = vec![
1322 "llama",
1323 "llama-moe",
1324 "llama4",
1325 "mistral",
1326 "mistral3",
1327 "mistral4",
1328 "qwen",
1329 "qwen2",
1330 "qwen2moe",
1331 "qwen3",
1332 "qwen3moe",
1333 "qwen3next",
1334 "qwen35",
1335 "qwen35moe",
1336 "qwen2vl",
1337 "qwen3vl",
1338 "qwen3vlmoe",
1339 "gemma",
1340 "gemma2",
1341 "gemma3",
1342 "gemma3n",
1343 "gemma4",
1344 "gemma4-assistant",
1345 "phi2",
1346 "phi3",
1347 "phimoe",
1348 "phi4",
1349 "cohere",
1350 "cohere2",
1351 "deepseek",
1352 "deepseek2",
1353 "deepseek2-ocr",
1354 "deepseek32",
1355 "internlm2",
1356 "glm4",
1357 "glm4moe",
1358 "chatglm",
1359 "exaone",
1360 "exaone4",
1361 "exaone-moe",
1362 "minicpm",
1363 "minicpm3",
1364 "minicpmo",
1365 "falcon",
1366 "falcon-h1",
1367 "falcon3",
1368 "rwkv6",
1369 "rwkv6qwen2",
1370 "rwkv7",
1371 "arwkv7",
1372 "granite",
1373 "granitehybrid",
1374 "granitemoe",
1375 "hunyuan-dense",
1376 "hunyuan-moe",
1377 "hunyuan_vl",
1378 "olmo",
1379 "olmo2",
1380 "olmoe",
1381 "sonar",
1382 "mamba",
1383 "mamba2",
1384 "mamba_ssm",
1385 "dbrx",
1386 "starcoder",
1387 "starcoder2",
1388 "baichuan",
1389 "gpt-neox",
1390 "gptj",
1391 "mpt",
1392 "jais",
1393 "jais2",
1394 "stablelm",
1395 "chameleon",
1396 "nemo",
1397 "nemotron",
1398 "nemotron_h",
1399 "nemotron_h_moe",
1400 "plamo",
1401 "plamo2",
1402 "plamo3",
1403 "ernie4_5",
1404 "ernie4_5-moe",
1405 "mini-max-m2",
1406 "talkie",
1407 "apertus",
1408 "arcee",
1409 "arctic",
1410 "jamba",
1411 "lfm2",
1412 "lfm2moe",
1413 "llada",
1414 "llada-moe",
1415 "maincoder",
1416 "mellum",
1417 "mimo2",
1418 "refact",
1419 "rnd1",
1420 "smallthinker",
1421 "xverse",
1422 "gpt2",
1423 "codeshell",
1424 "cogvlm",
1425 "deci",
1426 "dots1",
1427 "dream",
1428 "app",
1429 "step35",
1430 "smollm3",
1431 "megrez",
1432 "yandex",
1433 "bailing",
1434 "bailingmoe",
1435 "bailingmoe2",
1436 "bailing2",
1437 "bailing-think",
1438 "kimi-k2",
1439 "seed_oss",
1440 "grok",
1441 "solar-open",
1442 "gpt-oss",
1443 "pangu-embedded",
1444 "gigachat",
1445 "stable-diffusion",
1446 ];
1447 for arch in &archs {
1448 if let Some(template) = arch_to_chat_template(arch)
1449 && seen.insert(template.to_string())
1450 {
1451 templates.push(template.to_string());
1452 }
1453 }
1454 templates.push("Select a Template file...".to_string());
1455 templates.push("None".to_string());
1456 templates
1457}
1458
1459#[derive(Debug, Clone)]
1461pub struct ServerMetrics {
1462 pub loaded: bool,
1463 pub tps: f64,
1464 pub prompt_tps: f64,
1465 pub cpu_usage: f64,
1466 pub gpu_mem_used: u64,
1467 pub gpu_mem_total: u64,
1468 pub ram_used: u64,
1469 pub ctx_used: u32,
1470 pub ctx_max: u32,
1471 pub total_vram_used: u64,
1473 pub decoded_tokens: u64,
1475 pub gen_tps: f64,
1477 pub latency_per_token_ms: f64,
1479 pub prompt_latency_ms: f64,
1481 pub prompt_tokens: u64,
1483 pub prompt_progress: f64,
1485 pub prompt_elapsed_ms: f64,
1487 pub prompt_tps_eval: f64,
1489}
1490
1491#[derive(Debug, Clone)]
1493pub struct GPUBuffer {
1494 pub device: String,
1495 pub buffer_size_mib: f64,
1496}
1497
1498#[derive(Debug, Clone, Default)]
1500pub struct LoadProgress {
1501 pub layers_total: Option<u32>,
1503 pub layers_loaded: Option<u32>,
1505 pub tensors_total: Option<u32>,
1507 pub tensors_loaded: u32,
1509 pub model_loaded: u32,
1511 pub model_total: Option<u32>,
1513 pub buffers: Vec<GPUBuffer>,
1515}
1516
1517impl Default for ServerMetrics {
1518 fn default() -> Self {
1519 Self {
1520 loaded: false,
1521 tps: 0.0,
1522 prompt_tps: 0.0,
1523 cpu_usage: 0.0,
1524 gpu_mem_used: 0,
1525 gpu_mem_total: 0,
1526 ram_used: 0,
1527 ctx_used: 0,
1528 ctx_max: 0,
1529 total_vram_used: 0,
1530 decoded_tokens: 0,
1531 gen_tps: 0.0,
1532 latency_per_token_ms: 0.0,
1533 prompt_latency_ms: 0.0,
1534 prompt_tokens: 0,
1535 prompt_progress: 0.0,
1536 prompt_elapsed_ms: 0.0,
1537 prompt_tps_eval: 0.0,
1538 }
1539 }
1540}
1541
1542#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1544pub struct WsMetrics {
1545 pub model_name: String,
1546 pub loaded: bool,
1547 pub state: String,
1548 pub tps: f64,
1549 pub prompt_tps: f64,
1550 pub ctx_used: u32,
1551 pub ctx_max: u32,
1552 pub cpu_usage: f64,
1553 pub gpu_mem_used: u64,
1554 pub gpu_mem_total: u64,
1555 pub ram_used: u64,
1556 pub latency_per_token_ms: f64,
1557 pub decoded_tokens: u64,
1558 pub gen_tps: f64,
1559 pub prompt_tokens: u64,
1560 pub prompt_progress: f64,
1561 pub prompt_elapsed_ms: f64,
1562 pub prompt_tps_eval: f64,
1563 pub timestamp: u64,
1564 pub cmd_display: Option<String>,
1566 pub threads: u32,
1568 pub threads_batch: u32,
1569 pub context_length: u32,
1570 pub ubatch_size: u32,
1571 pub batch_size: u32,
1572 pub temperature: f32,
1573 pub top_k: u32,
1574 pub top_p: f32,
1575 pub min_p: f32,
1576 pub typical_p: f32,
1577 pub seed: i32,
1578 pub repeat_penalty: f32,
1579 pub repeat_last_n: i32,
1580 pub presence_penalty: Option<f32>,
1581 pub frequency_penalty: Option<f32>,
1582 pub mirostat: Option<u32>,
1583 pub mirostat_lr: Option<f32>,
1584 pub mirostat_ent: Option<f32>,
1585 pub max_tokens: Option<u32>,
1586 pub flash_attn: bool,
1587 pub kv_cache_offload: bool,
1588 pub cache_type_k: Option<String>,
1589 pub cache_type_v: Option<String>,
1590 pub uniform_cache: bool,
1591 pub mlock: bool,
1592 pub mmap: bool,
1593 pub embedding: bool,
1594 pub jinja: bool,
1595 pub ignore_eos: bool,
1596 pub samplers: String,
1597 pub expert_count: i32,
1598 pub gpu_layers: String,
1599 pub backend: String,
1600 pub llama_cpp_version: String,
1601 pub spec_type: String,
1602 pub draft_tokens: u32,
1603}
1604
1605impl WsMetrics {
1606 pub fn from_metrics(
1607 metrics: &ServerMetrics,
1608 model_name: &str,
1609 state: &str,
1610 settings: &crate::models::ModelSettings,
1611 cmd_display: Option<&str>,
1612 ) -> Self {
1613 use std::time::{SystemTime, UNIX_EPOCH};
1614 let timestamp = SystemTime::now()
1615 .duration_since(UNIX_EPOCH)
1616 .map(|d| d.as_secs())
1617 .unwrap_or(0);
1618 let gpu_layers = match settings.gpu_layers_mode {
1619 crate::models::GpuLayersMode::Auto => "Auto".to_string(),
1620 crate::models::GpuLayersMode::Specific(n) => n.to_string(),
1621 crate::models::GpuLayersMode::All => "All".to_string(),
1622 };
1623 Self {
1624 model_name: model_name.to_string(),
1625 loaded: metrics.loaded,
1626 state: state.to_string(),
1627 tps: metrics.tps,
1628 prompt_tps: metrics.prompt_tps,
1629 ctx_used: metrics.ctx_used,
1630 ctx_max: metrics.ctx_max,
1631 cpu_usage: metrics.cpu_usage,
1632 gpu_mem_used: metrics.gpu_mem_used,
1633 gpu_mem_total: metrics.gpu_mem_total,
1634 ram_used: metrics.ram_used,
1635 latency_per_token_ms: metrics.latency_per_token_ms,
1636 decoded_tokens: metrics.decoded_tokens,
1637 gen_tps: metrics.gen_tps,
1638 prompt_tokens: metrics.prompt_tokens,
1639 prompt_progress: metrics.prompt_progress,
1640 prompt_elapsed_ms: metrics.prompt_elapsed_ms,
1641 prompt_tps_eval: metrics.prompt_tps_eval,
1642 timestamp,
1643 cmd_display: cmd_display.map(String::from),
1644 threads: settings.threads,
1645 threads_batch: settings.threads_batch,
1646 context_length: settings.context_length,
1647 ubatch_size: settings.ubatch_size,
1648 batch_size: settings.batch_size,
1649 temperature: settings.temperature,
1650 top_k: (settings.top_k.max(0)) as u32,
1651 top_p: settings.top_p,
1652 min_p: settings.min_p,
1653 typical_p: settings.typical_p,
1654 seed: settings.seed,
1655 repeat_penalty: settings.repeat_penalty,
1656 repeat_last_n: settings.repeat_last_n,
1657 presence_penalty: settings.presence_penalty,
1658 frequency_penalty: settings.frequency_penalty,
1659 mirostat: Some(match settings.mirostat {
1660 crate::models::Mirostat::Off => 0,
1661 crate::models::Mirostat::V1 => 1,
1662 crate::models::Mirostat::Mirostat2 => 2,
1663 }),
1664 mirostat_lr: Some(settings.mirostat_lr),
1665 mirostat_ent: Some(settings.mirostat_ent),
1666 max_tokens: settings.max_tokens,
1667 flash_attn: settings.flash_attn,
1668 kv_cache_offload: settings.kv_cache_offload,
1669 cache_type_k: settings.cache_type_k.map(|k| k.to_string()),
1670 cache_type_v: settings.cache_type_v.map(|k| k.to_string()),
1671 uniform_cache: settings.uniform_cache,
1672 mlock: settings.mlock,
1673 mmap: settings.mmap,
1674 embedding: settings.embedding,
1675 jinja: settings.jinja,
1676 ignore_eos: settings.ignore_eos,
1677 samplers: settings.samplers.to_string(),
1678 expert_count: settings.expert_count,
1679 gpu_layers,
1680 backend: settings.backend.to_string(),
1681 llama_cpp_version: settings.get_active_backend_version_display().to_string(),
1682 spec_type: settings.spec_type.clone(),
1683 draft_tokens: settings.draft_tokens,
1684 }
1685 }
1686}
1687
1688pub fn estimate_vram_mib(
1700 model_mib: u64,
1701 settings: &ModelSettings,
1702 total_layers: u32,
1703 hidden_size_opt: Option<u32>,
1704 n_head_opt: Option<u32>,
1705 n_kv_head_opt: Option<u32>,
1706 gpu_mem_total_mib: u64,
1707) -> u64 {
1708 let model_mib_f = model_mib as f64;
1709
1710 let gpu_layers = match settings.gpu_layers_mode {
1712 GpuLayersMode::Auto => {
1713 if total_layers > 0 {
1715 (total_layers as f64 * 0.6) as u32
1716 } else {
1717 20
1718 }
1719 }
1720 GpuLayersMode::Specific(n) => {
1721 if total_layers > 0 {
1722 n.min(total_layers)
1723 } else {
1724 n
1725 }
1726 }
1727 GpuLayersMode::All => {
1728 if total_layers > 0 {
1729 total_layers
1730 } else {
1731 32
1732 }
1733 }
1734 };
1735
1736 let model_vram = if total_layers > 0 && gpu_layers > 0 {
1738 model_mib_f * (gpu_layers as f64 / total_layers as f64).min(1.0)
1739 } else if gpu_layers > 0 {
1740 model_mib_f
1741 } else {
1742 0.0
1743 };
1744
1745 if matches!(settings.gpu_layers_mode, GpuLayersMode::Specific(0)) {
1746 return 0; }
1748
1749 let hidden_size = match hidden_size_opt {
1752 Some(h) => h as f64,
1753 None => {
1754 let params_est = model_mib_f / 550.0;
1755 (1024.0 * params_est.sqrt().max(1.0) * 1.5).max(512.0)
1756 }
1757 };
1758
1759 let gqa_ratio = match (n_head_opt, n_kv_head_opt) {
1765 (Some(n_head), Some(n_kv_head)) if n_head > 0 => n_kv_head as f64 / n_head as f64,
1766 _ => 1.0, };
1768
1769 let flash_attn_factor = if settings.flash_attn { 0.5 } else { 1.0 };
1772
1773 let uniform_cache_factor = if settings.uniform_cache {
1775 1.0 / settings.parallel as f64
1776 } else {
1777 1.0
1778 };
1779
1780 let effective_ctx = settings.context_length as f64 * settings.rope_scale as f64;
1782
1783 let kv_mib = (2.0
1790 * hidden_size
1791 * effective_ctx
1792 * total_layers as f64
1793 * gqa_ratio
1794 * gpu_layers as f64
1795 / total_layers as f64 * flash_attn_factor
1797 * uniform_cache_factor
1798 * kv_quant_bytes(
1799 settings.cache_type_k.unwrap_or(CacheTypeK::F16),
1800 settings.cache_type_v.unwrap_or(CacheTypeV::F16)
1801 ))
1802 / (1024.0 * 1024.0);
1803
1804 let activation_mib = (settings.batch_size as f64 * hidden_size * 8.0) / (1024.0 * 1024.0);
1807
1808 let fixed_overhead = if gpu_mem_total_mib > 0 {
1811 gpu_mem_total_mib as f64 * 0.038
1812 } else {
1813 500.0
1814 };
1815
1816 let total_mib = model_vram + kv_mib + activation_mib + fixed_overhead + 550.0;
1817
1818 total_mib.ceil() as u64
1819}
1820
1821fn kv_quant_bytes(k_type: CacheQuantType, v_type: CacheQuantType) -> f64 {
1826 let get_bytes = |t: CacheQuantType| match t {
1827 CacheQuantType::F32 => 4.0,
1828 CacheQuantType::F16 | CacheQuantType::BF16 => 2.0,
1829 CacheQuantType::Q8_0 => 1.0,
1830 CacheQuantType::Q5_0 | CacheQuantType::Q5_1 => 0.625, CacheQuantType::Q4_0 | CacheQuantType::Q4_1 | CacheQuantType::Iq4Nl => 0.5, };
1833 (get_bytes(k_type) + get_bytes(v_type)) / 2.0
1834}
1835
1836impl ModelSettings {
1837 pub fn is_dirty(&self, other: &Self) -> bool {
1840 self != other
1841 }
1842}
1843
1844#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1846pub struct BenchTuneConfig {
1847 pub model_path: PathBuf,
1848 pub num_iterations: u32,
1849 pub prompt: String,
1850 pub params_to_test: Vec<BenchTuneParam>,
1851 pub test_duration: Duration,
1852 pub bench_mode: BenchTuneMode,
1853 pub n_predict: u32,
1854 pub chat_template_kwargs: Option<String>,
1855 pub test_timeout: Duration,
1856}
1857
1858#[derive(Debug, Clone, Serialize, Deserialize)]
1859pub struct BenchTuneParam {
1860 pub name: String,
1861 pub min: f64,
1862 pub max: f64,
1863 pub step: f64,
1864 pub enabled: bool,
1865 pub variants: Vec<String>,
1866}
1867
1868impl PartialEq for BenchTuneParam {
1869 fn eq(&self, other: &Self) -> bool {
1870 self.name == other.name
1871 && self.min.to_bits() == other.min.to_bits()
1872 && self.max.to_bits() == other.max.to_bits()
1873 && self.step.to_bits() == other.step.to_bits()
1874 && self.enabled == other.enabled
1875 && self.variants == other.variants
1876 }
1877}
1878impl Eq for BenchTuneParam {}
1879
1880#[derive(Debug, Clone, Serialize, Deserialize)]
1881pub struct BenchTuneParamValue {
1882 pub temperature: Option<f64>,
1883 pub top_p: Option<f64>,
1884 pub top_k: Option<i64>,
1885 pub repeat_penalty: Option<f64>,
1886 pub context_length: Option<u32>,
1887 pub batch_size: Option<u32>,
1888 pub flash_attn: Option<bool>,
1889 pub threads: Option<u32>,
1890 pub expert_count: Option<i32>,
1891 pub spec_type: Option<String>,
1892 pub draft_tokens: Option<u32>,
1893}
1894
1895impl PartialEq for BenchTuneParamValue {
1896 fn eq(&self, other: &Self) -> bool {
1897 self.temperature.map(|v| v.to_bits()) == other.temperature.map(|v| v.to_bits())
1898 && self.top_p.map(|v| v.to_bits()) == other.top_p.map(|v| v.to_bits())
1899 && self.top_k == other.top_k
1900 && self.repeat_penalty.map(|v| v.to_bits()) == other.repeat_penalty.map(|v| v.to_bits())
1901 && self.context_length == other.context_length
1902 && self.batch_size == other.batch_size
1903 && self.flash_attn == other.flash_attn
1904 && self.threads == other.threads
1905 && self.expert_count == other.expert_count
1906 && self.spec_type == other.spec_type
1907 && self.draft_tokens == other.draft_tokens
1908 }
1909}
1910impl Eq for BenchTuneParamValue {}
1911
1912#[derive(Debug, Clone, Serialize, Deserialize)]
1913pub struct BenchTuneResult {
1914 pub params: BenchTuneParamValue,
1915 pub metrics: BenchTuneMetrics,
1916 pub outputs: Vec<String>,
1917 pub per_iteration_metrics: Vec<BenchTuneMetrics>,
1918 pub base_settings: Option<ModelSettings>,
1919 pub server_command: Option<String>,
1920}
1921
1922#[derive(Debug, Clone, Serialize, Deserialize)]
1923pub struct BenchTuneMetrics {
1924 pub prompt_tps: f64,
1925 pub generation_tps: f64,
1926 pub combined_tps: f64,
1927 pub latency_per_token: f64,
1928 pub prompt_processing_time: f64,
1929}
1930
1931#[derive(Debug, Clone, Serialize, Deserialize)]
1932pub enum BenchTuneStatus {
1933 Running {
1934 current: usize,
1935 total: usize,
1936 progress: f32,
1937 current_params: BenchTuneParamValue,
1938 },
1939 Completed {
1940 total_tests: usize,
1941 successful_tests: usize,
1942 elapsed: Duration,
1943 },
1944 PartiallyCompleted {
1945 total_tests: usize,
1946 successful_tests: usize,
1947 failed_tests: usize,
1948 elapsed: Duration,
1949 },
1950 Cancelled {
1951 total_tests: usize,
1952 successful_tests: usize,
1953 failed_tests: usize,
1954 elapsed: Duration,
1955 },
1956 Error {
1957 error: String,
1958 },
1959}
1960
1961#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
1962pub enum BenchTuneMode {
1963 RuntimeOnly,
1965 #[default]
1967 Full,
1968}
1969
1970#[derive(Debug, Clone)]
1972pub enum BenchTuneProgress {
1973 Running {
1975 current: usize,
1976 total: usize,
1977 progress: f32,
1978 current_params: BenchTuneParamValue,
1979 },
1980 Completed {
1982 total_tests: usize,
1983 successful_tests: usize,
1984 elapsed: Duration,
1985 },
1986 PartiallyCompleted {
1988 total_tests: usize,
1989 successful_tests: usize,
1990 failed_tests: usize,
1991 elapsed: Duration,
1992 },
1993 Cancelled {
1995 total_tests: usize,
1996 successful_tests: usize,
1997 failed_tests: usize,
1998 elapsed: Duration,
1999 },
2000 Error { error: String },
2002}
2003
2004impl BenchTuneProgress {
2005 pub fn from_status(status: &BenchTuneStatus) -> Option<Self> {
2006 match status {
2007 BenchTuneStatus::Running {
2008 current,
2009 total,
2010 progress,
2011 current_params,
2012 } => Some(BenchTuneProgress::Running {
2013 current: *current,
2014 total: *total,
2015 progress: *progress,
2016 current_params: current_params.clone(),
2017 }),
2018 BenchTuneStatus::Completed {
2019 total_tests,
2020 successful_tests,
2021 elapsed,
2022 } => Some(BenchTuneProgress::Completed {
2023 total_tests: *total_tests,
2024 successful_tests: *successful_tests,
2025 elapsed: *elapsed,
2026 }),
2027 BenchTuneStatus::PartiallyCompleted {
2028 total_tests,
2029 successful_tests,
2030 failed_tests,
2031 elapsed,
2032 } => Some(BenchTuneProgress::PartiallyCompleted {
2033 total_tests: *total_tests,
2034 successful_tests: *successful_tests,
2035 failed_tests: *failed_tests,
2036 elapsed: *elapsed,
2037 }),
2038 BenchTuneStatus::Cancelled {
2039 total_tests,
2040 successful_tests,
2041 failed_tests,
2042 elapsed,
2043 } => Some(BenchTuneProgress::Cancelled {
2044 total_tests: *total_tests,
2045 successful_tests: *successful_tests,
2046 failed_tests: *failed_tests,
2047 elapsed: *elapsed,
2048 }),
2049 BenchTuneStatus::Error { error } => Some(BenchTuneProgress::Error {
2050 error: error.clone(),
2051 }),
2052 }
2053 }
2054}
2055
2056impl BenchTuneConfig {
2057 pub fn new(model_path: PathBuf, num_iterations: u32, prompt: String) -> Self {
2058 Self {
2059 model_path,
2060 num_iterations,
2061 prompt,
2062 params_to_test: vec![
2063 BenchTuneParam {
2064 name: "temperature".to_string(),
2065 min: 0.4,
2066 max: 1.0,
2067 step: 0.1,
2068 enabled: false,
2069 variants: vec![],
2070 },
2071 BenchTuneParam {
2072 name: "top_p".to_string(),
2073 min: 0.8,
2074 max: 1.0,
2075 step: 0.1,
2076 enabled: false,
2077 variants: vec![],
2078 },
2079 BenchTuneParam {
2080 name: "top_k".to_string(),
2081 min: 10.0,
2082 max: 40.0,
2083 step: 5.0,
2084 enabled: false,
2085 variants: vec![],
2086 },
2087 BenchTuneParam {
2088 name: "repeat_penalty".to_string(),
2089 min: 1.0,
2090 max: 1.5,
2091 step: 0.1,
2092 enabled: false,
2093 variants: vec![],
2094 },
2095 BenchTuneParam {
2096 name: "flash_attn".to_string(),
2097 min: 0.0,
2098 max: 1.0,
2099 step: 1.0,
2100 enabled: false,
2101 variants: vec![],
2102 },
2103 BenchTuneParam {
2104 name: "threads".to_string(),
2105 min: 4.0,
2106 max: 16.0,
2107 step: 4.0,
2108 enabled: false,
2109 variants: vec![],
2110 },
2111 BenchTuneParam {
2112 name: "batch_size".to_string(),
2113 min: 512.0,
2114 max: 2048.0,
2115 step: 512.0,
2116 enabled: false,
2117 variants: vec![],
2118 },
2119 BenchTuneParam {
2120 name: "expert_count".to_string(),
2121 min: -1.0,
2122 max: 4.0,
2123 step: 1.0,
2124 enabled: false,
2125 variants: vec![],
2126 },
2127 BenchTuneParam {
2128 name: "spec_type".to_string(),
2129 min: 0.0,
2130 max: 8.0,
2131 step: 1.0,
2132 enabled: false,
2133 variants: vec![
2134 "Off".to_string(),
2135 "draft-mtp".to_string(),
2136 "draft-simple".to_string(),
2137 "draft-eagle3".to_string(),
2138 "ngram-simple".to_string(),
2139 "ngram-map-k".to_string(),
2140 "ngram-map-k4v".to_string(),
2141 "ngram-mod".to_string(),
2142 "ngram-cache".to_string(),
2143 ],
2144 },
2145 BenchTuneParam {
2146 name: "draft_tokens".to_string(),
2147 min: 0.0,
2148 max: 8.0,
2149 step: 1.0,
2150 enabled: false,
2151 variants: vec![],
2152 },
2153 ],
2154 test_duration: Duration::from_secs(30),
2155 bench_mode: BenchTuneMode::default(),
2156 n_predict: 512,
2157 chat_template_kwargs: Some(r#"{"enable_thinking": false}"#.to_string()),
2158 test_timeout: Duration::from_secs(60),
2159 }
2160 }
2161
2162 pub fn generate_combinations(&self) -> Vec<BenchTuneParamValue> {
2164 let mut temp_values = vec![None];
2165 let mut top_p_values = vec![None];
2166 let mut top_k_values = vec![None];
2167 let mut repeat_penalty_values = vec![None];
2168 let mut flash_attn_values = vec![None];
2169 let mut threads_values = vec![None];
2170 let mut batch_size_values = vec![None];
2171 let mut expert_count_values = vec![None];
2172 let mut selected_spec_type = None;
2173 if let Some(p) = self.params_to_test.iter().find(|p| p.name == "spec_type") {
2174 let base_idx = (p.min as usize).min(p.variants.len().saturating_sub(1));
2175 let val = &p.variants[base_idx];
2176 if val != "Off" {
2177 selected_spec_type = Some(val.clone());
2178 }
2179 }
2180 let mut spec_type_values = vec![selected_spec_type];
2181 let mut draft_tokens_values = vec![None];
2182
2183 let spec_type_options = vec![
2184 "Off".to_string(),
2185 "draft-mtp".to_string(),
2186 "draft-simple".to_string(),
2187 "draft-eagle3".to_string(),
2188 "ngram-simple".to_string(),
2189 "ngram-map-k".to_string(),
2190 "ngram-map-k4v".to_string(),
2191 "ngram-mod".to_string(),
2192 "ngram-cache".to_string(),
2193 ];
2194
2195 for p in &self.params_to_test {
2196 if !p.enabled {
2197 continue;
2198 }
2199
2200 let vals: Vec<f64> = {
2201 let step_count = ((p.max - p.min) / p.step).ceil() as usize;
2202 (0..=step_count)
2203 .map(|i| (p.min + (i as f64 * p.step)).min(p.max))
2204 .collect()
2205 };
2206
2207 match p.name.as_str() {
2208 "temperature" => temp_values = vals.into_iter().map(Some).collect(),
2209 "top_p" => top_p_values = vals.into_iter().map(Some).collect(),
2210 "top_k" => top_k_values = vals.into_iter().map(|v| Some(v as i64)).collect(),
2211 "repeat_penalty" => repeat_penalty_values = vals.into_iter().map(Some).collect(),
2212 "flash_attn" => {
2213 flash_attn_values = vals.into_iter().map(|v| Some(v >= 0.5)).collect()
2214 }
2215 "threads" => threads_values = vals.into_iter().map(|v| Some(v as u32)).collect(),
2216 "batch_size" => {
2217 batch_size_values = vals.into_iter().map(|v| Some(v as u32)).collect()
2218 }
2219 "expert_count" => {
2220 expert_count_values = vals.into_iter().map(|v| Some(v as i32)).collect()
2221 }
2222 "spec_type" => {
2223 if !p.variants.is_empty() {
2224 spec_type_values = p.variants.clone().into_iter().map(Some).collect();
2225 } else {
2226 let step_count = ((p.max - p.min) / p.step).ceil() as usize;
2227 spec_type_values = (0..=step_count)
2228 .map(|i| {
2229 let idx = i.min(spec_type_options.len() - 1);
2230 Some(spec_type_options[idx].clone())
2231 })
2232 .collect()
2233 }
2234 }
2235 "draft_tokens" => {
2236 draft_tokens_values = vals.into_iter().map(|v| Some(v as u32)).collect()
2237 }
2238 _ => {}
2239 }
2240 }
2241
2242 let mut combinations = Vec::new();
2243 for &temp in &temp_values {
2244 for &top_p in &top_p_values {
2245 for &top_k in &top_k_values {
2246 for &rp in &repeat_penalty_values {
2247 for &fa in &flash_attn_values {
2248 for &th in &threads_values {
2249 for &bs in &batch_size_values {
2250 for &ec in &expert_count_values {
2251 for st in &spec_type_values {
2252 for &dt in &draft_tokens_values {
2253 combinations.push(BenchTuneParamValue {
2254 temperature: temp,
2255 top_p,
2256 top_k,
2257 repeat_penalty: rp,
2258 context_length: None,
2259 batch_size: bs,
2260 flash_attn: fa,
2261 threads: th,
2262 expert_count: ec,
2263 spec_type: st.clone(),
2264 draft_tokens: dt,
2265 });
2266 }
2267 }
2268 }
2269 }
2270 }
2271 }
2272 }
2273 }
2274 }
2275 }
2276
2277 combinations
2278 }
2279
2280 pub fn get_total_tests_count(&self) -> usize {
2282 self.generate_combinations().len()
2283 }
2284
2285 pub fn get_num_combinations(&self) -> usize {
2287 let mut count: u64 = 1;
2288 for p in &self.params_to_test {
2289 if !p.enabled {
2290 continue;
2291 }
2292 let vals = if p.name == "flash_attn" {
2293 2 } else if !p.variants.is_empty() {
2295 p.variants.len()
2296 } else if p.name == "spec_type" {
2297 let step_count = ((p.max - p.min) / p.step).ceil() as usize;
2298 (step_count + 1).min(9)
2299 } else {
2300 let step_count = ((p.max - p.min) / p.step).ceil() as usize;
2301 step_count + 1
2302 };
2303 count *= vals as u64;
2304 }
2305 count as usize
2306 }
2307}
2308
2309#[cfg(test)]
2312mod field_count_tests {
2313 use super::*;
2314
2315 #[test]
2319 fn test_model_settings_field_count() {
2320 let s = ModelSettings::default();
2324 let field_count = count_model_settings_fields(&s);
2325 assert_eq!(
2326 field_count, 75,
2327 "ModelSettings has {} fields (expected 75). \
2328 Update the checklist at src/models.rs:665 and all locations listed there.",
2329 field_count
2330 );
2331 }
2332
2333 #[allow(clippy::too_many_lines)]
2337 fn count_model_settings_fields(s: &ModelSettings) -> usize {
2338 let _ = (
2339 &s.context_length,
2340 &s.threads,
2341 &s.threads_batch,
2342 &s.batch_size,
2343 &s.ubatch_size,
2344 &s.parallel,
2345 &s.max_concurrent_predictions,
2346 &s.uniform_cache,
2347 &s.kv_cache_offload,
2348 &s.cache_type_k,
2349 &s.cache_type_v,
2350 &s.keep,
2351 &s.swa_full,
2352 &s.mlock,
2353 &s.mmap,
2354 &s.numa,
2355 &s.system_prompt,
2356 &s.system_prompt_preset_name,
2357 &s.gpu_layers_mode,
2358 &s.split_mode,
2359 &s.tensor_split,
2360 &s.main_gpu,
2361 &s.fit,
2362 &s.lora,
2363 &s.lora_scaled,
2364 &s.rpc,
2365 &s.embedding,
2366 &s.flash_attn,
2367 &s.expert_count,
2368 &s.jinja,
2369 &s.chat_template,
2370 &s.chat_template_kwargs,
2371 &s.seed,
2372 &s.temperature,
2373 &s.top_k,
2374 &s.top_p,
2375 &s.min_p,
2376 &s.typical_p,
2377 &s.mirostat,
2378 &s.mirostat_lr,
2379 &s.mirostat_ent,
2380 &s.ignore_eos,
2381 &s.samplers,
2382 &s.repeat_penalty,
2383 &s.repeat_last_n,
2384 &s.presence_penalty,
2385 &s.frequency_penalty,
2386 &s.dry_multiplier,
2387 &s.dry_base,
2388 &s.dry_allowed_length,
2389 &s.dry_penalty_last_n,
2390 &s.rope_scaling,
2391 &s.rope_scale,
2392 &s.rope_freq_base,
2393 &s.rope_freq_scale,
2394 &s.rope_yarn_enabled,
2395 &s.host,
2396 &s.port,
2397 &s.timeout,
2398 &s.cache_prompt,
2399 &s.cache_reuse,
2400 &s.webui,
2401 &s.max_tokens,
2402 &s.cache_type,
2403 &s.backend,
2404 &s.llama_cpp_version_cpu,
2405 &s.llama_cpp_version_vulkan,
2406 &s.llama_cpp_version_rocm,
2407 &s.llama_cpp_version_rocm_lemonade,
2408 &s.llama_cpp_version_cuda,
2409 &s.api_endpoint_enabled,
2410 &s.api_endpoint_port,
2411 &s.spec_type,
2412 &s.draft_tokens,
2413 &s.tags,
2414 );
2415 75
2416 }
2417
2418 #[test]
2422 fn test_is_dirty_uses_derived_eq() {
2423 let s1 = ModelSettings::default();
2424 let s2 = ModelSettings::default();
2425 let s3 = s1.clone();
2426
2427 assert!(!s1.is_dirty(&s2));
2429 assert!(!s1.is_dirty(&s3));
2430
2431 assert_eq!(s1 != s2, s1.is_dirty(&s2));
2433 assert_eq!(s1 != s3, s1.is_dirty(&s3));
2434 }
2435
2436 #[test]
2439 fn test_from_default_params_completeness() {
2440 let dp = crate::config::DefaultParams::default();
2441 let ms: ModelSettings = dp.clone().into();
2442
2443 assert_eq!(ms.context_length, 131072);
2446 assert_eq!(ms.threads, dp.threads);
2447 assert_eq!(ms.temperature, 0.8);
2448 assert_eq!(ms.backend, dp.backend);
2451 }
2452}