Downloads · 30 days
0
mchen04/jev-local-lab-decision-heads
jev-local-lab-decision-heads is a text classification model from mchen04. Use it when you need a label for a piece of text. It is set up for mlx. The card lists the license as other.
Hi — this is for-fun side-project stuff. I tried seeing how well a small local model on a 24 GB Mac mini could read structured decisions out of documents, and these are the tiny trained heads that came out of it. The…
Downloads · 30 days
0
Access
Public
Updated Sep 22, 2026
Repo size
33.5 MB
Likes
0
Public
Click a slice to open those files.
.safetensors33.5 MB · 100%
From the Hugging Face model README
Hi — this is for-fun side-project stuff. I tried seeing how well a small local model on a 24 GB Mac mini could read structured decisions out of documents, and these are the tiny trained heads that came out of it. The work runs from 2026-09-19 to 2026-09-22 in the project's own commit history; I am not going to round that into a tidier story than it was. They are not production-ready, they are not a reproduction of any commercial product, and they do not reach parity with the published "Jev" numbers I was comparing against. Two of the most interesting things in here are failures, and I've left them in on purpose.
tl;dr of the honest result: on the task families these heads were trained on they beat their own backbone's zero-shot read-out by +14.8 points. On a fresh, never-touched set of six different task families they LOST to it by 3.25 points (exact paired McNemar p = 0.0167), and on an external workflow suite they lost again. If you want a small local decision reader, the plain zero-shot read-out on the same backbone was better than these heads on everything unseen.
Ten decision heads: small trained modules that sit on top of a frozen, unmodified 4-bit Qwen2.5-1.5B-Instruct and turn one hidden state into a probability distribution over a label set supplied at call time. They are not language models and they do not generate text. Each head is 0.20–1.32 M parameters (0.8–5.3 MB), i.e. roughly 0.01–0.09 % of the backbone.
The scoring function is a cosine-bilinear form between the backbone's hidden state at the read position and the mean-pooled input embeddings of each candidate label, so the same head works for any label set without retraining. Seven heads are a straight hyper-parameter sweep (rank, head shape, distillation weight); three are deliberate held-out-task probes trained with some task families removed, to test whether the head generalises. It does not — see Evaluation.
mlx-community/Qwen2.5-1.5B-Instruct-4bit
at revision 8b403126fc14f14cfc99bb4cfa72ecbc129ea677 (Apache-2.0)There is no public code repository. The engine that produces the hidden state these heads consume
(jevlocal) lives in a private local checkout, so this card documents the head's maths in full
instead of pointing at an import. See How to get started — I would rather
give you the equations than imply a turnkey download that does not exist.
Weights are supplied. Every file below is a real trained artifact, not a recipe. Each directory
holds head.safetensors (the weights) and record.json (full recipe, per-epoch history, the
selected epoch, and the sha256 the weights must match).
| directory | head | rank | distil α | τ | params | bytes | dev3 macro | note |
|---|---|---|---|---|---|---|---|---|
p3h-kd-a05 | bilinear | 256 | 0.5 | 2.0 | 791 042 | 3 164 652 | 0.6885 | best on dev3; the one carried to the untouched split, where it lost |
p3h-ce-r256 | bilinear | 256 | 0.0 | — | 791 042 | 3 164 652 | 0.6819 | no distillation |
p3h-kd-a05-t4 | bilinear | 256 | 0.5 | 4.0 | 791 042 | 3 164 652 | 0.6812 | hotter teacher |
p3h-ce-r64 | bilinear | 64 | 0.0 | — | 201 218 | 805 347 | 0.6774 | smallest head here |
p3h-ce-mlp | mlp | 256 | 0.0 | — | 1 315 842 | 5 263 986 | 0.6632 | nonlinear projection |
p3h-kd-mlp-a05 | mlp | 256 | 0.5 | 2.0 | 1 315 842 | 5 263 986 | 0.6583 | |
p3h-kd-a09 | bilinear | 256 | 0.9 | 2.0 | 791 042 | 3 164 652 | 0.6410 | mostly-teacher loss; worse |
p3h-holdout-small | bilinear | 256 | 0.0 | — | 791 042 | 3 164 652 | 0.6712 | held out scicite_intent, winogrande_coref |
p3h-holdout-mixed | bilinear | 256 | 0.0 | — | 791 042 | 3 164 652 | 0.6170 | held out clinc_intent, openbookqa_science |
p3h-holdout | bilinear | 256 | 0.0 | — | 791 042 | 3 164 652 | 0.5590 | held out clinc_intent, ledgar_provision |
dev3 macro is the mean per-task accuracy over the nine development task families, 960 questions.
It is development data — it was looked at repeatedly and used to pick things. Treat it as
selection evidence, not as a performance claim.
Also included: manifest.json (this table, machine-readable, with sha256 per file).
Reproducing or poking at the experiment: attach a head to the frozen backbone, feed it a hidden state and a label set, get a distribution. Useful if you want to see what a ~0.8 M-parameter cosine-bilinear head can and cannot learn on top of a small quantised model, or to check my numbers.
clinc_intent (150 labels) and ledgar_provision (100
labels) removed from training, p3h-holdout scores 0.7170 on the task families it did see
and 0.0063 on the two it did not. That is not a degradation, it is a collapse.p3h-holdout-small, holding out two small-label tasks) reaches 0.5938 on held-out tasks against
0.6933 on seen ones. Transfer degrades with the size of the unseen label set.If you want a small local decision reader for unseen tasks, use the plain zero-shot restricted read-out on the same backbone instead. That is what my own measurements say, and it is why I am publishing these heads as an experiment record rather than as a recommendation.
train3: 3405 items drawn from the train splits of nine public datasets, at row indices the
development split does not use, deduplicated by a sha256 of the normalised input text against every
other split in the project. The nine families:
| family | source dataset | published licence (as tagged on the Hub) |
|---|---|---|
pubmedqa_decision | qiaojin/PubMedQA | mit |
rte_entailment | nyu-mll/glue (RTE) | other |
scicite_intent | allenai/scicite | unknown |
climatefever_claim | tdiggelm/climate_fever | unknown |
openbookqa_science | allenai/openbookqa | unknown |
ledgar_provision | coastalcph/lex_glue (LEDGAR) | cc-by-4.0 |
clinc_intent | clinc/clinc_oos | cc-by-3.0 |
winogrande_coref | allenai/winogrande | no licence tag published |
race_middle_multi | ehovy/race | other — RACE's own terms restrict use to research |
No dataset text is included in this repo, and none was uploaded anywhere — the built task files stay on the machine that made them.
What that does NOT establish: I have not run any extraction, memorisation or privacy evaluation on these heads, so I cannot tell you what, if anything, is recoverable from the weights. A small parameter count makes verbatim memorisation less likely a priori, but "unlikely a priori" is not a measurement and I am not going to present it as one. If that matters for your use case, treat it as an open question and test it.
The backbone is frozen and unmodified throughout — only the head trains.
(1 − α) · CE(student, gold) + α · τ² · KL(teacher ‖ student), both at temperature τ. α = 0 for the ce- variants. The KL is the standard soft-target direction — teacher first — computed as Σ p_teacher · (log p_teacher − log p_student).kd- variants): a local 4-bit Qwen3.8-27B read-out
(lmstudio-community/Qwen3.8-27B-MLX-4bit @ 6067b15cf581666a4aecf6af3afaba4bb5efc20c)
producing soft label distributions. Coverage was 1499 of 3405 items (44 %) — the teacher pass
was time-bounded at ≤150 rows per flat task. So the distillation gain is a gain at 44 % coverage,
not at saturation, and it was small: +0.66 pp macro over the best non-distilled head on dev3.dev3 macro epoch, recorded per head in record.json.record.json's lineage): both
sides RMS-normalised before projection; the bilinear form cosine-normalised (a raw dot product
diverged — cross-entropy went 0.70 → 1.25 → 185.3 in three steps at lr 1e-3); the logit scale
learned but clipped to [1, 50].Training a head is seconds to a couple of minutes on cached hidden states. Caching the hidden states for 3405 train + 960 dev items took about 4 minutes on the hardware below. The 27B teacher pass took 1 h 34 min. Checkpoints are 0.8–5.3 MB.
Three evaluations, all with the same head (p3h-kd-a05, the one dev3 selected):
final3 — a fresh split of six task families disjoint from training, frozen before any
training and opened exactly once. 1200 questions. Families: sciq_support, qnli_entailment,
piqa_physical, medmcqa_choice, paws_paraphrase, snli_entailment.dev3
split into seen vs unseen families.Metric: accuracy, and macro accuracy = mean of per-task accuracies. Comparisons use the exact paired McNemar test on per-item outcomes keyed by (item, question).
The untouched split (final3, n = 1200, 6 unseen families):
| system | macro accuracy |
|---|---|
| plain zero-shot read-out, same frozen 1.5B backbone | 0.6867 |
p3h-kd-a05 head on the same backbone | 0.6542 |
−3.25 pp for the head, exact paired McNemar p = 0.0167. The head is behind on all six tasks
(medmcqa −5.50, snli −5.50, piqa −4.00, qnli −2.50, sciq −1.50, paws −0.50 pp). No single
per-task difference reaches significance — those six p-values are unadjusted and exploratory — but
the overall paired test on all 1200 items, which is the confirmatory one, does.
The external workflow suite (n = 348): the head agreed with the published reference on 0.5816 of pairs against 0.6499 for the plain read-out on the same backbone. Third independent measurement, same direction.
Held-out-task probes (dev3, seen vs unseen families):
| head | families held out | macro on seen | macro on held out |
|---|---|---|---|
p3h-holdout | clinc_intent (150 labels), ledgar_provision (100) | 0.7170 | 0.0063 |
p3h-holdout-mixed | clinc_intent (150), openbookqa_science (4) | 0.6987 | 0.3312 |
p3h-holdout-small | scicite_intent (3), winogrande_coref (2) | 0.6933 | 0.5938 |
Development numbers, for completeness and not as a performance claim: on dev3 (nine training
families, 960 questions) p3h-kd-a05 reaches macro 0.6885 against the same backbone's zero-shot
read-out at 0.5410 — +14.8 pp. That is the number that did not survive contact with unseen
tasks, and it is exactly why the fresh split existed.
The head learned the task families it was trained on, not "how to read a decision". On dev it looked like the bargain of the project; on anything unseen it is worse than not using it. I am publishing it because a clean, reproducible negative result is worth more than a quiet deletion.
The project compared against published numbers for a commercial product (agreement 0.9110 on a 337-pair frozen subset). Those answers were published by the benchmark's authors and were never run on my hardware — closed weights, no local execution. Nothing here reproduces, approximates or matches that system. The best non-head configuration in the project reached 0.8398 agreement on that subset, which was still significantly behind (p = 0.000536); the head reached 0.5816. Agreement with a published two-model consensus is also not ground-truth accuracy.
Listed because their absence matters:
final3 — they were diagnostic only.For a hidden state h (shape [hidden]) and label embedding matrix E (shape
[n_labels, hidden]), with rms(x) = x / sqrt(mean(x²) + 1e-6):
hn = rms(h) * g # g: [hidden]
En = rms(E) * ge # ge: [hidden]
a = hn @ Wh # Wh: [hidden, rank] -> [rank]
B = En @ We # We: [hidden, rank] -> [n_labels, rank]
a = a / max(||a||, 1e-6)
B = B / max(||B||_rowwise, 1e-6)
s = clip(exp(logit_scale), 1, 50) * (a @ B.T) + (En @ b) + c
P = softmax(s)
Tensors in head.safetensors: g, ge, Wh, We, b, c, logit_scale (all float32). For the
two mlp variants, a = gelu(hn @ W1 + b1) @ W2 replaces a = hn @ Wh.
hidden = 1536 for this backbone. E[i] is the mean over the backbone's input embeddings of the
tokens of " " + label[i].
mlx 0.32.x, mlx-lm 0.31.3), Python 3.12.Be honest about what this is: the head is a plain safetensors dict, and the surrounding
engine (prefill → KV-cache broadcast → batched branch pass → restricted read-out) is not published.
So this is not a one-line from_pretrained. What you get is the exact maths above plus a runnable
reference implementation of the head itself.
# pip install safetensors numpy
import math
import numpy as np
from safetensors.numpy import load_file
W = load_file("p3h-kd-a05/head.safetensors") # or p3h-ce-mlp / p3h-kd-mlp-a05 for the mlp heads
def rms(x, eps=1e-6):
return x / np.sqrt((x * x).mean(axis=-1, keepdims=True) + eps)
# the two `mlp` heads in this repo were trained with EXACT GELU, x * Phi(x) -- not the tanh
# approximation, not ReLU. numpy has no erf, so vectorise math.erf rather than substitute.
_erf = np.vectorize(math.erf)
def gelu(x):
return x * (1.0 + _erf(x / math.sqrt(2.0))) / 2.0
def head_scores(h, E, W):
"""h: [hidden] backbone hidden state. E: [n_labels, hidden] mean-pooled label embeddings."""
hn = rms(h.astype(np.float32)) * W["g"]
En = rms(E.astype(np.float32)) * W["ge"]
a = hn @ W["Wh"] if "Wh" in W else gelu(hn @ W["W1"] + W["b1"]) @ W["W2"]
B = En @ W["We"]
a = a / max(np.linalg.norm(a), 1e-6)
B = B / np.maximum(np.linalg.norm(B, axis=-1, keepdims=True), 1e-6)
scale = float(np.clip(np.exp(W["logit_scale"][0]), 1.0, 50.0))
return scale * (a @ B.T) + (En @ W["b"]) + W["c"]
# sanity check against the published metadata rather than trusting the file
import json, hashlib
man = {m["version"]: m for m in json.load(open("manifest.json"))}
blob = open("p3h-kd-a05/head.safetensors", "rb").read()
assert hashlib.sha256(blob).hexdigest() == man["p3h-kd-a05"]["sha256"]
print("shapes:", {k: v.shape for k, v in W.items()})
This is the real scorer, not a paraphrase of it. Both branches were run against the trained MLX modules that produced these weights, on fixed deterministic inputs, and agree to under 1e-5:
| head | weights | max abs difference vs the MLX scorer |
|---|---|---|
bilinear | p3h-kd-a05 | 1.431e-06 |
bilinear | p3h-ce-r64 | 1.907e-06 |
mlp | p3h-ce-mlp | 1.631e-06 |
mlp | p3h-kd-mlp-a05 | 2.891e-06 |
Full method and inputs: REFERENCE_CHECK.md in this repo.
To get h and E you need the backbone. E is straightforward (embed " " + label, mean-pool).
h is whatever hidden state you choose to read at — the numbers in this card come from a specific
read position produced by unpublished code, so your numbers will differ if you read elsewhere,
and I would not expect to reproduce the table above without reimplementing that pipeline.
Upstream, clear:
mlx-community/Qwen2.5-1.5B-Instruct-4bit,
revision 8b403126fc14f14cfc99bb4cfa72ecbc129ea677, Apache-2.0, derived from
Qwen/Qwen2.5-1.5B. Its weights are not redistributed here — download it from its own repo.lmstudio-community/Qwen3.8-27B-MLX-4bit,
revision 6067b15cf581666a4aecf6af3afaba4bb5efc20c, Apache-2.0. Not redistributed here
either, and not a model I made — I only evaluated it and used its outputs as soft targets.ehovy/race
publishes terms restricting use to research, and allenai/scicite,
tdiggelm/climate_fever and allenai/openbookqa carry no resolvable licence (unknown), while
allenai/winogrande carries no licence tag at all.These head weights: I have not selected an open-source licence for them, so the default applies — copyright retained by the author, published here so the experiment can be read, checked and reproduced. I am not granting broader rights in this card because I do not want to guess, and because anyone using these weights still has to satisfy the upstream dataset terms above independently. If you want a specific licence, open a discussion and ask.
See LICENSE.md in this repo for the same statement in one place.
Michael Chen. Questions or corrections: the Community tab of this repo.
No paper. If you refer to this, please describe it as what it is: a personal side-project experiment, including its negative transfer result.