mlboydaisuke commited on
Commit
1554568
Β·
verified Β·
1 Parent(s): 5bc58a3

Upload README.md with huggingface_hub

Browse files
Files changed (1) hide show
  1. README.md +88 -0
README.md ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ tags:
4
+ - executorch
5
+ - xnnpack
6
+ - pte
7
+ - on-device
8
+ - mask-generation
9
+ ---
10
+ # MobileSAM β€” ExecuTorch XNNPACK (encoder + decoder)
11
+
12
+ Promptable segmentation in two `.pte` files: run the encoder once per image, the
13
+ decoder once per click.
14
+
15
+ - `mobilesam_encoder_xnnpack_fp32.pte` (28.3 MB) β€” image (1,3,1024,1024) β†’
16
+ image_embed (1,256,64,64)
17
+ - `mobilesam_encoder_xnnpack_int8.pte` (14.0 MB) β€” same, dynamically quantized,
18
+ corr 0.999880
19
+ - `mobilesam_decoder_xnnpack_fp32.pte` (20.5 MB) β€” (image_embed,
20
+ points (1,N,2) fp32 pixel coords in 1024-space, labels (1,N) fp32 1=fg/0=bg)
21
+ β†’ mask logits (1,3,256,256), iou scores (1,3)
22
+ - `mobilesam_decoder_xnnpack_fp16.pte` (10.5 MB) β€” same decoder, corr 0.999825
23
+
24
+ All four take and return fp32 tensors, so any encoder file pairs with any decoder
25
+ file. The smallest working pair is 24.5 MB against 48.8 MB for fp32 throughout.
26
+
27
+ The encoder has no fp16 build: TinyViT does not survive half precision, and not
28
+ because of anything the conversion does β€” plain `model.half()` in eager already
29
+ returns corr -0.37 against the fp32 model. Dynamic int8 is the size lever here
30
+ instead, and it holds at corr 0.9999. The decoder has no int8 build for the opposite
31
+ reason: it came out at 21.8 MB, larger than its own fp32 file, because a dynamically
32
+ quantized transformer leaves the decoder's large constant positional embedding in
33
+ fp32 and adds quantization metadata on top.
34
+
35
+ MobileSAM is SAM with its ViT-H encoder replaced by TinyViT. Same prompt contract as
36
+ the [SAM2.1](https://huggingface.co/mlboydaisuke/SAM2.1-hiera-tiny-ExecuTorch) and
37
+ [EdgeTAM](https://huggingface.co/mlboydaisuke/EdgeTAM-ExecuTorch) conversions, with
38
+ two differences worth knowing: this decoder needs only the image embedding (no
39
+ high-resolution feature maps), and labels are fp32 rather than int64.
40
+
41
+ - **Source**: [ChaoningZhang/MobileSAM](https://github.com/ChaoningZhang/MobileSAM),
42
+ weights from [dhkim2810/MobileSAM](https://huggingface.co/dhkim2810/MobileSAM)
43
+ - **License**: Apache-2.0 (code) / MIT (weights)
44
+ - **Preprocess**: RGB, SAM norm (mean 123.675/116.28/103.53, std 58.395/57.12/57.375),
45
+ resize the longest side to 1024 and pad to 1024Γ—1024
46
+ - **Postprocess**: take argmax(iou) of the 3 mask logits, threshold at > 0, upsample
47
+ 4Γ— (256β†’1024) back to image space, then crop the padding. The prompt encoder is
48
+ inside the decoder β€” pass raw click coordinates.
49
+
50
+ ## Verification (Mac arm64, executorch 1.4.0, torch 2.13.0)
51
+
52
+ Both graphs match torch fp32 eager at corr 1.000000, and the wrappers compose back
53
+ to the stock modules exactly (max_abs_diff 0.000e+00).
54
+
55
+ | graph | output | shape | max_abs_diff | corr |
56
+ |-------|--------|-------|--------------|------|
57
+ | encoder | image_embed | [1, 256, 64, 64] | 3.815e-06 | 1.000000 |
58
+ | decoder | mask logits | [1, 3, 256, 256] | 1.717e-05 | 1.000000 |
59
+ | decoder | iou | [1, 3] | 1.192e-07 | 1.000000 |
60
+
61
+ Median over 10 runs, Mac arm64 single process β€” a relative reference, not a device
62
+ number: encoder 130.3 ms (torch eager 138.7 ms), decoder 20.5 ms (eager 11.2 ms).
63
+ XNNPACK delegate coverage: encoder 89.0%, decoder 80.6%.
64
+
65
+ ## Conversion
66
+
67
+ torch.export β†’ to_edge_transform_and_lower(XnnpackPartitioner) β†’ .pte
68
+ (conversion script: [executorch-models](https://github.com/john-rocky/executorch-models))
69
+
70
+ Three rewrites were needed, and the reasons generalize to other SAM-family ports:
71
+
72
+ **The constant positional embedding is precomputed.** `TwoWayTransformer.forward`
73
+ opens with `image_pe.flatten(2).permute(0, 2, 1)`. That input is constant for a
74
+ fixed image size, and leaving the reshape in the graph corrupts the block that
75
+ consumes it β€” layer 0's keys came out at corr 0.78 against eager. Handing the
76
+ transformer the already-flat tensor restores corr 1.000000. This reproduces with no
77
+ delegate at all, and every operator involved verifies clean in isolation, so it is
78
+ worth knowing about rather than rediscovering.
79
+
80
+ **Boolean-mask assignment is rewritten as arithmetic.** The prompt encoder writes
81
+ `point_embedding[labels == -1] = 0.0` and three more masked `+=`. torch.export turns
82
+ each into an `index_put` behind a `nonzero`, which is a data-dependent shape. The
83
+ equivalent `emb * (1 - m) + m * w` form has fixed shapes and no runtime guards.
84
+
85
+ **Identity `repeat_interleave` is dropped.** The mask decoder calls
86
+ `torch.repeat_interleave(x, tokens.shape[0], dim=0)` with one point batch β€” a no-op
87
+ whose lowered form the delegate mis-sizes. Note this model uses the functional form,
88
+ not the tensor method.