From 399acabd580ffa673b79e9b93adee31b6146c1cd Mon Sep 17 00:00:00 2001 From: TBNilles Date: Sun, 7 Jun 2026 17:14:37 -0400 Subject: [PATCH] feat(model-manager): "Free GPU memory" button to unload ComfyUI models ComfyUI caches the last model when RAM is plentiful (unified memory), so memory doesn't drop after switching models even though models are being swapped, not accumulated. Add a sidebar "Free GPU memory" button that proxies ComfyUI's POST /free (unload_models + free_memory) via a new /api/comfyui/free endpoint (COMFYUI_URL env). Verified it releases ~7GB. README documents this plus the --disable-smart-memory auto-unload option. Co-Authored-By: Claude Opus 4.8 --- README.md | 5 +++++ docker-compose.yml | 2 ++ model-manager/app/config.py | 4 ++++ model-manager/app/main.py | 15 +++++++++++++++ model-manager/app/static/app.js | 18 ++++++++++++++++++ model-manager/app/static/index.html | 7 ++++++- model-manager/app/static/style.css | 3 +++ 7 files changed, 53 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a5d0ec6..6d0e012 100644 --- a/README.md +++ b/README.md @@ -256,6 +256,11 @@ SparkyUI includes a **StabilityMatrix-style Model Manager** - a lightweight Fast - **HuggingFace** - paste a `resolve` URL (works with gated repos via your token) - **Settings** - store your **CivitAI API key** and **HuggingFace token** persistently (saved to a SQLite DB under `./sparkyui-data`, never committed to git) +- **Free GPU memory** - a sidebar button that unloads all models from ComfyUI and releases + memory (proxies ComfyUI's `/free`). ComfyUI keeps the last model cached for fast reuse when + RAM is plentiful, so memory won't drop on its own after switching models - use this to + release it on demand. (For automatic unload after every generation, add + `--disable-smart-memory` to `COMFYUI_FLAGS`, at the cost of reloading each run.) **How it works:** - Runs as a FastAPI server in its own container (`python:3.12-slim`) diff --git a/docker-compose.yml b/docker-compose.yml index b900d9b..84e934a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -154,6 +154,8 @@ services: # Ports used by the device-routing landing page (/start) COMFYUI_PORT: "${COMFYUI_PORT:-8188}" COMFYUIMINI_PORT: "${COMFYUIMINI_PORT:-3000}" + # Internal URL used to proxy the "Free GPU memory" action + COMFYUI_URL: "http://comfyui:8188" volumes: # Shared models dir - read-WRITE here so downloads land on the host. diff --git a/model-manager/app/config.py b/model-manager/app/config.py index 5458e12..4ba815b 100644 --- a/model-manager/app/config.py +++ b/model-manager/app/config.py @@ -19,6 +19,10 @@ DB_PATH = DATA_DIR / "manager.db" COMFYUI_PORT = os.environ.get("COMFYUI_PORT", "8188") COMFYUIMINI_PORT = os.environ.get("COMFYUIMINI_PORT", "3000") +# Internal URL of the ComfyUI container (reachable over the docker network), +# used to proxy the "free memory / unload models" action. +COMFYUI_URL = os.environ.get("COMFYUI_URL", "http://comfyui:8188") + # Image file types shown in the gallery. IMAGE_EXTS = {".png", ".jpg", ".jpeg", ".webp", ".gif", ".bmp"} diff --git a/model-manager/app/main.py b/model-manager/app/main.py index 7a51e05..b7388c0 100644 --- a/model-manager/app/main.py +++ b/model-manager/app/main.py @@ -15,6 +15,7 @@ from pydantic import BaseModel from . import db, downloader, registries from .config import ( COMFYUI_PORT, + COMFYUI_URL, COMFYUIMINI_PORT, IMAGE_EXTS, KEY_BY_FOLDER, @@ -314,6 +315,20 @@ def ui_config() -> dict: return {"comfyui_port": COMFYUI_PORT, "comfyuimini_port": COMFYUIMINI_PORT} +@app.post("/api/comfyui/free") +async def comfyui_free() -> dict: + """Ask ComfyUI to unload all models and free GPU/RAM (proxies POST /free).""" + try: + async with httpx.AsyncClient(timeout=30) as client: + resp = await client.post( + f"{COMFYUI_URL}/free", + json={"unload_models": True, "free_memory": True}) + resp.raise_for_status() + except Exception as exc: # noqa: BLE001 - report to UI + raise HTTPException(502, f"Could not reach ComfyUI: {exc}") + return {"ok": True} + + @app.get("/start") def start() -> FileResponse: return FileResponse(STATIC_DIR / "start.html") diff --git a/model-manager/app/static/app.js b/model-manager/app/static/app.js index eb209ed..32291e8 100644 --- a/model-manager/app/static/app.js +++ b/model-manager/app/static/app.js @@ -663,6 +663,24 @@ $("#saveSettings").addEventListener("click", async () => { } }); +// ---- free GPU memory ------------------------------------------------------ + +$("#freeMemBtn").addEventListener("click", async () => { + const btn = $("#freeMemBtn"); + const msg = $("#freeMemMsg"); + btn.disabled = true; + msg.className = "form-msg"; msg.textContent = "Unloading…"; + try { + await api("/api/comfyui/free", { method: "POST" }); + msg.className = "form-msg ok"; msg.textContent = "Models unloaded."; + } catch (err) { + msg.className = "form-msg err"; msg.textContent = err.message; + } finally { + btn.disabled = false; + setTimeout(() => { msg.textContent = ""; }, 4000); + } +}); + // ---- boot ----------------------------------------------------------------- (async function init() { diff --git a/model-manager/app/static/index.html b/model-manager/app/static/index.html index 23a5413..3acea13 100644 --- a/model-manager/app/static/index.html +++ b/model-manager/app/static/index.html @@ -33,7 +33,12 @@ Settings - +
diff --git a/model-manager/app/static/style.css b/model-manager/app/static/style.css index c3882e6..f624945 100644 --- a/model-manager/app/static/style.css +++ b/model-manager/app/static/style.css @@ -58,7 +58,10 @@ nav { display: flex; flex-direction: column; gap: 4px; } .sidebar-foot { margin-top: auto; color: var(--text-dim); font-size: 11px; word-break: break-all; padding: 8px 6px 0; + display: flex; flex-direction: column; gap: 6px; } +.sidebar-foot .btn { width: 100%; font-size: 13px; } +.sidebar-foot .form-msg { font-size: 11px; text-align: center; } /* ---- content ---- */ .content { flex: 1; overflow-y: auto; padding: 26px 32px; }