Repository navigation
Commit 04063c0
fix(llm): snapshot vision encoder output before caching (#1229)
## Description
In any multimodal conversation with more than one image, the model
starts describing earlier images as the most recently sent one on later
turns.
`VisionEncoder::encode` caches the `EValue` returned by
`vision_encoder.execute()` per image path. That tensor aliases the
method's reusable output buffer, so the next `execute()` (the second
image, or any later encode) overwrites the bytes behind every cached
entry. On re-prefilled turns the prefiller then splices the latest
image's embeddings into every image slot. The audio path already
snapshots its encoder output for exactly this reason (see the
`AudioSlot` comment in `multimodal_prefiller.cpp`); vision never got the
same treatment.
The fix copies the encoder output into bytes owned by the cache entry
immediately after `execute()` and serves cache hits from a tensor
wrapping those owned bytes (`unordered_map` nodes are pointer-stable, so
the blob stays valid).
The bug is backend-independent (the cache sits above the delegate), so
XNNPACK/Vulkan multimodal models are affected the same way.
### Introduces a breaking change?
- [ ] Yes
- [x] No
### Type of change
- [x] Bug fix (change which fixes an issue)
- [ ] New feature (change which adds functionality)
- [ ] Documentation update (improves or adds clarity to existing
documentation)
- [ ] Other (chores, tests, code style improvements etc.)
### Tested on
- [x] iOS
- [ ] Android
### Testing instructions
1. Run the example LLM app with a multimodal model (e.g. Gemma 4 E2B
multimodal) on the Multimodal LLM screen.
2. Send image A with "What's in this picture?" — answer is correct.
3. Send image B (different content) with the same question — answer is
correct.
4. Ask "What was in the FIRST picture I sent?".
Before this fix, step 4 describes image B's content (both image slots
receive B's embeddings on the re-prefilled turn). After the fix, the
model correctly recalls image A.
### Screenshots
N/A
### Related issues
N/A
### Checklist
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have updated the documentation accordingly
- [x] My changes generate no new warnings
### Additional notes
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>1 parent 140bf84 commit 04063c0
2 files changed
Lines changed: 28 additions & 6 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
112 | 112 | | |
113 | 113 | | |
114 | 114 | | |
115 | | - | |
| 115 | + | |
116 | 116 | | |
117 | 117 | | |
118 | 118 | | |
| |||
128 | 128 | | |
129 | 129 | | |
130 | 130 | | |
131 | | - | |
132 | | - | |
133 | | - | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
134 | 143 | | |
135 | 144 | | |
136 | 145 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| 5 | + | |
5 | 6 | | |
| 7 | + | |
6 | 8 | | |
7 | 9 | | |
8 | 10 | | |
9 | 11 | | |
| 12 | + | |
10 | 13 | | |
11 | 14 | | |
12 | 15 | | |
| |||
26 | 29 | | |
27 | 30 | | |
28 | 31 | | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
29 | 43 | | |
30 | 44 | | |
31 | 45 | | |
32 | 46 | | |
33 | 47 | | |
34 | | - | |
35 | | - | |
| 48 | + | |
36 | 49 | | |
37 | 50 | | |
38 | 51 | | |
0 commit comments