wip: snapshot das alterações não commitadas (2026-10-20)
Origem: D:/Documents/Github/ForgeLM Arquivos omitidos (segredos, bancos de dados, logs, >50 MB): 1
This commit is contained in:
1 parent
f2481cf7e6
commit
4f293dcb3b
34 files changed
+7131
-255
No files matched your search
@@ -1,56 +1,32 @@
|
||||
//! Model alias resolution and startup model selection policy.
|
||||
//!
|
||||
//! Alias table:
|
||||
//! 0.8B family → "Cooper"
|
||||
//! 2B family → "Iron"
|
||||
//! 4B family → "Steel"
|
||||
|
||||
use crate::model_files::ModelFile;
|
||||
|
||||
/// Canonical display alias for a model family.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ModelAlias {
|
||||
Cooper,
|
||||
Iron,
|
||||
Steel,
|
||||
Bonsai,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl ModelAlias {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
ModelAlias::Cooper => "Cooper",
|
||||
ModelAlias::Iron => "Iron",
|
||||
ModelAlias::Steel => "Steel",
|
||||
ModelAlias::Bonsai => "Bonsai",
|
||||
ModelAlias::Unknown => "Unknown",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve a model file path/name to a display alias.
|
||||
/// Priority:
|
||||
/// 1. filename contains "0.8b" (case-insensitive) → Cooper
|
||||
/// 2. filename contains "4b" (case-insensitive) → Steel (checked before 2b to avoid false match)
|
||||
/// 3. filename contains "2b" (case-insensitive) → Iron
|
||||
/// 4. fallback → Unknown
|
||||
pub fn resolve_alias(path: &str) -> ModelAlias {
|
||||
let lower = path.to_lowercase();
|
||||
// Extract just the filename for matching
|
||||
let filename = lower.rsplit(['/', '\\']).next().unwrap_or(&lower);
|
||||
|
||||
if filename.contains("0.8b") {
|
||||
ModelAlias::Cooper
|
||||
} else if filename.contains("4b") {
|
||||
ModelAlias::Steel
|
||||
} else if filename.contains("2b") {
|
||||
ModelAlias::Iron
|
||||
if filename.contains("bonsai") {
|
||||
ModelAlias::Bonsai
|
||||
} else {
|
||||
ModelAlias::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of startup model resolution.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct StartupModelResolution {
|
||||
pub model_path: String,
|
||||
@@ -62,18 +38,10 @@ pub struct StartupModelResolution {
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum StartupReason {
|
||||
SavedPreference,
|
||||
DefaultIron,
|
||||
FallbackCooper,
|
||||
DefaultBonsai,
|
||||
MissingSavedModel,
|
||||
}
|
||||
|
||||
/// Resolve which model to start at app launch.
|
||||
///
|
||||
/// Policy (from SDD):
|
||||
/// 1. If user saved a preferred alias and a matching model exists → use it
|
||||
/// 2. Prefer Iron (2B)
|
||||
/// 3. Fallback to Cooper (0.8B)
|
||||
/// 4. Steel is ignored by auto-start policy
|
||||
pub fn resolve_startup_model(
|
||||
preferred_alias: Option<&str>,
|
||||
discovered: &[ModelFile],
|
||||
@@ -82,7 +50,6 @@ pub fn resolve_startup_model(
|
||||
return None;
|
||||
}
|
||||
|
||||
// Classify all discovered models
|
||||
let classified: Vec<(&ModelFile, ModelAlias)> = discovered
|
||||
.iter()
|
||||
.map(|m| (m, resolve_alias(&m.path)))
|
||||
@@ -91,9 +58,7 @@ pub fn resolve_startup_model(
|
||||
// 1. Respect saved preference if available
|
||||
if let Some(pref) = preferred_alias {
|
||||
let target_alias = match pref.to_lowercase().as_str() {
|
||||
"cooper" => ModelAlias::Cooper,
|
||||
"iron" => ModelAlias::Iron,
|
||||
"steel" => ModelAlias::Steel,
|
||||
"bonsai" => ModelAlias::Bonsai,
|
||||
_ => ModelAlias::Unknown,
|
||||
};
|
||||
if target_alias != ModelAlias::Unknown {
|
||||
@@ -104,30 +69,24 @@ pub fn resolve_startup_model(
|
||||
reason: StartupReason::SavedPreference,
|
||||
});
|
||||
}
|
||||
// Saved model not found — fall through to default policy with MissingSavedModel reason
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Prefer Iron
|
||||
if let Some((model, _)) = classified.iter().find(|(_, a)| *a == ModelAlias::Iron) {
|
||||
// 2. Default: Bonsai (8B)
|
||||
if let Some((model, _)) = classified.iter().find(|(_, a)| *a == ModelAlias::Bonsai) {
|
||||
return Some(StartupModelResolution {
|
||||
model_path: model.path.clone(),
|
||||
alias: ModelAlias::Iron,
|
||||
reason: StartupReason::DefaultIron,
|
||||
alias: ModelAlias::Bonsai,
|
||||
reason: StartupReason::DefaultBonsai,
|
||||
});
|
||||
}
|
||||
|
||||
// 3. Fallback to Cooper
|
||||
if let Some((model, _)) = classified.iter().find(|(_, a)| *a == ModelAlias::Cooper) {
|
||||
return Some(StartupModelResolution {
|
||||
model_path: model.path.clone(),
|
||||
alias: ModelAlias::Cooper,
|
||||
reason: StartupReason::FallbackCooper,
|
||||
});
|
||||
}
|
||||
|
||||
// 4. No suitable model found (Steel only or Unknown only)
|
||||
None
|
||||
// 3. Fallback: pick the first discovered model regardless of alias
|
||||
discovered.first().map(|model| StartupModelResolution {
|
||||
model_path: model.path.clone(),
|
||||
alias: resolve_alias(&model.path),
|
||||
reason: StartupReason::DefaultBonsai,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -143,26 +102,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_alias_detects_cooper_from_filename() {
|
||||
fn resolve_alias_detects_bonsai_from_filename() {
|
||||
assert_eq!(
|
||||
resolve_alias("D:/models/rahul7star_Qwen3.5-0.8B-Coder.gguf"),
|
||||
ModelAlias::Cooper
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_alias_detects_iron_from_filename() {
|
||||
assert_eq!(
|
||||
resolve_alias("D:/models/Qwen3.5-2B.Q6_K.gguf"),
|
||||
ModelAlias::Iron
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_alias_detects_steel_from_filename() {
|
||||
assert_eq!(
|
||||
resolve_alias("D:/models/Qwen3.5-4B-Abliterated.i1-Q6_K.gguf"),
|
||||
ModelAlias::Steel
|
||||
resolve_alias("D:/models/Bonsai-8B.gguf"),
|
||||
ModelAlias::Bonsai
|
||||
);
|
||||
}
|
||||
|
||||
@@ -175,40 +118,30 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn startup_resolver_picks_iron_when_both_available() {
|
||||
fn startup_resolver_picks_bonsai_when_available() {
|
||||
let models = vec![
|
||||
make_model("rahul7star_Qwen3.5-0.8B-Coder.gguf"),
|
||||
make_model("Qwen3.5-2B.Q6_K.gguf"),
|
||||
make_model("Bonsai-8B.gguf"),
|
||||
make_model("some-other-model.gguf"),
|
||||
];
|
||||
let result = resolve_startup_model(None, &models).unwrap();
|
||||
assert_eq!(result.alias, ModelAlias::Iron);
|
||||
assert_eq!(result.reason, StartupReason::DefaultIron);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn startup_resolver_falls_back_to_cooper_when_iron_missing() {
|
||||
let models = vec![make_model("rahul7star_Qwen3.5-0.8B-Coder.gguf")];
|
||||
let result = resolve_startup_model(None, &models).unwrap();
|
||||
assert_eq!(result.alias, ModelAlias::Cooper);
|
||||
assert_eq!(result.reason, StartupReason::FallbackCooper);
|
||||
assert_eq!(result.alias, ModelAlias::Bonsai);
|
||||
assert_eq!(result.reason, StartupReason::DefaultBonsai);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn startup_resolver_respects_saved_preference() {
|
||||
let models = vec![
|
||||
make_model("rahul7star_Qwen3.5-0.8B-Coder.gguf"),
|
||||
make_model("Qwen3.5-2B.Q6_K.gguf"),
|
||||
];
|
||||
let result = resolve_startup_model(Some("cooper"), &models).unwrap();
|
||||
assert_eq!(result.alias, ModelAlias::Cooper);
|
||||
let models = vec![make_model("Bonsai-8B.gguf")];
|
||||
let result = resolve_startup_model(Some("bonsai"), &models).unwrap();
|
||||
assert_eq!(result.alias, ModelAlias::Bonsai);
|
||||
assert_eq!(result.reason, StartupReason::SavedPreference);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn startup_resolver_ignores_steel_in_auto_policy() {
|
||||
let models = vec![make_model("Qwen3.5-4B-Abliterated.i1-Q6_K.gguf")];
|
||||
let result = resolve_startup_model(None, &models);
|
||||
assert!(result.is_none(), "Steel should not be auto-selected");
|
||||
fn startup_resolver_falls_back_to_first_model_when_no_bonsai() {
|
||||
let models = vec![make_model("some-other-model.gguf")];
|
||||
let result = resolve_startup_model(None, &models).unwrap();
|
||||
assert_eq!(result.model_path, "D:/models/some-other-model.gguf");
|
||||
assert_eq!(result.reason, StartupReason::DefaultBonsai);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -216,13 +149,4 @@ mod tests {
|
||||
let result = resolve_startup_model(None, &[]);
|
||||
assert!(result.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn startup_resolver_falls_through_when_saved_preference_not_found() {
|
||||
// Saved preference is "iron" but only Cooper is available → fallback to Cooper
|
||||
let models = vec![make_model("rahul7star_Qwen3.5-0.8B-Coder.gguf")];
|
||||
let result = resolve_startup_model(Some("iron"), &models).unwrap();
|
||||
assert_eq!(result.alias, ModelAlias::Cooper);
|
||||
assert_eq!(result.reason, StartupReason::FallbackCooper);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,8 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::model_library::types::RemotePreset;
|
||||
|
||||
pub const DEFAULT_CTX_SIZE: u32 = 4096;
|
||||
pub const DEFAULT_CTX_SIZE: u32 = 131072;
|
||||
pub const DEFAULT_GPU_LAYERS: i32 = -1;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
@@ -105,7 +106,7 @@ impl Default for RuntimeConfig {
|
||||
model_path: None,
|
||||
models_dir: None,
|
||||
addr: Self::default_addr(),
|
||||
gpu_layers: 0,
|
||||
gpu_layers: DEFAULT_GPU_LAYERS,
|
||||
ctx_size: DEFAULT_CTX_SIZE,
|
||||
extra_model_paths: Vec::new(),
|
||||
selected_model_id: None,
|
||||
@@ -118,13 +119,14 @@ impl Default for RuntimeConfig {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ConnectionMode, RuntimeConfig, DEFAULT_CTX_SIZE};
|
||||
use super::{ConnectionMode, RuntimeConfig, DEFAULT_CTX_SIZE, DEFAULT_GPU_LAYERS};
|
||||
|
||||
#[test]
|
||||
fn runtime_config_defaults_to_safe_context_window() {
|
||||
let config = RuntimeConfig::default();
|
||||
|
||||
assert_eq!(config.ctx_size, DEFAULT_CTX_SIZE);
|
||||
assert_eq!(config.gpu_layers, DEFAULT_GPU_LAYERS);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user