|
Download README.md from NagaYu/SmolVLM-256M-Instruct-CoreML: direct link, hf CLI and curl.
- Browser
- Download file 13 kB
-
https://huggingface.co/NagaYu/SmolVLM-256M-Instruct-CoreML/resolve/main/README.md
- Command line
-
hf download hf://NagaYu/SmolVLM-256M-Instruct-CoreML/README.md
-
curl -L -o README.md https://huggingface.co/NagaYu/SmolVLM-256M-Instruct-CoreML/resolve/main/README.md
13 kB
| license: apache-2.0 | |
| base_model: HuggingFaceTB/SmolVLM-256M-Instruct | |
| library_name: coreml | |
| pipeline_tag: image-text-to-text | |
| language: | |
| - en | |
| tags: | |
| - coreml | |
| - core-ml | |
| - apple-silicon | |
| - on-device | |
| - vlm | |
| - smolvlm | |
| - image-to-text | |
| # SmolVLM-256M β Core ML | |
| Core ML (`.mlpackage`) builds of [HuggingFaceTB/SmolVLM-256M-Instruct](https://huggingface.co/HuggingFaceTB/SmolVLM-256M-Instruct), | |
| converted for on-device image captioning and VQA on Apple silicon. | |
| > **Two of the four compute-unit settings load successfully and return garbage.** They do not | |
| > crash, so nothing tells you they are wrong. Use `.all` or `.cpuAndGPU`, and compile the | |
| > package before loading it. Details below. | |
| Captions an image in **~2 s of model time on macOS 27.2** (~1.3 s on macOS 26.6.2), entirely on device. The companion Swift CLI | |
| that drives these packages has **no Python and no third-party runtime dependencies**, and | |
| its preprocessing is bit-exact with the HuggingFace reference β a libjpeg-compatible JPEG | |
| decoder and a Pillow-exact LANCZOS resampler, both reimplemented in Swift and re-verified | |
| against golden data on every run. | |
| ``` | |
| image βββΊ vision (fp16) βββΊ prefill (K/V as plain outputs) | |
| β | |
| text βββΊ BPE tokenizer ββββββββββ΄ββΊ decode (S=1, MLState) βββΊ tokens | |
| ``` | |
| ## β οΈ Compute units: pick `.all` or `.cpuAndGPU`, and compile first | |
| Measured on macOS 26.5 / coremltools 9.0, loading the **precompiled `.mlmodelc`** from a | |
| Swift app: | |
| | setting | loads | correct | notes | | |
| |---|---|---|---| | |
| | `.all` | yes | **yes** | fastest measured β vision ~87 ms, decode ~44 tok/s | | |
| | `.cpuAndGPU` | yes | **yes** | vision ~167 ms, decode ~43 tok/s | | |
| | `.cpuAndNeuralEngine` | yes | **no** | emits `<end_of_utterance>` from the first token | | |
| | `.cpuOnly` | yes | **no** | caption collapses to `This Is That Is.` | | |
| Correctness needs the GPU in the mix. The two GPU-less settings **do not crash** β they | |
| silently return wrong output, which is the harder failure to notice. | |
| Separately, loading the `.mlpackage` **from Python** with `compute_units=ALL` | |
| (`ct.models.MLModel(...)`) still crashes with SIGSEGV on this environment β re-confirmed on | |
| macOS 27.2 with the real `smolvlm_decode_hybrid.mlpackage`, where it dies during the load. Compile to | |
| `.mlmodelc` first, or stay on `.cpuAndGPU` in Python. On macOS 27.2 the CLI's `auto` probe still | |
| selects `.all`; `.cpuAndNeuralEngine` and `.cpuOnly` were not re-tested there. | |
| Because "it loaded" is not evidence of correctness, the companion CLI defaults to | |
| `--compute-units auto`: it probes each candidate in a **subprocess** (these failures are | |
| process-killing signals, not catchable errors) and accepts one only if its output token | |
| matches `.cpuAndGPU`. The result is cached per OS build, costing about 0.7 s once. | |
| ## Companion repos | |
| - **Verify with the dataset β [NagaYu/smolvlm-coreml-golden](https://huggingface.co/datasets/NagaYu/smolvlm-coreml-golden)** (~43 MB). It holds the golden token ids, pixel tensors, raw PIL decode and test image that `smolvlm --check --root .` compares against on every run. Download it next to these packages if you want the bit-exactness claims on this card proven on your machine rather than taken on faith. | |
| - **Compare against the Space β `smolvlm-coreml-oracle` (currently not provided).** The Space is not published at the moment, so there is no link; its source is in [`space/`](https://github.com/NagaYu/smolvlm-coreml/tree/main/space) of the companion repo. It runs the original SmolVLM-256M in **PyTorch on CPU**; Hugging Face Spaces are Linux, so Core ML cannot execute there and this Space is deliberately *not* a demo of these packages. It is the OS-independent reference oracle β it emits the reference artifacts you diff your on-device Core ML run against, and its output does not move when your Mac's OS moves. | |
| > **Arrived here after an Apple OS update?** Read [docs/os-upgrade.md](https://github.com/NagaYu/smolvlm-coreml/blob/main/docs/os-upgrade.md) first β these packages still load (built with `minimum_deployment_target = iOS18`, specificationVersion 9 = iOS18/macOS15), but the goldens and the compute-unit verdicts are stamped to macOS 26.5 / coremltools 9.0 (the timings below were re-measured on macOS 27.2), and that guide is the checklist for re-proving them. | |
| ## Files | |
| | File | Role | | |
| |---|---| | |
| | `smolvlm_vision.mlpackage` | Vision encoder + connector (fp16) | | |
| | `smolvlm_prefill_kv.mlpackage` | Batch prefill, P=128, K/V returned as plain outputs (fp16) | | |
| | `smolvlm_decode_hybrid.mlpackage` | S=1 decode, state only for generated tokens (fp16) | | |
| | `smolvlm_prefill_kv_1024.mlpackage` | Prefill for the 13-tile hi-res split path, P=1024 | | |
| | `smolvlm_decode_hybrid_1024.mlpackage` | Decode for the split path | | |
| | `*_int8.mlpackage` | INT8 per-channel **asymmetric** variants of the prefill/decode packages β roughly half the size, see the caveat below | | |
| There is deliberately **no INT8 vision package**: quantizing the vision tower drops cosine | |
| similarity against the torch reference from 1.00000 to 0.99407, which is enough to change the | |
| caption wording (still accurate, but no longer matching the HuggingFace output). Vision stays fp16. | |
| All packages were converted with **coremltools 9.0**. Other versions may lower the graph | |
| differently; regenerate with `convert_all.py` if you need a different toolchain. | |
| ## Usage | |
| The Swift CLI, the conversion scripts, and the bit-exactness verification all live in the | |
| companion repository: **[NagaYu/smolvlm-coreml](https://github.com/NagaYu/smolvlm-coreml)**. | |
| The CLI resolves both the `.mlpackage` files **and** `exports/` from a single `--root` | |
| directory, so download the packages and runtime assets **into the cloned repo**. This repo | |
| mirrors the clone's layout, so `--local-dir .` puts every file where `--root .` looks for it: | |
| ```bash | |
| git clone https://github.com/NagaYu/smolvlm-coreml && cd smolvlm-coreml | |
| # download only the minimal fp16 set + runtime assets, into the repo root | |
| hf download NagaYu/SmolVLM-256M-Instruct-CoreML --local-dir . \ | |
| --include "smolvlm_vision.mlpackage/*" \ | |
| --include "smolvlm_prefill_kv.mlpackage/*" \ | |
| --include "smolvlm_decode_hybrid.mlpackage/*" \ | |
| --include "exports/embeddings.bin" --include "exports/tok/*" --include "exports/specials.json" | |
| cd SmolVLMSwift && swift build -c release && cd .. | |
| ./SmolVLMSwift/.build/release/smolvlm --image test_image.jpg --root . | |
| ./SmolVLMSwift/.build/release/smolvlm --image test_image.jpg --question "How many cats?" --root . | |
| ./SmolVLMSwift/.build/release/smolvlm --image test_image.jpg --split --root . # 13-tile hi-res | |
| ./SmolVLMSwift/.build/release/smolvlm --check --root . # re-prove bit-exactness | |
| ``` | |
| Add `--include "*_1024*"` for the hi-res split path, or `--include "*_int8*"` for the | |
| INT8 variants. The non-split goldens for `--check` are committed in the GitHub repo; the | |
| split-path pixel golden is in the dataset repo. You can also regenerate every package | |
| yourself with `python convert_all.py && python export_artifacts.py` instead of downloading them. | |
| ## Measured performance | |
| Apple M2 MacBook Pro, macOS 27.2 (build 26B123), fp16, warm process, median of 10 runs after | |
| 2 warmups. The CLI default (`--compute-units auto`) still selects `.all` here; `.cpuAndGPU` | |
| is shown as the conservative fallback. The `--split` column was not re-measured on 27.2 and | |
| is the earlier macOS 26.6.2 measurement: | |
| | Stage | `.all` (auto-selected) | `.cpuAndGPU` | `--split`, 13 tiles (P=1024, `.cpuAndGPU`) β 26.6.2 | | |
| |---|---|---|---| | |
| | Model load (warm) | 0.25 s | 0.2 s | 0.3 s | | |
| | Vision | ~90 ms (1 tile) | ~168 ms | ~1.3 s (13 tiles) | | |
| | Prefill + first logits | ~950 ms (81 tok) | ~960 ms | ~300 ms (876 tok) | | |
| | Decode | ~41 tok/s | ~41 tok/s | ~34 tok/s | | |
| | 40-token caption, model time | ~2.0 s | ~2.1 s | β | | |
| On macOS 26.6.2 the same packages measured vision ~87 ms / ~167 ms, prefill + first logits | |
| ~200 ms / ~180 ms and decode ~43 / ~40 tok/s (`.all` / `.cpuAndGPU`), for a ~1.3 s caption. | |
| The 27.2 prefill slowdown reproduces with both the original compile and a fresh 27.2 compile, | |
| so it comes from the OS runtime, not the packages. Decode rate varies by a few tok/s between | |
| sessions. The first run in a fresh process also pays a cold-start penalty on vision and | |
| prefill; the numbers above are warm. | |
| ## Why the architecture looks like this | |
| The obvious design β a multifunction package where `prefill` and `decode` share `MLState`, | |
| as the WWDC-era examples suggest β does not survive on this runtime. It crashes in the | |
| decode loop under every compute-unit combination tested, including `CPU_ONLY Γ CPU_ONLY`; | |
| in the run that was instrumented step-by-step, the first cross-function predict succeeded | |
| before the loop died. The same goes for `write_state` (appears to succeed, round-trips | |
| cleanly through `read_state`, then dies dozens of predicts later) and for passing one | |
| model's state into another model's `predict` (immediate crash). | |
| So state never crosses a boundary here. **Prefill is stateless** and returns all 30 layers' | |
| K/V as ordinary outputs (`[1, 90, P, 64]`); **decode takes them back as ordinary inputs** | |
| and keeps `MLState` only for the tokens it generates itself. That design ported to Swift | |
| without a single crash. | |
| The full catalog of seven reproducible crashes, with the reasoning and repro steps: | |
| **[the field report](https://github.com/NagaYu/smolvlm-coreml/blob/main/docs/field-report.md)**. | |
| ## Verified parity | |
| The companion CLI's `--check` mode re-proves the tokenizer, JPEG decode and resize rows on | |
| every run (re-confirmed on macOS 27.2 / Swift 6.4: 81/81, 876/876, 0/255, maxdiff 0.000000). | |
| It does not run Core ML; the normalization, prefill and caption rows were measured against | |
| the Python pipeline separately. | |
| | Component | Match level | | |
| |---|---| | |
| | Tokenizer | Exact β 81/81 tokens non-split, 876/876 split | | |
| | JPEG decode | Bit-exact vs libjpeg, maxdiff 0/255 | | |
| | Resize | Bit-exact vs Pillow LANCZOS (down to its fixed-point arithmetic) | | |
| | Normalization | Bit-exact, all 256 values identical in fp16 | | |
| | Prefill embeddings / K/V | Bitwise identical to the Python Core ML pipeline | | |
| | Caption output | Character-identical to the Python Core ML pipeline, deterministic across runs | | |
| The non-split caption also matches HuggingFace `transformers` greedy output token-for-token. | |
| ## Limitations β please read | |
| - **Environment-stamped.** The packages were built and the goldens exported on macOS 26.5 / | |
| coremltools 9.0 / Apple silicon; the timings, the `--check` results and the Python `ALL` | |
| crash were re-measured on macOS 27.2 (Apple M2, Swift 6.4) without rebuilding anything. A | |
| later OS may fix the crashes described here β or quietly change the numerics or the | |
| speed. All three have been observed. | |
| - **Greedy output drifts across OS sessions.** With bit-identical inputs and artifacts, | |
| greedy decode produced different captions across OS sessions days apart, while remaining | |
| fully deterministic within a session. Likely last-ulp changes in GPU kernels between | |
| system updates β probably not a bug, but it means **never use a saved caption string as a | |
| regression baseline across OS updates.** Re-derive goldens against the current OS. | |
| - **INT8 costs ~12 s of GPU shader compilation per process.** Core ML does not cache it | |
| across launches, so you trade a cold-start tax on every run for roughly half the size. | |
| Worth it for a long-lived process, not for a CLI β which is why fp16 is the default. | |
| - **Split-path captions can differ from a torch-fp32-vision HuggingFace run in late | |
| tokens.** This is inherent to running the vision tower in Core ML fp16, not a porting | |
| bug: the *Python* Core ML pipeline diverges from torch-fp32 in exactly the same place. | |
| - **This is a reference implementation for one model, not a general library.** Dimensions | |
| (576 hidden, 30 layers) are hardcoded where it keeps the code readable. | |
| - The bundled JPEG decoder covers baseline 4:4:4 and grayscale β what libjpeg parity was | |
| verified against. Other JPEGs fall back to CoreGraphics (Β±4/255 decode variance). | |
| ## Licenses & attribution | |
| - **Weights**: derivative works of [HuggingFaceTB/SmolVLM-256M-Instruct](https://huggingface.co/HuggingFaceTB/SmolVLM-256M-Instruct), | |
| Apache 2.0. All credit for the model itself goes to the SmolVLM authors at HuggingFaceTB. | |
| - **Conversion scripts and Swift CLI**: MIT, in the [companion repository](https://github.com/NagaYu/smolvlm-coreml). | |
| ## Citation | |
| If the crash catalog or the stateless-prefill design is useful in your own work, a link to | |
| the [field report](https://github.com/NagaYu/smolvlm-coreml/blob/main/docs/field-report.md) | |
| or the [repository](https://github.com/NagaYu/smolvlm-coreml) is appreciated. Reports of | |
| these crashes reproducing β or *not* reproducing β on other OS versions are especially | |
| welcome; the more independent reproductions exist, the faster they get looked at. | |