multimodalart HF Staff commited on
Commit
939c549
·
verified ·
1 Parent(s): ecc3183

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +123 -72
app.py CHANGED
@@ -6,42 +6,43 @@ import datetime
6
  import numpy as np
7
  from PIL import Image
8
  import imageio
9
- import spaces
10
 
11
  # --- Part 1: Auto-Setup (Clone Repo & Download Weights) ---
12
 
13
  REPO_URL = "https://github.com/Tencent-Hunyuan/HunyuanVideo-1.5.git"
14
  REPO_DIR = os.path.abspath("HunyuanVideo-1.5")
15
- # Use Absolute Path to ensure the loader finds the folder
16
- MODEL_DIR = os.path.abspath("ckpts")
17
- HF_REPO_ID = "tencent/HunyuanVideo-1.5"
 
 
18
 
19
  # Configuration
20
  TRANSFORMER_VERSION = "480p_i2v_distilled"
21
  DTYPE = torch.bfloat16
22
- ENABLE_OFFLOADING = True
 
23
 
24
  def setup_environment():
25
- """Clones the repo and downloads weights if they don't exist."""
26
  print("=" * 50)
27
  print("Checking Environment & Dependencies...")
28
 
29
- # 1. Clone Repository
30
  if not os.path.exists(REPO_DIR):
31
  print(f"Cloning repository to {REPO_DIR}...")
32
  subprocess.run(["git", "clone", REPO_URL, REPO_DIR], check=True)
33
- else:
34
- print(f"Repository exists at {REPO_DIR}")
35
 
36
  # 2. Add Repo to Python Path
37
  if REPO_DIR not in sys.path:
38
  sys.path.insert(0, REPO_DIR)
39
 
40
- # 3. Download Weights
41
- # Check if key folders exist to verify download
42
- transformer_path = os.path.join(MODEL_DIR, "transformer", TRANSFORMER_VERSION)
43
- if not os.path.exists(transformer_path):
44
- print(f"Downloading weights to {MODEL_DIR}...")
 
45
  try:
46
  from huggingface_hub import snapshot_download
47
  allow_patterns = [
@@ -53,92 +54,127 @@ def setup_environment():
53
  "tokenizer/*"
54
  ]
55
  snapshot_download(
56
- repo_id=HF_REPO_ID,
57
  local_dir=MODEL_DIR,
58
- allow_patterns=allow_patterns
 
59
  )
60
- print("Download complete.")
61
  except Exception as e:
62
- print(f"Error downloading weights: {e}")
63
  sys.exit(1)
64
- else:
65
- print(f"Weights found in {MODEL_DIR}")
66
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  print("Environment Ready.")
68
  print("=" * 50)
69
 
70
- # Run setup immediately
71
  setup_environment()
72
 
73
- # --- Part 2: Imports from Cloned Repo ---
74
-
75
- # Set Env Vars for HyVideo
76
- if 'PYTORCH_CUDA_ALLOC_CONF' not in os.environ:
77
- os.environ['PYTORCH_CUDA_ALLOC_CONF'] = 'expandable_segments:True'
78
- # Even for single GPU, HyVideo code expects these env vars to be set
79
- os.environ['RANK'] = '0'
80
- os.environ['WORLD_SIZE'] = '1'
81
 
 
82
  try:
83
- from hyvideo.pipelines.hunyuan_video_pipeline import HunyuanVideo_1_5_Pipeline
84
- from hyvideo.commons.infer_state import initialize_infer_state
85
- # Import module for patching
86
  import hyvideo.commons
87
  import hyvideo.pipelines.hunyuan_video_pipeline
 
 
 
88
  except ImportError as e:
89
  print(f"CRITICAL ERROR: {e}")
90
  sys.exit(1)
91
 
92
  import gradio as gr
93
 
 
 
94
  def dummy_get_gpu_memory(device=None):
95
- # Return 40GB (in bytes) to trick the config loader into
96
- # thinking we have a high-end GPU, allowing it to select
97
- # optimal inference params without triggering torch.cuda.init()
98
- return 68 * 1024 * 1024 * 1024
99
 
