1use crate::config::Profile;
2use crate::models::{CacheQuantType, GpuLayersMode, Mirostat, ModelSettings, NumMode, SplitMode};
3use crate::tui::colors::*;
4use crate::tui::format_context_k;
5use ratatui::{
6 style::{Modifier, Style},
7 text::{Line, Span},
8};
9
10pub type DisplayFn = fn(&ModelSettings) -> String;
13pub type DirtyFn = fn(&ModelSettings, &ModelSettings) -> bool;
14pub type AdjustFn = fn(&mut ModelSettings, i32, u32); pub type ApplyEditFn = fn(&mut ModelSettings, &str);
16pub type CtrlEToggleFn = fn(&mut ModelSettings);
17
18pub struct SettingField {
21 pub id: &'static str,
22 pub name: &'static str,
23 pub section: &'static str,
24 pub display: DisplayFn,
25 pub dirty: DirtyFn,
26 pub adjust: AdjustFn,
27 pub apply_edit: ApplyEditFn,
28 pub ctrl_e_toggle: Option<CtrlEToggleFn>,
29 pub is_expert: bool,
30 pub is_ultra: bool,
31 pub is_enabled: Option<fn(&ModelSettings) -> bool>,
32}
33
34impl SettingField {
35 pub fn name(&self) -> &str {
36 self.name
37 }
38
39 pub fn display(&self, settings: &ModelSettings) -> String {
40 (self.display)(settings)
41 }
42
43 pub fn is_dirty(&self, settings: &ModelSettings, cached: &ModelSettings) -> bool {
44 (self.dirty)(settings, cached)
45 }
46
47 pub fn adjust(&self, settings: &mut ModelSettings, delta: i32, context_limit: u32) {
48 (self.adjust)(settings, delta, context_limit);
49 }
50
51 pub fn apply_edit(&self, settings: &mut ModelSettings, buf: &str) {
52 (self.apply_edit)(settings, buf);
53 }
54
55 pub fn ctrl_e_toggle(&self, settings: &mut ModelSettings) {
56 if let Some(toggle) = self.ctrl_e_toggle {
57 toggle(settings);
58 }
59 }
60
61 pub fn is_new_section(&self, prev_section: Option<&str>) -> bool {
63 Some(self.section) != prev_section
64 }
65}
66
67macro_rules! make_field_fn {
74 ($fn:ident, $expert:expr, $ultra:expr, toggle) => {
75 fn $fn(
76 id: &'static str,
77 name: &'static str,
78 section: &'static str,
79 display: DisplayFn,
80 dirty: DirtyFn,
81 adjust: AdjustFn,
82 apply_edit: ApplyEditFn,
83 ctrl_e_toggle: CtrlEToggleFn,
84 _help_text: &'static str,
85 ) -> SettingField {
86 SettingField {
87 id,
88 name,
89 section,
90 display,
91 dirty,
92 adjust,
93 apply_edit,
94 ctrl_e_toggle: Some(ctrl_e_toggle),
95 is_expert: $expert,
96 is_ultra: $ultra,
97 is_enabled: None,
98 }
99 }
100 };
101 ($fn:ident, $expert:expr, $ultra:expr, @none) => {
102 fn $fn(
103 id: &'static str,
104 name: &'static str,
105 section: &'static str,
106 display: DisplayFn,
107 dirty: DirtyFn,
108 adjust: AdjustFn,
109 apply_edit: ApplyEditFn,
110 _help_text: &'static str,
111 ) -> SettingField {
112 SettingField {
113 id,
114 name,
115 section,
116 display,
117 dirty,
118 adjust,
119 apply_edit,
120 ctrl_e_toggle: None,
121 is_expert: $expert,
122 is_ultra: $ultra,
123 is_enabled: None,
124 }
125 }
126 };
127}
128
129make_field_fn!(field, false, false, @none);
130make_field_fn!(expert_field, true, false, @none);
131make_field_fn!(ultra_field, true, true, @none);
132make_field_fn!(field_with_toggle, false, false, toggle);
133make_field_fn!(expert_field_with_toggle, true, false, toggle);
134make_field_fn!(ultra_field_with_toggle, true, true, toggle);
135
136fn gpu_layers_adjust(settings: &mut ModelSettings, delta: i32, _context_limit: u32) {
139 settings.gpu_layers_mode = match (delta, &settings.gpu_layers_mode) {
140 (1, GpuLayersMode::Auto) => GpuLayersMode::Specific(1),
141 (1, GpuLayersMode::Specific(n)) => GpuLayersMode::Specific(n + 1),
142 (1, GpuLayersMode::All) => GpuLayersMode::Auto,
143 (-1, GpuLayersMode::Auto) => GpuLayersMode::Auto,
144 (-1, GpuLayersMode::Specific(n)) if *n == 0 => GpuLayersMode::Auto,
145 (-1, GpuLayersMode::Specific(n)) if *n == 1 => GpuLayersMode::Specific(0),
146 (-1, GpuLayersMode::Specific(n)) => GpuLayersMode::Specific(n - 1),
147 (-1, GpuLayersMode::All) => GpuLayersMode::All,
148 _ => settings.gpu_layers_mode,
149 };
150}
151
152fn gpu_layers_apply(settings: &mut ModelSettings, buf: &str) {
153 if let Ok(v) = buf.parse::<i32>() {
154 settings.gpu_layers_mode = if v < 0 {
155 GpuLayersMode::All
156 } else {
157 GpuLayersMode::Specific(v as u32)
158 };
159 }
160}
161
162fn toggle_mlock(settings: &mut ModelSettings) {
163 settings.mlock = !settings.mlock;
164}
165fn toggle_flash_attn(settings: &mut ModelSettings) {
166 settings.flash_attn = !settings.flash_attn;
167}
168fn toggle_auto_chat_template(settings: &mut ModelSettings) {
169 settings.auto_chat_template = !settings.auto_chat_template;
170}
171
172fn toggle_fit(settings: &mut ModelSettings) {
173 settings.fit = !settings.fit;
174}
175fn toggle_kv_cache_offload(settings: &mut ModelSettings) {
176 settings.kv_cache_offload = !settings.kv_cache_offload;
177}
178fn toggle_uniform_cache(settings: &mut ModelSettings) {
179 settings.uniform_cache = !settings.uniform_cache;
180}
181fn toggle_swa_full(settings: &mut ModelSettings) {
182 settings.swa_full = !settings.swa_full;
183}
184fn toggle_mtp(settings: &mut ModelSettings) {
185 if settings.spec_type.is_empty() {
186 settings.spec_type = "draft-mtp".to_string();
187 } else {
188 settings.spec_type = String::new();
189 }
190}
191
192fn toggle_rope_yarn_enabled(settings: &mut ModelSettings) {
193 settings.rope_yarn_enabled = !settings.rope_yarn_enabled;
194}
195fn toggle_ignore_eos(settings: &mut ModelSettings) {
196 settings.ignore_eos = !settings.ignore_eos;
197}
198fn toggle_max_tokens(settings: &mut ModelSettings) {
199 settings.max_tokens = settings.max_tokens.map_or(Some(2048), |_| None);
200}
201fn toggle_max_concurrent_predictions(settings: &mut ModelSettings) {
202 settings.max_concurrent_predictions = settings
203 .max_concurrent_predictions
204 .map_or(Some(1), |_| None);
205}
206fn toggle_cache_type_k(settings: &mut ModelSettings) {
207 settings.cache_type_k = settings
208 .cache_type_k
209 .map_or(Some(CacheQuantType::F16), |_| None);
210}
211fn toggle_cache_type_v(settings: &mut ModelSettings) {
212 settings.cache_type_v = settings
213 .cache_type_v
214 .map_or(Some(CacheQuantType::F16), |_| None);
215}
216fn toggle_expert_count(settings: &mut ModelSettings) {
217 settings.expert_count = match settings.expert_count {
218 0 => 1,
219 -1 => 0,
220 _ => -1,
221 };
222}
223fn toggle_presence_penalty(settings: &mut ModelSettings) {
224 settings.presence_penalty = settings.presence_penalty.map_or(Some(0.0), |_| None);
225}
226fn toggle_frequency_penalty(settings: &mut ModelSettings) {
227 settings.frequency_penalty = settings.frequency_penalty.map_or(Some(0.0), |_| None);
228}
229
230macro_rules! diff_int {
233 ($parts:expr, $s:expr, $c:expr, $field:ident, $label:literal) => {
234 if let Some(v) = $s.$field
235 && v != $c.$field
236 {
237 $parts.push(format!("{}={}", $label, v));
238 }
239 };
240}
241macro_rules! diff_float {
242 ($parts:expr, $s:expr, $c:expr, $field:ident, $label:literal) => {
243 if let Some(v) = $s.$field
244 && (v - $c.$field).abs() > 0.001
245 {
246 $parts.push(format!("{}={:.2}", $label, v));
247 }
248 };
249}
250macro_rules! diff_bool {
251 ($parts:expr, $s:expr, $c:expr, $field:ident, $label:literal) => {
252 if let Some(v) = $s.$field
253 && v != $c.$field
254 {
255 $parts.push(format!("{}={}", $label, v));
256 }
257 };
258}
259macro_rules! diff_string {
260 ($parts:expr, $s:expr, $c:expr, $field:ident, $label:literal) => {
261 if let Some(v) = &$s.$field
262 && v != &$c.$field
263 {
264 $parts.push(format!("{}={}", $label, v));
265 }
266 };
267}
268macro_rules! diff_enum {
269 ($parts:expr, $s:expr, $c:expr, $field:ident, $label:literal) => {
270 if let Some(ref v) = $s.$field
271 && *v != $c.$field
272 {
273 $parts.push(format!("{}={}", $label, v));
274 }
275 };
276}
277macro_rules! diff_option {
278 ($parts:expr, $s:expr, $c:expr, $field:ident, $label:literal) => {
279 if $s.$field != $c.$field {
280 if let Some(ref v) = $s.$field {
281 $parts.push(format!("{}={}", $label, v));
282 }
283 }
284 };
285}
286macro_rules! diff_option_float {
287 ($parts:expr, $s:expr, $c:expr, $field:ident, $label:literal) => {
288 if let Some(v) = $s.$field {
289 let current_val = $c.$field.unwrap_or(0.0);
290 if (v - current_val).abs() > 0.001 {
291 $parts.push(format!("{}={:.2}", $label, v));
292 }
293 }
294 };
295}
296
297pub fn all_fields() -> Vec<SettingField> {
300 vec![
301 field(
303 "system_prompt_preset_name",
304 "Prompt",
305 "Loading",
306 |s| s.system_prompt_preset_name.clone(),
307 |s, c| s.system_prompt_preset_name != c.system_prompt_preset_name,
308 |_, _, _| {},
309 |_, _| {},
310 "System prompt preset. Pre-configured prompts that shape how the model behaves (e.g., 'coder', 'assistant', 'creative'). Affects the model's personality and output style.",
311 ),
312 field(
313 "context_length",
314 "Context",
315 "Loading",
316 |s| {
317 format_context_k(s.context_length, s.rope_yarn_enabled, s.rope_scale)
318 },
319 |s, c| s.context_length != c.context_length,
320 |s, delta, ctx_limit| {
321 let mut val = (s.context_length as i32 + delta * 128).max(128) as u32;
322 if ctx_limit > 0 {
323 val = val.min(ctx_limit);
324 }
325 s.context_length = val;
326 },
327 |s, buf| {
328 if let Ok(v) = buf.parse::<u32>() {
329 s.context_length = v.max(128);
330 }
331 },
332 "Context window size in tokens. Determines how much of the conversation history is kept in memory. A larger context allows longer conversations but uses more RAM. Typical: 32k-256k depending on model and RAM.",
333 ),
334 expert_field_with_toggle(
335 "rope_yarn_enabled",
336 "Yarn RoPE",
337 "Loading",
338 |s| s.rope_yarn_enabled.to_string(),
339 |s, c| s.rope_yarn_enabled != c.rope_yarn_enabled,
340 |_, _, _| {},
341 |_, _| {},
342 toggle_rope_yarn_enabled,
343 "Enable YaRN (Yet another RoPE extensioN) for scaling context beyond training limits. YaRN uses a frequency rescaling technique to handle longer contexts. Toggle on/off with Enter.",
344 ),
345 {
346 let mut f = expert_field(
347 "yarn_params",
348 "Yarn Params",
349 "Loading",
350 |s| {
351 format!(
352 "scale={:.2} base={:.2} scale_f={:.2}",
353 s.rope_scale, s.rope_freq_base, s.rope_freq_scale
354 )
355 },
356 |s, c| {
357 s.rope_scale != c.rope_scale
358 || s.rope_freq_base != c.rope_freq_base
359 || s.rope_freq_scale != c.rope_freq_scale
360 },
361 |_, _, _| {},
362 |_, _| {},
363 "YaRN configuration: rope_scale (context multiplier), rope_freq_base (frequency base), rope_freq_scale (frequency scaling). Press Enter to open the YaRN parameter editor.",
364 );
365 f.is_enabled = Some(|s| s.rope_yarn_enabled);
366 f
367 },
368 ultra_field(
369 "threads_batch",
370 "Threads Batch",
371 "Loading",
372 |s| s.threads_batch.to_string(),
373 |s, c| s.threads_batch != c.threads_batch,
374 |s, delta, _| {
375 s.threads_batch = (s.threads_batch as i32 + delta).max(1) as u32;
376 },
377 |s, buf| {
378 if let Ok(v) = buf.parse::<u32>() {
379 s.threads_batch = v.max(1);
380 }
381 },
382 "CPU threads for batch processing (1 to 32). Separate from Threads (inference threads). Keep equal for most workloads, or reduce batch threads to lower CPU usage during batch operations.",
383 ),
384 ultra_field(
385 "ubatch_size",
386 "UBatch Size",
387 "Loading",
388 |s| s.ubatch_size.to_string(),
389 |s, c| s.ubatch_size != c.ubatch_size,
390 |s, delta, _| {
391 s.ubatch_size = (s.ubatch_size as i32 + delta * 64).max(1) as u32;
392 },
393 |s, buf| {
394 if let Ok(v) = buf.parse::<u32>() {
395 s.ubatch_size = v.max(1);
396 }
397 },
398 "Unlimited batch size for prompt processing. Larger values improve prompt evaluation throughput but use more RAM. Typical: 512-2048. Set to 0 to match context_length.",
399 ),
400 ultra_field(
401 "keep",
402 "Keep",
403 "Loading",
404 |s| s.keep.to_string(),
405 |s, c| s.keep != c.keep,
406 |s, delta, _| {
407 s.keep = (s.keep + delta).max(0);
408 },
409 |s, buf| {
410 if let Ok(v) = buf.parse::<i32>() {
411 s.keep = v;
412 }
413 },
414 "Number of layers to keep in memory when swapping (negative = all). Useful for fast reloading of the same model. Typical: -1 (all) or 0 (none).",
415 ),
416 field_with_toggle(
417 "mlock",
418 "Keep in memory (mlock)",
419 "Loading",
420 |s| s.mlock.to_string(),
421 |s, c| s.mlock != c.mlock,
422 |_, _, _| {},
423 |_, _| {},
424 toggle_mlock,
425 "Lock model weights in RAM (mlock). Prevents the OS from swapping model weights to disk. Slows model load time but ensures faster inference once loaded. Useful for repeated use.",
426 ),
427 expert_field_with_toggle(
428 "chat_template",
429 "Chat Template",
430 "Loading",
431 |s| {
432 if s.auto_chat_template {
433 "Auto (detect)".to_string()
434 } else if let Some(ref t) = s.chat_template {
435 if t.ends_with(".jinja") {
436 let filename = std::path::Path::new(t)
437 .file_name()
438 .and_then(|n| n.to_str())
439 .unwrap_or(t);
440 filename.to_string()
441 } else {
442 t.clone()
443 }
444 } else {
445 "None".to_string()
446 }
447 },
448 |s, c| {
449 s.auto_chat_template != c.auto_chat_template || s.chat_template != c.chat_template
450 },
451 |_, _, _| {},
452 |_, _| {},
453 toggle_auto_chat_template,
454 "Select chat template: Auto (detect) uses GGUF architecture metadata, specific template names use llama.cpp built-in templates, Select a Template file picks a .jinja file from any directory, None disables template. Press Enter to open picker.",
455 ),
456 expert_field(
457 "numa",
458 "NUMA",
459 "Loading",
460 |s| s.numa.to_string(),
461 |s, c| s.numa != c.numa,
462 |s, delta, _| {
463 let mut val = s.numa;
464 val = match (delta, val) {
465 (1, NumMode::None) => NumMode::Distribute,
466 (1, NumMode::Distribute) => NumMode::Isolate,
467 (1, NumMode::Isolate) => NumMode::Numactl,
468 (1, NumMode::Numactl) => NumMode::None,
469 (-1, NumMode::None) => NumMode::Numactl,
470 (-1, NumMode::Distribute) => NumMode::None,
471 (-1, NumMode::Isolate) => NumMode::Distribute,
472 (-1, NumMode::Numactl) => NumMode::Isolate,
473 _ => val,
474 };
475 s.numa = val;
476 },
477 |_, _| {},
478 "NUMA (Non-Uniform Memory Access) strategy: None, Distribute, Isolate, or Numactl. Affects CPU thread affinity on multi-socket systems. None = default.",
479 ),
480 field(
482 "gpu_layers_mode",
483 "GPU Layers",
484 "GPU Offload",
485 |s| match s.gpu_layers_mode {
486 GpuLayersMode::Auto => "Auto".to_string(),
487 GpuLayersMode::Specific(n) => n.to_string(),
488 GpuLayersMode::All => "All".to_string(),
489 },
490 |s, c| s.gpu_layers_mode != c.gpu_layers_mode,
491 gpu_layers_adjust,
492 gpu_layers_apply,
493 "How many model layers to offload to GPU. Arrow keys cycle: Auto → 1 → 2 → ... → N → All → Auto. Auto lets llama.cpp decide based on VRAM. All loads every layer (999). Specific number sets exact offload count.",
494 ),
495 ultra_field(
496 "split_mode",
497 "Split Mode",
498 "GPU Offload",
499 |s| s.split_mode.to_string(),
500 |s, c| s.split_mode != c.split_mode,
501 |s, delta, _| {
502 let mut val = s.split_mode;
503 val = match (delta, val) {
504 (1, SplitMode::None) => SplitMode::Layer,
505 (1, SplitMode::Layer) => SplitMode::Row,
506 (1, SplitMode::Row) => SplitMode::Tensor,
507 (1, SplitMode::Tensor) => SplitMode::None,
508 (-1, SplitMode::None) => SplitMode::Tensor,
509 (-1, SplitMode::Layer) => SplitMode::None,
510 (-1, SplitMode::Row) => SplitMode::Layer,
511 (-1, SplitMode::Tensor) => SplitMode::Row,
512 _ => val,
513 };
514 s.split_mode = val;
515 },
516 |_, _| {},
517 "GPU split strategy: None, Layer (default), Row, or Tensor. Controls how model layers are distributed across multiple GPUs. Layer splits by layer count, Row/Tensor split by matrix dimensions for multi-GPU setups.",
518 ),
519 ultra_field(
520 "tensor_split",
521 "Tensor Split",
522 "GPU Offload",
523 |s| s.tensor_split.clone(),
524 |s, c| s.tensor_split != c.tensor_split,
525 |_, _, _| {},
526 |_, _| {},
527 "Fraction of model weights to load on each GPU (colon-separated for multi-GPU, e.g., '0.5:0.5'). For single GPU, leave empty. Press Enter to edit.",
528 ),
529 expert_field(
530 "main_gpu",
531 "Main GPU",
532 "GPU Offload",
533 |s| s.main_gpu.to_string(),
534 |s, c| s.main_gpu != c.main_gpu,
535 |s, delta, _| {
536 s.main_gpu = (s.main_gpu + delta).max(0);
537 },
538 |s, buf| {
539 if let Ok(v) = buf.parse::<i32>() {
540 s.main_gpu = v;
541 }
542 },
543 "Index of the main GPU (0-based). Handles initial model loading and some computations. Typical: 0 for single GPU, 0 for primary in multi-GPU setups.",
544 ),
545 field_with_toggle(
546 "fit",
547 "Fit",
548 "GPU Offload",
549 |s| s.fit.to_string(),
550 |s, c| s.fit != c.fit,
551 |_, _, _| {},
552 |_, _| {},
553 toggle_fit,
554 "Automatically adjust arguments to fit device memory. Toggle on/off with Enter.",
555 ),
556 field_with_toggle(
557 "flash_attn",
558 "Flash Attention",
559 "GPU Offload",
560 |s| s.flash_attn.to_string(),
561 |s, c| s.flash_attn != c.flash_attn,
562 |_, _, _| {},
563 |_, _| {},
564 toggle_flash_attn,
565 "Enable Flash Attention (flash-attn) for faster inference. Requires compatible GPU (Ampere+ / Ada). Significantly speeds up long-context inference. Only works with certain GGUF formats.",
566 ),
567 field_with_toggle(
568 "kv_cache_offload",
569 "KV Cache Offload",
570 "GPU Offload",
571 |s| s.kv_cache_offload.to_string(),
572 |s, c| s.kv_cache_offload != c.kv_cache_offload,
573 |_, _, _| {},
574 |_, _| {},
575 toggle_kv_cache_offload,
576 "Offload KV cache to RAM when GPU memory is full. Allows larger batch sizes and contexts at the cost of some speed. Useful when VRAM is limited but you still want longer conversations.",
577 ),
578 {
580 let mut f = expert_field(
581 "cache_type_k",
582 "Cache Type K",
583 "GPU Offload",
584 |s| {
585 s.cache_type_k
586 .map(|v| v.to_string())
587 .unwrap_or_else(|| "Disabled".to_string())
588 },
589 |s, c| s.cache_type_k != c.cache_type_k,
590 |s, delta, _| {
591 let mut val = s.cache_type_k.unwrap_or(CacheQuantType::F16);
592 val = if delta > 0 { val.next() } else { val.prev() };
593 s.cache_type_k = Some(val);
594 },
595 |s, buf| {
596 if let Ok(n) = buf.parse::<u8>() {
597 s.cache_type_k = Some(CacheQuantType::from_u8(n));
598 }
599 },
600 "Quantization precision for KV cache keys. Lower precision (e.g., Q4, Q8) saves VRAM but may slightly reduce quality. Default is usually FP16. Use lower values if running out of VRAM.",
601 );
602 f.ctrl_e_toggle = Some(toggle_cache_type_k);
603 f
604 },
605 {
606 let mut f = expert_field(
607 "cache_type_v",
608 "Cache Type V",
609 "GPU Offload",
610 |s| {
611 s.cache_type_v
612 .map(|v| v.to_string())
613 .unwrap_or_else(|| "Disabled".to_string())
614 },
615 |s, c| s.cache_type_v != c.cache_type_v,
616 |s, delta, _| {
617 let mut val = s.cache_type_v.unwrap_or(CacheQuantType::F16);
618 val = if delta > 0 { val.next() } else { val.prev() };
619 s.cache_type_v = Some(val);
620 },
621 |s, buf| {
622 if let Ok(n) = buf.parse::<u8>() {
623 s.cache_type_v = Some(CacheQuantType::from_u8(n));
624 }
625 },
626 "Quantization precision for KV cache values. Lower precision (e.g., Q4, Q8) saves VRAM but may slightly reduce quality. Default is usually FP16. Use lower values if running out of VRAM.",
627 );
628 f.ctrl_e_toggle = Some(toggle_cache_type_v);
629 f
630 },
631 expert_field_with_toggle(
632 "expert_count",
633 "Active Experts",
634 "GPU Offload",
635 |s| {
636 if s.expert_count > 0 {
637 s.expert_count.to_string()
638 } else if s.expert_count == -1 {
639 "Auto".to_string()
640 } else {
641 "Disabled".to_string()
642 }
643 },
644 |s, c| s.expert_count != c.expert_count,
645 |s, delta, _| {
646 s.expert_count = (s.expert_count + delta).clamp(-1, 99);
647 },
648 |s, buf| {
649 if let Ok(v) = buf.parse::<i32>() {
650 s.expert_count = v.clamp(-1, 99);
651 }
652 },
653 toggle_expert_count,
654 "Number of MoE (Mixture of Experts) experts to activate per token. -1 = auto (all active). Reducing this speeds up inference for MoE models like Mixtral but may reduce quality. Typical: 2-8 for Mixtral.",
655 ),
656 field(
658 "batch_size",
659 "Eval Batch",
660 "Evaluation",
661 |s| s.batch_size.to_string(),
662 |s, c| s.batch_size != c.batch_size,
663 |s, delta, _| {
664 s.batch_size = (s.batch_size as i32 + delta * 64).max(1) as u32;
665 },
666 |s, buf| {
667 if let Ok(v) = buf.parse::<u32>() {
668 s.batch_size = v.max(1);
669 }
670 },
671 "Batch size for evaluation (inference). Larger batches use more VRAM but can improve throughput via parallelism. Small values (1-8) for low VRAM, larger (16-128) for high VRAM setups.",
672 ),
673 field_with_toggle(
674 "uniform_cache",
675 "Unified KV",
676 "Evaluation",
677 |s| s.uniform_cache.to_string(),
678 |s, c| s.uniform_cache != c.uniform_cache,
679 |_, _, _| {},
680 |_, _| {},
681 toggle_uniform_cache,
682 "Share KV cache across sequences. Reduces VRAM usage when running multiple requests by reusing allocated cache. May slightly reduce performance but enables more concurrent users.",
683 ),
684 expert_field(
685 "cache_reuse",
686 "Cache Reuse",
687 "Evaluation",
688 |s| s.cache_reuse.to_string(),
689 |s, c| s.cache_reuse != c.cache_reuse,
690 |s, delta, _| {
691 s.cache_reuse = (s.cache_reuse as i32 + delta * 16).max(0) as u32;
692 },
693 |s, buf| {
694 if let Ok(v) = buf.parse::<u32>() {
695 s.cache_reuse = v;
696 }
697 },
698 "Minimum chunk size (in tokens) before KV cache is reused across requests. Higher values (e.g., 128, 256) only cache large shared prefixes, reducing disk write churn. Lower values (0-32) cache more aggressively. Adjust with Left/Right arrows (step 16).",
699 ),
700 expert_field_with_toggle(
701 "swa_full",
702 "SWA Full Cache",
703 "Evaluation",
704 |s| s.swa_full.to_string(),
705 |s, c| s.swa_full != c.swa_full,
706 |_, _, _| {},
707 |_, _| {},
708 toggle_swa_full,
709 "Enable full-size sliding window attention cache. Stores complete KV entries for SWA layers instead of compressed representation. Uses more VRAM but preserves quality on very long contexts. Toggle with Enter.",
710 ),
711 expert_field_with_toggle(
712 "max_concurrent_predictions",
713 "Max Concurrent Pred",
714 "Evaluation",
715 |s| {
716 s.max_concurrent_predictions
717 .map(|v| v.to_string())
718 .unwrap_or_else(|| "Off".to_string())
719 },
720 |s, c| s.max_concurrent_predictions != c.max_concurrent_predictions,
721 |s, delta, _| match s.max_concurrent_predictions {
722 Some(n) => {
723 s.max_concurrent_predictions = Some(((n as i32) + delta).clamp(1, 10) as u32)
724 }
725 None => s.max_concurrent_predictions = Some(1),
726 },
727 |s, buf| {
728 if let Ok(v) = buf.parse::<u32>() {
729 s.max_concurrent_predictions = Some(v.clamp(1, 10));
730 }
731 },
732 toggle_max_concurrent_predictions,
733 "Maximum number of models that can run simultaneously. Press Enter to open a picker that shows how context length divides per model. Each model needs its own VRAM/CPU resources.",
734 ),
735 field(
737 "seed",
738 "Seed",
739 "Sampling",
740 |s| s.seed.to_string(),
741 |s, c| s.seed != c.seed,
742 |s, delta, _| {
743 s.seed = (s.seed + delta).max(-1);
744 },
745 |s, buf| {
746 if let Ok(v) = buf.parse::<i32>() {
747 s.seed = v;
748 }
749 },
750 "Random seed for reproducible outputs. -1 = random (default). Set to a fixed value for deterministic, repeatable responses — useful for debugging or testing prompts.",
751 ),
752 field(
753 "temperature",
754 "Temp",
755 "Sampling",
756 |s| format!("{:.2}", s.temperature),
757 |s, c| (s.temperature - c.temperature).abs() > 0.001,
758 |s, delta, _| {
759 s.temperature =
760 ((s.temperature * 100.0 + delta as f32 * 5.0) / 100.0).clamp(0.0, 2.0);
761 },
762 |s, buf| {
763 if let Ok(v) = buf.parse::<f32>() {
764 s.temperature = v.clamp(0.0, 2.0);
765 }
766 },
767 "Sampling temperature. Controls creativity: 0 = deterministic (most predictable), 0.7 = balanced, 1.0+ = creative. Lower values produce more focused, factual outputs. Typical: 0.7-0.9 for general use.",
768 ),
769 field(
770 "top_k",
771 "Top-k",
772 "Sampling",
773 |s| s.top_k.to_string(),
774 |s, c| s.top_k != c.top_k,
775 |s, delta, _| {
776 s.top_k = (s.top_k + delta).max(1);
777 },
778 |s, buf| {
779 if let Ok(v) = buf.parse::<i32>() {
780 s.top_k = v.max(0);
781 }
782 },
783 "Only consider the top k most likely tokens at each step. Smaller top-k (e.g., 10-40) makes output more deterministic. Larger values allow more variety. Typical: 40-50. Set to 0 to disable.",
784 ),
785 field(
786 "top_p",
787 "Top-p",
788 "Sampling",
789 |s| format!("{:.2}", s.top_p),
790 |s, c| (s.top_p - c.top_p).abs() > 0.001,
791 |s, delta, _| {
792 s.top_p = ((s.top_p * 100.0 + delta as f32 * 5.0) / 100.0).clamp(0.0, 1.0);
793 },
794 |s, buf| {
795 if let Ok(v) = buf.parse::<f32>() {
796 s.top_p = v.clamp(0.0, 1.0);
797 }
798 },
799 "Nucleus sampling: only consider tokens whose cumulative probability reaches p. Smaller top-p (e.g., 0.9) is more conservative, larger (e.g., 0.95-0.99) allows more variety. Often preferred over top-k. Typical: 0.9-0.95.",
800 ),
801 field(
802 "min_p",
803 "Min P",
804 "Sampling",
805 |s| format!("{:.2}", s.min_p),
806 |s, c| (s.min_p - c.min_p).abs() > 0.001,
807 |s, delta, _| {
808 s.min_p = ((s.min_p * 100.0 + delta as f32 * 5.0) / 100.0).clamp(0.0, 1.0);
809 },
810 |s, buf| {
811 if let Ok(v) = buf.parse::<f32>() {
812 s.min_p = v.clamp(0.0, 1.0);
813 }
814 },
815 "Minimum probability threshold relative to the most likely token. Tokens below min_p * max_prob are excluded. A filter that's more principled than top-k/top-p for controlling diversity. Typical: 0.01-0.1.",
816 ),
817 ultra_field(
818 "typical_p",
819 "Typical P",
820 "Sampling",
821 |s| format!("{:.2}", s.typical_p),
822 |s, c| (s.typical_p - c.typical_p).abs() > 0.001,
823 |s, delta, _| {
824 s.typical_p = ((s.typical_p * 100.0 + delta as f32 * 5.0) / 100.0).clamp(0.0, 1.0);
825 },
826 |s, buf| {
827 if let Ok(v) = buf.parse::<f32>() {
828 s.typical_p = v.clamp(0.0, 1.0);
829 }
830 },
831 "Locally typical sampling (typ_p). Controls diversity by keeping tokens with typical probability mass. Values near 1.0 = no effect, 0.1-0.5 = moderate diversity. Typical: 1.0 (off).",
832 ),
833 ultra_field(
834 "mirostat",
835 "Mirostat",
836 "Sampling",
837 |s| s.mirostat.to_string(),
838 |s, c| s.mirostat != c.mirostat,
839 |s, delta, _| {
840 let mut val = s.mirostat;
841 val = match (delta, val) {
842 (1, Mirostat::Off) => Mirostat::V1,
843 (1, Mirostat::V1) => Mirostat::Mirostat2,
844 (1, Mirostat::Mirostat2) => Mirostat::Off,
845 (-1, Mirostat::Off) => Mirostat::Mirostat2,
846 (-1, Mirostat::V1) => Mirostat::Off,
847 (-1, Mirostat::Mirostat2) => Mirostat::V1,
848 _ => val,
849 };
850 s.mirostat = val;
851 },
852 |_, _| {},
853 "Mirostat sampling mode: Off (default), Mirostat, or Mirostat2. Adaptive temperature control that maintains target perplexity. Mirostat2 is more aggressive. Useful for consistent output quality.",
854 ),
855 ultra_field(
856 "mirostat_lr",
857 "Mirostat LR",
858 "Sampling",
859 |s| format!("{:.2}", s.mirostat_lr),
860 |s, c| (s.mirostat_lr - c.mirostat_lr).abs() > 0.001,
861 |s, delta, _| {
862 s.mirostat_lr =
863 ((s.mirostat_lr * 100.0 + delta as f32 * 5.0) / 100.0).clamp(0.0, 1.0);
864 },
865 |s, buf| {
866 if let Ok(v) = buf.parse::<f32>() {
867 s.mirostat_lr = v.clamp(0.0, 1.0);
868 }
869 },
870 "Mirostat learning rate (eta). Controls how quickly the temperature adapts. Smaller = smoother adjustments. Typical: 0.1.",
871 ),
872 ultra_field(
873 "mirostat_ent",
874 "Mirostat Ent",
875 "Sampling",
876 |s| format!("{:.2}", s.mirostat_ent),
877 |s, c| (s.mirostat_ent - c.mirostat_ent).abs() > 0.001,
878 |s, delta, _| {
879 s.mirostat_ent =
880 ((s.mirostat_ent * 100.0 + delta as f32 * 5.0) / 100.0).clamp(0.0, 10.0);
881 },
882 |s, buf| {
883 if let Ok(v) = buf.parse::<f32>() {
884 s.mirostat_ent = v.clamp(0.0, 10.0);
885 }
886 },
887 "Mirostat target entropy. Controls the diversity of output. Higher = more diverse. Typical: 5.0.",
888 ),
889 ultra_field_with_toggle(
890 "ignore_eos",
891 "Ignore EOS",
892 "Sampling",
893 |s| s.ignore_eos.to_string(),
894 |s, c| s.ignore_eos != c.ignore_eos,
895 |_, _, _| {},
896 |_, _| {},
897 toggle_ignore_eos,
898 "Ignore end-of-sequence tokens during generation. Toggle on/off with Enter. Useful when you want to force the model to continue generating.",
899 ),
900 ultra_field(
901 "samplers",
902 "Samplers",
903 "Sampling",
904 |s| s.samplers.0.clone(),
905 |s, c| s.samplers.0 != c.samplers.0,
906 |_, _, _| {},
907 |_, _| {},
908 "Semicolon-separated sampler order string (e.g., 'mirostat;temperature;top_k;top_p'). Controls which samplers are applied and in what order. Press Enter to edit.",
909 ),
910 field_with_toggle(
911 "max_tokens",
912 "Max Tokens",
913 "Sampling",
914 |s| {
915 s.max_tokens
916 .map(|v| v.to_string())
917 .unwrap_or_else(|| "Disabled".to_string())
918 },
919 |s, c| s.max_tokens != c.max_tokens,
920 |s, delta, _| {
921 let current = s.max_tokens.unwrap_or(0);
922 s.max_tokens = Some((current as i32 + delta * 16).max(16) as u32);
923 },
924 |s, buf| {
925 if let Ok(v) = buf.parse::<i32>() {
926 s.max_tokens = if v == 0 { None } else { Some(v as u32) };
927 }
928 },
929 toggle_max_tokens,
930 "Maximum number of tokens to generate in the response. Prevents runaway responses. Set to 0 or Disabled for no limit. Typical: 4096-8192 for chat, higher for code generation.",
931 ),
932 field(
934 "repeat_penalty",
935 "Repeat Penalty",
936 "Repetition",
937 |s| format!("{:.2}", s.repeat_penalty),
938 |s, c| (s.repeat_penalty - c.repeat_penalty).abs() > 0.001,
939 |s, delta, _| {
940 s.repeat_penalty =
941 ((s.repeat_penalty * 100.0 + delta as f32 * 5.0) / 100.0).clamp(1.0, 2.0);
942 },
943 |s, buf| {
944 if let Ok(v) = buf.parse::<f32>() {
945 s.repeat_penalty = v.clamp(0.0, 2.0);
946 }
947 },
948 "Controls repetition penalty (1.0 = no penalty, 1.1 = mild, 1.2 = strong). Higher values discourage the model from repeating phrases. Typical: 1.05-1.15 for most use cases.",
949 ),
950 field(
951 "repeat_last_n",
952 "Repeat Last N",
953 "Repetition",
954 |s| s.repeat_last_n.to_string(),
955 |s, c| s.repeat_last_n != c.repeat_last_n,
956 |s, delta, _| {
957 s.repeat_last_n = (s.repeat_last_n + delta).max(0);
958 },
959 |s, buf| {
960 if let Ok(v) = buf.parse::<i32>() {
961 s.repeat_last_n = v.max(0);
962 }
963 },
964 "How many recent tokens to check for repetition (0 = all). Smaller values (32-64) focus on local repetition, larger values (128-256) catch longer patterns. Typical: 64.",
965 ),
966 expert_field_with_toggle(
967 "presence_penalty",
968 "Presence Penalty",
969 "Repetition",
970 |s| {
971 s.presence_penalty
972 .map(|v| {
973 if (v - 0.0).abs() < 0.001 {
974 "Off".to_string()
975 } else {
976 format!("{:.2}", v)
977 }
978 })
979 .unwrap_or_else(|| "Off".to_string())
980 },
981 |s, c| match (s.presence_penalty, c.presence_penalty) {
982 (Some(v1), Some(v2)) => (v1 - v2).abs() > 0.001,
983 (None, None) => false,
984 _ => true,
985 },
986 |s, delta, _| {
987 let current = s.presence_penalty.unwrap_or(0.0);
988 s.presence_penalty =
989 Some(((current * 100.0 + delta as f32 * 5.0) / 100.0).clamp(-2.0, 2.0));
990 },
991 |s, buf| {
992 if let Ok(v) = buf.parse::<f32>() {
993 s.presence_penalty = Some(v.clamp(-2.0, 2.0));
994 }
995 },
996 toggle_presence_penalty,
997 "Encourages the model to talk about new topics (+) or stay on topic (-). Positive values reduce topic repetition, negative values encourage deeper exploration. Typical: 0.0 (off).",
998 ),
999 expert_field_with_toggle(
1000 "frequency_penalty",
1001 "Freq Penalty",
1002 "Repetition",
1003 |s| {
1004 s.frequency_penalty
1005 .map(|v| {
1006 if (v - 0.0).abs() < 0.001 {
1007 "Off".to_string()
1008 } else {
1009 format!("{:.2}", v)
1010 }
1011 })
1012 .unwrap_or_else(|| "Off".to_string())
1013 },
1014 |s, c| match (s.frequency_penalty, c.frequency_penalty) {
1015 (Some(v1), Some(v2)) => (v1 - v2).abs() > 0.001,
1016 (None, None) => false,
1017 _ => true,
1018 },
1019 |s, delta, _| {
1020 let current = s.frequency_penalty.unwrap_or(0.0);
1021 s.frequency_penalty =
1022 Some(((current * 100.0 + delta as f32 * 5.0) / 100.0).clamp(-2.0, 2.0));
1023 },
1024 |s, buf| {
1025 if let Ok(v) = buf.parse::<f32>() {
1026 s.frequency_penalty = Some(v.clamp(-2.0, 2.0));
1027 }
1028 },
1029 toggle_frequency_penalty,
1030 "Penalizes tokens based on how often they appear in the text (+) or rewards them (-). Positive values reduce word repetition, negative values encourage denser language. Typical: 0.0 (off).",
1031 ),
1032 ultra_field(
1034 "dry_multiplier",
1035 "DRY Multiplier",
1036 "DRY",
1037 |s| format!("{:.2}", s.dry_multiplier),
1038 |s, c| (s.dry_multiplier - c.dry_multiplier).abs() > 0.001,
1039 |s, delta, _| {
1040 s.dry_multiplier =
1041 ((s.dry_multiplier * 100.0 + delta as f32 * 5.0) / 100.0).clamp(0.0, 10.0);
1042 },
1043 |s, buf| {
1044 if let Ok(v) = buf.parse::<f32>() {
1045 s.dry_multiplier = v.clamp(0.0, 10.0);
1046 }
1047 },
1048 "DRY (Don't Repeat Yourself) multiplier. Scales the penalty for repetition. Higher values = stronger anti-repetition. Typical: 1.75.",
1049 ),
1050 ultra_field(
1051 "dry_base",
1052 "DRY Base",
1053 "DRY",
1054 |s| format!("{:.2}", s.dry_base),
1055 |s, c| (s.dry_base - c.dry_base).abs() > 0.001,
1056 |s, delta, _| {
1057 s.dry_base = ((s.dry_base * 100.0 + delta as f32 * 5.0) / 100.0).clamp(0.0, 10.0);
1058 },
1059 |s, buf| {
1060 if let Ok(v) = buf.parse::<f32>() {
1061 s.dry_base = v.clamp(0.0, 10.0);
1062 }
1063 },
1064 "DRY penalty base (log scale). Controls the strength of the repetition penalty. Typical: 1.0 (log2) or 0.0 (linear).",
1065 ),
1066 ultra_field(
1067 "dry_allowed_length",
1068 "DRY Allowed Length",
1069 "DRY",
1070 |s| s.dry_allowed_length.to_string(),
1071 |s, c| s.dry_allowed_length != c.dry_allowed_length,
1072 |s, delta, _| {
1073 s.dry_allowed_length = (s.dry_allowed_length + delta).max(0);
1074 },
1075 |s, buf| {
1076 if let Ok(v) = buf.parse::<i32>() {
1077 s.dry_allowed_length = v;
1078 }
1079 },
1080 "Number of recent tokens to check for repetition (penalty starts after this). Higher values check longer context. Typical: 2.",
1081 ),
1082 ultra_field(
1083 "dry_penalty_last_n",
1084 "DRY Penalty Last N",
1085 "DRY",
1086 |s| s.dry_penalty_last_n.to_string(),
1087 |s, c| s.dry_penalty_last_n != c.dry_penalty_last_n,
1088 |s, delta, _| {
1089 s.dry_penalty_last_n = (s.dry_penalty_last_n + delta).max(0);
1090 },
1091 |s, buf| {
1092 if let Ok(v) = buf.parse::<i32>() {
1093 s.dry_penalty_last_n = v;
1094 }
1095 },
1096 "How many tokens to consider for DRY penalty (0 = all). Larger values catch longer repetition patterns. Typical: -1 (all) or 128.",
1097 ),
1098 expert_field_with_toggle(
1100 "is_mtp",
1101 "MTP",
1102 "Speculative",
1103 |s| {
1104 if s.spec_type.is_empty() {
1105 "Off".to_string()
1106 } else {
1107 s.spec_type.clone()
1108 }
1109 },
1110 |s, c| s.spec_type != c.spec_type,
1111 |_, _, _| {},
1112 |_, _| {},
1113 toggle_mtp,
1114 "Speculative decoding method for faster inference. Options: Off, draft-mtp (MTP-based), draft-simple, draft-eagle3, ngram-simple, ngram-map-k, ngram-map-k4v, ngram-mod, ngram-cache. Draft-mtp requires a compatible model with MTP architecture.",
1115 ),
1116 expert_field(
1117 "spec_type",
1118 "Spec Type",
1119 "Speculative",
1120 |s| {
1121 if s.spec_type.is_empty() {
1122 "Off".to_string()
1123 } else {
1124 s.spec_type.clone()
1125 }
1126 },
1127 |s, c| s.spec_type != c.spec_type,
1128 |_, _, _| {},
1129 |_, _| {},
1130 "Speculative decoding method for faster inference. Options: Off, draft-mtp (MTP-based), draft-simple, draft-eagle3, ngram-simple, ngram-map-k, ngram-map-k4v, ngram-mod, ngram-cache. Draft-mtp requires a compatible model with MTP architecture.",
1131 ),
1132 expert_field(
1133 "draft_tokens",
1134 "Spec Draft N Max",
1135 "Speculative",
1136 |s| s.draft_tokens.to_string(),
1137 |s, c| s.draft_tokens != c.draft_tokens,
1138 |s, delta, _| {
1139 s.draft_tokens = (s.draft_tokens as i32 + delta).clamp(0, 16) as u32;
1140 },
1141 |s, buf| {
1142 if let Ok(v) = buf.parse::<u32>() {
1143 s.draft_tokens = v.min(16);
1144 }
1145 },
1146 "Maximum number of draft tokens per step (0-16). More drafts = more potential speedup but also more wasted computation if drafts are rejected. Typical: 4-8 for draft-mtp.",
1147 ),
1148 field(
1150 "tags",
1151 "Tags (Enter to edit)",
1152 "Tags",
1153 |s| {
1154 if s.tags.is_empty() {
1155 "None".to_string()
1156 } else {
1157 s.tags.join(", ")
1158 }
1159 },
1160 |s, c| s.tags != c.tags,
1161 |_, _, _| {},
1162 |_, _| {},
1163 "Comma-separated labels for the model (e.g., 'coding, chat, reasoning'). Used for filtering and organization. Press Enter to open a tag editor.",
1164 ),
1165 field(
1167 "backend_version",
1168 "LLama.cpp Version",
1169 "Backend",
1170 |s| s.get_active_backend_version_display().to_string(),
1171 |s, c| s.get_active_backend_version() != c.get_active_backend_version(),
1172 |_, _, _| {},
1173 |_, _| {},
1174 "Select the llama.cpp backend binary (CPU / Vulkan / ROCm / CUDA). Press Enter to open a version picker. Different backends support different GPU types and features.",
1175 ),
1176 ]
1177}
1178
1179pub fn filtered_fields(expert_mode: bool) -> Vec<SettingField> {
1180 all_fields()
1181 .into_iter()
1182 .filter(|f| {
1183 if !expert_mode {
1184 !f.is_expert
1185 } else {
1186 !f.is_ultra }
1188 })
1189 .collect()
1190}
1191
1192#[allow(clippy::too_many_arguments)]
1196pub fn add_setting(
1197 lines: &mut Vec<Line<'static>>,
1198 total_count: &mut usize,
1199 _settings: &ModelSettings,
1200 _cached: &ModelSettings,
1201 selected_line_idx: &mut usize,
1202 selected_content_line: &mut usize,
1203 idx: usize,
1204 name: &str,
1205 val: &str,
1206 selected: usize,
1207 _edit_buf: &str,
1208 _editing: bool,
1209 disabled: bool,
1210) {
1211 let current_line = lines.len();
1212 let name_style = if disabled {
1213 Style::default().fg(GRAY)
1214 } else {
1215 Style::default().fg(YELLOW)
1216 };
1217 let val_style = if disabled {
1218 Style::default()
1219 .fg(GRAY)
1220 } else {
1221 Style::default().fg(WHITE)
1222 };
1223 if idx == selected {
1224 *selected_line_idx = current_line;
1225 *selected_content_line = current_line;
1226 lines.push(Line::from(vec![
1227 Span::styled(
1228 "> ",
1229 Style::default()
1230 .fg(YELLOW)
1231 .add_modifier(if disabled {
1232 Modifier::DIM
1233 } else {
1234 Modifier::BOLD
1235 }),
1236 ),
1237 Span::styled(format!("{name}: "), name_style),
1238 Span::styled(
1239 val.to_string(),
1240 Style::default()
1241 .fg(BLACK)
1242 .bg(GREEN)
1243 .add_modifier(Modifier::BOLD),
1244 ),
1245 ]));
1246 } else {
1247 lines.push(Line::from(vec![
1248 Span::styled(" ", name_style),
1249 Span::styled(format!("{name}: "), name_style),
1250 Span::styled(val.to_string(), val_style),
1251 ]));
1252 }
1253 *total_count += 1;
1254}
1255
1256pub fn profile_settings_parts(profile: &Profile, current: &ModelSettings) -> Vec<String> {
1258 let mut parts = Vec::new();
1259 let s = &profile.settings;
1260
1261 diff_int!(parts, s, current, context_length, "ctx");
1263 diff_int!(parts, s, current, threads, "threads");
1264 diff_int!(parts, s, current, threads_batch, "threads_batch");
1265 diff_int!(parts, s, current, batch_size, "batch");
1266 diff_int!(parts, s, current, ubatch_size, "ubatch");
1267 diff_int!(parts, s, current, parallel, "parallel");
1268 diff_option!(parts, s, current, max_concurrent_predictions, "concurrent");
1269 diff_int!(parts, s, current, cache_reuse, "cache_reuse");
1270 diff_option!(parts, s, current, max_tokens, "max_tokens");
1271 diff_int!(parts, s, current, draft_tokens, "draft_tokens");
1272
1273 diff_int!(parts, s, current, keep, "keep");
1274 diff_int!(parts, s, current, main_gpu, "main_gpu");
1275 diff_int!(parts, s, current, expert_count, "expert_count");
1276 diff_int!(parts, s, current, seed, "seed");
1277 diff_int!(parts, s, current, top_k, "top_k");
1278 diff_int!(parts, s, current, repeat_last_n, "repeat_last_n");
1279 diff_int!(parts, s, current, dry_allowed_length, "dry_allowed");
1280 diff_int!(parts, s, current, dry_penalty_last_n, "dry_penalty_last_n");
1281
1282 diff_float!(parts, s, current, temperature, "temp");
1284 diff_float!(parts, s, current, top_p, "top_p");
1285 diff_float!(parts, s, current, min_p, "min_p");
1286 diff_float!(parts, s, current, typical_p, "typical_p");
1287 diff_float!(parts, s, current, mirostat_lr, "mirostat_lr");
1288 diff_float!(parts, s, current, mirostat_ent, "mirostat_ent");
1289 diff_float!(parts, s, current, repeat_penalty, "rep_pen");
1290 diff_option_float!(parts, s, current, presence_penalty, "pres_pen");
1291 diff_option_float!(parts, s, current, frequency_penalty, "freq_pen");
1292 diff_float!(parts, s, current, dry_multiplier, "dry_mult");
1293 diff_float!(parts, s, current, dry_base, "dry_base");
1294 diff_float!(parts, s, current, rope_scale, "rope_scale");
1295 diff_float!(parts, s, current, rope_freq_base, "rope_freq_base");
1296 diff_float!(parts, s, current, rope_freq_scale, "rope_freq_scale");
1297
1298 diff_bool!(parts, s, current, swa_full, "swa_full");
1300 diff_bool!(parts, s, current, mlock, "mlock");
1301 diff_bool!(parts, s, current, mmap, "mmap");
1302 diff_bool!(parts, s, current, uniform_cache, "uniform_cache");
1303 diff_bool!(parts, s, current, kv_cache_offload, "kv_cache_offload");
1304 diff_bool!(parts, s, current, fit, "fit");
1305 diff_bool!(parts, s, current, embedding, "embedding");
1306 diff_bool!(parts, s, current, flash_attn, "flash_attn");
1307 diff_bool!(parts, s, current, jinja, "jinja");
1308 diff_bool!(parts, s, current, auto_chat_template, "auto_chat_template");
1309 diff_bool!(parts, s, current, ignore_eos, "ignore_eos");
1310 diff_bool!(parts, s, current, rope_yarn_enabled, "yarn_enabled");
1311 diff_bool!(parts, s, current, cache_prompt, "cache_prompt");
1312 diff_bool!(parts, s, current, webui, "webui");
1313 diff_string!(parts, s, current, system_prompt_preset_name, "preset");
1315 diff_string!(parts, s, current, tensor_split, "tensor_split");
1316 diff_string!(parts, s, current, rpc, "rpc");
1317 if s.chat_template != current.chat_template {
1318 if let Some(ref v) = s.chat_template {
1319 let filename = std::path::Path::new(v)
1320 .file_name()
1321 .and_then(|n| n.to_str())
1322 .unwrap_or(v);
1323 parts.push(format!("chat_template={}", filename));
1324 }
1325 }
1326 diff_option!(
1327 parts,
1328 s,
1329 current,
1330 chat_template_kwargs,
1331 "chat_template_kwargs"
1332 );
1333 diff_option!(parts, s, current, llama_cpp_version_cpu, "llama_cpp_cpu");
1334 diff_option!(
1335 parts,
1336 s,
1337 current,
1338 llama_cpp_version_vulkan,
1339 "llama_cpp_vulkan"
1340 );
1341 diff_option!(parts, s, current, llama_cpp_version_rocm, "llama_cpp_rocm");
1342 diff_option!(
1343 parts,
1344 s,
1345 current,
1346 llama_cpp_version_rocm_lemonade,
1347 "llama_cpp_rocm_lemonade"
1348 );
1349 diff_option!(parts, s, current, llama_cpp_version_cuda, "llama_cpp_cuda");
1350 diff_string!(parts, s, current, spec_type, "spec_type");
1351
1352 diff_enum!(parts, s, current, numa, "numa");
1354 diff_enum!(parts, s, current, split_mode, "split_mode");
1355 diff_enum!(parts, s, current, mirostat, "mirostat");
1356 diff_enum!(parts, s, current, samplers, "samplers");
1357 diff_enum!(parts, s, current, rope_scaling, "rope_scaling");
1358 diff_enum!(parts, s, current, cache_type, "cache_type");
1359 diff_option!(parts, s, current, cache_type_k, "cache_type_k");
1360 diff_option!(parts, s, current, cache_type_v, "cache_type_v");
1361
1362 if let Some(v) = s.gpu_layers_mode
1364 && v != current.gpu_layers_mode
1365 {
1366 let display = match v {
1367 crate::models::GpuLayersMode::Auto => "Auto".to_string(),
1368 crate::models::GpuLayersMode::Specific(n) => n.to_string(),
1369 crate::models::GpuLayersMode::All => "All".to_string(),
1370 };
1371 parts.push(format!("gpu_layers={}", display));
1372 }
1373
1374 parts
1375}