Multiple improvements and hardening across the Power LoRA stack: - adaln: stronger caching (file-version keys), device-safe grid/basis computation, normalized tensor hashing for basis cache, more robust table-to-table rebasing with residual checks and fallback, better detection of adaLN weight name variants. - apply: validate numeric strengths, reject unsupported passengers, handle loading errors, detect shape mismatches, track rejected/skipped/quantized merges, improved PDD handoff and reporting. - pdd: introduce movable PddBank/PddState, register banks as additional models, thread-local PDD context, strength-scheduled blending and safer interval clamping. - keymap/detect/gain/modality/nodes/branch: add support for varied LoRA key spellings, more validation, skip incomplete adapters, and small bugfixes. - server: surface partial calibration note. - pyproject/README/.gitignore: packaging, docs, and ignore cleanup; add Python >=3.10 requirement and publisher repo fix. - web UI: fix balance button behavior, safer auto-balance error handling and factor sanitization. - tests: add unit tests for adaln, modality and pdd. Overall: better robustness to varied LoRA formats, mixed devices, malformed inputs, and improved user-facing error/reporting.
94 lines
3.1 KiB
Python
94 lines
3.1 KiB
Python
"""HTTP route backing the node's auto-balance button.
|
|
|
|
The measurement has to happen server-side: it needs the LoRA files themselves,
|
|
which the browser has no access to. Results are cached in :mod:`.gain` on
|
|
(path, mtime, size), so re-pressing the button, or toggling a row, is free after
|
|
the first read of each file.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from . import gain
|
|
|
|
LOG = logging.getLogger("h3.powerlorastack")
|
|
|
|
ROUTE = "/h3_power_lora_stack/balance"
|
|
|
|
|
|
def measure_names(names):
|
|
"""``[lora_name]`` -> ``{lora_name: {rel, factor, layers, note}}``.
|
|
|
|
Imported lazily by the route so the module stays importable without a
|
|
running server (the calibration scripts use it directly).
|
|
"""
|
|
from .nodes import _resolve_lora
|
|
|
|
out = {}
|
|
seen: dict[str, str] = {}
|
|
for name in names:
|
|
if not name or name == "None" or name in out:
|
|
continue
|
|
path = _resolve_lora(name)
|
|
if path is None:
|
|
out[name] = {"rel": None, "factor": 1.0, "layers": 0,
|
|
"note": "not found"}
|
|
continue
|
|
info = gain.measure_file(path)
|
|
entry = {
|
|
"rel": info.get("rel"),
|
|
"factor": float(info.get("factor", 1.0)),
|
|
"layers": info.get("layers", 0),
|
|
"ranks": info.get("ranks", []),
|
|
"note": "",
|
|
}
|
|
if info.get("error"):
|
|
entry["note"] = "unreadable"
|
|
elif not info.get("rel"):
|
|
entry["note"] = "no measurable layers"
|
|
elif not info.get("complete", True):
|
|
entry["note"] = "calibration is partial for unsupported adapter tensors"
|
|
else:
|
|
fp = info.get("fingerprint")
|
|
first = seen.get(fp) if fp else None
|
|
if first is not None:
|
|
entry["note"] = f"same adapter as {first}"
|
|
elif fp:
|
|
seen[fp] = name
|
|
out[name] = entry
|
|
return out
|
|
|
|
|
|
def register():
|
|
"""Attach the route to ComfyUI's aiohttp app, if there is one."""
|
|
try:
|
|
from aiohttp import web
|
|
from server import PromptServer
|
|
except Exception: # running outside ComfyUI
|
|
return False
|
|
instance = getattr(PromptServer, "instance", None)
|
|
if instance is None or not hasattr(instance, "routes"):
|
|
return False
|
|
|
|
@instance.routes.post(ROUTE)
|
|
async def _balance(request):
|
|
try:
|
|
body = await request.json()
|
|
except Exception:
|
|
return web.json_response({"error": "bad request"}, status=400)
|
|
names = body.get("loras")
|
|
if not isinstance(names, list):
|
|
return web.json_response({"error": "expected {'loras': [...]}"}, status=400)
|
|
try:
|
|
results = measure_names([n for n in names if isinstance(n, str)])
|
|
except Exception as exc:
|
|
LOG.exception("H3 Power LoRA Stack: balance failed")
|
|
return web.json_response({"error": str(exc)}, status=500)
|
|
return web.json_response({
|
|
"results": results,
|
|
"reference": gain.REFERENCE_REL,
|
|
})
|
|
|
|
return True
|