100
- print("🛠️ Applying ZeroGPU Monkey Patch to hyvideo.commons.get_gpu_memory...")
101
  hyvideo.commons.get_gpu_memory = dummy_get_gpu_memory
102
  hyvideo.pipelines.hunyuan_video_pipeline.get_gpu_memory = dummy_get_gpu_memory
103
 
104
- # --- Part 3: Model Initialization (Pre-Load) ---
105
 
106
- # Mock args for inference configuration (required by internal logic)
107
  class ArgsNamespace:
108
  def __init__(self):
109
  self.use_sageattn = False
110
  self.sage_blocks_range = "0-53"
111
  self.enable_torch_compile = False
112
 
113
- # Initialize internal state mock
114
  initialize_infer_state(ArgsNamespace())
115
 
116
- # Global Pipeline Variable
117
  pipe = None
118
 
119
- # Double check path exists
120
- if not os.path.isdir(MODEL_DIR):
121
- print(f" Error: Model directory not found at {MODEL_DIR}")
122
- sys.exit(1)
123
-
124
- print(f"⏳ Initializing Pipeline ({TRANSFORMER_VERSION}) from {MODEL_DIR}...")
125
-
126
- try:
127
- pipe = HunyuanVideo_1_5_Pipeline.create_pipeline(
128
- pretrained_model_name_or_path=MODEL_DIR,
129
- transformer_version=TRANSFORMER_VERSION,
130
- enable_offloading=ENABLE_OFFLOADING,
131
- enable_group_offloading=ENABLE_OFFLOADING,
132
- transformer_dtype=DTYPE,
133
- )
134
- print(" Model loaded successfully!")
135
- except Exception as e:
136
- print(f"❌ Failed to load model: {e}")
137
- import traceback
138
- traceback.print_exc()
139
- sys.exit(1)
140
-
141
- pipe.to("cuda")
142
 
143
  def save_video_tensor(video_tensor, path, fps=24):
144
  if isinstance(video_tensor, list): video_tensor = video_tensor[0]
@@ -147,6 +183,8 @@ def save_video_tensor(video_tensor, path, fps=24):
147
  vid = vid.permute(1, 2, 3, 0).cpu().numpy()
148
  imageio.mimwrite(path, vid, fps=fps)
149
 
 
 
150
  @spaces.GPU(duration=120)
151
  def generate(input_image, prompt, length, steps, shift, seed, guidance):
152
  if pipe is None:
@@ -161,11 +199,17 @@ def generate(input_image, prompt, length, steps, shift, seed, guidance):
161
  if seed == -1: seed = torch.randint(0, 1000000, (1,)).item()
162
  generator = torch.Generator(device="cpu").manual_seed(int(seed))
163
 
164
- print(f"Generating: {prompt} | Seed: {seed}")
165
-
166
  try:
 
 
 
 
 
167
  pipe.execution_device = torch.device("cuda")
168
-
 
169
  output = pipe(
170
  prompt=prompt,
171
  height=480, width=854, aspect_ratio="16:9",
@@ -180,28 +224,35 @@ def generate(input_image, prompt, length, steps, shift, seed, guidance):
180
  enable_sr=False,
181
  return_dict=True
182
  )
 
 
 
 
183
  except Exception as e:
 
 
 
184
  raise gr.Error(f"Inference Failed: {e}")
185
 
186
  timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
187
  os.makedirs("outputs", exist_ok=True)
188
  output_path = f"outputs/gen_{timestamp}.mp4"
189
  save_video_tensor(output.videos, output_path)
190
-
191
  return output_path
192
 
193
- # --- Part 4: UI Definition & Launch ---
194
 
195
  def create_ui():
196
  with gr.Blocks(title="HunyuanVideo 1.5 I2V") as demo:
197
  gr.Markdown(f"### 🎬 HunyuanVideo 1.5 I2V ({TRANSFORMER_VERSION})")
 
