File size: 7,596 Bytes
e5f9bbf 84adf89 2cb83a1 51a2c60 90b05d0 51a2c60 2cb83a1 e5f9bbf d2bdf28 e5f9bbf d2bdf28 e5f9bbf 43cdcf1 d2bdf28 e5f9bbf | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | ---
license: apache-2.0
base_model: openbmb/MiniCPM-V-4.6
tags:
- core-ai
- coreai
- vision-language
- vlm
- on-device
- iphone
- apple
pipeline_tag: image-text-to-text
language:
- en
- zh
---
# MiniCPM-V-4.6 β Core AI
**On-device vision-language model for iPhone / Apple Silicon.** A Core AI port of
[`openbmb/MiniCPM-V-4.6`](https://huggingface.co/openbmb/MiniCPM-V-4.6) β the strongest
sub-2B open VLM β running fully local on the GPU via the Core AI **pipelined engine**:
pick a photo, ask about it, stream the answer.
Verified on **iPhone 17 Pro**: image β grounded answer at **~51.5 tok/s** decode, all local.
<p align="center">
<img src="https://github.com/user-attachments/assets/c4baa524-5217-4bb3-a23f-b0acd6249bd4" width="300" alt="MiniCPM-V 4.6 on iPhone β a fridge photo becomes recipe ideas, fully on-device in CoreAIChat">
</p>
<p align="center"><em>Fridge photo β recipe ideas, fully on-device on an iPhone 17 Pro (CoreAIChat).</em></p>
<!-- gen-cards:use-it begin id=minicpm-v-4.6 (managed by scripts/gen-cards β edit cards.json / QuickStart.swift, not this block) -->
## Use it
β‘ **One line** β run the kit's task op on this model
(`import CoreAIOps`; no session, no model plumbing, downloads on first use):
```swift
let caption = try await CoreAI.caption(imageAt: url, options: .model("minicpm-v-4.6"))
```
Twenty ops, one shape β [Cookbook](https://github.com/john-rocky/coreai-kit/blob/main/docs/COOKBOOK.md).
βΆοΈ **Run it (source)** β the [VLChat runner](https://github.com/john-rocky/coreai-kit/tree/main/Examples/VLChat)
(GUI + CLI, one app for every vision-language model in the catalog):
```bash
git clone https://github.com/john-rocky/coreai-kit
open coreai-kit/Examples/VLChat/VLChat.xcodeproj
# β Run, then pick "MiniCPM-V 4.6" in the model picker
# agents / headless (macOS):
cd coreai-kit/Examples/VLChat
swift run vlchat-cli --model minicpm-v-4.6 --image sample.jpg --prompt "What is in this image?"
```
π» **Build with it** β complete; the glue is kit API, copy-paste runs:
```swift
import CoreAIKit
import FoundationModels
let vlm = try await KitVisionModel(catalog: "minicpm-v-4.6")
let session = LanguageModelSession(model: vlm)
let image = try ImageFile.load(imageURL) // any image file β CGImage + EXIF orientation
let reply = try await session.respond(to: Prompt {
prompt
Attachment(image.cgImage, orientation: image.orientation)
})
// reply.content: the answer about the image, generated fully on-device
```
The take-home is [`Examples/VLChat/Sources/QuickStart.swift`](https://github.com/john-rocky/coreai-kit/blob/main/Examples/VLChat/Sources/QuickStart.swift)
β this exact code as one typed function, no UI; the CLI is an argument shell over it, and
the GUI drives the same `KitVisionModel(catalog:)` behind a `LanguageModelSession`.
Multi-turn about the same image? Hold the `LanguageModelSession` and call `respond(to:)`
per turn. The photo picker / file chooser is your app's own chrome β `ImageFile.load`
(kit API) turns any image file into model input.
**Integration checklist**
- SPM: `https://github.com/john-rocky/coreai-kit` β product **CoreAIKit**
- Info.plist: `NSPhotoLibraryUsageDescription` β only if you use PhotosPicker
- Entitlements (iOS): `com.apple.developer.kernel.increased-memory-limit`
- First run downloads the model β 2.1 GB (Mac) / 2.1 GB (iPhone) β then it loads from the
local cache (Application Support; progress via the `downloadProgress` callback)
- Measure in Release β Debug is ~3Γ slower on per-token host work
<!-- gen-cards:use-it end -->
## Architecture
MiniCPM-V-4.6 (1.3B) = a **SigLIP So400m vision tower** (980px / patch 14 / 27 layers, with a
window-attention insert-merger @ layer 6 + a downsample-MLP merger β Γ·16 = 64 visual tokens per
448px slice) + a **Qwen3.5-hybrid text backbone** (`qwen3_5_text`: 0.8B, 24 layers, GatedDeltaNet
linear attention Γ3 : full attention Γ1, head_dim 256, vocab 248094, tied head). Connector =
2Γ2 spatial merges + MLP, spliced into the text embeddings at `<image>` positions (`masked_scatter`).
## Bundles
**Recommended (optimized, 2026-06-25):**
| path | what | dtype | size |
|---|---|---|---|
| `gpu-pipelined/minicpmv46_vlm_decode_int8hu/` | VLM text decoder (`input_ids β logits` + static `image_embeds[64,1024]`; in-graph gather `ids β₯ V ? image_embeds[ids-V] : embed[ids]`) | int8 body + **untied int8 head** | ~1.2 GB |
| `gpu-pipelined/minicpmv46_vision_int8lin/` | fixed-grid SigLIP vision encoder (`pixel_values[1,3,448,448] β image_features[64,1024]`) | **int8** | ~0.6 GB |
The **int8 head** quantizes the big-vocab LM head (fp16 in `int8lin` = ~half the per-token read) β **+48% decode
on iPhone 17 Pro** (46β68 tok/s). The **int8 vision** halves the encoder's size (the encode is compute-bound, so
this is a size/memory win); pair it with a one-shot vision-graph warmup at load to hide the ~2.7 s first-photo
cold compile. Original `β¦_int8lin` decoder + fp16 `minicpmv46_vision` remain for compatibility.
The decoder is a complete qwen3.5-hybrid text LLM when `image_embeds` is zero β same bundle, no image needed.
## How a VLM rides the text-only engine
The pipelined engine knows nothing about images. The whole multimodal state rides the
**static-input hook** (`image_embeds` buffer) + an id-space trick β the graph stays `ids + positions β logits`:
- The host runs the vision encoder **once per image** (resize 448, normalize `x/127.5β1`) and writes
`image_embeds [64,1024]` into one owned MTLBuffer the engine binds on every step.
- The prompt's `<|image_pad|>` ids are rewritten to **extension ids** `V + slot` (slot 0..63).
In-graph: `embed = ids < V ? table[ids] : image_embeds[ids β V]`.
- Positions are **plain 1D** (no M-RoPE / no rope-shift), the qwen3.5-hybrid KV + conv + recurrent
states are the engine's; nothing else changes.
Simpler than the [Qwen3-VL port](https://huggingface.co/mlboydaisuke/Qwen3-VL-2B-CoreAI) (no deepstack, no M-RoPE).
## Measured (iPhone 17 Pro, iOS 27 beta, release)
- **iPhone 17 Pro decode (int8 head)**: VLM in-app A/B, same conditions, back-to-back β int8lin **51.5 β int8hu
70.0 tok/s = +36%** (the VLM bundle binds `image_embeds` every step, which dilutes the head gain; the text core
alone is 46 β 68 = +48%). ~64β70 tok/s in practice by device temperature. Β· M4 Max text core ~224 tok/s
(`llm-benchmark`), engine cold-spec ~2β4 s, ~1.5 GB resident (jetsam-safe).
- **Vision**: int8 encoder β fp16 encode time (compute-bound) at ~0.6 GB (half); the ~2.7 s first-image latency
is the SigLIP graph's cold compile β run a dummy encode at load to make the user's first photo warm (~tens of ms).
- **Numerics**: fp32-torch parity bit-exact (vision cos 1.000000, full overlay logits cos 1.00004);
Core AI engine β‘ python β‘ HF (text 24/24; image path reproduces the HF description modulo one int8
near-tie token, then reconverges).
- Real-photo example (kakigΕri): *"a bowl of shaved ice ... chunks of mango ... a dark blue saucer ...
a menu or a book, hinting at a cafΓ© ... a wooden table"* β accurate, fully on-device.
## Use it
`apps/CoreAIChat` and the standalone `MiniCPMVLM` app have a **MiniCPM-V 4.6 mode with a photo picker**:
pick an image, ask, stream. The vision tower runs once per image (~hundreds of ms); each turn re-prefills (S=1).
Conversion + gates: see [coreai-model-zoo / minicpm-v-4.6](https://github.com/john-rocky/coreai-model-zoo/blob/main/zoo/minicpm-v-4.6.md).
License: Apache-2.0 (inherited from `openbmb/MiniCPM-V-4.6`).
|