AnimeOverlord commited on
Commit
1c0a58f
·
1 Parent(s): 6ba9d20

still initial commit

Browse files
Files changed (2) hide show
  1. app.py +46 -12
  2. backend/backend.py +53 -9
app.py CHANGED
@@ -1,3 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  import cv2
3
  import numpy as np
@@ -45,13 +61,14 @@ def _offline_frame(frame: np.ndarray, message: str) -> np.ndarray:
45
  return out
46
 
47
 
48
- def _run_voxel_backend(frame: np.ndarray) -> np.ndarray:
49
- """Encodes and ships raw image bytes to the Modal worker."""
50
  success, encoded = cv2.imencode(".jpg", frame, [int(cv2.IMWRITE_JPEG_QUALITY), 85])
51
  if not success:
52
  return frame
53
  try:
54
- processed_bytes = voxel_backend.remote(encoded.tobytes())
 
55
  result = cv2.imdecode(np.frombuffer(processed_bytes, dtype=np.uint8), cv2.IMREAD_COLOR)
56
  return result if result is not None else frame
57
  except Exception as err:
@@ -64,9 +81,9 @@ def _run_voxel_backend(frame: np.ndarray) -> np.ndarray:
64
  # ── Core Stream Handler ─────────────────────────────────────────────────────
65
  frame_counter = 0
66
 
67
- def process_video_stream(frame: np.ndarray, mode: str, is_running: bool) -> np.ndarray:
68
  """
69
- Accepts incoming frame from the webcam, pipeline settings, and execution state flag.
70
  """
71
  global frame_counter
72
  if frame is None:
@@ -85,10 +102,10 @@ def process_video_stream(frame: np.ndarray, mode: str, is_running: bool) -> np.n
85
  # Runtime console indicator: logs transmission health to terminal every 15 frames
86
  frame_counter += 1
87
  if frame_counter % 15 == 0:
88
- print(f"🚀 [LIVE PIPELINE] Actively transmitting frames. Dispatched {frame_counter} payloads to Modal worker.")
89
 
90
- # Process via the lightweight single-image pipeline
91
- processed = _run_voxel_backend(frame)
92
 
93
  # Mode A: Full view rendering
94
  if mode == "Minecraft Filter":
@@ -126,8 +143,8 @@ with gr.Blocks(title="⛏️ Minecraft Spatial Voxel Filter") as demo:
126
  ui_logs = gr.Textbox(
127
  value="\n".join(init_logs),
128
  label="💻 System Initialization Logs",
129
- lines=6,
130
- max_lines=10,
131
  interactive=False,
132
  )
133
 
@@ -137,6 +154,23 @@ with gr.Blocks(title="⛏️ Minecraft Spatial Voxel Filter") as demo:
137
  label="🎯 Pipeline Mode",
138
  interactive=True,
139
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
 
141
  with gr.Row():
142
  start_btn = gr.Button("🚀 Start Processing", variant="primary")
@@ -151,10 +185,10 @@ with gr.Blocks(title="⛏️ Minecraft Spatial Voxel Filter") as demo:
151
  start_btn.click(fn=lambda: True, inputs=None, outputs=is_running)
152
  stop_btn.click(fn=lambda: False, inputs=None, outputs=is_running)
153
 
154
- # Core engine transmission loop
155
  input_stream.stream(
156
  fn=process_video_stream,
157
- inputs=[input_stream, mode_dropdown, is_running],
158
  outputs=[output_stream]
159
  )
160
 
 
1
+ import sys
2
+
3
+ # ── 🛠️ 0. GRADIO SCHEMAS BUG MONKEYPATCH ─────────────────────────────────────
4
+ # Fixes: TypeError: argument of type 'bool' is not iterable inside gradio_client.
5
+ # This prevents the app from crashing during initialization on Hugging Face Spaces.
6
+ try:
7
+ import gradio_client.utils
8
+ old_get_type = gradio_client.utils.get_type
9
+ def patched_get_type(schema):
10
+ if isinstance(schema, bool):
11
+ return "bool"
12
+ return old_get_type(schema)
13
+ gradio_client.utils.get_type = patched_get_type
14
+ except Exception:
15
+ pass
16
+
17
  import os
18
  import cv2
19
  import numpy as np
 