198
 
199
  with gr.Row():
200
  with gr.Column():
201
  img = gr.Image(label="Reference", type="pil", height=250)
202
  prompt = gr.Textbox(label="Prompt", placeholder="Describe motion...", lines=2)
203
  with gr.Row():
204
- steps = gr.Slider(2, 20, value=6, step=1, label="Steps")
205
  guidance = gr.Slider(1.0, 5.0, value=1.0, step=0.1, label="Guidance")
206
  with gr.Row():
207
  shift = gr.Slider(1.0, 20.0, value=5.0, step=0.5, label="Shift")
@@ -216,6 +267,6 @@ def create_ui():
216
  return demo
217
 
218
  if __name__ == "__main__":
219
- # 2. Launch UI
220
  ui = create_ui()
221
  ui.queue().launch(server_name="0.0.0.0", share=True)
 
6
  import numpy as np
7
  from PIL import Image
8
  import imageio
9
+ import shutil
10
 
11
  # --- Part 1: Auto-Setup (Clone Repo & Download Weights) ---
12
 
13
  REPO_URL = "https://github.com/Tencent-Hunyuan/HunyuanVideo-1.5.git"
14
  REPO_DIR = os.path.abspath("HunyuanVideo-1.5")
15
+ MODEL_DIR = os.path.abspath("ckpts")
16
+
17
+ # Repositories
18
+ HF_MAIN_REPO = "tencent/HunyuanVideo-1.5"
19
+ HF_GLYPH_REPO = "multimodalart/glyph-sdxl-v2-byt5-small"
20
 
21
  # Configuration
22
  TRANSFORMER_VERSION = "480p_i2v_distilled"
23
  DTYPE = torch.bfloat16
24
+ # ZeroGPU: Set False so we control offloading manually (CPU -> GPU -> CPU)
25
+ ENABLE_OFFLOADING = False
26
 
27
  def setup_environment():
 
28
  print("=" * 50)
29
  print("Checking Environment & Dependencies...")
30
 
31
+ # 1. Clone Code Repository
32
  if not os.path.exists(REPO_DIR):
33
  print(f"Cloning repository to {REPO_DIR}...")
34
  subprocess.run(["git", "clone", REPO_URL, REPO_DIR], check=True)
 
 
35
 
36
  # 2. Add Repo to Python Path
37
  if REPO_DIR not in sys.path:
38
  sys.path.insert(0, REPO_DIR)
39
 
40
+ # 3. Download Main Weights
41
+ os.makedirs(MODEL_DIR, exist_ok=True)
42
+ target_transformer = os.path.join(MODEL_DIR, "transformer", TRANSFORMER_VERSION)
43
+
44
+ if not os.path.exists(target_transformer):
45
+ print(f"Downloading Main Weights from {HF_MAIN_REPO}...")
46
  try:
47
  from huggingface_hub import snapshot_download
48
  allow_patterns = [
 
54
  "tokenizer/*"
55
  ]
56
  snapshot_download(
57
+ repo_id=HF_MAIN_REPO,
58
  local_dir=MODEL_DIR,
59
+ allow_patterns=allow_patterns,
60
+ local_dir_use_symlinks=False
61
  )
 
62
  except Exception as e:
63
+ print(f"Error downloading main weights: {e}")
64
  sys.exit(1)
