Spaces:
Sleeping
Sleeping
Download nafnet.py from Monishcloud8/mathsnap-deblur: direct link, hf CLI and curl.
- Browser
- Download file 4.33 kB
-
https://huggingface.co/spaces/Monishcloud8/mathsnap-deblur/resolve/main/nafnet.py
- Command line
-
hf download hf://spaces/Monishcloud8/mathsnap-deblur/nafnet.py
-
curl -L -o nafnet.py https://huggingface.co/spaces/Monishcloud8/mathsnap-deblur/resolve/main/nafnet.py
4.33 kB
| """NAFNet (megvii-research, MIT) rebuilt in PyTorch to load the mlx-community NHWC safetensors.""" | |
| import json | |
| import numpy as np | |
| import torch | |
| import torch.nn as nn | |
| from safetensors.numpy import load_file | |
| class LayerNorm2d(nn.Module): | |
| def __init__(self, c, eps=1e-6): | |
| super().__init__() | |
| self.weight = nn.Parameter(torch.ones(c)) | |
| self.bias = nn.Parameter(torch.zeros(c)) | |
| self.eps = eps | |
| def forward(self, x): | |
| mu = x.mean(1, keepdim=True) | |
| var = (x - mu).pow(2).mean(1, keepdim=True) | |
| y = (x - mu) / torch.sqrt(var + self.eps) | |
| return self.weight.view(1, -1, 1, 1) * y + self.bias.view(1, -1, 1, 1) | |
| class SimpleGate(nn.Module): | |
| def forward(self, x): | |
| a, b = x.chunk(2, dim=1) | |
| return a * b | |
| class NAFBlock(nn.Module): | |
| def __init__(self, c): | |
| super().__init__() | |
| dw = c * 2 | |
| self.conv1 = nn.Conv2d(c, dw, 1) | |
| self.conv2 = nn.Conv2d(dw, dw, 3, padding=1, groups=dw) | |
| self.conv3 = nn.Conv2d(dw // 2, c, 1) | |
| # Simplified channel attention; global average pooling (the tile size equals the TLC window). | |
| self.sca = nn.Conv2d(dw // 2, dw // 2, 1) | |
| self.sg = SimpleGate() | |
| self.conv4 = nn.Conv2d(c, 2 * c, 1) | |
| self.conv5 = nn.Conv2d(c, c, 1) | |
| self.norm1 = LayerNorm2d(c) | |
| self.norm2 = LayerNorm2d(c) | |
| self.beta = nn.Parameter(torch.zeros(1, c, 1, 1)) | |
| self.gamma = nn.Parameter(torch.zeros(1, c, 1, 1)) | |
| def forward(self, inp): | |
| x = self.sg(self.conv2(self.conv1(self.norm1(inp)))) | |
| x = x * self.sca(x.mean(dim=(2, 3), keepdim=True)) | |
| y = inp + self.conv3(x) * self.beta | |
| x = self.conv5(self.sg(self.conv4(self.norm2(y)))) | |
| return y + x * self.gamma | |
| class NAFNet(nn.Module): | |
| def __init__(self, img_channel=3, width=64, middle_blk_num=1, enc_blk_nums=(1, 1, 1, 28), dec_blk_nums=(1, 1, 1, 1), **_): | |
| super().__init__() | |
| self.intro = nn.Conv2d(img_channel, width, 3, padding=1) | |
| self.ending = nn.Conv2d(width, img_channel, 3, padding=1) | |
| self.encoders, self.decoders, self.ups, self.downs = nn.ModuleList(), nn.ModuleList(), nn.ModuleList(), nn.ModuleList() | |
| chan = width | |
| for num in enc_blk_nums: | |
| self.encoders.append(nn.Sequential(*[NAFBlock(chan) for _ in range(num)])) | |
| self.downs.append(nn.Conv2d(chan, 2 * chan, 2, 2)) | |
| chan *= 2 | |
| self.middle_blks = nn.Sequential(*[NAFBlock(chan) for _ in range(middle_blk_num)]) | |
| for num in dec_blk_nums: | |
| self.ups.append(nn.Sequential(nn.Conv2d(chan, chan * 2, 1, bias=False), nn.PixelShuffle(2))) | |
| chan //= 2 | |
| self.decoders.append(nn.Sequential(*[NAFBlock(chan) for _ in range(num)])) | |
| def forward(self, inp): | |
| x = self.intro(inp) | |
| skips = [] | |
| for enc, down in zip(self.encoders, self.downs): | |
| x = enc(x) | |
| skips.append(x) | |
| x = down(x) | |
| x = self.middle_blks(x) | |
| for dec, up, skip in zip(self.decoders, self.ups, skips[::-1]): | |
| x = dec(up(x) + skip) | |
| return self.ending(x) + inp | |
| def load(weights, config=None): | |
| cfg = config or {"width": 64, "middle_blk_num": 1, "enc_blk_nums": [1, 1, 1, 28], "dec_blk_nums": [1, 1, 1, 1]} | |
| model = NAFNet(**cfg) | |
| raw = load_file(weights) | |
| state = {} | |
| target = model.state_dict() | |
| for key, value in raw.items(): | |
| name = key | |
| if key.startswith("ups.") and key.count(".") == 2: | |
| name = key.replace(".weight", ".0.weight") # ups.N is Sequential(conv, PixelShuffle) | |
| if value.ndim == 4 and name.endswith("weight"): | |
| value = np.transpose(value, (0, 3, 1, 2)) # MLX (O,kH,kW,I) -> PyTorch (O,I,kH,kW) | |
| elif name.endswith(("beta", "gamma")): | |
| value = np.transpose(value, (0, 3, 1, 2)) | |
| state[name] = torch.from_numpy(np.ascontiguousarray(value)) | |
| missing = set(target) - set(state) | |
| unused = set(state) - set(target) | |
| bad = [k for k in target if k in state and tuple(target[k].shape) != tuple(state[k].shape)] | |
| if missing or unused or bad: | |
| raise RuntimeError(f"missing={sorted(missing)[:8]} unused={sorted(unused)[:8]} shape={bad[:8]}") | |
| model.load_state_dict(state, strict=True) | |
| return model.eval() | |