File size: 4,370 Bytes
79c5a5c
 
 
 
 
 
 
 
 
 
 
 
 
 
bfc9b0e
 
 
79c5a5c
bfc9b0e
 
 
 
79c5a5c
bfc9b0e
79c5a5c
bfc9b0e
 
 
 
 
79c5a5c
 
 
bfc9b0e
 
9beb623
 
 
bfc9b0e
9beb623
 
 
 
 
 
bfc9b0e
 
 
 
 
 
 
 
 
9beb623
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bfc9b0e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79c5a5c
 
 
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
---
license: apache-2.0
tags:
- executorch
- xnnpack
- pte
- on-device
- sentence-similarity
- feature-extraction
base_model:
- sentence-transformers/all-MiniLM-L12-v2
---
# all-MiniLM-L12-v2 β€” ExecuTorch

all-MiniLM at twice the depth, on the same recipe. Text in, one
384-dimensional vector out, for search and retrieval that never leaves the
device.

- **Source**: sentence-transformers/all-MiniLM-L12-v2 β€” 12 layers, 384 dimensions, 30,522 vocabulary
- **License**: apache-2.0
- **Input**: `input_ids` and `attention_mask`, both `[1, 256]` int64
- **Output**: `[1, 384]`, mean-pooled and L2-normalised inside the graph

## The recipe is in the graph, and it was read off this repo

sentence-transformers stores it per model, and the shelf's seven embedding models do
not agree. This one pools **mean** and
**normalises**, read from
`1_Pooling/config.json` and `modules.json` rather than inferred from the family name.
Getting it wrong does not throw; it returns vectors that look fine and rank wrong.

## Verification

| build | file | size (MB) | Mac ms* | backend takes | worst cosine vs eager | retrieval budget |
|---|---|---|---|---|---|---|
| fp32 | `embed_all_minilm_l12_xnnpack_fp32.pte` | 133.0 | 20.6 | 78.5% | 1.000000 | 0% |
| fp16 | `embed_all_minilm_l12_xnnpack_fp16.pte` | 66.7 | 32.0 | 67.6% | 0.999999 | 4% |
| Core ML (fp16, iOS) | `embed_all_minilm_l12_coreml_all.pte` | 67.2 | 3.7 | 100.0% | 0.999969 | 23% |

\*Mac arm64, one 256-token sequence, **fastest of five medians of ten** β€” a reference
point for relative cost, not a device number. The host shares its cores with other work,
and a single median does not survive that: the same eager model here measured 19.6 ms and
182.8 ms twenty minutes apart. Contention only ever adds time, so the fastest repetition is
the one that means something. Torch eager fp32, measured the same way, is
19.1 ms.

Cosine is measured against the model run in eager through its own pooling, over eight
sentences. The last column is the one that decides: rank those eight against each
other, and ask whether this build's score error is smaller than the gap between the
document a query retrieves and the runner-up. Every shipped build keeps all eight
top-1 results.

## The attention is eager, and that is the faster export

`F.scaled_dot_product_attention` does not survive export as one operation. The edge
dialect lowers it through `_safe_softmax`, whose guard against a row with no unmasked key
at all leaves **11 operations XNNPACK cannot take, in every attention
block** β€” `scalar_tensor`, `where`, `mul.Scalar`, `logical_not`, `eq`, `full_like`, `any.dim`. Each one cuts the subgraph in two.

The switch is `attn_implementation="eager"`: transformers then builds the mask
itself, as `torch.finfo(dtype).min`, instead of handing `F.sdpa` a **boolean** mask
for PyTorch to fill with `-inf`.

The guard is emitted whether or not it can ever fire, and here it cannot: it triggers only
on `-inf`, and this arm never produces one. So the two differ only about rows that have no
unmasked key at all β€” sdpa zeroes them, this one gives them a uniform row β€” and those are
padding rows, which the pooling discards and which every real query row masks out anyway.
Measured with all but eight positions masked, as adversarial as this shape gets, the two
graphs agree to 1.4e-07.

XNNPACK fp32 goes from **62.6% to 78.5%** delegated.

## Not shipped: int8

`embed_all_minilm_l12_xnnpack_int8.pte` is **69.5 MB** against fp16's 66.7 MB. Dynamic int8 quantises the
linear weights and leaves the token embedding table in fp32, and here that table is
47 MB of the 133.0 MB model β€” **35%**. The size a build comes out at is
`0.5 + 1.5 x (table share)` times the fp16 build; at 35% that is
1.03, so there was never a smaller file to be had.

It is withheld on the number that decides. Ranking the eight test sentences against
each other, this build moves a pair score by at most **0.0107** while the
closest fp32 decision β€” the gap between the document a query retrieves and the
runner-up β€” is **0.0078**. That is **138%** of
the room available, against a bar of 50%.

Correlation reads 0.998465 for this build, which no correlation gate
would stop.

torch.export -> to_edge_transform_and_lower(partitioner) -> .pte
(conversion scripts: [executorch-models](https://github.com/john-rocky/executorch-models))