RosticFACE commited on
Commit
d888747
·
verified ·
1 Parent(s): 5c627f7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +52 -73
app.py CHANGED
@@ -14,8 +14,6 @@ from torchao.quantization import quantize_
14
  from torchao.quantization import Float8DynamicActivationFloat8WeightConfig
15
  from torchao.quantization import Int8WeightOnlyConfig
16
 
17
- import aoti
18
-
19
 
20
  MODEL_ID = "Wan-AI/Wan2.2-I2V-A14B-Diffusers"
21
 
@@ -32,61 +30,62 @@ MIN_FRAMES_MODEL = 8
32
  MIN_DURATION = round(MIN_FRAMES_MODEL / FIXED_FPS, 1)
33
  DEFAULT_DURATION = 5.0
34
 
35
- # Модель загружается с device_map='auto' для распределения больших трансформеров
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  pipe = WanImageToVideoPipeline.from_pretrained(
37
  MODEL_ID,
38
- transformer=WanTransformer3DModel.from_pretrained(
39
- 'cbensimon/Wan2.2-I2V-A14B-bf16-Diffusers',
40
- subfolder='transformer',
41
- torch_dtype=torch.bfloat16,
42
- device_map='auto',
43
- ),
44
- transformer_2=WanTransformer3DModel.from_pretrained(
45
- 'cbensimon/Wan2.2-I2V-A14B-bf16-Diffusers',
46
- subfolder='transformer_2',
47
- torch_dtype=torch.bfloat16,
48
- device_map='auto',
49
- ),
50
  torch_dtype=torch.bfloat16,
51
  )
52
 
53
- # Загрузка и фьюзинг LoRA
 
 
54
  pipe.load_lora_weights(
55
- "Kijai/WanVideo_comfy",
56
- weight_name="Lightx2v/lightx2v_I2V_14B_480p_cfg_step_distill_rank128_bf16.safetensors",
57
  adapter_name="lightx2v"
58
  )
59
- kwargs_lora = {"load_into_transformer_2": True}
60
  pipe.load_lora_weights(
61
- "Kijai/WanVideo_comfy",
62
- weight_name="Lightx2v/lightx2v_I2V_14B_480p_cfg_step_distill_rank128_bf16.safetensors",
63
- adapter_name="lightx2v_2",
64
- **kwargs_lora
65
  )
 
66
  pipe.set_adapters(["lightx2v", "lightx2v_2"], adapter_weights=[1., 1.])
67
  pipe.fuse_lora(adapter_names=["lightx2v"], lora_scale=3., components=["transformer"])
68
  pipe.fuse_lora(adapter_names=["lightx2v_2"], lora_scale=1., components=["transformer_2"])
69
  pipe.unload_lora_weights()
70
 
71
- # Квантизация
72
  quantize_(pipe.text_encoder, Int8WeightOnlyConfig())
73
  quantize_(pipe.transformer, Float8DynamicActivationFloat8WeightConfig())
74
  quantize_(pipe.transformer_2, Float8DynamicActivationFloat8WeightConfig())
75
 
76
- # AOTI
77
- aoti.aoti_blocks_load(pipe.transformer, 'zerogpu-aoti/Wan2', variant='fp8da')
78
- aoti.aoti_blocks_load(pipe.transformer_2, 'zerogpu-aoti/Wan2', variant='fp8da')
79
 
80
- # 🟢 ИСПРАВЛЕНИЕ 1: Явно переводим пайплайн на GPU.
81
- # Это решает проблему "Cannot generate a cpu tensor from a generator of type cuda."
82
- pipe.to("cuda")
83
 
84
  default_prompt_i2v = "make this image come alive, cinematic motion, smooth animation"
85
  default_negative_prompt = (
86
  "色调艳丽, 过曝, 静态, 细节模糊不清, 字幕, 风格, 作品, 画作, 画面, 静止, "
87
- "整体发灰, 最差质量, 低质量, JPEG压缩残留, 丑陋의, 残缺的, 多余的手指, "
88
- "画得不хорошие руки, 画得不хорошие лица, 畸形の, 毀容の, 形态畸形的肢体, 手指融合, "
89
- "静止不动的画面, 杂乱的背景, 三条腿, 背景人很多, 倒着走"
90
  )
