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| format_context_k(s.context_length, s.rope_yarn_enabled, s.rope_scale),
317 |s, c| s.context_length != c.context_length,
318 |s, delta, ctx_limit| {
319 let mut val = (s.context_length as i32 + delta * 128).max(128) as u32;
320 if ctx_limit > 0 {
321 val = val.min(ctx_limit);
322 }
323 s.context_length = val;
324 },
325 |s, buf| {
326 if let Ok(v) = buf.parse::<u32>() {
327 s.context_length = v.max(128);
328 }
329 },
330 "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.",
331 ),
332 expert_field_with_toggle(
333 "rope_yarn_enabled",
334 "Yarn RoPE",
335 "Loading",
336 |s| s.rope_yarn_enabled.to_string(),
337 |s, c| s.rope_yarn_enabled != c.rope_yarn_enabled,
338 |_, _, _| {},
339 |_, _| {},
340 toggle_rope_yarn_enabled,
341 "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.",
342 ),
343 {
344 let mut f = expert_field(
345 "yarn_params",
346 "Yarn Params",
347 "Loading",
348 |s| {
349 format!(
350 "scale={:.2} base={:.2} scale_f={:.2}",
351 s.rope_scale, s.rope_freq_base, s.rope_freq_scale
352 )
353 },
354 |s, c| {
355 s.rope_scale != c.rope_scale
356 || s.rope_freq_base != c.rope_freq_base
357 || s.rope_freq_scale != c.rope_freq_scale
358 },
359 |_, _, _| {},
360 |_, _| {},
361 "YaRN configuration: rope_scale (context multiplier), rope_freq_base (frequency base), rope_freq_scale (frequency scaling). Press Enter to open the YaRN parameter editor.",
362 );
363 f.is_enabled = Some(|s| s.rope_yarn_enabled);
364 f
365 },
366 ultra_field(
367 "threads_batch",
368 "Threads Batch",
369 "Loading",
370 |s| s.threads_batch.to_string(),
371 |s, c| s.threads_batch != c.threads_batch,
372 |s, delta, _| {
373 s.threads_batch = (s.threads_batch as i32 + delta).max(1) as u32;
374 },
375 |s, buf| {
376 if let Ok(v) = buf.parse::<u32>() {
377 s.threads_batch = v.max(1);
378 }
379 },
380 "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.",
381 ),
382 ultra_field(
383 "ubatch_size",
384 "UBatch Size",
385 "Loading",
386 |s| s.ubatch_size.to_string(),
387 |s, c| s.ubatch_size != c.ubatch_size,
388 |s, delta, _| {
389 s.ubatch_size = (s.ubatch_size as i32 + delta * 64).max(1) as u32;
390 },
391 |s, buf| {
392 if let Ok(v) = buf.parse::<u32>() {
393 s.ubatch_size = v.max(1);
394 }
395 },
396 "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.",
397 ),
398 ultra_field(
399 "keep",
400 "Keep",
401 "Loading",
402 |s| s.keep.to_string(),
403 |s, c| s.keep != c.keep,
404 |s, delta, _| {
405 s.keep = (s.keep + delta).max(0);
406 },
407 |s, buf| {
408 if let Ok(v) = buf.parse::<i32>() {
409 s.keep = v;
410 }
411 },
412 "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).",
413 ),
414 field_with_toggle(
415 "mlock",
416 "Keep in memory (mlock)",
417 "Loading",
418 |s| s.mlock.to_string(),
419 |s, c| s.mlock != c.mlock,
420 |_, _, _| {},
421 |_, _| {},
422 toggle_mlock,
423 "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.",
424 ),
425 expert_field_with_toggle(
426 "chat_template",
427 "Chat Template",
428 "Loading",
429 |s| {
430 if s.auto_chat_template {
431 "Auto (detect)".to_string()
432 } else if let Some(ref t) = s.chat_template {
433 if t.ends_with(".jinja") {
434 let filename = std::path::Path::new(t)
435 .file_name()
436 .and_then(|n| n.to_str())
437 .unwrap_or(t);
438 filename.to_string()
439 } else {
440 t.clone()
441 }
442 } else {
443 "None".to_string()
444 }
445 },
446 |s, c| {
447 s.auto_chat_template != c.auto_chat_template || s.chat_template != c.chat_template
448 },
449 |_, _, _| {},
450 |_, _| {},
451 toggle_auto_chat_template,
452 "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.",
453 ),
454 expert_field(
455 "numa",
456 "NUMA",
457 "Loading",
458 |s| s.numa.to_string(),
459 |s, c| s.numa != c.numa,
460 |s, delta, _| {
461 let mut val = s.numa;
462 val = match (delta, val) {
463 (1, NumMode::None) => NumMode::Distribute,
464 (1, NumMode::Distribute) => NumMode::Isolate,
465 (1, NumMode::Isolate) => NumMode::Numactl,
466 (1, NumMode::Numactl) => NumMode::None,
467 (-1, NumMode::None) => NumMode::Numactl,
468 (-1, NumMode::Distribute) => NumMode::None,
469 (-1, NumMode::Isolate) => NumMode::Distribute,
470 (-1, NumMode::Numactl) => NumMode::Isolate,
471 _ => val,
472 };
473 s.numa = val;
474 },
475 |_, _| {},
476 "NUMA (Non-Uniform Memory Access) strategy: None, Distribute, Isolate, or Numactl. Affects CPU thread affinity on multi-socket systems. None = default.",
477 ),
478 field(
480 "gpu_layers_mode",
481 "GPU Layers",
482 "GPU Offload",
483 |s| match s.gpu_layers_mode {
484 GpuLayersMode::Auto => "Auto".to_string(),
485 GpuLayersMode::Specific(n) => n.to_string(),
486 GpuLayersMode::All => "All".to_string(),
487 },
488 |s, c| s.gpu_layers_mode != c.gpu_layers_mode,
489 gpu_layers_adjust,
490 gpu_layers_apply,
491 "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.",
492 ),
493 ultra_field(
494 "split_mode",
495 "Split Mode",
496 "GPU Offload",
497 |s| s.split_mode.to_string(),
498 |s, c| s.split_mode != c.split_mode,
499 |s, delta, _| {
500 let mut val = s.split_mode;
501 val = match (delta, val) {
502 (1, SplitMode::None) => SplitMode::Layer,
503 (1, SplitMode::Layer) => SplitMode::Row,
504 (1, SplitMode::Row) => SplitMode::Tensor,
505 (1, SplitMode::Tensor) => SplitMode::None,
506 (-1, SplitMode::None) => SplitMode::Tensor,
507 (-1, SplitMode::Layer) => SplitMode::None,
508 (-1, SplitMode::Row) => SplitMode::Layer,
509 (-1, SplitMode::Tensor) => SplitMode::Row,
510 _ => val,
511 };
512 s.split_mode = val;
513 },
514 |_, _| {},
515 "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.",
516 ),
517 ultra_field(
518 "tensor_split",
519 "Tensor Split",
520 "GPU Offload",
521 |s| s.tensor_split.clone(),
522 |s, c| s.tensor_split != c.tensor_split,
523 |_, _, _| {},
524 |_, _| {},
525 "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.",
526 ),
527 expert_field(
528 "main_gpu",
529 "Main GPU",
530 "GPU Offload",
531 |s| s.main_gpu.to_string(),
532 |s, c| s.main_gpu != c.main_gpu,
533 |s, delta, _| {
534 s.main_gpu = (s.main_gpu + delta).max(0);
535 },
536 |s, buf| {
537 if let Ok(v) = buf.parse::<i32>() {
538 s.main_gpu = v;
539 }
540 },
541 "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.",
542 ),
543 field_with_toggle(
544 "fit",
545 "Fit",
546 "GPU Offload",
547 |s| s.fit.to_string(),
548 |s, c| s.fit != c.fit,
549 |_, _, _| {},
550 |_, _| {},
551 toggle_fit,
552 "Automatically adjust arguments to fit device memory. Toggle on/off with Enter.",
553 ),
554 field_with_toggle(
555 "flash_attn",
556 "Flash Attention",
557 "GPU Offload",
558 |s| s.flash_attn.to_string(),
559 |s, c| s.flash_attn != c.flash_attn,
560 |_, _, _| {},
561 |_, _| {},
562 toggle_flash_attn,
563 "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.",
564 ),
565 field_with_toggle(
566 "kv_cache_offload",
567 "KV Cache Offload",
568 "GPU Offload",
569 |s| s.kv_cache_offload.to_string(),
570 |s, c| s.kv_cache_offload != c.kv_cache_offload,
571 |_, _, _| {},
572 |_, _| {},
573 toggle_kv_cache_offload,
574 "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.",
575 ),
576 {
578 let mut f = expert_field(
579 "cache_type_k",
580 "Cache Type K",
581 "GPU Offload",
582 |s| {
583 s.cache_type_k
584 .map(|v| v.to_string())
585 .unwrap_or_else(|| "Disabled".to_string())
586 },
587 |s, c| s.cache_type_k != c.cache_type_k,
588 |s, delta, _| {
589 let mut val = s.cache_type_k.unwrap_or(CacheQuantType::F16);
590 val = if delta > 0 { val.next() } else { val.prev() };
591 s.cache_type_k = Some(val);
592 },
593 |s, buf| {
594 if let Ok(n) = buf.parse::<u8>() {
595 s.cache_type_k = Some(CacheQuantType::from_u8(n));
596 }
597 },
598 "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.",
599 );
600 f.ctrl_e_toggle = Some(toggle_cache_type_k);
601 f
602 },
603 {
604 let mut f = expert_field(
605 "cache_type_v",
606 "Cache Type V",
607 "GPU Offload",
608 |s| {
609 s.cache_type_v
610 .map(|v| v.to_string())
611 .unwrap_or_else(|| "Disabled".to_string())
612 },
613 |s, c| s.cache_type_v != c.cache_type_v,
614 |s, delta, _| {
615 let mut val = s.cache_type_v.unwrap_or(CacheQuantType::F16);
616 val = if delta > 0 { val.next() } else { val.prev() };
617 s.cache_type_v = Some(val);
618 },
619 |s, buf| {
620 if let Ok(n) = buf.parse::<u8>() {
621 s.cache_type_v = Some(CacheQuantType::from_u8(n));
622 }
623 },
624 "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.",
625 );
626 f.ctrl_e_toggle = Some(toggle_cache_type_v);
627 f
628 },
629 expert_field_with_toggle(
630 "expert_count",
631 "Active Experts",
632 "GPU Offload",
633 |s| {
634 if s.expert_count > 0 {
635 s.expert_count.to_string()
636 } else if s.expert_count == -1 {
637 "Auto".to_string()
638 } else {
639 "Disabled".to_string()
640 }
641 },
642 |s, c| s.expert_count != c.expert_count,
643 |s, delta, _| {
644 s.expert_count = (s.expert_count + delta).clamp(-1, 99);
645 },
646 |s, buf| {
647 if let Ok(v) = buf.parse::<i32>() {
648 s.expert_count = v.clamp(-1, 99);
649 }
650 },
651 toggle_expert_count,
652 "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.",
653 ),
654 field(
656 "batch_size",
657 "Eval Batch",
658 "Evaluation",
659 |s| s.batch_size.to_string(),
660 |s, c| s.batch_size != c.batch_size,
661 |s, delta, _| {
662 s.batch_size = (s.batch_size as i32 + delta * 64).max(1) as u32;
663 },
664 |s, buf| {
665 if let Ok(v) = buf.parse::<u32>() {
666 s.batch_size = v.max(1);
667 }
668 },
669 "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.",
670 ),
671 field_with_toggle(
672 "uniform_cache",
673 "Unified KV",
674 "Evaluation",
675 |s| s.uniform_cache.to_string(),
676 |s, c| s.uniform_cache != c.uniform_cache,
677 |_, _, _| {},
678 |_, _| {},
679 toggle_uniform_cache,
680 "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.",
681 ),
682 expert_field(
683 "cache_reuse",
684 "Cache Reuse",
685 "Evaluation",
686 |s| s.cache_reuse.to_string(),
687 |s, c| s.cache_reuse != c.cache_reuse,
688 |s, delta, _| {
689 s.cache_reuse = (s.cache_reuse as i32 + delta * 16).max(0) as u32;
690 },
691 |s, buf| {
692 if let Ok(v) = buf.parse::<u32>() {
693 s.cache_reuse = v;
694 }
695 },
696 "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).",
697 ),
698 expert_field_with_toggle(
699 "swa_full",
700 "SWA Full Cache",
701 "Evaluation",
702 |s| s.swa_full.to_string(),
703 |s, c| s.swa_full != c.swa_full,
704 |_, _, _| {},
705 |_, _| {},
706 toggle_swa_full,
707 "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.",
708 ),
709 expert_field_with_toggle(
710 "max_concurrent_predictions",
711 "Max Concurrent Pred",
712 "Evaluation",
713 |s| {
714 s.max_concurrent_predictions
715 .map(|v| v.to_string())
716 .unwrap_or_else(|| "Off".to_string())
717 },
718 |s, c| s.max_concurrent_predictions != c.max_concurrent_predictions,
719 |s, delta, _| match s.max_concurrent_predictions {
720 Some(n) => {
721 s.max_concurrent_predictions = Some(((n as i32) + delta).clamp(1, 10) as u32)
722 }
723 None => s.max_concurrent_predictions = Some(1),
724 },
725 |s, buf| {
726 if let Ok(v) = buf.parse::<u32>() {
727 s.max_concurrent_predictions = Some(v.clamp(1, 10));
728 }
729 },
730 toggle_max_concurrent_predictions,
731 "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.",
732 ),
733 field(
735 "seed",
736 "Seed",
737 "Sampling",
738 |s| s.seed.to_string(),
739 |s, c| s.seed != c.seed,
740 |s, delta, _| {
741 s.seed = (s.seed + delta).max(-1);
742 },
743 |s, buf| {
744 if let Ok(v) = buf.parse::<i32>() {
745 s.seed = v;
746 }
747 },
748 "Random seed for reproducible outputs. -1 = random (default). Set to a fixed value for deterministic, repeatable responses — useful for debugging or testing prompts.",
749 ),
750 field(
751 "temperature",
752 "Temp",
753 "Sampling",
754 |s| format!("{:.2}", s.temperature),
755 |s, c| (s.temperature - c.temperature).abs() > 0.001,
756 |s, delta, _| {
757 s.temperature =
758 ((s.temperature * 100.0 + delta as f32 * 5.0) / 100.0).clamp(0.0, 2.0);
759 },
760 |s, buf| {
761 if let Ok(v) = buf.parse::<f32>() {
762 s.temperature = v.clamp(0.0, 2.0);
763 }
764 },
765 "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.",
766 ),
767 field(
768 "top_k",
769 "Top-k",
770 "Sampling",
771 |s| s.top_k.to_string(),
772 |s, c| s.top_k != c.top_k,
773 |s, delta, _| {
774 s.top_k = (s.top_k + delta).max(1);
775 },
776 |s, buf| {
777 if let Ok(v) = buf.parse::<i32>() {
778 s.top_k = v.max(0);
779 }
780 },
781 "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.",
782 ),
783 field(
784 "top_p",
785 "Top-p",
786 "Sampling",
787 |s| format!("{:.2}", s.top_p),
788 |s, c| (s.top_p - c.top_p).abs() > 0.001,
789 |s, delta, _| {
790 s.top_p = ((s.top_p * 100.0 + delta as f32 * 5.0) / 100.0).clamp(0.0, 1.0);
791 },
792 |s, buf| {
793 if let Ok(v) = buf.parse::<f32>() {
794 s.top_p = v.clamp(0.0, 1.0);
795 }
796 },
797 "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.",
798 ),
799 field(
800 "min_p",
801 "Min P",
802 "Sampling",
803 |s| format!("{:.2}", s.min_p),
804 |s, c| (s.min_p - c.min_p).abs() > 0.001,
805 |s, delta, _| {
806 s.min_p = ((s.min_p * 100.0 + delta as f32 * 5.0) / 100.0).clamp(0.0, 1.0);
807 },
808 |s, buf| {
809 if let Ok(v) = buf.parse::<f32>() {
810 s.min_p = v.clamp(0.0, 1.0);
811 }
812 },
813 "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.",
814 ),
815 ultra_field(
816 "typical_p",
817 "Typical P",
818 "Sampling",
819 |s| format!("{:.2}", s.typical_p),
820 |s, c| (s.typical_p - c.typical_p).abs() > 0.001,
821 |s, delta, _| {
822 s.typical_p = ((s.typical_p * 100.0 + delta as f32 * 5.0) / 100.0).clamp(0.0, 1.0);
823 },
824 |s, buf| {
825 if let Ok(v) = buf.parse::<f32>() {
826 s.typical_p = v.clamp(0.0, 1.0);
827 }
828 },
829 "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).",
830 ),
831 ultra_field(
832 "mirostat",
833 "Mirostat",
834 "Sampling",
835 |s| s.mirostat.to_string(),
836 |s, c| s.mirostat != c.mirostat,
837 |s, delta, _| {
838 let mut val = s.mirostat;
839 val = match (delta, val) {
840 (1, Mirostat::Off) => Mirostat::V1,
841 (1, Mirostat::V1) => Mirostat::Mirostat2,
842 (1, Mirostat::Mirostat2) => Mirostat::Off,
843 (-1, Mirostat::Off) => Mirostat::Mirostat2,
844 (-1, Mirostat::V1) => Mirostat::Off,
845 (-1, Mirostat::Mirostat2) => Mirostat::V1,
846 _ => val,
847 };
848 s.mirostat = val;
849 },
850 |_, _| {},
851 "Mirostat sampling mode: Off (default), Mirostat, or Mirostat2. Adaptive temperature control that maintains target perplexity. Mirostat2 is more aggressive. Useful for consistent output quality.",
852 ),
853 ultra_field(
854 "mirostat_lr",
855 "Mirostat LR",
856 "Sampling",
857 |s| format!("{:.2}", s.mirostat_lr),
858 |s, c| (s.mirostat_lr - c.mirostat_lr).abs() > 0.001,
859 |s, delta, _| {
860 s.mirostat_lr =
861 ((s.mirostat_lr * 100.0 + delta as f32 * 5.0) / 100.0).clamp(0.0, 1.0);
862 },
863 |s, buf| {
864 if let Ok(v) = buf.parse::<f32>() {
865 s.mirostat_lr = v.clamp(0.0, 1.0);
866 }
867 },
868 "Mirostat learning rate (eta). Controls how quickly the temperature adapts. Smaller = smoother adjustments. Typical: 0.1.",
869 ),
870 ultra_field(
871 "mirostat_ent",
872 "Mirostat Ent",
873 "Sampling",
874 |s| format!("{:.2}", s.mirostat_ent),
875 |s, c| (s.mirostat_ent - c.mirostat_ent).abs() > 0.001,
876 |s, delta, _| {
877 s.mirostat_ent =
878 ((s.mirostat_ent * 100.0 + delta as f32 * 5.0) / 100.0).clamp(0.0, 10.0);
879 },
880 |s, buf| {
881 if let Ok(v) = buf.parse::<f32>() {
882 s.mirostat_ent = v.clamp(0.0, 10.0);
883 }
884 },
885 "Mirostat target entropy. Controls the diversity of output. Higher = more diverse. Typical: 5.0.",
886 ),
887 ultra_field_with_toggle(
888 "ignore_eos",
889 "Ignore EOS",
890 "Sampling",
891 |s| s.ignore_eos.to_string(),
892 |s, c| s.ignore_eos != c.ignore_eos,
893 |_, _, _| {},
894 |_, _| {},
895 toggle_ignore_eos,
896 "Ignore end-of-sequence tokens during generation. Toggle on/off with Enter. Useful when you want to force the model to continue generating.",
897 ),
898 ultra_field(
899 "samplers",
900 "Samplers",
901 "Sampling",
902 |s| s.samplers.0.clone(),
903 |s, c| s.samplers.0 != c.samplers.0,
904 |_, _, _| {},
905 |_, _| {},
906 "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.",
907 ),
908 field_with_toggle(
909 "max_tokens",
910 "Max Tokens",
911 "Sampling",
912 |s| {
913 s.max_tokens
914 .map(|v| v.to_string())
915 .unwrap_or_else(|| "Disabled".to_string())
916 },
917 |s, c| s.max_tokens != c.max_tokens,
918 |s, delta, _| {
919 let current = s.max_tokens.unwrap_or(0);
920 s.max_tokens = Some((current as i32 + delta * 16).max(16) as u32);
921 },
922 |s, buf| {
923 if let Ok(v) = buf.parse::<i32>() {
924 s.max_tokens = if v == 0 { None } else { Some(v as u32) };
925 }
926 },
927 toggle_max_tokens,
928 "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.",
929 ),
930 field(
932 "repeat_penalty",
933 "Repeat Penalty",
934 "Repetition",
935 |s| format!("{:.2}", s.repeat_penalty),
936 |s, c| (s.repeat_penalty - c.repeat_penalty).abs() > 0.001,
937 |s, delta, _| {
938 s.repeat_penalty =
939 ((s.repeat_penalty * 100.0 + delta as f32 * 5.0) / 100.0).clamp(1.0, 2.0);
940 },
941 |s, buf| {
942 if let Ok(v) = buf.parse::<f32>() {
943 s.repeat_penalty = v.clamp(0.0, 2.0);
944 }
945 },
946 "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.",
947 ),
948 field(
949 "repeat_last_n",
950 "Repeat Last N",
951 "Repetition",
952 |s| s.repeat_last_n.to_string(),
953 |s, c| s.repeat_last_n != c.repeat_last_n,
954 |s, delta, _| {
955 s.repeat_last_n = (s.repeat_last_n + delta).max(0);
956 },
957 |s, buf| {
958 if let Ok(v) = buf.parse::<i32>() {
959 s.repeat_last_n = v.max(0);
960 }
961 },
962 "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.",
963 ),
964 expert_field_with_toggle(
965 "presence_penalty",
966 "Presence Penalty",
967 "Repetition",
968 |s| {
969 s.presence_penalty
970 .map(|v| {
971 if (v - 0.0).abs() < 0.001 {
972 "Off".to_string()
973 } else {
974 format!("{:.2}", v)
975 }
976 })
977 .unwrap_or_else(|| "Off".to_string())
978 },
979 |s, c| match (s.presence_penalty, c.presence_penalty) {
980 (Some(v1), Some(v2)) => (v1 - v2).abs() > 0.001,
981 (None, None) => false,
982 _ => true,
983 },
984 |s, delta, _| {
985 let current = s.presence_penalty.unwrap_or(0.0);
986 s.presence_penalty =
987 Some(((current * 100.0 + delta as f32 * 5.0) / 100.0).clamp(-2.0, 2.0));
988 },
989 |s, buf| {
990 if let Ok(v) = buf.parse::<f32>() {
991 s.presence_penalty = Some(v.clamp(-2.0, 2.0));
992 }
993 },
994 toggle_presence_penalty,
995 "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).",
996 ),
997 expert_field_with_toggle(
998 "frequency_penalty",
999 "Freq Penalty",
1000 "Repetition",
1001 |s| {
1002 s.frequency_penalty
1003 .map(|v| {
1004 if (v - 0.0).abs() < 0.001 {
1005 "Off".to_string()
1006 } else {
1007 format!("{:.2}", v)
1008 }
1009 })
1010 .unwrap_or_else(|| "Off".to_string())
1011 },
1012 |s, c| match (s.frequency_penalty, c.frequency_penalty) {
1013 (Some(v1), Some(v2)) => (v1 - v2).abs() > 0.001,
1014 (None, None) => false,
1015 _ => true,
1016 },
1017 |s, delta, _| {
1018 let current = s.frequency_penalty.unwrap_or(0.0);
1019 s.frequency_penalty =
1020 Some(((current * 100.0 + delta as f32 * 5.0) / 100.0).clamp(-2.0, 2.0));
1021 },
1022 |s, buf| {
1023 if let Ok(v) = buf.parse::<f32>() {
1024 s.frequency_penalty = Some(v.clamp(-2.0, 2.0));
1025 }
1026 },
1027 toggle_frequency_penalty,
1028 "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).",
1029 ),
1030 ultra_field(
1032 "dry_multiplier",
1033 "DRY Multiplier",
1034 "DRY",
1035 |s| format!("{:.2}", s.dry_multiplier),
1036 |s, c| (s.dry_multiplier - c.dry_multiplier).abs() > 0.001,
1037 |s, delta, _| {
1038 s.dry_multiplier =
1039 ((s.dry_multiplier * 100.0 + delta as f32 * 5.0) / 100.0).clamp(0.0, 10.0);
1040 },
1041 |s, buf| {
1042 if let Ok(v) = buf.parse::<f32>() {
1043 s.dry_multiplier = v.clamp(0.0, 10.0);
1044 }
1045 },
1046 "DRY (Don't Repeat Yourself) multiplier. Scales the penalty for repetition. Higher values = stronger anti-repetition. Typical: 1.75.",
1047 ),
1048 ultra_field(
1049 "dry_base",
1050 "DRY Base",
1051 "DRY",
1052 |s| format!("{:.2}", s.dry_base),
1053 |s, c| (s.dry_base - c.dry_base).abs() > 0.001,
1054 |s, delta, _| {
1055 s.dry_base = ((s.dry_base * 100.0 + delta as f32 * 5.0) / 100.0).clamp(0.0, 10.0);
1056 },
1057 |s, buf| {
1058 if let Ok(v) = buf.parse::<f32>() {
1059 s.dry_base = v.clamp(0.0, 10.0);
1060 }
1061 },
1062 "DRY penalty base (log scale). Controls the strength of the repetition penalty. Typical: 1.0 (log2) or 0.0 (linear).",
1063 ),
1064 ultra_field(
1065 "dry_allowed_length",
1066 "DRY Allowed Length",
1067 "DRY",
1068 |s| s.dry_allowed_length.to_string(),
1069 |s, c| s.dry_allowed_length != c.dry_allowed_length,
1070 |s, delta, _| {
1071 s.dry_allowed_length = (s.dry_allowed_length + delta).max(0);
1072 },
1073 |s, buf| {
1074 if let Ok(v) = buf.parse::<i32>() {
1075 s.dry_allowed_length = v;
1076 }
1077 },
1078 "Number of recent tokens to check for repetition (penalty starts after this). Higher values check longer context. Typical: 2.",
1079 ),
1080 ultra_field(
1081 "dry_penalty_last_n",
1082 "DRY Penalty Last N",
1083 "DRY",
1084 |s| s.dry_penalty_last_n.to_string(),
1085 |s, c| s.dry_penalty_last_n != c.dry_penalty_last_n,
1086 |s, delta, _| {
1087 s.dry_penalty_last_n = (s.dry_penalty_last_n + delta).max(0);
1088 },
1089 |s, buf| {
1090 if let Ok(v) = buf.parse::<i32>() {
1091 s.dry_penalty_last_n = v;
1092 }
1093 },
1094 "How many tokens to consider for DRY penalty (0 = all). Larger values catch longer repetition patterns. Typical: -1 (all) or 128.",
1095 ),
1096 expert_field_with_toggle(
1098 "is_mtp",
1099 "MTP",
1100 "Speculative",
1101 |s| {
1102 if s.spec_type.is_empty() {
1103 "Off".to_string()
1104 } else {
1105 s.spec_type.clone()
1106 }
1107 },
1108 |s, c| s.spec_type != c.spec_type,
1109 |_, _, _| {},
1110 |_, _| {},
1111 toggle_mtp,
1112 "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.",
1113 ),
1114 expert_field(
1115 "draft_tokens",
1116 "Spec Draft N Max",
1117 "Speculative",
1118 |s| s.draft_tokens.to_string(),
1119 |s, c| s.draft_tokens != c.draft_tokens,
1120 |s, delta, _| {
1121 s.draft_tokens = (s.draft_tokens as i32 + delta).clamp(0, 16) as u32;
1122 },
1123 |s, buf| {
1124 if let Ok(v) = buf.parse::<u32>() {
1125 s.draft_tokens = v.min(16);
1126 }
1127 },
1128 "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.",
1129 ),
1130 field(
1132 "tags",
1133 "Tags (Enter to edit)",
1134 "Tags",
1135 |s| {
1136 if s.tags.is_empty() {
1137 "None".to_string()
1138 } else {
1139 s.tags.join(", ")
1140 }
1141 },
1142 |s, c| s.tags != c.tags,
1143 |_, _, _| {},
1144 |_, _| {},
1145 "Comma-separated labels for the model (e.g., 'coding, chat, reasoning'). Used for filtering and organization. Press Enter to open a tag editor.",
1146 ),
1147 field(
1149 "backend_version",
1150 "LLama.cpp Version",
1151 "Backend",
1152 |s| s.get_active_backend_version_display().to_string(),
1153 |s, c| s.get_active_backend_version() != c.get_active_backend_version(),
1154 |_, _, _| {},
1155 |_, _| {},
1156 "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.",
1157 ),
1158 ]
1159}
1160
1161pub fn filtered_fields(expert_mode: bool) -> Vec<SettingField> {
1162 all_fields()
1163 .into_iter()
1164 .filter(|f| {
1165 if !expert_mode {
1166 !f.is_expert
1167 } else {
1168 !f.is_ultra }
1170 })
1171 .collect()
1172}
1173
1174#[allow(clippy::too_many_arguments)]
1178pub fn add_setting(
1179 lines: &mut Vec<Line<'static>>,
1180 total_count: &mut usize,
1181 _settings: &ModelSettings,
1182 _cached: &ModelSettings,
1183 selected_line_idx: &mut usize,
1184 selected_content_line: &mut usize,
1185 idx: usize,
1186 name: &str,
1187 val: &str,
1188 selected: usize,
1189 _edit_buf: &str,
1190 _editing: bool,
1191 disabled: bool,
1192) {
1193 let current_line = lines.len();
1194 let name_style = if disabled {
1195 Style::default().fg(GRAY)
1196 } else {
1197 Style::default().fg(ACCENT)
1198 };
1199 let val_style = if disabled {
1200 Style::default().fg(GRAY)
1201 } else {
1202 Style::default().fg(WHITE)
1203 };
1204 if idx == selected {
1205 *selected_line_idx = current_line;
1206 *selected_content_line = current_line;
1207 lines.push(Line::from(vec![
1208 Span::styled(
1209 "> ",
1210 Style::default().fg(ACCENT).add_modifier(if disabled {
1211 Modifier::DIM
1212 } else {
1213 Modifier::BOLD
1214 }),
1215 ),
1216 Span::styled(format!("{name}: "), name_style),
1217 Span::styled(
1218 val.to_string(),
1219 Style::default()
1220 .fg(BLACK)
1221 .bg(ACCENT)
1222 .add_modifier(Modifier::BOLD),
1223 ),
1224 ]));
1225 } else {
1226 lines.push(Line::from(vec![
1227 Span::styled(" ", name_style),
1228 Span::styled(format!("{name}: "), name_style),
1229 Span::styled(val.to_string(), val_style),
1230 ]));
1231 }
1232 *total_count += 1;
1233}
1234
1235pub fn profile_settings_parts(profile: &Profile, current: &ModelSettings) -> Vec<String> {
1237 let mut parts = Vec::new();
1238 let s = &profile.settings;
1239
1240 diff_int!(parts, s, current, context_length, "ctx");
1242 diff_int!(parts, s, current, threads, "threads");
1243 diff_int!(parts, s, current, threads_batch, "threads_batch");
1244 diff_int!(parts, s, current, batch_size, "batch");
1245 diff_int!(parts, s, current, ubatch_size, "ubatch");
1246 diff_int!(parts, s, current, parallel, "parallel");
1247 diff_option!(parts, s, current, max_concurrent_predictions, "concurrent");
1248 diff_int!(parts, s, current, cache_reuse, "cache_reuse");
1249 diff_option!(parts, s, current, max_tokens, "max_tokens");
1250 diff_int!(parts, s, current, draft_tokens, "draft_tokens");
1251
1252 diff_int!(parts, s, current, keep, "keep");
1253 diff_int!(parts, s, current, main_gpu, "main_gpu");
1254 diff_int!(parts, s, current, expert_count, "expert_count");
1255 diff_int!(parts, s, current, seed, "seed");
1256 diff_int!(parts, s, current, top_k, "top_k");
1257 diff_int!(parts, s, current, repeat_last_n, "repeat_last_n");
1258 diff_int!(parts, s, current, dry_allowed_length, "dry_allowed");
1259 diff_int!(parts, s, current, dry_penalty_last_n, "dry_penalty_last_n");
1260
1261 diff_float!(parts, s, current, temperature, "temp");
1263 diff_float!(parts, s, current, top_p, "top_p");
1264 diff_float!(parts, s, current, min_p, "min_p");
1265 diff_float!(parts, s, current, typical_p, "typical_p");
1266 diff_float!(parts, s, current, mirostat_lr, "mirostat_lr");
1267 diff_float!(parts, s, current, mirostat_ent, "mirostat_ent");
1268 diff_float!(parts, s, current, repeat_penalty, "rep_pen");
1269 diff_option_float!(parts, s, current, presence_penalty, "pres_pen");
1270 diff_option_float!(parts, s, current, frequency_penalty, "freq_pen");
1271 diff_float!(parts, s, current, dry_multiplier, "dry_mult");
1272 diff_float!(parts, s, current, dry_base, "dry_base");
1273 diff_float!(parts, s, current, rope_scale, "rope_scale");
1274 diff_float!(parts, s, current, rope_freq_base, "rope_freq_base");
1275 diff_float!(parts, s, current, rope_freq_scale, "rope_freq_scale");
1276
1277 diff_bool!(parts, s, current, swa_full, "swa_full");
1279 diff_bool!(parts, s, current, mlock, "mlock");
1280 diff_bool!(parts, s, current, mmap, "mmap");
1281 diff_bool!(parts, s, current, uniform_cache, "uniform_cache");
1282 diff_bool!(parts, s, current, kv_cache_offload, "kv_cache_offload");
1283 diff_bool!(parts, s, current, fit, "fit");
1284 diff_bool!(parts, s, current, embedding, "embedding");
1285 diff_bool!(parts, s, current, flash_attn, "flash_attn");
1286 diff_bool!(parts, s, current, jinja, "jinja");
1287 diff_bool!(parts, s, current, auto_chat_template, "auto_chat_template");
1288 diff_bool!(parts, s, current, ignore_eos, "ignore_eos");
1289 diff_bool!(parts, s, current, rope_yarn_enabled, "yarn_enabled");
1290 diff_bool!(parts, s, current, cache_prompt, "cache_prompt");
1291 diff_bool!(parts, s, current, webui, "webui");
1292 diff_string!(parts, s, current, system_prompt_preset_name, "preset");
1294 diff_string!(parts, s, current, tensor_split, "tensor_split");
1295 diff_string!(parts, s, current, rpc, "rpc");
1296 if s.chat_template != current.chat_template
1297 && let Some(ref v) = s.chat_template
1298 {
1299 let filename = std::path::Path::new(v)
1300 .file_name()
1301 .and_then(|n| n.to_str())
1302 .unwrap_or(v);
1303 parts.push(format!("chat_template={}", filename));
1304 }
1305 diff_option!(
1306 parts,
1307 s,
1308 current,
1309 chat_template_kwargs,
1310 "chat_template_kwargs"
1311 );
1312 diff_option!(parts, s, current, llama_cpp_version_cpu, "llama_cpp_cpu");
1313 diff_option!(
1314 parts,
1315 s,
1316 current,
1317 llama_cpp_version_vulkan,
1318 "llama_cpp_vulkan"
1319 );
1320 diff_option!(parts, s, current, llama_cpp_version_rocm, "llama_cpp_rocm");
1321 diff_option!(
1322 parts,
1323 s,
1324 current,
1325 llama_cpp_version_rocm_lemonade,
1326 "llama_cpp_rocm_lemonade"
1327 );
1328 diff_option!(parts, s, current, llama_cpp_version_cuda, "llama_cpp_cuda");
1329 diff_string!(parts, s, current, spec_type, "spec_type");
1330
1331 diff_enum!(parts, s, current, numa, "numa");
1333 diff_enum!(parts, s, current, split_mode, "split_mode");
1334 diff_enum!(parts, s, current, mirostat, "mirostat");
1335 diff_enum!(parts, s, current, samplers, "samplers");
1336 diff_enum!(parts, s, current, rope_scaling, "rope_scaling");
1337 diff_enum!(parts, s, current, cache_type, "cache_type");
1338 diff_option!(parts, s, current, cache_type_k, "cache_type_k");
1339 diff_option!(parts, s, current, cache_type_v, "cache_type_v");
1340
1341 if let Some(v) = s.gpu_layers_mode
1343 && v != current.gpu_layers_mode
1344 {
1345 let display = match v {
1346 crate::models::GpuLayersMode::Auto => "Auto".to_string(),
1347 crate::models::GpuLayersMode::Specific(n) => n.to_string(),
1348 crate::models::GpuLayersMode::All => "All".to_string(),
1349 };
1350 parts.push(format!("gpu_layers={}", display));
1351 }
1352
1353 parts
1354}