Upload README.md with huggingface_hub
Browse files
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.
|