jevlite / README.md
eightman999's picture
Model card: add Japanese version
45d16e5 verified
|
Raw History Blame Contribute Delete
11.6 kB
---
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` を参照してください。