Spaces:
Runtime error
Runtime error
Connect HF FLUX runtime
Browse files- app.py +5 -2
- requirements.txt +4 -0
- src/nexus_visual_weaver/hf_runtime.py +126 -0
- src/nexus_visual_weaver/render.py +22 -4
- src/nexus_visual_weaver/styles.py +9 -0
- tests/test_hf_runtime.py +36 -0
app.py
CHANGED
|
@@ -20,6 +20,7 @@ except Exception: # pragma: no cover - local development does not require Space
|
|
| 20 |
spaces = None
|
| 21 |
|
| 22 |
from nexus_visual_weaver.catalog import catalog_summary
|
|
|
|
| 23 |
from nexus_visual_weaver.model_relay import WeaverModelRelay
|
| 24 |
from nexus_visual_weaver.planner import build_command_center_run
|
| 25 |
from nexus_visual_weaver.render import render_catalog_table, render_command_header, render_dashboard_regions
|
|
@@ -129,11 +130,13 @@ def run_weave(
|
|
| 129 |
adult_mode=adult_mode,
|
| 130 |
)
|
| 131 |
scan = scan_file(_file_path(upload))
|
|
|
|
| 132 |
operator_state = {
|
| 133 |
-
"provider_state": "checkpointed",
|
| 134 |
"checkpoint": "pending_review",
|
| 135 |
"export": scan.get("export_gate", "pending"),
|
| 136 |
-
"message": "Run packet generated. Human checkpoint required before provider promotion or export.",
|
|
|
|
| 137 |
}
|
| 138 |
regions = _dashboard_regions(
|
| 139 |
run=run,
|
|
|
|
| 20 |
spaces = None
|
| 21 |
|
| 22 |
from nexus_visual_weaver.catalog import catalog_summary
|
| 23 |
+
from nexus_visual_weaver.hf_runtime import generate_flux_image
|
| 24 |
from nexus_visual_weaver.model_relay import WeaverModelRelay
|
| 25 |
from nexus_visual_weaver.planner import build_command_center_run
|
| 26 |
from nexus_visual_weaver.render import render_catalog_table, render_command_header, render_dashboard_regions
|
|
|
|
| 130 |
adult_mode=adult_mode,
|
| 131 |
)
|
| 132 |
scan = scan_file(_file_path(upload))
|
| 133 |
+
generation = generate_flux_image(run.refined_prompt.refined, seed=int(run.checkpoint.checkpoint_id[-6:], 16) % 1_000_000)
|
| 134 |
operator_state = {
|
| 135 |
+
"provider_state": generation.provider_state if generation.status in {"success", "error", "missing_runtime", "no_cuda"} else "checkpointed",
|
| 136 |
"checkpoint": "pending_review",
|
| 137 |
"export": scan.get("export_gate", "pending"),
|
| 138 |
+
"message": generation.message or "Run packet generated. Human checkpoint required before provider promotion or export.",
|
| 139 |
+
"generation": generation.to_dict(),
|
| 140 |
}
|
| 141 |
regions = _dashboard_regions(
|
| 142 |
run=run,
|
requirements.txt
CHANGED
|
@@ -1,3 +1,7 @@
|
|
| 1 |
gradio==6.12.0
|
| 2 |
huggingface_hub==1.18.0
|
| 3 |
pytest==9.0.3
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
gradio==6.12.0
|
| 2 |
huggingface_hub==1.18.0
|
| 3 |
pytest==9.0.3
|
| 4 |
+
accelerate>=1.12.0
|
| 5 |
+
transformers>=4.57.1
|
| 6 |
+
Pillow>=11.1.0
|
| 7 |
+
git+https://github.com/huggingface/diffusers.git
|
src/nexus_visual_weaver/hf_runtime.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""HF-native model execution for the Space runtime."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import os
|
| 6 |
+
import time
|
| 7 |
+
from dataclasses import dataclass, asdict
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
from typing import Any
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
FLUX_REPO_ID = "black-forest-labs/FLUX.2-klein-9B"
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
@dataclass(frozen=True)
|
| 16 |
+
class HFGenerationResult:
|
| 17 |
+
status: str
|
| 18 |
+
provider_state: str
|
| 19 |
+
repo_id: str
|
| 20 |
+
output_path: str | None = None
|
| 21 |
+
message: str = ""
|
| 22 |
+
latency_seconds: float | None = None
|
| 23 |
+
width: int = 1024
|
| 24 |
+
height: int = 1024
|
| 25 |
+
steps: int = 4
|
| 26 |
+
|
| 27 |
+
def to_dict(self) -> dict[str, Any]:
|
| 28 |
+
return asdict(self)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def hf_runtime_enabled() -> bool:
|
| 32 |
+
if os.environ.get("NEXUS_DISABLE_REAL_HF") == "1":
|
| 33 |
+
return False
|
| 34 |
+
if os.environ.get("NEXUS_ENABLE_REAL_HF") == "1":
|
| 35 |
+
return True
|
| 36 |
+
return bool(os.environ.get("SPACE_ID") or os.environ.get("HF_SPACE_ID"))
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def _output_dir() -> Path:
|
| 40 |
+
root = Path(os.environ.get("NEXUS_OUTPUT_DIR") or ("/data/nexus_visual_weaver" if Path("/data").exists() else "outputs/runtime"))
|
| 41 |
+
root.mkdir(parents=True, exist_ok=True)
|
| 42 |
+
return root
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def _short_error(exc: BaseException) -> str:
|
| 46 |
+
text = str(exc).replace("\n", " ").strip()
|
| 47 |
+
if len(text) > 420:
|
| 48 |
+
text = text[:417] + "..."
|
| 49 |
+
return f"{exc.__class__.__name__}: {text}"
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def generate_flux_image(prompt: str, *, seed: int = 0, width: int = 1024, height: int = 1024, steps: int = 4) -> HFGenerationResult:
|
| 53 |
+
if not hf_runtime_enabled():
|
| 54 |
+
return HFGenerationResult(
|
| 55 |
+
status="disabled",
|
| 56 |
+
provider_state="dry-run",
|
| 57 |
+
repo_id=FLUX_REPO_ID,
|
| 58 |
+
message="Real HF generation disabled outside Space. Set NEXUS_ENABLE_REAL_HF=1 to force local execution.",
|
| 59 |
+
width=width,
|
| 60 |
+
height=height,
|
| 61 |
+
steps=steps,
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
started = time.perf_counter()
|
| 65 |
+
try:
|
| 66 |
+
import torch
|
| 67 |
+
from diffusers import Flux2KleinPipeline
|
| 68 |
+
except Exception as exc: # pragma: no cover - depends on Space runtime packages.
|
| 69 |
+
return HFGenerationResult(
|
| 70 |
+
status="missing_runtime",
|
| 71 |
+
provider_state="blocked",
|
| 72 |
+
repo_id=FLUX_REPO_ID,
|
| 73 |
+
message=f"FLUX runtime import failed. Install diffusers main + torch. {_short_error(exc)}",
|
| 74 |
+
width=width,
|
| 75 |
+
height=height,
|
| 76 |
+
steps=steps,
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
+
if not torch.cuda.is_available():
|
| 80 |
+
return HFGenerationResult(
|
| 81 |
+
status="no_cuda",
|
| 82 |
+
provider_state="blocked",
|
| 83 |
+
repo_id=FLUX_REPO_ID,
|
| 84 |
+
message="CUDA is not available to the Space callback; FLUX.2 9B requires GPU execution.",
|
| 85 |
+
width=width,
|
| 86 |
+
height=height,
|
| 87 |
+
steps=steps,
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
try:
|
| 91 |
+
dtype = torch.bfloat16
|
| 92 |
+
pipe = Flux2KleinPipeline.from_pretrained(FLUX_REPO_ID, torch_dtype=dtype)
|
| 93 |
+
pipe.enable_model_cpu_offload()
|
| 94 |
+
generator = torch.Generator(device="cuda").manual_seed(seed)
|
| 95 |
+
image = pipe(
|
| 96 |
+
prompt=prompt,
|
| 97 |
+
height=height,
|
| 98 |
+
width=width,
|
| 99 |
+
guidance_scale=1.0,
|
| 100 |
+
num_inference_steps=steps,
|
| 101 |
+
generator=generator,
|
| 102 |
+
).images[0]
|
| 103 |
+
output_path = _output_dir() / f"nexus_flux_{int(time.time())}_{seed}.png"
|
| 104 |
+
image.save(output_path)
|
| 105 |
+
return HFGenerationResult(
|
| 106 |
+
status="success",
|
| 107 |
+
provider_state="generated",
|
| 108 |
+
repo_id=FLUX_REPO_ID,
|
| 109 |
+
output_path=str(output_path),
|
| 110 |
+
message="FLUX.2 Klein generated a real image artifact on HF Space.",
|
| 111 |
+
latency_seconds=round(time.perf_counter() - started, 2),
|
| 112 |
+
width=width,
|
| 113 |
+
height=height,
|
| 114 |
+
steps=steps,
|
| 115 |
+
)
|
| 116 |
+
except Exception as exc: # pragma: no cover - exercised on HF Space with gated/runtime conditions.
|
| 117 |
+
return HFGenerationResult(
|
| 118 |
+
status="error",
|
| 119 |
+
provider_state="blocked",
|
| 120 |
+
repo_id=FLUX_REPO_ID,
|
| 121 |
+
message=f"FLUX.2 generation failed. Check model license acceptance, HF_TOKEN/Space access, and runtime deps. {_short_error(exc)}",
|
| 122 |
+
latency_seconds=round(time.perf_counter() - started, 2),
|
| 123 |
+
width=width,
|
| 124 |
+
height=height,
|
| 125 |
+
steps=steps,
|
| 126 |
+
)
|
src/nexus_visual_weaver/render.py
CHANGED
|
@@ -3,7 +3,9 @@
|
|
| 3 |
from __future__ import annotations
|
| 4 |
|
| 5 |
import os
|
|
|
|
| 6 |
from html import escape
|
|
|
|
| 7 |
|
| 8 |
from .catalog import catalog_summary, parameter_budget
|
| 9 |
from .schema import GenerationRun
|
|
@@ -51,6 +53,18 @@ def _space_runtime_status() -> dict[str, str]:
|
|
| 51 |
}
|
| 52 |
|
| 53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
def render_command_header() -> str:
|
| 55 |
return f"""
|
| 56 |
<section class="nw-command-header">
|
|
@@ -255,6 +269,10 @@ def render_artifact_lane(run: GenerationRun | None = None, scan: dict | None = N
|
|
| 255 |
active_prompt = run.refined_prompt.refined[:150] if run else "Awaiting first weave. The preview stage shows dry-run handoff packets until provider output exists."
|
| 256 |
checkpoint = operator_state.get("checkpoint", getattr(run.checkpoint, "recommendation", "pending") if run else "pending")
|
| 257 |
provider_state = str(operator_state.get("provider_state", "dry-run" if run else "idle"))
|
|
|
|
|
|
|
|
|
|
|
|
|
| 258 |
preview_mode = {
|
| 259 |
"idle": "Idle",
|
| 260 |
"dry-run": "Dry Run",
|
|
@@ -293,11 +311,11 @@ def render_artifact_lane(run: GenerationRun | None = None, scan: dict | None = N
|
|
| 293 |
</div>
|
| 294 |
<div class="nw-preview-stage">
|
| 295 |
<div class="nw-preview-frame">
|
| 296 |
-
<i class="nw-preview-image"></i>
|
| 297 |
<div class="nw-preview-caption">
|
| 298 |
-
<small>PRIMARY OUTPUT STAGE / JUDGE-SAFE DEMO OUTPUT / SEED {escape(demo_seed)}</small>
|
| 299 |
-
<strong>Deterministic Raven Chronicle proof frame</strong>
|
| 300 |
-
<span>{escape(active_prompt)}</span>
|
| 301 |
</div>
|
| 302 |
</div>
|
| 303 |
<div class="nw-preview-meta">
|
|
|
|
| 3 |
from __future__ import annotations
|
| 4 |
|
| 5 |
import os
|
| 6 |
+
import base64
|
| 7 |
from html import escape
|
| 8 |
+
from pathlib import Path
|
| 9 |
|
| 10 |
from .catalog import catalog_summary, parameter_budget
|
| 11 |
from .schema import GenerationRun
|
|
|
|
| 53 |
}
|
| 54 |
|
| 55 |
|
| 56 |
+
def _image_data_uri(path: str | None) -> str | None:
|
| 57 |
+
if not path:
|
| 58 |
+
return None
|
| 59 |
+
target = Path(path)
|
| 60 |
+
if not target.exists() or not target.is_file():
|
| 61 |
+
return None
|
| 62 |
+
suffix = target.suffix.lower()
|
| 63 |
+
mime = "image/png" if suffix == ".png" else "image/jpeg" if suffix in {".jpg", ".jpeg"} else "image/webp" if suffix == ".webp" else "application/octet-stream"
|
| 64 |
+
data = base64.b64encode(target.read_bytes()).decode("ascii")
|
| 65 |
+
return f"data:{mime};base64,{data}"
|
| 66 |
+
|
| 67 |
+
|
| 68 |
def render_command_header() -> str:
|
| 69 |
return f"""
|
| 70 |
<section class="nw-command-header">
|
|
|
|
| 269 |
active_prompt = run.refined_prompt.refined[:150] if run else "Awaiting first weave. The preview stage shows dry-run handoff packets until provider output exists."
|
| 270 |
checkpoint = operator_state.get("checkpoint", getattr(run.checkpoint, "recommendation", "pending") if run else "pending")
|
| 271 |
provider_state = str(operator_state.get("provider_state", "dry-run" if run else "idle"))
|
| 272 |
+
generation = operator_state.get("generation") or {}
|
| 273 |
+
generated_uri = _image_data_uri(generation.get("output_path")) if isinstance(generation, dict) else None
|
| 274 |
+
generated_status = str(generation.get("status", "")) if isinstance(generation, dict) else ""
|
| 275 |
+
generated_message = str(generation.get("message", "")) if isinstance(generation, dict) else ""
|
| 276 |
preview_mode = {
|
| 277 |
"idle": "Idle",
|
| 278 |
"dry-run": "Dry Run",
|
|
|
|
| 311 |
</div>
|
| 312 |
<div class="nw-preview-stage">
|
| 313 |
<div class="nw-preview-frame">
|
| 314 |
+
{'<img class="nw-preview-real-image" src="' + generated_uri + '" alt="Generated FLUX artifact" />' if generated_uri else '<i class="nw-preview-image"></i>'}
|
| 315 |
<div class="nw-preview-caption">
|
| 316 |
+
<small>PRIMARY OUTPUT STAGE / {escape(generated_status.upper() or "JUDGE-SAFE DEMO OUTPUT")} / SEED {escape(demo_seed)}</small>
|
| 317 |
+
<strong>{'Real FLUX.2 Klein artifact' if generated_uri else 'Deterministic Raven Chronicle proof frame'}</strong>
|
| 318 |
+
<span>{escape(generated_message or active_prompt)}</span>
|
| 319 |
</div>
|
| 320 |
</div>
|
| 321 |
<div class="nw-preview-meta">
|
src/nexus_visual_weaver/styles.py
CHANGED
|
@@ -657,6 +657,15 @@ footer { display: none !important; }
|
|
| 657 |
linear-gradient(155deg, #050608, #171d23 44%, #4f1020 45%, #07090c 63%);
|
| 658 |
border-right: 1px solid rgba(255,255,255,.08);
|
| 659 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 660 |
.nw-preview-caption {
|
| 661 |
display: grid;
|
| 662 |
align-content: end;
|
|
|
|
| 657 |
linear-gradient(155deg, #050608, #171d23 44%, #4f1020 45%, #07090c 63%);
|
| 658 |
border-right: 1px solid rgba(255,255,255,.08);
|
| 659 |
}
|
| 660 |
+
.nw-preview-real-image {
|
| 661 |
+
width: 100%;
|
| 662 |
+
min-height: 190px;
|
| 663 |
+
height: 100%;
|
| 664 |
+
object-fit: cover;
|
| 665 |
+
display: block;
|
| 666 |
+
border-right: 1px solid rgba(255,255,255,.08);
|
| 667 |
+
background: #050608;
|
| 668 |
+
}
|
| 669 |
.nw-preview-caption {
|
| 670 |
display: grid;
|
| 671 |
align-content: end;
|
tests/test_hf_runtime.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from PIL import Image
|
| 2 |
+
|
| 3 |
+
from nexus_visual_weaver.hf_runtime import generate_flux_image, hf_runtime_enabled
|
| 4 |
+
from nexus_visual_weaver.render import render_artifact_lane
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def test_hf_runtime_is_disabled_locally_by_default(monkeypatch) -> None:
|
| 8 |
+
monkeypatch.delenv("SPACE_ID", raising=False)
|
| 9 |
+
monkeypatch.delenv("HF_SPACE_ID", raising=False)
|
| 10 |
+
monkeypatch.delenv("NEXUS_ENABLE_REAL_HF", raising=False)
|
| 11 |
+
monkeypatch.delenv("NEXUS_DISABLE_REAL_HF", raising=False)
|
| 12 |
+
|
| 13 |
+
assert hf_runtime_enabled() is False
|
| 14 |
+
result = generate_flux_image("test prompt")
|
| 15 |
+
assert result.status == "disabled"
|
| 16 |
+
assert result.provider_state == "dry-run"
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def test_artifact_lane_embeds_generated_image() -> None:
|
| 20 |
+
image_path = "outputs/test-generated-artifact.png"
|
| 21 |
+
Image.new("RGB", (16, 16), color=(12, 16, 20)).save(image_path)
|
| 22 |
+
|
| 23 |
+
html = render_artifact_lane(
|
| 24 |
+
operator_state={
|
| 25 |
+
"provider_state": "generated",
|
| 26 |
+
"generation": {
|
| 27 |
+
"status": "success",
|
| 28 |
+
"output_path": str(image_path),
|
| 29 |
+
"message": "FLUX.2 generated a real artifact.",
|
| 30 |
+
},
|
| 31 |
+
}
|
| 32 |
+
)
|
| 33 |
+
|
| 34 |
+
assert "nw-preview-real-image" in html
|
| 35 |
+
assert "data:image/png;base64" in html
|
| 36 |
+
assert "Real FLUX.2 Klein artifact" in html
|