65
+
66
+ # 4. Download & Restructure Glyph Weights
67
+ # The pipeline expects: ckpts/text_encoder/Glyph-SDXL-v2/checkpoints/byt5_model.pt
68
+ glyph_root = os.path.join(MODEL_DIR, "text_encoder", "Glyph-SDXL-v2")
69
+ glyph_ckpt_target = os.path.join(glyph_root, "checkpoints", "byt5_model.pt")
70
+
71
+ if not os.path.exists(glyph_ckpt_target):
72
+ print(f"Downloading & Structuring Glyph Weights from {HF_GLYPH_REPO}...")
73
+ try:
74
+ from huggingface_hub import snapshot_download
75
+ # Download to a temp folder first
76
+ glyph_temp = os.path.join(MODEL_DIR, "glyph_temp")
77
+ snapshot_download(
78
+ repo_id=HF_GLYPH_REPO,
79
+ local_dir=glyph_temp,
80
+ local_dir_use_symlinks=False
81
+ )
82
+
83
+ # Create target structure
84
+ os.makedirs(os.path.join(glyph_root, "assets"), exist_ok=True)
85
+ os.makedirs(os.path.join(glyph_root, "checkpoints"), exist_ok=True)
86
+
87
+ # Move Assets (color_idx.json, etc.)
88
+ src_assets = os.path.join(glyph_temp, "assets")
89
+ if os.path.exists(src_assets):
90
+ for f in os.listdir(src_assets):
91
+ shutil.copy(os.path.join(src_assets, f), os.path.join(glyph_root, "assets", f))
92
+
93
+ # Move & Rename Model (pytorch_model.bin -> byt5_model.pt)
94
+ # Try bin first, then safetensors (code usually loads via torch.load, so bin/pt is safer)
95
+ src_bin = os.path.join(glyph_temp, "pytorch_model.bin")
96
+ if os.path.exists(src_bin):
97
+ print(" moving pytorch_model.bin -> byt5_model.pt")
98
+ shutil.move(src_bin, glyph_ckpt_target)
99
+ else:
100
+ # Fallback if repo changes structure
101
+ print("Warning: pytorch_model.bin not found, looking for safetensors...")
102
+ src_safe = os.path.join(glyph_temp, "model.safetensors")
103
+ if os.path.exists(src_safe):
104
+ # Note: Standard torch.load might fail on safetensors if code expects pickle,
105
+ # but let's try.
106
+ shutil.move(src_safe, glyph_ckpt_target)
107
+
108
+ # Clean up temp
109
+ shutil.rmtree(glyph_temp, ignore_errors=True)
110
+ print("Glyph setup complete.")
111
+
112
+ except Exception as e:
113
+ print(f"Error setting up Glyph weights: {e}")
114
+ # Don't exit, maybe the model can run without it if config tweaked,
115
+ # but likely it will fail later.
116
+ pass
117
+
118
  print("Environment Ready.")
119
  print("=" * 50)
120
 
 
121
  setup_environment()
122
 
123
+ # --- Part 2: Imports & Monkey Patching ---
 
 
 
 
 
 
 
124
 
125
+ # 1. Import Modules explicitly for patching
126
  try:
 
 
 
127
  import hyvideo.commons
128
  import hyvideo.pipelines.hunyuan_video_pipeline
129
+ from hyvideo.pipelines.hunyuan_video_pipeline import HunyuanVideo_1_5_Pipeline
130
+ from hyvideo.commons.infer_state import initialize_infer_state
131
+ import spaces
132
  except ImportError as e:
133
  print(f"CRITICAL ERROR: {e}")
134
  sys.exit(1)
135
 
136
  import gradio as gr
137
 
138
+ # 2. Apply ZeroGPU Monkey Patch
139
+ # We must patch the specific modules where get_gpu_memory is imported/used
140
  def dummy_get_gpu_memory(device=None):
141
+ return 80 * 1024 * 1024 * 1024 # Spoof 80GB
 
 
 
142
 
143
+ print("🛠️ Applying ZeroGPU Monkey Patch...")
144
  hyvideo.commons.get_gpu_memory = dummy_get_gpu_memory
145
  hyvideo.pipelines.hunyuan_video_pipeline.get_gpu_memory = dummy_get_gpu_memory
146
 
147
+ # --- Part 3: Model Initialization (CPU) ---
148
 
 
149
  class ArgsNamespace:
150
  def __init__(self):
151
  self.use_sageattn = False
152
  self.sage_blocks_range = "0-53"
153
  self.enable_torch_compile = False
154
 
 
155
  initialize_infer_state(ArgsNamespace())
156
 
 
157
  pipe = None
158
 
