mlboydaisuke commited on
Commit
5ff600a
Β·
verified Β·
1 Parent(s): 1935d85

Upload README.md with huggingface_hub

Browse files
Files changed (1) hide show
  1. README.md +121 -0
README.md ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ tags:
4
+ - executorch
5
+ - xnnpack
6
+ - pte
7
+ - on-device
8
+ - image-segmentation
9
+ - zero-shot-image-classification
10
+ base_model:
11
+ - CIDAS/clipseg-rd64-refined
12
+ ---
13
+ # CLIPSeg (rd64-refined) β€” ExecuTorch (segmentation from a text prompt)
14
+
15
+ Say what you want and get a mask for it. The shelf's other open-vocabulary model,
16
+ Grounding DINO, returns **boxes** for a phrase; SAM and EdgeTAM return a **mask for a
17
+ click**. This returns a **mask for a phrase**, which neither of those does.
18
+
19
+ Two files, because the expensive half does not depend on the prompt:
20
+
21
+ ```
22
+ clipseg_vision pixel_values (1, 3, 352, 352) fp32
23
+ -> three activations, each (1, 485, 768) # CLIP ViT-B/16 layers 3, 6, 9
24
+ clipseg_segment a3, a6, a9, input_ids (1, 77) int64
25
+ -> logits (1, 352, 352) fp32 # per-pixel; sigmoid for 0..1
26
+ ```
27
+
28
+ - **Source**: [CIDAS/clipseg-rd64-refined](https://huggingface.co/CIDAS/clipseg-rd64-refined) β€” 150.7M parameters
29
+ - **License**: apache-2.0
30
+ - **Threshold**: `logits > 0` is the mask. Everything below is background.
31
+
32
+ **Run the vision half once per image and the segment half once per phrase.** That is the
33
+ whole point of the split: trying "a tree", "the sky" and "grass" on one photograph costs
34
+ one ViT pass and three cheap ones, not three ViT passes. The three activations are 4.5 MB
35
+ in total β€” an intermediate the caller holds, not a file.
36
+
37
+ Checked against upstream's single-shot `forward`: **max_abs_diff 0.000e+00** on every
38
+ prompt. Splitting it changes nothing about the arithmetic.
39
+
40
+ ## Variants
41
+
42
+ | half | build | file | size (MB) | Mac ms* |
43
+ |---|---|---|---|---|
44
+ | vision | fp32 | `clipseg_vision_xnnpack_fp32.pte` | 286.7 | 194.8 |
45
+ | vision | fp16 | `clipseg_vision_xnnpack_fp16.pte` | 173.1 | 252.4 |
46
+ | segment | fp32 | `clipseg_segment_xnnpack_fp32.pte` | 260.8 | 24.7 |
47
+ | segment | fp16 | `clipseg_segment_xnnpack_fp16.pte` | 129.6 | 41.8 |
48
+ | segment | **Core ML (fp16, iOS)** | `clipseg_segment_coreml_all.pte` | 132.2 | **3.2** |
49
+
50
+ \*Mac arm64, median of 10 β€” a reference point for relative cost, not a device number.
51
+ Torch eager fp32 on the same machine is 53 ms for the vision half and 19 ms for the
52
+ segment half.
53
+
54
+ **The vision half is slower than eager on XNNPACK and there is no Core ML build for it.**
55
+ Both are worth knowing before you plan around this model:
56
+
57
+ - Core ML refuses a graph holding any non-contiguous `_clone_dim_order` node
58
+ (`AssertionError: Only contiguous memory format is supported in CoreML`). The vision
59
+ half has two β€” ExecuTorch's dim-order pass puts the patch convolution's output in
60
+ channels-last, and `interpolate_pos_encoding` produces another. Rewriting the patch
61
+ convolution as the equivalent linear was tried and only moved the problem: the
62
+ reshape-and-permute that gathers patches earns a channels-last clone of its own. The
63
+ segment half has no 4-D convolution and lowers to Core ML whole, 100% delegated.
64
+ - On XNNPACK the vision half delegates 66.8% of its ops across 43 subgraphs. **Why it is
65
+ 3.7x slower than eager has not been measured**, so this card does not name a cause.
66
+
67
+ ## Verification
68
+
69
+ Correlation over a mask is not the gate. A mask gets used by thresholding it, so the
70
+ number that matters is how many pixels change side:
71
+
72
+ | build | worst mask IoU vs eager |
73
+ |---|---|
74
+ | fp32 | **1.0000** |
75
+ | fp16 | 0.9974 |
76
+ | Core ML segment | 0.9994 |
77
+ | int8 | 0.9429 β€” **withheld** |
78
+
79
+ **Only prompts the reference actually answers are scored.** On the gate image the model
80
+ answers "a tree" (63% of pixels), "the sky" (21%) and "grass" (5%), and ignores "a road"
81
+ (0%) β€” an empty mask agrees with another empty mask perfectly, so scoring the ignored
82
+ prompt would flatter every build. The four prompts were picked by running the reference
83
+ over a dozen candidates rather than written from imagination.
84
+
85
+ ## Running it
86
+
87
+ **1. Preprocess to 352x352**, with `CLIPSegProcessor`. The CLIP backbone was trained at
88
+ 224 and CLIPSeg runs it at 352 by interpolating the position embeddings β€” feeding it 224
89
+ gives a mask at the wrong scale rather than an error.
90
+
91
+ **2. Tokenise the prompt to a fixed 77**, CLIP's context length:
92
+
93
+ ```python
94
+ ids = processor.tokenizer([prompt], padding="max_length", truncation=True,
95
+ max_length=77, return_tensors="pt")["input_ids"]
96
+ ```
97
+
98
+ Padding to the full 77 is safe here and it is worth knowing why: CLIP's text encoder is
99
+ causal and pools at the end-of-text token, so the padding that follows never reaches it.
100
+ Measured against the processor's own tight padding: **6.676e-06**.
101
+
102
+ **3. Vision once, segment per prompt**, then threshold at 0 and resize the 352x352 mask
103
+ back to your image.
104
+
105
+ ## Not shipped: int8
106
+
107
+ Both halves are withheld together, because the gate measures the pair. `clipseg_vision`
108
+ would come out at **83.5 MB** from 286.7 (dynamic int8 has almost nothing to leave alone
109
+ in a ViT β€” there is no token embedding table), and `clipseg_segment` at **141.6 MB**,
110
+ which is *larger* than its own fp16 at 129.6 MB: the segment half carries CLIP's
111
+ 49,408 x 512 text embedding table, 101 MB of its 260.8, a 38.8% share. This shelf's rule
112
+ `int8/fp16 = 0.5 + 1.5 x (table share)` predicts 1.082 and it came out at 1.092.
113
+
114
+ The quality is what decides it. Worst mask IoU **0.9429** against a 0.95 bar, and the
115
+ worst case is the *smallest* mask β€” "grass", 5% of the image, where boundary pixels are
116
+ most of the mask. Correlation for the same build reads 0.9967 and 0.9999 on the two
117
+ halves, which no correlation gate would stop.
118
+
119
+ This repo holds **both towers**: `clip_vit_b32_image_xnnpack_fp32.pte` (image) and
120
+ `clip_vit_b32_text_xnnpack_fp32.pte` (text, fixed len 77 + attention mask).
121
+ L2-normalize both embeddings, then cosine-match.