diff --git a/internal/loki/sys_loadpct_linux.go b/internal/loki/sys_loadpct_linux.go new file mode 100644 index 0000000..c41ad23 --- /dev/null +++ b/internal/loki/sys_loadpct_linux.go @@ -0,0 +1,76 @@ +//go:build linux + +package loki + +// sys_loadpct_linux.go — pourcentage de CHARGEMENT du modèle, pour la pastille +// « chargement… » de l'UI qui restait muette de longues minutes sur un gros +// GGUF (un 27B Q4 met facilement 1 à 3 minutes à monter en VRAM). +// +// llama-server n'expose aucun progrès pendant le chargement (/health renvoie +// juste 503). Mais le chargement, c'est LIRE le fichier .gguf : on mesure donc +// les octets lus par le process moteur (/proc//io, read_bytes — les pages +// mmap rentrées comptent dedans) et on les rapporte à la taille du fichier. +// Approximation honnête : monotone, ~0→100, et surtout elle BOUGE — c'est ce +// qui distingue « ça charge » de « c'est planté ». + +import ( + "os" + "path/filepath" + "strconv" + "strings" +) + +// engineLoadPct renvoie le pourcentage estimé de chargement du modèle, ou -1 +// quand il n'est pas mesurable (pas de PID, /proc illisible, taille inconnue). +func engineLoadPct() int { + pid := readServicePID() + if pid <= 0 { + return -1 + } + cfg := ReadConfig() + model := cfg["MODEL"] + if model == "" { + return -1 + } + path, err := resolveServeModelPath(model) + if err != nil { + return -1 + } + size := modelTotalSize(path) + if size <= 0 { + return -1 + } + b, err := os.ReadFile("/proc/" + strconv.Itoa(pid) + "/io") + if err != nil { + return -1 + } + var read int64 = -1 + for _, line := range strings.Split(string(b), "\n") { + if v, ok := strings.CutPrefix(line, "read_bytes:"); ok { + read, _ = strconv.ParseInt(strings.TrimSpace(v), 10, 64) + break + } + } + if read < 0 { + return -1 + } + pct := int(read * 100 / size) + if pct > 99 { + // 100 % n'existe pas ici : c'est /health qui dit « prêt ». Au-delà de 99 + // (relectures, autres IO du process), on plafonne. + pct = 99 + } + return pct +} + +// modelTotalSize : taille du modèle, shards compris (model-00001-of-00004.gguf +// → somme des tranches présentes — c'est tout ça qu'il faut charger). +func modelTotalSize(path string) int64 { + if total := shardFamilySize(filepath.Dir(path), filepath.Base(path)); total > 0 { + return total + } + if st, err := os.Stat(path); err == nil { + return st.Size() + } + return 0 +} diff --git a/internal/loki/sys_loadpct_other.go b/internal/loki/sys_loadpct_other.go new file mode 100644 index 0000000..bcdd404 --- /dev/null +++ b/internal/loki/sys_loadpct_other.go @@ -0,0 +1,7 @@ +//go:build !linux + +package loki + +// Hors Linux, pas de /proc//io : le pourcentage de chargement n'est pas +// mesurable, l'UI garde « chargement… » sans chiffre. +func engineLoadPct() int { return -1 } diff --git a/internal/loki/ui/index.html b/internal/loki/ui/index.html index e5925a0..d8b6dad 100644 --- a/internal/loki/ui/index.html +++ b/internal/loki/ui/index.html @@ -2199,7 +2199,9 @@ html[data-files="1"] #files-btn{color:var(--accent)}
- +
@@ -2752,7 +2754,7 @@ html[data-files="1"] #files-btn{color:var(--accent)} - +