159
+ def pre_load_model():
160
+ global pipe
161
+ print(f" Initializing Pipeline ({TRANSFORMER_VERSION})...")
162
+ try:
163
+ # Load to CPU explicitly
164
+ pipe = HunyuanVideo_1_5_Pipeline.create_pipeline(
165
+ pretrained_model_name_or_path=MODEL_DIR,
166
+ transformer_version=TRANSFORMER_VERSION,
167
+ enable_offloading=ENABLE_OFFLOADING,
168
+ enable_group_offloading=ENABLE_OFFLOADING,
169
+ transformer_dtype=DTYPE,
170
+ device=torch.device('cpu')
171
+ )
172
+ print("✅ Model loaded into CPU RAM.")
173
+ except Exception as e:
174
+ print(f" Failed to load model: {e}")
175
+ import traceback
176
+ traceback.print_exc()
177
+ sys.exit(1)
 
 
 
 
178
 
179
  def save_video_tensor(video_tensor, path, fps=24):
180
  if isinstance(video_tensor, list): video_tensor = video_tensor[0]
 
183
  vid = vid.permute(1, 2, 3, 0).cpu().numpy()
184
  imageio.mimwrite(path, vid, fps=fps)
185
 
186
+ # --- Part 4: Inference ---
187
+
188
  @spaces.GPU(duration=120)
189
  def generate(input_image, prompt, length, steps, shift, seed, guidance):
190
  if pipe is None:
 
199
  if seed == -1: seed = torch.randint(0, 1000000, (1,)).item()
200
  generator = torch.Generator(device="cpu").manual_seed(int(seed))
201
 
202
+ print(f"🚀 Moving Pipeline to GPU... (Prompt: {prompt})")
203
+
204
  try:
205
+ # 1. Move Weights
206
+ pipe.to("cuda")
207
+
208
+ # 2. FIX: Manually update internal device reference
209
+ # (Hunyuan uses this attribute instead of .device in some places)
210
  pipe.execution_device = torch.device("cuda")
211
+
212
+ # 3. Run Inference
213
  output = pipe(
214
  prompt=prompt,
215
  height=480, width=854, aspect_ratio="16:9",
 
224
  enable_sr=False,
225
  return_dict=True
226
  )
227
+
228
+ # 4. Optional: Move back to CPU?
229
+ # pipe.to("cpu")
230
+
231
  except Exception as e:
232
+ print(f"Generation Error: {e}")
233
+ import traceback
234
+ traceback.print_exc()
235
  raise gr.Error(f"Inference Failed: {e}")
236
 
237
  timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
238
  os.makedirs("outputs", exist_ok=True)
239
  output_path = f"outputs/gen_{timestamp}.mp4"
240
  save_video_tensor(output.videos, output_path)
 
241
  return output_path
242
 
243
+ # --- Part 5: UI ---
244
 
245
  def create_ui():
246
  with gr.Blocks(title="HunyuanVideo 1.5 I2V") as demo:
247
  gr.Markdown(f"### 🎬 HunyuanVideo 1.5 I2V ({TRANSFORMER_VERSION})")
248
+ gr.Markdown("Running on ZeroGPU. Weights are pre-loaded on CPU.")
249
 
250
  with gr.Row():
251
  with gr.Column():
252
  img = gr.Image(label="Reference", type="pil", height=250)
253
  prompt = gr.Textbox(label="Prompt", placeholder="Describe motion...", lines=2)
254
  with gr.Row():
255
+ steps = gr.Slider(2, 50, value=6, step=1, label="Steps")
256
  guidance = gr.Slider(1.0, 5.0, value=1.0, step=0.1, label="Guidance")
257
  with gr.Row():
258
  shift = gr.Slider(1.0, 20.0, value=5.0, step=0.5, label="Shift")
 
267
  return demo
268
 
269
  if __name__ == "__main__":
270
+ pre_load_model()
271
  ui = create_ui()
272
  ui.queue().launch(server_name="0.0.0.0", share=True)