Enhance GPU probing and performance tuning in provisioning
- Introduced GPU probing functionality to gather and store GPU specifications in `/mnt/swarm_data/.gpu-rent-gpu.json`, aiding in performance tuning. - Updated `install_ollama.sh` and `install_llamacpp.sh` to utilize GPU information for configuring optimal runtime parameters. - Enhanced `provision.py` to include GPU probing and performance tuning logic, ensuring better resource allocation for LLM operations. - Improved documentation in `decisions.md`, `llm.md`, and `swarmui.md` to reflect changes in GPU handling and performance tuning processes. - Added new tests to validate the GPU probing and model resolution logic, ensuring robustness in handling various GPU configurations.
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Probe NVIDIA GPU → /mnt/swarm_data/.gpu-rent-gpu.json (stdlib only)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
OUT = Path("/mnt/swarm_data/.gpu-rent-gpu.json")
|
||||
|
||||
|
||||
def tier_for_vram_mib(vram_mib: int) -> str:
|
||||
gib = vram_mib / 1024.0
|
||||
if gib < 16:
|
||||
return "low"
|
||||
if gib < 24:
|
||||
return "mid"
|
||||
if gib < 48:
|
||||
return "high"
|
||||
return "ultra"
|
||||
|
||||
|
||||
def run(argv: list[str]) -> str:
|
||||
try:
|
||||
return subprocess.check_output(argv, text=True, stderr=subprocess.DEVNULL).strip()
|
||||
except (OSError, subprocess.CalledProcessError):
|
||||
return ""
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if not run(["bash", "-lc", "command -v nvidia-smi"]):
|
||||
data = {
|
||||
"ok": False,
|
||||
"error": "nvidia-smi missing",
|
||||
"name": "",
|
||||
"vram_mib": 0,
|
||||
"compute_cap": "0.0",
|
||||
"uuid": "",
|
||||
"tier": "low",
|
||||
}
|
||||
OUT.parent.mkdir(parents=True, exist_ok=True)
|
||||
OUT.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
||||
print(json.dumps(data))
|
||||
return 0
|
||||
|
||||
# name, uuid, memory.total [MiB], compute_cap
|
||||
q = run(
|
||||
[
|
||||
"nvidia-smi",
|
||||
"--query-gpu=name,uuid,memory.total,compute_cap",
|
||||
"--format=csv,noheader,nounits",
|
||||
]
|
||||
)
|
||||
if not q:
|
||||
data = {
|
||||
"ok": False,
|
||||
"error": "nvidia-smi query failed",
|
||||
"name": "",
|
||||
"vram_mib": 0,
|
||||
"compute_cap": "0.0",
|
||||
"uuid": "",
|
||||
"tier": "low",
|
||||
}
|
||||
OUT.parent.mkdir(parents=True, exist_ok=True)
|
||||
OUT.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
||||
print(json.dumps(data))
|
||||
return 0
|
||||
|
||||
line = q.splitlines()[0]
|
||||
# CSV: "NVIDIA A100-SXM4-40GB, GPU-..., 40960, 8.0"
|
||||
parts = [p.strip() for p in line.split(",")]
|
||||
name = parts[0] if parts else "unknown"
|
||||
uuid = parts[1] if len(parts) > 1 else ""
|
||||
vram_raw = parts[2] if len(parts) > 2 else "0"
|
||||
cap = parts[3] if len(parts) > 3 else "0.0"
|
||||
m = re.search(r"(\d+)", vram_raw.replace(" ", ""))
|
||||
vram_mib = int(m.group(1)) if m else 0
|
||||
tier = tier_for_vram_mib(vram_mib)
|
||||
data = {
|
||||
"ok": True,
|
||||
"name": name,
|
||||
"vram_mib": vram_mib,
|
||||
"compute_cap": cap,
|
||||
"uuid": uuid,
|
||||
"tier": tier,
|
||||
}
|
||||
OUT.parent.mkdir(parents=True, exist_ok=True)
|
||||
OUT.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
||||
print(json.dumps(data))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -110,9 +110,32 @@ else
|
||||
log "нет GGUF в ${MODELS_DIR} — положи файл вручную и systemctl restart ${UNIT}"
|
||||
fi
|
||||
|
||||
# GPU layers: share card with Swarm — full offload on mid+, leave headroom on low.
|
||||
NGL=99
|
||||
CTX=8192
|
||||
if [[ -f "${DATA_ROOT}/.gpu-rent-gpu.json" ]]; then
|
||||
eval "$(python3 - <<'PY'
|
||||
import json
|
||||
from pathlib import Path
|
||||
gpu=json.loads(Path("/mnt/swarm_data/.gpu-rent-gpu.json").read_text())
|
||||
vram=int(gpu.get("vram_mib") or 0)
|
||||
gib=vram/1024.0
|
||||
if gib < 16:
|
||||
print("NGL=40"); print("CTX=4096")
|
||||
elif gib < 24:
|
||||
print("NGL=99"); print("CTX=8192")
|
||||
elif gib < 48:
|
||||
print("NGL=99"); print("CTX=16384")
|
||||
else:
|
||||
print("NGL=99"); print("CTX=32768")
|
||||
PY
|
||||
)" || true
|
||||
fi
|
||||
log "llama.cpp -ngl ${NGL} -c ${CTX}"
|
||||
|
||||
cat >/etc/systemd/system/${UNIT}.service <<EOF
|
||||
[Unit]
|
||||
Description=gpu-rent llama.cpp server (loopback)
|
||||
Description=gpu-rent llama.cpp server (loopback, GPU-tuned)
|
||||
After=network-online.target local-fs.target
|
||||
Wants=network-online.target
|
||||
|
||||
@@ -121,7 +144,7 @@ Type=simple
|
||||
User=${SWARM_USER}
|
||||
Group=${SWARM_USER}
|
||||
WorkingDirectory=${LLAMA_ROOT}
|
||||
ExecStart=${SERVER_BIN} ${MODEL_ARG} --host 127.0.0.1 --port 8080
|
||||
ExecStart=${SERVER_BIN} ${MODEL_ARG} --host 127.0.0.1 --port 8080 -ngl ${NGL} -c ${CTX}
|
||||
Restart=on-failure
|
||||
RestartSec=8
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install Ollama on the VM (idempotent). Models on data volume.
|
||||
# GPU-aware systemd env: flash attention, keep-alive, VRAM overhead for SwarmUI.
|
||||
set -euo pipefail
|
||||
|
||||
SWARM_USER="${SWARM_USER:-ubuntu}"
|
||||
DATA_ROOT="/mnt/swarm_data"
|
||||
OLLAMA_HOME="${DATA_ROOT}/ollama"
|
||||
UNIT="gpu-rent-ollama"
|
||||
GPU_JSON="${DATA_ROOT}/.gpu-rent-gpu.json"
|
||||
OLLAMA_ENV_FILE="${DATA_ROOT}/.gpu-rent-ollama.env"
|
||||
|
||||
log() { echo "[gpu-rent-ollama] $*"; }
|
||||
|
||||
@@ -17,6 +20,84 @@ fi
|
||||
mkdir -p "$OLLAMA_HOME"
|
||||
chown -R "${SWARM_USER}:${SWARM_USER}" "$OLLAMA_HOME"
|
||||
|
||||
# --- GPU probe → tier env (share card with SwarmUI / Krea 2) ---
|
||||
write_ollama_env() {
|
||||
python3 - <<'PY'
|
||||
import json, os
|
||||
from pathlib import Path
|
||||
|
||||
gpu_path = Path("/mnt/swarm_data/.gpu-rent-gpu.json")
|
||||
out = Path("/mnt/swarm_data/.gpu-rent-ollama.env")
|
||||
gpu = {}
|
||||
if gpu_path.is_file():
|
||||
try:
|
||||
gpu = json.loads(gpu_path.read_text())
|
||||
except Exception:
|
||||
gpu = {}
|
||||
vram = int(gpu.get("vram_mib") or 0)
|
||||
gib = vram / 1024.0
|
||||
cap = str(gpu.get("compute_cap") or "0.0")
|
||||
name = str(gpu.get("name") or "")
|
||||
try:
|
||||
parts = cap.split(".")
|
||||
maj, mnr = int(parts[0]), int(parts[1]) if len(parts) > 1 else 0
|
||||
ampere = (maj, mnr) >= (8, 0)
|
||||
except ValueError:
|
||||
ampere = False
|
||||
if gib >= 48:
|
||||
tier, overhead, keep, kv, flash = "ultra", 20 * 1024**3, "30m", "q8_0", True
|
||||
elif gib >= 24:
|
||||
tier, overhead, keep, kv, flash = "high", 14 * 1024**3, "15m", "q8_0", True
|
||||
elif gib >= 16:
|
||||
tier, overhead, keep, kv, flash = "mid", 10 * 1024**3, "5m", "q8_0", True
|
||||
else:
|
||||
tier, overhead, keep, kv, flash = "low", 6 * 1024**3, "2m", "q4_0", False
|
||||
flash = bool(flash and (ampere or "A100" in name.upper() or "H100" in name.upper() or gib >= 16))
|
||||
lines = [
|
||||
f"# auto gpu-rent ollama tune tier={tier} gpu={name!r} vram_mib={vram}",
|
||||
"OLLAMA_NUM_PARALLEL=1",
|
||||
"OLLAMA_MAX_LOADED_MODELS=1",
|
||||
f"OLLAMA_KEEP_ALIVE={keep}",
|
||||
f"OLLAMA_GPU_OVERHEAD={overhead}",
|
||||
]
|
||||
if flash:
|
||||
lines.append("OLLAMA_FLASH_ATTENTION=1")
|
||||
if kv:
|
||||
lines.append(f"OLLAMA_KV_CACHE_TYPE={kv}")
|
||||
out.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
print(f"tier={tier} flash={int(flash)} keep={keep} overhead_gib={overhead/1024**3:.0f}")
|
||||
PY
|
||||
}
|
||||
|
||||
# Prefer probe file written by gpu_probe.py; if missing, try nvidia-smi quickly.
|
||||
if [[ ! -f "$GPU_JSON" ]] && command -v nvidia-smi >/dev/null 2>&1; then
|
||||
log "нет ${GPU_JSON} — быстрый probe"
|
||||
python3 - <<'PY' || true
|
||||
import json, re, subprocess
|
||||
from pathlib import Path
|
||||
out = Path("/mnt/swarm_data/.gpu-rent-gpu.json")
|
||||
try:
|
||||
q = subprocess.check_output(
|
||||
["nvidia-smi", "--query-gpu=name,uuid,memory.total,compute_cap", "--format=csv,noheader,nounits"],
|
||||
text=True, stderr=subprocess.DEVNULL,
|
||||
).strip().splitlines()[0]
|
||||
parts = [p.strip() for p in q.split(",")]
|
||||
vram = int(re.search(r"(\d+)", parts[2]).group(1))
|
||||
gib = vram / 1024.0
|
||||
tier = "low" if gib < 16 else "mid" if gib < 24 else "high" if gib < 48 else "ultra"
|
||||
data = {"ok": True, "name": parts[0], "uuid": parts[1], "vram_mib": vram, "compute_cap": parts[3], "tier": tier}
|
||||
except Exception as e:
|
||||
data = {"ok": False, "error": str(e), "name": "", "uuid": "", "vram_mib": 0, "compute_cap": "0.0", "tier": "low"}
|
||||
out.parent.mkdir(parents=True, exist_ok=True)
|
||||
out.write_text(json.dumps(data, indent=2) + "\n")
|
||||
print(data)
|
||||
PY
|
||||
fi
|
||||
|
||||
write_ollama_env
|
||||
log "ollama env: $(tr '\n' ' ' < "$OLLAMA_ENV_FILE")"
|
||||
chown "${SWARM_USER}:${SWARM_USER}" "$OLLAMA_ENV_FILE" "$GPU_JSON" 2>/dev/null || true
|
||||
|
||||
if ! command -v ollama >/dev/null 2>&1; then
|
||||
# Supply-chain: prefer a pinned GitHub release. Official install.sh is curl|sh without checksum.
|
||||
# Override: OLLAMA_VERSION=0.6.5 OLLAMA_SHA256=<sha256 of ollama-linux-amd64.tgz>
|
||||
@@ -55,9 +136,16 @@ fi
|
||||
systemctl stop ollama 2>/dev/null || true
|
||||
systemctl disable ollama 2>/dev/null || true
|
||||
|
||||
# Build Environment= lines from env file
|
||||
ENV_LINES=""
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
[[ -z "$line" || "$line" =~ ^# ]] && continue
|
||||
ENV_LINES+="Environment=${line}"$'\n'
|
||||
done < "$OLLAMA_ENV_FILE"
|
||||
|
||||
cat >/etc/systemd/system/${UNIT}.service <<EOF
|
||||
[Unit]
|
||||
Description=gpu-rent Ollama (loopback)
|
||||
Description=gpu-rent Ollama (loopback, GPU-tuned)
|
||||
After=network-online.target local-fs.target
|
||||
Wants=network-online.target
|
||||
|
||||
@@ -68,7 +156,7 @@ Group=${SWARM_USER}
|
||||
Environment=HOME=/home/${SWARM_USER}
|
||||
Environment=OLLAMA_HOST=127.0.0.1:11434
|
||||
Environment=OLLAMA_MODELS=${OLLAMA_HOME}
|
||||
ExecStart=$(command -v ollama) serve
|
||||
${ENV_LINES}ExecStart=$(command -v ollama) serve
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
@@ -81,4 +169,4 @@ systemctl enable "$UNIT"
|
||||
systemctl restart "$UNIT"
|
||||
sleep 2
|
||||
systemctl is-active "$UNIT" >/dev/null
|
||||
log "ok — OLLAMA_HOST=127.0.0.1:11434 models=${OLLAMA_HOME}"
|
||||
log "ok — OLLAMA_HOST=127.0.0.1:11434 models=${OLLAMA_HOME} (GPU-tuned)"
|
||||
|
||||
@@ -13,7 +13,8 @@ DATA = Path("/mnt/swarm_data")
|
||||
MODELS = DATA / "Models"
|
||||
OUT = Path("/tmp/gpu-rent-inventory.json")
|
||||
|
||||
WEIGHT_SUFFIXES = {".safetensors", ".ckpt", ".pt", ".pth", ".bin", ".gguf", ".sft", ".onnx"}
|
||||
WEIGHT_SUFFIXES = {".safetensors", ".ckpt", ".gguf", ".sft", ".onnx"}
|
||||
# .pt/.pth/.bin намеренно вне capture — слишком много ложных «моделей» (torch scripts).
|
||||
|
||||
# SwarmUI folder name → models.yaml kind
|
||||
FOLDER_TO_KIND = {
|
||||
@@ -51,19 +52,13 @@ def sha256_file(path: Path, chunk: int = 1024 * 1024) -> str:
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def read_sidecar_ids(weight: Path) -> tuple[int | None, int | None]:
|
||||
"""Return (version_id, model_id) from {stem}.civitai.json if present."""
|
||||
sidecar = weight.parent / f"{weight.stem}.civitai.json"
|
||||
if not sidecar.is_file():
|
||||
return None, None
|
||||
try:
|
||||
data = json.loads(sidecar.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError):
|
||||
return None, None
|
||||
if not isinstance(data, dict):
|
||||
return None, None
|
||||
def _ids_from_dict(data: dict) -> tuple[int | None, int | None]:
|
||||
vid = data.get("id")
|
||||
if vid is None:
|
||||
vid = data.get("modelVersionId") or data.get("versionId")
|
||||
mid = data.get("modelId")
|
||||
if mid is None and isinstance(data.get("model"), dict):
|
||||
mid = data["model"].get("id")
|
||||
try:
|
||||
version_id = int(vid) if vid is not None else None
|
||||
except (TypeError, ValueError):
|
||||
@@ -75,6 +70,28 @@ def read_sidecar_ids(weight: Path) -> tuple[int | None, int | None]:
|
||||
return version_id, model_id
|
||||
|
||||
|
||||
def read_sidecar_ids(weight: Path) -> tuple[int | None, int | None]:
|
||||
"""Return (version_id, model_id) from civitai/swarm sidecars if present."""
|
||||
candidates = [
|
||||
weight.parent / f"{weight.stem}.civitai.json",
|
||||
weight.parent / f"{weight.stem}.swarm.json",
|
||||
weight.parent / f"{weight.stem}.json",
|
||||
]
|
||||
for sidecar in candidates:
|
||||
if not sidecar.is_file():
|
||||
continue
|
||||
try:
|
||||
data = json.loads(sidecar.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError):
|
||||
continue
|
||||
if not isinstance(data, dict):
|
||||
continue
|
||||
version_id, model_id = _ids_from_dict(data)
|
||||
if version_id is not None or model_id is not None:
|
||||
return version_id, model_id
|
||||
return None, None
|
||||
|
||||
|
||||
def scan_models() -> list[dict]:
|
||||
items: list[dict] = []
|
||||
if not MODELS.is_dir():
|
||||
@@ -92,6 +109,20 @@ def scan_models() -> list[dict]:
|
||||
continue
|
||||
rel = path.relative_to(MODELS).as_posix()
|
||||
version_id, model_id = read_sidecar_ids(path)
|
||||
# Full sidecar → skip expensive SHA256 (capture resolves URL locally).
|
||||
if version_id is not None and model_id is not None:
|
||||
items.append(
|
||||
{
|
||||
"kind": kind,
|
||||
"rel": rel,
|
||||
"name": path.name,
|
||||
"sha256": None,
|
||||
"version_id": version_id,
|
||||
"model_id": model_id,
|
||||
"sha_skipped": True,
|
||||
}
|
||||
)
|
||||
continue
|
||||
try:
|
||||
digest = sha256_file(path)
|
||||
except OSError as exc:
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env python3
|
||||
"""First-boot Swarm/Comfy performance tune: sageattention ExtraArgs + pip libs.
|
||||
|
||||
Idempotent. Marker: /mnt/swarm_data/.gpu-rent-perf-tuned
|
||||
Re-runs if GPU uuid changed.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
DATA = Path("/mnt/swarm_data")
|
||||
GPU_JSON = DATA / ".gpu-rent-gpu.json"
|
||||
MARKER = DATA / ".gpu-rent-perf-tuned"
|
||||
BACKENDS = DATA / "Data" / "Backends.fds"
|
||||
COMFY_VENV_CANDIDATES = [
|
||||
DATA / "dlbackend" / "comfy" / "venv" / "bin" / "pip",
|
||||
DATA / "dlbackend" / "comfy" / "ComfyUI" / "venv" / "bin" / "pip",
|
||||
Path("/opt/swarmui/dlbackend/comfy/venv/bin/pip"),
|
||||
Path("/opt/swarmui/dlbackend/comfy/ComfyUI/venv/bin/pip"),
|
||||
]
|
||||
|
||||
|
||||
def load_gpu() -> dict:
|
||||
if not GPU_JSON.is_file():
|
||||
return {}
|
||||
try:
|
||||
return json.loads(GPU_JSON.read_text(encoding="utf-8"))
|
||||
except json.JSONDecodeError:
|
||||
return {}
|
||||
|
||||
|
||||
def tier_notes(gpu: dict) -> dict:
|
||||
"""Mirror gpu_rent.perf_tiers.swarm_tune_for (stdlib-only on VM)."""
|
||||
vram = int(gpu.get("vram_mib") or 0)
|
||||
gib = vram / 1024.0
|
||||
if gib < 16:
|
||||
tier = "low"
|
||||
elif gib < 24:
|
||||
tier = "mid"
|
||||
elif gib < 48:
|
||||
tier = "high"
|
||||
else:
|
||||
tier = "ultra"
|
||||
cap = str(gpu.get("compute_cap") or "0.0")
|
||||
try:
|
||||
parts = cap.split(".")
|
||||
maj, mnr = int(parts[0]), int(parts[1]) if len(parts) > 1 else 0
|
||||
ampere = (maj, mnr) >= (8, 0)
|
||||
except ValueError:
|
||||
ampere = False
|
||||
use_sage = ampere and tier in {"mid", "high", "ultra"}
|
||||
return {
|
||||
"tier": tier,
|
||||
"use_sage": use_sage,
|
||||
"extra_args": "--use-sage-attention" if use_sage else "",
|
||||
"uuid": str(gpu.get("uuid") or ""),
|
||||
"name": str(gpu.get("name") or ""),
|
||||
}
|
||||
|
||||
|
||||
def find_pip() -> Path | None:
|
||||
for p in COMFY_VENV_CANDIDATES:
|
||||
if p.is_file():
|
||||
return p
|
||||
# glob
|
||||
for p in DATA.glob("dlbackend/**/venv/bin/pip"):
|
||||
return p
|
||||
return None
|
||||
|
||||
|
||||
def patch_backends_extra_args(extra: str) -> bool:
|
||||
"""Ensure ExtraArgs contains sage flag for Comfy self-start backends."""
|
||||
if not extra:
|
||||
return False
|
||||
if not BACKENDS.is_file():
|
||||
print(f"no {BACKENDS} yet — skip ExtraArgs (Comfy not registered)")
|
||||
return False
|
||||
text = BACKENDS.read_text(encoding="utf-8")
|
||||
if "--use-sage-attention" in text:
|
||||
print("Backends.fds already has --use-sage-attention")
|
||||
return False
|
||||
lines = text.splitlines()
|
||||
changed = False
|
||||
out = []
|
||||
for line in lines:
|
||||
if re.match(r"^(\s*)ExtraArgs:\s*$", line) or re.match(r"^(\s*)ExtraArgs:\s*\"\"\s*$", line):
|
||||
indent = re.match(r"^(\s*)", line).group(1)
|
||||
out.append(f"{indent}ExtraArgs: {extra}")
|
||||
changed = True
|
||||
elif re.match(r"^(\s*)ExtraArgs:\s+", line) and "--use-sage-attention" not in line:
|
||||
out.append(line.rstrip() + f" {extra}")
|
||||
changed = True
|
||||
else:
|
||||
out.append(line)
|
||||
if not changed:
|
||||
print("Backends.fds: no ExtraArgs field patched")
|
||||
return False
|
||||
BACKENDS.write_text("\n".join(out) + "\n", encoding="utf-8")
|
||||
print(f"patched {BACKENDS} ExtraArgs += {extra}")
|
||||
return True
|
||||
|
||||
|
||||
def pip_install_sage(pip: Path) -> None:
|
||||
print(f"pip install triton sageattention via {pip}")
|
||||
env = dict(os.environ)
|
||||
env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
|
||||
# Best-effort: do not fail whole tune if wheels missing for this torch.
|
||||
cmd = [str(pip), "install", "-U", "triton", "sageattention"]
|
||||
try:
|
||||
subprocess.check_call(cmd, env=env)
|
||||
print("triton + sageattention installed")
|
||||
except subprocess.CalledProcessError as exc:
|
||||
print(f"WARN: pip install failed ({exc}) — ExtraArgs may no-op until fixed")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
gpu = load_gpu()
|
||||
plan = tier_notes(gpu)
|
||||
prev = {}
|
||||
if MARKER.is_file():
|
||||
try:
|
||||
prev = json.loads(MARKER.read_text(encoding="utf-8"))
|
||||
except json.JSONDecodeError:
|
||||
prev = {}
|
||||
if prev.get("uuid") and prev.get("uuid") == plan["uuid"] and prev.get("extra_args") == plan["extra_args"]:
|
||||
if prev.get("pip_ok") or not plan["use_sage"]:
|
||||
print(f"perf tune already applied for {plan['name']} ({plan['tier']})")
|
||||
return 0
|
||||
|
||||
print(f"perf tune: {plan['name']} tier={plan['tier']} sage={plan['use_sage']}")
|
||||
pip_ok = False
|
||||
restarted_needed = False
|
||||
if plan["use_sage"]:
|
||||
pip = find_pip()
|
||||
if pip:
|
||||
pip_install_sage(pip)
|
||||
pip_ok = True
|
||||
else:
|
||||
print("Comfy venv pip not found yet — will retry next up")
|
||||
if patch_backends_extra_args(plan["extra_args"]):
|
||||
restarted_needed = True
|
||||
|
||||
marker = {
|
||||
"uuid": plan["uuid"],
|
||||
"name": plan["name"],
|
||||
"tier": plan["tier"],
|
||||
"extra_args": plan["extra_args"],
|
||||
"pip_ok": pip_ok or not plan["use_sage"],
|
||||
"restart_needed": restarted_needed,
|
||||
}
|
||||
MARKER.write_text(json.dumps(marker, indent=2) + "\n", encoding="utf-8")
|
||||
print("wrote", MARKER)
|
||||
if restarted_needed:
|
||||
print("RESTART_SWARMUI=1")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user