Upload README.md with huggingface_hub
Browse files
README.md
CHANGED
|
@@ -11,14 +11,14 @@ base_model:
|
|
| 11 |
---
|
| 12 |
# LFM2.5-VL-450M — ExecuTorch
|
| 13 |
|
| 14 |
-
`
|
| 15 |
|
| 16 |
- **Source**: LiquidAI/LFM2.5-VL-450M — SigLIP 2 vision tower (768 wide, 12 layers) and an
|
| 17 |
LFM2 decoder (1024 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
|
| 22 |
|
| 23 |
One file, three entry points — the shape ExecuTorch's multimodal runner asks for:
|
| 24 |
|
|
@@ -26,13 +26,18 @@ One file, three entry points — the shape ExecuTorch's multimodal runner asks f
|
|
| 26 |
|---|---|---|
|
| 27 |
| `vision_encoder` | patches, mask | `[1, 256, 1024]` rows in the decoder's embedding space |
|
| 28 |
| `token_embeddings` | token ids | embeddings |
|
| 29 |
-
| `text_model` | embeddings, positions | logits |
|
| 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 |
## Verification (Mac arm64, 2026-08-21)
|
| 37 |
|
| 38 |
Greedy, through the three methods, on photographs from `convert/calib_images`:
|
|
@@ -40,14 +45,14 @@ Greedy, through the three methods, on photographs from `convert/calib_images`:
|
|
| 40 |
| picture | answer |
|
| 41 |
|---|---|
|
| 42 |
| a London street | "A bustling street scene with people walking, outdoor seating, and various storefronts, including a prominent Pizza Express." |
|
| 43 |
-
| a man with a dog | "A man
|
| 44 |
| a studio portrait | "A man in a gray long-sleeve shirt poses against a white background…" |
|
| 45 |
|
| 46 |
The shop sign is read correctly, which is the check that matters: a caption that fits any
|
| 47 |
street would not tell you the vision half was wired up right.
|
| 48 |
|
| 49 |
-
The vision
|
| 50 |
-
|
| 51 |
|
| 52 |
## Square the picture first
|
| 53 |
|
|
@@ -64,7 +69,7 @@ python convert/run_lfm2_vl.py <image> "What is in this picture?"
|
|
| 64 |
|
| 65 |
## Conversion
|
| 66 |
|
| 67 |
-
`convert/export_lfm2_vl_bundle.py`.
|
| 68 |
|
| 69 |
- **SigLIP 2 reads its grid out of a tensor** to size the position embeddings, which
|
| 70 |
`torch.export` cannot follow. The grid is fixed here, so the resize is computed once and
|
|
@@ -72,5 +77,26 @@ python convert/run_lfm2_vl.py <image> "What is in this picture?"
|
|
| 72 |
- **The projector wants the grid back.** The tower returns a flat run of patches; the
|
| 73 |
projector's pixel-unshuffle trades resolution for channels and needs to know which patches
|
| 74 |
are neighbours.
|
| 75 |
-
|
| 76 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
---
|
| 12 |
# LFM2.5-VL-450M — ExecuTorch
|
| 13 |
|
| 14 |
+
`lfm2_5_vl_450m_xnnpack_8da4w.pte` (566 MB)
|
| 15 |
|
| 16 |
- **Source**: LiquidAI/LFM2.5-VL-450M — SigLIP 2 vision tower (768 wide, 12 layers) and an
|
| 17 |
LFM2 decoder (1024 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 |
|
|
|
|
| 26 |
|---|---|---|
|
| 27 |
| `vision_encoder` | patches, mask | `[1, 256, 1024]` 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 an iPhone 17 Pro
|
| 37 |
+
|
| 38 |
+
XNNPACK, CPU, Release build: opens in **0.3 s**, answers at **37 tok/s** (96 tokens in
|
| 39 |
+
2.6 s). Measured in the sample app below, not extrapolated from a single inference.
|
| 40 |
+
|
| 41 |
## Verification (Mac arm64, 2026-08-21)
|
| 42 |
|
| 43 |
Greedy, through the three methods, on photographs from `convert/calib_images`:
|
|
|
|
| 45 |
| picture | answer |
|
| 46 |
|---|---|
|
| 47 |
| a London street | "A bustling street scene with people walking, outdoor seating, and various storefronts, including a prominent Pizza Express." |
|
| 48 |
+
| a man with a dog | "A man in a hat and overalls stands next to a wagon loaded with logs, with a water tower in the background." |
|
| 49 |
| a studio portrait | "A man in a gray long-sleeve shirt poses against a white background…" |
|
| 50 |
|
| 51 |
The shop sign is read correctly, which is the check that matters: a caption that fits any
|
| 52 |
street would not tell you the vision half was wired up right.
|
| 53 |
|
| 54 |
+
The vision half of this file agrees with the untouched model at **worst corr 0.98877** over
|
| 55 |
+
24 photographs.
|
| 56 |
|
| 57 |
## Square the picture first
|
| 58 |
|
|
|
|
| 69 |
|
| 70 |
## Conversion
|
| 71 |
|
| 72 |
+
`convert/export_lfm2_vl_bundle.py`. Four things needed re-authoring:
|
| 73 |
|
| 74 |
- **SigLIP 2 reads its grid out of a tensor** to size the position embeddings, which
|
| 75 |
`torch.export` cannot follow. The grid is fixed here, so the resize is computed once and
|
|
|
|
| 77 |
- **The projector wants the grid back.** The tower returns a flat run of patches; the
|
| 78 |
projector's pixel-unshuffle trades resolution for channels and needs to know which patches
|
| 79 |
are neighbours.
|
| 80 |
+
- **The quantization is not uniform.** The decoder is int4 over groups of 32; the vision
|
| 81 |
+
tower is int8 per output channel. At int4 throughout, the model reads the Pizza Express
|
| 82 |
+
sign as "Pocket Express" — the letters live in the tower, and four bits does not hold
|
| 83 |
+
them.
|
| 84 |
+
- **The cache lives inside `text_model`.** ExecuTorch copies a mutable buffer into each
|
| 85 |
+
method that names it (`Program::load_mutable_subsegment_into` writes into the method's own
|
| 86 |
+
memory), so a prefill method and a decode method would each get their own and neither
|
| 87 |
+
would see the other's writes. One graph has to serve both, which rules out the two code
|
| 88 |
+
paths transformers keeps for LFM2's short convolution — a windowed convolution for a
|
| 89 |
+
prompt, a fused single-step update for a token, chosen in Python and therefore baked in by
|
| 90 |
+
tracing. Carrying the last `kernel - 1` columns and putting them in front of whatever
|
| 91 |
+
arrives is the same arithmetic in one branch-free path.
|
| 92 |
+
|
| 93 |
+
## The number that decides whether it runs on a phone
|
| 94 |
+
|
| 95 |
+
`CONTEXT`, the upper bound on the dynamic sequence dimension. The memory planner sizes its
|
| 96 |
+
arena for the bound, not for what a picture costs: at 4096 that arena is **3.7 GB** and iOS
|
| 97 |
+
kills the process with signal 9 before the first method has finished loading. One square
|
| 98 |
+
photograph is 1024 patches, which the projector unshuffles to 256 rows, so 512 leaves room
|
| 99 |
+
for a long question and a long answer and brings the arena to 164 MB.
|
| 100 |
+
|
| 101 |
+
(conversion scripts: [executorch-models](https://github.com/john-rocky/executorch-models) ·
|
| 102 |
+
iOS sample: [executorch-samples](https://github.com/john-rocky/executorch-samples))
|