Spaces:
Runtime error
Runtime error
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| import sys | |
| import os | |
| import gdown | |
| import inspect | |
| import textwrap | |
| import math | |
| # --- 0. FORCE CPU PATCH --- | |
| # Patches T5 to avoid "No NVIDIA driver" error | |
| t5_path = "/app/Wan2.1/wan/modules/t5.py" | |
| if os.path.exists(t5_path): | |
| print("--- PATCHING T5.PY ---") | |
| with open(t5_path, 'r') as f: | |
| content = f.read() | |
| new_content = content.replace("device=torch.cuda.current_device(),", "device='cpu',") | |
| with open(t5_path, 'w') as f: | |
| f.write(new_content) | |
| sys.path.append("/app/Wan2.1") | |
| # --- 1. DOWNLOAD WEIGHTS --- | |
| print("--- DOWNLOADING MODEL ---") | |
| file_id = '1pf-fhkWayK3_sv9bujvT2Od04ki2Wh4y' | |
| url = f'https://drive.google.com/uc?id={file_id}' | |
| ckpt_path = "/app/wan13bNSFWFull_v20I2v.safetensors" | |
| if not os.path.exists(ckpt_path): | |
| gdown.download(url, ckpt_path, quiet=False) | |
| # --- 2. CRITICAL PATCHES --- | |
| # A. RECURSION-PROOF LINEAR FIX | |
| _orig_linear = F.linear # <--- SAVES THE ORIGINAL FUNCTION | |
| def safe_linear(input, weight, bias=None): | |
| # Force mixed-precision to match (FP32 input + FP16 weight = FP16 input) | |
| if input.dtype == torch.float32 and weight.dtype == torch.float16: | |
| input = input.half() | |
| elif input.dtype == torch.float16 and weight.dtype == torch.float32: | |
| weight = weight.half() | |
| if bias is not None: bias = bias.half() | |
| return _orig_linear(input, weight, bias) # <--- CALLS SAVED FUNCTION | |
| # Apply the patch | |
| torch.nn.functional.linear = safe_linear | |
| torch.nn.modules.linear.F.linear = safe_linear | |
| # B. ROPE FIX (CPU Compatible) | |
| def manual_rope_apply_real(x, grid_sizes, freqs): | |
| n, l, h, d = x.shape | |
| if freqs.dim() == 2: | |
| l_freqs, d_half = freqs.shape | |
| if l_freqs != l: | |
| freqs_in = freqs.transpose(0, 1).unsqueeze(0).float() | |
| freqs_out = F.interpolate(freqs_in, size=l, mode='linear', align_corners=False) | |
| freqs = freqs_out.squeeze(0).transpose(0, 1) | |
| cos = torch.cos(freqs) | |
| sin = torch.sin(freqs) | |
| elif freqs.dim() == 3: | |
| l_freqs = freqs.shape[0] | |
| if l_freqs != l: | |
| f_flat = freqs.flatten(1) | |
| f_in = f_flat.transpose(0, 1).unsqueeze(0).float() | |
| f_out = F.interpolate(f_in, size=l, mode='linear', align_corners=False) | |
| freqs = f_out.squeeze(0).transpose(0, 1).view(l, -1, 2) | |
| cos = freqs[..., 0] | |
| sin = freqs[..., 1] | |
| else: | |
| cos = freqs[..., 0] | |
| sin = freqs[..., 1] | |
| x_pairs = x.float().view(n, l, h, d // 2, 2) | |
| r = x_pairs[..., 0] | |
| i = x_pairs[..., 1] | |
| cos = cos.unsqueeze(0).unsqueeze(2) | |
| sin = sin.unsqueeze(0).unsqueeze(2) | |
| out_r = r * cos - i * sin | |
| out_i = r * sin + i * cos | |
| return torch.stack([out_r, out_i], dim=-1).flatten(3).type_as(x) | |
| import wan.modules.model | |
| wan.modules.model.rope_apply = manual_rope_apply_real | |
| # C. FLASH ATTENTION FIX | |
| def manual_attention(q, k, v, dropout_p=0.0, **kwargs): | |
| if q.dtype != torch.float16: q = q.half() | |
| if k.dtype != torch.float16: k = k.half() | |
| if v.dtype != torch.float16: v = v.half() | |
| q = q.transpose(1, 2) | |
| k = k.transpose(1, 2) | |
| v = v.transpose(1, 2) | |
| out = F.scaled_dot_product_attention(q, k, v, dropout_p=dropout_p) | |
| return out.transpose(1, 2) | |
| import wan.modules.attention | |
| wan.modules.attention.flash_attention = manual_attention | |
| wan.modules.attention.FLASH_ATTN_2_AVAILABLE = False | |
| wan.modules.model.flash_attention = manual_attention | |
| # D. LAYERNORM FIX | |
| class ManualWanLayerNorm(nn.LayerNorm): | |
| def forward(self, x): | |
| orig_type = x.dtype | |
| return F.layer_norm(x.float(), self.normalized_shape, self.weight.float(), self.bias.float(), self.eps).to(orig_type) | |
| wan.modules.model.WanLayerNorm = ManualWanLayerNorm | |
| # --- 3. DYNAMIC CLASS PATCHER --- | |
| # Removes assert statements that crash the export | |
| from wan.modules.model import WanModel, sinusoidal_embedding_1d | |
| def patch_all_wan_classes(): | |
| print("=== APPLYING DYNAMIC PATCHES ===") | |
| target_module = wan.modules.model | |
| classes = [obj for name, obj in inspect.getmembers(target_module) if inspect.isclass(obj) and obj.__module__ == target_module.__name__] | |
| shared_globals = { | |
| 'torch': torch, 'math': math, 'F': F, | |
| 'amp': torch.cuda.amp if hasattr(torch.cuda, 'amp') else None, | |
| 'sinusoidal_embedding_1d': sinusoidal_embedding_1d, | |
| 'rope_apply': manual_rope_apply_real, | |
| 'nn': nn, 'flash_attention': manual_attention, | |
| } | |
| for cls in classes: | |
| shared_globals[cls.__name__] = cls | |
| for cls in classes: | |
| if not hasattr(cls, 'forward') or cls.__name__ == "ManualWanLayerNorm": continue | |
| try: | |
| src = inspect.getsource(cls.forward) | |
| except OSError: continue | |
| src = textwrap.dedent(src) | |
| lines = src.splitlines() | |
| new_lines = [] | |
| modified = False | |
| for line in lines: | |
| indent = len(line) - len(line.lstrip()) | |
| # FIX 1: Remove asserts that check for float32 | |
| if "assert" in line and ("float32" in line or "dtype" in line): | |
| new_lines.append(" " * indent + "pass") | |
| modified = True | |
| # FIX 2: Force time embedding to Half | |
| elif cls.__name__ == "WanModel" and "e = self.time_embedding(t, shift)" in line: | |
| new_lines.append(line.replace("e = self.time_embedding(t, shift)", "e = self.time_embedding(t, shift).half()")) | |
| modified = True | |
| # FIX 3: Force QKV to Half | |
| elif "q, k, v = qkv_fn(x)" in line: | |
| new_lines.append(" " * indent + "x = x.half()") | |
| new_lines.append(line) | |
| modified = True | |
| else: | |
| new_lines.append(line) | |
| if modified: | |
| new_src = chr(10).join(new_lines) | |
| exec_scope = shared_globals.copy() | |
| exec_scope[cls.__name__] = cls | |
| try: | |
| exec(new_src, exec_scope) | |
| cls.forward = exec_scope['forward'] | |
| print(f" [Patcher] SUCCESS: {cls.__name__} patched.") | |
| except Exception as e: | |
| print(f" [Patcher] FAILED {cls.__name__}: {e}") | |
| # --- 4. EXPORT LOGIC (PART 1) --- | |
| class HeadIdentity(nn.Module): | |
| def forward(self, x, e=None): return x | |
| def convert(): | |
| print("--- STARTING CONVERSION: PART 1 ---") | |
| patch_all_wan_classes() | |
| cfg = { | |
| "t5_model": "umt5_xxl", "t5_dtype": torch.float16, "text_len": 512, | |
| "param_dtype": torch.float16, "num_train_timesteps": 1000, "sample_fps": 16, | |
| "sample_neg_prompt": "", "t5_checkpoint": "", "t5_tokenizer": "", | |
| "vae_checkpoint": "", "vae_stride": (4, 8, 8), "patch_size": (1, 2, 2), | |
| "dim": 1536, "num_heads": 12, "num_layers": 30, "window_size": (-1, -1), | |
| "qk_norm": True, "cross_attn_norm": True, "eps": 1e-6, "rope_max_len": 4096, | |
| "base_dim": 256, "model_type": "i2v" | |
| } | |
| with torch.no_grad(): | |
| model = WanModel.from_config(cfg) | |
| model.eval().to("cpu") | |
| model.half() | |
| from safetensors.torch import load_file | |
| print(f"Loading weights from {ckpt_path}...") | |
| sd = load_file(ckpt_path) | |
| model.load_state_dict(sd, strict=False) | |
| old = model.patch_embedding | |
| model.patch_embedding = torch.nn.Conv3d(72, old.out_channels, old.kernel_size, old.stride, old.padding, bias=(old.bias is not None)).half() | |
| # EXPORT FIRST 15 BLOCKS | |
| all_blocks_ref = list(model.blocks) | |
| model.blocks = nn.ModuleList() | |
| model.blocks.extend(all_blocks_ref[:15]) | |
| model.head = HeadIdentity() | |
| model.unpatchify = lambda x, grid_sizes: x | |
| # Dummy inputs | |
| dtype = torch.float16 | |
| args = ( | |
| [torch.randn(36, 1, 32, 32, dtype=dtype)], | |
| torch.tensor([10.0], dtype=torch.float32), | |
| torch.randn(1, 512, 4096, dtype=dtype), | |
| torch.tensor([4096]), | |
| torch.randn(1, 257, 1280, dtype=dtype), | |
| [torch.randn(36, 1, 32, 32, dtype=dtype)] | |
| ) | |
| output_file = "/app/output/wan13b_part1.onnx" | |
| print(f"Exporting to {output_file}...") | |
| torch.onnx.export( | |
| model, args, output_file, | |
| input_names=['latents', 'timestep', 'context', 'seq_len', 'clip_fea', 'y'], | |
| output_names=['mid_block_states'], | |
| opset_version=14, | |
| do_constant_folding=False, | |
| export_params=True, | |
| keep_initializers_as_inputs=True | |
| ) | |
| print("--- SUCCESS ---") | |
| if __name__ == "__main__": | |
| convert() |