Download export/modnet.py from executorch-community/MODNet-ExecuTorch-WebGPU: direct link, hf CLI and curl.
- Browser
- Download file 5.58 kB
-
https://huggingface.co/executorch-community/MODNet-ExecuTorch-WebGPU/resolve/fd4bfe709c151e686bf1de95dcd782238d4f65dd/export/modnet.py
- Command line
-
hf download hf://executorch-community/MODNet-ExecuTorch-WebGPU@fd4bfe709c151e686bf1de95dcd782238d4f65dd/export/modnet.py
-
curl -L -o modnet.py https://huggingface.co/executorch-community/MODNet-ExecuTorch-WebGPU/resolve/fd4bfe709c151e686bf1de95dcd782238d4f65dd/export/modnet.py
5.58 kB
| #!/usr/bin/env python3 | |
| """MODNet portrait matting -> ExecuTorch WebGPU. | |
| The official code is on GitHub (ZHKKKe/MODNet) and the official checkpoints on Google | |
| Drive; neither is reachable from the export host and the author has no Hugging Face | |
| account. This pins a community Hugging Face mirror that carries a copy of the upstream | |
| source tree and the photographic checkpoint (sha256 checked; four independent mirrors | |
| publish the same checkpoint bytes). The webcam checkpoint comes from a second mirror. | |
| Input: [1, 3, 256, 256] float32 RGB normalized to [-1, 1] ((x / 255 - 0.5) / 0.5). | |
| Output: [1, 1, 256, 256] alpha matte in [0, 1] (inference=True, pred_matte only). | |
| """ | |
| import argparse | |
| import importlib | |
| import sys | |
| from pathlib import Path | |
| import torch | |
| sys.path.insert(0, str(Path(__file__).resolve().parent)) | |
| import _common # noqa: E402 | |
| SOURCE = { | |
| "repo": "boopathiraj/MODNet", | |
| "revision": "a967175f8fd63de67a1c161d8daac966d2b57e3a", | |
| "files": { | |
| "modnet/__init__.py": None, | |
| "modnet/modnet.py": None, | |
| "modnet/src/__init__.py": None, | |
| "modnet/src/models/__init__.py": None, | |
| "modnet/src/models/backbones/__init__.py": None, | |
| "modnet/src/models/backbones/wrapper.py": None, | |
| "modnet/src/models/backbones/mobilenetv2.py": None, | |
| "modnet/LICENSE": None, | |
| }, | |
| } | |
| CHECKPOINTS = { | |
| "photographic": { | |
| "repo": "boopathiraj/MODNet", | |
| "revision": "a967175f8fd63de67a1c161d8daac966d2b57e3a", | |
| "file": "modnet/pretrained/modnet_photographic_portrait_matting.ckpt", | |
| "sha256": "7c22235f0925deba15d4d63e53afcb654c47055bbcd98f56e393ab2584007ed8", | |
| }, | |
| "webcam": { | |
| "repo": "DavG25/modnet-pretrained-models", | |
| "revision": "903cc06311b3b12071edfa1b42b534e4a31c718a", | |
| "file": "models/modnet_webcam_portrait_matting.ckpt", | |
| "sha256": "913b82b66558db39b6286c150f809017d7528c872b156eb14333c9c6cb52108b", | |
| }, | |
| } | |
| class InstanceNorm2dDecomposed(torch.nn.Module): | |
| """InstanceNorm2d(affine=False) as mean/sub/mul/sqrt/div. | |
| The Vulkan/WebGPU partitioner does not take aten._native_batch_norm_legit.no_stats, | |
| so the stock module would leave 16 CPU kernels splitting the graph into 13 delegates. | |
| """ | |
| def __init__(self, eps): | |
| super().__init__() | |
| self.eps = eps | |
| def forward(self, x): | |
| d = x - x.mean(dim=(2, 3), keepdim=True) | |
| var = (d * d).mean(dim=(2, 3), keepdim=True) | |
| return d / torch.sqrt(var + self.eps) | |
| def build_model(variant): | |
| root = None | |
| files = {} | |
| for f in SOURCE["files"]: | |
| path, digest = _common.hf_file(SOURCE["repo"], f, SOURCE["revision"]) | |
| files[f] = digest | |
| root = Path(str(path)[: -len(f)]) | |
| sys.path.insert(0, str(root)) | |
| MODNet = importlib.import_module("modnet.modnet").MODNet | |
| sys.path.remove(str(root)) | |
| ck = CHECKPOINTS[variant] | |
| path, _ = _common.hf_file(ck["repo"], ck["file"], ck["revision"], ck["sha256"]) | |
| state = torch.load(path, map_location="cpu", weights_only=True) | |
| # Checkpoints were saved from nn.DataParallel. | |
| state = {k.removeprefix("module."): v for k, v in state.items()} | |
| model = MODNet(backbone_pretrained=False) | |
| model.load_state_dict(state, strict=True) | |
| for parent in list(model.modules()): | |
| for name, child in parent.named_children(): | |
| if isinstance(child, torch.nn.InstanceNorm2d): | |
| assert not child.affine and not child.track_running_stats | |
| setattr(parent, name, InstanceNorm2dDecomposed(child.eps)) | |
| class Matte(torch.nn.Module): | |
| def __init__(self, m): | |
| super().__init__() | |
| self.m = m | |
| def forward(self, img): | |
| return self.m(img, True)[2] | |
| return Matte(model).eval(), files | |
| def main(): | |
| ap = argparse.ArgumentParser(description=__doc__.splitlines()[0]) | |
| ap.add_argument("--out", required=True, help="output directory") | |
| ap.add_argument("--size", type=int, nargs=2, default=[256, 256], metavar=("H", "W")) | |
| ap.add_argument("--variant", choices=sorted(CHECKPOINTS), default="photographic") | |
| ap.add_argument( | |
| "--golden", | |
| help="optional goldens.manifest.json to compare against (key: bgremove)", | |
| ) | |
| args = ap.parse_args() | |
| model, source_files = build_model(args.variant) | |
| golden = _common.load_golden(args.golden, "bgremove") if args.golden else None | |
| _common.export_vision( | |
| name="modnet", | |
| model=model, | |
| example_input=torch.rand(1, 3, *args.size) * 2 - 1, | |
| out_dir=args.out, | |
| upstream={ | |
| "model": f"ZHKKKe/MODNet ({args.variant} portrait matting checkpoint)", | |
| "weights": { | |
| **CHECKPOINTS[args.variant], | |
| "official": False, | |
| "note": "community mirror; official checkpoints are on Google Drive, linked from GitHub, neither reachable from the export host", | |
| }, | |
| "architecture_source": { | |
| "repo": SOURCE["repo"], | |
| "revision": SOURCE["revision"], | |
| "files_sha256": source_files, | |
| "official": False, | |
| "note": "copy of the upstream src/models tree with modnet.py moved to the package root", | |
| }, | |
| "license": "Apache-2.0", | |
| }, | |
| extra_recipe={ | |
| "variant": args.variant, | |
| "preprocessing_outside_graph": "RGB / 255, then (x - 0.5) / 0.5", | |
| "postprocessing_outside_graph": "alpha in [0, 1]", | |
| }, | |
| golden=golden, | |
| ) | |
| if __name__ == "__main__": | |
| main() | |