Bauxi-23M
Does this decision still hold, given what just happened?
Bauxi-23M is a small English classifier that reads a decision and a new piece of
evidence and answers valid, invalid or unknown. It makes that call in one
forward pass on a CPU, with no GPU, no text generation and no network.
| Runs in | 0.1 of a CPU core and 80 MiB of RAM |
| Latency | 3–6 ms on one core for a typical pair, 35 ms at the longest |
| Size | 22 MiB on disk, INT8 |
| Parameters | 22.76 million |
| Accuracy | 90.7% on a sealed 150-case holdout, 71.1% on a later stress test of its weak spots |
| Use it as | A first-pass flag, never as the only check before an action |
| License | MIT, with a clean training lineage |
It is the first model from Classifyre.
What it answers
| Answer | Meaning | What your application does |
|---|---|---|
valid |
The evidence supports the decision, or leaves an unconditional one unchanged. | Carry on through your normal checks. |
invalid |
The evidence defeats a prerequisite or withdraws the decision. | Pause, and replan or ask for a new decision. |
unknown |
A necessary fact is missing, unresolved, stale or about something else. | Get current evidence, or ask. |
Real outputs of this model:
| Decision | Evidence | Bauxi | Score |
|---|---|---|---|
| Deploy if the integration tests pass. | All integration tests passed this morning. | valid |
0.99 |
| Deploy if the integration tests pass. | The integration tests failed. | invalid |
0.99 |
| Deploy if the integration tests pass. | The integration tests are still running. | unknown |
0.89 |
| Book the hall only if it seats at least 120 people. | The hall has 150 chairs, but 40 of them are broken. | invalid |
0.99 |
| Buy the licence if the total stays at or below 500 euros. | The price is 480 euros, but the setup fee has not been quoted. | unknown |
0.98 |
| Send the contract after legal and finance have both signed. | Finance signed. Legal rejected the draft. | invalid |
0.98 |
| Rename the planning sheet to 'Northwind'. | An unrelated printer jam was reported. | valid |
1.00 |
And a miss, because you should see one:
| Decision | Evidence | Bauxi | Score | Right answer |
|---|---|---|---|---|
| Ship the order before the Friday inspection. | The courier confirmed delivery on Wednesday. | invalid |
0.53 | valid |
Ordering days of the week is its weakest skill. See limits.
Use it
pip install onnxruntime tokenizers numpy huggingface_hub
import sys
from huggingface_hub import snapshot_download
path = snapshot_download(
"classifyre/Bauxi-23M",
allow_patterns=["bauxi.py", "bauxi.json", "tokenizer.json", "onnx/*", "ort/*"],
)
sys.path.insert(0, path)
from bauxi import Bauxi
model = Bauxi(path)
result = model.predict(
decision="Deploy if the integration tests pass.",
evidence="The integration tests failed.",
)
print(result.label) # invalid
print(result.probabilities) # {'valid': 0.01, 'invalid': 0.99, 'unknown': 0.00}
Bauxi picks the graph built for your machine on Linux (arm64 or amd64) and falls
back to the portable ONNX model anywhere else, including macOS and Windows. It
checks the hash of every file it loads and uses one CPU thread unless you pass
threads=.
Bauxi-23M is not a transformers pipeline model, on purpose. It needs a pair of
texts and 346 numeric features computed by bauxi.py, and a stock
text-classification pipeline can give it neither. So this repository has no
config.json, and loading it that way fails loudly instead of returning worse
answers quietly. model.safetensors holds the FP32 weights for fine-tuning.
As a service
curl http://localhost:8080/decide \
-H 'Content-Type: application/json' \
-d '{"decision":"Deploy if the integration tests pass.","evidence":"The integration tests failed."}'
{
"prediction": "invalid",
"probabilities": { "valid": 0.01, "invalid": 0.99, "unknown": 0.0 },
"calibrated": false
}
serving/ holds the FastAPI service. GET /health is the probe. Empty, extra or
oversized fields get HTTP 422; nothing is ever truncated.
Input rules
- English, one short decision and one short piece of evidence.
- 1–800 characters per field, and at most 128 tokens together.
- Longer input is refused, because truncation could cut the very fact that matters.
Help it by being explicit: name the action, the entity and the condition, and resolve pronouns, dates and units before you call it.
Benchmarks
The full report, with every model and every measurement, is in BENCHMARKS.md.
Quality
All numbers are from the published INT8 model. Predictions for the holdout are in
eval/, so you can check them.
| Evaluation | Cases | Accuracy | Macro-F1 | Invalid recall | False-safe | Role |
|---|---|---|---|---|---|---|
| Sealed holdout | 150 | 90.7% | 0.906 | 90% | 5/50 | Final test |
| Stress test | 180 | 71.1% | 0.719 | 66.7% | 20/60 | Aimed at known weak spots |
| Regression set | 888 | 97.1% | 0.971 | 92.2% | 5/296 | Generated, same grammar as training |
| Development | 660 | 96.5% | 0.964 | 91.8% | 15/220 | Used to select the model, so optimistic |
False-safe means an invalid case answered valid. It is the expensive error,
so watch it separately: at 5/50 it is high enough that valid must never be the
only thing standing between a model and an action.
On the holdout, per class:
| Precision | Recall | F1 | |
|---|---|---|---|
valid |
87.5% | 84% | 0.857 |
invalid |
86.5% | 90% | 0.882 |
unknown |
98% | 98% | 0.980 |
Confusion on the holdout (rows are the truth):
→ valid |
→ invalid |
→ unknown |
|
|---|---|---|---|
valid |
42 | 7 | 1 |
invalid |
5 | 45 | 0 |
unknown |
1 | 0 | 49 |
For comparison, the obvious alternative is to take a stock NLI cross-encoder and
map entailment, contradiction and neutral onto the three answers. On the same
holdout, cross-encoder/nli-MiniLM2-L6-H768 (82 million parameters) scores
43.3%. Whether a statement follows from a text is a different question from
whether a decision can still go ahead.
CPU
Bauxi-23M was built for small pods. It was measured as a Kubernetes pod with hard limits of 100m CPU and 80 MiB of memory, on Linux arm64 on an Apple M1 Pro, with the load generated from outside the pod. Peak memory is the cgroup's own charge across startup and every phase.
At the limit: 0.1 of a core, 80 MiB. Peak memory was 66.4 MiB in this run and 76.6 MiB in an earlier one, with zero restarts, OOM kills or memory-limit events.
| Workload | Requests | Throughput | CPU used | Latency p50 | Latency p95 |
|---|---|---|---|---|---|
| Idle, with health probes | – | – | 2.6m | – | – |
| Paced at 5 per second | 300 | 5.0/s | 59m | 15 ms | 100 ms |
| One at a time, flat out | 200 | 8.0/s | 100m | 103 ms | 201 ms |
| Five at once | 100 | 9.2/s | 100m | 505 ms | 696 ms |
| Longest input, 127 tokens | 100 | 2.1/s | 100m | 496 ms | 695 ms |
At 0.1 of a core the quota is the bottleneck, so latency climbs as soon as requests queue. Below about 5 requests per second it stays comfortable.
On one full core. The same image in a container limited to one CPU, in process, with no HTTP and one thread:
| Tokens | Latency p50 | Latency p95 | Predictions per second |
|---|---|---|---|
| 7 | 2.2 ms | 2.6 ms | 436 |
| 15 | 3.5 ms | 3.9 ms | 282 |
| 31 | 6.7 ms | 8.4 ms | 143 |
| 63 | 14.1 ms | 21.0 ms | 65 |
| 127 | 35.9 ms | 42.7 ms | 27 |
The model loads in about 0.2 seconds. Peak process RSS here was about 90 MiB, which is higher than the pod's charge because shared, memory-mapped pages count differently.
These numbers are from one machine; measure on your own hardware before you rely on
them. The amd64 graph was built and verified under emulation, where every holdout
label matches, but it has no native x86 benchmark yet. Staying near 80 MiB also
depends on the settings in serving/: one process, one worker, one ONNX thread,
staged loading and MALLOC_ARENA_MAX=1. Plan for the higher peak: 76.6 MiB is close to the limit, and the gap is not
spare capacity.
Compared with hosted decision models
On the same 330 cases, 25 hosted decision models set the bar. They are far larger and need a network; the smallest is about 175 times the size of Bauxi-23M.
| Model | Size | Holdout | False-safe | Stress | False-safe | $ per 1,000 |
|---|---|---|---|---|---|---|
| Drex Max | large | 98.7% | 0/50 | 96.7% | 1/60 | 0.010 |
| Mammoth 1 | 27B | 97.3% | 0/50 | 96.7% | 0/60 | 0.017 |
| Jev 1.14 | not stated | 87.3% | 3/50 | 91.7% | 3/60 | 0.017 |
| Kev 4B | 4B | 89.3% | 3/50 | 82.2% | 6/60 | 0.004 |
| Bauxi-23M | 0.023B | 90.7% | 5/50 | 71.1% | 20/60 | local |
Over both sets it places 25th of 26. If your data may leave the machine, a hosted decision model is the better choice. Bauxi-23M is for places where it may not, or where there is no network, no per-call budget, or only a small pod to run in.
What the numbers mean
The 90.7% is a point estimate from 150 cases in 50 scenario families, written by one author. Resampling whole families gives a 95% interval of 85.3–95.3%. The model, preprocessing and temperature were sealed before the holdout was opened, and nothing was tuned afterwards. An earlier model had been scored on the same 150 cases; no error from those cases went into training this one.
The 71.1% comes from 180 cases in 60 families, written later and weighted to the
categories that failed on the holdout, so it measures the weak spots and not general
accuracy. It is still the more useful warning: most of its 20 false-safes are one
mistake, where the condition's words appear in the evidence and Bauxi-23M answers
valid. "Until the tank is full" with a full tank is the typical case. Neither set
has independent human adjudication, and neither is a promise for your traffic.
How it works
evidence + decision ──► MiniLM encoder (6 layers, 384 wide) ──► pooled text
│
numbers, weekdays, clock times ──► 346 relations ───────────────────►├──► head ──► logits
- Encoder: a six-layer MiniLM, 384 hidden units, 12 attention heads and a 30,522-word vocabulary. Evidence comes first in the pair.
- Numeric relations:
bauxi.pyextracts up to six numbers per field and exposes comparisons, differences, sums, weekday and clock order to a learned head. They contain no rules and never pick a label; the head decides what they mean. This is why "26 chairs, four broken" can fail "at least 24". - Quantization: dynamic UINT8 on MatMul and Gather. Compared with FP32, 98.8% of labels are unchanged across 664 cases.
- Temperature: 1.573, fitted on 444 separate synthetic cases. Responses carry
calibrated: falsebecause that does not establish real-world calibration: a score of 0.95 is not a 95% chance of being right.
Training
| Step | Data | Device |
|---|---|---|
| NLI warm-up, 4 epochs | 102,885 WANLI pairs | Apple MPS |
| Decision training, 3,500 of 4,000 steps | 51,475 cases | Apple MPS |
Each decision-training batch of 32 holds 8 task cases, 4 WANLI cases, 10 numeric
contrasts and 10 language contrasts, so that the large public set cannot drown the
small curricula that carry the hard cases. Weights are an exponential moving average
(0.999), and the invalid class has a loss weight of 1.2. The checkpoint was chosen on
development data, on 0.3 × generated macro-F1 + 0.7 × hard macro-F1 − 0.2 × hard false-safe rate.
Most of the training data is authored and generated: prerequisites, AND/OR, entity and time scope, unreliable sources, negation, numbers, deadlines and irrelevant updates. Each scenario appears with all three labels, so that no word can stand in for the answer. Training used an accelerator; inference needs only a CPU.
Use it for
Use it to decide what a person or a stronger system should look at first, on your own hardware, with nothing leaving the machine.
- Agent replanning: a dependency moved, so review the plan before the next step.
- Approval changes: one of two required reviewers withdrew.
- Workflow prerequisites: the inspection is still pending, so wait.
- Memory upkeep: a new update conflicts with a stored commitment, so queue it for review.
- Document review: check a corpus against a standing decision. In
Classifyre it runs as a code detector;
every answer on both test sets was reproduced through a full scan, in
eval/classifyre-pipeline.json.
Never let valid authorize an action by itself. With thresholds fitted beforehand,
requiring P(valid) ≥ 0.995 gave no wrong go-aheads on the 330 test cases, but only 3.6%
of cases passed it. So treat invalid and unknown as useful flags, and treat valid
as "no flag raised", not as permission. Start in shadow mode and compare it with what
your operators decide.
Limits
- Not an authorization engine. It does not authenticate facts, retrieve anything or grant permission. Real permissions, signatures and arithmetic belong in code.
- Known weak spots: "either A or B" when both fail (22/36 correct on the stress test),
untilandwhileconditions (19/36), and weekday order (28/36). - Numbers are bounded. It sees six numbers per field. It does not solve arithmetic in general, relative dates, time zones, unit conversion or business calendars.
- English only, short pairs only. Long histories, several decisions at once, unresolved pronouns and mixed languages are out of scope and were not evaluated.
- Probabilities differ slightly between CPU types. Labels on the holdout match between arm64 and amd64, but scores can move by up to 0.1 near a decision boundary.
License and lineage
Weights, code and authored data are MIT. Every input was chosen so that this holds:
| Component | License |
|---|---|
| Encoder start: nreimers/MiniLM-L6-H384-uncased | MIT |
| Language data: WANLI | CC BY 4.0, attributed in NOTICE.md |
| Decision data: authored and generated for this model | MIT |
WANLI was used for training and is not redistributed here. Its labels were renamed and its hypotheses restated as prerequisites; NOTICE.md gives the changes and the citation.
Citation
@misc{classifyre-bauxi-2026,
title = {Bauxi-23M: a small CPU classifier for checking decisions against evidence},
author = {{Classifyre}},
year = {2026},
url = {https://huggingface.co/classifyre/Bauxi-23M}
}
Model tree for classifyre/Bauxi-23M
Base model
nreimers/MiniLM-L6-H384-uncasedDataset used to train classifyre/Bauxi-23M
Evaluation results
- Accuracy on Bauxi-23M authored holdout (150 cases, 50 scenario families)self-reported0.907
- Macro F1 on Bauxi-23M authored holdout (150 cases, 50 scenario families)self-reported0.906
- Accuracy on Bauxi stress test of known weak spots (180 cases, 60 scenario families)self-reported0.711