61
  return out
62
 
63
 
64
+ def _run_voxel_backend(frame: np.ndarray, prompt: str, strength: float) -> np.ndarray:
65
+ """Encodes and ships raw image bytes along with UI parameters to the Modal worker."""
66
  success, encoded = cv2.imencode(".jpg", frame, [int(cv2.IMWRITE_JPEG_QUALITY), 85])
67
  if not success:
68
  return frame
69
  try:
70
+ # Match the 3 required arguments expected by the VoxelModel class on Modal
71
+ processed_bytes = voxel_backend.remote(encoded.tobytes(), prompt, strength)
72
  result = cv2.imdecode(np.frombuffer(processed_bytes, dtype=np.uint8), cv2.IMREAD_COLOR)
73
  return result if result is not None else frame
74
  except Exception as err:
 
81
  # ── Core Stream Handler ─────────────────────────────────────────────────────
82
  frame_counter = 0
83
 
84
+ def process_video_stream(frame: np.ndarray, mode: str, is_running: bool, prompt: str, strength: float) -> np.ndarray:
85
  """
86
+ Accepts incoming frame from the webcam, pipeline settings, execution state, and prompt config.
87
  """
88
  global frame_counter
89
  if frame is None:
 
102
  # Runtime console indicator: logs transmission health to terminal every 15 frames
103
  frame_counter += 1
104
  if frame_counter % 15 == 0:
105
+ print(f"🚀 [LIVE PIPELINE] Transmitting frames. Dispatched {frame_counter} payloads to Modal.")
106
 
107
+ # Process via the single-image pipeline, passing down prompt strings and context values
108
+ processed = _run_voxel_backend(frame, prompt, strength)
109
 
110
  # Mode A: Full view rendering
111
  if mode == "Minecraft Filter":
 
143
  ui_logs = gr.Textbox(
144
  value="\n".join(init_logs),
145
  label="💻 System Initialization Logs",
146
+ lines=4,
147
+ max_lines=5,
148
  interactive=False,
149
  )
150
 
 
154
  label="🎯 Pipeline Mode",
155
  interactive=True,
156
  )
157
+
158
+ # Added controls to dynamically configure the FLUX diffusion backend
159
+ prompt_input = gr.Textbox(
160
+ value="isometric 3d minecraft block voxel style, high resolution, volumetric lighting",
161
+ label="✨ Generation Prompt",
162
+ lines=2,
163
+ interactive=True
164
+ )
165
+
166
+ strength_slider = gr.Slider(
167
+ minimum=0.1,
168
+ maximum=1.0,
169
+ value=0.45,
170
+ step=0.05,
171
+ label="🎛️ Image Transformation Strength",
172
+ interactive=True
173
+ )
174
 
175
  with gr.Row():
176
  start_btn = gr.Button("🚀 Start Processing", variant="primary")
 
185
  start_btn.click(fn=lambda: True, inputs=None, outputs=is_running)
186
  stop_btn.click(fn=lambda: False, inputs=None, outputs=is_running)
187
 
188
+ # Core engine transmission loop linked up with the new UI input arguments
189
  input_stream.stream(
190
  fn=process_video_stream,
191
+ inputs=[input_stream, mode_dropdown, is_running, prompt_input, strength_slider],
192
  outputs=[output_stream]
193
  )
194
 
backend/backend.py CHANGED
@@ -2,13 +2,26 @@ import io
2
  import os
3
  import modal
4
 
 
 
 
 
 
 
 
5
  # Define container environment
6
- image = modal.Image.debian_slim(python_version="3.12").pip_install(
7
- "diffusers",
8
- "transformers",
9
- "accelerate",
10
- "pillow",
11
- "torch"
 
 
 
 
 
 
12
  )
13
 
14
  app = modal.App("flux-klein-voxel-backend", image=image)
@@ -34,20 +47,21 @@ def demo_stream_frame(img_bytes: bytes) -> bytes:
34
  # ==============================================================================
35
  @app.cls(
36
  gpu="A10G",
37
- # Using the secret name from your example: "huggingface-secret"
38
  secrets=[modal.Secret.from_name("huggingface-secret")],
39
- max_containers=5
 
 
40
  )
41
  class VoxelModel:
42
 
