CLM-v0.1-8B projection heads — Apple Core AI
The state and action projection heads of
Contrastive-LM/CLM-v0.1-8B as Core AI
(.aimodel) graphs, for on-device reranking and verification on Apple Silicon.
CLM is a verifier: two small MLP heads over a frozen Qwen3-8B encoder, trained with a bidirectional InfoNCE loss to connect states and actions. Upstream's own numbers put it on par with Jev on computer-use, gaming and tool-calling at up to 9× lower latency, and SOTA as a fine-tuned verifier on DeepSWE (81.6%) and Terminal-Bench 2.1 (87.6%).
Why only the heads. Qwen3-8B already has official MLX builds — Qwen/Qwen3-8B-MLX-8bit among
them — so the encoder half is not the gap. The heads had no MLX and no Core AI build anywhere, and
at 18.9M parameters (72 MB fp32) they are small enough to run on-device. This repository fills that
gap and nothing else.
The pipeline
Qwen/Qwen3-8B ──(MLX, e.g. Qwen/Qwen3-8B-MLX-8bit)──► last-token hidden state [1,4096]
│
┌───────────────────────────────────────┴────────────────────────┐
▼ ▼
clm-v0.1-state-head.aimodel clm-v0.1-action-head.aimodel
[1,512] L2-normalised [1,512] L2-normalised
Score a candidate as scale × cosine(state_vector, action_vector) with scale = 100.0.
States and actions are encoded separately, which is the point of the design: action embeddings can be reused across many states, and upstream reports ~13× speedup over Jev at ~1k candidates.
A working on-device stack
The encoder is MLX, the heads are Core AI, and nothing in between needs torch:
mlx-community/Qwen3-8B-4bit ──mlx-lm──► last-token hidden state [1,4096]
│
┌───────────────────────────────┴──────────────────────────────┐
▼ ▼
clm-v0.1-state-head.aimodel clm-v0.1-action-head.aimodel
[1,512] L2-normalised [1,512] L2-normalised
│
score = 100.0 x cosine(z_state, z_action)
stack_check.py in this repository builds exactly that, end to end, and is what produced the numbers
below. One trap worth carrying over: the MLX encoder returns bfloat16, and numpy cannot consume a
bf16 buffer — it reports the opaque Unknown PEP 3118 data type specifier 'oat16' rather than naming
the type. Cast inside MLX first: h.astype(mx.float32).
Real last-token activations on Qwen3-8B have a norm of 168–186, which is worth knowing before feeding these heads anything you have scaled yourself.
Verified equivalence — on real activations
Measured against upstream's own loader (clm.heads.HeadPair, imported from the published package,
not a reimplementation), on six real Qwen3-8B last-token activations produced by the MLX encoder:
| Kind | Input | state max abs diff | action max abs diff |
|---|---|---|---|
state |
Customer: my invoice was charged twice and n | 5.96e-08 | 3.73e-08 |
state |
Build failed on CI: undefined symbol in libs | 4.47e-08 | 5.96e-08 |
state |
العميل بيقول إن الفاتورة اتخصمت مرتين ومحدش | 2.98e-08 | 4.47e-08 |
action |
Route to the billing team: charges, invoices | 2.98e-08 | 5.96e-08 |
action |
Route to technical support: bugs and outages | 2.98e-08 | 3.73e-08 |
action |
Escalate to a human supervisor as a complain | 7.45e-08 | 3.35e-08 |
Worst across all twelve comparisons: 7.45e-08, min cosine 1.0000000000, worst deviation from unit norm 8.85e-08.
An earlier revision of this card reported 4.172e-07 measured on six deterministic synthetic
4096-d vectors, because the 8B encoder was not resident at the time. Both are float32 rounding; the
figure above is the one measured on the inputs the heads actually receive, and the synthetic one is
kept in validation.json rather than deleted.
What the heads do with it — a demonstration, not a benchmark
Scoring three candidate actions against the state "my invoice was charged twice and nobody answers
the phone", 100.0 x cosine:
| Score | Action |
|---|---|
| 25.745 | Escalate to a human supervisor as a complaint |
| 23.124 | Route to the billing team: charges, invoices and refunds |
| 19.247 | Route to technical support: bugs and outages |
The ordering is plausible — a double charge with an unanswered phone is not a technical bug. This is one example on one state. It is not a benchmark, and none of the model card's own claims (on par with Jev, up to 9x lower latency, DeepSWE 81.6%, Terminal-Bench 2.1 87.6%) were reproduced here.
Two upstream traps, both hit while building this
HeadPair.project_states()/project_actions()fail on first use withTypeError: 'NoneType' object is not callable. They evaluateself.state_headbefore_projectcallsensure(), soNoneis passed to a head that loads fine moments later. Callensure()first. The measurement here does, and the scripts in this repository show it.logit_scaleis clamped at 100.exp(4.6132) = 100.8, and upstream'sclamp(max=100.0)lands it exactly on 100.0 — the clamp is active on the shipped checkpoint, not a formality. Use 100.0, not 100.8.
Use
import asyncio, numpy as np
from coreai.runtime import AIModel, NDArray, SpecializationOptions
SCALE = 100.0
async def project(which: str, embedding: np.ndarray):
"""`which` is "state" or "action"; `embedding` is the [1,4096] Qwen3-8B last-token state."""
model = await AIModel.load(f"clm-v0.1-{which}-head.aimodel", SpecializationOptions.default())
fn = model.load_function("main")
out = await fn({"x": NDArray(embedding.astype("float32"))})
return np.asarray(list(out.values())[0].numpy()).reshape(-1)
async def score(state_emb, candidate_emb):
zs = await project("state", state_emb)
za = await project("action", candidate_emb)
return SCALE * float(zs @ za) # the number the library reports
Input selection is by position: the runtime names tensors after the op that produced them, so read the single output rather than indexing it by a name.
What is in here
| File | |
|---|---|
clm-v0.1-state-head.aimodel/, clm-v0.1-action-head.aimodel/ |
the two graphs |
manifest.json |
the exact forward, activation, the residual=False fact, the score scale, shapes |
validation.json |
every measured number above |
clm_coreai.py |
the exporter and measurer — --check, --export, --measure |
config.json, LICENSE |
upstream's, for reference |
requirements.txt |
the pinned Core AI builds, plus how to install the reference without vLLM |
Limits
- The encoder is not in this repository. You need
Qwen/Qwen3-8Bseparately, and this bundle does nothing on its own without it. - No benchmark. The equivalence above is arithmetic, and the demonstration is a single state. The model's own accuracy and speed claims were not reproduced, and nothing was measured about how the encoder's 4-bit quantisation affects ranking.
- Fixed shape
[1,4096]in,[1,512]out. Batch by looping, or re-export at the batch size you need. - No latency or memory measurement of the stack was taken.
Licence and attribution
Apache-2.0, as declared by upstream. A derivative work of Contrastive-LM/CLM-v0.1-8B; the model and
the training work are theirs. The encoder is Qwen/Qwen3-8B, under its own licence.