__ __ / /___ _ ___ __ __ ___ / // // _ `// _ \/ // /(_-< \___/ \_,_//_//_/\_,_//___/
Janus 4B: calibrated typed decisions in one forward pass.
Janus is an independent reconstruction of a "System 1" decision model, built from public descriptions of the
interface. Give Janus 4B a state (a ticket, a log, a policy, a JSON record) and any number of choice,
score and noul (yes/no) questions. In one forward pass, it returns a calibrated probability for every option
of every question, with no text generation. It is a LoRA adapter (r16) plus a pointer decision head on
Qwen/Qwen3.5-4B at revision 851bf6e806efd8d0a36b00ddf55e13ccb7b8cd0a.
The state is encoded once however many questions ride on it, each question is answered independently, a choice
can have up to 255 options, and one short question takes about 17 ms on an RTX 5090. For a smaller footprint, use
Janus 0.8B.
Code, server and Docker image: github.com/IcarusAICo/janus.
| Questions | choice (1 to 255 options), score (2 to 10 ordered levels, with the expected level), noul (p(true)) |
| Input | Up to 16,384 tokens in the Docker image (--max-tokens sets it); trained on requests up to 8,192 |
| Wire format | The POST /v1/systemone request and response shape; output tokens are always 0 |
| Memory | bf16 on an RTX 3090: 9.0 GB peak with a 150-token state, 9.9 GB with 4k tokens; about 17 GB reserved with the server's start-up pre-capture |
Results
All numbers are from 2026-09-23/24, with every system served by its own code: accuracy on one RTX 3090, latency on
an RTX 3090 and an RTX 5090. Janus 4B is the
v3-instruct checkpoint. Full tables and sources: release/RESULTS.md in the GitHub repository.
| set | metric | Janus 0.8B | Janus 4B | JevK5 v0.2 | Laya 0.3.11 |
|---|---|---|---|---|---|
| JevBench public items, easy / standard / hard | accuracy | 1.000 / 0.778 / 0.613 | 1.000 / 0.972 / 0.712 | 1.000 / 0.972 / 0.730 | 0.958 / 0.694 / 0.333 |
| JevBench public items, all 231 | accuracy | 0.745 | 0.853 | 0.861 | 0.576 |
| JevBench public hard tier | ECE | 0.072 | 0.071 | 0.062 | 0.206 |
| MASSIVE, 51 locales, official test split | 20-way intent accuracy / ECE | 0.635 / 0.059 | 0.767 / 0.039 | refused (16-option limit) | 0.272 / 0.173 |
| XNLI, 15 languages, official test split | accuracy / ECE | 0.655 / 0.044 | 0.757 / 0.055 | 0.608 / 0.165 | 0.522 / 0.051 |
The JevBench rows are our runs of the 231 public items through JevBench's own runner. They are not an official JevBench score (the official v1.4 score also pools 308 sealed items), and they were never used to select a checkpoint, with one disclosed exception: at our request, a candidate trained further on new multi-step data was run on them before we settled on this checkpoint. It scored hard 0.703 and was not chosen; this checkpoint is the one our pre-set procedure kept. A v1.4 what-if with an assumed sealed accuracy of 0.31 for every system (not an official score), scored the way the board scores self-hosted rows (list-price cost, timing on an RTX 5090), puts Janus 4B at 55.3 and JevK5 at 55.7: slightly behind, within run-to-run noise.
Latency. Median / p90 ms per request, warm, one request at a time, on the final serving code (start-up pre-capture on). Every system was served by its own code on the same card. Laya truncates long states to its context (26-35% of long-state requests), which also shortens its latency there. On the smaller cards Janus's long-state p90 includes graph re-captures after a memory release.
| request | card | Janus 0.8B | Janus 4B | JevK5 v0.2 | Laya 0.3.11 |
|---|---|---|---|---|---|
| 1 short question (XNLI) | RTX 5090 | 5.0 / 5.4 | 17.3 / 18.1 | 18.7 / 19.6 | 6.1 / 7.4 |
| RTX 3090 | 10.7 / 11.2 | 47.0 / 47.5 | 40.9 / 41.4 | 6.3 / 8.9 | |
| public-dataset test (1-2 questions) | RTX 5090 | 8.7 / 14.4 | 27.9 / 55.7 | 40.3 / 117.8 | 9.1 / 14.8 |
| RTX 3090 | 16.9 / 32.6 | 69.7 / 149.4 | 82.0 / 322.1 | 14.5 / 31.4 | |
| 1 question, long state (hard-tier v2) | RTX 5090 | 7.9 / 44.4 | 27.0 / 161.6 | 32.6 / 247.0 | 9.1 / 13.9 |
| RTX 3090 | 16.4 / 94.3 | 75.0 / 462.1 | 80.6 / 647.7 | 14.9 / 23.8 | |
| 8 questions, one long state | RTX 5090 | 34.3 / 88.5 | 107.0 / 271.4 | 251.8 / 1,964.4 | 26.0 / 56.4 |
| RTX 3090 | 73.4 / 169.6 | 337.8 / 857.3 | 641.7 / 5,194.8 | 66.6 / 110.9 |
Not chosen: distillation. We also trained 4Bs on a distilled mix (targets blended 0.5 gold + 0.5 from our own Janus 35B-A3B, plus multilingual rows), and kept this gold-trained checkpoint twice.
- Partial epoch. The first distilled 4B (38% of an epoch) was set aside before any of its neutral results had been
read. It then scored:
- JevBench public: 0.823 overall, hard 0.649, hard ECE 0.103.
- MASSIVE-51: 0.815 / 0.020 (better than this checkpoint). XNLI-15: 0.767 / 0.027 (better than this checkpoint).
- Hard-tier v2: 0.797 / 0.018. Judge v2: 0.740 / 0.092. Public-dataset test: 0.709 / 0.026.
- Full epoch. Distillation was then tested at a full epoch, under a rule agreed beforehand: keep distillation only if it beats this checkpoint on judge v2 and hard-tier v2. It was better on dev (NLL 0.336 against 0.374, accuracy 0.854 against 0.840), tied on hard-tier v2 (0.827 against 0.828) and on public test (0.728 both), and was worse on judge v2 (0.860 against 0.870, NLL 0.449 against 0.417). So distillation was dropped for the 4B. That decision was made before any of the full-epoch model's MASSIVE-51, XNLI-15 or JevBench results existed.
Use
pip install "janus @ git+https://github.com/IcarusAICo/janus"
import janus
m = janus.load("cmxu/janus-4b", device="cuda") # downloads this repo and the Qwen/Qwen3.5-4B backbone
response = m.predict(
"Refunds need a receipt and a purchase within 30 days. The customer bought 12 days ago and has no receipt.",
{"refund": {"type": "noul", "instructions": "Is a refund permitted under the policy?"},
"route": {"type": "choice", "instructions": "Which team should handle this?",
"criteria": {"billing": "Payments and refunds", "support": "Anything else"}}},
)
response["answers"]["refund"]["noul"] # p(true)
response["answers"]["route"]["probabilities"] # {"billing": ..., "support": ...}
m.predict_batch([request, ...]) scores many /v1/systemone bodies with shared forward passes. To serve over HTTP
from a download of this repo:
python -m janus serve --checkpoint model.pt --calibration calibration.json --model-id janus-4b --device cuda
The repository's Dockerfile builds an offline container around a pinned revision of this repo.
Files
| file | contents |
|---|---|
model.pt |
trainable tensors only (LoRA + decision head), sha256 95101dff8cadd8519496a6a880366351b14b9df3dac9cbc6068e901a664f41de (v3-instruct); the backbone is fetched from Qwen/Qwen3.5-4B at 851bf6e806efd8d0a36b00ddf55e13ccb7b8cd0a |
calibration.json |
global, per-family and per-option-count temperatures, bound to model.pt by its sha256 |
janus_config.json |
model id and pinned base model |
The global (fallback) temperature is 1.0239. A request whose group_id starts with family: gets that family's
temperature. Calibrated families: adversarial, ambiguous, arena, badges, civil, delivery, evidence,
helpsteer, judge_hard, long_policy, massive, multi_hop, ord, post, probability, quality,
record_match, rel, retrieval, routing_hard, tabfact, temporal_numeric, tradeoff, trap, urgency,
wikispeedia.
Training
Run: runs/phase4/production/qwen35_4b_prod_v3_instruct.
Data. 51,996 requests (
data/production-v3):- synthetic decision families, some generated by code and some written by GPT-5.6 Luna and reviewed by GPT-5.6 Terra (hard-tier policies, multi-step lookups, dates and numbers, probability, trade-offs, answer judging);
- public datasets converted to typed requests (the
datasetsabove); - Wikispeedia next-click decisions, long-context and many-option rows.
English only (MASSIVE en-US is its only MASSIVE data). Gold targets only, with no distillation. Rows are deduplicated by state and kept disjoint from evaluation states. The hard-tier generators reject any state sharing a 12-word sequence with a public JevBench item. The datasets and their licences are listed in the GitHub README.
Schedule. One full epoch: 1,625 steps of 32 requests, 50 warm-up steps, then cosine decay (backbone lr 2e-4, head lr 1e-3, weight decay 0.01). Cross-entropy plus an option-order consistency term (weight 0.1). RTX 5090, about 10.4 h.
Selection. Step 1,500 within the run, by the rule fixed before training: dev NLL plus the NLL on 300 held-out requests from unseen families. We then chose this run over the partial-epoch distilled 4B. On a shared dev split the two tied (NLL 0.3737 against 0.3720), and this run led on our three internal held-out splits (hard-tier v2 0.828 against 0.797, judge v2 0.870 against 0.740, public test 0.728 against 0.709). For this model those three splits are therefore selection numbers, not unbiased test results. The neutral comparisons are JevBench public, MASSIVE-51 and XNLI-15. JevBench did not enter the choice: this run's JevBench public numbers had been seen, but the distilled run had none yet, so no comparison was possible. The distilled run's MASSIVE-51 metrics existed but were unread when the choice was made, and its XNLI-15 had not run.
Multi-step top-up (not released). We then trained this checkpoint further on 848 new GPT-written multi-step lookup rows (data/hardtier-v3). The selection rule, fixed beforehand, kept step 0, i.e. this checkpoint unchanged. The final step was checked diagnostically and passed our held-out gate (hardtier-v3 multi_hop test 0.924 against 0.889). At our request its JevBench public score was then seen (hard 0.703, what-if 54.1) before we chose. It was not chosen, and none of that data is in this model.
Apart from that one look at the top-up candidate, JevBench items were never used for selection. The per-family temperatures were fitted after selection on 2,925 held-out calibration requests that share no state or group with training or selection data. The global (fallback) temperature, used for every request without a calibrated family, was then refit on the 1,028 hard-tier requests among them (1.1025 to 1.0239). That lowered hard-tier ECE (JevBench public hard 0.097 to 0.071, by exact re-tempering of the served probabilities) at a small cost on easier sets (public-dataset test 0.031 to 0.033; most on the multilingual sets, MASSIVE-51 0.028 to 0.039 and XNLI-15 0.045 to 0.055).
License and credits
Code and adapter weights: Apache-2.0. The base model Qwen3.5-4B is by the Qwen team (Apache-2.0). The training datasets keep their own licences.
"Jev", "System One" and TypeSafe are names of TypeSafe AI's products, used here only to describe interface compatibility.