43
  @modal.enter()
44
  def load_pipeline(self):
45
  import torch
 
46
  from diffusers import AutoPipelineForImage2Image
47
 
48
  model_id = "AnimeOverlord/flux2-klein-4b-mc"
49
 
50
- # Following your provided pattern for auth
51
  self.pipe = AutoPipelineForImage2Image.from_pretrained(
52
  model_id,
53
  torch_dtype=torch.bfloat16,
@@ -56,6 +70,36 @@ class VoxelModel:
56
  self.pipe.to("cuda")
57
  self.pipe.enable_attention_slicing()
58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  @modal.method()
60
  def process_frame(self, img_bytes: bytes, prompt: str, strength: float) -> bytes:
61
  from PIL import Image
 
2
  import os
3
  import modal
4
 
5
+ # ==============================================================================
6
+ # 💾 0. CACHE VOLUME SETUP
7
+ # ==============================================================================
8
+ # This creates a persistent volume that survives between container cold-starts.
9
+ cache_volume = modal.Volume.from_name("flux-inductor-cache", create_if_missing=True)
10
+ CACHE_DIR = "/root/.cache/torch/inductor"
11
+
12
  # Define container environment
13
+ image = (
14
+ modal.Image.debian_slim(python_version="3.12")
15
+ .pip_install(
16
+ "diffusers",
17
+ "transformers",
18
+ "accelerate",
19
+ "pillow",
20
+ "torch",
21
+ "triton" # Essential for torch.compile
22
+ )
23
+ # Tell PyTorch Inductor to write its cache to our persistent volume directory
24
+ .env({"TORCHINDUCTOR_CACHE_DIR": CACHE_DIR})
25
  )
26
 
27
  app = modal.App("flux-klein-voxel-backend", image=image)
 
47
  # ==============================================================================
48
  @app.cls(
49
  gpu="A10G",
 
50
  secrets=[modal.Secret.from_name("huggingface-secret")],
51
+ max_containers=5,
52
+ # Mount the persistent cache volume to the exact path PyTorch is looking at
53
+ volumes={CACHE_DIR: cache_volume}
54
  )
55
  class VoxelModel:
56
 
57
  @modal.enter()
58
  def load_pipeline(self):
59
  import torch
60
+ from PIL import Image
61
  from diffusers import AutoPipelineForImage2Image
62
 
63
  model_id = "AnimeOverlord/flux2-klein-4b-mc"
64
 
 
65
  self.pipe = AutoPipelineForImage2Image.from_pretrained(
66
  model_id,
67
  torch_dtype=torch.bfloat16,
 
70
  self.pipe.to("cuda")
71
  self.pipe.enable_attention_slicing()
72
 
73
+ # ---------------------------------------------------------
74
+ # ✨ COMPILE AND OPTIMIZE THE TRANSFORMER
75
+ # ---------------------------------------------------------
76
+ print("Initializing torch.compile on the transformer block...")
77
+
78
+ # Channels-last memory format often yields slightly faster compiled kernels
79
+ self.pipe.transformer.to(memory_format=torch.channels_last)
80
+
81
+ # Compile the heaviest part of the FLUX architecture
82
+ self.pipe.transformer = torch.compile(
83
+ self.pipe.transformer,
84
+ mode="reduce-overhead", # Trades a bit of compile time for faster inference
85
+ fullgraph=False
86
+ )
87
+
88
+ # WARMUP RUN: We force a dummy inference right now.
89
+ # If the cache is empty (first run ever), this traces the graph and saves to the Modal Volume.
90
+ # If the cache exists (subsequent cold-starts), it loads from the Volume in seconds.
91
+ print("Running warmup to build/load inductor cache...")
92
+ dummy_image = Image.new("RGB", (512, 512), (0, 0, 0))
93
+ with torch.inference_mode():
94
+ self.pipe(
95
+ prompt="warmup pass",
96
+ image=dummy_image,
97
+ strength=0.5,
98
+ num_inference_steps=2,
99
+ guidance_scale=1.0,
100
+ )
101
+ print("Warmup complete. Ready for real-time requests!")
102
+
103
  @modal.method()
104
  def process_frame(self, img_bytes: bytes, prompt: str, strength: float) -> bytes:
105
  from PIL import Image