mlboydaisuke commited on
Commit
ed0f013
·
verified ·
1 Parent(s): 7d9572c

Upload README.md with huggingface_hub

Browse files
Files changed (1) hide show
  1. README.md +103 -0
README.md ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: other
3
+ tags:
4
+ - executorch
5
+ - xnnpack
6
+ - pte
7
+ - on-device
8
+ - image-text-to-text
9
+ base_model:
10
+ - LiquidAI/LFM2.5-VL-1.6B
11
+ ---
12
+ # LFM2.5-VL-1.6B — ExecuTorch
13
+
14
+ `lfm2_5_vl_1_6b_xnnpack_8da4w.pte` (2.03 GB)
15
+
16
+ - **Source**: LiquidAI/LFM2.5-VL-1.6B — SigLIP 2 vision tower (1152 wide, 27 layers) and an
17
+ LFM2 decoder (2048 wide, 16 layers)
18
+ - **License**: LFM Open License v1.0
19
+ - **Input**: a 512×512 tile as `[1, 1024, 768]` patches plus `[1, 1024]` attention mask,
20
+ and token ids for the words around it
21
+ - **Output**: logits over the 64,400-token vocabulary
22
+
23
+ One file, three entry points — the shape ExecuTorch's multimodal runner asks for:
24
+
25
+ | method | in | out |
26
+ |---|---|---|
27
+ | `vision_encoder` | patches, mask | `[1, 256, 2048]` rows in the decoder's embedding space |
28
+ | `token_embeddings` | token ids | embeddings |
29
+ | `text_model` | embeddings, positions | logits, and its own cache |
30
+
31
+ A decoder that only takes token ids cannot be told about a picture. Splitting a
32
+ vision-language model into a vision `.pte` and a text `.pte` runs aground there;
33
+ `MultimodalPrefiller::load` asks one module for `token_embeddings` and `text_model` and uses
34
+ `vision_encoder` if it finds it.
35
+
36
+ ## On a phone
37
+
38
+ 2.03 GB, and its arena is 176 MB — it fits where the fp32 export (6.93 GB) did not. Not
39
+ measured on device; the rate quoted for
40
+ [the 450M](https://huggingface.co/mlboydaisuke/LFM2.5-VL-450M-ExecuTorch) is that file's,
41
+ not this one's.
42
+
43
+ ## Verification (Mac arm64, 2026-08-21)
44
+
45
+ Greedy, through the three methods, on photographs from `convert/calib_images`:
46
+
47
+ | picture | answer |
48
+ |---|---|
49
+ | a London street | "A group of people are walking on a sidewalk in front of a building that says Pizza Express." |
50
+ | a man with a dog | "A black and white photograph shows a man standing next to a dog and a cart, with a large stack of wood in the background." |
51
+
52
+ The shop sign is read correctly, which is the check that matters: a caption that fits any
53
+ street would not tell you the vision half was wired up right.
54
+
55
+ The vision half of this file agrees with the untouched model at **worst corr 0.98877** over
56
+ 24 photographs.
57
+
58
+ ## Square the picture first
59
+
60
+ The processor picks a tile grid from the aspect ratio — a 768×477 photograph becomes one
61
+ 24×40 tile, a 1280×960 one becomes seven — and this graph takes 32×32. Centre-crop to a
62
+ square before the processor sees it and every picture becomes exactly one tile. Feeding a
63
+ stretched square instead is visible in the output: asked about a squashed street, the model
64
+ called the scene "distorted and warped", which was a fair description of what it had been
65
+ given.
66
+
67
+ ```bash
68
+ python convert/run_lfm2_vl.py <image> "What is in this picture?"
69
+ ```
70
+
71
+ ## Conversion
72
+
73
+ `convert/export_lfm2_vl_bundle.py`. Four things needed re-authoring:
74
+
75
+ - **SigLIP 2 reads its grid out of a tensor** to size the position embeddings, which
76
+ `torch.export` cannot follow. The grid is fixed here, so the resize is computed once and
77
+ the constant handed to a replacement forward.
78
+ - **The projector wants the grid back.** The tower returns a flat run of patches; the
79
+ projector's pixel-unshuffle trades resolution for channels and needs to know which patches
80
+ are neighbours.
81
+ - **The quantization is not uniform.** The decoder is int4 over groups of 32; the vision
82
+ tower is int8 per output channel. At int4 throughout, the model reads the Pizza Express
83
+ sign as "Pocket Express" — the letters live in the tower, and four bits does not hold
84
+ them.
85
+ - **The cache lives inside `text_model`.** ExecuTorch copies a mutable buffer into each
86
+ method that names it (`Program::load_mutable_subsegment_into` writes into the method's own
87
+ memory), so a prefill method and a decode method would each get their own and neither
88
+ would see the other's writes. One graph has to serve both, which rules out the two code
89
+ paths transformers keeps for LFM2's short convolution — a windowed convolution for a
90
+ prompt, a fused single-step update for a token, chosen in Python and therefore baked in by
91
+ tracing. Carrying the last `kernel - 1` columns and putting them in front of whatever
92
+ arrives is the same arithmetic in one branch-free path.
93
+
94
+ ## The number that decides whether it runs on a phone
95
+
96
+ `CONTEXT`, the upper bound on the dynamic sequence dimension. The memory planner sizes its
97
+ arena for the bound, not for what a picture costs: at 4096 that arena is **3.7 GB** and iOS
98
+ kills the process with signal 9 before the first method has finished loading. One square
99
+ photograph is 1024 patches, which the projector unshuffles to 256 rows, so 512 leaves room
100
+ for a long question and a long answer and brings the arena to 176 MB.
101
+
102
+ (conversion scripts: [executorch-models](https://github.com/john-rocky/executorch-models) ·
103
+ iOS sample: [executorch-samples](https://github.com/john-rocky/executorch-samples))