--- license: apache-2.0 base_model: answerdotai/ModernBERT-base base_model_relation: finetune datasets: - LocalLLaMA/typed-decisions library_name: coreml pipeline_tag: text-classification tags: - coreml - mlpackage - apple - apple-silicon - apple-neural-engine - ane - macos - on-device - modernbert - typed-decisions --- # JevLite for Apple Silicon — typed decisions on the Neural Engine ![Apple Silicon](https://img.shields.io/badge/Apple%20Silicon-arm64-000000?logo=apple&logoColor=white) ![Core ML](https://img.shields.io/badge/Core%20ML-.mlpackage-0a84ff?logo=apple&logoColor=white) ![Neural Engine](https://img.shields.io/badge/Neural%20Engine-fp16-34c759) ![Params](https://img.shields.io/badge/params-149M-555) ![License](https://img.shields.io/badge/license-Apache--2.0-2a78d6) **English** | [日本語](#日本語) **Ready-to-run Core ML packages of a 149M ModernBERT typed-decision engine, built for Apple Silicon.** Hand it a state plus any set of typed questions (`noul` / `choice` / `score`) and it answers all of them in one forward pass with a confidence each — fully on-device, no server, no API cost. - 🍎 **Core ML `.mlpackage`, fp16, runs on the Apple Neural Engine** (`compute_units="cpu_and_ne"`) - ⚡ **13 ms** per call at 256 tokens on an M2 Max — **5.4× faster than PyTorch-CPU**, with the GPU left free - 🎯 **No accuracy loss vs PyTorch**: 99.65% decision agreement on the full test split, accuracy −0.15 pp - 🧩 **Schema stays dynamic**: questions and labels are data, not baked into the graph — new questions need no re-export - 🔁 fp32 packages (exact parity) and the PyTorch weights included for verification and non-Apple platforms ## Apple Neural Engine performance Measured on **Mac14,6 — Apple M2 Max (32 GB), macOS 26.6.2**, coremltools 9.0, batch 1, median of 100 runs after 20 warmup, real encoded inputs. | seq len | PyTorch CPU | PyTorch MPS | Core ML `cpu_only` | **Core ML `cpu_and_ne`** | |---|---|---|---|---| | 256 | 71.5 ms | 13.4 ms | 32.8 ms | **13.2 ms** | | 512 | 123.8 ms | 22.9 ms | 64.2 ms | **37.1 ms** | | 1024 | 253.7 ms | 47.4 ms | 169.0 ms | **111.1 ms** | - `cpu_and_ne` beats Core ML `cpu_only` by 2.5× / 1.7× / 1.5× and PyTorch-CPU by 5.4× / 3.3× / 2.3×. - It matches the GPU (MPS) at 256 tokens while keeping the GPU idle — the low-power, GPU-freeing path. At 512/1024 tokens MPS is still faster. - ANE participation is inferred from `cpu_and_ne` being consistently faster than `cpu_only` with identical math; a per-op placement trace from Xcode was not produced. ### Parity vs PyTorch (full test split, 400 cases / 2,000 decisions, `cpu_and_ne`) | metric | value | |---|---| | decision agreement | **0.9965** | | accuracy (Core ML / PyTorch) | 0.6445 / 0.6430 | | confidence MAE | 0.0024 | The fp32 packages are exact (max |Δlogit| 0.0000) but run on CPU/GPU only — the ANE is fp16-only. ## Requirements | package set | OS | hardware | |---|---|---| | `coreml/` (fp16, **ANE**) | **macOS 26+** | Apple Silicon | | `coreml-fp32/` (exact) | macOS 15+ | Apple Silicon | | `jevlite.pt` / `model.safetensors` | any | CPU / CUDA / MPS | Python: `torch`, `transformers>=4.48`, `numpy`, `coremltools>=9.0,<10`. The tokenizer is fetched from `answerdotai/ModernBERT-base`. ## Quick start (Mac) ```bash hf download j-llm/jevlite --local-dir jevlite cd jevlite pip install torch "transformers>=4.48" numpy "coremltools>=9.0,<10" python decide.py --backend coreml --pkg-dir coreml # interactive console on the Neural Engine ``` ```python from engine import DecisionEngine from typed_schema import Question eng = DecisionEngine(backend="coreml", ckpt="jevlite.pt", pkg_dir="coreml", compute_units="cpu_and_ne") # Neural Engine; backend="torch" elsewhere print(eng.decide({"user": "I was charged twice this month"}, { "category": Question.choice("What kind of support request is this?", { "billing": "Charges, invoices, refunds or payment methods.", "bug": "Something in the product is broken.", "other": "Anything else."}), "urgent": Question.noul("This request is urgent."), })) eng.close() ``` Run from the repo root (`engine.py` loads `05_serve.py` by relative path). The default `coreml_mode="worker"` isolates the Core ML runtime in a subprocess — use it behind any server. ## Files | path | what | |---|---| | `coreml/jevlite-{256,512,1024}.mlpackage` | Core ML fp16, Neural Engine. Smallest bucket covering the input is used. ~300 MB each. | | `coreml-fp32/jevlite-{256,512,1024}.mlpackage` | Core ML fp32, exact parity reference. | | `jevlite.pt` | PyTorch checkpoint (`state_dict`, encoder, max_len=1024, temperatures). Source of all exports. | | `model.safetensors` | Same weights as `jevlite.pt` (fp32, 149M params); metadata holds encoder, max_len, temperatures. | | `*.py`, `requirements.txt` | Minimal inference code from the source repo (unchanged). | ## How the Core ML graph is split The `.mlpackage` computes `token_logits [1, S]` — ModernBERT encoder + score head at every position, inputs `input_ids` / `attention_mask` (int32). Tokenizing the questions, gathering the label-marker positions, per-type temperature and per-question softmax run in numpy on the CPU (`coreml_backend.py`, a few µs per call). That is why the schema stays fully dynamic. ## Model ModernBERT-base + `Linear(d, 1)` token head, trained 20 epochs on [`LocalLLaMA/typed-decisions`](https://huggingface.co/datasets/LocalLLaMA/typed-decisions). Test accuracy 0.6430, ECE 0.0536 (official 400-case split). Source: [eightman999/open-jev-typed-decision-engine](https://github.com/eightman999/open-jev-typed-decision-engine) (fork of [intikhab49/open-jev-typed-decision-engine](https://github.com/intikhab49/open-jev-typed-decision-engine)) — see `ANE_REPORT.md` and `BENCHMARK.md` there for full measurements and limitations. --- ## 日本語 [English](#jevlite-for-apple-silicon--typed-decisions-on-the-neural-engine) | **日本語** **Apple Silicon 向けにそのまま動かせる、149M パラメータの ModernBERT 型付き判定エンジン(Core ML 版)です。** 状態(JSON やテキスト)と、型付きの質問(`noul` = 真偽 / `choice` = 選択 / `score` = 段階評価)を任意の数だけ渡すと、 1回の推論ですべてに答え、それぞれの確信度を返します。端末内で完結し、サーバーも API 料金も不要です。 - 🍎 **Core ML `.mlpackage`(fp16)で Apple Neural Engine 上で動作**(`compute_units="cpu_and_ne"`) - ⚡ M2 Max で 256 トークンなら **1回 13 ms**。PyTorch(CPU)の **5.4 倍速く**、GPU は空いたまま - 🎯 **PyTorch と比べて精度は落ちない**:テストデータ全件で判定一致率 99.65%、精度差 −0.15 ポイント - 🧩 **質問は自由に変えられる**:質問やラベルはデータとして渡すので、グラフに焼き込まれていない。新しい質問でも再変換は不要 - 🔁 完全一致の検証用 fp32 版と、Apple 以外でも使える PyTorch の重みも同梱 ### Apple Neural Engine での性能 計測環境:**Mac14,6 — Apple M2 Max(32 GB)、macOS 26.6.2**、coremltools 9.0、バッチ 1、 ウォームアップ 20 回のあと 100 回計測した中央値、実際にエンコードした入力を使用。 | 入力長 | PyTorch CPU | PyTorch MPS | Core ML `cpu_only` | **Core ML `cpu_and_ne`** | |---|---|---|---|---| | 256 | 71.5 ms | 13.4 ms | 32.8 ms | **13.2 ms** | | 512 | 123.8 ms | 22.9 ms | 64.2 ms | **37.1 ms** | | 1024 | 253.7 ms | 47.4 ms | 169.0 ms | **111.1 ms** | - `cpu_and_ne` は Core ML `cpu_only` より 2.5 / 1.7 / 1.5 倍、PyTorch(CPU)より 5.4 / 3.3 / 2.3 倍速い。 - 256 トークンでは GPU(MPS)と同等の速さで、GPU を使わずに済む。省電力で GPU を空けておける構成。 512・1024 トークンでは MPS のほうが速い。 - Neural Engine を使っていることは、同じ計算で `cpu_and_ne` が `cpu_only` より常に速いことから推測している。 Xcode での演算ごとの割り当ては確認していない。 ### PyTorch との一致(テストデータ全件:400 件 / 2,000 判定、`cpu_and_ne`) | 指標 | 値 | |---|---| | 判定一致率 | **0.9965** | | 精度(Core ML / PyTorch) | 0.6445 / 0.6430 | | 確信度の平均絶対誤差 | 0.0024 | fp32 版は完全一致(logit 差の最大 0.0000)ですが、Neural Engine は fp16 専用のため CPU/GPU で動きます。 ### 動作要件 | ファイル | OS | ハードウェア | |---|---|---| | `coreml/`(fp16、**Neural Engine**) | **macOS 26 以上** | Apple Silicon | | `coreml-fp32/`(完全一致) | macOS 15 以上 | Apple Silicon | | `jevlite.pt` / `model.safetensors` | 問わない | CPU / CUDA / MPS | Python パッケージ:`torch`、`transformers>=4.48`、`numpy`、`coremltools>=9.0,<10`。 トークナイザーは `answerdotai/ModernBERT-base` から取得します。 ### 使い方(Mac) ```bash hf download j-llm/jevlite --local-dir jevlite cd jevlite pip install torch "transformers>=4.48" numpy "coremltools>=9.0,<10" python decide.py --backend coreml --pkg-dir coreml # Neural Engine で対話コンソールを起動 ``` Python から使う場合は、上の英語版にあるコード例をそのまま使えます(`backend="coreml"`、`pkg_dir="coreml"`)。 `engine.py` は `05_serve.py` を相対パスで読むので、ダウンロードしたフォルダで実行してください。 既定の `coreml_mode="worker"` は Core ML の実行をサブプロセスに分けるので、サーバーに組み込むときはこちらを使ってください。 ### ファイル | パス | 内容 | |---|---| | `coreml/jevlite-{256,512,1024}.mlpackage` | Core ML fp16(Neural Engine 用)。入力長をカバーする最小のものが使われる。各約 300 MB。 | | `coreml-fp32/jevlite-{256,512,1024}.mlpackage` | Core ML fp32。PyTorch と完全一致する検証用。 | | `jevlite.pt` | PyTorch チェックポイント(`state_dict`、エンコーダ名、max_len=1024、温度)。すべての変換の元。 | | `model.safetensors` | `jevlite.pt` と同じ重み(fp32、149M)。メタデータにエンコーダ名・max_len・温度を格納。 | | `*.py`、`requirements.txt` | 元リポジトリの推論コード(変更なし)。 | ### Core ML 側と Python 側の分担 `.mlpackage` が計算するのは `token_logits [1, S]`(ModernBERT エンコーダ+スコアヘッドを全位置で評価した値)だけです。 入力は `input_ids` / `attention_mask`(int32)。質問のトークン化、ラベル位置の取り出し、型ごとの温度、 質問ごとの softmax は CPU 上の numpy で行います(`coreml_backend.py`、1回数マイクロ秒)。 そのため質問の形は自由に変えられます。 ### モデル ModernBERT-base に `Linear(d, 1)` のトークンヘッドを付け、 [`LocalLLaMA/typed-decisions`](https://huggingface.co/datasets/LocalLLaMA/typed-decisions) で 20 エポック学習しました。 テスト精度 0.6430、ECE 0.0536(公式の 400 件テストセット)。 元リポジトリ:[eightman999/open-jev-typed-decision-engine](https://github.com/eightman999/open-jev-typed-decision-engine) ([intikhab49/open-jev-typed-decision-engine](https://github.com/intikhab49/open-jev-typed-decision-engine) のフォーク)。 詳しい計測結果と制約は、そちらの `ANE_REPORT.md` と `BENCHMARK.md` を参照してください。