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, Serialize, Deserialize, PartialEq, Eq)]
50pub struct SearchResult {
51 pub model_id: String,
52 pub model_name: String,
53 pub tags: Vec<String>,
54 pub downloads: u64,
55 pub likes: u64,
56 pub pipeline_tag: Option<String>,
57 pub size: Option<u64>,
58 pub parameters: Option<String>,
59 pub capabilities: Vec<String>,
60 pub context_length: Option<u32>,
61 pub readme: Option<String>,
62 pub quantization: Option<String>,
64 pub license: Option<String>,
66 pub trending_score: i64,
68 pub created_at: Option<String>,
70 #[serde(default)]
72 pub downloaded: bool,
73}
74
75#[derive(Debug, Clone)]
77pub struct DownloadState {
78 pub model_id: String,
79 pub filename: String,
80 pub total_bytes: u64,
81 pub downloaded_bytes: u64,
82 pub status: DownloadStatus,
83 pub cancelled: bool,
84 pub cancel_token: Option<std::sync::Arc<std::sync::atomic::AtomicBool>>,
85 pub download_state: u8,
87 pub download_state_arc: Option<std::sync::Arc<std::sync::atomic::AtomicU8>>,
89 pub start_time: std::time::Instant,
90 pub bytes_per_second: f64,
91 pub dest: Option<std::path::PathBuf>,
93}
94
95impl DownloadState {
96 pub fn new(model_id: String, filename: String, total_bytes: u64) -> Self {
97 Self {
98 model_id,
99 filename,
100 total_bytes,
101 downloaded_bytes: 0,
102 status: DownloadStatus::Downloading,
103 cancelled: false,
104 cancel_token: None,
105 download_state: 1,
106 download_state_arc: None,
107 start_time: std::time::Instant::now(),
108 bytes_per_second: 0.0,
109 dest: None,
110 }
111 }
112}
113
114impl std::hash::Hash for ModelSettings {
115 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
116 self.context_length.hash(state);
118 self.threads.hash(state);
119 self.threads_batch.hash(state);
120 self.batch_size.hash(state);
121 self.ubatch_size.hash(state);
122 self.parallel.hash(state);
123 self.max_concurrent_predictions.hash(state);
124 self.uniform_cache.hash(state);
125 self.kv_cache_offload.hash(state);
126 self.cache_type_k.hash(state);
127 self.cache_type_v.hash(state);
128 self.keep.hash(state);
129 self.swa_full.hash(state);
130 self.mlock.hash(state);
131 self.mmap.hash(state);
132 self.numa.hash(state);
133 self.system_prompt.hash(state);
134 self.system_prompt_preset_name.hash(state);
135 self.gpu_layers_mode.hash(state);
137 self.split_mode.hash(state);
138 self.tensor_split.hash(state);
139 self.main_gpu.hash(state);
140 self.fit.hash(state);
141 self.lora.hash(state);
142 if let Some((path, scale)) = &self.lora_scaled {
143 path.hash(state);
144 scale.to_bits().hash(state);
145 }
146 self.rpc.hash(state);
147 self.embedding.hash(state);
148 self.flash_attn.hash(state);
149 self.expert_count.hash(state);
150 self.jinja.hash(state);
151 self.chat_template.hash(state);
152 self.chat_template_kwargs.hash(state);
153 self.seed.hash(state);
155 self.temperature.to_bits().hash(state);
156 self.top_k.hash(state);
157 self.top_p.to_bits().hash(state);
158 self.min_p.to_bits().hash(state);
159 self.typical_p.to_bits().hash(state);
160 self.mirostat.hash(state);
161 self.mirostat_lr.to_bits().hash(state);
162 self.mirostat_ent.to_bits().hash(state);
163 self.ignore_eos.hash(state);
164 self.samplers.hash(state);
165 self.repeat_penalty.to_bits().hash(state);
167 self.repeat_last_n.hash(state);
168 self.presence_penalty.map(|v| v.to_bits()).hash(state);
169 self.frequency_penalty.map(|v| v.to_bits()).hash(state);
170 self.dry_multiplier.to_bits().hash(state);
171 self.dry_base.to_bits().hash(state);
172 self.dry_allowed_length.hash(state);
173 self.dry_penalty_last_n.hash(state);
174 self.rope_scaling.hash(state);
176 self.rope_scale.to_bits().hash(state);
177 self.rope_freq_base.to_bits().hash(state);
178 self.rope_freq_scale.to_bits().hash(state);
179 self.rope_yarn_enabled.hash(state);
180 self.host.hash(state);
182 self.port.hash(state);
183 self.timeout.hash(state);
184 self.cache_prompt.hash(state);
185 self.cache_reuse.hash(state);
186 self.webui.hash(state);
187 self.max_tokens.hash(state);
189 self.cache_type.hash(state);
190 self.backend.hash(state);
191 self.llama_cpp_version_cpu.hash(state);
192 self.llama_cpp_version_vulkan.hash(state);
193 self.llama_cpp_version_rocm.hash(state);
194 self.llama_cpp_version_rocm_lemonade.hash(state);
195 self.llama_cpp_version_cuda.hash(state);
196 self.api_endpoint_enabled.hash(state);
197 self.api_endpoint_port.hash(state);
198 self.spec_type.hash(state);
199 self.draft_tokens.hash(state);
200 self.tags.hash(state);
201 }
202}
203
204impl ModelSettings {
205 pub fn get_active_backend_version(&self) -> Option<&String> {
207 match self.backend {
208 Backend::Cpu => self.llama_cpp_version_cpu.as_ref(),
209 Backend::Vulkan => self.llama_cpp_version_vulkan.as_ref(),
210 Backend::Rocm => self.llama_cpp_version_rocm.as_ref(),
211 Backend::RocmLemonade => self.llama_cpp_version_rocm_lemonade.as_ref(),
212 Backend::Cuda => self.llama_cpp_version_cuda.as_ref(),
213 _ => None,
214 }
215 }
216
217 pub fn get_active_backend_version_display(&self) -> &str {
219 self.get_active_backend_version()
220 .map(|s| s.as_str())
221 .unwrap_or("latest")
222 }
223
224 pub fn set_active_backend_version(&mut self, tag: Option<String>) {
226 match self.backend {
227 Backend::Cpu => self.llama_cpp_version_cpu = tag,
228 Backend::Vulkan => self.llama_cpp_version_vulkan = tag,
229 Backend::Rocm => self.llama_cpp_version_rocm = tag,
230 Backend::RocmLemonade => self.llama_cpp_version_rocm_lemonade = tag,
231 Backend::Cuda => self.llama_cpp_version_cuda = tag,
232 _ => {}
233 }
234 }
235}
236
237pub fn strip_gguf(name: &str) -> &str {
239 name.strip_suffix(".gguf")
240 .or_else(|| name.strip_suffix(".GGUF"))
241 .unwrap_or(name)
242}
243
244pub fn clean_host(host: &str) -> String {
248 let host = host.trim();
249 if host.is_empty() {
250 return "127.0.0.1".to_string();
251 }
252 let host = host.split_whitespace().next().unwrap_or(host);
254 if host.contains(':') && !host.starts_with('[') {
255 format!("[{}]", host)
256 } else {
257 host.to_string()
258 }
259}
260
261pub fn format_host(host: &str) -> String {
263 match host {
264 "" | "127.0.0.1" => crate::t!("server.host_label").to_string(),
265 _ => host.to_string(),
266 }
267}
268
269impl From<crate::config::DefaultParams> for ModelSettings {
270 fn from(dp: crate::config::DefaultParams) -> Self {
271 Self {
272 context_length: dp.context_length,
273 threads: dp.threads,
274 threads_batch: dp.threads_batch,
275 batch_size: dp.batch_size,
276 ubatch_size: dp.ubatch_size,
277 parallel: dp.parallel,
278 max_concurrent_predictions: dp.max_concurrent_predictions,
279 uniform_cache: dp.uniform_cache,
280 kv_cache_offload: dp.kv_cache_offload,
281 cache_type_k: dp.cache_type_k,
282 cache_type_v: dp.cache_type_v,
283 keep: dp.keep,
284 swa_full: dp.swa_full,
285 mlock: dp.mlock,
286 mmap: dp.mmap,
287 numa: dp.numa,
288 system_prompt: dp.system_prompt,
289 system_prompt_preset_name: dp.system_prompt_preset_name,
290
291 gpu_layers_mode: match dp.gpu_layers {
292 n if n < 0 => GpuLayersMode::All,
293 _ => dp.gpu_layers_mode,
294 },
295 split_mode: dp.split_mode,
296 tensor_split: dp.tensor_split,
297 main_gpu: dp.main_gpu,
298 fit: dp.fit,
299 lora: dp.lora,
300 lora_scaled: dp.lora_scaled,
301 rpc: dp.rpc,
302 embedding: dp.embedding,
303 flash_attn: dp.flash_attn,
304 expert_count: dp.expert_count,
305 jinja: dp.jinja,
306 chat_template: dp.chat_template,
307 chat_template_kwargs: dp.chat_template_kwargs,
308 seed: dp.seed,
309 temperature: dp.temperature,
310 top_k: dp.top_k,
311 top_p: dp.top_p,
312 min_p: dp.min_p,
313 typical_p: dp.typical_p,
314 mirostat: dp.mirostat,
315 mirostat_lr: dp.mirostat_lr,
316 mirostat_ent: dp.mirostat_ent,
317 ignore_eos: dp.ignore_eos,
318 samplers: dp.samplers,
319 repeat_penalty: dp.repeat_penalty,
320 repeat_last_n: dp.repeat_last_n,
321 presence_penalty: dp.presence_penalty,
322 frequency_penalty: dp.frequency_penalty,
323 dry_multiplier: dp.dry_multiplier,
324 dry_base: dp.dry_base,
325 dry_allowed_length: dp.dry_allowed_length,
326 dry_penalty_last_n: dp.dry_penalty_last_n,
327 rope_scaling: dp.rope_scaling,
328 rope_scale: dp.rope_scale,
329 rope_freq_base: dp.rope_freq_base,
330 rope_freq_scale: dp.rope_freq_scale,
331 rope_yarn_enabled: dp.rope_yarn_enabled,
332 host: dp.host,
333 port: dp.port,
334 timeout: dp.timeout,
335 cache_prompt: dp.cache_prompt,
336 cache_reuse: dp.cache_reuse,
337 webui: dp.webui,
338 max_tokens: dp.max_tokens,
339 cache_type: dp.cache_type,
340 backend: dp.backend,
341 llama_cpp_version_cpu: dp.llama_cpp_version_cpu,
342 llama_cpp_version_vulkan: dp.llama_cpp_version_vulkan,
343 llama_cpp_version_rocm: dp.llama_cpp_version_rocm,
344 llama_cpp_version_rocm_lemonade: dp.llama_cpp_version_rocm_lemonade,
345 llama_cpp_version_cuda: dp.llama_cpp_version_cuda,
346 api_endpoint_enabled: dp.api_endpoint_enabled,
347 api_endpoint_port: dp.api_endpoint_port,
348
349 spec_type: dp.spec_type,
350 draft_tokens: dp.draft_tokens,
351 tags: dp.tags,
352 }
353 }
354}
355
356#[derive(
358 Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, Default,
359)]
360pub enum GpuLayersMode {
361 #[default]
362 Auto,
363 Specific(u32),
364 All,
365}
366
367#[derive(Debug, Clone, PartialEq, Eq)]
368pub enum DownloadStatus {
369 Downloading,
370 Pausing,
371 Paused,
372 Complete,
373 Error(String),
374 Cancelled,
375}
376
377#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, Default)]
381pub enum CacheType {
382 #[serde(rename = "f16")]
383 #[default]
384 F16,
385 #[serde(rename = "bf16")]
386 BF16,
387 #[serde(rename = "fq8_0")]
388 Fq8_0,
389 #[serde(rename = "fq4_1")]
390 Fq4_1,
391}
392
393impl std::fmt::Display for CacheType {
394 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
395 match self {
396 CacheType::F16 => write!(f, "f16"),
397 CacheType::BF16 => write!(f, "bf16"),
398 CacheType::Fq8_0 => write!(f, "fq8_0"),
399 CacheType::Fq4_1 => write!(f, "fq4_1"),
400 }
401 }
402}
403
404#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default, Hash)]
406pub enum CacheQuantType {
407 #[serde(rename = "f32")]
408 F32,
409 #[serde(rename = "f16")]
410 #[default]
411 F16,
412 #[serde(rename = "bf16")]
413 BF16,
414 #[serde(rename = "q8_0")]
415 Q8_0,
416 #[serde(rename = "q4_0")]
417 Q4_0,
418 #[serde(rename = "q4_1")]
419 Q4_1,
420 #[serde(rename = "iq4_nl")]
421 Iq4Nl,
422 #[serde(rename = "q5_0")]
423 Q5_0,
424 #[serde(rename = "q5_1")]
425 Q5_1,
426}
427
428pub type CacheTypeK = CacheQuantType;
429pub type CacheTypeV = CacheQuantType;
430
431impl CacheQuantType {
432 pub fn from_u8(n: u8) -> Self {
433 match n {
434 0 => Self::F32,
435 1 => Self::F16,
436 2 => Self::BF16,
437 3 => Self::Q8_0,
438 4 => Self::Q5_1,
439 5 => Self::Q5_0,
440 6 => Self::Q4_1,
441 7 => Self::Q4_0,
442 8 => Self::Iq4Nl,
443 _ => Self::F16,
444 }
445 }
446 pub fn next(&self) -> Self {
447 match self {
448 Self::F32 => Self::F16,
449 Self::F16 => Self::BF16,
450 Self::BF16 => Self::Q8_0,
451 Self::Q8_0 => Self::Q5_1,
452 Self::Q5_1 => Self::Q5_0,
453 Self::Q5_0 => Self::Q4_1,
454 Self::Q4_1 => Self::Q4_0,
455 Self::Q4_0 => Self::Iq4Nl,
456 Self::Iq4Nl => Self::F32,
457 }
458 }
459 pub fn prev(&self) -> Self {
460 match self {
461 Self::F32 => Self::Iq4Nl,
462 Self::F16 => Self::F32,
463 Self::BF16 => Self::F16,
464 Self::Q8_0 => Self::BF16,
465 Self::Q5_1 => Self::Q8_0,
466 Self::Q5_0 => Self::Q5_1,
467 Self::Q4_1 => Self::Q5_0,
468 Self::Q4_0 => Self::Q4_1,
469 Self::Iq4Nl => Self::Q4_0,
470 }
471 }
472}
473
474impl std::fmt::Display for CacheQuantType {
475 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
476 match self {
477 Self::F32 => write!(f, "f32"),
478 Self::F16 => write!(f, "f16"),
479 Self::BF16 => write!(f, "bf16"),
480 Self::Q8_0 => write!(f, "q8_0"),
481 Self::Q4_0 => write!(f, "q4_0"),
482 Self::Q4_1 => write!(f, "q4_1"),
483 Self::Iq4Nl => write!(f, "iq4_nl"),
484 Self::Q5_0 => write!(f, "q5_0"),
485 Self::Q5_1 => write!(f, "q5_1"),
486 }
487 }
488}
489
490impl From<&str> for CacheQuantType {
491 fn from(s: &str) -> Self {
492 match s {
493 "F32" => Self::F32,
494 "F16" => Self::F16,
495 "BF16" => Self::BF16,
496 "Q8_0" => Self::Q8_0,
497 "Q4_0" => Self::Q4_0,
498 "Q4_1" => Self::Q4_1,
499 "Iq4Nl" => Self::Iq4Nl,
500 "Q5_0" => Self::Q5_0,
501 "Q5_1" => Self::Q5_1,
502 _ => Self::F16, }
504 }
505}
506
507#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Hash, Default)]
509pub enum SplitMode {
510 #[serde(rename = "none")]
511 None,
512 #[serde(rename = "layer")]
513 #[default]
514 Layer,
515 #[serde(rename = "row")]
516 Row,
517 #[serde(rename = "tensor")]
518 Tensor,
519}
520
521impl std::fmt::Display for SplitMode {
522 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
523 match self {
524 SplitMode::None => write!(f, "none"),
525 SplitMode::Layer => write!(f, "layer"),
526 SplitMode::Row => write!(f, "row"),
527 SplitMode::Tensor => write!(f, "tensor"),
528 }
529 }
530}
531
532#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Hash, Default)]
534pub enum NumMode {
535 #[serde(rename = "none")]
536 #[default]
537 None,
538 #[serde(rename = "distribute")]
539 Distribute,
540 #[serde(rename = "isolate")]
541 Isolate,
542 #[serde(rename = "numactl")]
543 Numactl,
544}
545
546impl std::fmt::Display for NumMode {
547 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
548 match self {
549 NumMode::None => write!(f, "none"),
550 NumMode::Distribute => write!(f, "distribute"),
551 NumMode::Isolate => write!(f, "isolate"),
552 NumMode::Numactl => write!(f, "numactl"),
553 }
554 }
555}
556
557#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, Default)]
559pub enum RopeScaling {
560 #[serde(rename = "none")]
561 #[default]
562 None,
563 #[serde(rename = "linear")]
564 Linear,
565 #[serde(rename = "yarn")]
566 Yarn,
567}
568
569impl std::fmt::Display for RopeScaling {
570 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
571 match self {
572 RopeScaling::None => write!(f, "none"),
573 RopeScaling::Linear => write!(f, "linear"),
574 RopeScaling::Yarn => write!(f, "yarn"),
575 }
576 }
577}
578
579#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, Default)]
581pub enum Mirostat {
582 #[serde(rename = "0")]
583 #[default]
584 Off,
585 #[serde(rename = "1")]
586 V1,
587 #[serde(rename = "2")]
588 Mirostat2,
589}
590
591impl std::fmt::Display for Mirostat {
592 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
593 match self {
594 Mirostat::Off => write!(f, "off"),
595 Mirostat::V1 => write!(f, "1"),
596 Mirostat::Mirostat2 => write!(f, "2"),
597 }
598 }
599}
600
601#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
604pub struct Samplers(pub String);
605
606impl Default for Samplers {
607 fn default() -> Self {
608 Self("penalties;dry;top_n_sigma;top_k;typ_p;top_p;min_p;xtc;temperature".to_string())
609 }
610}
611
612impl std::fmt::Display for Samplers {
613 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
614 write!(f, "{}", self.0)
615 }
616}
617
618#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
620pub enum Backend {
621 #[serde(rename = "cpu")]
622 #[default]
623 Cpu,
624 #[serde(rename = "vulkan")]
625 Vulkan,
626 #[serde(rename = "rocm")]
627 Rocm,
628 #[serde(rename = "rocm_lemonade")]
629 RocmLemonade,
630 #[serde(rename = "cuda")]
631 Cuda,
632 #[serde(rename = "cpu_arm64")]
633 CpuArm64,
634 #[serde(rename = "win_cpu")]
635 CpuWindows,
636 #[serde(rename = "win_vulkan")]
637 VulkanWindows,
638 #[serde(rename = "win_cuda_12_4")]
639 CudaWindows12_4,
640 #[serde(rename = "win_cuda_13_1")]
641 CudaWindows13_1,
642 #[serde(rename = "win_hip")]
643 HipWindows,
644 #[serde(rename = "macos_arm64")]
645 CpuMacosArm64,
646 #[serde(rename = "macos_x64")]
647 CpuMacosX64,
648}
649
650impl Backend {
651 pub fn slug(&self) -> &'static str {
653 match self {
654 Backend::Cpu => "cpu",
655 Backend::Vulkan => "vulkan",
656 Backend::Rocm => "rocm",
657 Backend::RocmLemonade => "rocm-lemonade",
658 Backend::Cuda => "cuda",
659 Backend::CpuArm64 => "cpu-arm64",
660 Backend::CpuWindows => "win-cpu",
661 Backend::VulkanWindows => "win-vulkan",
662 Backend::CudaWindows12_4 => "win-cuda-12.4",
663 Backend::CudaWindows13_1 => "win-cuda-13.1",
664 Backend::HipWindows => "win-hip",
665 Backend::CpuMacosArm64 => "macos-arm64",
666 Backend::CpuMacosX64 => "macos-x64",
667 }
668 }
669
670 pub fn is_linux(self) -> bool {
672 matches!(
673 self,
674 Backend::Cpu
675 | Backend::Vulkan
676 | Backend::Rocm
677 | Backend::RocmLemonade
678 | Backend::Cuda
679 | Backend::CpuArm64
680 )
681 }
682
683 pub fn is_windows(self) -> bool {
685 matches!(
686 self,
687 Backend::CpuWindows
688 | Backend::VulkanWindows
689 | Backend::CudaWindows12_4
690 | Backend::CudaWindows13_1
691 | Backend::HipWindows
692 )
693 }
694
695 pub fn is_macos(self) -> bool {
697 matches!(self, Backend::CpuMacosArm64 | Backend::CpuMacosX64)
698 }
699
700 pub fn from_str(s: &str) -> Self {
702 let s = s.to_lowercase();
703 if s.starts_with("vulkan") || s.starts_with("vk") {
704 Backend::Vulkan
705 } else if s.starts_with("rocm") || s.starts_with("ro") {
706 if s.contains("lemonade") {
707 Backend::RocmLemonade
708 } else {
709 Backend::Rocm
710 }
711 } else if s.starts_with("cuda") || s.starts_with("cu") {
712 Backend::Cuda
713 } else {
714 Backend::Cpu }
716 }
717}
718
719impl std::fmt::Display for Backend {
720 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
721 write!(f, "{}", self.slug())
722 }
723}
724
725#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
727pub enum ServerMode {
728 #[serde(rename = "normal")]
729 #[default]
730 Normal,
731 #[serde(rename = "router")]
732 Router,
733 #[serde(rename = "bench_gpu", alias = "bench")]
734 Bench,
735 #[serde(rename = "bench_tune")]
736 BenchTune,
737}
738
739impl std::fmt::Display for ServerMode {
740 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
741 match self {
742 ServerMode::Normal => write!(f, "Normal"),
743 ServerMode::Router => write!(f, "Router (XP!)"),
744 ServerMode::Bench => write!(f, "Bench GPU"),
745 ServerMode::BenchTune => write!(f, "BenchTune"),
746 }
747 }
748}
749
750#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
772pub struct ModelSettings {
773 pub context_length: u32,
776 pub threads: u32,
778 pub threads_batch: u32,
780 pub batch_size: u32,
782 pub ubatch_size: u32,
784 pub parallel: u32,
786 pub max_concurrent_predictions: Option<u32>,
788 pub uniform_cache: bool,
790 pub kv_cache_offload: bool,
792 pub cache_type_k: Option<CacheTypeK>,
794 pub cache_type_v: Option<CacheTypeV>,
796 pub keep: i32,
798 pub swa_full: bool,
800 pub mlock: bool,
802 pub mmap: bool,
804 pub numa: NumMode,
806 pub system_prompt: String,
808 pub system_prompt_preset_name: String,
810
811 pub gpu_layers_mode: GpuLayersMode,
814 pub split_mode: SplitMode,
816 pub tensor_split: String,
818 pub main_gpu: i32,
820 pub fit: bool,
822 pub lora: Option<PathBuf>,
824 pub lora_scaled: Option<(PathBuf, f32)>,
826 pub rpc: String,
828 pub embedding: bool,
830 pub flash_attn: bool,
832 pub expert_count: i32,
834 pub jinja: bool,
836 pub chat_template: Option<String>,
838 pub chat_template_kwargs: Option<String>,
840
841 pub seed: i32,
844 pub temperature: f32,
846 pub top_k: i32,
848 pub top_p: f32,
850 pub min_p: f32,
852 pub typical_p: f32,
854 pub mirostat: Mirostat,
856 pub mirostat_lr: f32,
858 pub mirostat_ent: f32,
860 pub ignore_eos: bool,
862 pub samplers: Samplers,
864
865 pub repeat_penalty: f32,
868 pub repeat_last_n: i32,
870 pub presence_penalty: Option<f32>,
872 pub frequency_penalty: Option<f32>,
874 pub dry_multiplier: f32,
876 pub dry_base: f32,
878 pub dry_allowed_length: i32,
880 pub dry_penalty_last_n: i32,
882
883 pub rope_scaling: RopeScaling,
886 pub rope_scale: f32,
888 pub rope_freq_base: f32,
890 pub rope_freq_scale: f32,
892 pub rope_yarn_enabled: bool,
894
895 pub host: String,
898 pub port: u16,
900 pub timeout: u32,
902 pub cache_prompt: bool,
904 pub cache_reuse: u32,
906 pub webui: bool,
908
909 pub max_tokens: Option<u32>,
912 pub cache_type: CacheType,
914 pub backend: Backend,
916 pub llama_cpp_version_cpu: Option<String>,
918 pub llama_cpp_version_vulkan: Option<String>,
920 pub llama_cpp_version_rocm: Option<String>,
922 pub llama_cpp_version_rocm_lemonade: Option<String>,
924 pub llama_cpp_version_cuda: Option<String>,
926 pub api_endpoint_enabled: bool,
928 pub api_endpoint_port: u16,
930 pub spec_type: String,
932 pub draft_tokens: u32,
934 pub tags: Vec<String>,
936}
937
938impl Default for ModelSettings {
939 fn default() -> Self {
940 let mut s: Self = crate::config::DefaultParams::default().into();
941 s.uniform_cache = false;
943 s.cache_type_k = Some(CacheTypeK::F16);
944 s.cache_type_v = Some(CacheTypeV::F16);
945 s.cache_type = CacheType::default();
946 s.backend = Backend::Cpu;
947 s.presence_penalty = Some(0.0);
948 s.frequency_penalty = Some(0.0);
949 s
950 }
951}
952
953impl ModelSettings {
954 pub fn from_config(config: &crate::config::Config) -> Self {
956 config.default.clone().into()
957 }
958}
959
960pub const BENCHMARK_PROMPT: &str = "Create Mona Lisa image in ascii art using text, number, symbol, everything possible. this should be the perfect painting.";
962
963#[derive(Debug, Clone)]
965pub struct DiscoveredModel {
966 pub path: PathBuf,
967 pub name: String,
968 pub file_size: u64,
969 pub display_name: String, pub pipeline_tag: Option<String>,
971 pub capabilities: Vec<String>,
972}
973
974#[derive(Debug, Clone, Default)]
976pub struct GgufMetadata {
977 pub layers: u32,
978 pub hidden_size: u32,
979 pub n_ctx_train: u32,
980 pub n_head: u32,
981 pub n_kv_head: u32,
982 pub arch: String,
983 pub file_type: String,
984 pub quantization: String,
985 pub model_parameters: String,
986 pub domain: String,
987 pub capabilities: Vec<String>,
988 pub tokenizer: String,
989 pub draft_tokens: u32,
990}
991
992impl GgufMetadata {
993 pub fn from_path(path: &std::path::Path) -> anyhow::Result<Self> {
994 let path_str = path.to_string_lossy();
995 let mut container = gguf_rs::get_gguf_container(&path_str)
996 .map_err(|e| anyhow::anyhow!("Failed to get GGUF container: {}", e))?;
997 let model_data = container
998 .decode()
999 .map_err(|e| anyhow::anyhow!("Failed to decode GGUF: {}", e))?;
1000
1001 let mut meta = Self::default();
1002
1003 let extract_str = |key: &str| -> String {
1004 model_data
1005 .metadata()
1006 .get(key)
1007 .and_then(|v| v.as_str().map(|s| s.to_string()))
1008 .unwrap_or_default()
1009 };
1010
1011 let extract_num = |key: &str| -> Option<u64> {
1012 model_data.metadata().get(key).and_then(|v| {
1013 v.as_u64()
1014 .or_else(|| v.as_i64().map(|x| x as u64))
1015 .or_else(|| v.as_f64().map(|x| x as u64))
1016 })
1017 };
1018
1019 meta.arch = extract_str("general.architecture");
1020 let prefix = if meta.arch.is_empty() {
1021 "llama"
1022 } else {
1023 &meta.arch
1024 };
1025
1026 let get_num_with_fallback = |suffix: &str| -> u32 {
1027 extract_num(&format!("{}.{}", prefix, suffix))
1028 .or_else(|| {
1029 if prefix != "llama" {
1030 extract_num(&format!("llama.{}", suffix))
1031 } else {
1032 None
1033 }
1034 })
1035 .unwrap_or(0) as u32
1036 };
1037
1038 meta.layers = get_num_with_fallback("block_count");
1039 meta.hidden_size = get_num_with_fallback("embedding_length");
1040 meta.n_ctx_train = get_num_with_fallback("context_length");
1041 meta.n_head = get_num_with_fallback("attention.head_count");
1042 meta.n_kv_head = get_num_with_fallback("attention.head_count_kv");
1043
1044 if meta.arch == "mtp" {
1045 meta.draft_tokens = extract_num("mtp.draft_tokens").unwrap_or(0) as u32;
1046 }
1047
1048 if let Some(v) = extract_num("general.file_type") {
1049 meta.file_type = match v {
1050 0 => "F32".to_string(),
1051 1 => "F16".to_string(),
1052 2 => "Q4_0".to_string(),
1053 3 => "Q4_1".to_string(),
1054 7 => "Q8_0".to_string(),
1055 8 => "Q5_0".to_string(),
1056 9 => "Q5_1".to_string(),
1057 10 => "Q2_K".to_string(),
1058 11 => "Q3_K_S".to_string(),
1059 12 => "Q3_K_M".to_string(),
1060 13 => "Q3_K_L".to_string(),
1061 14 => "Q4_K_S".to_string(),
1062 15 => "Q4_K_M".to_string(),
1063 16 => "Q5_K_S".to_string(),
1064 17 => "Q5_K_M".to_string(),
1065 18 => "Q6_K".to_string(),
1066 19 => "IQ2_XXS".to_string(),
1067 20 => "IQ2_XS".to_string(),
1068 21 => "IQ3_XXS".to_string(),
1069 22 => "IQ1_S".to_string(),
1070 23 => "IQ4_NL".to_string(),
1071 24 => "IQ3_S".to_string(),
1072 25 => "IQ2_S".to_string(),
1073 26 => "IQ4_XS".to_string(),
1074 _ => format!("Unknown ({})", v),
1075 };
1076 }
1077
1078 if let Some(value) = model_data.metadata().get("general.capabilities")
1079 && let Some(arr) = value.as_array()
1080 {
1081 for v in arr {
1082 if let Some(s) = v.as_str() {
1083 meta.capabilities.push(s.to_string());
1084 }
1085 }
1086 }
1087
1088 if model_data
1089 .metadata()
1090 .contains_key("tokenizer.chat_template")
1091 {
1092 meta.capabilities.push("chat".to_string());
1093 }
1094
1095 meta.tokenizer = extract_str("tokenizer.ggml.model");
1096 meta.domain = extract_str("general.domain");
1097 meta.model_parameters = model_data.model_parameters();
1098
1099 Ok(meta)
1100 }
1101}
1102
1103pub fn arch_to_pipeline_tag(arch: &str) -> Option<&'static str> {
1106 match arch {
1107 "llama" | "mpt" | "falcon" | "gpt-neox" | "gptj" | "qwen" | "qwen2" | "qwen3"
1108 | "qwen2_vl" | "qwen3_vl" | "mistral" | "starcoder2" | "baichuan" | "gemma"
1109 | "gemma3" | "cohere" | "deepseek" | "deepseek2" | "phi" | "phi3" | "phi3small"
1110 | "jais" | "stable-lm" | "stablelm2" | "llama-moe" | "qwen2_moe"
1111 | "olmo" | "olmo2" | "sonar" | "mamba" | "mamba_ssm" | "minicpm" | "minicpm3"
1112 | "minicpmo" | "nemo" | "chameleon" | "internlm2" | "exaone" | "dbrx" => {
1113 Some("text-generation")
1114 }
1115 "bert" | "nomic-bert" | "roformer" | "deberta-v2" | "deberta" => {
1116 Some("feature-extraction")
1117 }
1118 "resnet" | "vit" | "segformer" => Some("image-classification"),
1119 "whisper" => Some("automatic-speech-recognition"),
1120 "stable-diffusion" => Some("text-to-image"),
1121 "bark" => Some("text-to-audio"),
1122 "speech-to-text" => Some("speech-to-text"),
1123 "text-to-speech" => Some("text-to-speech"),
1124 _ => None,
1125 }
1126}
1127
1128#[derive(Debug, Clone)]
1130pub struct ServerMetrics {
1131 pub loaded: bool,
1132 pub tps: f64,
1133 pub prompt_tps: f64,
1134 pub cpu_usage: f64,
1135 pub gpu_mem_used: u64,
1136 pub gpu_mem_total: u64,
1137 pub ram_used: u64,
1138 pub ctx_used: u32,
1139 pub ctx_max: u32,
1140 pub total_vram_used: u64,
1142 pub decoded_tokens: u64,
1144 pub gen_tps: f64,
1146 pub latency_per_token_ms: f64,
1148 pub prompt_latency_ms: f64,
1150}
1151
1152#[derive(Debug, Clone)]
1154pub struct GPUBuffer {
1155 pub device: String,
1156 pub buffer_size_mib: f64,
1157}
1158
1159#[derive(Debug, Clone, Default)]
1161pub struct LoadProgress {
1162 pub layers_total: Option<u32>,
1164 pub layers_loaded: Option<u32>,
1166 pub tensors_total: Option<u32>,
1168 pub tensors_loaded: u32,
1170 pub buffers: Vec<GPUBuffer>,
1172}
1173
1174impl Default for ServerMetrics {
1175 fn default() -> Self {
1176 Self {
1177 loaded: false,
1178 tps: 0.0,
1179 prompt_tps: 0.0,
1180 cpu_usage: 0.0,
1181 gpu_mem_used: 0,
1182 gpu_mem_total: 0,
1183 ram_used: 0,
1184 ctx_used: 0,
1185 ctx_max: 0,
1186 total_vram_used: 0,
1187 decoded_tokens: 0,
1188 gen_tps: 0.0,
1189 latency_per_token_ms: 0.0,
1190 prompt_latency_ms: 0.0,
1191 }
1192 }
1193}
1194
1195#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1197pub struct WsMetrics {
1198 pub model_name: String,
1199 pub loaded: bool,
1200 pub state: String,
1201 pub tps: f64,
1202 pub prompt_tps: f64,
1203 pub ctx_used: u32,
1204 pub ctx_max: u32,
1205 pub cpu_usage: f64,
1206 pub gpu_mem_used: u64,
1207 pub gpu_mem_total: u64,
1208 pub ram_used: u64,
1209 pub latency_per_token_ms: f64,
1210 pub decoded_tokens: u64,
1211 pub gen_tps: f64,
1212 pub timestamp: u64,
1213 pub cmd_display: Option<String>,
1215 pub threads: u32,
1217 pub threads_batch: u32,
1218 pub context_length: u32,
1219 pub ubatch_size: u32,
1220 pub batch_size: u32,
1221 pub temperature: f32,
1222 pub top_k: u32,
1223 pub top_p: f32,
1224 pub min_p: f32,
1225 pub typical_p: f32,
1226 pub seed: i32,
1227 pub repeat_penalty: f32,
1228 pub repeat_last_n: i32,
1229 pub presence_penalty: Option<f32>,
1230 pub frequency_penalty: Option<f32>,
1231 pub mirostat: Option<u32>,
1232 pub mirostat_lr: Option<f32>,
1233 pub mirostat_ent: Option<f32>,
1234 pub max_tokens: Option<u32>,
1235 pub flash_attn: bool,
1236 pub kv_cache_offload: bool,
1237 pub cache_type_k: Option<String>,
1238 pub cache_type_v: Option<String>,
1239 pub uniform_cache: bool,
1240 pub mlock: bool,
1241 pub mmap: bool,
1242 pub embedding: bool,
1243 pub jinja: bool,
1244 pub ignore_eos: bool,
1245 pub samplers: String,
1246 pub expert_count: i32,
1247 pub gpu_layers: String,
1248 pub backend: String,
1249 pub llama_cpp_version: String,
1250 pub spec_type: String,
1251 pub draft_tokens: u32,
1252}
1253
1254impl WsMetrics {
1255 pub fn from_metrics(
1256 metrics: &ServerMetrics,
1257 model_name: &str,
1258 state: &str,
1259 settings: &crate::models::ModelSettings,
1260 cmd_display: Option<&str>,
1261 ) -> Self {
1262 use std::time::{SystemTime, UNIX_EPOCH};
1263 let timestamp = SystemTime::now()
1264 .duration_since(UNIX_EPOCH)
1265 .map(|d| d.as_secs())
1266 .unwrap_or(0);
1267 let gpu_layers = match settings.gpu_layers_mode {
1268 crate::models::GpuLayersMode::Auto => "Auto".to_string(),
1269 crate::models::GpuLayersMode::Specific(n) => n.to_string(),
1270 crate::models::GpuLayersMode::All => "All".to_string(),
1271 };
1272 Self {
1273 model_name: model_name.to_string(),
1274 loaded: metrics.loaded,
1275 state: state.to_string(),
1276 tps: metrics.tps,
1277 prompt_tps: metrics.prompt_tps,
1278 ctx_used: metrics.ctx_used,
1279 ctx_max: metrics.ctx_max,
1280 cpu_usage: metrics.cpu_usage,
1281 gpu_mem_used: metrics.gpu_mem_used,
1282 gpu_mem_total: metrics.gpu_mem_total,
1283 ram_used: metrics.ram_used,
1284 latency_per_token_ms: metrics.latency_per_token_ms,
1285 decoded_tokens: metrics.decoded_tokens,
1286 gen_tps: metrics.gen_tps,
1287 timestamp,
1288 cmd_display: cmd_display.map(String::from),
1289 threads: settings.threads,
1290 threads_batch: settings.threads_batch,
1291 context_length: settings.context_length,
1292 ubatch_size: settings.ubatch_size,
1293 batch_size: settings.batch_size,
1294 temperature: settings.temperature,
1295 top_k: settings.top_k as u32,
1296 top_p: settings.top_p,
1297 min_p: settings.min_p,
1298 typical_p: settings.typical_p,
1299 seed: settings.seed,
1300 repeat_penalty: settings.repeat_penalty,
1301 repeat_last_n: settings.repeat_last_n,
1302 presence_penalty: settings.presence_penalty,
1303 frequency_penalty: settings.frequency_penalty,
1304 mirostat: Some(match settings.mirostat {
1305 crate::models::Mirostat::Off => 0,
1306 crate::models::Mirostat::V1 => 1,
1307 crate::models::Mirostat::Mirostat2 => 2,
1308 }),
1309 mirostat_lr: Some(settings.mirostat_lr),
1310 mirostat_ent: Some(settings.mirostat_ent),
1311 max_tokens: settings.max_tokens,
1312 flash_attn: settings.flash_attn,
1313 kv_cache_offload: settings.kv_cache_offload,
1314 cache_type_k: settings.cache_type_k.map(|k| k.to_string()),
1315 cache_type_v: settings.cache_type_v.map(|k| k.to_string()),
1316 uniform_cache: settings.uniform_cache,
1317 mlock: settings.mlock,
1318 mmap: settings.mmap,
1319 embedding: settings.embedding,
1320 jinja: settings.jinja,
1321 ignore_eos: settings.ignore_eos,
1322 samplers: settings.samplers.to_string(),
1323 expert_count: settings.expert_count,
1324 gpu_layers,
1325 backend: settings.backend.to_string(),
1326 llama_cpp_version: settings.get_active_backend_version_display().to_string(),
1327 spec_type: settings.spec_type.clone(),
1328 draft_tokens: settings.draft_tokens,
1329 }
1330 }
1331}
1332
1333pub fn estimate_vram_mib(
1345 model_mib: u64,
1346 settings: &ModelSettings,
1347 total_layers: u32,
1348 hidden_size_opt: Option<u32>,
1349 n_head_opt: Option<u32>,
1350 n_kv_head_opt: Option<u32>,
1351 gpu_mem_total_mib: u64,
1352) -> u64 {
1353 let model_mib_f = model_mib as f64;
1354
1355 let gpu_layers = match settings.gpu_layers_mode {
1357 GpuLayersMode::Auto => {
1358 if total_layers > 0 {
1360 (total_layers as f64 * 0.6) as u32
1361 } else {
1362 20
1363 }
1364 }
1365 GpuLayersMode::Specific(n) => {
1366 if total_layers > 0 {
1367 n.min(total_layers)
1368 } else {
1369 n
1370 }
1371 }
1372 GpuLayersMode::All => {
1373 if total_layers > 0 {
1374 total_layers
1375 } else {
1376 32
1377 }
1378 }
1379 };
1380
1381 let model_vram = if total_layers > 0 && gpu_layers > 0 {
1383 model_mib_f * (gpu_layers as f64 / total_layers as f64).min(1.0)
1384 } else if gpu_layers > 0 {
1385 model_mib_f
1386 } else {
1387 0.0
1388 };
1389
1390 if matches!(settings.gpu_layers_mode, GpuLayersMode::Specific(0)) {
1391 return 0; }
1393
1394 let hidden_size = match hidden_size_opt {
1397 Some(h) => h as f64,
1398 None => {
1399 let params_est = model_mib_f / 550.0;
1400 (1024.0 * params_est.sqrt().max(1.0) * 1.5).max(512.0)
1401 }
1402 };
1403
1404 let gqa_ratio = match (n_head_opt, n_kv_head_opt) {
1410 (Some(n_head), Some(n_kv_head)) if n_head > 0 => n_kv_head as f64 / n_head as f64,
1411 _ => 1.0, };
1413
1414 let flash_attn_factor = if settings.flash_attn { 0.5 } else { 1.0 };
1417
1418 let uniform_cache_factor = if settings.uniform_cache {
1420 1.0 / settings.parallel as f64
1421 } else {
1422 1.0
1423 };
1424
1425 let effective_ctx = settings.context_length as f64 * settings.rope_scale as f64;
1427
1428 let kv_mib = (2.0
1435 * hidden_size
1436 * effective_ctx
1437 * total_layers as f64
1438 * gqa_ratio
1439 * gpu_layers as f64
1440 / total_layers as f64 * flash_attn_factor
1442 * uniform_cache_factor
1443 * kv_quant_bytes(
1444 settings.cache_type_k.unwrap_or(CacheTypeK::F16),
1445 settings.cache_type_v.unwrap_or(CacheTypeV::F16)
1446 ))
1447 / (1024.0 * 1024.0);
1448
1449 let activation_mib = (settings.batch_size as f64 * hidden_size * 8.0) / (1024.0 * 1024.0);
1452
1453 let fixed_overhead = if gpu_mem_total_mib > 0 {
1456 gpu_mem_total_mib as f64 * 0.038
1457 } else {
1458 500.0
1459 };
1460
1461 let total_mib = model_vram + kv_mib + activation_mib + fixed_overhead + 550.0;
1462
1463 total_mib.ceil() as u64
1464}
1465
1466fn kv_quant_bytes(k_type: CacheQuantType, v_type: CacheQuantType) -> f64 {
1471 let get_bytes = |t: CacheQuantType| match t {
1472 CacheQuantType::F32 => 4.0,
1473 CacheQuantType::F16 | CacheQuantType::BF16 => 2.0,
1474 CacheQuantType::Q8_0 => 1.0,
1475 CacheQuantType::Q5_0 | CacheQuantType::Q5_1 => 0.625, CacheQuantType::Q4_0 | CacheQuantType::Q4_1 | CacheQuantType::Iq4Nl => 0.5, };
1478 (get_bytes(k_type) + get_bytes(v_type)) / 2.0
1479}
1480
1481impl ModelSettings {
1482 pub fn is_dirty(&self, other: &Self) -> bool {
1485 self != other
1486 }
1487}
1488
1489#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1491pub struct BenchTuneConfig {
1492 pub model_path: PathBuf,
1493 pub num_iterations: u32,
1494 pub prompt: String,
1495 pub params_to_test: Vec<BenchTuneParam>,
1496 pub test_duration: Duration,
1497 pub bench_mode: BenchTuneMode,
1498 pub n_predict: u32,
1499 pub chat_template_kwargs: Option<String>,
1500 pub test_timeout: Duration,
1501}
1502
1503#[derive(Debug, Clone, Serialize, Deserialize)]
1504pub struct BenchTuneParam {
1505 pub name: String,
1506 pub min: f64,
1507 pub max: f64,
1508 pub step: f64,
1509 pub enabled: bool,
1510 pub variants: Vec<String>,
1511}
1512
1513impl PartialEq for BenchTuneParam {
1514 fn eq(&self, other: &Self) -> bool {
1515 self.name == other.name
1516 && self.min.to_bits() == other.min.to_bits()
1517 && self.max.to_bits() == other.max.to_bits()
1518 && self.step.to_bits() == other.step.to_bits()
1519 && self.enabled == other.enabled
1520 && self.variants == other.variants
1521 }
1522}
1523impl Eq for BenchTuneParam {}
1524
1525#[derive(Debug, Clone, Serialize, Deserialize)]
1526pub struct BenchTuneParamValue {
1527 pub temperature: Option<f64>,
1528 pub top_p: Option<f64>,
1529 pub top_k: Option<i64>,
1530 pub repeat_penalty: Option<f64>,
1531 pub context_length: Option<u32>,
1532 pub batch_size: Option<u32>,
1533 pub flash_attn: Option<bool>,
1534 pub threads: Option<u32>,
1535 pub expert_count: Option<i32>,
1536 pub spec_type: Option<String>,
1537 pub draft_tokens: Option<u32>,
1538}
1539
1540impl PartialEq for BenchTuneParamValue {
1541 fn eq(&self, other: &Self) -> bool {
1542 self.temperature.map(|v| v.to_bits()) == other.temperature.map(|v| v.to_bits())
1543 && self.top_p.map(|v| v.to_bits()) == other.top_p.map(|v| v.to_bits())
1544 && self.top_k == other.top_k
1545 && self.repeat_penalty.map(|v| v.to_bits()) == other.repeat_penalty.map(|v| v.to_bits())
1546 && self.context_length == other.context_length
1547 && self.batch_size == other.batch_size
1548 && self.flash_attn == other.flash_attn
1549 && self.threads == other.threads
1550 && self.expert_count == other.expert_count
1551 && self.spec_type == other.spec_type
1552 && self.draft_tokens == other.draft_tokens
1553 }
1554}
1555impl Eq for BenchTuneParamValue {}
1556
1557#[derive(Debug, Clone, Serialize, Deserialize)]
1558pub struct BenchTuneResult {
1559 pub params: BenchTuneParamValue,
1560 pub metrics: BenchTuneMetrics,
1561 pub outputs: Vec<String>,
1562 pub per_iteration_metrics: Vec<BenchTuneMetrics>,
1563 pub base_settings: Option<ModelSettings>,
1564 pub server_command: Option<String>,
1565}
1566
1567#[derive(Debug, Clone, Serialize, Deserialize)]
1568pub struct BenchTuneMetrics {
1569 pub prompt_tps: f64,
1570 pub generation_tps: f64,
1571 pub combined_tps: f64,
1572 pub latency_per_token: f64,
1573 pub prompt_processing_time: f64,
1574}
1575
1576#[derive(Debug, Clone, Serialize, Deserialize)]
1577pub enum BenchTuneStatus {
1578 Running {
1579 current: usize,
1580 total: usize,
1581 progress: f32,
1582 current_params: BenchTuneParamValue,
1583 },
1584 Completed {
1585 total_tests: usize,
1586 successful_tests: usize,
1587 elapsed: Duration,
1588 },
1589 PartiallyCompleted {
1590 total_tests: usize,
1591 successful_tests: usize,
1592 failed_tests: usize,
1593 elapsed: Duration,
1594 },
1595 Cancelled {
1596 total_tests: usize,
1597 successful_tests: usize,
1598 failed_tests: usize,
1599 elapsed: Duration,
1600 },
1601 Error {
1602 error: String,
1603 },
1604}
1605
1606#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
1607pub enum BenchTuneMode {
1608 RuntimeOnly,
1610 #[default]
1612 Full,
1613}
1614
1615#[derive(Debug, Clone)]
1617pub enum BenchTuneProgress {
1618 Running {
1620 current: usize,
1621 total: usize,
1622 progress: f32,
1623 current_params: BenchTuneParamValue,
1624 },
1625 Completed {
1627 total_tests: usize,
1628 successful_tests: usize,
1629 elapsed: Duration,
1630 },
1631 PartiallyCompleted {
1633 total_tests: usize,
1634 successful_tests: usize,
1635 failed_tests: usize,
1636 elapsed: Duration,
1637 },
1638 Cancelled {
1640 total_tests: usize,
1641 successful_tests: usize,
1642 failed_tests: usize,
1643 elapsed: Duration,
1644 },
1645 Error { error: String },
1647}
1648
1649impl BenchTuneProgress {
1650 pub fn from_status(status: &BenchTuneStatus) -> Option<Self> {
1651 match status {
1652 BenchTuneStatus::Running {
1653 current,
1654 total,
1655 progress,
1656 current_params,
1657 } => Some(BenchTuneProgress::Running {
1658 current: *current,
1659 total: *total,
1660 progress: *progress,
1661 current_params: current_params.clone(),
1662 }),
1663 BenchTuneStatus::Completed {
1664 total_tests,
1665 successful_tests,
1666 elapsed,
1667 } => Some(BenchTuneProgress::Completed {
1668 total_tests: *total_tests,
1669 successful_tests: *successful_tests,
1670 elapsed: *elapsed,
1671 }),
1672 BenchTuneStatus::PartiallyCompleted {
1673 total_tests,
1674 successful_tests,
1675 failed_tests,
1676 elapsed,
1677 } => Some(BenchTuneProgress::PartiallyCompleted {
1678 total_tests: *total_tests,
1679 successful_tests: *successful_tests,
1680 failed_tests: *failed_tests,
1681 elapsed: *elapsed,
1682 }),
1683 BenchTuneStatus::Cancelled {
1684 total_tests,
1685 successful_tests,
1686 failed_tests,
1687 elapsed,
1688 } => Some(BenchTuneProgress::Cancelled {
1689 total_tests: *total_tests,
1690 successful_tests: *successful_tests,
1691 failed_tests: *failed_tests,
1692 elapsed: *elapsed,
1693 }),
1694 BenchTuneStatus::Error { error } => Some(BenchTuneProgress::Error {
1695 error: error.clone(),
1696 }),
1697 }
1698 }
1699}
1700
1701impl BenchTuneConfig {
1702 pub fn new(model_path: PathBuf, num_iterations: u32, prompt: String) -> Self {
1703 Self {
1704 model_path,
1705 num_iterations,
1706 prompt,
1707 params_to_test: vec![
1708 BenchTuneParam {
1709 name: "temperature".to_string(),
1710 min: 0.4,
1711 max: 1.0,
1712 step: 0.1,
1713 enabled: false,
1714 variants: vec![],
1715 },
1716 BenchTuneParam {
1717 name: "top_p".to_string(),
1718 min: 0.8,
1719 max: 1.0,
1720 step: 0.1,
1721 enabled: false,
1722 variants: vec![],
1723 },
1724 BenchTuneParam {
1725 name: "top_k".to_string(),
1726 min: 10.0,
1727 max: 40.0,
1728 step: 5.0,
1729 enabled: false,
1730 variants: vec![],
1731 },
1732 BenchTuneParam {
1733 name: "repeat_penalty".to_string(),
1734 min: 1.0,
1735 max: 1.5,
1736 step: 0.1,
1737 enabled: false,
1738 variants: vec![],
1739 },
1740 BenchTuneParam {
1741 name: "flash_attn".to_string(),
1742 min: 0.0,
1743 max: 1.0,
1744 step: 1.0,
1745 enabled: false,
1746 variants: vec![],
1747 },
1748 BenchTuneParam {
1749 name: "threads".to_string(),
1750 min: 4.0,
1751 max: 16.0,
1752 step: 4.0,
1753 enabled: false,
1754 variants: vec![],
1755 },
1756 BenchTuneParam {
1757 name: "batch_size".to_string(),
1758 min: 512.0,
1759 max: 2048.0,
1760 step: 512.0,
1761 enabled: false,
1762 variants: vec![],
1763 },
1764 BenchTuneParam {
1765 name: "expert_count".to_string(),
1766 min: -1.0,
1767 max: 4.0,
1768 step: 1.0,
1769 enabled: false,
1770 variants: vec![],
1771 },
1772 BenchTuneParam {
1773 name: "spec_type".to_string(),
1774 min: 0.0,
1775 max: 8.0,
1776 step: 1.0,
1777 enabled: false,
1778 variants: vec![
1779 "Off".to_string(),
1780 "draft-mtp".to_string(),
1781 "draft-simple".to_string(),
1782 "draft-eagle3".to_string(),
1783 "ngram-simple".to_string(),
1784 "ngram-map-k".to_string(),
1785 "ngram-map-k4v".to_string(),
1786 "ngram-mod".to_string(),
1787 "ngram-cache".to_string(),
1788 ],
1789 },
1790 BenchTuneParam {
1791 name: "draft_tokens".to_string(),
1792 min: 0.0,
1793 max: 8.0,
1794 step: 1.0,
1795 enabled: false,
1796 variants: vec![],
1797 },
1798 ],
1799 test_duration: Duration::from_secs(30),
1800 bench_mode: BenchTuneMode::default(),
1801 n_predict: 512,
1802 chat_template_kwargs: Some(r#"{"enable_thinking": false}"#.to_string()),
1803 test_timeout: Duration::from_secs(60),
1804 }
1805 }
1806
1807 pub fn generate_combinations(&self) -> Vec<BenchTuneParamValue> {
1809 let mut temp_values = vec![None];
1810 let mut top_p_values = vec![None];
1811 let mut top_k_values = vec![None];
1812 let mut repeat_penalty_values = vec![None];
1813 let mut flash_attn_values = vec![None];
1814 let mut threads_values = vec![None];
1815 let mut batch_size_values = vec![None];
1816 let mut expert_count_values = vec![None];
1817 let mut selected_spec_type = None;
1818 if let Some(p) = self.params_to_test.iter().find(|p| p.name == "spec_type") {
1819 let base_idx = (p.min as usize).min(p.variants.len().saturating_sub(1));
1820 let val = &p.variants[base_idx];
1821 if val != "Off" {
1822 selected_spec_type = Some(val.clone());
1823 }
1824 }
1825 let mut spec_type_values = vec![selected_spec_type];
1826 let mut draft_tokens_values = vec![None];
1827
1828 let spec_type_options = vec![
1829 "Off".to_string(),
1830 "draft-mtp".to_string(),
1831 "draft-simple".to_string(),
1832 "draft-eagle3".to_string(),
1833 "ngram-simple".to_string(),
1834 "ngram-map-k".to_string(),
1835 "ngram-map-k4v".to_string(),
1836 "ngram-mod".to_string(),
1837 "ngram-cache".to_string(),
1838 ];
1839
1840 for p in &self.params_to_test {
1841 if !p.enabled {
1842 continue;
1843 }
1844
1845 let vals: Vec<f64> = {
1846 let step_count = ((p.max - p.min) / p.step).ceil() as usize;
1847 (0..=step_count)
1848 .map(|i| (p.min + (i as f64 * p.step)).min(p.max))
1849 .collect()
1850 };
1851
1852 match p.name.as_str() {
1853 "temperature" => temp_values = vals.into_iter().map(Some).collect(),
1854 "top_p" => top_p_values = vals.into_iter().map(Some).collect(),
1855 "top_k" => top_k_values = vals.into_iter().map(|v| Some(v as i64)).collect(),
1856 "repeat_penalty" => repeat_penalty_values = vals.into_iter().map(Some).collect(),
1857 "flash_attn" => {
1858 flash_attn_values = vals.into_iter().map(|v| Some(v >= 0.5)).collect()
1859 }
1860 "threads" => threads_values = vals.into_iter().map(|v| Some(v as u32)).collect(),
1861 "batch_size" => {
1862 batch_size_values = vals.into_iter().map(|v| Some(v as u32)).collect()
1863 }
1864 "expert_count" => {
1865 expert_count_values = vals.into_iter().map(|v| Some(v as i32)).collect()
1866 }
1867 "spec_type" => {
1868 if !p.variants.is_empty() {
1869 spec_type_values = p.variants.clone().into_iter().map(Some).collect();
1870 } else {
1871 let step_count = ((p.max - p.min) / p.step).ceil() as usize;
1872 spec_type_values = (0..=step_count)
1873 .map(|i| {
1874 let idx = i.min(spec_type_options.len() - 1);
1875 Some(spec_type_options[idx].clone())
1876 })
1877 .collect()
1878 }
1879 }
1880 "draft_tokens" => {
1881 draft_tokens_values = vals.into_iter().map(|v| Some(v as u32)).collect()
1882 }
1883 _ => {}
1884 }
1885 }
1886
1887 let mut combinations = Vec::new();
1888 for &temp in &temp_values {
1889 for &top_p in &top_p_values {
1890 for &top_k in &top_k_values {
1891 for &rp in &repeat_penalty_values {
1892 for &fa in &flash_attn_values {
1893 for &th in &threads_values {
1894 for &bs in &batch_size_values {
1895 for &ec in &expert_count_values {
1896 for st in &spec_type_values {
1897 for &dt in &draft_tokens_values {
1898 combinations.push(BenchTuneParamValue {
1899 temperature: temp,
1900 top_p,
1901 top_k,
1902 repeat_penalty: rp,
1903 context_length: None,
1904 batch_size: bs,
1905 flash_attn: fa,
1906 threads: th,
1907 expert_count: ec,
1908 spec_type: st.clone(),
1909 draft_tokens: dt,
1910 });
1911 }
1912 }
1913 }
1914 }
1915 }
1916 }
1917 }
1918 }
1919 }
1920 }
1921
1922 combinations
1923 }
1924
1925 pub fn get_total_tests_count(&self) -> usize {
1927 self.generate_combinations().len()
1928 }
1929
1930 pub fn get_num_combinations(&self) -> usize {
1932 let mut count: u64 = 1;
1933 for p in &self.params_to_test {
1934 if !p.enabled {
1935 continue;
1936 }
1937 let vals = if p.name == "flash_attn" {
1938 2 } else if !p.variants.is_empty() {
1940 p.variants.len()
1941 } else if p.name == "spec_type" {
1942 let step_count = ((p.max - p.min) / p.step).ceil() as usize;
1943 (step_count + 1).min(9)
1944 } else {
1945 let step_count = ((p.max - p.min) / p.step).ceil() as usize;
1946 step_count + 1
1947 };
1948 count *= vals as u64;
1949 }
1950 count as usize
1951 }
1952}
1953
1954#[cfg(test)]
1957mod field_count_tests {
1958 use super::*;
1959
1960 #[test]
1964 fn test_model_settings_field_count() {
1965 let s = ModelSettings::default();
1969 let field_count = count_model_settings_fields(&s);
1970 assert_eq!(
1971 field_count, 75,
1972 "ModelSettings has {} fields (expected 75). \
1973 Update the checklist at src/models.rs:665 and all locations listed there.",
1974 field_count
1975 );
1976 }
1977
1978 #[allow(clippy::too_many_lines)]
1982 fn count_model_settings_fields(s: &ModelSettings) -> usize {
1983 let _ = (
1984 &s.context_length,
1985 &s.threads,
1986 &s.threads_batch,
1987 &s.batch_size,
1988 &s.ubatch_size,
1989 &s.parallel,
1990 &s.max_concurrent_predictions,
1991 &s.uniform_cache,
1992 &s.kv_cache_offload,
1993 &s.cache_type_k,
1994 &s.cache_type_v,
1995 &s.keep,
1996 &s.swa_full,
1997 &s.mlock,
1998 &s.mmap,
1999 &s.numa,
2000 &s.system_prompt,
2001 &s.system_prompt_preset_name,
2002 &s.gpu_layers_mode,
2003 &s.split_mode,
2004 &s.tensor_split,
2005 &s.main_gpu,
2006 &s.fit,
2007 &s.lora,
2008 &s.lora_scaled,
2009 &s.rpc,
2010 &s.embedding,
2011 &s.flash_attn,
2012 &s.expert_count,
2013 &s.jinja,
2014 &s.chat_template,
2015 &s.chat_template_kwargs,
2016 &s.seed,
2017 &s.temperature,
2018 &s.top_k,
2019 &s.top_p,
2020 &s.min_p,
2021 &s.typical_p,
2022 &s.mirostat,
2023 &s.mirostat_lr,
2024 &s.mirostat_ent,
2025 &s.ignore_eos,
2026 &s.samplers,
2027 &s.repeat_penalty,
2028 &s.repeat_last_n,
2029 &s.presence_penalty,
2030 &s.frequency_penalty,
2031 &s.dry_multiplier,
2032 &s.dry_base,
2033 &s.dry_allowed_length,
2034 &s.dry_penalty_last_n,
2035 &s.rope_scaling,
2036 &s.rope_scale,
2037 &s.rope_freq_base,
2038 &s.rope_freq_scale,
2039 &s.rope_yarn_enabled,
2040 &s.host,
2041 &s.port,
2042 &s.timeout,
2043 &s.cache_prompt,
2044 &s.cache_reuse,
2045 &s.webui,
2046 &s.max_tokens,
2047 &s.cache_type,
2048 &s.backend,
2049 &s.llama_cpp_version_cpu,
2050 &s.llama_cpp_version_vulkan,
2051 &s.llama_cpp_version_rocm,
2052 &s.llama_cpp_version_rocm_lemonade,
2053 &s.llama_cpp_version_cuda,
2054 &s.api_endpoint_enabled,
2055 &s.api_endpoint_port,
2056 &s.spec_type,
2057 &s.draft_tokens,
2058 &s.tags,
2059 );
2060 75
2061 }
2062
2063 #[test]
2067 fn test_is_dirty_uses_derived_eq() {
2068 let s1 = ModelSettings::default();
2069 let s2 = ModelSettings::default();
2070 let s3 = s1.clone();
2071
2072 assert!(!s1.is_dirty(&s2));
2074 assert!(!s1.is_dirty(&s3));
2075
2076 assert_eq!(s1 != s2, s1.is_dirty(&s2));
2078 assert_eq!(s1 != s3, s1.is_dirty(&s3));
2079 }
2080
2081 #[test]
2084 fn test_from_default_params_completeness() {
2085 let dp = crate::config::DefaultParams::default();
2086 let ms: ModelSettings = dp.clone().into();
2087
2088 assert_eq!(ms.context_length, 131072);
2091 assert_eq!(ms.threads, dp.threads);
2092 assert_eq!(ms.temperature, 0.8);
2093 assert_eq!(ms.backend, dp.backend);
2096 }
2097}