feat: retry evicted models in one load action
This commit is contained in:
@@ -47,7 +47,10 @@ The Available downloads controls support:
|
|||||||
|
|
||||||
Popularity and date ordering use upstream metadata only; the plugin does not invent popularity, dates, RAM requirements, or token metrics.
|
Popularity and date ordering use upstream metadata only; the plugin does not invent popularity, dates, RAM requirements, or token metrics.
|
||||||
|
|
||||||
## Per-model placement
|
## Staged multi-model loading
|
||||||
|
|
||||||
|
A single **Load selected permanently** action now loads models in verified stages. RAM-only models are attempted first, followed by GPU + RAM models. The backend checks Ollama `/api/ps` after each runner starts and automatically performs one recovery pass for any selected model Ollama evicted. Already resident models are not reloaded. The result includes a retry count and reports when automatic eviction recovery completed, so users do not need to click the load action again manually.
|
||||||
|
|
||||||
|
|
||||||
Each installed model in the Model pool has a persistent placement selector:
|
Each installed model in the Model pool has a persistent placement selector:
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
-1
@@ -336,7 +336,7 @@
|
|||||||
if (endpoint === "/models/load") {
|
if (endpoint === "/models/load") {
|
||||||
var resident = Array.isArray(result.resident) ? result.resident : ((result.runtime && result.runtime.model_memory) || []).map(function (item) { return item.name; });
|
var resident = Array.isArray(result.resident) ? result.resident : ((result.runtime && result.runtime.model_memory) || []).map(function (item) { return item.name; });
|
||||||
var missing = Array.isArray(result.not_resident) ? result.not_resident : requested.filter(function (name) { return resident.indexOf(name) < 0; });
|
var missing = Array.isArray(result.not_resident) ? result.not_resident : requested.filter(function (name) { return resident.indexOf(name) < 0; });
|
||||||
setNotice(missing.length ? { warning: "Ollama kept resident: " + resident.join(", ") + ". Not resident: " + missing.join(", ") + ". This is an Ollama scheduler/capacity warning, not a plugin error." } : { ok: "Loaded and resident: " + resident.join(", ") });
|
setNotice(missing.length ? { warning: "Ollama kept resident: " + resident.join(", ") + ". Not resident: " + missing.join(", ") + ". This is an Ollama scheduler/capacity warning, not a plugin error." } : { ok: result.message || ("Loaded and resident: " + resident.join(", ")) });
|
||||||
} else {
|
} else {
|
||||||
setNotice({ ok: label + ": " + requested.join(", ") });
|
setNotice({ ok: label + ": " + requested.join(", ") });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"label": "Ollama Models",
|
"label": "Ollama Models",
|
||||||
"description": "Inspect, manage, and chat with local Ollama models, including shared persistent conversations, performance metrics, images, PDFs, URLs, and live memory telemetry.",
|
"description": "Inspect, manage, and chat with local Ollama models, including shared persistent conversations, performance metrics, images, PDFs, URLs, and live memory telemetry.",
|
||||||
"icon": "Cpu",
|
"icon": "Cpu",
|
||||||
"version": "1.5.15",
|
"version": "1.5.16",
|
||||||
"tab": {"path": "/ollama-manager", "position": "after:models"},
|
"tab": {"path": "/ollama-manager", "position": "after:models"},
|
||||||
"entry": "dist/index.js",
|
"entry": "dist/index.js",
|
||||||
"css": "dist/style.css",
|
"css": "dist/style.css",
|
||||||
|
|||||||
+26
-14
@@ -1446,21 +1446,29 @@ def models_load(body: ModelsRequest) -> dict[str, Any]:
|
|||||||
if not names:
|
if not names:
|
||||||
raise HTTPException(400, "Select at least one model to load")
|
raise HTTPException(400, "Select at least one model to load")
|
||||||
placements = {name: _model_placement(body.placements.get(name)) for name in names}
|
placements = {name: _model_placement(body.placements.get(name)) for name in names}
|
||||||
results = []
|
# Load RAM-only models first, then GPU+RAM models. This avoids asking the
|
||||||
for name in names:
|
# GPU scheduler to rearrange an already GPU-resident runner unnecessarily.
|
||||||
_model_load_update(name, active=True, state="queued", stage="Waiting for Ollama", started_at=time.time(), error="", placement=placements[name])
|
ordered_names = sorted(names, key=lambda name: 0 if placements[name] == "ram_only" else 1)
|
||||||
for name in names:
|
results_by_name: dict[str, dict[str, Any]] = {}
|
||||||
placement = placements[name]
|
for pass_index in range(2):
|
||||||
stage = "Loading into GPU + RAM" if placement == "gpu_ram" else "Loading into system RAM only"
|
resident_now = {str(row.get("name") or row.get("model")) for row in _local_ps()}
|
||||||
_model_load_update(name, state="loading", stage=stage)
|
missing_now = [name for name in ordered_names if name not in resident_now]
|
||||||
try:
|
if not missing_now:
|
||||||
results.append({"name": name, "placement": placement, "ok": True, "result": _load_model(name, placement)})
|
break
|
||||||
_model_load_update(name, state="checking", stage="Checking Ollama resident state")
|
for name in missing_now:
|
||||||
except Exception as exc:
|
placement = placements[name]
|
||||||
results.append({"name": name, "placement": placement, "ok": False, "error": str(exc)})
|
stage = "Loading into GPU + RAM" if placement == "gpu_ram" else "Loading into system RAM only"
|
||||||
_model_load_update(name, active=False, state="failed", stage="Ollama load failed", finished_at=time.time(), error=str(exc))
|
_model_load_update(name, active=True, state="loading", stage=stage + " · pass " + str(pass_index + 1), attempt=pass_index + 1)
|
||||||
|
try:
|
||||||
|
result = _load_model(name, placement)
|
||||||
|
results_by_name[name] = {"name": name, "placement": placement, "ok": True, "result": result, "attempts": pass_index + 1}
|
||||||
|
_model_load_update(name, state="checking", stage="Checking Ollama resident state", attempt=pass_index + 1)
|
||||||
|
except Exception as exc:
|
||||||
|
results_by_name[name] = {"name": name, "placement": placement, "ok": False, "error": str(exc), "attempts": pass_index + 1}
|
||||||
|
_model_load_update(name, active=False, state="failed", stage="Ollama load failed", finished_at=time.time(), error=str(exc), attempt=pass_index + 1)
|
||||||
resident_rows = _local_ps()
|
resident_rows = _local_ps()
|
||||||
resident_names = {str(row.get("name") or row.get("model")) for row in resident_rows}
|
resident_names = {str(row.get("name") or row.get("model")) for row in resident_rows}
|
||||||
|
results = [results_by_name.get(name, {"name": name, "placement": placements[name], "ok": name in resident_names, "attempts": 0}) for name in names]
|
||||||
for item in results:
|
for item in results:
|
||||||
item["resident"] = item["name"] in resident_names
|
item["resident"] = item["name"] in resident_names
|
||||||
not_resident = [name for name in names if name not in resident_names]
|
not_resident = [name for name in names if name not in resident_names]
|
||||||
@@ -1470,16 +1478,20 @@ def models_load(body: ModelsRequest) -> dict[str, Any]:
|
|||||||
elif item["ok"]:
|
elif item["ok"]:
|
||||||
_model_load_update(item["name"], active=False, state="evicted", stage="Ollama did not retain this model", finished_at=time.time())
|
_model_load_update(item["name"], active=False, state="evicted", stage="Ollama did not retain this model", finished_at=time.time())
|
||||||
runtime = _runtime_snapshot()
|
runtime = _runtime_snapshot()
|
||||||
|
retry_count = sum(max(0, int(item.get("attempts") or 0) - 1) for item in results)
|
||||||
return {
|
return {
|
||||||
"ok": bool(results) and not not_resident and all(item["ok"] for item in results),
|
"ok": bool(results) and not not_resident and all(item["ok"] for item in results),
|
||||||
"requested": names,
|
"requested": names,
|
||||||
"resident": sorted(resident_names),
|
"resident": sorted(resident_names),
|
||||||
"not_resident": not_resident,
|
"not_resident": not_resident,
|
||||||
"results": results,
|
"results": results,
|
||||||
|
"retries": retry_count,
|
||||||
"runtime": runtime,
|
"runtime": runtime,
|
||||||
"keep_alive": "permanent",
|
"keep_alive": "permanent",
|
||||||
"message": (
|
"message": (
|
||||||
"All selected models are resident."
|
"All selected models are resident. Automatic Ollama eviction recovery completed."
|
||||||
|
if not not_resident and retry_count
|
||||||
|
else "All selected models are resident."
|
||||||
if not not_resident
|
if not not_resident
|
||||||
else "Ollama did not keep every selected model resident; see not_resident."
|
else "Ollama did not keep every selected model resident; see not_resident."
|
||||||
),
|
),
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
name: ollama-manager
|
name: ollama-manager
|
||||||
version: 1.5.15
|
version: 1.5.16
|
||||||
description: Native dashboard manager and chat interface for local Ollama models, attachments, URLs, shared persistent conversations, performance metrics, and live runtime telemetry.
|
description: Native dashboard manager and chat interface for local Ollama models, attachments, URLs, shared persistent conversations, performance metrics, and live runtime telemetry.
|
||||||
auto_install_dependencies: true
|
auto_install_dependencies: true
|
||||||
python_dependencies:
|
python_dependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user