akhaliq HF Staff commited on
Commit
8a843ca
Β·
1 Parent(s): fff2a34

Migrate to native Hugging Face Gradio SDK Space with ZeroGPU support

Browse files
Dockerfile DELETED
@@ -1,41 +0,0 @@
1
- # syntax=docker/dockerfile:1.6
2
- # CUDA 12.8 runtime β€” gemlite/Triton kernels JIT against the runtime ptxas
3
- # that comes with this image; no need for the larger -devel variant.
4
- FROM nvidia/cuda:12.8.0-runtime-ubuntu22.04
5
-
6
- # ── system deps ──────────────────────────────────────────────────────────────
7
- # ca-certificates, curl, git: basic utilities.
8
- # build-essential: needed for JIT kernel compile and some sdist setups.
9
- # python3-venv: python baseline for uv package manager.
10
- # procps: provides process management.
11
- RUN apt-get update && apt-get install -y --no-install-recommends \
12
- ca-certificates curl git build-essential python3 python3-venv \
13
- libgomp1 libssl3 procps \
14
- && rm -rf /var/lib/apt/lists/*
15
-
16
- # Non-root user: HF Spaces convention is uid 1000.
17
- RUN useradd -m -u 1000 user
18
- USER user
19
- ENV HOME=/home/user PATH="/home/user/.local/bin:$PATH"
20
-
21
- # uv (Python venv + package manager). The demo's setup.sh assumes it's on PATH.
22
- RUN curl -fsSL https://astral.sh/uv/install.sh | sh
23
-
24
- WORKDIR /home/user/app
25
-
26
- # ── clone + run setup.sh ─────────────────────────────────────────────────────
27
- # SKIP_DOWNLOAD=1 keeps setup.sh from pulling the 3.5 GB model at build time
28
- # β€” entrypoint.sh handles that at boot so a Space restart doesn't have to
29
- # rebuild the image.
30
- RUN git clone https://github.com/PrismML-Eng/Bonsai-image-demo.git . \
31
- && SKIP_DOWNLOAD=1 BONSAI_PACKAGE_MIN_AGE_DAYS=0 ./setup.sh
32
-
33
- # ── Space-local files ────────────────────────────────────────────────────────
34
- # All Space-specific code lives under space/ (Python gradio.Server app + HTML frontend).
35
- # The demo's own code stays at the repo root (cloned earlier) so they don't collide.
36
- COPY --chown=user space/ /home/user/app/space/
37
- RUN chmod +x /home/user/app/space/entrypoint.sh
38
-
39
- EXPOSE 7860
40
-
41
- CMD ["/home/user/app/space/entrypoint.sh"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md CHANGED
@@ -3,8 +3,7 @@ title: Bonsai Image GPU
3
  emoji: 🎨
4
  colorFrom: green
5
  colorTo: blue
6
- sdk: docker
7
- app_port: 7860
8
  suggested_hardware: l40sx1
9
  pinned: true
10
  short_description: Run Bonsai-Image-4B models on GPU
 
3
  emoji: 🎨
4
  colorFrom: green
5
  colorTo: blue
6
+ sdk: gradio
 
7
  suggested_hardware: l40sx1
8
  pinned: true
9
  short_description: Run Bonsai-Image-4B models on GPU
{space/__pycache__ β†’ __pycache__}/app.cpython-314.pyc RENAMED
Binary files a/space/__pycache__/app.cpython-314.pyc and b/__pycache__/app.cpython-314.pyc differ
 
space/app.py β†’ app.py RENAMED
@@ -1,20 +1,20 @@
1
- """gradio.Server backend for Bonsai-Image-Demo.
2
 
3
- Replaces the complex custom metrics middleware and N-replica queues
4
- with Gradio's native queue engine and Server class.
5
  """
6
  from __future__ import annotations
7
 
8
  import os
 
9
  import logging
10
  import time
11
  import base64
12
- import uuid
13
- import tempfile
14
  from contextlib import asynccontextmanager
15
  from fastapi import Request
16
  from fastapi.responses import HTMLResponse
17
  from gradio import Server
 
18
 
19
  # Initialize logging
20
  logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s")
@@ -23,6 +23,56 @@ logger = logging.getLogger(__name__)
23
  # Ensure MFLUX_STUDIO_GPU_TOKEN is set for backend compatibility
24
  os.environ.setdefault("MFLUX_STUDIO_GPU_TOKEN", "local-demo-unused")
25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  # Apply low-memory and Triton/gemlite caching monkeypatches from scripts.local_backend
27
  try:
28
  import scripts.local_backend
@@ -32,6 +82,21 @@ except ImportError:
32
 
33
  from backend_gpu.pipeline_gpu import GpuPipeline, DEFAULT_GPU_BACKEND, _normalize_gpu_backend
34
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  @asynccontextmanager
36
  async def lifespan(fastapi_app: Server):
37
  logger.info("Initializing GpuPipeline inside gradio.Server lifespan...")
@@ -46,7 +111,7 @@ async def lifespan(fastapi_app: Server):
46
  pipeline.prewarm()
47
  fastapi_app.state.pipeline = pipeline
48
 
49
- # Run warmup shapes if configured
50
  warmup_shapes = os.environ.get("BONSAI_WARMUP_SHAPES", "")
51
  skip_warmup = os.environ.get("BONSAI_SKIP_WARMUP", "").lower() in {"1", "true", "yes"}
52
 
@@ -70,6 +135,7 @@ async def lifespan(fastapi_app: Server):
70
  app = Server(lifespan=lifespan)
71
 
72
  @app.api(name="generate")
 
73
  def generate(
74
  prompt: str,
75
  seed: int = 0,
@@ -82,7 +148,7 @@ def generate(
82
  ) -> dict:
83
  """Generate an image using the GpuPipeline and return base64 data and metrics.
84
 
85
- Runs inside Gradio's native queue engine with serial concurrency.
86
  """
87
  pipeline = app.state.pipeline
88
  try:
 
1
+ """gradio.Server backend with ZeroGPU dynamic allocation support.
2
 
3
+ Relocated to root level for native Hugging Face Gradio SDK Space compatibility.
4
+ Handles model weight downloads and environment path setup directly in Python.
5
  """
6
  from __future__ import annotations
7
 
8
  import os
9
+ import glob
10
  import logging
11
  import time
12
  import base64
 
 
13
  from contextlib import asynccontextmanager
14
  from fastapi import Request
15
  from fastapi.responses import HTMLResponse
16
  from gradio import Server
17
+ from huggingface_hub import snapshot_download
18
 
19
  # Initialize logging
20
  logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s")
 
23
  # Ensure MFLUX_STUDIO_GPU_TOKEN is set for backend compatibility
24
  os.environ.setdefault("MFLUX_STUDIO_GPU_TOKEN", "local-demo-unused")
25
 
26
+ # ── Dynamic Model Download and Path Configuration ───────────────────────────
27
+ def init_models_and_env():
28
+ # Detect persistent storage mount at /data
29
+ has_data = os.path.exists("/data") and os.access("/data", os.W_OK)
30
+ model_root = "/data/models" if has_data else "models"
31
+
32
+ # 1. Download models using huggingface_hub snapshot_download
33
+ ternary_dir = os.path.join(model_root, "bonsai-image-4B-ternary-gemlite")
34
+ logger.info(f"Syncing ternary model to: {ternary_dir}...")
35
+ snapshot_download(
36
+ repo_id='prism-ml/bonsai-image-ternary-4B-gemlite-2bit',
37
+ local_dir=ternary_dir,
38
+ max_workers=8
39
+ )
40
+
41
+ binary_dir = os.path.join(model_root, "bonsai-image-4B-binary-gemlite")
42
+ logger.info(f"Syncing binary model to: {binary_dir}...")
43
+ snapshot_download(
44
+ repo_id='prism-ml/bonsai-image-binary-4B-gemlite-1bit',
45
+ local_dir=binary_dir,
46
+ max_workers=8
47
+ )
48
+
49
+ # 2. Resolve dynamic paths and configure environment variables
50
+ os.environ["MFLUX_STUDIO_GPU_DEFAULT_BACKEND"] = "bonsai-ternary-gemlite"
51
+
52
+ ternary_transformer = glob.glob(os.path.join(ternary_dir, "transformer-gemlite-*"))
53
+ if not ternary_transformer:
54
+ raise FileNotFoundError(f"Ternary transformer subdirectory not found under {ternary_dir}")
55
+ os.environ["MFLUX_STUDIO_GPU_TERNARY_TRANSFORMER_PATH"] = ternary_transformer[0]
56
+
57
+ binary_transformer = glob.glob(os.path.join(binary_dir, "transformer-gemlite-*"))
58
+ if not binary_transformer:
59
+ raise FileNotFoundError(f"Binary transformer subdirectory not found under {binary_dir}")
60
+ os.environ["MFLUX_STUDIO_GPU_BINARY_TRANSFORMER_PATH"] = binary_transformer[0]
61
+
62
+ os.environ["MFLUX_STUDIO_GPU_TEXT_ENCODER_PATH"] = os.path.join(ternary_dir, "text_encoder-hqq-4bit")
63
+ os.environ["MFLUX_STUDIO_GPU_VAE_PATH"] = os.path.join(ternary_dir, "vae")
64
+ os.environ["MFLUX_STUDIO_GPU_TOKENIZER_PATH"] = os.path.join(ternary_dir, "text_encoder-hqq-4bit/tokenizer")
65
+
66
+ # Setup JIT compilation caches
67
+ if has_data:
68
+ os.environ["TRITON_CACHE_DIR"] = "/data/cache/triton"
69
+ logger.info("Persistent Storage Bucket attached. Model weights and Triton caches will persist.")
70
+ else:
71
+ logger.warning("/data not mounted. Caches will be ephemeral.")
72
+
73
+ # Run model setup and environment pinning
74
+ init_models_and_env()
75
+
76
  # Apply low-memory and Triton/gemlite caching monkeypatches from scripts.local_backend
77
  try:
78
  import scripts.local_backend
 
82
 
83
  from backend_gpu.pipeline_gpu import GpuPipeline, DEFAULT_GPU_BACKEND, _normalize_gpu_backend
84
 
85
+ # ── ZeroGPU spaces module loading ────────────────────────────────────────────
86
+ try:
87
+ import spaces
88
+ has_spaces = True
89
+ logger.info("ZeroGPU spaces library loaded successfully.")
90
+ except ImportError:
91
+ has_spaces = False
92
+ logger.warning("ZeroGPU spaces library not found. Running in standard GPU/CPU fallback mode.")
93
+
94
+ # Conditional decorator for ZeroGPU support
95
+ def spaces_gpu(func):
96
+ if has_spaces:
97
+ return spaces.GPU(func)
98
+ return func
99
+
100
  @asynccontextmanager
101
  async def lifespan(fastapi_app: Server):
102
  logger.info("Initializing GpuPipeline inside gradio.Server lifespan...")
 
111
  pipeline.prewarm()
112
  fastapi_app.state.pipeline = pipeline
113
 
114
+ # Run warmup shapes if configured (ZeroGPU AOT caching benefit)
115
  warmup_shapes = os.environ.get("BONSAI_WARMUP_SHAPES", "")
116
  skip_warmup = os.environ.get("BONSAI_SKIP_WARMUP", "").lower() in {"1", "true", "yes"}
117
 
 
135
  app = Server(lifespan=lifespan)
136
 
137
  @app.api(name="generate")
138
+ @spaces_gpu
139
  def generate(
140
  prompt: str,
141
  seed: int = 0,
 
148
  ) -> dict:
149
  """Generate an image using the GpuPipeline and return base64 data and metrics.
150
 
151
+ Decorated with @spaces.GPU to dynamically lease Blackwell GPUs on Hugging Face.
152
  """
153
  pipeline = app.state.pipeline
154
  try:
space/index.html β†’ index.html RENAMED
File without changes
requirements.txt ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ torch
2
+ torchvision
3
+ transformers
4
+ diffusers
5
+ accelerate
6
+ huggingface_hub
7
+ gradio>=5.0.0
8
+ hqq
9
+ uvicorn
10
+ spaces
11
+ gemlite
12
+ git+https://github.com/PrismML-Eng/mflux-prism.git
13
+ git+https://github.com/PrismML-Eng/image-studio.git#subdirectory=backend_gpu
space/__init__.py DELETED
File without changes
space/entrypoint.sh DELETED
@@ -1,108 +0,0 @@
1
- #!/bin/bash
2
- # Streamlined entrypoint for Bonsai Image Studio (gradio.Server)
3
- #
4
- # Boot order:
5
- # 1. Detect platform and active GPU.
6
- # 2. Set up persistent storage caches (/data) if available.
7
- # 3. Sync model weights (ternary and binary) β€” public, no tokens needed.
8
- # 4. Start the single gradio.Server app on :7860 via uvicorn.
9
- #
10
- set -euo pipefail
11
-
12
- APP_DIR="${HOME:-/home/user}/app"
13
- cd "$APP_DIR"
14
-
15
- export PATH="$APP_DIR/.venv/bin:$PATH"
16
- export HF_HUB_ENABLE_HF_TRANSFER=1
17
-
18
- echo "==> Starting Bonsai Image Studio initialization ..."
19
-
20
- # ── GPU detection ───────────────────────────────────────────────────────────
21
- GPU_NAME=$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | awk 'NR==1' | xargs)
22
- GPU_CAP=$(nvidia-smi --query-gpu=compute_cap --format=csv,noheader 2>/dev/null | awk 'NR==1' | tr -d '.')
23
- [ -z "$GPU_NAME" ] && GPU_NAME="unknown"
24
- [ -z "$GPU_CAP" ] && GPU_CAP="00"
25
- echo "[OK] GPU: $GPU_NAME (sm_${GPU_CAP})"
26
-
27
- # ── persistent storage detection ─────────────────────────────────────────────
28
- _setup_persistent() {
29
- [ -d /data ] && [ -w /data ] || return 1
30
-
31
- # Namespaced caches
32
- _gemlite_dir="/data/cache/gemlite-sm${GPU_CAP}"
33
- _triton_dir="/data/cache/triton-sm${GPU_CAP}"
34
-
35
- # Migrations
36
- if [ -d /data/cache/gemlite ] && [ ! -e "$_gemlite_dir" ]; then
37
- echo "[INFO] migrating /data/cache/gemlite β†’ gemlite-sm${GPU_CAP}"
38
- mv /data/cache/gemlite "$_gemlite_dir" 2>/dev/null || true
39
- fi
40
- if [ -d /data/cache/triton ] && [ ! -e "$_triton_dir" ]; then
41
- echo "[INFO] migrating /data/cache/triton β†’ triton-sm${GPU_CAP}"
42
- mv /data/cache/triton "$_triton_dir" 2>/dev/null || true
43
- fi
44
-
45
- mkdir -p /data/models "$_gemlite_dir" "$_triton_dir" /data/state /data/state/daily 2>/dev/null || return 1
46
- rm -rf "$APP_DIR/models" 2>/dev/null || return 1
47
- ln -s /data/models "$APP_DIR/models" 2>/dev/null || return 1
48
- mkdir -p "$APP_DIR/outputs" 2>/dev/null || return 1
49
- rm -rf "$APP_DIR/outputs/.gemlite_cache" "$APP_DIR/outputs/.triton_cache" 2>/dev/null || true
50
- ln -s "$_gemlite_dir" "$APP_DIR/outputs/.gemlite_cache" 2>/dev/null || return 1
51
- ln -s "$_triton_dir" "$APP_DIR/outputs/.triton_cache" 2>/dev/null || return 1
52
- return 0
53
- }
54
-
55
- if _setup_persistent; then
56
- echo "[OK] /data Storage Bucket attached β€” model + caches will persist"
57
- export BONSAI_STATE_DIR=/data/state
58
- export BONSAI_PERSISTENT_STORAGE=1
59
- else
60
- echo "[WARN] /data not mounted β€” caches will reset on every Space restart."
61
- export BONSAI_STATE_DIR="$APP_DIR/outputs/.state"
62
- export BONSAI_PERSISTENT_STORAGE=0
63
- mkdir -p "$BONSAI_STATE_DIR" 2>/dev/null || true
64
- fi
65
-
66
- # ── model download / sync ────────────────────────────────────────────────────
67
- MODEL_DIR="$APP_DIR/models/bonsai-image-4B-ternary-gemlite"
68
- BINARY_MODEL_DIR="$APP_DIR/models/bonsai-image-4B-binary-gemlite"
69
-
70
- # Set token if provided, but download works token-free for public models
71
- if [ -n "${HF_TOKEN:-}" ]; then
72
- export BONSAI_TOKEN="$HF_TOKEN"
73
- fi
74
-
75
- echo "==> syncing bonsai-image-ternary-4B-gemlite-2bit ..."
76
- ./scripts/download_model.sh --model ternary-gemlite || echo "[WARN] Ternary model sync encountered errors."
77
- echo "==> syncing bonsai-image-binary-4B-gemlite-1bit ..."
78
- ./scripts/download_model.sh --model binary-gemlite || echo "[WARN] Binary model sync encountered errors."
79
-
80
- # ── pin model paths once; shared across all workers ──────────────────────────
81
- export MFLUX_STUDIO_GPU_DEFAULT_BACKEND="bonsai-ternary-gemlite"
82
-
83
- _ternary_transformer_dir=$(ls -d "$MODEL_DIR"/transformer-gemlite-* 2>/dev/null | awk 'NR==1')
84
- if [ -z "$_ternary_transformer_dir" ]; then
85
- echo "[ERR] no transformer-gemlite-* subdir under $MODEL_DIR" >&2
86
- exit 1
87
- fi
88
- export MFLUX_STUDIO_GPU_TERNARY_TRANSFORMER_PATH="$_ternary_transformer_dir"
89
-
90
- _binary_transformer_dir=$(ls -d "$BINARY_MODEL_DIR"/transformer-gemlite-* 2>/dev/null | awk 'NR==1')
91
- if [ -z "$_binary_transformer_dir" ]; then
92
- echo "[ERR] no transformer-gemlite-* subdir under $BINARY_MODEL_DIR" >&2
93
- exit 1
94
- fi
95
- export MFLUX_STUDIO_GPU_BINARY_TRANSFORMER_PATH="$_binary_transformer_dir"
96
- export MFLUX_STUDIO_GPU_TEXT_ENCODER_PATH="$MODEL_DIR/text_encoder-hqq-4bit"
97
- export MFLUX_STUDIO_GPU_VAE_PATH="$MODEL_DIR/vae"
98
- export MFLUX_STUDIO_GPU_TOKENIZER_PATH="$MODEL_DIR/text_encoder-hqq-4bit/tokenizer"
99
-
100
- # Warmup configurations
101
- : "${BONSAI_WARMUP_SHAPES:=512x512,1024x1024}"
102
- export BONSAI_WARMUP_SHAPES
103
-
104
- # ── Start the gradio.Server app directly on port 7860 ────────────────────────
105
- echo "==> Starting gradio.Server on public port 7860 ..."
106
- exec uvicorn space.app:app \
107
- --host 0.0.0.0 --port 7860 \
108
- --no-access-log