Views
No views yet
Package.swift:1.package(url: "https://github.com/john-rocky/CoreML-LLM", branch: "main"),
2
3// In your target:
4.product(name: "CoreMLLLM", package: "CoreML-LLM"),1import CoreMLLLM
2
3let llm = try await CoreMLLLM.load(repo: "mlboydaisuke/qwen3-vl-2b-stateful-coreml")
4
5let stream = try await llm.generate(
6 [CoreMLLLM.Message(role: .user, content: "Hello!")],
7 maxTokens: 256
8)
9for await chunk in stream { print(chunk, terminator: "") }1import CoreGraphics
2
3let cgImage: CGImage = ... // your CGImage
4
5let stream = try await llm.generate(
6 [CoreMLLLM.Message(role: .user,
7 content: "What's in this image?")],
8 image: cgImage,
9 maxTokens: 256
10)
11for await chunk in stream { print(chunk, terminator: "") }Qwen/Qwen3-VL-2B-Instruct for iPhone / iPad / Mac Apple Neural Engine. Text + vision, INT8, 2.3 GB on disk.| Phase 1 (this repo) | v1.4.0 recurrent | |
|---|---|---|
| decode tok/s | 22–24 | ~10 |
| prefill tok/s (text) | ~260 effective | batched T=8 |
phys_footprint | 256–264 MB | ~1.7 GB |
| vision TTFT (first turn, ~200 prompt tokens) | ~2.7 s | ~5.5 s |
MLState + slice_update so there is no silent GPU spill.qwen3_vl_2b_stateful_chunks/
├── chunk_0.mlpackage ← multifunction: infer (T=1) + prefill_b8 (T=8)
├── chunk_1.mlpackage same
├── chunk_2.mlpackage same
├── chunk_3.mlpackage same
├── chunk_0_vision.mlpackage ← chunk_0 + DeepStack injection (multifunction)
├── chunk_head.mlpackage ← final_norm + lm_head + in-graph argmax
└── embed_weight.bin ← raw fp16 embed table (151936 × 2048), Swift mmaps it
qwen3_vl_2b_vision/
└── vision.mlpackage ← 448×448 → 196 tokens + 3 DeepStack tapsct.StateType named kv_cache_0 (shape (14, 8, 2048, 128) fp16):infer — T=1 decodeprefill_b8 — T=8 batched prefillMLState once from the prefill model instance and re-uses it across both functions — Core ML binds state by name+shape, not by MLModel instance (per the ANEMLL Qwen3-1.7B recipe).model_config.json — Core ML packs shapes into each .mlpackage. coremltools reads them directly.1from transformers import AutoTokenizer, AutoProcessor
2tok = AutoTokenizer.from_pretrained("Qwen/Qwen3-VL-2B-Instruct")
3proc = AutoProcessor.from_pretrained("Qwen/Qwen3-VL-2B-Instruct")Vision preprocessing: mean=std=0.5 (not CLIP defaults);pixel_valuesshape is(3, 2, 448, 448)(pre-patchified). Seeconversion/build_qwen3_vl_2b_vision.py.
1import coremltools as ct, numpy as np
2from huggingface_hub import snapshot_download
3
4local = snapshot_download("mlboydaisuke/qwen3-vl-2b-stateful-coreml")
5root = f"{local}/qwen3_vl_2b_stateful_chunks"
6
7# Multifunction load — pick the function you need per step.
8prefill_chunks = [ct.models.MLModel(
9 f"{root}/chunk_{i}.mlpackage", function_name="prefill_b8"
10) for i in range(4)]
11decode_chunks = [ct.models.MLModel(
12 f"{root}/chunk_{i}.mlpackage", function_name="infer"
13) for i in range(4)]
14head = ct.models.MLModel(f"{root}/chunk_head.mlpackage")
15embed = np.memmap(f"{root}/embed_weight.bin",
16 dtype=np.float16, mode="r",
17 shape=(151936, 2048))
18
19# State is created once and shared across infer + prefill_b8.
20state = prefill_chunks[0].make_state()1vision = ct.models.MLModel(
2 f"{local}/qwen3_vl_2b_vision/vision.mlpackage")
3out = vision.predict({"pixel_values": img_3x2x448x448_fp16})
4# out: {"hidden": (1, 196, 2048), "deepstack_5/11/17": (1, 196, 2048)}Qwen3VL2BGenerator.swift. Tap Qwen3-VL 2B (stateful, Phase 1) in the picker.