Add Hugging Face support and enhance model resolution logic

- Introduced support for Hugging Face API integration, allowing fallback model resolution when Civitai fails.
- Updated configuration to include `HF_TOKEN` and `HF_TOKEN_PATH` for authentication.
- Enhanced model capture logic to differentiate between Civitai and Hugging Face sources.
- Improved error handling for model downloads, providing clearer messages for authentication issues.
- Updated documentation to reflect new environment variables and usage instructions for Hugging Face integration.
- Added tests to validate the new fallback mechanism and ensure robust model resolution.
This commit is contained in:
Leonid Pershin
2026-08-21 07:34:15 +03:00
parent 7343fb0e83
commit 618e6e4806
12 changed files with 642 additions and 96 deletions
+75 -25
View File
@@ -234,16 +234,10 @@ def seed_swarmui_api_keys(cfg: Config, host: str, log: Log) -> None:
Swarm stores them in Users.ldb GenericData — needed for Model Downloader in the UI.
Call after SwarmUI HTTP is up (after wait_backend / verify).
"""
import os
keys: dict[str, str] = {}
if cfg.civitai_api_token:
keys["civitai_api"] = cfg.civitai_api_token
hf = (
os.environ.get("HF_TOKEN")
or os.environ.get("HUGGING_FACE_HUB_TOKEN")
or ""
).strip()
hf = (cfg.hf_token or "").strip()
if hf:
keys["huggingface_api"] = hf
if not keys:
@@ -273,21 +267,52 @@ def seed_swarmui_api_keys(cfg: Config, host: str, log: Log) -> None:
def seed_civitai(cfg: Config, host: str, log: Log) -> None:
from gpu_rent.huggingface import is_huggingface_url
entries = parse_models(cfg.models_manifest)
if not cfg.civitai_api_token:
log("Civitai-seed пропущен: нет CIVITAI_API_TOKEN — дефолт SwarmUI")
return
if not entries:
log("Civitai-seed пропущен: манифест пуст — дефолт SwarmUI")
log("model-seed пропущен: манифест пуст — дефолт SwarmUI")
return
jobs = []
jobs: list[dict] = []
for entry in entries:
url = (entry.url or "").strip()
if url and is_huggingface_url(url):
name = url.rstrip("/").rsplit("/", 1)[-1].split("?", 1)[0] or "model.safetensors"
folder = MODEL_DIRS.get(entry.kind, entry.kind)
dest = f"{DATA}/Models/{folder}/{name}"
stem = Path(name).stem
swarm = {
"name": stem,
"title": stem,
"description": f"Hugging Face: {url}",
"trigger_phrase": "",
"author": "",
"tags": ["huggingface"],
}
jobs.append(
{
"dest": dest,
"url": url,
"sha256": "",
"auth": "hf",
"sidecars": {
f"{stem}.swarm.json": json.dumps(swarm, ensure_ascii=False, indent=2),
},
}
)
continue
if not cfg.civitai_api_token:
continue
vid = entry.version_id or (extract_version_id(entry.url) if entry.url else None)
if not vid:
log(f"пропуск {entry.kind}: нет version_id")
log(f"пропуск {entry.kind}: нет version_id / HF url")
continue
try:
api_host, version = fetch_model_version(cfg.civitai_api_token, cfg.civitai_api_host, vid)
api_host, version = fetch_model_version(
cfg.civitai_api_token, cfg.civitai_api_host, vid
)
except CloudError as exc:
log(str(exc))
continue
@@ -309,7 +334,11 @@ def seed_civitai(cfg: Config, host: str, log: Log) -> None:
"title": version.get("name") or stem,
"description": (version.get("description") or "")[:2000],
"trigger_phrase": phrase,
"author": ((version.get("model") or {}) if isinstance(version.get("model"), dict) else {}).get("name"),
"author": (
((version.get("model") or {}) if isinstance(version.get("model"), dict) else {}).get(
"name"
)
),
"tags": version.get("tags") or [],
}
jobs.append(
@@ -317,22 +346,43 @@ def seed_civitai(cfg: Config, host: str, log: Log) -> None:
"dest": dest,
"url": _download_url(api_host, vid, info),
"sha256": sha,
"auth": "civitai",
"sidecars": {
f"{stem}.civitai.json": civitai_json,
f"{stem}.swarm.json": json.dumps(swarm, ensure_ascii=False, indent=2),
},
}
)
if not jobs:
log("Civitai-seed: ни одной скачиваемой строки")
if not cfg.civitai_api_token:
log("Civitai-seed пропущен: нет CIVITAI_API_TOKEN — дефолт SwarmUI")
else:
log("Civitai-seed: ни одной скачиваемой строки")
return
hf_n = sum(1 for j in jobs if j.get("auth") == "hf")
civ_n = len(jobs) - hf_n
if hf_n and not cfg.hf_token:
log(
"⚠ в манифесте есть Hugging Face URL, но нет HF_TOKEN — "
"gated/abliterated файлы дадут 401. Токен: huggingface.co/settings/tokens"
)
if civ_n and not cfg.civitai_api_token:
log("⚠ Civitai-строки пропущены: нет CIVITAI_API_TOKEN")
if not any(e.kind == "checkpoint" for e in entries):
log("в манифесте нет checkpoint — генерация может не стартовать")
put_text(cfg, host, "/tmp/gpu-rent-civitai-jobs.json", json.dumps(jobs, indent=2))
put_text(cfg, host, "/tmp/gpu-rent-civitai.token", cfg.civitai_api_token + "\n", mode=0o600)
if cfg.civitai_api_token:
put_text(
cfg, host, "/tmp/gpu-rent-civitai.token", cfg.civitai_api_token + "\n", mode=0o600
)
if cfg.hf_token:
put_text(cfg, host, "/tmp/gpu-rent-hf.token", cfg.hf_token + "\n", mode=0o600)
log(
f"Civitai: {len(jobs)} в манифесте — на VM качаю отсутствующие "
f"(уже есть + sha → skip; прогресс [N/{len(jobs)}])"
f"model-seed: {len(jobs)} файл(ов) "
f"(civitai={civ_n}, huggingface={hf_n}) — прогресс [N/{len(jobs)}]"
)
run_python(
cfg,
@@ -419,7 +469,6 @@ def provision_llm(cfg: Config, host: str, log: Log) -> None:
gguf_filename_from_url,
parse_llamacpp_models,
)
import os
entries = parse_llamacpp_models(cfg.llamacpp_models_manifest)
defaults = [e for e in entries if e.default]
@@ -439,13 +488,14 @@ def provision_llm(cfg: Config, host: str, log: Log) -> None:
put_text(
cfg, host, "/tmp/gpu-rent-llamacpp-models.json", json.dumps(jobs, indent=2)
)
hf = (
os.environ.get("HF_TOKEN")
or os.environ.get("HUGGING_FACE_HUB_TOKEN")
or ""
).strip()
hf = (cfg.hf_token or "").strip()
if hf:
put_text(cfg, host, "/tmp/gpu-rent-hf.token", hf + "\n", mode=0o600)
else:
log(
"⚠ HF_TOKEN не задан — gated GGUF (abliterated и др.) часто дают 401. "
"Добавь HF_TOKEN=hf_… в .env → https://huggingface.co/settings/tokens"
)
log(f"llama.cpp: скачиваю {len(jobs)} GGUF из манифеста")
run_python(
cfg,