Jev-style parallel constrained decisions for any MLX model on Apple Silicon. Typed, schema-valid JSON in one forward pass.
Python
1
95 commits
updated Sep 17, 2026
Typed decisions from a local LLM in one forward pass. Apple Silicon, MLX.
Pre-release. Accuracy/latency tables are generated by jevmlx eval and committed under benchmarks/results/ (first full run pending).
Inspired by and built on rorshopping/jev-on-a-laptop, the research repo that reproduced the technique on a laptop. jevmlx turns that study into an installable library. Unofficial, not affiliated with TypeSafe AI or Jev.
uv pip install git+https://github.com/bnsd55/jevmlx
jevmlx decide --preset fintech_fraud
Illustrative output:
Preset : FinTech Fraud & Autonomous AML Compliance (28 Fields)
Model : mlx-community/Qwen2.5-1.5B-Instruct-4bit
Latency: 970.9 ms (prefill 416.6 + batched pass 530.4)
field value conf type
------------------------------ ---------------------- ----- -----
is_fraudulent True 0.825 boolean
risk_tier HIGH 0.752 enum
recommended_action BLOCK_TRANSACTION 0.702 enum
...
The same decision from Python:
from typing import Literal
from pydantic import BaseModel, Field
import jevmlx
class Fraud(BaseModel):
is_fraudulent: bool = Field(description="Whether the transaction is fraudulent")
risk_tier: Literal["LOW", "MEDIUM", "HIGH", "CRITICAL"] = Field(description="Risk tier")
context = "Wire transfer to a new IBAN, requested from a Tor exit node on an unrecognized device"
d = jevmlx.decide(Fraud, context, model="mlx-community/Qwen2.5-1.5B-Instruct-4bit")
d.value # validated Fraud instance
d.confidence # {"is_fraudulent": 0.xx, "risk_tier": 0.xx}
Generation can produce malformed or off-schema output and gives no per-field confidence. jevmlx reads probabilities directly from the model at the decision position. The whole object is assembled, never generated. One prefill plus one batched pass instead of N generated tokens.
[context + schema] ──► prefill (once) ──► KV cache
│ broadcast ×N fields
┌───────────┬───────────┬─────────────┴───────────┐
field 1 field 2 ... field N
└───────────┴──── one batched forward pass ────────┘
│
restricted next-token probs per branch → trie → pick value + P(choice)
Two scoring modes: trie (default) and letter slots (--scoring letters). Letters list each field's choices as lettered options and read the next-token distribution at the field's slot position — no tokenization collisions by construction; try it when a schema's choices share long first tokens.
multi fields return the subset of options that apply.jevmlx calibrate fits one temperature to correct overconfidence.jevmlx.decide for one context, jevmlx.decide_many for many with one model load.jevmlx serve --model M loads the model once and serves one decision per request on POST /decide (serial — one Metal GPU).jevmlx calibrate fits one scalar temperature on labeled JSONL by minimizing NLL and reports binned ECE before and after.jevmlx validate SCHEMA.json loads the tokenizer only (no model download) and reports first-token collisions with rename suggestions, duplicate choices, single-choice fields, and compile errors.Latency and memory numbers below come from benchmarks/compat.py. Accuracy and calibration numbers come from jevmlx eval / jevmlx report and live in benchmarks/results/.
Every preset field is decided in one batched pass — measured on a MacBook Pro M2 Pro, 34 GB, macOS (warm runs, benchmarks/compat.py). "Warm latency" is the average of the second run on both presets; peak memory is Metal's process high-water mark after both presets.
| Model | loads | presets valid | warm latency (ms, avg of 2 presets) | prompt tokens | peak GPU mem (GB) |
|---|---|---|---|---|---|
mlx-community/Qwen2.5-1.5B-Instruct-4bit | y | ok, ok | 862 | 469 | 2.26 |
mlx-community/Qwen2.5-7B-Instruct-4bit | y | ok, ok | 3785 | 469 | 6.30 |
mlx-community/Llama-3.2-3B-Instruct-4bit | y | ok, ok | 1701 | 422 | 5.03 |
mlx-community/gemma-2-2b-it-4bit | y | ok, ok | 1407 | 474 | 4.83 |
mlx-community/Mistral-7B-Instruct-v0.3-4bit | y | ok, ok | 5519 | 557 | 10.32 |
mlx-community/Phi-3.5-mini-instruct-4bit | y | ok, ok | 7562 | 569 | 13.73 |
Build the labeled JSONL, run it through a track, then summarize offline:
python -m benchmarks.to_jsonl --out cases.jsonl # bundled fintech cases
python -m benchmarks.typesafe.fetch --out typesafe.jsonl # TypeSafe public examples
jevmlx eval --data cases.jsonl --track parallel --out DIR
jevmlx report --predictions DIR/predictions.jsonl --out DIR/report.json
Three tracks: parallel (the constrained engine), naive_local (the same local model writing the JSON object itself, parsed strictly), api_baseline (an OpenAI-compatible chat model via --api-base / --api-model). Raw predictions, run manifests, and reports land in benchmarks/results/. To contribute results from your own Mac, see BENCHMARKING.md.
Where this is going next: ROADMAP.md. A correctness gate from an external review, an evaluation loop, latency profiling, then PyPI.
Branch off main, run ruff check --fix . && ruff format . and pytest -m "not slow" before pushing. Details: CONTRIBUTING.md.
jevmlx is MIT-licensed (see LICENSE); third-party credits are listed in NOTICE.
79 commits
16 commits
Hacker News (1)
Python
99.8%
Jev-style parallel constrained decisions for any MLX model on Apple Silicon. Typed, schema-valid JSON in one forward pass.
Python
1
95 commits
updated Sep 17, 2026
Typed decisions from a local LLM in one forward pass. Apple Silicon, MLX.
Pre-release. Accuracy/latency tables are generated by jevmlx eval and committed under benchmarks/results/ (first full run pending).
Inspired by and built on rorshopping/jev-on-a-laptop, the research repo that reproduced the technique on a laptop. jevmlx turns that study into an installable library. Unofficial, not affiliated with TypeSafe AI or Jev.
uv pip install git+https://github.com/bnsd55/jevmlx
jevmlx decide --preset fintech_fraud
Illustrative output:
Preset : FinTech Fraud & Autonomous AML Compliance (28 Fields)
Model : mlx-community/Qwen2.5-1.5B-Instruct-4bit
Latency: 970.9 ms (prefill 416.6 + batched pass 530.4)
field value conf type
------------------------------ ---------------------- ----- -----
is_fraudulent True 0.825 boolean
risk_tier HIGH 0.752 enum
recommended_action BLOCK_TRANSACTION 0.702 enum
...
The same decision from Python:
from typing import Literal
from pydantic import BaseModel, Field
import jevmlx
class Fraud(BaseModel):
is_fraudulent: bool = Field(description="Whether the transaction is fraudulent")
risk_tier: Literal["LOW", "MEDIUM", "HIGH", "CRITICAL"] = Field(description="Risk tier")
context = "Wire transfer to a new IBAN, requested from a Tor exit node on an unrecognized device"
d = jevmlx.decide(Fraud, context, model="mlx-community/Qwen2.5-1.5B-Instruct-4bit")
d.value # validated Fraud instance
d.confidence # {"is_fraudulent": 0.xx, "risk_tier": 0.xx}
Generation can produce malformed or off-schema output and gives no per-field confidence. jevmlx reads probabilities directly from the model at the decision position. The whole object is assembled, never generated. One prefill plus one batched pass instead of N generated tokens.
[context + schema] ──► prefill (once) ──► KV cache
│ broadcast ×N fields
┌───────────┬───────────┬─────────────┴───────────┐
field 1 field 2 ... field N
└───────────┴──── one batched forward pass ────────┘
│
restricted next-token probs per branch → trie → pick value + P(choice)
Two scoring modes: trie (default) and letter slots (--scoring letters). Letters list each field's choices as lettered options and read the next-token distribution at the field's slot position — no tokenization collisions by construction; try it when a schema's choices share long first tokens.
multi fields return the subset of options that apply.jevmlx calibrate fits one temperature to correct overconfidence.jevmlx.decide for one context, jevmlx.decide_many for many with one model load.jevmlx serve --model M loads the model once and serves one decision per request on POST /decide (serial — one Metal GPU).jevmlx calibrate fits one scalar temperature on labeled JSONL by minimizing NLL and reports binned ECE before and after.jevmlx validate SCHEMA.json loads the tokenizer only (no model download) and reports first-token collisions with rename suggestions, duplicate choices, single-choice fields, and compile errors.Latency and memory numbers below come from benchmarks/compat.py. Accuracy and calibration numbers come from jevmlx eval / jevmlx report and live in benchmarks/results/.
Every preset field is decided in one batched pass — measured on a MacBook Pro M2 Pro, 34 GB, macOS (warm runs, benchmarks/compat.py). "Warm latency" is the average of the second run on both presets; peak memory is Metal's process high-water mark after both presets.
| Model | loads | presets valid | warm latency (ms, avg of 2 presets) | prompt tokens | peak GPU mem (GB) |
|---|---|---|---|---|---|
mlx-community/Qwen2.5-1.5B-Instruct-4bit | y | ok, ok | 862 | 469 | 2.26 |
mlx-community/Qwen2.5-7B-Instruct-4bit | y | ok, ok | 3785 | 469 | 6.30 |
mlx-community/Llama-3.2-3B-Instruct-4bit | y | ok, ok | 1701 | 422 | 5.03 |
mlx-community/gemma-2-2b-it-4bit | y | ok, ok | 1407 | 474 | 4.83 |
mlx-community/Mistral-7B-Instruct-v0.3-4bit | y | ok, ok | 5519 | 557 | 10.32 |
mlx-community/Phi-3.5-mini-instruct-4bit | y | ok, ok | 7562 | 569 | 13.73 |
Build the labeled JSONL, run it through a track, then summarize offline:
python -m benchmarks.to_jsonl --out cases.jsonl # bundled fintech cases
python -m benchmarks.typesafe.fetch --out typesafe.jsonl # TypeSafe public examples
jevmlx eval --data cases.jsonl --track parallel --out DIR
jevmlx report --predictions DIR/predictions.jsonl --out DIR/report.json
Three tracks: parallel (the constrained engine), naive_local (the same local model writing the JSON object itself, parsed strictly), api_baseline (an OpenAI-compatible chat model via --api-base / --api-model). Raw predictions, run manifests, and reports land in benchmarks/results/. To contribute results from your own Mac, see BENCHMARKING.md.
Where this is going next: ROADMAP.md. A correctness gate from an external review, an evaluation loop, latency profiling, then PyPI.
Branch off main, run ruff check --fix . && ruff format . and pytest -m "not slow" before pushing. Details: CONTRIBUTING.md.
jevmlx is MIT-licensed (see LICENSE); third-party credits are listed in NOTICE.
Hacker News (1)
79 commits
16 commits
Python
99.8%