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