Spaces:
Running on Zero
Running on Zero
Usage counter: append one metadata-only JSON line per generation to a private dataset (all replicas)
Browse files
app.py
CHANGED
|
@@ -25,6 +25,7 @@ import io
|
|
| 25 |
import json
|
| 26 |
import random
|
| 27 |
import re
|
|
|
|
| 28 |
import time
|
| 29 |
from pathlib import Path
|
| 30 |
|
|
@@ -35,6 +36,7 @@ import compare
|
|
| 35 |
from PIL import Image, ImageOps
|
| 36 |
from diffusers import FlowMatchEulerDiscreteScheduler, QwenImage21Pipeline, QwenImage21Transformer2DModel
|
| 37 |
from diffusers.pipelines.qwenimage21.pipeline_qwenimage21 import calculate_dimensions
|
|
|
|
| 38 |
|
| 39 |
# Students in the model repo. STUDENT="lora" (default): the v0.2.1 LoRA (r256) at the repo root, applied at runtime on
|
| 40 |
# top of the base transformer and never merged (merging into bf16 keeps only ~47% of the adapter delta, the
|
|
@@ -47,6 +49,30 @@ STUDENT = os.environ.get("STUDENT", "lora")
|
|
| 47 |
LORA_FILE = os.environ.get("LORA_FILE", "Qwen-Image-2.1-viggle-turbo-v0.2.1-6step-lora-r256.safetensors")
|
| 48 |
STUDENT_TAG = {"full": "v0.1 full fine-tune (transformer/)", "lora": "v0.2.1 LoRA r256" if "v0.2.1" in LORA_FILE else f"LoRA {LORA_FILE}"}[STUDENT]
|
| 49 |
HF_TOKEN = os.environ.get("HF_TOKEN") # only needed while the model repo is private
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
# The LoRA is sampled on 6 Euler steps whose raw (pre-shift) sigma nodes are RAW_NODES: the 4-step training schedule
|
| 51 |
# linspace(1, 1/4, 4) with its highest-noise segment [1, 0.75] cut into three (1, 0.9375, 0.875, 0.75). The composition
|
| 52 |
# is decided in that segment, and one big Euler step there ghosts and drifts the layout; the low-noise nodes 0.75, 0.5,
|
|
@@ -209,6 +235,7 @@ def denoise(prompt, images, width, height, steps, seed):
|
|
| 209 |
|
| 210 |
|
| 211 |
def generate(prompt, refs, size_label, seed, randomize_seed, enhance="Auto", steps=STEPS, custom_width=1024, custom_height=1024):
|
|
|
|
| 212 |
steps = min(max(int(steps), 3), 8) # 6 (RAW_NODES) is the validated schedule; 3-8 are accepted, fewer or more degrade quickly
|
| 213 |
# refs: the gallery's (filepath, caption) pairs in upload order, which is the order the prompt's "image 1, 2, ..." refer
|
| 214 |
# to. EXIF rotation + RGB is what the gr.Image slots used to do.
|
|
@@ -243,11 +270,19 @@ def generate(prompt, refs, size_label, seed, randomize_seed, enhance="Auto", ste
|
|
| 243 |
used_prompt = enhance_prompt(prompt, images, ratio_name) or prompt
|
| 244 |
pe_time = time.perf_counter() - started
|
| 245 |
enhance_note = f" · enhanced {pe_time:.1f}s" if used_prompt != prompt else " · enhancement failed, prompt sent as written"
|
| 246 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 247 |
# one line per generation, for counting usage from the Space logs (no prompt text)
|
| 248 |
-
print(f"[gen] {time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())} mode={
|
| 249 |
f"steps={steps} size={result.width}x{result.height} enhance={int(used_prompt != prompt)} pe={pe_time:.1f}s gen={elapsed:.2f}s",
|
| 250 |
flush=True)
|
|
|
|
|
|
|
| 251 |
return result, f"seed `{seed}` · {result.width}×{result.height} · {steps} steps · {elapsed:.2f}s · {STUDENT_TAG}{enhance_note}{clamped}", used_prompt
|
| 252 |
|
| 253 |
|
|
|
|
| 25 |
import json
|
| 26 |
import random
|
| 27 |
import re
|
| 28 |
+
import socket
|
| 29 |
import time
|
| 30 |
from pathlib import Path
|
| 31 |
|
|
|
|
| 36 |
from PIL import Image, ImageOps
|
| 37 |
from diffusers import FlowMatchEulerDiscreteScheduler, QwenImage21Pipeline, QwenImage21Transformer2DModel
|
| 38 |
from diffusers.pipelines.qwenimage21.pipeline_qwenimage21 import calculate_dimensions
|
| 39 |
+
from huggingface_hub import CommitScheduler
|
| 40 |
|
| 41 |
# Students in the model repo. STUDENT="lora" (default): the v0.2.1 LoRA (r256) at the repo root, applied at runtime on
|
| 42 |
# top of the base transformer and never merged (merging into bf16 keeps only ~47% of the adapter delta, the
|
|
|
|
| 49 |
LORA_FILE = os.environ.get("LORA_FILE", "Qwen-Image-2.1-viggle-turbo-v0.2.1-6step-lora-r256.safetensors")
|
| 50 |
STUDENT_TAG = {"full": "v0.1 full fine-tune (transformer/)", "lora": "v0.2.1 LoRA r256" if "v0.2.1" in LORA_FILE else f"LoRA {LORA_FILE}"}[STUDENT]
|
| 51 |
HF_TOKEN = os.environ.get("HF_TOKEN") # only needed while the model repo is private
|
| 52 |
+
# Usage counter. The Space autoscales to several replicas and its log view shows only one of them, so the [gen] log lines
|
| 53 |
+
# undercount. Every generate() call that reaches the GPU also appends one JSON line (metadata only: no prompt, no images)
|
| 54 |
+
# to this container's own file, which CommitScheduler pushes to a private dataset every 10 minutes and at exit; one file
|
| 55 |
+
# per container, so replicas never write the same path. The token is STATS_TOKEN, else HF_TOKEN; it needs write access to
|
| 56 |
+
# that dataset. Without a token, or if the scheduler cannot start with it, the counter is off and the app runs as before.
|
| 57 |
+
STATS_DIR = Path("/tmp/gen_stats")
|
| 58 |
+
STATS_DIR.mkdir(exist_ok=True)
|
| 59 |
+
STATS_FILE = STATS_DIR / f"{time.strftime('%Y%m%dT%H%M%SZ', time.gmtime())}-{socket.gethostname()}.jsonl"
|
| 60 |
+
STATS_TOKEN = os.environ.get("STATS_TOKEN") or HF_TOKEN
|
| 61 |
+
stats = None
|
| 62 |
+
if STATS_TOKEN:
|
| 63 |
+
try:
|
| 64 |
+
stats = CommitScheduler(repo_id="Viggle/qwen-image-21-turbo-space-stats", repo_type="dataset", folder_path=STATS_DIR,
|
| 65 |
+
path_in_repo="gens", every=10, private=True, token=STATS_TOKEN)
|
| 66 |
+
except Exception as e: # e.g. a token that cannot see the dataset: count nothing rather than fail to start
|
| 67 |
+
print(f"[stats] off: {type(e).__name__}: {e}", flush=True)
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def record(**row):
|
| 71 |
+
if stats:
|
| 72 |
+
with stats.lock, STATS_FILE.open("a") as f:
|
| 73 |
+
f.write(json.dumps({"ts": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), **row}) + "\n")
|
| 74 |
+
|
| 75 |
+
|
| 76 |
# The LoRA is sampled on 6 Euler steps whose raw (pre-shift) sigma nodes are RAW_NODES: the 4-step training schedule
|
| 77 |
# linspace(1, 1/4, 4) with its highest-noise segment [1, 0.75] cut into three (1, 0.9375, 0.875, 0.75). The composition
|
| 78 |
# is decided in that segment, and one big Euler step there ghosts and drifts the layout; the low-noise nodes 0.75, 0.5,
|
|
|
|
| 235 |
|
| 236 |
|
| 237 |
def generate(prompt, refs, size_label, seed, randomize_seed, enhance="Auto", steps=STEPS, custom_width=1024, custom_height=1024):
|
| 238 |
+
t0 = time.perf_counter()
|
| 239 |
steps = min(max(int(steps), 3), 8) # 6 (RAW_NODES) is the validated schedule; 3-8 are accepted, fewer or more degrade quickly
|
| 240 |
# refs: the gallery's (filepath, caption) pairs in upload order, which is the order the prompt's "image 1, 2, ..." refer
|
| 241 |
# to. EXIF rotation + RGB is what the gr.Image slots used to do.
|
|
|
|
| 270 |
used_prompt = enhance_prompt(prompt, images, ratio_name) or prompt
|
| 271 |
pe_time = time.perf_counter() - started
|
| 272 |
enhance_note = f" · enhanced {pe_time:.1f}s" if used_prompt != prompt else " · enhancement failed, prompt sent as written"
|
| 273 |
+
mode = "edit" if images else "t2i"
|
| 274 |
+
try:
|
| 275 |
+
result, elapsed = denoise(used_prompt, images, width, height, steps, seed)
|
| 276 |
+
except Exception as e: # ZeroGPU quota / GPU timeout errors land here: count them, then show them as before
|
| 277 |
+
record(ok=False, mode=mode, refs=len(images), steps=steps, error=f"{type(e).__name__}: {e}"[:200],
|
| 278 |
+
total=round(time.perf_counter() - t0, 2))
|
| 279 |
+
raise
|
| 280 |
# one line per generation, for counting usage from the Space logs (no prompt text)
|
| 281 |
+
print(f"[gen] {time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())} mode={mode} refs={len(images)} "
|
| 282 |
f"steps={steps} size={result.width}x{result.height} enhance={int(used_prompt != prompt)} pe={pe_time:.1f}s gen={elapsed:.2f}s",
|
| 283 |
flush=True)
|
| 284 |
+
record(ok=True, mode=mode, refs=len(images), steps=steps, size=f"{result.width}x{result.height}", enhance=enhance,
|
| 285 |
+
enhanced=used_prompt != prompt, pe=round(pe_time, 2), gen=round(elapsed, 2), total=round(time.perf_counter() - t0, 2))
|
| 286 |
return result, f"seed `{seed}` · {result.width}×{result.height} · {steps} steps · {elapsed:.2f}s · {STUDENT_TAG}{enhance_note}{clamped}", used_prompt
|
| 287 |
|
| 288 |
|