Moteur : LOAD_GUARD ne refuse plus sur la seule VRAM NVIDIA (Vulkan, cartes mixtes)

La borne basse des poids en RAM se calculait avec la VRAM de nvidia-smi. Un
moteur Vulkan sur une machine NVIDIA + AMD, sans --device, place aussi des
poids sur la carte AMD : la borne était fausse et le lancement refusé à tort.
Un moteur ROCm à côté d'une carte NVIDIA n'utilise pas du tout celle-ci.

- Le refus se décide désormais sur les cartes que le moteur liste lui-même
  (--list-devices, dans l'environnement du lancement, sélection GPU
  comprise), toutes additionnées : rien au-delà ne peut recevoir de poids, la
  borne est sûre. Une carte listée sans servir (iGPU) ne fait que rendre le
  refus plus rare.
- Lu seulement quand un refus se profile sur la VRAM NVIDIA : aucun lancement
  de plus dans le cas ordinaire. Les cartes de SPLIT_MODE sont reprises si
  elles sont déjà lues.
- Liste illisible, vide ou carte à 0 Mio (déjà pleine) : avertissement,
  jamais de refus. Même chose dans l'aperçu de l'éditeur, qui reprend la
  dernière liste complète du moteur (devices.json) sans le lancer.
- Le reste ne bouge pas : VRAM inconnue, --device, --rpc, mémoire unifiée.
- Tests : faux refus Vulkan évité, nvidia-smi seul n'est qu'un avis, lecture
  déclenchée seulement avec un refus en vue, somme des cartes (JSON relu).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Opus 5.5 committed 2026-10-04 14:33:46 +02:00
1 parent a4db6d4da1
commit f0fef91773
5 files changed
+164 -16

No files matched your search

+26 -4
View File
@@ -98,17 +98,39 @@ func probeSplit(cfg map[string]string, bin string, si *serveSysInfo) {
if v := os.Getenv("GGML_CUDA_ALLREDUCE"); v != "" {
si.UserEnv["GGML_CUDA_ALLREDUCE"] = v
}
si.SplitDevs = listEngineDevices(bin)
}
// listEngineDevices : les cartes que CE moteur voit (--list-devices), dans
// l'environnement du processus — celui du vrai lancement quand cmdServe
// appelle. nil si le moteur échoue : sortie tronquée (carte pleine, moteur
// planté), on ne s'y fie pas.
func listEngineDevices(bin string) []splitDev {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
out, err := hideCmd(exec.CommandContext(ctx, bin, "--list-devices")).CombinedOutput()
if err != nil {
return // sortie tronquée (carte pleine, moteur planté) : on ne s'y fie pas
return nil
}
for _, d := range parseListDevices(string(out)) {
return splitDevsFrom(parseListDevices(string(out)))
}
// splitDevsFrom : les cartes de parseListDevices (ou de devices.json, relu en
// JSON : les nombres y sont des float64).
func splitDevsFrom(list []map[string]any) []splitDev {
var devs []splitDev
for _, d := range list {
id, _ := d["id"].(string)
total, _ := d["total_mib"].(int)
si.SplitDevs = append(si.SplitDevs, splitDev{ID: id, TotalMiB: int64(total)})
var total int64
switch v := d["total_mib"].(type) {
case int:
total = int64(v)
case float64:
total = int64(v)
}
devs = append(devs, splitDev{ID: id, TotalMiB: total})
}
return devs
}
// splitPlan décide de SPLIT_MODE=tensor pour ce lancement. Fonction pure.