Text Classification
MLX
Safetensors
qwen3_5
clef
decision-model
systemone
structured-output
classification
qwen3.5
4-bit precision
Instructions to use TrevorJS/clef-flash-mlx-4bit with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use TrevorJS/clef-flash-mlx-4bit with MLX:
# Download the model from the Hub pip install huggingface_hub[hf_xet] hf download TrevorJS/clef-flash-mlx-4bit --local-dir clef-flash-mlx-4bit
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- Atomic Chat
File size: 9,954 Bytes
6d4dc3f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | """Clef / Clef-Flash (Cloudflare, Apache-2.0) on MLX, for scoring with readout_mlx.py.
The backbone runs through mlx-lm (a qwen3_5 checkpoint converted with mlx_lm.convert); the joint schema head and the
record encoding are ported from the release's joint_schema_model.py (text-only path). The head runs in float32.
clef = load("TrevorJS/clef-flash-mlx-8bit")
decide(clef, {"state": "Checkout fails for every customer.", "questions": {"urgent": {"type": "noul", "instructions": "Urgent?"}}})
python readout_mlx.py --clef --models TrevorJS/clef-flash-mlx-8bit (JevBench scoring in this repo)"""
import json, math, os, time
import numpy as np
import mlx.core as mx
import mlx.nn as nn
SYSTEM_PROMPT = ("Read the complete state and schema. Decide every field jointly. Each answer "
"must be exactly one of that field's allowed options.")
QUESTION_TYPES = {"noul": 0, "choice": 1, "score": 2}
def render(value):
return value if isinstance(value, str) else json.dumps(value, ensure_ascii=False, separators=(",", ":"), sort_keys=True)
def question_options(question):
t = str(question["type"])
if t == "noul":
c = {"true": "The proposition is true or the answer is yes.", "false": "The proposition is false or the answer is no."}
c.update(question.get("criteria") or {})
return [(k, c[k]) for k in ("true", "false")]
if t == "choice":
return sorted((str(k), v) for k, v in question["criteria"].items())
return [(str(i), v) for i, v in enumerate(question["criteria"])]
def encode_record(tok, record, max_length=16384):
"""Token ids plus, per question, its type, instruction span, option spans and option ids (release layout)."""
T = lambda s: tok.encode(s, add_special_tokens=False)
schema = T("\n\nSCHEMA FIELDS:\n"); qs = []
for qi, (qid, q) in enumerate(record["questions"].items()):
schema += T(f"\nFIELD {qi + 1}\nID: {qid}\nTYPE: {q['type']}\nINSTRUCTION: ")
q0 = len(schema); schema += T(render(q.get("instructions") or str(qid))); q1 = len(schema)
schema += T("\nALLOWED OPTIONS:\n"); spans, oids = [], []
for oi, (oid, desc) in enumerate(question_options(q)):
schema += T(f"OPTION {oi + 1}: "); s0 = len(schema)
sem = {"option_id": oid}
if desc is not None: sem["description"] = desc
schema += T(render(sem)); spans.append((s0, len(schema))); oids.append(oid)
schema += T("\n")
schema += T("END FIELD\n")
qs.append({"type": QUESTION_TYPES[str(q["type"])], "qspan": (q0, q1), "ospans": spans, "oids": oids})
prefix = T(f"<|im_start|>system\n{SYSTEM_PROMPT}<|im_end|>\n<|im_start|>user\nSTATE:\n")
suffix = T("\n<|im_end|>\n<|im_start|>assistant\n<think>\n\n</think>\n\nJOINT SCHEMA DECISIONS:")
state = T(render(record["state"]))[: max_length - len(prefix) - len(schema) - len(suffix)]
off = len(prefix) + len(state)
for q in qs:
q["qspan"] = (q["qspan"][0] + off, q["qspan"][1] + off)
q["ospans"] = [(a + off, b + off) for a, b in q["ospans"]]
return prefix + state + schema + suffix, qs
# ---------- joint schema head (torch module semantics, float32) ----------
def _ln(W, p, x, eps=1e-5):
mu = x.mean(-1, keepdims=True); var = ((x - mu) ** 2).mean(-1, keepdims=True)
return (x - mu) * mx.rsqrt(var + eps) * W[p + ".weight"] + W[p + ".bias"]
def _lin(W, p, x, bias=True):
y = x @ W[p + ".weight"].T
return y + W[p + ".bias"] if bias else y
def _mha(W, p, q_in, kv_in, heads):
"""torch.nn.MultiheadAttention (batch_first, no masks) for one sequence: q_in (Lq, w), kv_in (Lk, w)."""
w = q_in.shape[-1]; d = w // heads
Wq, Wk, Wv = mx.split(W[p + ".in_proj_weight"], 3, axis=0); bq, bk, bv = mx.split(W[p + ".in_proj_bias"], 3)
sh = lambda x: x.reshape(x.shape[0], heads, d).transpose(1, 0, 2)[None]
q, k, v = sh(q_in @ Wq.T + bq), sh(kv_in @ Wk.T + bk), sh(kv_in @ Wv.T + bv)
o = mx.fast.scaled_dot_product_attention(q, k, v, scale=1.0 / math.sqrt(d))[0].transpose(1, 0, 2).reshape(-1, w)
return _lin(W, p + ".out_proj", o)
def _normalize(x, eps=1e-12):
return x / mx.maximum(mx.linalg.norm(x, axis=-1, keepdims=True), eps)
class JointSchemaHead:
def __init__(self, path):
self.cfg = json.load(open(os.path.join(path, "joint_head_config.json")))
self.W = {k: v.astype(mx.float32) for k, v in mx.load(os.path.join(path, "joint_head.safetensors")).items()}
def __call__(self, hidden, ids, qs, lexical_rows):
"""hidden (L, H) final backbone states; lexical_rows(token_ids) -> output-embedding rows. Returns logits per question."""
W, c = self.W, self.cfg; heads = c["heads"]; w = c["width"]
h = _ln(W, "hidden_norm", hidden.astype(mx.float32))
memory = _lin(W, "memory_projection", h, bias=False)
g = h[-1]
span = lambda a, b: h[a:b].mean(0)
qv = mx.stack([span(*q["qspan"]) for q in qs])
types = mx.array([q["type"] for q in qs])
ctx = [mx.stack([span(a, b) for a, b in q["ospans"]]) for q in qs]
lex = [mx.stack([lexical_rows(ids[a:b]).astype(mx.float32).mean(0) for a, b in q["ospans"]]) for q in qs]
queries = mx.concatenate([_lin(W, "option_context_projection", cx, False) + _lin(W, "option_lexical_projection", lx, False)
+ _lin(W, "option_question_projection", qv[i], False)[None] for i, (cx, lx) in enumerate(zip(ctx, lex))])
for i in range(c["routing_layers"]):
p = f"evidence_layers.{i}"
mem = _ln(W, p + ".memory_norm", memory)
queries = queries + _mha(W, p + ".attention", _ln(W, p + ".query_norm", queries), mem, heads)
f = _ln(W, p + ".feedforward_norm", queries)
queries = queries + _lin(W, p + ".feedforward.3", nn.gelu(_lin(W, p + ".feedforward.0", f)))
counts = [len(q["ospans"]) for q in qs]
split = mx.split(queries, list(np.cumsum(counts)[:-1]), axis=0) if len(counts) > 1 else [queries]
base = _lin(W, "question_projection", qv, False)
summ = mx.stack([(mx.softmax(o @ base[i] / math.sqrt(w), axis=0)[:, None] * o).sum(0) for i, o in enumerate(split)])
fields = base + _ln(W, "option_summary_norm", summ) + _lin(W, "global_projection", g, False)[None] + W["type_embedding.weight"][types]
for i in range(c["layers"]):
p = f"layers.{i}"
x = _ln(W, p + ".norm1", fields); fields = fields + _mha(W, p + ".self_attn", x, x, heads)
fields = fields + _mha(W, p + ".multihead_attn", _ln(W, p + ".norm2", fields), memory, heads)
fields = fields + _lin(W, p + ".linear2", nn.gelu(_lin(W, p + ".linear1", _ln(W, p + ".norm3", fields))))
fields = _ln(W, "field_norm", fields)
prior_scale = mx.exp(mx.minimum(W["prior_logit_scale"], math.log(100.0)))
joint_scale = mx.exp(mx.minimum(W["joint_logit_scale"], math.log(100.0)))
gate = mx.sigmoid(W["residual_gate"])
out = []
for i, (q, o) in enumerate(zip(qs, split)):
anchor = _normalize(qv[i] + g)
prior = prior_scale * (_normalize(lex[i]) @ anchor)
o = _ln(W, "option_norm", o); f = mx.broadcast_to(fields[i], o.shape)
cos = (f * o).sum(-1) / mx.maximum(mx.linalg.norm(f, axis=-1) * mx.linalg.norm(o, axis=-1), 1e-8)
feats = mx.concatenate([f, o, f * o, mx.abs(f - o)], axis=-1)
resid = _lin(W, "residual_scorer.3", nn.gelu(_lin(W, "residual_scorer.0", feats)))[:, 0]
out.append(prior + gate * (joint_scale * cos + resid))
return out
def lexical_fn(model):
"""Rows of the output embedding (lm_head) for a list of token ids, dequantised if the head is quantised."""
lm = model.language_model.lm_head if hasattr(model.language_model, "lm_head") else model.language_model.model.embed_tokens
if hasattr(lm, "scales"):
return lambda t: mx.dequantize(lm.weight[mx.array(t)], lm.scales[mx.array(t)], lm.biases[mx.array(t)],
group_size=lm.group_size, bits=lm.bits)
return lambda t: lm.weight[mx.array(t)]
def load(path):
"""(model, tokenizer, head, lexical) from a local dir or HF repo holding an MLX backbone plus joint_head.*"""
from huggingface_hub import snapshot_download
from mlx_lm import load as mlx_load
path = path if os.path.isdir(path) else snapshot_download(path)
model, tok = mlx_load(path)
return model, tok, JointSchemaHead(path), lexical_fn(model)
def decide(clef, record, max_length=16384):
"""Jev/SystemOne-style record {"state": ..., "questions": {id: {type, instructions, criteria}}} ->
{question_id: {option_id: probability}}; all questions are scored jointly in one prefill."""
model, tok, head, lex = clef
ids, qs = encode_record(tok, record, max_length)
logits = head(model.language_model.model(mx.array(ids)[None])[0], ids, qs, lex)
return {qid: dict(zip(q["oids"], np.array(mx.softmax(l)).tolist()))
for qid, q, l in zip(record["questions"], qs, logits)}
def read_one_clef(model, tok, head, item, lex):
"""JevBench item -> probabilities in readout_mlx's option order, via Clef's own record layout."""
import readout_mlx as R
q = item["question"]; opts = R.options(q)
record = {"state": item["state"], "questions": {"decision": {"type": q["type"], "instructions": q["instructions"],
"criteria": q["criteria"]}}}
ids, qs = encode_record(tok, record)
t0 = time.perf_counter()
hidden = model.language_model.model(mx.array(ids)[None])[0]
logits = head(hidden, ids, qs, lex)[0]
pc = dict(zip(qs[0]["oids"], np.array(mx.softmax(logits)).tolist()))
dt = time.perf_counter() - t0
p = np.array([pc[str(k)] for _, k, _ in opts])
return R.row(item, opts, p, 1.0, len(ids), dt)
|