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

+41 -1
View File
@@ -220,7 +220,9 @@ func strataPackHasExpertsBin(args []string) bool {
// Pure : la décision se teste en table.
func strataResolveExperts(cfg map[string]string, args []string, availGB, helperGB float64) string {
binReady := strataPackHasExpertsBin(args) && strataEngineInstalled() == strataEngineAsset
dropReady := binReady && strataHelperOn(cfg) && cfg["STRATA_DROP"] != "0"
// (experts de la carte d'aide hors RAM : propre au mode d'aide, jamais en
// partage des couches, où la seconde carte ne garde pas d'experts)
dropReady := binReady && strataHelperOn(cfg) && !strataLayerSplit(cfg) && cfg["STRATA_DROP"] != "0"
arena := strataArenaGB(cfg)
switch strings.ToLower(strings.TrimSpace(cfg["STRATA_EXPERTS"])) {
case strataExpertsDisk:
@@ -504,6 +506,7 @@ type strataDetails struct {
// pour les réglages : ce que la machine permet et l'état actuel
HasHelper bool `json:"has_helper"` // une carte d'aide est disponible
HelperOn bool `json:"helper_on"`
Split bool `json:"split_layers"` // les deux cartes se partagent les couches
Vision bool `json:"vision"`
}
@@ -565,6 +568,7 @@ func strataDetailsFor(presetID string, env strataEnv) *strataDetails {
if strataHelperOn(pc) {
d.HelperOn = true
d.HelperGPU = gpuName(h)
d.Split = strataLayerSplit(pc)
}
}
d.Vision = pc["STRATA_VISION"] != "0"
@@ -968,6 +972,16 @@ func strataBuildConfig(base map[string]any, cfg map[string]string, apiKey, mode
}
helper := strings.TrimSpace(cfg["STRATA_HELPER_GPU"])
hasHelper := strataHelperOn(cfg)
if strataLayerSplit(cfg) {
// Partage des couches : le serveur de Strata le fait de lui-même quand sa
// clé « gpu » liste plusieurs cartes (--layer-split auto, réparti selon la
// VRAM libre), numérotées comme nvidia-smi. Pas de chemin « aide » : la
// réserve de VRAM de l'installeur et l'encodeur d'images restent en place.
main, _ := strconv.Atoi(strings.TrimSpace(cfg["STRATA_MAIN_GPU"]))
second, _ := strconv.Atoi(helper)
out["gpu"] = []any{main, second}
hasHelper = false
}
// Deuxième carte en aide : elle garde et CALCULE sa part des experts (le
// chemin « helper » du moteur), la carte principale reste CUDA0. Les cartes
// sont posées par CUDA_VISIBLE_DEVICES (serveStrata), PAS par la clé « gpu » :
@@ -1191,6 +1205,16 @@ func strataHelperOn(cfg map[string]string) bool {
return h != "" && h != "-1" && cfg["STRATA_HELPER"] != "0"
}
// strataLayerSplit : les deux cartes se partagent les COUCHES du modèle (clé de
// preset STRATA_SPLIT=layers) au lieu que la seconde n'aide que pour une part
// des experts. Mesuré par un utilisateur d'AJEAN sur deux RTX 3060 identiques
// (#121 amont) : la carte d'aide ne travaillait qu'à ~15 %, le partage des
// couches donnait +50 % en génération. Sur deux cartes très différentes, le
// mode d'aide reste le défaut mesuré : le partage est un choix, jamais imposé.
func strataLayerSplit(cfg map[string]string) bool {
return strataHelperOn(cfg) && cfg["STRATA_SPLIT"] == "layers"
}
// strataCudaDevices : ordre des cartes vu par le moteur, la principale en CUDA0.
func strataCudaDevices(cfg map[string]string) string {
main := strings.TrimSpace(cfg["STRATA_MAIN_GPU"])
@@ -1437,6 +1461,9 @@ type strataSettingsReq struct {
Vision bool `json:"vision"`
Helper bool `json:"helper"`
Experts string `json:"experts"` // auto, ram ou disk ; vide = inchangé
// Split : rôle de la seconde carte, « helper » (aide aux experts) ou
// « layers » (partage des couches). Vide = inchangé (anciens clients).
Split string `json:"split"`
}
// strataApplySettings réécrit les clés du preset (pure, testable).
@@ -1451,6 +1478,8 @@ func strataApplySettings(content string, r strataSettingsReq) (string, error) {
return "", fmt.Errorf("MTP hors limites : %d", r.Spec)
case r.Experts != "" && r.Experts != strataExpertsAuto && r.Experts != strataExpertsRAM && r.Experts != strataExpertsDisk:
return "", fmt.Errorf("placement des experts inconnu : %s", r.Experts)
case r.Split != "" && r.Split != "helper" && r.Split != "layers":
return "", fmt.Errorf("répartition inconnue : %s", r.Split)
}
set := map[string]string{
"CTX": strconv.Itoa(r.Ctx), "STRATA_KV": r.KV, "STRATA_SPEC": strconv.Itoa(r.Spec),
@@ -1461,6 +1490,14 @@ func strataApplySettings(content string, r strataSettingsReq) (string, error) {
set["STRATA_EXPERTS"] = r.Experts
keys = append(keys, "STRATA_EXPERTS")
}
if r.Split != "" {
// « helper » est le défaut : la clé disparaît plutôt que d'être écrite
set["STRATA_SPLIT"] = ""
if r.Split == "layers" {
set["STRATA_SPLIT"] = "layers"
}
keys = append(keys, "STRATA_SPLIT")
}
var lines []string
for _, l := range strings.Split(strings.TrimRight(content, "\n"), "\n") {
k, _, _ := strings.Cut(strings.TrimSpace(l), "=")
@@ -1470,6 +1507,9 @@ func strataApplySettings(content string, r strataSettingsReq) (string, error) {
lines = append(lines, l)
}
for _, k := range keys {
if set[k] == "" && k == "STRATA_SPLIT" {
continue
}
lines = append(lines, k+"="+set[k])
}
return strings.Join(lines, "\n") + "\n", nil
+124
View File
@@ -0,0 +1,124 @@
package loki
import (
"os"
"path/filepath"
"strings"
"testing"
)
// Partage des couches (#121 amont) : la clé « gpu » liste les deux cartes (le
// serveur de Strata en déduit --layer-split), et rien du mode d'aide ne subsiste.
func TestStrataBuildConfigLayerSplit(t *testing.T) {
base := map[string]any{
"args": []any{"--pack", "/p", "--vram-reserve-mib", "900"},
"vision": map[string]any{"exe": "/v"},
}
cfg := map[string]string{"STRATA_MAIN_GPU": "1", "STRATA_HELPER_GPU": "0", "STRATA_SPLIT": "layers"}
out, err := strataBuildConfig(base, cfg, "", "drop")
if err != nil {
t.Fatal(err)
}
g, ok := out["gpu"].([]any)
if !ok || len(g) != 2 || g[0] != 1 || g[1] != 0 {
t.Fatalf("gpu = %v, attendu [1 0] (principale d'abord, numéros nvidia-smi)", out["gpu"])
}
a := strataArgs(t, out)
if strataHasArg(a, "--remote-expert-opt") || strataHasArg(a, "--expert-cache-device1") {
t.Errorf("options du mode d'aide en partage des couches : %v", a)
}
if argAfter(a, "--vram-reserve-mib") != "900" {
t.Errorf("la réserve de VRAM de l'installeur doit rester : %v", a)
}
if argAfter(a, "--short-read") != "150" {
t.Errorf("lecture des prompts par la carte d'aide en partage des couches : %v", a)
}
env := out["env"].(map[string]any)
if env["STRATA_REMOTE_DROP"] != nil || env["STRATA_PREFILL_HELPER_SOURCE"] != nil {
t.Errorf("experts hors RAM ou source d'aide (mode d'aide) en partage des couches : %v", env)
}
if _, has := out["vision"].(map[string]any)["cuda_device"]; has {
t.Errorf("encodeur d'images déplacé sur la seconde carte en partage des couches")
}
// Sans la clé, rien ne change : le mode d'aide reste le défaut.
delete(cfg, "STRATA_SPLIT")
out, _ = strataBuildConfig(base, cfg, "", "drop")
if _, has := out["gpu"]; has {
t.Errorf("clé gpu posée en mode d'aide : %v", out["gpu"])
}
if !strataHasArg(strataArgs(t, out), "--remote-expert-opt") {
t.Errorf("mode d'aide perdu sans STRATA_SPLIT")
}
// Seconde carte coupée : pas de partage même si la clé traîne.
cfg["STRATA_SPLIT"], cfg["STRATA_HELPER"] = "layers", "0"
if strataLayerSplit(cfg) {
t.Errorf("partage des couches avec la seconde carte coupée")
}
}
// En partage des couches, la seconde carte ne garde pas d'experts : le mode
// hors RAM (qui compte sur elle pour en loger une part) n'est jamais choisi.
func TestStrataResolveExpertsLayerSplit(t *testing.T) {
home := t.TempDir()
t.Setenv("LOKI_HOME", home)
pack := filepath.Join(home, "pack")
_ = os.MkdirAll(pack, 0o755)
_ = os.WriteFile(filepath.Join(pack, "experts.bin"), []byte("x"), 0o644)
_ = os.MkdirAll(filepath.Join(strataSrcDir(), "engine-cuda12"), 0o755)
_ = os.WriteFile(strataEngineMarker(), []byte(strataEngineAsset+"\n"), 0o644)
args := []string{"--pack", pack}
cfg := map[string]string{"STRATA_CONFIG": "/x/strata-swift-iq3_xxs.json", "STRATA_HELPER_GPU": "1", "STRATA_DROP": "1"}
if got := strataResolveExperts(cfg, args, 44, 12); got != "drop" {
t.Fatalf("mode d'aide : %s, attendu drop", got)
}
cfg["STRATA_SPLIT"] = "layers"
if got := strataResolveExperts(cfg, args, 44, 12); got != "arena" {
t.Errorf("partage des couches : %s, attendu arena", got)
}
cfg["STRATA_EXPERTS"] = "ram"
if got := strataResolveExperts(cfg, args, 44, 12); got != "ram" {
t.Errorf("partage des couches, experts en RAM : %s, attendu ram", got)
}
}
func TestStrataApplySettingsSplit(t *testing.T) {
in := "ENGINE=strata\nSTRATA_HELPER_GPU=0\n"
req := strataSettingsReq{Ctx: 131072, KV: "fp16", Spec: 3, Vision: true, Helper: true, Split: "layers"}
out, err := strataApplySettings(in, req)
if err != nil || !strings.Contains(out, "STRATA_SPLIT=layers\n") {
t.Fatalf("STRATA_SPLIT=layers attendu : %q (%v)", out, err)
}
req.Split = "helper"
if out, _ = strataApplySettings(out, req); strings.Contains(out, "STRATA_SPLIT") {
t.Errorf("retour au mode d'aide : la clé doit disparaître : %q", out)
}
// un ancien client n'envoie pas « split » : la clé est conservée
req.Split = ""
out, _ = strataApplySettings("STRATA_SPLIT=layers\n", req)
if !strings.Contains(out, "STRATA_SPLIT=layers") {
t.Errorf("clé effacée par un client qui ne la connaît pas : %q", out)
}
req.Split = "nimporte"
if _, err := strataApplySettings(in, req); err == nil {
t.Errorf("répartition inconnue acceptée")
}
}
func TestStrataDetailsForLayerSplit(t *testing.T) {
home := t.TempDir()
t.Setenv("LOKI_HOME", home)
base := filepath.Join(home, "base.json")
_ = os.WriteFile(base, []byte(`{"args":["--pack","/p"],"vision":{"exe":"v"}}`), 0o644)
_ = os.MkdirAll(presetsDir(), 0o755)
preset := "ENGINE=strata\nSTRATA_CONFIG=" + base + "\nSTRATA_MAIN_GPU=1\nSTRATA_HELPER_GPU=0\nSTRATA_SPLIT=layers\n"
_ = os.WriteFile(filepath.Join(presetsDir(), "X.env"), []byte(preset), 0o644)
env := strataEnv{GPUs: []strataGPU{{Index: 0, Name: "NVIDIA GeForce RTX 3060", VRAMGB: 12}, {Index: 1, Name: "NVIDIA GeForce RTX 3060", VRAMGB: 12}}}
d := strataDetailsFor("X", env)
if d == nil {
t.Fatal("pas de détails")
}
if !d.Split || !d.HelperOn || d.HelperGPU == "" || d.VisionGPU != d.MainGPU || d.ShortRead != "150" {
t.Errorf("%+v", *d)
}
}
+10 -1
View File
@@ -333,7 +333,7 @@ func TestPariteStrata(t *testing.T) {
mu.Unlock()
st.After = map[string]string{}
cfg := ReadConfig()
for _, k := range []string{"ENGINE", "STRATA_CONFIG", "CTX", "STRATA_KV", "STRATA_HELPER"} {
for _, k := range []string{"ENGINE", "STRATA_CONFIG", "CTX", "STRATA_KV", "STRATA_HELPER", "STRATA_SPLIT"} {
if v := cfg[k]; v != "" {
st.After["config/"+k] = mask(v)
}
@@ -382,6 +382,10 @@ func TestPariteStrata(t *testing.T) {
Method: "POST", Path: "/api/strata/settings", Body: settings("S", 98304, "")},
{Name: "réglages du preset actif pendant une optimisation", Tune: true,
Method: "POST", Path: "/api/strata/settings", Body: settings("S", 98304, "")},
{Name: "réglages, répartition inconnue", Method: "POST", Path: "/api/strata/settings", Body: settings("S", 98304, `,"split":"nimporte"`)},
{Name: "réglages, partage des couches", Method: "POST", Path: "/api/strata/settings", Body: settings("S", 98304, `,"split":"layers"`)},
{Name: "réglages d'un ancien client, partage gardé", Method: "POST", Path: "/api/strata/settings", Body: settings("S", 98304, "")},
{Name: "réglages, retour à l'aide aux experts", Method: "POST", Path: "/api/strata/settings", Body: settings("S", 98304, `,"split":"helper"`)},
// Installation.
{Name: "installation en GET", Method: "GET", Path: "/api/strata/install"},
@@ -417,6 +421,11 @@ func TestPariteStrata(t *testing.T) {
"STRATA_CONFIG={{HOME}}/strata/" + srcDir + "/strata-swift-iq3_xxs.json\nSTRATA_MAIN_GPU=1\nSTRATA_HELPER_GPU=0\n" +
"STRATA_VISION=1\nSTRATA_EXPERTS=auto\nSTRATA_DROP=1\nSTRATA_KV=fp16\nSTRATA_KV_RESIDENT=32768\nSTRATA_SPEC=3\nCTX=196608\nREASONING=off\n",
}, Method: "GET", Path: "/api/strata"},
{Name: "seconde carte en partage des couches", Files: map[string]string{
"presets/STRATA FLASH NEXT SWIFT 1.5 IQ3_XXS.env": "# NAME=Qwen3.8 Flash Next Swift 1.5 IQ3_XXS (Strata)\nENGINE=strata\n" +
"STRATA_CONFIG={{HOME}}/strata/" + srcDir + "/strata-swift-iq3_xxs.json\nSTRATA_MAIN_GPU=1\nSTRATA_HELPER_GPU=0\n" +
"STRATA_VISION=1\nSTRATA_EXPERTS=auto\nSTRATA_DROP=1\nSTRATA_KV=fp16\nSTRATA_KV_RESIDENT=32768\nSTRATA_SPEC=3\nCTX=196608\nREASONING=off\nSTRATA_SPLIT=layers\n",
}, Method: "GET", Path: "/api/strata"},
{Name: "mode hors RAM coupé dans le preset", Files: map[string]string{
"presets/STRATA FLASH NEXT SWIFT 1.5 IQ3_XXS.env": "# NAME=Qwen3.8 Flash Next Swift 1.5 IQ3_XXS (Strata)\nENGINE=strata\n" +
"STRATA_CONFIG={{HOME}}/strata/" + srcDir + "/strata-swift-iq3_xxs.json\nSTRATA_MAIN_GPU=1\nSTRATA_HELPER_GPU=0\n" +
+11 -3
View File
@@ -4521,6 +4521,7 @@ async function loadCfg(){
rows.push(row('CTX', d.ctx));
rows.push(row('KV', d.kv+(d.kv_resident ? ' · '+k(d.kv_resident)+' VRAM' : '')));
rows.push(row('MTP', d.spec));
if(d.helper_gpu) rows.push(row('GPU 2', d.split_layers ? 'couches' : 'aide', d.helper_gpu));
rows.push(row('EXPERTS', ({drop:'mlock (hors appoint)', ram:'RAM', arena:'experts.bin', disk:'mmap (disque)'})[d.mode] || (d.mmap ? 'mmap' : 'RAM'), d.mode_label||''));
if(d.vision_gpu) rows.push(row('VISION', 'on', d.vision_gpu));
}else if(c.CTX) rows.push(row('CTX', c.CTX));
@@ -9281,7 +9282,11 @@ function strataRenderSettings(fit){
['Experts', sel('strata-s-experts', [['auto','auto (selon la RAM libre)'],['ram','en RAM (le plus rapide)'],['disk','depuis le disque (mmap, économe)']], d.experts||'auto')],
['Lire les images', sw('strata-s-vision', d.vision)],
];
if(d.has_helper) rows.push(["Carte d'appoint", sw('strata-s-helper', d.helper_on)]);
// Seconde carte : aide aux experts (défaut mesuré) ou partage des couches,
// souvent plus rapide avec deux cartes identiques (#121 d'AJEAN).
if(d.has_helper) rows.push(['Seconde carte', sel('strata-s-helper',
[['off', 'non utilisée'], ['helper', 'aide aux experts'], ['layers', 'partage des couches']],
!d.helper_on ? 'off' : d.split_layers ? 'layers' : 'helper')]);
document.getElementById('strata-settings').innerHTML = rows.map(([a,b])=>
'<div class="moe-kv"><span>'+escHtml(a)+'</span><span>'+b+'</span></div>').join('');
strataCtxLbl();
@@ -9301,8 +9306,11 @@ async function strataSaveSettings(){
if(!fit || !fit.preset) return;
const v = id => document.getElementById(id);
const body = {preset: fit.preset, ctx: +v('strata-s-ctx').value, kv: v('strata-s-kv').value, spec: +v('strata-s-spec').value,
vision: v('strata-s-vision').checked, helper: v('strata-s-helper') ? v('strata-s-helper').checked : false,
vision: v('strata-s-vision').checked,
experts: v('strata-s-experts') ? v('strata-s-experts').value : ''};
const second = v('strata-s-helper') ? v('strata-s-helper').value : 'off';
body.helper = second !== 'off';
if(body.helper) body.split = second;
if(fit.active && !await askConfirm('Le modèle va redémarrer avec ces réglages (quelques minutes).', {title:'Strata — Qwen3.8 Flash Next', okText:'Enregistrer'})) return;
const r = await jpost('/api/strata/settings', body);
if(!r.ok){ toast('Erreur : '+(r.error||'')); return; }
@@ -9321,7 +9329,7 @@ function strataRenderDetails(fit){
const k = n => (+n >= 1024 ? Math.round(+n/1024)+'K' : n);
const rows = [
['Carte principale', d.main_gpu],
["Carte d'appoint", d.helper_gpu || 'aucune'],
['Seconde carte', d.helper_gpu ? d.helper_gpu + (d.split_layers ? ' · partage des couches' : ' · aide aux experts') : 'aucune'],
['Lecture des images', d.vision_gpu || 'aucune'],
['Contexte', (+d.ctx).toLocaleString('fr-FR')+' jetons'],
['Cache KV', d.kv + (d.kv_resident ? ' · '+k(d.kv_resident)+' en VRAM, le reste en RAM' : '')],
+1
View File
@@ -330,6 +330,7 @@ async function loadCfg(){
rows.push(row('CTX', d.ctx));
rows.push(row('KV', d.kv+(d.kv_resident ? ' · '+k(d.kv_resident)+' VRAM' : '')));
rows.push(row('MTP', d.spec));
if(d.helper_gpu) rows.push(row('GPU 2', d.split_layers ? 'couches' : 'aide', d.helper_gpu));
rows.push(row('EXPERTS', ({drop:'mlock (hors appoint)', ram:'RAM', arena:'experts.bin', disk:'mmap (disque)'})[d.mode] || (d.mmap ? 'mmap' : 'RAM'), d.mode_label||''));
if(d.vision_gpu) rows.push(row('VISION', 'on', d.vision_gpu));
}else if(c.CTX) rows.push(row('CTX', c.CTX));
+10 -3
View File
@@ -128,7 +128,11 @@ function strataRenderSettings(fit){
['Experts', sel('strata-s-experts', [['auto','auto (selon la RAM libre)'],['ram','en RAM (le plus rapide)'],['disk','depuis le disque (mmap, économe)']], d.experts||'auto')],
['Lire les images', sw('strata-s-vision', d.vision)],
];
if(d.has_helper) rows.push(["Carte d'appoint", sw('strata-s-helper', d.helper_on)]);
// Seconde carte : aide aux experts (défaut mesuré) ou partage des couches,
// souvent plus rapide avec deux cartes identiques (#121 d'AJEAN).
if(d.has_helper) rows.push(['Seconde carte', sel('strata-s-helper',
[['off', 'non utilisée'], ['helper', 'aide aux experts'], ['layers', 'partage des couches']],
!d.helper_on ? 'off' : d.split_layers ? 'layers' : 'helper')]);
document.getElementById('strata-settings').innerHTML = rows.map(([a,b])=>
'<div class="moe-kv"><span>'+escHtml(a)+'</span><span>'+b+'</span></div>').join('');
strataCtxLbl();
@@ -148,8 +152,11 @@ async function strataSaveSettings(){
if(!fit || !fit.preset) return;
const v = id => document.getElementById(id);
const body = {preset: fit.preset, ctx: +v('strata-s-ctx').value, kv: v('strata-s-kv').value, spec: +v('strata-s-spec').value,
vision: v('strata-s-vision').checked, helper: v('strata-s-helper') ? v('strata-s-helper').checked : false,
vision: v('strata-s-vision').checked,
experts: v('strata-s-experts') ? v('strata-s-experts').value : ''};
const second = v('strata-s-helper') ? v('strata-s-helper').value : 'off';
body.helper = second !== 'off';
if(body.helper) body.split = second;
if(fit.active && !await askConfirm('Le modèle va redémarrer avec ces réglages (quelques minutes).', {title:'Strata — Qwen3.8 Flash Next', okText:'Enregistrer'})) return;
const r = await jpost('/api/strata/settings', body);
if(!r.ok){ toast('Erreur : '+(r.error||'')); return; }
@@ -168,7 +175,7 @@ function strataRenderDetails(fit){
const k = n => (+n >= 1024 ? Math.round(+n/1024)+'K' : n);
const rows = [
['Carte principale', d.main_gpu],
["Carte d'appoint", d.helper_gpu || 'aucune'],
['Seconde carte', d.helper_gpu ? d.helper_gpu + (d.split_layers ? ' · partage des couches' : ' · aide aux experts') : 'aucune'],
['Lecture des images', d.vision_gpu || 'aucune'],
['Contexte', (+d.ctx).toLocaleString('fr-FR')+' jetons'],
['Cache KV', d.kv + (d.kv_resident ? ' · '+k(d.kv_resident)+' en VRAM, le reste en RAM' : '')],