"""Hugging Face Space entry point (Gradio SDK on ZeroGPU hardware). ZeroGPU rules followed here: ``spaces`` is imported before torch, the NLI cross-encoder is placed on CUDA at module level (ZeroGPU emulates CUDA outside GPU functions), and only the NLI scoring call runs inside ``@spaces.GPU``. Off ZeroGPU the decorator does nothing and everything runs on CPU, so the same file works locally: uv run --extra demo --extra nli python space/app.py """ from __future__ import annotations import os import sys from pathlib import Path import spaces # must be imported before torch on ZeroGPU HERE = Path(__file__).resolve().parent # In the Space, src/ sits next to this file; in the repository it is one level up. ROOT = HERE if (HERE / "src").is_dir() else HERE.parent sys.path.insert(0, str(ROOT / "src")) os.chdir(ROOT) # configs/default.yaml uses paths relative to the project root import torch from sentence_transformers import CrossEncoder from graphtrace.app.gradio_app import CSS, DemoData, build_demo, make_theme from graphtrace.config import load_project from graphtrace.verification import NLIVerifier cfg = load_project("configs/default.yaml") MODEL = cfg.extraction.nli_model DEVICE = "cuda" if torch.cuda.is_available() else "cpu" nli = NLIVerifier(MODEL, encoder=CrossEncoder(MODEL, device=DEVICE)) _cpu_nli: NLIVerifier | None = nli if DEVICE == "cpu" else None @spaces.GPU(duration=20) def gpu_verify(claim: str, texts: list[str]): return nli.verify(claim, texts) def cpu_verify(claim: str, texts: list[str]): """Fallback when the GPU is unavailable (quota reached, allocation failed).""" global _cpu_nli if _cpu_nli is None: _cpu_nli = NLIVerifier(MODEL, encoder=CrossEncoder(MODEL, device="cpu")) return _cpu_nli.verify(claim, texts) demo = build_demo(DemoData.load(cfg), nli, verify=gpu_verify, fallback=None if DEVICE == "cpu" else cpu_verify) demo.queue(default_concurrency_limit=4, max_size=32) if __name__ == "__main__": demo.launch(theme=make_theme(), css=CSS, server_port=int(os.environ.get("PORT", "7860")))