91
 
92
 
@@ -97,8 +96,8 @@ def resize_image(image: Image.Image) -> Image.Image:
97
  return image.resize((SQUARE_DIM, SQUARE_DIM), Image.LANCZOS)
98
 
99
  aspect_ratio = width / height
100
- MAX_ASPECT_RATIO = MAX_DIM / MIN_DIM
101
- MIN_ASPECT_RATIO = MIN_DIM / MAX_DIM
102
 
103
  target_w, target_h = width, height
104
  image_to_resize = image
@@ -134,28 +133,7 @@ def get_num_frames(duration_seconds: float):
134
  return 1 + int(round(duration_seconds * FIXED_FPS))
135
 
136
 
137
- def get_duration(
138
- input_image,
139
- prompt,
140
- steps,
141
- negative_prompt,
142
- duration_seconds,
143
- guidance_scale,
144
- guidance_scale_2,
145
- seed,
146
- randomize_seed,
147
- progress,
148
- ):
149
- BASE_FRAMES_HEIGHT_WIDTH = 81 * 832 * 624
150
- BASE_STEP_DURATION = 15
151
- width, height = resize_image(input_image).size
152
- frames = get_num_frames(duration_seconds)
153
- factor = frames * width * height / BASE_FRAMES_HEIGHT_WIDTH
154
- step_duration = BASE_STEP_DURATION * factor ** 1.5
155
- return 10 + int(steps) * step_duration
156
-
157
-
158
- @spaces.GPU(duration=get_duration)
159
  def generate_video(
160
  input_image,
161
  prompt,
@@ -163,20 +141,20 @@ def generate_video(
163
  negative_prompt=default_negative_prompt,
164
  duration_seconds=DEFAULT_DURATION,
165
  guidance_scale=1,
166
- guidance_scale_2=1,
167
  seed=42,
168
  randomize_seed=False,
169
  progress=gr.Progress(track_tqdm=True),
170
  ):
171
  if input_image is None:
172
  raise gr.Error("Please upload an input image.")
173
-
174
  num_frames = get_num_frames(duration_seconds)
175
  current_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
176
  resized_image = resize_image(input_image)
177
 
178
- # 🟢 ИСПРАВЛЕНИЕ 2: Удален аргумент 'device="cuda"', чтобы избежать TypeError,
179
- # так как пайплайн уже был переведен на CUDA перед функцией.
180
  output_frames_list = pipe(
181
  image=resized_image,
182
  prompt=prompt,
@@ -187,7 +165,7 @@ def generate_video(
187
  guidance_scale=float(guidance_scale),
188
  guidance_scale_2=float(guidance_scale_2),
189
  num_inference_steps=int(steps),
190
- generator=torch.Generator(device="cuda").manual_seed(current_seed),
191
  ).frames[0]
192
 
193
  with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmpfile:
@@ -197,11 +175,11 @@ def generate_video(
197
  return video_path, current_seed
198
 
199
 
200
- # --- Gradio Interface ---
201
  with gr.Blocks() as demo:
202
- gr.Markdown("# 🚀 Wan 2.2 I2V (14B) — Unlimited Duration Edition 🕒")
203
- gr.Markdown("Generate cinematic I2V animations without duration limits. Optimized for ZeroCPU.")
204
-
205
  with gr.Row():
206
  with gr.Column():
207
  input_image_component = gr.Image(type="pil", label="Input Image")
@@ -213,21 +191,21 @@ with gr.Blocks() as demo:
213
  step=0.5,
214
  value=DEFAULT_DURATION,
215
  label="Duration (seconds)",
216
- info=f"Each second = {FIXED_FPS} frames. Longer videos require more VRAM/time."
217
  )
218
-
219
  with gr.Accordion("Advanced Settings", open=False):
220
  negative_prompt_input = gr.Textbox(label="Negative Prompt", value=default_negative_prompt, lines=3)
221
  seed_input = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=42, interactive=True)
222
  randomize_seed_checkbox = gr.Checkbox(label="Randomize seed", value=True, interactive=True)
223
- steps_slider = gr.Slider(minimum=1, maximum=30, step=1, value=6, label="Inference Steps")
224
- guidance_scale_input = gr.Slider(minimum=0.0, maximum=10.0, step=0.5, value=1, label="Guidance Scale - high noise stage")
225
- guidance_scale_2_input = gr.Slider(minimum=0.0, maximum=10.0, step=0.5, value=1, label="Guidance Scale 2 - low noise stage")
226
 
227
  generate_button = gr.Button("Generate Video", variant="primary")
 
228
  with gr.Column():
229
  video_output = gr.Video(label="Generated Video", autoplay=True, interactive=False)
230
-
231
  ui_inputs = [
232
  input_image_component, prompt_input, steps_slider,
233
  negative_prompt_input, duration_seconds_input,
@@ -236,5 +214,6 @@ with gr.Blocks() as demo:
236
  ]
237
  generate_button.click(fn=generate_video, inputs=ui_inputs, outputs=[video_output, seed_input])
238
 
 
239
  if __name__ == "__main__":
240
- demo.queue().launch(mcp_server=True)
 
14
  from torchao.quantization import Float8DynamicActivationFloat8WeightConfig
15
  from torchao.quantization import Int8WeightOnlyConfig
16
 
 
 
17
 
18
  MODEL_ID = "Wan-AI/Wan2.2-I2V-A14B-Diffusers"
19
 
 
30
  MIN_DURATION = round(MIN_FRAMES_MODEL / FIXED_FPS, 1)
31
  DEFAULT_DURATION = 5.0
32
 
33
+ print("⬇️ Loading models...")
34
+
35
+ # ---- LOAD TRANSFORMERS ON CUDA ----
36
+ transformer = WanTransformer3DModel.from_pretrained(
37
+ 'cbensimon/Wan2.2-I2V-A14B-bf16-Diffusers',
38
+ subfolder='transformer',
39
+ torch_dtype=torch.bfloat16,
40
+ ).to("cuda")
41
+
42
+ transformer_2 = WanTransformer3DModel.from_pretrained(
43
+ 'cbensimon/Wan2.2-I2V-A14B-bf16-Diffusers',
44
+ subfolder='transformer_2',
45
+ torch_dtype=torch.bfloat16,
46
+ ).to("cuda")
47
+
48
  pipe = WanImageToVideoPipeline.from_pretrained(
49
  MODEL_ID,
50
+ transformer=transformer,
51
+ transformer_2=transformer_2,
 
 
 
 
 
 
 
 
 
 
52
  torch_dtype=torch.bfloat16,
53
  )
54
 
55
+ pipe.to("cuda")
56
+
57
+ # ---- LORA ----
58
  pipe.load_lora_weights(
59
+ "Kijai/WanVideo_comfy",
60
+ weight_name="Lightx2v/lightx2v_I2V_14B_480p_cfg_step_distill_rank128_bf16.safetensors",
61
  adapter_name="lightx2v"
62
  )
63
+
64
  pipe.load_lora_weights(
65
+ "Kijai/WanVideo_comfy",
66
+ weight_name="Lightx2v/lightx2v_I2V_14B_480p_cfg_step_distill_rank128_bf16.safetensors",
67
+ adapter_name="lightx2v_2",
68
+ load_into_transformer_2=True
69
  )
70
+
71
  pipe.set_adapters(["lightx2v", "lightx2v_2"], adapter_weights=[1., 1.])
72
  pipe.fuse_lora(adapter_names=["lightx2v"], lora_scale=3., components=["transformer"])
73
  pipe.fuse_lora(adapter_names=["lightx2v_2"], lora_scale=1., components=["transformer_2"])
74
  pipe.unload_lora_weights()
75
 
76
+ # ---- QUANT ----
77
  quantize_(pipe.text_encoder, Int8WeightOnlyConfig())
78
  quantize_(pipe.transformer, Float8DynamicActivationFloat8WeightConfig())
79
  quantize_(pipe.transformer_2, Float8DynamicActivationFloat8WeightConfig())
80
 
81
+ print("✔️ Loaded successfully!")
 
 
82
 
 
 
 
83
 
84
  default_prompt_i2v = "make this image come alive, cinematic motion, smooth animation"
85
  default_negative_prompt = (
86
  "色调艳丽, 过曝, 静态, 细节模糊不清, 字幕, 风格, 作品, 画作, 画面, 静止, "
87
+ "整体发灰, 最差质量, 低质量, JPEG压缩残留, 丑陋, 残缺的, 多余的手指, "
88
+ "画得不好 руки, 畸形, 毀容, 多余的 конечности, 手指融合, 静止不动的画面"
 
89
  )
90
 
91
 
 
96
  return image.resize((SQUARE_DIM, SQUARE_DIM), Image.LANCZOS)
97
 
98
  aspect_ratio = width / height
99
+ MAX_ASPECT_RATIO = MAX_DIM / MIN_DIM
100
+ MIN_ASPECT_RATIO = MIN_DIM / MAX_DIM
101
 
102
  target_w, target_h = width, height
103
  image_to_resize = image
 
133
  return 1 + int(round(duration_seconds * FIXED_FPS))
134
 
135
 
136
+ @spaces.GPU()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  def generate_video(
138
  input_image,
139
  prompt,
 
141
  negative_prompt=default_negative_prompt,
142
  duration_seconds=DEFAULT_DURATION,
143
  guidance_scale=1,
144
+ guidance_scale_2=1,
145
  seed=42,
146
  randomize_seed=False,
147
  progress=gr.Progress(track_tqdm=True),
148
  ):
149
  if input_image is None:
150
  raise gr.Error("Please upload an input image.")
151
+
152
  num_frames = get_num_frames(duration_seconds)
153
  current_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
154
  resized_image = resize_image(input_image)
155
 
156
+ generator = torch.cuda.manual_seed(current_seed)
157
+
158
  output_frames_list = pipe(
159
  image=resized_image,
160
  prompt=prompt,
 
165
  guidance_scale=float(guidance_scale),
166
  guidance_scale_2=float(guidance_scale_2),
167
  num_inference_steps=int(steps),
168
+ generator=generator,
169
  ).frames[0]
170
 
171
  with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmpfile:
 
175
  return video_path, current_seed
176
 
177
 
178
+ # ---- GRADIO UI ----
179
  with gr.Blocks() as demo:
180
+ gr.Markdown("# 🚀 Wan 2.2 I2V (14B) — HF CUDA Edition")
181
+ gr.Markdown("Generate cinematic I2V animations without ZeroGPU fully optimized for Nvidia L40S.")
182
+
183
  with gr.Row():
184
  with gr.Column():
185
  input_image_component = gr.Image(type="pil", label="Input Image")
 
191
  step=0.5,
192
  value=DEFAULT_DURATION,
193
  label="Duration (seconds)",
 
194
  )
