Strata : seconde carte en partage des couches au choix, l'aide aux experts reste le défaut

Repris d'AJEAN 38919238 (#121 amont) et de la partie moteur de 6db3f3d0 :
STRATA_SPLIT=layers dans le preset fait lister les deux cartes dans la clé
« gpu » de la config (principale d'abord, numéros nvidia-smi) ; le serveur de
Strata en déduit --layer-split. Rien du mode d'aide ne subsiste alors : ni
--expert-cache-device1 / --remote-expert-opt, ni source d'aide à la lecture
des prompts, ni encodeur d'images déplacé ; la réserve de VRAM de
l'installeur reste. Sans la clé, ou la seconde carte coupée, rien ne change.

Propre à Loki : le placement des experts selon la RAM disponible ne choisit
jamais le mode hors RAM en partage des couches (la seconde carte n'y garde
pas d'experts, la marge calculée pour elle serait fausse).

Réglages : « split » (helper ou layers ; absent = inchangé pour un ancien
client), détail split_layers. Fenêtre Strata : la case « Carte d'appoint »
devient un choix « Seconde carte » (non utilisée, aide aux experts, partage
des couches) ; la carte de configuration du moteur actif l'affiche.

Façade Rust alignée (strata/mod.rs, strata/api.rs, tests portés) ; journal
de parité régénéré sous Linux avec les réglages et l'affichage du partage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Opus 5.5 committed 2026-10-16 15:16:37 +02:00
1 parent 9792953d62
commit 615e58dd2f
13 files changed
+823 -120

No files matched your search

+4 -1
View File
@@ -345,7 +345,7 @@ impl Strata {
"mmap": mode == "disk" || mode == "arena", "drop": mode == "drop",
"experts": experts, "mode_label": experts_label(&mode), "mode": mode,
"main_gpu": main_gpu,
"has_helper": false, "helper_on": false,
"has_helper": false, "helper_on": false, "split_layers": false,
});
let kvr = val("--kv-resident");
if !kvr.is_empty() {
@@ -356,6 +356,7 @@ impl Strata {
d["has_helper"] = json!(true);
if helper_on(&pc) {
d["helper_on"] = json!(true);
d["split_layers"] = json!(layer_split(&pc));
let hg = gpu_name(h);
if !hg.is_empty() {
d["helper_gpu"] = json!(hg);
@@ -825,6 +826,7 @@ pub async fn serve(
("vision", Kind::Bool),
("helper", Kind::Bool),
("experts", Kind::Str),
("split", Kind::Str),
],
);
if let Some(e) = d.err.clone() {
@@ -838,6 +840,7 @@ pub async fn serve(
vision: d.bool("vision"),
helper: d.bool("helper"),
experts: d.str("experts"),
split: d.str("split"),
};
if r.preset.contains(['/', '\\']) || r.preset.is_empty() {
return fail(StatusCode::BAD_REQUEST, "preset invalide");
+35 -2
View File
@@ -287,6 +287,13 @@ pub fn helper_on(cfg: &Cfg) -> bool {
!h.is_empty() && h != "-1" && cfg_get(cfg, "STRATA_HELPER") != "0"
}
/// `strataLayerSplit` : les deux cartes se partagent les couches
/// (`STRATA_SPLIT=layers`) au lieu que la seconde n'aide que pour une part
/// des experts. Un choix, jamais imposé : le mode d'aide reste le défaut.
pub fn layer_split(cfg: &Cfg) -> bool {
helper_on(cfg) && cfg_get(cfg, "STRATA_SPLIT") == "layers"
}
/// `strataResolveExperts` : le mode lancé (drop, ram, arena, disk), d'après
/// le choix du preset et la RAM disponible.
pub fn resolve_experts(
@@ -297,7 +304,9 @@ pub fn resolve_experts(
helper_gb: f64,
) -> &'static str {
let bin_ready = pack_has_experts_bin(args) && paths.engine_installed() == ENGINE_ASSET;
let drop_ready = bin_ready && helper_on(cfg) && cfg_get(cfg, "STRATA_DROP") != "0";
// (jamais en partage des couches : la seconde carte n'y garde pas d'experts)
let drop_ready =
bin_ready && helper_on(cfg) && !layer_split(cfg) && cfg_get(cfg, "STRATA_DROP") != "0";
let arena = arena_gb(cfg);
match cfg_get(cfg, "STRATA_EXPERTS")
.trim()
@@ -411,7 +420,18 @@ pub fn build_config(base: &Value, cfg: &Cfg, api_key: &str, mode: &str) -> Resul
args = set_arg(args, flag, v);
}
let helper = cfg_get(cfg, "STRATA_HELPER_GPU").trim();
let has_helper = helper_on(cfg);
let mut has_helper = helper_on(cfg);
if layer_split(cfg) {
// Partage des couches : la clé « gpu » liste les deux cartes (numéros
// nvidia-smi), le serveur de Strata en déduit --layer-split.
let main = cfg_get(cfg, "STRATA_MAIN_GPU")
.trim()
.parse::<i64>()
.unwrap_or(0);
let second = helper.parse::<i64>().unwrap_or(0);
out.insert("gpu".into(), json!([main, second]));
has_helper = false;
}
if has_helper {
args = set_arg(args, "--expert-cache-device1", "auto");
if !has_arg(&args, "--remote-expert-opt") {
@@ -788,6 +808,8 @@ pub struct SettingsReq {
pub vision: bool,
pub helper: bool,
pub experts: String,
/// « helper » ou « layers » ; vide = inchangé (anciens clients).
pub split: String,
}
/// `strataApplySettings` : réécrit les clés du preset. Pure.
@@ -808,6 +830,9 @@ pub fn apply_settings(content: &str, r: &SettingsReq) -> Result<String, String>
{
return Err(format!("placement des experts inconnu : {}", r.experts));
}
if !r.split.is_empty() && r.split != "helper" && r.split != "layers" {
return Err(format!("répartition inconnue : {}", r.split));
}
let mut set: Vec<(&str, String)> = vec![
("CTX", r.ctx.to_string()),
("STRATA_KV", r.kv.clone()),
@@ -818,6 +843,11 @@ pub fn apply_settings(content: &str, r: &SettingsReq) -> Result<String, String>
if !r.experts.is_empty() {
set.push(("STRATA_EXPERTS", r.experts.clone()));
}
if !r.split.is_empty() {
// « helper » est le défaut : la clé disparaît plutôt que d'être écrite
let v = if r.split == "layers" { "layers" } else { "" };
set.push(("STRATA_SPLIT", v.into()));
}
let mut lines: Vec<String> = Vec::new();
for l in content.trim_end_matches('\n').split('\n') {
let k = l.trim().split_once('=').map_or(l.trim(), |(k, _)| k);
@@ -827,6 +857,9 @@ pub fn apply_settings(content: &str, r: &SettingsReq) -> Result<String, String>
lines.push(l.to_string());
}
for (k, v) in set {
if k == "STRATA_SPLIT" && v.is_empty() {
continue;
}
lines.push(format!("{k}={v}"));
}
Ok(lines.join("\n") + "\n")
+83
View File
@@ -405,6 +405,89 @@ fn hors_ram_reserve_et_contexte() {
}
}
// `TestStrataBuildConfigLayerSplit`.
#[test]
fn partage_des_couches() {
let base = json!({
"args": ["--pack", "/p", "--vram-reserve-mib", "900"],
"vision": { "exe": "/v" },
});
let mut c = cfg(&[
("STRATA_MAIN_GPU", "1"),
("STRATA_HELPER_GPU", "0"),
("STRATA_SPLIT", "layers"),
]);
let out = build_config(&base, &c, "", "drop").unwrap();
assert_eq!(out["gpu"], json!([1, 0]));
let a = args_of(&out);
assert!(!has_arg(&a, "--remote-expert-opt") && !has_arg(&a, "--expert-cache-device1"));
assert_eq!(arg_after(&a, "--vram-reserve-mib"), "900");
assert_eq!(arg_after(&a, "--short-read"), "150");
assert!(out["env"].get("STRATA_REMOTE_DROP").is_none());
assert!(out["env"].get("STRATA_PREFILL_HELPER_SOURCE").is_none());
assert!(out["vision"].get("cuda_device").is_none());
c.remove("STRATA_SPLIT");
let out = build_config(&base, &c, "", "drop").unwrap();
assert!(out.get("gpu").is_none());
assert!(has_arg(&args_of(&out), "--remote-expert-opt"));
c.insert("STRATA_SPLIT".into(), "layers".into());
c.insert("STRATA_HELPER".into(), "0".into());
assert!(!layer_split(&c));
}
// `TestStrataResolveExpertsLayerSplit`.
#[test]
fn partage_des_couches_sans_hors_ram() {
let d = tempfile::tempdir().unwrap();
let p = Paths {
home: d.path().to_path_buf(),
};
let pack = d.path().join("pack");
std::fs::create_dir_all(&pack).unwrap();
std::fs::write(pack.join("experts.bin"), "x").unwrap();
std::fs::create_dir_all(p.src_dir().join("engine-cuda12")).unwrap();
std::fs::write(p.engine_marker(), format!("{ENGINE_ASSET}\n")).unwrap();
let args = vec!["--pack".to_string(), pack.to_string_lossy().into_owned()];
let mut c = cfg(&[
("STRATA_CONFIG", "/x/strata-swift-iq3_xxs.json"),
("STRATA_HELPER_GPU", "1"),
("STRATA_DROP", "1"),
]);
assert_eq!(resolve_experts(&p, &c, &args, 44.0, 12.0), "drop");
c.insert("STRATA_SPLIT".into(), "layers".into());
assert_eq!(resolve_experts(&p, &c, &args, 44.0, 12.0), "arena");
c.insert("STRATA_EXPERTS".into(), "ram".into());
assert_eq!(resolve_experts(&p, &c, &args, 44.0, 12.0), "ram");
}
// `TestStrataApplySettingsSplit`.
#[test]
fn reglages_de_la_seconde_carte() {
let input = "ENGINE=strata\nSTRATA_HELPER_GPU=0\n";
let mut r = SettingsReq {
ctx: 131072,
kv: "fp16".into(),
spec: 3,
vision: true,
helper: true,
split: "layers".into(),
..SettingsReq::default()
};
let out = apply_settings(input, &r).unwrap();
assert!(out.contains("STRATA_SPLIT=layers\n"), "{out:?}");
r.split = "helper".into();
let out = apply_settings(&out, &r).unwrap();
assert!(!out.contains("STRATA_SPLIT"), "{out:?}");
r.split = String::new();
let out = apply_settings("STRATA_SPLIT=layers\n", &r).unwrap();
assert!(out.contains("STRATA_SPLIT=layers"), "{out:?}");
r.split = "nimporte".into();
assert_eq!(
apply_settings(input, &r).unwrap_err(),
"répartition inconnue : nimporte"
);
}
#[test]
fn reglages_des_experts() {
let input = "ENGINE=strata\nSTRATA_EXPERTS=auto\n";