CoolFace
Modelpublic

usejul/minicpm5-2b-decision

sourceHugging Faceapache-2.0updated 2d agoView on Hugging Face
1likes134downloads
Model Card

minicpm5-2b-decision (PyTorch, bf16) — v1.1

A decision model: one text (the state) and typed questions in, a probability per option out, in one forward pass. It never generates text. MiniCPM5-2B with a merged LoRA (r=16) and a pointer head, trained on multi-task decision data, in bfloat16, unquantized. The 4-bit MLX build is at `bdauzats/minicpm5-2b-decision-mlx-4bit`.

Made for [jul](https://github.com/bdauzats/jul) — local typed decisions with the interface of TypeSafe's (Jev) Python SDK, on Apple Silicon through MLX or anywhere else through PyTorch.

It follows the architecture and training code of Kev (Jared Palmer, Apache-2.0), which reconstructs a Jev-style model, applied here to a different base model and a different data mix.

What changed in v1.1

Trained on 44,720 records instead of 15,061, of which about 15% are French. French accuracy rises from 0.485 to 0.710 on MASSIVE, and English from 0.690 to 0.815 — the previous version's headline limitation, "another language costs about 20 points", is now about 10. Calibration error on 59-option questions falls from 0.241 to 0.069.

It is also trained for one epoch, not two. Keeping a checkpoint per epoch showed the second one buys 0.3 points of accuracy on unseen sources while more than doubling its confident errors (0.6% → 1.4%) and needing a much harsher temperature (1.29 → 2.14) that then misfits questions with many options. The first epoch is what ships.

The previous version stays available as revision="v1.0".

Use it with jul

bash
jul models add minicpm5-2b-decision --repo bdauzats/minicpm5-2b-decision --backend torch
jul ask choice "Which team should handle this ticket?" -o billing -o shipping -o access \
    --state "I was charged twice for order 4411" --model minicpm5-2b-decision
python
from jul import TypeSafeClient, Choice, Noul, Score

client = TypeSafeClient(model="minicpm5-2b-decision")
answer = client.system_one(
    state={"subject": "Charged twice", "body": "I see two charges for order #4411."},
    questions={"team": Choice(instructions="Which team should handle this ticket?",
                              criteria={"billing": "Payments and refunds",
                                        "shipping": "Delivery problems",
                                        "access": "Login and account access"}),
               "angry": Noul(instructions="Is the customer angry?"),
               "priority": Score(instructions="How urgent is this ticket?",
                                 criteria=["low", "normal", "high"])})

jul needs no code specific to this model: the input format, the readout, the pointer head and its temperature are described in decision.json, next to the weights. Kev's own server also serves these weights, from the unquantized checkpoint.

Results

Four zero-shot classification sets (BTZSC: FinancialPhraseBank, Yahoo Topics, Empathetic, Massive), 200 examples each. Measured on the 4-bit MLX build, which is the protocol the v1.0 numbers were measured with, so the two columns compare to each other rather than to these unquantized weights — quantizing costs about 4 points.

Development setv1.0**v1.1**
FinancialPhraseBank (3 options)0.7550.740
Yahoo Topics (10 options)0.6100.565
Empathetic (32 options)0.3950.460
Massive (59 options)0.6650.715
mean0.6060.620
mean calibration error0.1520.140

On these English sets the two versions are level. The mean moves by 1.4 points where the sampling noise on 800 questions is about 1.7, and two sets go each way. What is not noise is Massive: +5.0 points of accuracy and a calibration error divided by 3.5 (0.241 → 0.069), which is where the gentler temperature of a one-epoch model shows.

Measured in PyTorch, fp32, before quantization, on sources the model was never trained on:

v1.0**v1.1**
transfer-v4 (656 questions)0.7210.739
decision-v7 held-out (1264)0.8460.826

Per task on transfer-v4: contrastive authorization 1.00, SciQ 0.91, composition (or-not) 0.88, composition (and-or) 0.84, PAWS 0.81, QNLI 0.81, Emotion 0.75, TweetEval 0.69, composition (conditional) 0.56, MMLU 0.49, date arithmetic 0.38.

French

MASSIVE's French and English test sets are translations of one another: 200 utterances, 59 intents, options in English in both runs, so what is measured is whether it understands French, not French option names.

EnglishFrenchGap
v1.0, PyTorch fp320.7450.545−20.0
v1.1, PyTorch fp320.8550.755−10.0
v1.0, MLX 4-bit0.6900.485−20.5
v1.1, MLX 4-bit0.8150.710−10.5

The gap is the same either way, so it is the model's and not the quantization's.

The v1.0 card said that in French jul's untrained vector method (0.535) beat this model. That is no longer true: 0.710 against 0.535. The remaining 10-point gap is the base model's, which loses about that much on the same pair read by the vector method.

Calibration

A temperature of 1.289, fitted once on the in-distribution development set, is applied at load time. It never changes an answer. On transfer-v4 it takes calibration error from 0.058 to 0.033 and confident errors — wrong with probability ≥ 0.9 — from 1.2% to 0.6%. Set KEV_TEMPERATURE=1.0, or pass your own, for the raw logits.

These are the unquantized weights, so the probabilities are the reference ones: the 4-bit build keeps the answer on almost every example but moves probabilities by up to ~0.23. If the probabilities themselves matter to you, fit a calibration on your own data (jul does this from a Context).

Latency

Measured on an M4 Pro (24 GB) on mains power, through jul, on the 4-bit MLX build — these bf16 weights are slower. A call encodes the state once; every question then continues from that cached prefix, so several questions about one text cost little more than the first.

Requestp50
3 short options (FinancialPhraseBank)64 ms
10 options, long texts (Yahoo Topics)136 ms
32 options (Empathetic)234 ms
59 long options (Massive)596 ms

Cost grows with the number and the length of the options, which are read on every request. jul can route questions with many options to its vector reading instead, which is flat in the option count — it measures the threshold and what it costs when you add the model.

Files

FileWhat it is
model.safetensors, config.json, tokenizer.jsonMiniCPM5-2B with the LoRA merged in, bfloat16
pointer_head.npzthe pointer head (two 2048 → 256 linear layers)
decision.jsondelimiter tokens, layout, readout, head file, temperature (1.289), training limits

Training data

Kev's decision-v7 suite (12,576 records), 2,800 of our own English sets (dair-ai/emotion 1,000, and 300 each of CAP SOTU, Manifesto, app reviews, Bias Frames intent, Wikipedia toxicity, TrueTeacher), and 7,299 French/English records built from MTOP intent and domain, Amazon reviews (fr), XNLI and PAWS-X — French questions and options, plus about 20% English-option twins. 44,720 records in all, none rejected or truncated.

The public sources include AG News, Banking77, DBpedia14, TREC, SST-5, Yelp, Amazon reviews, IMDB, BoolQ and MNLI, each from its training split, with its own license. Results on those tasks are therefore not zero-shot, which matters if you compare this model with one that has never seen them.

One epoch, LoRA r=16 α=32 on q/k/v/o_proj and gate/up/down_proj, pointer head from scratch, cross-entropy, lr 5e-5 (one-cycle), batch 8, bf16 autocast with fp32 master weights; 2 h 34 on one L40S.

Limitations

  • —The Jev benchmark has not been read for this checkpoint. v1.0's numbers on it do not carry over.
  • —French is measured on one dataset. 0.710 against 0.815 in English is a real gap, and nothing here says how it behaves in a third language.
  • —A state longer than 384 tokens, or a question longer than 1024, goes beyond what it saw in training.
  • —Weak on knowledge and arithmetic: on transfer-v4, MMLU 0.49 and date arithmetic 0.38. It sorts and decides; it does not reason over facts it was never given.
  • —No evaluation outside the sets above, and none of them is a benchmark of your own data. Measure it.