195
+
196
  with gr.Accordion("Advanced Settings", open=False):
197
  negative_prompt_input = gr.Textbox(label="Negative Prompt", value=default_negative_prompt, lines=3)
198
  seed_input = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=42, interactive=True)
199
  randomize_seed_checkbox = gr.Checkbox(label="Randomize seed", value=True, interactive=True)
200
+ steps_slider = gr.Slider(minimum=1, maximum=30, step=1, value=6, label="Inference Steps")
201
+ guidance_scale_input = gr.Slider(minimum=0.0, maximum=10.0, step=0.5, value=1, label="Guidance Scale")
202
+ guidance_scale_2_input = gr.Slider(minimum=0.0, maximum=10.0, step=0.5, value=1, label="Guidance Scale 2")
203
 
204
  generate_button = gr.Button("Generate Video", variant="primary")
205
+
206
  with gr.Column():
207
  video_output = gr.Video(label="Generated Video", autoplay=True, interactive=False)
208
+
209
  ui_inputs = [
210
  input_image_component, prompt_input, steps_slider,
211
  negative_prompt_input, duration_seconds_input,
 
214
  ]
215
  generate_button.click(fn=generate_video, inputs=ui_inputs, outputs=[video_output, seed_input])
216
 
217
+
218
  if __name__ == "__main__":
219
+ demo.queue().launch()