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 from_str(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
803#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
825pub struct ModelSettings {
826 pub context_length: u32,
829 pub threads: u32,
831 pub threads_batch: u32,
833 pub batch_size: u32,
835 pub ubatch_size: u32,
837 pub parallel: u32,
839 pub max_concurrent_predictions: Option<u32>,
841 pub uniform_cache: bool,
843 pub kv_cache_offload: bool,
845 pub cache_type_k: Option<CacheTypeK>,
847 pub cache_type_v: Option<CacheTypeV>,
849 pub keep: i32,
851 pub swa_full: bool,
853 pub mlock: bool,
855 pub mmap: bool,
857 pub numa: NumMode,
859 pub system_prompt: String,
861 pub system_prompt_preset_name: String,
863 pub web_search_engine: String,
865 pub web_search_engine_url: String,
867
868 pub gpu_layers_mode: GpuLayersMode,
871 pub split_mode: SplitMode,
873 pub tensor_split: String,
875 pub main_gpu: i32,
877 pub fit: bool,
879 pub lora: Option<PathBuf>,
881 pub lora_scaled: Option<(PathBuf, f32)>,
883 pub rpc: String,
885 pub embedding: bool,
887 pub flash_attn: bool,
889 pub expert_count: i32,
891 pub jinja: bool,
893 pub auto_chat_template: bool,
895 pub chat_template: Option<String>,
897 pub chat_template_kwargs: Option<String>,
899
900 pub seed: i32,
903 pub temperature: f32,
905 pub top_k: i32,
907 pub top_p: f32,
909 pub min_p: f32,
911 pub typical_p: f32,
913 pub mirostat: Mirostat,
915 pub mirostat_lr: f32,
917 pub mirostat_ent: f32,
919 pub ignore_eos: bool,
921 pub samplers: Samplers,
923
924 pub repeat_penalty: f32,
927 pub repeat_last_n: i32,
929 pub presence_penalty: Option<f32>,
931 pub frequency_penalty: Option<f32>,
933 pub dry_multiplier: f32,
935 pub dry_base: f32,
937 pub dry_allowed_length: i32,
939 pub dry_penalty_last_n: i32,
941
942 pub rope_scaling: RopeScaling,
945 pub rope_scale: f32,
947 pub rope_freq_base: f32,
949 pub rope_freq_scale: f32,
951 pub rope_yarn_enabled: bool,
953
954 pub host: String,
957 pub port: u16,
959 pub timeout: u32,
961 pub cache_prompt: bool,
963 pub cache_reuse: u32,
965 pub webui: bool,
967
968 pub max_tokens: Option<u32>,
971 pub cache_type: CacheType,
973 pub backend: Backend,
975 pub llama_cpp_version_cpu: Option<String>,
977 pub llama_cpp_version_vulkan: Option<String>,
979 pub llama_cpp_version_rocm: Option<String>,
981 pub llama_cpp_version_rocm_lemonade: Option<String>,
983 pub llama_cpp_version_cuda: Option<String>,
985 pub api_endpoint_enabled: bool,
987 pub api_endpoint_port: u16,
989 pub api_endpoint_key: Option<String>,
991 pub spec_type: String,
993 pub draft_tokens: u32,
995 pub tags: Vec<String>,
997}
998
999impl Default for ModelSettings {
1000 fn default() -> Self {
1001 let mut s: Self = crate::config::DefaultParams::default().into();
1002 s.uniform_cache = false;
1004 s.cache_type_k = Some(CacheTypeK::F16);
1005 s.cache_type_v = Some(CacheTypeV::F16);
1006 s.cache_type = CacheType::default();
1007 s.backend = Backend::Cpu;
1008 s.presence_penalty = Some(0.0);
1009 s.frequency_penalty = Some(0.0);
1010 s
1011 }
1012}
1013
1014impl ModelSettings {
1015 pub fn from_config(config: &crate::config::Config) -> Self {
1017 config.default.clone().into()
1018 }
1019}
1020
1021pub const BENCHMARK_PROMPT: &str = "Create Mona Lisa image in ascii art using text, number, symbol, everything possible. this should be the perfect painting.";
1023
1024#[derive(Debug, Clone)]
1026pub struct DiscoveredModel {
1027 pub path: PathBuf,
1028 pub name: String,
1029 pub file_size: u64,
1030 pub display_name: String, pub pipeline_tag: Option<String>,
1032 pub capabilities: Vec<String>,
1033}
1034
1035#[derive(Debug, Clone, Default)]
1037pub struct GgufMetadata {
1038 pub layers: u32,
1039 pub hidden_size: u32,
1040 pub n_ctx_train: u32,
1041 pub n_head: u32,
1042 pub n_kv_head: u32,
1043 pub arch: String,
1044 pub file_type: String,
1045 pub quantization: String,
1046 pub model_parameters: String,
1047 pub domain: String,
1048 pub capabilities: Vec<String>,
1049 pub tokenizer: String,
1050 pub draft_tokens: u32,
1051 pub quality_rank: u8,
1052}
1053
1054impl GgufMetadata {
1055 pub fn from_path(path: &std::path::Path) -> anyhow::Result<Self> {
1056 let path_str = path.to_string_lossy();
1057 let mut container = gguf_rs::get_gguf_container(&path_str)
1058 .map_err(|e| anyhow::anyhow!("Failed to get GGUF container: {}", e))?;
1059 let model_data = container
1060 .decode()
1061 .map_err(|e| anyhow::anyhow!("Failed to decode GGUF: {}", e))?;
1062
1063 let mut meta = Self::default();
1064
1065 let extract_str = |key: &str| -> String {
1066 model_data
1067 .metadata()
1068 .get(key)
1069 .and_then(|v| v.as_str().map(|s| s.to_string()))
1070 .unwrap_or_default()
1071 };
1072
1073 let extract_num = |key: &str| -> Option<u64> {
1074 model_data.metadata().get(key).and_then(|v| {
1075 v.as_u64()
1076 .or_else(|| v.as_i64().map(|x| x as u64))
1077 .or_else(|| v.as_f64().map(|x| x as u64))
1078 })
1079 };
1080
1081 meta.arch = extract_str("general.architecture");
1082 let prefix = if meta.arch.is_empty() {
1083 "llama"
1084 } else {
1085 &meta.arch
1086 };
1087
1088 let get_num_with_fallback = |suffix: &str| -> u32 {
1089 extract_num(&format!("{}.{}", prefix, suffix))
1090 .or_else(|| {
1091 if prefix != "llama" {
1092 extract_num(&format!("llama.{}", suffix))
1093 } else {
1094 None
1095 }
1096 })
1097 .unwrap_or(0) as u32
1098 };
1099
1100 meta.layers = get_num_with_fallback("block_count");
1101 meta.hidden_size = get_num_with_fallback("embedding_length");
1102 meta.n_ctx_train = get_num_with_fallback("context_length");
1103 meta.n_head = get_num_with_fallback("attention.head_count");
1104 meta.n_kv_head = get_num_with_fallback("attention.head_count_kv");
1105
1106 if meta.arch == "mtp" {
1107 meta.draft_tokens = extract_num("mtp.draft_tokens").unwrap_or(0) as u32;
1108 }
1109
1110 if let Some(v) = extract_num("general.file_type") {
1111 meta.file_type = match v {
1112 0 => "F32".to_string(),
1113 1 => "F16".to_string(),
1114 2 => "Q4_0".to_string(),
1115 3 => "Q4_1".to_string(),
1116 4 => "Q4_1 (F16)".to_string(),
1117 5 => "Q4_1 (F16)".to_string(),
1118 6 => "Q5_0".to_string(),
1119 7 => "Q8_0".to_string(),
1120 8 => "Q5_0".to_string(),
1121 9 => "Q8_1".to_string(),
1122 10 => "Q2_K".to_string(),
1123 11 => "Q3_K_S".to_string(),
1124 12 => "Q3_K_M".to_string(),
1125 13 => "Q3_K_L".to_string(),
1126 14 => "Q4_K_S".to_string(),
1127 15 => "Q4_K_M".to_string(),
1128 16 => "Q5_K_S".to_string(),
1129 17 => "Q5_K_M".to_string(),
1130 18 => "Q6_K".to_string(),
1131 19 => "IQ2_XXS".to_string(),
1132 20 => "IQ2_XS".to_string(),
1133 21 => "IQ3_XXS".to_string(),
1134 22 => "IQ1_S".to_string(),
1135 23 => "IQ4_NL".to_string(),
1136 24 => "IQ3_S".to_string(),
1137 25 => "IQ2_S".to_string(),
1138 26 => "IQ4_XS".to_string(),
1139 27 => "F64".to_string(),
1140 29 => "IQ1_M".to_string(),
1141 30 => "BF16".to_string(),
1142 31 => "IQ2_L".to_string(),
1143 32 => "IQ3_L".to_string(),
1144 33 => "IQ4_M".to_string(),
1145 34 => "TQ1_0".to_string(),
1146 35 => "TQ2_0".to_string(),
1147 39 => "MXFP4".to_string(),
1148 _ => format!("Unknown ({})", v),
1149 };
1150 meta.quality_rank = match v {
1151 0 | 1 | 7 | 9 | 30 | 27 => 4,
1152 6 | 8 | 16 | 17 | 18 => 3,
1153 2 | 3 | 4 | 5 | 14 | 15 | 23 | 26 | 33 | 32 | 24 | 31 => 2,
1154 11 | 12 | 13 | 21 | 10 | 19 | 20 | 22 | 25 | 29 => 1,
1155 _ => 0,
1156 };
1157 }
1158
1159 if let Some(value) = model_data.metadata().get("general.capabilities")
1160 && let Some(arr) = value.as_array()
1161 {
1162 for v in arr {
1163 if let Some(s) = v.as_str() {
1164 meta.capabilities.push(s.to_string());
1165 }
1166 }
1167 }
1168
1169 if model_data
1170 .metadata()
1171 .contains_key("tokenizer.chat_template")
1172 {
1173 meta.capabilities.push("chat".to_string());
1174 }
1175
1176 meta.tokenizer = extract_str("tokenizer.ggml.model");
1177 meta.domain = extract_str("general.domain");
1178 meta.model_parameters = model_data.model_parameters();
1179
1180 Ok(meta)
1181 }
1182}
1183
1184pub fn arch_to_pipeline_tag(arch: &str) -> Option<&'static str> {
1188 match arch {
1189 "llama" | "llama-moe" | "qwen" | "qwen2" | "qwen2moe" | "qwen3" | "qwen3moe"
1191 | "qwen3next" | "qwen35" | "qwen35moe" | "qwen2vl" | "qwen3vl" | "qwen3vlmoe"
1192 | "mistral" | "mistral3" | "mistral4" | "gemma" | "gemma2" | "gemma3" | "gemma3n"
1193 | "gemma4" | "gemma4-assistant" | "gemma-embedding" | "cohere" | "cohere2" | "deepseek"
1194 | "deepseek2" | "deepseek2-ocr" | "deepseek32" | "phi2" | "phi3" | "phimoe" | "phi4"
1195 | "jais" | "jais2" | "stablelm" | "llama4" | "olmo" | "olmo2" | "olmoe" | "sonar"
1196 | "mamba" | "mamba2" | "minicpm" | "minicpm3" | "minicpmo" | "nemo" | "nemotron"
1197 | "nemotron_h" | "nemotron_h_moe" | "chameleon" | "internlm2" | "glm4" | "glm4moe"
1198 | "chatglm" | "exaone" | "exaone4" | "exaone-moe" | "dbrx" | "starcoder" | "starcoder2"
1199 | "baichuan" | "falcon" | "falcon-h1" | "falcon3" | "megrez" | "yandex" | "bailing"
1200 | "bailingmoe" | "bailingmoe2" | "bailing2" | "bailing-think" | "granite"
1201 | "granitehybrid" | "granitemoe" | "hunyuan-dense" | "hunyuan-moe" | "hunyuan_vl"
1202 | "kimi-k2" | "seed_oss" | "grok" | "solar-open" | "gpt-oss" | "smollm3"
1203 | "pangu-embedded" | "rwkv6" | "rwkv6qwen2" | "rwkv7" | "arwkv7" | "mpt" | "gpt-neox"
1204 | "gptj" | "plamo" | "plamo2" | "plamo3" | "orion" | "openchat" | "vicuna" | "zephyr"
1205 | "monarch" | "step35" | "ernie4_5" | "ernie4_5-moe" | "mini-max-m2" | "talkie"
1206 | "apertus" | "arcee" | "arctic" | "jamba" | "lfm2" | "lfm2moe" | "llada" | "llada-moe"
1207 | "maincoder" | "mellum" | "mimo2" | "refact" | "rnd1" | "smallthinker" | "xverse"
1208 | "gpt2" | "codeshell" | "cogvlm" | "deci" | "dots1" | "dream" | "app" | "mamba_ssm" => {
1209 Some("text-generation")
1210 }
1211 "bert" | "nomic-bert" | "nomic-bert-moe" | "roformer" | "deberta-v2" | "deberta"
1213 | "modern-bert" | "neo-bert" | "jina-bert-v2" | "jina-bert-v3" | "eurobert" | "t5"
1214 | "t5encoder" => Some("feature-extraction"),
1215 "resnet" | "vit" | "segformer" | "clip" => Some("image-classification"),
1217 "whisper" => Some("automatic-speech-recognition"),
1219 "bark" | "wavtokenizer-dec" => Some("text-to-audio"),
1220 "speech-to-text" => Some("speech-to-text"),
1222 "text-to-speech" => Some("text-to-speech"),
1223 "stable-diffusion" => Some("text-to-image"),
1225 _ => None,
1226 }
1227}
1228
1229pub fn arch_to_chat_template(arch: &str) -> Option<&'static str> {
1234 match arch {
1235 "llama" | "llama-moe" => Some("llama3"),
1237 "llama4" => Some("llama4"),
1238 "mistral" | "mistral3" | "mistral4" => Some("mistral-v1"),
1240 "qwen" | "qwen2" | "qwen2moe" | "qwen3" | "qwen3moe" | "qwen3next" | "qwen35"
1242 | "qwen35moe" | "qwen2vl" | "qwen3vl" | "qwen3vlmoe" => Some("chatml"),
1243 "gemma" | "gemma2" | "gemma3" | "gemma3n" | "gemma4" | "gemma4-assistant" => Some("gemma"),
1245 "phi2" | "phi3" | "phimoe" => Some("phi3"),
1247 "phi4" => Some("phi4"),
1248 "cohere" | "cohere2" => Some("command-r"),
1250 "deepseek" | "deepseek2" | "deepseek2-ocr" => Some("deepseek"),
1252 "deepseek32" => Some("deepseek3"),
1253 "internlm2" | "glm4" | "glm4moe" | "chatglm" => Some("chatglm4"),
1255 "exaone" => Some("exaone3"),
1257 "exaone4" => Some("exaone4"),
1258 "exaone-moe" => Some("exaone-moe"),
1259 "minicpm" | "minicpm3" | "minicpmo" => Some("minicpm"),
1261 "falcon" | "falcon-h1" | "falcon3" => Some("chatml"),
1263 "rwkv6" | "rwkv6qwen2" | "rwkv7" | "arwkv7" => Some("rwkv-world"),
1265 "granite" | "granitehybrid" | "granitemoe" => Some("granite"),
1267 "hunyuan-dense" | "hunyuan-moe" | "hunyuan_vl" => Some("hunyuan-dense"),
1269 "olmo" | "olmo2" | "olmoe" | "sonar" | "mamba" | "mamba2" | "mamba_ssm" | "dbrx"
1271 | "starcoder" | "starcoder2" | "baichuan" | "gpt-neox" | "gptj" | "mpt" | "jais"
1272 | "jais2" | "stablelm" | "chameleon" | "nemo" | "nemotron" | "nemotron_h"
1273 | "nemotron_h_moe" | "plamo" | "plamo2" | "plamo3" | "ernie4_5" | "ernie4_5-moe"
1274 | "mini-max-m2" | "talkie" | "apertus" | "arcee" | "arctic" | "jamba" | "lfm2"
1275 | "lfm2moe" | "llada" | "llada-moe" | "maincoder" | "mellum" | "mimo2" | "refact"
1276 | "rnd1" | "smallthinker" | "xverse" | "gpt2" | "codeshell" | "cogvlm" | "deci"
1277 | "dots1" | "dream" | "app" | "step35" | "smollm3" => Some("chatml"),
1278 "megrez" => Some("megrez"),
1280 "yandex" => Some("yandex"),
1281 "bailing" | "bailingmoe" | "bailingmoe2" | "bailing2" | "bailing-think" => Some("bailing"),
1282 "kimi-k2" => Some("kimi-k2"),
1283 "seed_oss" => Some("seed_oss"),
1284 "grok" => Some("grok-2"),
1285 "solar-open" => Some("solar-open"),
1286 "gpt-oss" => Some("gpt-oss"),
1287 "pangu-embedded" => Some("pangu-embedded"),
1288 "gigachat" => Some("gigachat"),
1289 _ => None,
1294 }
1295}
1296
1297pub fn get_available_chat_templates() -> Vec<String> {
1301 let mut templates = vec!["Auto (detect)".to_string()];
1302 let mut seen = std::collections::HashSet::new();
1303 let archs = vec![
1305 "llama",
1306 "llama-moe",
1307 "llama4",
1308 "mistral",
1309 "mistral3",
1310 "mistral4",
1311 "qwen",
1312 "qwen2",
1313 "qwen2moe",
1314 "qwen3",
1315 "qwen3moe",
1316 "qwen3next",
1317 "qwen35",
1318 "qwen35moe",
1319 "qwen2vl",
1320 "qwen3vl",
1321 "qwen3vlmoe",
1322 "gemma",
1323 "gemma2",
1324 "gemma3",
1325 "gemma3n",
1326 "gemma4",
1327 "gemma4-assistant",
1328 "phi2",
1329 "phi3",
1330 "phimoe",
1331 "phi4",
1332 "cohere",
1333 "cohere2",
1334 "deepseek",
1335 "deepseek2",
1336 "deepseek2-ocr",
1337 "deepseek32",
1338 "internlm2",
1339 "glm4",
1340 "glm4moe",
1341 "chatglm",
1342 "exaone",
1343 "exaone4",
1344 "exaone-moe",
1345 "minicpm",
1346 "minicpm3",
1347 "minicpmo",
1348 "falcon",
1349 "falcon-h1",
1350 "falcon3",
1351 "rwkv6",
1352 "rwkv6qwen2",
1353 "rwkv7",
1354 "arwkv7",
1355 "granite",
1356 "granitehybrid",
1357 "granitemoe",
1358 "hunyuan-dense",
1359 "hunyuan-moe",
1360 "hunyuan_vl",
1361 "olmo",
1362 "olmo2",
1363 "olmoe",
1364 "sonar",
1365 "mamba",
1366 "mamba2",
1367 "mamba_ssm",
1368 "dbrx",
1369 "starcoder",
1370 "starcoder2",
1371 "baichuan",
1372 "gpt-neox",
1373 "gptj",
1374 "mpt",
1375 "jais",
1376 "jais2",
1377 "stablelm",
1378 "chameleon",
1379 "nemo",
1380 "nemotron",
1381 "nemotron_h",
1382 "nemotron_h_moe",
1383 "plamo",
1384 "plamo2",
1385 "plamo3",
1386 "ernie4_5",
1387 "ernie4_5-moe",
1388 "mini-max-m2",
1389 "talkie",
1390 "apertus",
1391 "arcee",
1392 "arctic",
1393 "jamba",
1394 "lfm2",
1395 "lfm2moe",
1396 "llada",
1397 "llada-moe",
1398 "maincoder",
1399 "mellum",
1400 "mimo2",
1401 "refact",
1402 "rnd1",
1403 "smallthinker",
1404 "xverse",
1405 "gpt2",
1406 "codeshell",
1407 "cogvlm",
1408 "deci",
1409 "dots1",
1410 "dream",
1411 "app",
1412 "step35",
1413 "smollm3",
1414 "megrez",
1415 "yandex",
1416 "bailing",
1417 "bailingmoe",
1418 "bailingmoe2",
1419 "bailing2",
1420 "bailing-think",
1421 "kimi-k2",
1422 "seed_oss",
1423 "grok",
1424 "solar-open",
1425 "gpt-oss",
1426 "pangu-embedded",
1427 "gigachat",
1428 "stable-diffusion",
1429 ];
1430 for arch in &archs {
1431 if let Some(template) = arch_to_chat_template(arch) {
1432 if seen.insert(template.to_string()) {
1433 templates.push(template.to_string());
1434 }
1435 }
1436 }
1437 templates.push("Select a Template file...".to_string());
1438 templates.push("None".to_string());
1439 templates
1440}
1441
1442#[derive(Debug, Clone)]
1444pub struct ServerMetrics {
1445 pub loaded: bool,
1446 pub tps: f64,
1447 pub prompt_tps: f64,
1448 pub cpu_usage: f64,
1449 pub gpu_mem_used: u64,
1450 pub gpu_mem_total: u64,
1451 pub ram_used: u64,
1452 pub ctx_used: u32,
1453 pub ctx_max: u32,
1454 pub total_vram_used: u64,
1456 pub decoded_tokens: u64,
1458 pub gen_tps: f64,
1460 pub latency_per_token_ms: f64,
1462 pub prompt_latency_ms: f64,
1464 pub prompt_tokens: u64,
1466 pub prompt_progress: f64,
1468 pub prompt_elapsed_ms: f64,
1470 pub prompt_tps_eval: f64,
1472}
1473
1474#[derive(Debug, Clone)]
1476pub struct GPUBuffer {
1477 pub device: String,
1478 pub buffer_size_mib: f64,
1479}
1480
1481#[derive(Debug, Clone, Default)]
1483pub struct LoadProgress {
1484 pub layers_total: Option<u32>,
1486 pub layers_loaded: Option<u32>,
1488 pub tensors_total: Option<u32>,
1490 pub tensors_loaded: u32,
1492 pub buffers: Vec<GPUBuffer>,
1494}
1495
1496impl Default for ServerMetrics {
1497 fn default() -> Self {
1498 Self {
1499 loaded: false,
1500 tps: 0.0,
1501 prompt_tps: 0.0,
1502 cpu_usage: 0.0,
1503 gpu_mem_used: 0,
1504 gpu_mem_total: 0,
1505 ram_used: 0,
1506 ctx_used: 0,
1507 ctx_max: 0,
1508 total_vram_used: 0,
1509 decoded_tokens: 0,
1510 gen_tps: 0.0,
1511 latency_per_token_ms: 0.0,
1512 prompt_latency_ms: 0.0,
1513 prompt_tokens: 0,
1514 prompt_progress: 0.0,
1515 prompt_elapsed_ms: 0.0,
1516 prompt_tps_eval: 0.0,
1517 }
1518 }
1519}
1520
1521#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1523pub struct WsMetrics {
1524 pub model_name: String,
1525 pub loaded: bool,
1526 pub state: String,
1527 pub tps: f64,
1528 pub prompt_tps: f64,
1529 pub ctx_used: u32,
1530 pub ctx_max: u32,
1531 pub cpu_usage: f64,
1532 pub gpu_mem_used: u64,
1533 pub gpu_mem_total: u64,
1534 pub ram_used: u64,
1535 pub latency_per_token_ms: f64,
1536 pub decoded_tokens: u64,
1537 pub gen_tps: f64,
1538 pub prompt_tokens: u64,
1539 pub prompt_progress: f64,
1540 pub prompt_elapsed_ms: f64,
1541 pub prompt_tps_eval: f64,
1542 pub timestamp: u64,
1543 pub cmd_display: Option<String>,
1545 pub threads: u32,
1547 pub threads_batch: u32,
1548 pub context_length: u32,
1549 pub ubatch_size: u32,
1550 pub batch_size: u32,
1551 pub temperature: f32,
1552 pub top_k: u32,
1553 pub top_p: f32,
1554 pub min_p: f32,
1555 pub typical_p: f32,
1556 pub seed: i32,
1557 pub repeat_penalty: f32,
1558 pub repeat_last_n: i32,
1559 pub presence_penalty: Option<f32>,
1560 pub frequency_penalty: Option<f32>,
1561 pub mirostat: Option<u32>,
1562 pub mirostat_lr: Option<f32>,
1563 pub mirostat_ent: Option<f32>,
1564 pub max_tokens: Option<u32>,
1565 pub flash_attn: bool,
1566 pub kv_cache_offload: bool,
1567 pub cache_type_k: Option<String>,
1568 pub cache_type_v: Option<String>,
1569 pub uniform_cache: bool,
1570 pub mlock: bool,
1571 pub mmap: bool,
1572 pub embedding: bool,
1573 pub jinja: bool,
1574 pub ignore_eos: bool,
1575 pub samplers: String,
1576 pub expert_count: i32,
1577 pub gpu_layers: String,
1578 pub backend: String,
1579 pub llama_cpp_version: String,
1580 pub spec_type: String,
1581 pub draft_tokens: u32,
1582}
1583
1584impl WsMetrics {
1585 pub fn from_metrics(
1586 metrics: &ServerMetrics,
1587 model_name: &str,
1588 state: &str,
1589 settings: &crate::models::ModelSettings,
1590 cmd_display: Option<&str>,
1591 ) -> Self {
1592 use std::time::{SystemTime, UNIX_EPOCH};
1593 let timestamp = SystemTime::now()
1594 .duration_since(UNIX_EPOCH)
1595 .map(|d| d.as_secs())
1596 .unwrap_or(0);
1597 let gpu_layers = match settings.gpu_layers_mode {
1598 crate::models::GpuLayersMode::Auto => "Auto".to_string(),
1599 crate::models::GpuLayersMode::Specific(n) => n.to_string(),
1600 crate::models::GpuLayersMode::All => "All".to_string(),
1601 };
1602 Self {
1603 model_name: model_name.to_string(),
1604 loaded: metrics.loaded,
1605 state: state.to_string(),
1606 tps: metrics.tps,
1607 prompt_tps: metrics.prompt_tps,
1608 ctx_used: metrics.ctx_used,
1609 ctx_max: metrics.ctx_max,
1610 cpu_usage: metrics.cpu_usage,
1611 gpu_mem_used: metrics.gpu_mem_used,
1612 gpu_mem_total: metrics.gpu_mem_total,
1613 ram_used: metrics.ram_used,
1614 latency_per_token_ms: metrics.latency_per_token_ms,
1615 decoded_tokens: metrics.decoded_tokens,
1616 gen_tps: metrics.gen_tps,
1617 prompt_tokens: metrics.prompt_tokens,
1618 prompt_progress: metrics.prompt_progress,
1619 prompt_elapsed_ms: metrics.prompt_elapsed_ms,
1620 prompt_tps_eval: metrics.prompt_tps_eval,
1621 timestamp,
1622 cmd_display: cmd_display.map(String::from),
1623 threads: settings.threads,
1624 threads_batch: settings.threads_batch,
1625 context_length: settings.context_length,
1626 ubatch_size: settings.ubatch_size,
1627 batch_size: settings.batch_size,
1628 temperature: settings.temperature,
1629 top_k: settings.top_k as u32,
1630 top_p: settings.top_p,
1631 min_p: settings.min_p,
1632 typical_p: settings.typical_p,
1633 seed: settings.seed,
1634 repeat_penalty: settings.repeat_penalty,
1635 repeat_last_n: settings.repeat_last_n,
1636 presence_penalty: settings.presence_penalty,
1637 frequency_penalty: settings.frequency_penalty,
1638 mirostat: Some(match settings.mirostat {
1639 crate::models::Mirostat::Off => 0,
1640 crate::models::Mirostat::V1 => 1,
1641 crate::models::Mirostat::Mirostat2 => 2,
1642 }),
1643 mirostat_lr: Some(settings.mirostat_lr),
1644 mirostat_ent: Some(settings.mirostat_ent),
1645 max_tokens: settings.max_tokens,
1646 flash_attn: settings.flash_attn,
1647 kv_cache_offload: settings.kv_cache_offload,
1648 cache_type_k: settings.cache_type_k.map(|k| k.to_string()),
1649 cache_type_v: settings.cache_type_v.map(|k| k.to_string()),
1650 uniform_cache: settings.uniform_cache,
1651 mlock: settings.mlock,
1652 mmap: settings.mmap,
1653 embedding: settings.embedding,
1654 jinja: settings.jinja,
1655 ignore_eos: settings.ignore_eos,
1656 samplers: settings.samplers.to_string(),
1657 expert_count: settings.expert_count,
1658 gpu_layers,
1659 backend: settings.backend.to_string(),
1660 llama_cpp_version: settings.get_active_backend_version_display().to_string(),
1661 spec_type: settings.spec_type.clone(),
1662 draft_tokens: settings.draft_tokens,
1663 }
1664 }
1665}
1666
1667pub fn estimate_vram_mib(
1679 model_mib: u64,
1680 settings: &ModelSettings,
1681 total_layers: u32,
1682 hidden_size_opt: Option<u32>,
1683 n_head_opt: Option<u32>,
1684 n_kv_head_opt: Option<u32>,
1685 gpu_mem_total_mib: u64,
1686) -> u64 {
1687 let model_mib_f = model_mib as f64;
1688
1689 let gpu_layers = match settings.gpu_layers_mode {
1691 GpuLayersMode::Auto => {
1692 if total_layers > 0 {
1694 (total_layers as f64 * 0.6) as u32
1695 } else {
1696 20
1697 }
1698 }
1699 GpuLayersMode::Specific(n) => {
1700 if total_layers > 0 {
1701 n.min(total_layers)
1702 } else {
1703 n
1704 }
1705 }
1706 GpuLayersMode::All => {
1707 if total_layers > 0 {
1708 total_layers
1709 } else {
1710 32
1711 }
1712 }
1713 };
1714
1715 let model_vram = if total_layers > 0 && gpu_layers > 0 {
1717 model_mib_f * (gpu_layers as f64 / total_layers as f64).min(1.0)
1718 } else if gpu_layers > 0 {
1719 model_mib_f
1720 } else {
1721 0.0
1722 };
1723
1724 if matches!(settings.gpu_layers_mode, GpuLayersMode::Specific(0)) {
1725 return 0; }
1727
1728 let hidden_size = match hidden_size_opt {
1731 Some(h) => h as f64,
1732 None => {
1733 let params_est = model_mib_f / 550.0;
1734 (1024.0 * params_est.sqrt().max(1.0) * 1.5).max(512.0)
1735 }
1736 };
1737
1738 let gqa_ratio = match (n_head_opt, n_kv_head_opt) {
1744 (Some(n_head), Some(n_kv_head)) if n_head > 0 => n_kv_head as f64 / n_head as f64,
1745 _ => 1.0, };
1747
1748 let flash_attn_factor = if settings.flash_attn { 0.5 } else { 1.0 };
1751
1752 let uniform_cache_factor = if settings.uniform_cache {
1754 1.0 / settings.parallel as f64
1755 } else {
1756 1.0
1757 };
1758
1759 let effective_ctx = settings.context_length as f64 * settings.rope_scale as f64;
1761
1762 let kv_mib = (2.0
1769 * hidden_size
1770 * effective_ctx
1771 * total_layers as f64
1772 * gqa_ratio
1773 * gpu_layers as f64
1774 / total_layers as f64 * flash_attn_factor
1776 * uniform_cache_factor
1777 * kv_quant_bytes(
1778 settings.cache_type_k.unwrap_or(CacheTypeK::F16),
1779 settings.cache_type_v.unwrap_or(CacheTypeV::F16)
1780 ))
1781 / (1024.0 * 1024.0);
1782
1783 let activation_mib = (settings.batch_size as f64 * hidden_size * 8.0) / (1024.0 * 1024.0);
1786
1787 let fixed_overhead = if gpu_mem_total_mib > 0 {
1790 gpu_mem_total_mib as f64 * 0.038
1791 } else {
1792 500.0
1793 };
1794
1795 let total_mib = model_vram + kv_mib + activation_mib + fixed_overhead + 550.0;
1796
1797 total_mib.ceil() as u64
1798}
1799
1800fn kv_quant_bytes(k_type: CacheQuantType, v_type: CacheQuantType) -> f64 {
1805 let get_bytes = |t: CacheQuantType| match t {
1806 CacheQuantType::F32 => 4.0,
1807 CacheQuantType::F16 | CacheQuantType::BF16 => 2.0,
1808 CacheQuantType::Q8_0 => 1.0,
1809 CacheQuantType::Q5_0 | CacheQuantType::Q5_1 => 0.625, CacheQuantType::Q4_0 | CacheQuantType::Q4_1 | CacheQuantType::Iq4Nl => 0.5, };
1812 (get_bytes(k_type) + get_bytes(v_type)) / 2.0
1813}
1814
1815impl ModelSettings {
1816 pub fn is_dirty(&self, other: &Self) -> bool {
1819 self != other
1820 }
1821}
1822
1823#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1825pub struct BenchTuneConfig {
1826 pub model_path: PathBuf,
1827 pub num_iterations: u32,
1828 pub prompt: String,
1829 pub params_to_test: Vec<BenchTuneParam>,
1830 pub test_duration: Duration,
1831 pub bench_mode: BenchTuneMode,
1832 pub n_predict: u32,
1833 pub chat_template_kwargs: Option<String>,
1834 pub test_timeout: Duration,
1835}
1836
1837#[derive(Debug, Clone, Serialize, Deserialize)]
1838pub struct BenchTuneParam {
1839 pub name: String,
1840 pub min: f64,
1841 pub max: f64,
1842 pub step: f64,
1843 pub enabled: bool,
1844 pub variants: Vec<String>,
1845}
1846
1847impl PartialEq for BenchTuneParam {
1848 fn eq(&self, other: &Self) -> bool {
1849 self.name == other.name
1850 && self.min.to_bits() == other.min.to_bits()
1851 && self.max.to_bits() == other.max.to_bits()
1852 && self.step.to_bits() == other.step.to_bits()
1853 && self.enabled == other.enabled
1854 && self.variants == other.variants
1855 }
1856}
1857impl Eq for BenchTuneParam {}
1858
1859#[derive(Debug, Clone, Serialize, Deserialize)]
1860pub struct BenchTuneParamValue {
1861 pub temperature: Option<f64>,
1862 pub top_p: Option<f64>,
1863 pub top_k: Option<i64>,
1864 pub repeat_penalty: Option<f64>,
1865 pub context_length: Option<u32>,
1866 pub batch_size: Option<u32>,
1867 pub flash_attn: Option<bool>,
1868 pub threads: Option<u32>,
1869 pub expert_count: Option<i32>,
1870 pub spec_type: Option<String>,
1871 pub draft_tokens: Option<u32>,
1872}
1873
1874impl PartialEq for BenchTuneParamValue {
1875 fn eq(&self, other: &Self) -> bool {
1876 self.temperature.map(|v| v.to_bits()) == other.temperature.map(|v| v.to_bits())
1877 && self.top_p.map(|v| v.to_bits()) == other.top_p.map(|v| v.to_bits())
1878 && self.top_k == other.top_k
1879 && self.repeat_penalty.map(|v| v.to_bits()) == other.repeat_penalty.map(|v| v.to_bits())
1880 && self.context_length == other.context_length
1881 && self.batch_size == other.batch_size
1882 && self.flash_attn == other.flash_attn
1883 && self.threads == other.threads
1884 && self.expert_count == other.expert_count
1885 && self.spec_type == other.spec_type
1886 && self.draft_tokens == other.draft_tokens
1887 }
1888}
1889impl Eq for BenchTuneParamValue {}
1890
1891#[derive(Debug, Clone, Serialize, Deserialize)]
1892pub struct BenchTuneResult {
1893 pub params: BenchTuneParamValue,
1894 pub metrics: BenchTuneMetrics,
1895 pub outputs: Vec<String>,
1896 pub per_iteration_metrics: Vec<BenchTuneMetrics>,
1897 pub base_settings: Option<ModelSettings>,
1898 pub server_command: Option<String>,
1899}
1900
1901#[derive(Debug, Clone, Serialize, Deserialize)]
1902pub struct BenchTuneMetrics {
1903 pub prompt_tps: f64,
1904 pub generation_tps: f64,
1905 pub combined_tps: f64,
1906 pub latency_per_token: f64,
1907 pub prompt_processing_time: f64,
1908}
1909
1910#[derive(Debug, Clone, Serialize, Deserialize)]
1911pub enum BenchTuneStatus {
1912 Running {
1913 current: usize,
1914 total: usize,
1915 progress: f32,
1916 current_params: BenchTuneParamValue,
1917 },
1918 Completed {
1919 total_tests: usize,
1920 successful_tests: usize,
1921 elapsed: Duration,
1922 },
1923 PartiallyCompleted {
1924 total_tests: usize,
1925 successful_tests: usize,
1926 failed_tests: usize,
1927 elapsed: Duration,
1928 },
1929 Cancelled {
1930 total_tests: usize,
1931 successful_tests: usize,
1932 failed_tests: usize,
1933 elapsed: Duration,
1934 },
1935 Error {
1936 error: String,
1937 },
1938}
1939
1940#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
1941pub enum BenchTuneMode {
1942 RuntimeOnly,
1944 #[default]
1946 Full,
1947}
1948
1949#[derive(Debug, Clone)]
1951pub enum BenchTuneProgress {
1952 Running {
1954 current: usize,
1955 total: usize,
1956 progress: f32,
1957 current_params: BenchTuneParamValue,
1958 },
1959 Completed {
1961 total_tests: usize,
1962 successful_tests: usize,
1963 elapsed: Duration,
1964 },
1965 PartiallyCompleted {
1967 total_tests: usize,
1968 successful_tests: usize,
1969 failed_tests: usize,
1970 elapsed: Duration,
1971 },
1972 Cancelled {
1974 total_tests: usize,
1975 successful_tests: usize,
1976 failed_tests: usize,
1977 elapsed: Duration,
1978 },
1979 Error { error: String },
1981}
1982
1983impl BenchTuneProgress {
1984 pub fn from_status(status: &BenchTuneStatus) -> Option<Self> {
1985 match status {
1986 BenchTuneStatus::Running {
1987 current,
1988 total,
1989 progress,
1990 current_params,
1991 } => Some(BenchTuneProgress::Running {
1992 current: *current,
1993 total: *total,
1994 progress: *progress,
1995 current_params: current_params.clone(),
1996 }),
1997 BenchTuneStatus::Completed {
1998 total_tests,
1999 successful_tests,
2000 elapsed,
2001 } => Some(BenchTuneProgress::Completed {
2002 total_tests: *total_tests,
2003 successful_tests: *successful_tests,
2004 elapsed: *elapsed,
2005 }),
2006 BenchTuneStatus::PartiallyCompleted {
2007 total_tests,
2008 successful_tests,
2009 failed_tests,
2010 elapsed,
2011 } => Some(BenchTuneProgress::PartiallyCompleted {
2012 total_tests: *total_tests,
2013 successful_tests: *successful_tests,
2014 failed_tests: *failed_tests,
2015 elapsed: *elapsed,
2016 }),
2017 BenchTuneStatus::Cancelled {
2018 total_tests,
2019 successful_tests,
2020 failed_tests,
2021 elapsed,
2022 } => Some(BenchTuneProgress::Cancelled {
2023 total_tests: *total_tests,
2024 successful_tests: *successful_tests,
2025 failed_tests: *failed_tests,
2026 elapsed: *elapsed,
2027 }),
2028 BenchTuneStatus::Error { error } => Some(BenchTuneProgress::Error {
2029 error: error.clone(),
2030 }),
2031 }
2032 }
2033}
2034
2035impl BenchTuneConfig {
2036 pub fn new(model_path: PathBuf, num_iterations: u32, prompt: String) -> Self {
2037 Self {
2038 model_path,
2039 num_iterations,
2040 prompt,
2041 params_to_test: vec![
2042 BenchTuneParam {
2043 name: "temperature".to_string(),
2044 min: 0.4,
2045 max: 1.0,
2046 step: 0.1,
2047 enabled: false,
2048 variants: vec![],
2049 },
2050 BenchTuneParam {
2051 name: "top_p".to_string(),
2052 min: 0.8,
2053 max: 1.0,
2054 step: 0.1,
2055 enabled: false,
2056 variants: vec![],
2057 },
2058 BenchTuneParam {
2059 name: "top_k".to_string(),
2060 min: 10.0,
2061 max: 40.0,
2062 step: 5.0,
2063 enabled: false,
2064 variants: vec![],
2065 },
2066 BenchTuneParam {
2067 name: "repeat_penalty".to_string(),
2068 min: 1.0,
2069 max: 1.5,
2070 step: 0.1,
2071 enabled: false,
2072 variants: vec![],
2073 },
2074 BenchTuneParam {
2075 name: "flash_attn".to_string(),
2076 min: 0.0,
2077 max: 1.0,
2078 step: 1.0,
2079 enabled: false,
2080 variants: vec![],
2081 },
2082 BenchTuneParam {
2083 name: "threads".to_string(),
2084 min: 4.0,
2085 max: 16.0,
2086 step: 4.0,
2087 enabled: false,
2088 variants: vec![],
2089 },
2090 BenchTuneParam {
2091 name: "batch_size".to_string(),
2092 min: 512.0,
2093 max: 2048.0,
2094 step: 512.0,
2095 enabled: false,
2096 variants: vec![],
2097 },
2098 BenchTuneParam {
2099 name: "expert_count".to_string(),
2100 min: -1.0,
2101 max: 4.0,
2102 step: 1.0,
2103 enabled: false,
2104 variants: vec![],
2105 },
2106 BenchTuneParam {
2107 name: "spec_type".to_string(),
2108 min: 0.0,
2109 max: 8.0,
2110 step: 1.0,
2111 enabled: false,
2112 variants: vec![
2113 "Off".to_string(),
2114 "draft-mtp".to_string(),
2115 "draft-simple".to_string(),
2116 "draft-eagle3".to_string(),
2117 "ngram-simple".to_string(),
2118 "ngram-map-k".to_string(),
2119 "ngram-map-k4v".to_string(),
2120 "ngram-mod".to_string(),
2121 "ngram-cache".to_string(),
2122 ],
2123 },
2124 BenchTuneParam {
2125 name: "draft_tokens".to_string(),
2126 min: 0.0,
2127 max: 8.0,
2128 step: 1.0,
2129 enabled: false,
2130 variants: vec![],
2131 },
2132 ],
2133 test_duration: Duration::from_secs(30),
2134 bench_mode: BenchTuneMode::default(),
2135 n_predict: 512,
2136 chat_template_kwargs: Some(r#"{"enable_thinking": false}"#.to_string()),
2137 test_timeout: Duration::from_secs(60),
2138 }
2139 }
2140
2141 pub fn generate_combinations(&self) -> Vec<BenchTuneParamValue> {
2143 let mut temp_values = vec![None];
2144 let mut top_p_values = vec![None];
2145 let mut top_k_values = vec![None];
2146 let mut repeat_penalty_values = vec![None];
2147 let mut flash_attn_values = vec![None];
2148 let mut threads_values = vec![None];
2149 let mut batch_size_values = vec![None];
2150 let mut expert_count_values = vec![None];
2151 let mut selected_spec_type = None;
2152 if let Some(p) = self.params_to_test.iter().find(|p| p.name == "spec_type") {
2153 let base_idx = (p.min as usize).min(p.variants.len().saturating_sub(1));
2154 let val = &p.variants[base_idx];
2155 if val != "Off" {
2156 selected_spec_type = Some(val.clone());
2157 }
2158 }
2159 let mut spec_type_values = vec![selected_spec_type];
2160 let mut draft_tokens_values = vec![None];
2161
2162 let spec_type_options = vec![
2163 "Off".to_string(),
2164 "draft-mtp".to_string(),
2165 "draft-simple".to_string(),
2166 "draft-eagle3".to_string(),
2167 "ngram-simple".to_string(),
2168 "ngram-map-k".to_string(),
2169 "ngram-map-k4v".to_string(),
2170 "ngram-mod".to_string(),
2171 "ngram-cache".to_string(),
2172 ];
2173
2174 for p in &self.params_to_test {
2175 if !p.enabled {
2176 continue;
2177 }
2178
2179 let vals: Vec<f64> = {
2180 let step_count = ((p.max - p.min) / p.step).ceil() as usize;
2181 (0..=step_count)
2182 .map(|i| (p.min + (i as f64 * p.step)).min(p.max))
2183 .collect()
2184 };
2185
2186 match p.name.as_str() {
2187 "temperature" => temp_values = vals.into_iter().map(Some).collect(),
2188 "top_p" => top_p_values = vals.into_iter().map(Some).collect(),
2189 "top_k" => top_k_values = vals.into_iter().map(|v| Some(v as i64)).collect(),
2190 "repeat_penalty" => repeat_penalty_values = vals.into_iter().map(Some).collect(),
2191 "flash_attn" => {
2192 flash_attn_values = vals.into_iter().map(|v| Some(v >= 0.5)).collect()
2193 }
2194 "threads" => threads_values = vals.into_iter().map(|v| Some(v as u32)).collect(),
2195 "batch_size" => {
2196 batch_size_values = vals.into_iter().map(|v| Some(v as u32)).collect()
2197 }
2198 "expert_count" => {
2199 expert_count_values = vals.into_iter().map(|v| Some(v as i32)).collect()
2200 }
2201 "spec_type" => {
2202 if !p.variants.is_empty() {
2203 spec_type_values = p.variants.clone().into_iter().map(Some).collect();
2204 } else {
2205 let step_count = ((p.max - p.min) / p.step).ceil() as usize;
2206 spec_type_values = (0..=step_count)
2207 .map(|i| {
2208 let idx = i.min(spec_type_options.len() - 1);
2209 Some(spec_type_options[idx].clone())
2210 })
2211 .collect()
2212 }
2213 }
2214 "draft_tokens" => {
2215 draft_tokens_values = vals.into_iter().map(|v| Some(v as u32)).collect()
2216 }
2217 _ => {}
2218 }
2219 }
2220
2221 let mut combinations = Vec::new();
2222 for &temp in &temp_values {
2223 for &top_p in &top_p_values {
2224 for &top_k in &top_k_values {
2225 for &rp in &repeat_penalty_values {
2226 for &fa in &flash_attn_values {
2227 for &th in &threads_values {
2228 for &bs in &batch_size_values {
2229 for &ec in &expert_count_values {
2230 for st in &spec_type_values {
2231 for &dt in &draft_tokens_values {
2232 combinations.push(BenchTuneParamValue {
2233 temperature: temp,
2234 top_p,
2235 top_k,
2236 repeat_penalty: rp,
2237 context_length: None,
2238 batch_size: bs,
2239 flash_attn: fa,
2240 threads: th,
2241 expert_count: ec,
2242 spec_type: st.clone(),
2243 draft_tokens: dt,
2244 });
2245 }
2246 }
2247 }
2248 }
2249 }
2250 }
2251 }
2252 }
2253 }
2254 }
2255
2256 combinations
2257 }
2258
2259 pub fn get_total_tests_count(&self) -> usize {
2261 self.generate_combinations().len()
2262 }
2263
2264 pub fn get_num_combinations(&self) -> usize {
2266 let mut count: u64 = 1;
2267 for p in &self.params_to_test {
2268 if !p.enabled {
2269 continue;
2270 }
2271 let vals = if p.name == "flash_attn" {
2272 2 } else if !p.variants.is_empty() {
2274 p.variants.len()
2275 } else if p.name == "spec_type" {
2276 let step_count = ((p.max - p.min) / p.step).ceil() as usize;
2277 (step_count + 1).min(9)
2278 } else {
2279 let step_count = ((p.max - p.min) / p.step).ceil() as usize;
2280 step_count + 1
2281 };
2282 count *= vals as u64;
2283 }
2284 count as usize
2285 }
2286}
2287
2288#[cfg(test)]
2291mod field_count_tests {
2292 use super::*;
2293
2294 #[test]
2298 fn test_model_settings_field_count() {
2299 let s = ModelSettings::default();
2303 let field_count = count_model_settings_fields(&s);
2304 assert_eq!(
2305 field_count, 75,
2306 "ModelSettings has {} fields (expected 75). \
2307 Update the checklist at src/models.rs:665 and all locations listed there.",
2308 field_count
2309 );
2310 }
2311
2312 #[allow(clippy::too_many_lines)]
2316 fn count_model_settings_fields(s: &ModelSettings) -> usize {
2317 let _ = (
2318 &s.context_length,
2319 &s.threads,
2320 &s.threads_batch,
2321 &s.batch_size,
2322 &s.ubatch_size,
2323 &s.parallel,
2324 &s.max_concurrent_predictions,
2325 &s.uniform_cache,
2326 &s.kv_cache_offload,
2327 &s.cache_type_k,
2328 &s.cache_type_v,
2329 &s.keep,
2330 &s.swa_full,
2331 &s.mlock,
2332 &s.mmap,
2333 &s.numa,
2334 &s.system_prompt,
2335 &s.system_prompt_preset_name,
2336 &s.gpu_layers_mode,
2337 &s.split_mode,
2338 &s.tensor_split,
2339 &s.main_gpu,
2340 &s.fit,
2341 &s.lora,
2342 &s.lora_scaled,
2343 &s.rpc,
2344 &s.embedding,
2345 &s.flash_attn,
2346 &s.expert_count,
2347 &s.jinja,
2348 &s.chat_template,
2349 &s.chat_template_kwargs,
2350 &s.seed,
2351 &s.temperature,
2352 &s.top_k,
2353 &s.top_p,
2354 &s.min_p,
2355 &s.typical_p,
2356 &s.mirostat,
2357 &s.mirostat_lr,
2358 &s.mirostat_ent,
2359 &s.ignore_eos,
2360 &s.samplers,
2361 &s.repeat_penalty,
2362 &s.repeat_last_n,
2363 &s.presence_penalty,
2364 &s.frequency_penalty,
2365 &s.dry_multiplier,
2366 &s.dry_base,
2367 &s.dry_allowed_length,
2368 &s.dry_penalty_last_n,
2369 &s.rope_scaling,
2370 &s.rope_scale,
2371 &s.rope_freq_base,
2372 &s.rope_freq_scale,
2373 &s.rope_yarn_enabled,
2374 &s.host,
2375 &s.port,
2376 &s.timeout,
2377 &s.cache_prompt,
2378 &s.cache_reuse,
2379 &s.webui,
2380 &s.max_tokens,
2381 &s.cache_type,
2382 &s.backend,
2383 &s.llama_cpp_version_cpu,
2384 &s.llama_cpp_version_vulkan,
2385 &s.llama_cpp_version_rocm,
2386 &s.llama_cpp_version_rocm_lemonade,
2387 &s.llama_cpp_version_cuda,
2388 &s.api_endpoint_enabled,
2389 &s.api_endpoint_port,
2390 &s.spec_type,
2391 &s.draft_tokens,
2392 &s.tags,
2393 );
2394 75
2395 }
2396
2397 #[test]
2401 fn test_is_dirty_uses_derived_eq() {
2402 let s1 = ModelSettings::default();
2403 let s2 = ModelSettings::default();
2404 let s3 = s1.clone();
2405
2406 assert!(!s1.is_dirty(&s2));
2408 assert!(!s1.is_dirty(&s3));
2409
2410 assert_eq!(s1 != s2, s1.is_dirty(&s2));
2412 assert_eq!(s1 != s3, s1.is_dirty(&s3));
2413 }
2414
2415 #[test]
2418 fn test_from_default_params_completeness() {
2419 let dp = crate::config::DefaultParams::default();
2420 let ms: ModelSettings = dp.clone().into();
2421
2422 assert_eq!(ms.context_length, 131072);
2425 assert_eq!(ms.threads, dp.threads);
2426 assert_eq!(ms.temperature, 0.8);
2427 assert_eq!(ms.backend, dp.backend);
2430 }
2431}