mlboydaisuke commited on
Commit
9b60f0b
·
verified ·
1 Parent(s): 04ebb3e

Upload README.md with huggingface_hub

Browse files
Files changed (1) hide show
  1. README.md +35 -9
README.md CHANGED
@@ -11,14 +11,14 @@ base_model:
11
  ---
12
  # LFM2.5-VL-450M — ExecuTorch
13
 
14
- `lfm2_5_vl_450m_xnnpack.pte` (2.07 GB)
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 65,536-token vocabulary
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 with a dog is standing in front of a stack of logs with a water tower in the background." |
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 encoder alone agrees with the untouched model at **corr 0.99996** (XNNPACK fp32,
50
- against `get_image_features` on real imagery).
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`. Two things needed re-authoring:
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
- (conversion scripts: [executorch-models](https://github.com/john-rocky/executorch-models))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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))