TokForge
- Website: https://tokforge.ai
- Discord: https://discord.gg/Acv3CBtfVm
- Google Play: https://play.google.com/store/apps/details?id=dev.tokforge
- iOS TestFlight: https://testflight.apple.com/join/jnufjzRr
Runs on-device in the TokForge app.
TokForge-Qwen2.5-3B-CoreML-ANE-INT8
Qwen/Qwen2.5-3B-Instruct β CoreML stateful INT8, targeted at the Apple Neural Engine (ANE) on iOS 18+.
This is a stateful CoreML conversion of Qwen2.5-3B-Instruct with an in-model KV-cache
(MLState), per-block INT8 weight quantization, and a fixed context window of
2048 tokens. Built for the TokForge iOS app's CoreML/ANE inference track
(a flagship "bigger model in the same RAM" path alongside llama.cpp/MNN).
What it is
- Architecture: Qwen2.5-3B-Instruct β 36 layers, 16 attention heads, 2 KV heads (GQA), head_dim 128, hidden 2048, vocab 151936.
- Format: CoreML
.mlmodelc(precompiled), stateful KV-cache viaMLState. - Quantization: weights
per_block(32)linear_symmetricINT8 (decompressed to fp16 on the ANE at compute time β saves RAM/bandwidth, not compute). - Context window: fixed 2048 tokens (static-shape KV buffers).
- Sizes: fp16 mlpackage 5889 MB β INT8 mlpackage 3129 MB.
Requirements
- iOS 18+ / macOS 15+ (MLState stateful models + per-block INT8 are iOS18-only).
- Apple Neural Engine (A17 Pro / A18 / A19 / M-series). Load with
MLComputeUnits.cpuAndNeuralEngine. Verify ANE dispatch at runtime β CoreML can silently fall back to GPU/CPU if ANEF specialization fails. - The HuggingFace tokenizer (
tokenizer.json+tokenizer_config.jsonincluded here), e.g. viaswift-transformers.
I/O signature (the bridge contract)
Stateful model. 36 layers β 72 KV-cache states
(key_cache_<i> / value_cache_<i>, i = 0..35).
Inputs:
input_ids: {"dtype": "INT32", "shape": [1, 16], "flex": "shapeRange", "flex_detail": [[1, 1], [1, 2048]]}causal_mask: {"dtype": "FLOAT16", "shape": [1, 1, 16, 2048], "flex": "shapeRange", "flex_detail": [[1, 1], [1, 1], [1, 2048], [2048, 2048]]}position_ids: {"dtype": "INT32", "shape": [1, 16], "flex": "shapeRange", "flex_detail": [[1, 1], [1, 2048]]}cache_offset: {"dtype": "INT32", "shape": [1], "flex": null, "flex_detail": null}
Outputs:
logits: {"dtype": "FLOAT16", "shape": [], "flex": null, "flex_detail": null}
States (MLState): 72 entries, each shape [1, 2, 2048, 128] fp16:
key_cache_0β¦key_cache_35value_cache_0β¦value_cache_35
Driving it from Swift
input_ids[1, S]int32 β token ids for this step (S = prefill length, or 1 for decode).causal_mask[1, 1, S, 2048]fp16 β additive mask (0 = attend, -1e4 = masked); column j valid iff slot j has been written and j β€ that row's absolute position.position_ids[1, S]int32 β absolute RoPE positions of the S tokens.cache_offset[1]int32 β the begin slot in the KV window; the S new K/V rows are written in place to[cache_offset : cache_offset + S](contiguous static-length slice).- The model writes K/V into the
key_cache_*/value_cache_*MLState buffers in place, attends over the full 2048-slot window (validity enforced bycausal_mask), and returnslogits. Runtime output shape is[1, S, 151936](the spec listslogitswith an unbound shape because of the flexible seq dim β at runtime it is[1, S, 151936]). Sample fromlogits[:, -1, :].
The seq dim is a flexible RangeDim(1..2048). Inputs input_ids / position_ids
are [1, RangeDim], causal_mask is [1, 1, RangeDim, 2048], cache_offset is fixed [1].
Verified on the M4 (host)
- Loads on
CPU_AND_NEin ~58 s first-load (ANEF device-specialization β ship the precompiled.mlmodelc+ rely on the on-device ANEF cache so only first load is slow). - Prefill S=16 runs and returns finite
logits [1, 16, 151936](~0.44 s β 36 tok/s prefill). - Known caveat (S=1 decode on the macOS host) β UPDATED after a Phase-1b investigation:
Single-token (S=1) stateful decode does not execute on the M4 build host (macOS 26.3,
coremltools 9.0): every compute unit (
CPU_ONLY,CPU_AND_GPU,CPU_AND_NE,ALL) fails withE5RT: Error(s) occurred executing a BNNS Op("file a radar on BasicNeuralNetworkSubroutines"). This was root-caused to a host-side E5RT/BNNS execution limitation for small-sequence stateful slice-update graphs, NOT a defect in this artifact:- It is independent of the shape spec. A re-export with
EnumeratedShapesincluding S=1 ({1,16,64,128}) was built and tested β it does NOT fix S=1, and is strictly worse on this host: enumerated stateful models fail the execution-plan build entirely (error code: -14,make_state()β "not loaded with the Core ML Framework"; cf. coremltools issues #2548 / #2271 and Apple'sAsymmetricalEnumeratedShapesException). So the published artifact intentionally keeps theRangeDim(1..2048)spec. - It is independent of quantization and of this model β a minimal fixed-shape S=1
stateful toy model reproduces the same
E5RT BNNSerror. - Prefill works: S=16 returns finite
logits [1,16,151936]onCPU_AND_NE. Why this is very likely host-only: Apple's own shipping CoreML LLMs (WWDC24 Mistral-7B, the on-device Llama-3.1 post β ~33 tok/s on an M1 Max) use exactly thisRangeDim(1..max)+ stateful KV-cache recipe and do decode at S=1 on real devices. The on-device A-series ANE runtime differs from this Mac's E5RT/BNNS host path. Therefore S=1 decode must be verified on a real iOS-18 device (the planned Lane-1.5 on-device measure), not on the Mac. If a device also fails S=1, the fallback is a fixed-width decode window (pad to the working S and mask), but that is not expected to be necessary.
- It is independent of the shape spec. A re-export with
Conversion recipe (reproducible)
- coremltools 9.0, torch 2.8, transformers 4.44.2 on Apple Silicon (M4).
- HF model loaded fp16 with
attn_implementation="eager"(FlashAttention swapped for SDPA β FA can't convert). - Stateful wrapper: per-layer
register_bufferKV tensors[1, 2, 2048, 128], new K/V scattered with a static-windowindex_copy(AppleSliceUpdateKeyValueCachestyle). This avoids the coremltools-9.0rank0_expand_dims_swap/ "Cannot delete op 'end_1'" graph-pass bug that a naive dynamic end-index write-slice triggers (which blocks quantizing the stateful model). ct.convert(..., states=[StateType...], minimum_deployment_target=ct.target.iOS18, compute_units=ct.ComputeUnit.CPU_AND_NE, compute_precision=FLOAT16).linear_quantize_weights(OpLinearQuantizerConfig(mode="linear_symmetric", dtype="int8", granularity="per_block", block_size=32))(per-block requires iOS18). NOT global kmeans palettization β that throws inf on Qwen's weight outliers (the TQ3 "Qwen activations 3-5Γ wider" lesson).- Compiled to
.mlmodelcwithxcrun coremlcompiler compile.
Conversion script: convert_qwen_coreml.py (in this repo / on the build host).
Timings (M4, 32GB)
- fp16 stateful convert: 102.9 s
- INT8 quant (per_block(32)): 145.0 s
Attribution & license
- Base model Qwen2.5-3B-Instruct Β© Alibaba Cloud / Qwen team, Apache-2.0 (https://huggingface.co/Qwen/Qwen2.5-3B-Instruct). This conversion inherits that license.
- Conversion via Apple coremltools (BSD-3). Stateful KV-cache mirrors Apple's
SliceUpdateKeyValueCachereference and the ANEMLL / john-rocky CoreML-LLM patterns. - Packaged by TokForge for on-device iOS inference.