File size: 7,278 Bytes
bd33bcf
 
 
7262b6a
bd33bcf
 
 
 
 
 
 
 
7262b6a
bd33bcf
 
7262b6a
bd33bcf
7262b6a
bd33bcf
bb17c8b
bd33bcf
7262b6a
bd33bcf
7262b6a
 
 
 
 
 
 
 
bd33bcf
7262b6a
bd33bcf
7262b6a
bd33bcf
7262b6a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bd33bcf
 
 
 
 
 
 
 
 
 
7262b6a
bd33bcf
7262b6a
bd33bcf
7262b6a
068a086
 
 
 
 
 
7262b6a
068a086
7262b6a
bd33bcf
7262b6a
bd33bcf
7262b6a
 
bd33bcf
7262b6a
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
---
license: apache-2.0
base_model: google/siglip2-base-patch16-256
pipeline_tag: feature-extraction
tags:
  - coreml
  - siglip2
  - siglip
  - clip
  - image-text
  - embeddings
  - vision
  - apple-silicon
---

# SigLIP2 base-patch16-256 — Core ML

Core ML (`.mlpackage`) conversion of [`google/siglip2-base-patch16-256`](https://huggingface.co/google/siglip2-base-patch16-256) for on-device text and image embedding on Apple platforms. The image and text towers ship as separate models; both produce 768-dimensional, L2-normalized embeddings in a shared space, so image↔text similarity is a single dot product.

Converted and maintained by [nodevorg](https://huggingface.co/nodevorg) for [Alarian](https://alarian.ai)'s semantic frame search. Not affiliated with Google.

## Files

| file | size | sha256 |
|---|---|---|
| `ImageEncoder.mlpackage.zip` | 163 MiB | `406938456c8f8e91f01632cb74131fdfb993064d8d0bc8f6fbbd142709f1ba66` |
| `TextEncoder.int8emb.mlpackage.zip`**recommended** | 323 MiB | `11471872101a6ca88dd51ed9de668b728ae9d09c0f853aeac8a6cc96f1c109d6` |
| `TextEncoder.mlpackage.zip` | 506 MiB | `4f31f38a1729a0044f0ffc95a940dc1a6a2fdcd5fcf78a03cbb662bada6cbafb` |
| `tokenizer.json` | 33 MiB | `cb9140fae3ac5122c972d37adf83e1248471a38147ad76f8215c8872c6fd8322` |
| `tokenizer_config.json` | — | `14afe629fe4959b9e0d51e1852b8d9f7ad074f90a1a7125a4fcdd17f06e78fc8` |
| `special_tokens_map.json` | — | `baec30ea10906f16adb8c18af7a34023002c1746542612b8b41c9f09e1351351` |

`TextEncoder.int8emb` is a drop-in replacement for the fp16 `TextEncoder` — identical input/output names, shapes, and dtypes — with the token-embedding table quantized to int8 (36% smaller download, worst-case parity 0.9996; details below). Use the full-fp16 text encoder only if you need maximum precision.

The tokenizer files are copied verbatim from the source checkpoint (Gemma BPE, 256k vocabulary).

## Model interface

### ImageEncoder

- **Input**`pixel_values`: Core ML `ImageType`, RGB, 256×256. Preprocessing is baked into the model (`scale=1/127.5`, `bias=[-1,-1,-1]`, matching the checkpoint's `SiglipImageProcessor` configuration): resize to 256×256 and pass raw `[0, 255]` RGB — no caller-side normalization. Resize with an aspect-ratio squash rather than a center crop; that matches training.
- **Output**`image_embedding`: 768-dim float16, L2-normalized.

### TextEncoder (both variants)

- **Input**`input_ids`: 1×64 int32 token ids. Tokenize with the included tokenizer using `padding="max_length"`, `max_length=64`, lowercased input (`do_lower_case=true`). There is deliberately **no `attention_mask` input**: SigLIP's text tower trains with unmasked bidirectional attention over the fixed 64-token sequence and pools the last sequence position, so a mask would deviate from training.
- **Output** — `text_embedding`: 768-dim float16, L2-normalized.

Because both outputs are normalized in-graph, cosine similarity is `dot(image_embedding, text_embedding)`.

## Usage (Swift)

```swift
import CoreML

let image = try MLModel(contentsOf: compiledImageEncoderURL)
let text  = try MLModel(contentsOf: compiledTextEncoderURL)

// Image: 256×256 RGB CVPixelBuffer, raw [0, 255] values
let imageOut = try image.prediction(
    from: MLDictionaryFeatureProvider(dictionary: ["pixel_values": pixelBuffer]))
let imageEmbedding = imageOut.featureValue(for: "image_embedding")!.multiArrayValue!

// Text: 64 int32 token ids (e.g. via swift-transformers' Tokenizers module,
// loading the tokenizer.json from this repo), lowercased, padded to length 64
let ids = try MLMultiArray(shape: [1, 64], dataType: .int32) // fill with token ids
let textOut = try text.prediction(
    from: MLDictionaryFeatureProvider(dictionary: ["input_ids": ids]))
let textEmbedding = textOut.featureValue(for: "text_embedding")!.multiArrayValue!

// Similarity = dot product (embeddings are already L2-normalized)
```

## Architecture note

The source checkpoint's `config.json` declares `model_type: siglip`, not `siglip2`. At this fixed-resolution, non-NaFlex tier, the SigLIP2-trained weights are architecturally identical to SigLIP v1 — `transformers.AutoModel` resolves the checkpoint to `SiglipModel`, and that is what was converted. Relative to SigLIP v1, only the weights and the tokenizer (Gemma BPE 256k, vs. 32k SentencePiece) differ.

Most of the text encoder's size is the 256k-row token-embedding table (256000 × 768 × 2 bytes ≈ 375 MiB at fp16), which is why quantizing that single tensor recovers most of the possible size savings.

## Quantized text encoder

`TextEncoder.int8emb.mlpackage` is produced by post-training quantization of the converted fp16 model via `coremltools.optimize.coreml`, applying `OpLinearQuantizerConfig(mode="linear_symmetric", dtype="int8", granularity="per_channel")` to **only** the `text_model.embeddings.token_embedding` weight (op-name-targeted); attention and MLP weights remain fp16. The embedding table is a gather weight — each forward pass reads one row per token, so quantization error does not compound through a matmul chain.

A whole-model int8 variant was also evaluated: ≈270 MiB unzipped (vs. ≈352 MiB for embedding-only) but with worst-case parity of 0.9964 vs. 0.9996. The embedding-only variant was published as the better size/precision trade-off for retrieval workloads, where score margins matter.

## Parity vs. PyTorch reference

Cosine similarity between the PyTorch fp32 reference (`transformers`) and the converted Core ML models, on a fixture of 4 images (solid red/green/blue, synthetic sunset gradient) × 4 texts. Acceptance gate: > 0.99 per item, with unchanged text↔image rankings.

### fp16 encoders

| item | cosine |
|---|---|
| image: solid_red | 0.9999988 |
| image: solid_green | 0.9999992 |
| image: solid_blue | 0.9999992 |
| image: gradient_sky | 0.9999987 |
| text: "a red square" | 0.9999995 |
| text: "a photo of a sunset over the ocean" | 0.9999997 |
| text: "a solid blue color" | 0.9999995 |
| text: "a green field" | 0.9999996 |

Worst case **0.9999987**. Text↔image rankings match the PyTorch reference exactly across all 4×4 pairs.

### int8-embedding text encoder

| text | cosine |
|---|---|
| "a red square" | 0.99978 |
| "a photo of a sunset over the ocean" | 0.99976 |
| "a solid blue color" | 0.99962 |
| "a green field" | 0.99975 |

Worst case **0.99962**; rankings identical to both the PyTorch reference and the fp16 model.

## Conversion details

Converted from the Hugging Face `transformers` reference implementation. Each tower was traced independently (`torch.jit.trace` on thin wrappers around `SiglipModel.vision_model` / `.text_model`) and converted with:

- `convert_to="mlprogram"`, `compute_precision=FLOAT16`
- `minimum_deployment_target`: macOS 15

Pinned toolchain: `torch==2.7.0`, `transformers==5.14.1`, `coremltools==9.0`, `numpy==2.3.5`. Note that `numpy>=2.4.0` breaks the `coremltools==9.0` PyTorch frontend for models using `torch.nn.MultiheadAttention` (SigLIP's vision pooling head) — see [apple/coremltools#2633](https://github.com/apple/coremltools/issues/2633); pin `numpy<2.4.0` until the fix ships in a release.

## License

Apache-2.0, inherited from the base model. Free for commercial and closed-source use.