arraypress commited on
Commit
5a64a48
·
verified ·
1 Parent(s): 4152ef4

BiRefNet general as a Core AI .aimodel (float16), with README and licence

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ cutout-birefnet-float16.aimodel/main.mlirb filter=lfs diff=lfs merge=lfs -text
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2024 ZhengPeng
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
NOTICE ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ cutout-birefnet: BiRefNet general (ZhengPeng7/BiRefNet at revision
2
+ e2bf8e4460fc8fa32bba5ea4d94b3233d367b0e4), MIT, Copyright (c) 2024 ZhengPeng.
3
+ See LICENSE.
4
+
5
+ Changed for Core AI, in export_birefnet.py:
6
+ - torchvision's deform_conv2d is computed from gathers and matrix products
7
+ (identical result: max |alpha difference| 4.6e-6 in float32 on the CPU).
8
+ - The ImageNet normalisation and the final sigmoid are inside the graph; only
9
+ the last, full-size prediction is returned.
10
+ - Weights and arithmetic are float16; the input and output stay float32.
11
+ The trained weights are otherwise the authors', unmodified.
README.md ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ base_model: ZhengPeng7/BiRefNet
4
+ tags:
5
+ - background-removal
6
+ - image-matting
7
+ - dichotomous-image-segmentation
8
+ - birefnet
9
+ - core-ai
10
+ - apple-silicon
11
+ - macos
12
+ library_name: swift-object-cutout
13
+ pipeline_tag: image-segmentation
14
+ ---
15
+
16
+ # cutout-birefnet: BiRefNet general for Core AI (macOS 27)
17
+
18
+ [BiRefNet](https://github.com/ZhengPeng7/BiRefNet) general — Zheng, Gao, Fan, Liu, Laaksonen,
19
+ Ouyang and Sebe, *Bilateral Reference for High-Resolution Dichotomous Image Segmentation*, CAAI
20
+ Artificial Intelligence Research, 2024; code and weights MIT — exported to an Apple Core AI
21
+ `.aimodel`. Give it a photo and it returns a soft alpha matte of the main subject: a product, a
22
+ plant, a glass, an animal, a person — the background-removal job, on device.
23
+
24
+ It is what [`cutout`](https://github.com/arraypress/swift-cutout-cli) uses for `--subject` and
25
+ `--person`, through [swift-object-cutout](https://github.com/arraypress/swift-object-cutout), and
26
+ what `design` uses to lift a subject off its photo. Both download this repository by themselves the
27
+ first time it is needed, pinned to one commit, every file checked against its published hash.
28
+ Apple's built-in Vision matte stays as the no-download fallback.
29
+
30
+ ## Files
31
+
32
+ | Path | What |
33
+ |---|---|
34
+ | `cutout-birefnet-float16.aimodel/` | One entry point, `main`: `image` `[1,3,1024,1024]` float32, RGB in 0…1 → `alpha` `[1,1,1024,1024]` float32 in 0…1. Float16 weights and arithmetic, 463 MB. |
35
+ | `export_birefnet.py` | The export script (`uv run export_birefnet.py --dtype float16`). |
36
+ | `LICENSE`, `NOTICE` | The authors' MIT licence, and what the export changed. |
37
+
38
+ The host resizes the picture to 1024×1024 without keeping its aspect ratio (as the network was
39
+ trained) and stretches the alpha back to the picture's size with bilinear sampling. ImageNet
40
+ normalisation and the sigmoid are inside the asset.
41
+
42
+ ## What changed
43
+
44
+ Core AI has no deformable convolution, which BiRefNet's decoder uses. The export computes it from
45
+ what it is: for each kernel tap, a bilinear sample of the feature map (four gathers from a
46
+ zero-padded copy), scaled by the modulator, then a matrix product with that tap's weights, summed
47
+ over the taps. Sample positions stay float32 in the float16 export. Against the authors' code in
48
+ float32 on the CPU, the rewrite differs by at most 4.6 × 10⁻⁶ in alpha.
49
+
50
+ ## Fidelity
51
+
52
+ Run through Core AI on the GPU, against the authors' PyTorch model (float32, CPU) on the same 30
53
+ inputs at 1024²:
54
+
55
+ | Export | Mean abs. alpha diff. | 99.9th percentile | Worst pixel | Mask IoU (>0.5), worst image |
56
+ |---|---|---|---|---|
57
+ | float32 | 1.9 × 10⁻⁷ | 2.4 × 10⁻⁵ | 1.3 × 10⁻⁴ | 0.99996 |
58
+ | float16 (this repo) | 1.9 × 10⁻⁵ | 1.5 × 10⁻³ | 0.068 | 0.9985 |
59
+
60
+ End to end (Swift, Core Graphics resizing) the float16 asset scores the same as PyTorch on the
61
+ true-alpha sets below: SAD 23.40 against 23.39 on objects and 6.12 against 6.10 on animals.
62
+
63
+ ## Quality
64
+
65
+ Measured with the standard matting metrics against true alpha on AIM-500 (Li et al., 2021), which
66
+ this checkpoint was not trained on (its training sets are DIS5K, DUTS, HRSOD, UHRSD, HRS10K, P3M-10k
67
+ and a humans set). 100 objects (plants, furniture, toys, fruit, glass), 100 animals and the 100
68
+ portraits, at their own size (at most 1920 px). Lower is better except IoU; the edge band is the
69
+ uncertain strip round the subject where hair and fur live.
70
+
71
+ | Set | Matte | SAD | MSE ×10³ | Grad | Conn | IoU | Edge-band MSE ×10³ |
72
+ |---|---|---|---|---|---|---|---|
73
+ | Objects | Apple Vision subject mask, cutout's crisp edge | 81.39 | 33.09 | 0.690 | 82.82 | 0.802 | 92.5 |
74
+ | Objects | **this model** | **23.40** | **3.29** | **0.211** | **22.87** | **0.919** | **12.6** |
75
+ | Animals | Apple Vision subject mask, crisp edge | 17.17 | 5.79 | 0.309 | 17.15 | 0.976 | 47.7 |
76
+ | Animals | **this model** | **6.12** | **0.99** | **0.074** | **5.50** | **0.991** | **9.4** |
77
+ | Portraits | Apple Vision person segmentation, crisp edge | 24.91 | 10.63 | 0.224 | 24.45 | 0.962 | 36.0 |
78
+ | Portraits | **this model** | **4.86** | **0.62** | **0.058** | **4.19** | **0.992** | **6.7** |
79
+
80
+ It beat Vision by SAD on 99 of the 100 objects, all 100 animals and 99 of the 100 portraits. Vision found no subject at all in 4 of the objects (counted
81
+ as empty mattes); glass is the hardest category for both (SAD 211 for Vision, 51 here). Re-solving this
82
+ model's alpha from colour (the crisp edge that helps Vision's softer mask) made every set worse, so
83
+ cutout passes this model's alpha through.
84
+
85
+ ## Speed and memory
86
+
87
+ Apple M3 Max, macOS 27, GPU: the first load compiles the model for the Mac in about 15 s and keeps a
88
+ 553 MB compiled copy in `~/Library/Caches/coreai-cache`; later loads are quick. About 0.9 s per
89
+ 1024² inference from Python, and 0.9–1.3 s per photo through `cutout` end to end (decode, model, edge,
90
+ PNG). A folder run of 100 photos peaked at 3.2–3.8 GB of memory, against 5.6 GB for the float32 export
91
+ and up to 10 GB for PyTorch on MPS.
92
+
93
+ ## Use
94
+
95
+ ```sh
96
+ cutout photo.jpg # fetches this model on first use
97
+ cutout ./shots --out ./cut # a folder, the model loaded once
98
+ cutout model remove subject # the model and its compiled copy
99
+ ```
100
+
101
+ Or by hand: `hf download arraypress/cutout-birefnet --local-dir models`, then point
102
+ `$CUTOUT_SUBJECT_MODEL` at the `.aimodel`. Requires macOS 27 (Core AI) on Apple silicon. Converted
103
+ with coreai-torch 0.4.3, coreai-core 1.0.0b3, torch 2.9.0 and transformers 4.57.1.
104
+
105
+ ## Licence
106
+
107
+ MIT, as the original: see `LICENSE` (the authors' copyright) and `NOTICE` (what the export changed).
cutout-birefnet-float16.aimodel/main.hash ADDED
@@ -0,0 +1 @@
 
 
1
+ j�@��پō��4s`]!��tU��S/�w��fw
cutout-birefnet-float16.aimodel/main.mlirb ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6aad408d80d9bec58df1e93473605d21169dfe7455bda3532f987708add66677
3
+ size 463261242
cutout-birefnet-float16.aimodel/metadata.json ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "producer" : "coreai-core 1.0.0b3",
3
+ "creationDate" : "20261016T175354Z",
4
+ "description" : "BiRefNet general (ZhengPeng7\/BiRefNet@e2bf8e4), float16. Entry 'main': image [1,3,1024,1024] float32 RGB 0…1 → alpha [1,1,1024,1024] float32 0…1. Resize to 1024² and back in the host.",
5
+ "assetVersion" : "2.0",
6
+ "license" : "MIT",
7
+ "author" : "Zheng, Gao, Fan, Liu, Laaksonen, Ouyang, Sebe — BiRefNet (CAAI AIR 2024), general; Core AI export by ObjectCutout"
8
+ }
export_birefnet.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # /// script
2
+ # requires-python = ">=3.11,<3.13"
3
+ # dependencies = ["torch==2.9.0", "torchvision==0.24.0", "transformers==4.57.1", "timm", "kornia", "einops",
4
+ # "numpy", "pillow", "coreai-torch==0.4.3", "coreai-core==1.0.0b3"]
5
+ # ///
6
+ """BiRefNet general (Zheng et al., CAAI AIR 2024; MIT) → cutout-birefnet-<dtype>.aimodel.
7
+
8
+ uv run Tools/export_birefnet.py [--dtype float16|float32] [--out Tools/exports] [--check image.jpg …]
9
+
10
+ The network is the authors' own (ZhengPeng7/BiRefNet at a pinned revision, loaded with its remote code); one
11
+ change, made for the runtime and checked against the original: torchvision's `deform_conv2d`, which Core AI does
12
+ not have, is rewritten as what it computes — each kernel tap's bilinear sample (four gathers from the zero-padded
13
+ feature map), scaled by the modulator, then a matrix product with that tap's weights, summed over the taps.
14
+ Sample positions are computed in float32 even in a float16 export: at 256 px a half float steps by 0.125, which
15
+ would move every bilinear weight.
16
+
17
+ One entry point, `main`: `image` [1,3,1024,1024] float32 RGB in 0…1 (resized by the host) → `alpha`
18
+ [1,1,1024,1024] float32 in 0…1. The ImageNet normalisation and the final sigmoid are inside. In a float16 export
19
+ the weights and arithmetic are half; input and output stay float32, so the host is the same for both.
20
+
21
+ --check runs the original network (torchvision's deform_conv2d) and the rewritten one on each image in float32 on
22
+ the CPU and prints the alpha difference, before anything is exported."""
23
+ import argparse, os, sys, time, shutil
24
+ from pathlib import Path
25
+ import numpy as np, torch, torch.nn as nn, torch.nn.functional as F
26
+
27
+ HERE = Path(__file__).resolve().parent
28
+ REPOSITORY = "ZhengPeng7/BiRefNet"
29
+ REVISION = "e2bf8e4460fc8fa32bba5ea4d94b3233d367b0e4"
30
+ SIZE = 1024
31
+
32
+ ap = argparse.ArgumentParser()
33
+ ap.add_argument("--dtype", default="float16", choices=["float16", "float32"])
34
+ ap.add_argument("--out", default=str(HERE / "exports"))
35
+ ap.add_argument("--check", nargs="*", default=[], help="images to compare original vs rewritten on (float32, CPU)")
36
+ ap.add_argument("--no-export", action="store_true")
37
+ args = ap.parse_args()
38
+
39
+ from transformers import AutoModelForImageSegmentation
40
+ torch.set_num_threads(4)
41
+ model = AutoModelForImageSegmentation.from_pretrained(REPOSITORY, trust_remote_code=True, revision=REVISION).float().eval()
42
+ remote = sys.modules[type(model).__module__]
43
+ print(f"BiRefNet general @ {REVISION[:7]}: {sum(p.numel() for p in model.parameters())/1e6:.1f} M parameters", flush=True)
44
+
45
+
46
+ def deform_conv2d(x, offset, weight, bias, padding, mask):
47
+ """torchvision.ops.deform_conv2d (stride 1, dilation 1, one offset group), from gathers and matmuls."""
48
+ _, C, H, W = x.shape
49
+ O, _, kh, kw = weight.shape
50
+ pad = padding if isinstance(padding, int) else padding[0]
51
+ Ho, Wo = H + 2 * pad - kh + 1, W + 2 * pad - kw + 1
52
+ Hp, Wp = H + 2, W + 2
53
+ # One row of zeros round the map: every corner that falls outside reads 0, as torchvision's does.
54
+ table = F.pad(x, (1, 1, 1, 1)).reshape(C, Hp * Wp).t() # [Hp*Wp, C]
55
+ off = offset.float().reshape(kh * kw, 2, Ho, Wo) # (dy, dx) per tap
56
+ mod = mask.float().reshape(kh * kw, Ho * Wo, 1)
57
+ ys = torch.arange(Ho, dtype=torch.float32).reshape(Ho, 1) - pad
58
+ xs = torch.arange(Wo, dtype=torch.float32).reshape(1, Wo) - pad
59
+ out = None
60
+ for k in range(kh * kw):
61
+ ky, kx = divmod(k, kw)
62
+ py = ys + ky + off[k, 0]
63
+ px = xs + kx + off[k, 1]
64
+ y0, x0 = torch.floor(py), torch.floor(px)
65
+ ly, lx = py - y0, px - x0
66
+ y0c, y1c = y0.clamp(-1, H) + 1, (y0 + 1).clamp(-1, H) + 1
67
+ x0c, x1c = x0.clamp(-1, W) + 1, (x0 + 1).clamp(-1, W) + 1
68
+ sample = None
69
+ for yc, xc, w in ((y0c, x0c, (1 - ly) * (1 - lx)), (y0c, x1c, (1 - ly) * lx),
70
+ (y1c, x0c, ly * (1 - lx)), (y1c, x1c, ly * lx)):
71
+ index = (yc * Wp + xc).reshape(-1).to(torch.int64)
72
+ term = F.embedding(index, table) * (w.reshape(-1, 1) * mod[k]).to(x.dtype)
73
+ sample = term if sample is None else sample + term
74
+ part = sample @ weight[:, :, ky, kx].t() # [Ho*Wo, O]
75
+ out = part if out is None else out + part
76
+ out = out.t().reshape(1, O, Ho, Wo)
77
+ return out if bias is None else out + bias.reshape(1, O, 1, 1)
78
+
79
+
80
+ def rewritten_forward(self, x):
81
+ offset = self.offset_conv(x)
82
+ modulator = 2. * torch.sigmoid(self.modulator_conv(x))
83
+ return deform_conv2d(x, offset, self.regular_conv.weight, self.regular_conv.bias, self.padding, modulator)
84
+
85
+
86
+ original_forward = remote.DeformableConv2d.forward
87
+ MEAN = torch.tensor([0.485, 0.456, 0.406]).reshape(1, 3, 1, 1)
88
+ STD = torch.tensor([0.229, 0.224, 0.225]).reshape(1, 3, 1, 1)
89
+
90
+
91
+ class Net(nn.Module):
92
+ """0…1 RGB in, alpha out: the normalisation and the sigmoid inside, the last (full-size) prediction only."""
93
+ def __init__(self, m, dtype):
94
+ super().__init__(); self.m = m.to(dtype); self.dtype = dtype
95
+ self.register_buffer("mean", MEAN.to(dtype)); self.register_buffer("std", STD.to(dtype))
96
+ def forward(self, image):
97
+ x = (image.to(self.dtype) - self.mean) / self.std
98
+ return self.m(x)[-1].sigmoid().to(torch.float32)
99
+
100
+
101
+ if args.check:
102
+ from PIL import Image
103
+ with torch.no_grad():
104
+ for path in args.check:
105
+ im = Image.open(path).convert("RGB").resize((SIZE, SIZE), Image.BILINEAR)
106
+ x = torch.from_numpy(np.asarray(im, np.float32) / 255).permute(2, 0, 1)[None]
107
+ remote.DeformableConv2d.forward = original_forward
108
+ a = Net(model, torch.float32)(x)
109
+ remote.DeformableConv2d.forward = rewritten_forward
110
+ b = Net(model, torch.float32)(x)
111
+ d = (a - b).abs()
112
+ print(f"{Path(path).name}: |original − rewritten| max {d.max():.2e} mean {d.mean():.2e}", flush=True)
113
+
114
+ if args.no_export:
115
+ sys.exit(0)
116
+
117
+ import coreai_torch
118
+ from coreai.runtime import AIModelAssetMetadata
119
+ remote.DeformableConv2d.forward = rewritten_forward
120
+ dtype = torch.float16 if args.dtype == "float16" else torch.float32
121
+ net = Net(model, dtype).eval()
122
+ t0 = time.time()
123
+ converter = coreai_torch.TorchConverter(mode=coreai_torch.TorchConverter.Mode.RELEASE)
124
+ with torch.no_grad():
125
+ ex = torch.export.export(net, (torch.rand(1, 3, SIZE, SIZE),)).run_decompositions(coreai_torch.get_decomp_table())
126
+ print(f"traced in {time.time()-t0:.0f} s", flush=True)
127
+ converter.add_exported_program(ex, input_names=["image"], output_names=["alpha"], entrypoint_name="main")
128
+ program = converter.to_coreai()
129
+ print(f"converted in {time.time()-t0:.0f} s", flush=True)
130
+ meta = AIModelAssetMetadata()
131
+ meta.author = "Zheng, Gao, Fan, Liu, Laaksonen, Ouyang, Sebe — BiRefNet (CAAI AIR 2024), general; Core AI export by ObjectCutout"
132
+ meta.license = "MIT"
133
+ meta.model_description = (f"BiRefNet general ({REPOSITORY}@{REVISION[:7]}), {args.dtype}. Entry 'main': image [1,3,{SIZE},{SIZE}] "
134
+ f"float32 RGB 0…1 → alpha [1,1,{SIZE},{SIZE}] float32 0…1. Resize to {SIZE}² and back in the host.")
135
+ meta.creation_date = int(time.time())
136
+ out = Path(args.out); out.mkdir(parents=True, exist_ok=True)
137
+ asset = out / f"cutout-birefnet-{args.dtype}.aimodel"
138
+ if asset.exists(): shutil.rmtree(asset)
139
+ program.save_asset(asset, meta)
140
+ print(f"saved {asset} ({sum(f.stat().st_size for f in asset.rglob('*'))/1e6:.0f} MB) in {time.time()-t0:.0f} s", flush=True)