Files changed (1) hide show
  1. app_lora.py +237 -0
app_lora.py ADDED
@@ -0,0 +1,237 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import types
2
+ import random
3
+ import spaces
4
+
5
+ import torch
6
+ import numpy as np
7
+ from diffusers import AutoencoderKLWan, UniPCMultistepScheduler
8
+ from diffusers.utils import export_to_video
9
+ from diffusers import AutoModel
10
+ import gradio as gr
11
+ import tempfile
12
+ from huggingface_hub import hf_hub_download
13
+
14
+ from src.pipeline_wan_nag import NAGWanPipeline
15
+ from src.transformer_wan_nag import NagWanTransformer3DModel
16
+
17
+
18
+ MOD_VALUE = 32
19
+ DEFAULT_DURATION_SECONDS = 4
20
+ DEFAULT_STEPS = 4
21
+ DEFAULT_SEED = 2025
22
+ DEFAULT_H_SLIDER_VALUE = 480
23
+ DEFAULT_W_SLIDER_VALUE = 832
24
+ NEW_FORMULA_MAX_AREA = 480.0 * 832.0
25
+
26
+ SLIDER_MIN_H, SLIDER_MAX_H = 128, 896
27
+ SLIDER_MIN_W, SLIDER_MAX_W = 128, 896
28
+ MAX_SEED = np.iinfo(np.int32).max
29
+
30
+ FIXED_FPS = 16
31
+ MIN_FRAMES_MODEL = 8
32
+ MAX_FRAMES_MODEL = 81
33
+
34
+ DEFAULT_NAG_NEGATIVE_PROMPT = "Static, motionless, still, ugly, bad quality, worst quality, poorly drawn, low resolution, blurry, lack of details"
35
+
36
+ MODEL_ID = "Wan-AI/Wan2.1-T2V-14B-Diffusers"
37
+ SUB_MODEL_ID = "vrgamedevgirl84/Wan14BT2VFusioniX"
38
+ SUB_MODEL_FILENAME = "Wan14BT2VFusioniX_fp16_.safetensors"
39
+ LORA_REPO_ID = "Kijai/WanVideo_comfy"
40
+ LORA_FILENAME = "Wan21_CausVid_14B_T2V_lora_rank32.safetensors"
41
+
42
+ vae = AutoencoderKLWan.from_pretrained(MODEL_ID, subfolder="vae", torch_dtype=torch.float32)
43
+ wan_path = hf_hub_download(repo_id=SUB_MODEL_ID, filename=SUB_MODEL_FILENAME)
44
+ transformer = NagWanTransformer3DModel.from_single_file(wan_path, torch_dtype=torch.bfloat16)
45
+ pipe = NAGWanPipeline.from_pretrained(
46
+ MODEL_ID, vae=vae, transformer=transformer, torch_dtype=torch.bfloat16
47
+ )
48
+ pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config, flow_shift=5.0)
49
+ pipe.to("cuda")
50
+
51
+ #causvid_path = hf_hub_download(repo_id=LORA_REPO_ID, filename=LORA_FILENAME)
52
+ #pipe.load_lora_weights(causvid_path, adapter_name="causvid_lora")
53
+ #pipe.set_adapters(["causvid_lora"], adapter_weights=[0.95])
54
+ #for name, param in pipe.transformer.named_parameters():
55
+ # if "lora_B" in name:
56
+ # if "blocks.0" in name:
57
+ # param.data = param.data * 0.25
58
+ #pipe.fuse_lora()
59
+ #pipe.unload_lora_weights()
60
+
61
+ pipe.transformer.__class__.attn_processors = NagWanTransformer3DModel.attn_processors
62
+ pipe.transformer.__class__.set_attn_processor = NagWanTransformer3DModel.set_attn_processor
63
+ pipe.transformer.__class__.forward = NagWanTransformer3DModel.forward
64
+
65
+ examples = [
66
+ ["A ginger cat passionately plays eletric guitar with intensity and emotion on a stage. The background is shrouded in deep darkness. Spotlights casts dramatic shadows.", DEFAULT_NAG_NEGATIVE_PROMPT, 11],
67
+ ["A red vintage Porsche convertible flying over a rugged coastal cliff. Monstrous waves violently crashing against the rocks below. A lighthouse stands tall atop the cliff.", DEFAULT_NAG_NEGATIVE_PROMPT, 11],
68
+ ["Enormous glowing jellyfish float slowly across a sky filled with soft clouds. Their tentacles shimmer with iridescent light as they drift above a peaceful mountain landscape. Magical and dreamlike, captured in a wide shot. Surreal realism style with detailed textures.", DEFAULT_NAG_NEGATIVE_PROMPT, 11],
69
+ ]
70
+
71
+
72
+ def get_duration(
73
+ prompt,
74
+ nag_negative_prompt, nag_scale,
75
+ height, width, duration_seconds,
76
+ steps,
77
+ seed, randomize_seed,
78
+ compare,
79
+ ):
80
+ duration = int(duration_seconds) * int(steps) * 2.25 + 5
81
+ if compare:
82
+ duration *= 2
83
+ return duration
84
+
85
+ @spaces.GPU(duration=get_duration)
86
+ def generate_video(
87
+ prompt,
88
+ nag_negative_prompt, nag_scale,
89
+ height=DEFAULT_H_SLIDER_VALUE, width=DEFAULT_W_SLIDER_VALUE, duration_seconds=DEFAULT_DURATION_SECONDS,
90
+ steps=DEFAULT_STEPS,
91
+ seed=DEFAULT_SEED, randomize_seed=False,
92
+ compare=True,
93
+ ):
94
+ target_h = max(MOD_VALUE, (int(height) // MOD_VALUE) * MOD_VALUE)
95
+ target_w = max(MOD_VALUE, (int(width) // MOD_VALUE) * MOD_VALUE)
96
+
97
+ num_frames = np.clip(int(round(int(duration_seconds) * FIXED_FPS) + 1), MIN_FRAMES_MODEL, MAX_FRAMES_MODEL)
98
+
99
+ current_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
100
+
101
+ with torch.inference_mode():
102
+ nag_output_frames_list = pipe(
103
+ prompt=prompt,
104
+ nag_negative_prompt=nag_negative_prompt,
105
+ nag_scale=nag_scale,
106
+ nag_tau=3.5,
107
+ nag_alpha=0.5,
108
+ height=target_h, width=target_w, num_frames=num_frames,
109
+ guidance_scale=0.,
110
+ num_inference_steps=int(steps),
111
+ generator=torch.Generator(device="cuda").manual_seed(current_seed)
112
+ ).frames[0]
113
+
114
+ with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmpfile:
115
+ nag_video_path = tmpfile.name
116
+ export_to_video(nag_output_frames_list, nag_video_path, fps=FIXED_FPS)
117
+
118
+ if compare:
119
+ baseline_output_frames_list = pipe(
120
+ prompt=prompt,
121
+ nag_negative_prompt=nag_negative_prompt,
122
+ height=target_h, width=target_w, num_frames=num_frames,
123
+ guidance_scale=0.,
124
+ num_inference_steps=int(steps),
125
+ generator=torch.Generator(device="cuda").manual_seed(current_seed)
126
+ ).frames[0]
127
+
128
+ with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmpfile:
129
+ baseline_video_path = tmpfile.name
130
+ export_to_video(baseline_output_frames_list, baseline_video_path, fps=FIXED_FPS)
131
+ else:
132
+ baseline_video_path = None
133
+
134
+ if torch.cuda.is_available():
135
+ print("Allocated:", torch.cuda.memory_allocated() / 1024**2, "MB")
136
+ print("Cached: ", torch.cuda.memory_reserved() / 1024**2, "MB")
137
+
138
+ return nag_video_path, baseline_video_path, current_seed
139
+
140
+
141
+ def generate_video_with_example(
142
+ prompt,
143
+ nag_negative_prompt,
144
+ nag_scale,
145
+ ):
146
+ nag_video_path, baseline_video_path, seed = generate_video(
147
+ prompt=prompt,
148
+ nag_negative_prompt=nag_negative_prompt, nag_scale=nag_scale,
149
+ height=DEFAULT_H_SLIDER_VALUE, width=DEFAULT_W_SLIDER_VALUE, duration_seconds=DEFAULT_DURATION_SECONDS,
150
+ steps=DEFAULT_STEPS,
151
+ seed=DEFAULT_SEED, randomize_seed=False,
152
+ compare=True,
153
+ )
154
+ if torch.cuda.is_available():
155
+ print("Allocated:", torch.cuda.memory_allocated() / 1024**2, "MB")
156
+ print("Cached: ", torch.cuda.memory_reserved() / 1024**2, "MB")
157
+ return nag_video_path, baseline_video_path, \
158
+ DEFAULT_H_SLIDER_VALUE, DEFAULT_W_SLIDER_VALUE, \
159
+ DEFAULT_DURATION_SECONDS, DEFAULT_STEPS, seed, True
160
+
161
+
162
+ with gr.Blocks() as demo:
163
+ gr.Markdown('''# Normalized Attention Guidance (NAG) for fast 4 steps Wan2.1-T2V-14B with CausVid LoRA
164
+ Implementation of [Normalized Attention Guidance](https://chendaryen.github.io/NAG.github.io/).
165
+
166
+ [CausVid](https://github.com/tianweiy/CausVid) is a distilled version of Wan2.1 to run faster in just 4-8 steps, [extracted as LoRA by Kijai](https://huggingface.co/Kijai/WanVideo_comfy/blob/main/Wan21_CausVid_14B_T2V_lora_rank32.safetensors).
167
+ ''')
168
+
169
+ with gr.Row():
170
+ with gr.Column():
171
+ prompt = gr.Textbox(
172
+ label="Prompt",
173
+ max_lines=3,
174
+ placeholder="Enter your prompt",
175
+ )
176
+ nag_negative_prompt = gr.Textbox(
177
+ label="Negative Prompt for NAG",
178
+ value=DEFAULT_NAG_NEGATIVE_PROMPT,
179
+ max_lines=3,
180
+ )
181
+ nag_scale = gr.Slider(label="NAG Scale", minimum=1., maximum=20., step=0.25, value=11.)
182
+ compare = gr.Checkbox(
183
+ label="Compare with baseline",
184
+ info="If unchecked, only sample with NAG will be generated.", value=True,
185
+ )
186
+
187
+ with gr.Accordion("Advanced Settings", open=False):
188
+ steps_slider = gr.Slider(minimum=1, maximum=8, step=1, value=DEFAULT_STEPS, label="Inference Steps")
189
+ duration_seconds_input = gr.Slider(
190
+ minimum=1, maximum=5, step=1, value=DEFAULT_DURATION_SECONDS,
191
+ label="Duration (seconds)",
192
+ )
193
+ seed_input = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=DEFAULT_SEED, interactive=True)
194
+ randomize_seed_checkbox = gr.Checkbox(label="Randomize seed", value=True, interactive=True)
195
+ with gr.Row():
196
+ height_input = gr.Slider(minimum=SLIDER_MIN_H, maximum=SLIDER_MAX_H, step=MOD_VALUE,
197
+ value=DEFAULT_H_SLIDER_VALUE,
198
+ label=f"Output Height (multiple of {MOD_VALUE})")
199
+ width_input = gr.Slider(minimum=SLIDER_MIN_W, maximum=SLIDER_MAX_W, step=MOD_VALUE,
200
+ value=DEFAULT_W_SLIDER_VALUE,
201
+ label=f"Output Width (multiple of {MOD_VALUE})")
202
+
203
+ generate_button = gr.Button("Generate Video", variant="primary")
204
+ with gr.Column():
205
+ nag_video_output = gr.Video(label="Video with NAG", autoplay=True, interactive=False)
206
+ baseline_video_output = gr.Video(label="Baseline Video without NAG", autoplay=True, interactive=False)
207
+
208
+ gr.Examples(
209
+ examples=examples,
210
+ fn=generate_video_with_example,
211
+ inputs=[prompt, nag_negative_prompt, nag_scale],
212
+ outputs=[
213
+ nag_video_output, baseline_video_output,
214
+ height_input, width_input, duration_seconds_input,
215
+ steps_slider,
216
+ seed_input,
217
+ compare,
218
+ ],
219
+ cache_examples="lazy"
220
+ )
221
+
222
+ ui_inputs = [
223
+ prompt,
224
+ nag_negative_prompt, nag_scale,
225
+ height_input, width_input, duration_seconds_input,
226
+ steps_slider,
227
+ seed_input, randomize_seed_checkbox,
228
+ compare,
229
+ ]
230
+ generate_button.click(
231
+ fn=generate_video,
232
+ inputs=ui_inputs,
233
+ outputs=[nag_video_output, baseline_video_output, seed_input],
234
+ )
235
+
236
+ if __name__ == "__main__":
237
+ demo.queue().launch()