Keep runtime files inside application support

This commit is contained in:
Georg Bauer
2026-07-28 18:34:08 +02:00
parent 31f6426eef
commit 9cf1c70a67
8 changed files with 141 additions and 108 deletions

View File

@@ -38,7 +38,6 @@ typedef struct {
} web_buf; } web_buf;
struct ds4_web { struct ds4_web {
char home[PATH_MAX];
char profile_dir[PATH_MAX]; char profile_dir[PATH_MAX];
int port; int port;
pid_t chrome_pid; pid_t chrome_pid;
@@ -1324,11 +1323,9 @@ static char *web_run_page_js(ds4_web *web, const char *url, const char *js,
ds4_web *ds4_web_create(const ds4_web_config *cfg) { ds4_web *ds4_web_create(const ds4_web_config *cfg) {
ds4_web *web = web_xmalloc(sizeof(*web)); ds4_web *web = web_xmalloc(sizeof(*web));
memset(web, 0, sizeof(*web)); memset(web, 0, sizeof(*web));
const char *home = cfg && cfg->home_dir && cfg->home_dir[0] ? const char *profile = cfg && cfg->profile_dir && cfg->profile_dir[0] ?
cfg->home_dir : getenv("HOME"); cfg->profile_dir : "browser";
if (!home || !home[0]) home = "."; snprintf(web->profile_dir, sizeof(web->profile_dir), "%s", profile);
snprintf(web->home, sizeof(web->home), "%s", home);
snprintf(web->profile_dir, sizeof(web->profile_dir), "%s/.ds4/browser", home);
web->port = cfg && cfg->port > 0 ? cfg->port : DS4_WEB_DEFAULT_PORT; web->port = cfg && cfg->port > 0 ? cfg->port : DS4_WEB_DEFAULT_PORT;
web->chrome_pid = 0; web->chrome_pid = 0;
web->next_cdp_id = 1; web->next_cdp_id = 1;

View File

@@ -10,7 +10,7 @@ typedef void (*ds4_web_log_fn)(void *privdata, const char *message);
typedef bool (*ds4_web_cancel_fn)(void *privdata); typedef bool (*ds4_web_cancel_fn)(void *privdata);
typedef struct { typedef struct {
const char *home_dir; const char *profile_dir;
int port; int port;
ds4_web_confirm_fn confirm; ds4_web_confirm_fn confirm;
void *confirm_privdata; void *confirm_privdata;

View File

@@ -32,7 +32,7 @@ pub(crate) const COMPACTION_OBSERVATION_PREFIX: &str = "Bash job update after co
#[repr(C)] #[repr(C)]
struct WebConfig { struct WebConfig {
home_dir: *const c_char, profile_dir: *const c_char,
port: c_int, port: c_int,
confirm: Option<unsafe extern "C" fn(*mut c_void, *const c_char, *mut c_char, usize) -> c_int>, confirm: Option<unsafe extern "C" fn(*mut c_void, *const c_char, *mut c_char, usize) -> c_int>,
confirm_data: *mut c_void, confirm_data: *mut c_void,
@@ -77,16 +77,15 @@ impl Browser {
let mut callbacks = Box::new(WebCallbacks { let mut callbacks = Box::new(WebCallbacks {
cancel: AtomicPtr::new(std::ptr::null_mut()), cancel: AtomicPtr::new(std::ptr::null_mut()),
}); });
let home = CString::new( let profile = CString::new(
std::env::var_os("HOME") crate::app::browser_profile_path()
.unwrap_or_else(|| ".".into())
.to_string_lossy() .to_string_lossy()
.as_bytes(), .as_bytes(),
) )
.map_err(|_| "The home directory contains a NUL byte.".to_owned())?; .map_err(|_| "The browser profile path contains a NUL byte.".to_owned())?;
let data = (&mut *callbacks) as *mut WebCallbacks as *mut c_void; let data = (&mut *callbacks) as *mut WebCallbacks as *mut c_void;
let config = WebConfig { let config = WebConfig {
home_dir: home.as_ptr(), profile_dir: profile.as_ptr(),
port: 9333, port: 9333,
confirm: Some(web_confirm), confirm: Some(web_confirm),
confirm_data: data, confirm_data: data,

View File

@@ -2453,7 +2453,7 @@ fn export_markdown(title: &str, conversation: &[ChatMessage]) -> String {
output output
} }
fn application_support_path() -> PathBuf { pub(crate) fn application_support_path() -> PathBuf {
std::env::var_os("HOME") std::env::var_os("HOME")
.map(PathBuf::from) .map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from(".")) .unwrap_or_else(|| PathBuf::from("."))
@@ -2462,10 +2462,14 @@ fn application_support_path() -> PathBuf {
.join(APP_ID) .join(APP_ID)
} }
fn models_path() -> PathBuf { pub(crate) fn models_path() -> PathBuf {
application_support_path().join("models") application_support_path().join("models")
} }
pub(crate) fn browser_profile_path() -> PathBuf {
application_support_path().join("browser")
}
/// The settings file, beside the project database. /// The settings file, beside the project database.
pub(crate) fn config_path() -> PathBuf { pub(crate) fn config_path() -> PathBuf {
application_support_path().join("config.yaml") application_support_path().join("config.yaml")
@@ -2911,4 +2915,36 @@ mod tests {
); );
assert!(!exported.contains("private")); assert!(!exported.contains("private"));
} }
#[test]
fn owned_runtime_paths_stay_in_application_support() {
let root = application_support_path();
assert!(models_path().starts_with(&root));
assert!(browser_profile_path().starts_with(&root));
assert!(config_path().starts_with(&root));
assert!(kv_cache_path().starts_with(&root));
}
#[test]
fn source_does_not_depend_on_a_sibling_ds4_checkout() {
fn scan(path: &Path, forbidden: &str) {
if path.is_dir() {
for entry in fs::read_dir(path).unwrap() {
scan(&entry.unwrap().path(), forbidden);
}
} else if let Ok(source) = fs::read_to_string(path) {
assert!(
!source.contains(forbidden),
"{} references a sibling DS4 checkout",
path.display()
);
}
}
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let forbidden = format!("{}/", ["..", "ds4"].join("/"));
for path in ["src", "native", "build.rs", "Cargo.toml", "Makefile"] {
scan(&root.join(path), &forbidden);
}
}
} }

View File

@@ -1838,9 +1838,13 @@ mod sampling_tests {
#[ignore = "requires the 80 GiB Flash checkpoint and Apple Metal"] #[ignore = "requires the 80 GiB Flash checkpoint and Apple Metal"]
fn metal_executes_real_flash_token() { fn metal_executes_real_flash_token() {
configure_metal_sources().unwrap(); configure_metal_sources().unwrap();
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join( let path = crate::model::engine_artifacts(
"../ds4/gguf/DeepSeek-V4-Flash-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-chat-v2-imatrix.gguf", ModelChoice::DeepSeekV4Flash,
); false,
false,
&crate::app::models_path(),
)
.model;
let model = Model::open_main(&path, ModelChoice::DeepSeekV4Flash).unwrap(); let model = Model::open_main(&path, ModelChoice::DeepSeekV4Flash).unwrap();
let tokens = model.render_prompt( let tokens = model.render_prompt(
"You are a helpful assistant", "You are a helpful assistant",

View File

@@ -2687,7 +2687,7 @@ impl Session {
// `session` must release every Buffer before `_context` calls ds4_gpu_cleanup(), // `session` must release every Buffer before `_context` calls ds4_gpu_cleanup(),
// and `_context` must drop before `model` unmaps memory wrapped without copying // and `_context` must drop before `model` unmaps memory wrapped without copying
// by native/metal/ds4_metal.m:10329. This intentionally differs from // by native/metal/ds4_metal.m:10329. This intentionally differs from
// ../ds4/ds4.c:56287-56288; do not reorder these fields to match it. // DS4's `ds4.c` consumes this exact field order; do not reorder it.
#[derive(Clone, Copy, Default)] #[derive(Clone, Copy, Default)]
pub(super) struct ExecutionStats { pub(super) struct ExecutionStats {
pub(super) speculative_mode: u8, pub(super) speculative_mode: u8,
@@ -7068,6 +7068,14 @@ mod tests {
}; };
use crate::engine::{FLASH, PRO}; use crate::engine::{FLASH, PRO};
fn installed_artifacts(
model: crate::model::ModelChoice,
legacy_mtp: bool,
dspark: bool,
) -> crate::model::EngineArtifacts {
crate::model::engine_artifacts(model, legacy_mtp, dspark, &crate::app::models_path())
}
#[test] #[test]
fn compression_schedule_tracks_the_deepseek_model_shape() { fn compression_schedule_tracks_the_deepseek_model_shape() {
assert_eq!(compression_ratio(FLASH, 0), 0); assert_eq!(compression_ratio(FLASH, 0), 0);
@@ -7165,16 +7173,14 @@ mod tests {
use crate::settings::{ use crate::settings::{
EngineSpeculativeSettings, EngineSsdSettings, EngineSteeringSettings, EngineSpeculativeSettings, EngineSsdSettings, EngineSteeringSettings,
}; };
use std::path::Path;
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
let main_path = Path::new( let artifacts = installed_artifacts(ModelChoice::DeepSeekV4Flash, true, false);
"../ds4/gguf/DeepSeek-V4-Flash-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-chat-v2-imatrix.gguf", let main_path = artifacts.model;
);
configure_sources().unwrap(); configure_sources().unwrap();
let support_path = Path::new("../ds4/gguf/DeepSeek-V4-Flash-MTP-Q4K-Q8_0-F32.gguf"); let support_path = artifacts.mtp.unwrap();
let mut model = Model::open_main(main_path, ModelChoice::DeepSeekV4Flash).unwrap(); let mut model = Model::open_main(&main_path, ModelChoice::DeepSeekV4Flash).unwrap();
let support = Gguf::open(support_path).unwrap(); let support = Gguf::open(&support_path).unwrap();
model.support_kind = Some(validate_support(&support, &model.shape).unwrap()); model.support_kind = Some(validate_support(&support, &model.shape).unwrap());
model.support = Some(support); model.support = Some(support);
let prompt = model.render_conversation( let prompt = model.render_conversation(
@@ -7304,16 +7310,14 @@ mod tests {
use crate::settings::{ use crate::settings::{
EngineSpeculativeSettings, EngineSsdSettings, EngineSteeringSettings, EngineSpeculativeSettings, EngineSsdSettings, EngineSteeringSettings,
}; };
use std::path::Path;
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
configure_sources().unwrap(); configure_sources().unwrap();
let main_path = Path::new( let artifacts = installed_artifacts(ModelChoice::DeepSeekV4Flash, false, true);
"../ds4/gguf/DeepSeek-V4-Flash-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-chat-v2-imatrix.gguf", let main_path = artifacts.model;
); let support_path = artifacts.mtp.unwrap();
let support_path = Path::new("../ds4/gguf/DeepSeek-V4-Flash-DSpark-support.gguf"); let mut model = Model::open_main(&main_path, ModelChoice::DeepSeekV4Flash).unwrap();
let mut model = Model::open_main(main_path, ModelChoice::DeepSeekV4Flash).unwrap(); let support = Gguf::open(&support_path).unwrap();
let support = Gguf::open(support_path).unwrap();
model.support_kind = Some(validate_support(&support, &model.shape).unwrap()); model.support_kind = Some(validate_support(&support, &model.shape).unwrap());
model.support = Some(support); model.support = Some(support);
let prompt = model.render_conversation( let prompt = model.render_conversation(
@@ -7446,24 +7450,19 @@ mod tests {
use crate::settings::{ use crate::settings::{
EngineSpeculativeSettings, EngineSsdSettings, EngineSteeringSettings, EngineSpeculativeSettings, EngineSsdSettings, EngineSteeringSettings,
}; };
use std::path::Path;
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
configure_sources().unwrap(); configure_sources().unwrap();
let main_path = Path::new( let legacy = installed_artifacts(ModelChoice::DeepSeekV4Flash, true, false);
"../ds4/gguf/DeepSeek-V4-Flash-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-chat-v2-imatrix.gguf", let dspark_artifacts = installed_artifacts(ModelChoice::DeepSeekV4Flash, false, true);
); let main_path = legacy.model;
let cases = [ let cases = [
( (legacy.mtp.unwrap(), false, 4),
"../ds4/gguf/DeepSeek-V4-Flash-MTP-Q4K-Q8_0-F32.gguf", (dspark_artifacts.mtp.unwrap(), true, 1),
false,
4,
),
("../ds4/gguf/DeepSeek-V4-Flash-DSpark-support.gguf", true, 1),
]; ];
for (support_path, dspark, draft_tokens) in cases { for (support_path, dspark, draft_tokens) in cases {
let mut model = Model::open_main(main_path, ModelChoice::DeepSeekV4Flash).unwrap(); let mut model = Model::open_main(&main_path, ModelChoice::DeepSeekV4Flash).unwrap();
let support = Gguf::open(Path::new(support_path)).unwrap(); let support = Gguf::open(&support_path).unwrap();
model.support_kind = Some(validate_support(&support, &model.shape).unwrap()); model.support_kind = Some(validate_support(&support, &model.shape).unwrap());
model.support = Some(support); model.support = Some(support);
let prompt = model.render_conversation( let prompt = model.render_conversation(
@@ -7528,7 +7527,8 @@ mod tests {
assert_eq!( assert_eq!(
generated, generated,
[19_923, 3, 1_730, 588, 342, 1_694, 440, 4_316], [19_923, 3, 1_730, 588, 342, 1_694, 440, 4_316],
"SSD speculative output differed for {support_path}" "SSD speculative output differed for {}",
support_path.display()
); );
} }
} }
@@ -7604,12 +7604,10 @@ mod tests {
} }
configure_sources().unwrap(); configure_sources().unwrap();
let path = Path::new( let path = installed_artifacts(ModelChoice::DeepSeekV4Flash, false, false).model;
"../ds4/gguf/DeepSeek-V4-Flash-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-chat-v2-imatrix.gguf", let resident = run(&path, false);
);
let resident = run(path, false);
assert_eq!(resident, [19_923, 3, 1_730, 588]); assert_eq!(resident, [19_923, 3, 1_730, 588]);
assert_eq!(resident, run(path, true)); assert_eq!(resident, run(&path, true));
} }
#[test] #[test]
@@ -7621,16 +7619,10 @@ mod tests {
use crate::settings::{ use crate::settings::{
EngineSpeculativeSettings, EngineSsdSettings, EngineSteeringSettings, EngineSpeculativeSettings, EngineSsdSettings, EngineSteeringSettings,
}; };
use std::path::Path;
configure_sources().unwrap(); configure_sources().unwrap();
let model = Model::open_main( let path = installed_artifacts(ModelChoice::DeepSeekV4Flash, false, false).model;
Path::new( let model = Model::open_main(&path, ModelChoice::DeepSeekV4Flash).unwrap();
"../ds4/gguf/DeepSeek-V4-Flash-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-chat-v2-imatrix.gguf",
),
ModelChoice::DeepSeekV4Flash,
)
.unwrap();
let prompt = model.render_conversation( let prompt = model.render_conversation(
"", "",
&[crate::engine::ChatTurn { &[crate::engine::ChatTurn {
@@ -7690,16 +7682,10 @@ mod tests {
use crate::settings::{ use crate::settings::{
EngineSpeculativeSettings, EngineSsdSettings, EngineSteeringSettings, ReasoningMode, EngineSpeculativeSettings, EngineSsdSettings, EngineSteeringSettings, ReasoningMode,
}; };
use std::path::Path;
configure_sources().unwrap(); configure_sources().unwrap();
let model = Model::open_main( let path = installed_artifacts(ModelChoice::DeepSeekV4Flash, false, false).model;
Path::new( let model = Model::open_main(&path, ModelChoice::DeepSeekV4Flash).unwrap();
"../ds4/gguf/DeepSeek-V4-Flash-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-chat-v2-imatrix.gguf",
),
ModelChoice::DeepSeekV4Flash,
)
.unwrap();
let prompts = ["Reply with A.", "Reply with B."].map(|content| { let prompts = ["Reply with A.", "Reply with B."].map(|content| {
model.render_conversation( model.render_conversation(
"", "",
@@ -7775,19 +7761,17 @@ mod tests {
use crate::settings::{ use crate::settings::{
EngineSpeculativeSettings, EngineSsdSettings, EngineSteeringSettings, EngineSpeculativeSettings, EngineSsdSettings, EngineSteeringSettings,
}; };
use std::path::Path;
configure_sources().unwrap(); configure_sources().unwrap();
let model_path = Path::new( let model_path = installed_artifacts(ModelChoice::DeepSeekV4Flash, false, false).model;
"../ds4/gguf/DeepSeek-V4-Flash-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-chat-v2-imatrix.gguf", let steering_path = std::env::var("DS4_STEERING_FILE")
); .expect("set DS4_STEERING_FILE to the DS4 verbosity direction fixture");
let steering_path = "../ds4/dir-steering/out/verbosity.f32";
let cases = [ let cases = [
(1.0, 0.0, [19_923, 3, 1_730, 588, 342, 8_233, 440, 4_316]), (1.0, 0.0, [19_923, 3, 1_730, 588, 342, 8_233, 440, 4_316]),
(0.0, 1.0, [19_923, 3, 1_730, 588, 342, 1_694, 440, 4_316]), (0.0, 1.0, [19_923, 3, 1_730, 588, 342, 1_694, 440, 4_316]),
]; ];
for (ffn_scale, attention_scale, expected) in cases { for (ffn_scale, attention_scale, expected) in cases {
let model = Model::open_main(model_path, ModelChoice::DeepSeekV4Flash).unwrap(); let model = Model::open_main(&model_path, ModelChoice::DeepSeekV4Flash).unwrap();
let prompt = model.render_conversation( let prompt = model.render_conversation(
"", "",
&[crate::engine::ChatTurn { &[crate::engine::ChatTurn {
@@ -7827,7 +7811,7 @@ mod tests {
preload_experts: 0, preload_experts: 0,
}, },
EngineSteeringSettings { EngineSteeringSettings {
file: Some(steering_path.into()), file: Some(steering_path.clone()),
ffn_scale, ffn_scale,
attention_scale, attention_scale,
}, },
@@ -7853,18 +7837,15 @@ mod tests {
use crate::settings::{ use crate::settings::{
EngineSpeculativeSettings, EngineSsdSettings, EngineSteeringSettings, EngineSpeculativeSettings, EngineSsdSettings, EngineSteeringSettings,
}; };
use std::path::Path;
configure_sources().unwrap(); configure_sources().unwrap();
let path = std::env::var("DS4_PRO_MODEL").unwrap_or_else(|_| { let path = installed_artifacts(ModelChoice::DeepSeekV4Pro, false, false).model;
"../ds4/models/DeepSeek-V4-Pro-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-Instruct-imatrix.gguf".into() if !path.is_file() {
}); eprintln!("skipping unavailable Pro fixture: {}", path.display());
if !Path::new(&path).is_file() {
eprintln!("skipping unavailable Pro fixture: {path}");
return; return;
} }
let run = |cache_experts| { let run = |cache_experts| {
let model = Model::open_main(Path::new(&path), ModelChoice::DeepSeekV4Pro).unwrap(); let model = Model::open_main(&path, ModelChoice::DeepSeekV4Pro).unwrap();
let prompt = model.render_conversation( let prompt = model.render_conversation(
"", "",
&[crate::engine::ChatTurn { &[crate::engine::ChatTurn {

View File

@@ -3221,7 +3221,11 @@ mod tests {
use crate::engine::{GLM, Model, ReasoningMode}; use crate::engine::{GLM, Model, ReasoningMode};
use crate::model::ModelChoice; use crate::model::ModelChoice;
use crate::settings::EngineSsdSettings; use crate::settings::EngineSsdSettings;
use std::path::Path;
fn installed_glm_path() -> std::path::PathBuf {
crate::model::engine_artifacts(ModelChoice::Glm52, false, false, &crate::app::models_path())
.model
}
#[test] #[test]
fn dsa_indexer_schedule_matches_the_reference() { fn dsa_indexer_schedule_matches_the_reference() {
@@ -3258,16 +3262,14 @@ mod tests {
#[test] #[test]
#[ignore = "requires the 197 GiB GLM 5.2 checkpoint and Apple Metal"] #[ignore = "requires the 197 GiB GLM 5.2 checkpoint and Apple Metal"]
fn resident_and_streamed_glm_match_ds4_decode_oracles() { fn resident_and_streamed_glm_match_ds4_decode_oracles() {
let path = std::env::var("DS4_GLM_MODEL").unwrap_or_else(|_| { let path = installed_glm_path();
"../ds4/models/GLM-5.2-UD-IQ2_XXS_RoutedIQ2XXS_blk78Q2K.gguf".into() if !path.is_file() {
}); eprintln!("skipping unavailable GLM fixture: {}", path.display());
if !Path::new(&path).is_file() {
eprintln!("skipping unavailable GLM fixture: {path}");
return; return;
} }
let prompt = b"Complete the C statement with the next exact token only:\nreturn snprintf(buf, sizeof(buf), \"%d\", value"; let prompt = b"Complete the C statement with the next exact token only:\nreturn snprintf(buf, sizeof(buf), \"%d\", value";
for streamed in [false, true] { for streamed in [false, true] {
let model = Model::open_main(Path::new(&path), ModelChoice::Glm52).unwrap(); let model = Model::open_main(&path, ModelChoice::Glm52).unwrap();
let tokens = model.tokenize(std::str::from_utf8(prompt).unwrap()); let tokens = model.tokenize(std::str::from_utf8(prompt).unwrap());
let executor = GlmExecutor::open( let executor = GlmExecutor::open(
model, model,
@@ -3329,15 +3331,13 @@ mod tests {
#[test] #[test]
#[ignore = "requires the 197 GiB GLM 5.2 checkpoint and Apple Metal"] #[ignore = "requires the 197 GiB GLM 5.2 checkpoint and Apple Metal"]
fn streamed_glm_uses_ds4_indexed_prefill_for_long_prompts() { fn streamed_glm_uses_ds4_indexed_prefill_for_long_prompts() {
let path = std::env::var("DS4_GLM_MODEL").unwrap_or_else(|_| { let path = installed_glm_path();
"../ds4/models/GLM-5.2-UD-IQ2_XXS_RoutedIQ2XXS_blk78Q2K.gguf".into() if !path.is_file() {
}); eprintln!("skipping unavailable GLM fixture: {}", path.display());
if !Path::new(&path).is_file() {
eprintln!("skipping unavailable GLM fixture: {path}");
return; return;
} }
let prompt = "Complete each C statement. Example: return snprintf(buf, sizeof(buf), \"%d\", value); Example: return snprintf(buf, sizeof(buf), \"%d\", value); Example: return snprintf(buf, sizeof(buf), \"%d\", value); Example: return snprintf(buf, sizeof(buf), \"%d\", value); Example: return snprintf(buf, sizeof(buf), \"%d\", value); Now complete exactly: return snprintf(buf, sizeof(buf), \"%d\", value"; let prompt = "Complete each C statement. Example: return snprintf(buf, sizeof(buf), \"%d\", value); Example: return snprintf(buf, sizeof(buf), \"%d\", value); Example: return snprintf(buf, sizeof(buf), \"%d\", value); Example: return snprintf(buf, sizeof(buf), \"%d\", value); Example: return snprintf(buf, sizeof(buf), \"%d\", value); Now complete exactly: return snprintf(buf, sizeof(buf), \"%d\", value";
let model = Model::open_main(Path::new(&path), ModelChoice::Glm52).unwrap(); let model = Model::open_main(&path, ModelChoice::Glm52).unwrap();
let tokens = let tokens =
model.render_prompt("You are a helpful assistant", prompt, ReasoningMode::Direct); model.render_prompt("You are a helpful assistant", prompt, ReasoningMode::Direct);
assert_eq!(tokens.len(), 102); assert_eq!(tokens.len(), 102);
@@ -3382,15 +3382,13 @@ mod tests {
use crate::settings::EngineSpeculativeSettings; use crate::settings::EngineSpeculativeSettings;
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
let path = std::env::var("DS4_GLM_MODEL").unwrap_or_else(|_| { let path = installed_glm_path();
"../ds4/models/GLM-5.2-UD-IQ2_XXS_RoutedIQ2XXS_blk78Q2K.gguf".into() if !path.is_file() {
}); eprintln!("skipping unavailable GLM fixture: {}", path.display());
if !Path::new(&path).is_file() {
eprintln!("skipping unavailable GLM fixture: {path}");
return; return;
} }
let run = |enabled| { let run = |enabled| {
let model = Model::open_main(Path::new(&path), ModelChoice::Glm52).unwrap(); let model = Model::open_main(&path, ModelChoice::Glm52).unwrap();
let tokens = model.tokenize("Write one short greeting."); let tokens = model.tokenize("Write one short greeting.");
let mut executor = GlmExecutor::open_profile( let mut executor = GlmExecutor::open_profile(
model, model,

View File

@@ -1019,13 +1019,17 @@ mod tests {
#[test] #[test]
fn installed_ds4_fixture_opens_and_renders_a_prompt() { fn installed_ds4_fixture_opens_and_renders_a_prompt() {
let path = Path::new( let path = crate::model::engine_artifacts(
"../ds4/gguf/DeepSeek-V4-Flash-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-chat-v2-imatrix.gguf", ModelChoice::DeepSeekV4Flash,
); false,
false,
&crate::app::models_path(),
)
.model;
if !path.exists() { if !path.exists() {
return; return;
} }
let model = Model::open_main(path, ModelChoice::DeepSeekV4Flash).unwrap(); let model = Model::open_main(&path, ModelChoice::DeepSeekV4Flash).unwrap();
let summary = model.summary(); let summary = model.summary();
assert_eq!(summary.model, ModelChoice::DeepSeekV4Flash); assert_eq!(summary.model, ModelChoice::DeepSeekV4Flash);
assert_eq!(summary.vocabulary_size, 129_280); assert_eq!(summary.vocabulary_size, 129_280);
@@ -1223,18 +1227,32 @@ mod tests {
#[test] #[test]
fn installed_dspark_fixture_passes_the_target_layout() { fn installed_dspark_fixture_passes_the_target_layout() {
let path = Path::new("../ds4/gguf/DeepSeek-V4-Flash-DSpark-support.gguf"); let path = crate::model::engine_artifacts(
ModelChoice::DeepSeekV4Flash,
false,
true,
&crate::app::models_path(),
)
.mtp
.unwrap();
if path.exists() { if path.exists() {
validate_model_artifact(path, ModelChoice::DeepSeekV4Flash, true).unwrap(); validate_model_artifact(&path, ModelChoice::DeepSeekV4Flash, true).unwrap();
} }
} }
#[test] #[test]
fn installed_legacy_mtp_fixture_passes_the_target_layout() { fn installed_legacy_mtp_fixture_passes_the_target_layout() {
let path = Path::new("../ds4/gguf/DeepSeek-V4-Flash-MTP-Q4K-Q8_0-F32.gguf"); let path = crate::model::engine_artifacts(
ModelChoice::DeepSeekV4Flash,
true,
false,
&crate::app::models_path(),
)
.mtp
.unwrap();
if path.exists() { if path.exists() {
assert_eq!( assert_eq!(
validate_support(&Gguf::open(path).unwrap(), &FLASH).unwrap(), validate_support(&Gguf::open(&path).unwrap(), &FLASH).unwrap(),
SupportKind::LegacyMtp SupportKind::LegacyMtp
); );
} }