basal-2.0 (4.5B) is part of basal-2.0, a family of typed-decision models for Polish and English; it is the main model: the best balance of accuracy and speed. Built on Bielik-4.5B-v3.0-Instruct. Trained further from earlier basal checkpoints.
What it is. The model reads a state (a message, a document, retrieved passages, an agent trace, a web page as
JSON) and answers a typed question about it (choice, yes/no noul, score, multi, act, tool) by returning a
calibrated probability for each allowed answer, in one forward pass, without generating text. The answer never falls
outside the options you give, and the probability says how sure the model is. Because the classes are described in the
request, one model serves many tasks without retraining: routing, rule and policy checks, questions about long
documents, RAG decisions, urgency and risk scores, judging answers, agent and tool decisions, and triage with a
confidence threshold.
| model | params | use |
|---|---|---|
| basal-2.0-mini | 1.5B | fastest |
| basal-2.0 (4.5B) | 4.5B | main model; experimental thinking mode |
| basal-2.0-max | 11B | most accurate |
Each model also comes as -FP8 (NVIDIA Hopper and Blackwell), -GGUF (Q8_0 and Q5_K_M; Ollama, llama.cpp) and -MLX-8bit (Apple Silicon).
tool question over OpenAI or MCP tool definitions picks the tool, not its arguments); triage that hands uncertain cases to a person (an act
question with a defer action and max_error).1. Install the basal engine v2.0.0 into a fresh uv environment (NVIDIA GPU; the cu128 torch needs a driver for CUDA 12.8 or newer; DGX Spark and B300 use the cu130 build, see docs/HARDWARE.md):
uv venv --python 3.12 --allow-existing ~/basal-env && source ~/basal-env/bin/activate
uv pip install torch==2.11.0 --index-url https://download.pytorch.org/whl/cu128
uv pip install "basal[fp8] @ https://github.com/rkinas/basal/archive/refs/tags/v2.0.0.tar.gz"
2. Start the server and wait for basal: model ... ready:
basal-serve --model Remek/basal-2.0-4.5B --mode fast --template-prefix off --port 8000
--template-prefix off is needed for this model (bf16 and -FP8): the prompt-template cache failed its check, and its numbers were measured without it.
3. Ask questions with curl. Several questions about one state go into one request and share the state:
curl -s localhost:8000/v1/systemone -H 'content-type: application/json' -d '{
"state": "Klient: od wczoraj nie mogę zalogować się do bankowości internetowej, system pokazuje błąd hasła.",
"questions": {
"dept": {"type": "choice", "instructions": "Do którego działu skierować zgłoszenie?",
"criteria": {"cards": "Reklamacje kart", "online": "Wsparcie bankowości elektronicznej", "loans": "Kredyty"}},
"urgent": {"type": "noul", "instructions": "Czy klient nie może korzystać z usługi?"}}}'
Each answer has a probability for every option, the chosen key and the confidence:
"dept": {"type": "choice", "choice": "<key>", "probabilities": {"cards": p, "online": p, "loans": p}, "confidence": p}.
To send uncertain decisions to a person, ask the same question as an act question with a defer action and
"max_error": 0.05 (max_error works on act and tool questions only):
curl -s localhost:8000/v1/systemone -H 'content-type: application/json' -d '{
"state": "Klient: od wczoraj nie mogę zalogować się do bankowości internetowej, system pokazuje błąd hasła.",
"questions": {
"dept": {"type": "act", "instructions": "Do którego działu skierować zgłoszenie?",
"criteria": {"cards": "Reklamacje kart", "online": "Wsparcie bankowości elektronicznej", "loans": "Kredyty"},
"option_keys": "hide", "costs": {"wrong": 19, "defer": 1}, "max_error": 0.05}}}'
The answer is the action with the lowest expected cost (an option key, or defer). Below the confidence threshold
certified for 5% error it returns defer, names the refused action and says "deferred": true. "option_keys": "hide" shows the model the option descriptions only, as the calibration was fitted (with the keys shown, answers say
"calibration": "unvalidated"); keep the cost of a wrong answer moderate, because a large one defers by expected cost
alone, before max_error is applied.
Or from Python (the package includes a small client):
from basal.client import Basal
b = Basal("http://127.0.0.1:8000")
state = "Klient: od wczoraj nie mogę zalogować się do bankowości internetowej, system pokazuje błąd hasła."
a = b.choice(state, "Do którego działu skierować zgłoszenie?",
{"cards": "Reklamacje kart", "online": "Wsparcie bankowości elektronicznej", "loans": "Kredyty"})
print(a["choice"], a["confidence"])
print(b.yes_no(state, "Czy klient nie może korzystać z usługi?")["noul"]) # P(yes)
Many items from a file: basal-run --input items.jsonl --output answers.jsonl. API and features: README.
The same basal engine v2.0.0 also serves the experimental basal-omni-1.0 models (images and audio): README.
Measured once on the frozen release weights, bf16 (basal-serve --mode fast) on one H100, and published as measured, including where basal-2.0 is not better.
| benchmark | basal-1.5 | basal-2.0 | 2.0 - 1.5 | note |
|---|---|---|---|---|
| Werdykt v1, macro [95% CI] | 0.721 [0.710, 0.733] | 0.729 [0.717, 0.739] | +0.7 pt | hidden PL + EN benchmark, 5,000 decisions |
| Werdykt v1, PL / EN | 0.724 / 0.718 | 0.729 / 0.728 | +0.4 / +1.0 pt | |
| Werdykt calibration error (ECE) | 0.068 | 0.060 | -0.008 | lower is better |
| Werdykt automated at 1% target error | 22.5% at 1.23% observed error | 11.9% at 0.66% observed error | share automated, error observed | |
| Werdykt automated at 5% target error | 50.1% at 5.04% observed error | 51.3% at 5.08% observed error | share automated, error observed | |
| Werdykt board rank | 46 | 45 | of 94 systems on the public board | |
| Werdykt speed pass, decisions/s at 16 clients (public board) | 23.0 (c1 p50 34 ms) | 32.2 (c1 p50 25 ms) | x1.4 | basal-1.5 on SGLang, basal-2.0 in basal-serve's default mode; bf16, one H100 80GB |
| Werdykt speed pass, both in basal-serve's default mode | 8.0 (c1 p50 31 ms) | 32.2 (c1 p50 25 ms) | x4.0 | basal-1.5 on its own basal-serve engine |
| Decision Index (JDI 0.2.1) | 27.65 | 39.86 | +12.21 | |
| JDI requests left unanswered | 17.2% (25,782 of 150,317) | 0.00% (0 of 150,317) | basal-1.5 answers questions with at most 10 options | |
| LEK, Polish medical licensing exam, PL / EN (% correct) | 60.5 / 59.7 | 62.0 / 63.0 | +1.5 / +3.3 pt | 5 options |
| PL-Holdout (4,000 items) | 0.8505 | 0.8560 | +0.0055 [-0.0018, +0.0130] | paired |
| Polish decisions (7,081 items) | 0.9223 | 0.9236 | +0.0013 [-0.0040, +0.0066] | paired |
| Polish general (3,079 items; dev label) | 0.7463 | 0.7619 | +0.0156 [+0.0042, +0.0276] | paired |
| English corpus test (1,479 items; dev label) | 0.6430 | 0.6444 | +0.0014 [-0.0169, +0.0189] | paired |
| English panel (10 tasks; mean accuracy) | 0.6881 | 0.6519 | -0.0362 (items -0.0362 [-0.0527, -0.0201]) | mean of tasks; items paired |
| PL-Real-300, doc-clean subset (158 items; primary) | 0.9494 | 0.9494 | +0.0000 [-0.0316, +0.0316] | paired |
| PL-Real-300, all 300 items | 0.9633 | 0.9433 | -0.0200 [-0.0433, +0.0033] | paired |
basal-1.5 is basal-1.5-4.5B, the previous release (Remek/basal-1.5-4.5B). Werdykt rows are the public board's rows (basal-1.5 served on SGLang, basal-2.0 by basal-serve v2.0.0 in its default mode), each release with its own calibration file, accuracy with --deterministic; automated at X% target error = a confidence threshold chosen on one half of the items for that error, applied to the other half. The speed pass is the board's (bf16, 16 clients, one H100 80GB; separate pods); the second speed row has basal-1.5 in basal-serve's default mode too. The rank is on the public Werdykt board (basal.si5.pl/benchmark), 94 systems, one row per system. All other rows: both models served by engine v2.0.0 (commit adc4c71) on the same items; differences are paired, with 95% bootstrap intervals. Polish general and the English corpus test were used to choose between training runs, so their numbers are development numbers. LEK questions: NASK-PIB Reassess, 2008-2026 sessions (CC BY-NC-SA 4.0), shown for the basal models only, with no comparison to other systems.
--template-prefix off: it failed this model's check). They need FlashAttention 3 (Hopper): the Werdykt rows on this card ran with FlashAttention 3; the release check, the port checks, the FP8 speed and the calibration ran with FlashAttention 2, without them. Engine v2.0.0 schedules long requests (over 2,048 tokens) behind short ones: in a mixed load at 32 clients short requests answer in 0.81 s instead of 1.38 s (p50), and long requests take about 7% longer (p50 1.96 s instead of 1.83 s); --scheduler off serves in arrival order.Off by default, and we recommend leaving it off. With --think auto the model writes a short reasoning note before
it decides, but only for questions whose calibrated confidence is below the type's certified 5% threshold; the decision is
then read out again over the state and the note, with its own calibration file (CALIBRATION.think.json). The note is
generated by a vLLM server that basal-serve starts in its own process on the think weights in think/. vLLM brings its
own torch (vLLM 0.31: torch built for CUDA 13.0, which needs an NVIDIA driver 580 or newer), so install it in its own
environment and point --think-python at that environment's Python; the server itself runs from the quick-start
environment:
uv venv --python 3.12 --allow-existing ~/basal-vllm
uv pip install --python ~/basal-vllm/bin/python "basal[vllm] @ https://github.com/rkinas/basal/archive/refs/tags/v2.0.0.tar.gz"
source ~/basal-env/bin/activate
basal-serve --model Remek/basal-2.0-4.5B --mode fast --template-prefix off --think auto --think-python ~/basal-vllm/bin/python --port 8000
basal-serve --model Remek/basal-2.0-4.5B --mode fast --template-prefix off --think auto --think-tokens 128 \
--think-python ~/basal-vllm/bin/python --port 8000 # shorter notes (default 256)
basal-serve --model Remek/basal-2.0-4.5B --mode fast --template-prefix off --port 8000 # off (the default: --think off)
Per request, on a --think auto server: "think": "auto" (the default), "off" (one-pass answer for this request) or
"force" (every question of a think type thinks); "think_text": true returns the note. A question that thought answers
with the second readout's probabilities and confidence plus "think": {"tokens", "ms", "calibration", "no_think": {...}};
every answer of a think server carries "meta": {"think": {"used", "tokens"}}. Multi questions, tool sets and requests
whose state was truncated never think. On a server started without --think auto (the default) there is no think path.
Measured once on Werdykt, both rows on the same pod (one H100 80GB, default mode, 16 clients for the speed column):
| server | macro | PL / EN | ECE | automated at 5% | questions that thought | c1 p50 | decisions/s |
|---|---|---|---|---|---|---|---|
--think off | 0.729 | 0.729 / 0.728 | 0.060 | 51.3% | - | 23 ms | 31.2 |
--think auto | 0.719 | 0.718 / 0.720 | 0.099 | 47.1% | 56.7% (mean 142 tokens) | 499 ms | 11.5 |
On Werdykt, thinking lowered the macro score by 0.9 points (0.719 against 0.729), raised the calibration error (0.099 against 0.060) and cut throughput to 11.5 decisions/s from 31.2. Every other number on this card is the default one-pass mode. Try --think auto only with your own measurement on
your own data.
Every port is checked against this bf16 model on the development set dev v2: agreement is the share of decisions where the port picks the same option. Bars for this tier: agreement 2-10 >= 0.9415, agreement at reference margin >= 0.2 >= 0.9863, accuracy within 1 point, ECE at most +0.01.
| package | agreement with bf16 (options 2-10) | agreement at margin >= 0.2 | accuracy diff | ECE diff | size | speed | gate |
|---|---|---|---|---|---|---|---|
| FP8 | 0.9478 | 0.9890 | -0.0008 | +0.0009 | 4.9 GB | 38.6 dec/s c16, c1 p50 29 ms (H100) | PASS |
| GGUF Q8_0 | 0.9839 | 0.9988 | +0.0002 | +0.0035 | 4.8 GB | not measured in this release | PASS |
| GGUF Q5_K_M | 0.9569 | 0.9920 | -0.0039 | +0.0040 | 3.2 GB | not measured in this release | PASS |
| MLX 8-bit | 0.9792 | 0.9989 | +0.0000 | -0.0009 | 4.9 GB | 278 ms c1 p50, 698 ms for 5 questions (Mac) | PASS |
For every question basal returns a probability for each answer; the confidence is the probability of the answer it picks. The probabilities are calibrated (a temperature per question type and option-count bucket) on question families that were never used for training, so they hold on new tasks but are cautious. Held-out numbers below are leave-one-family-out on the development and test splits.
| basal-2.0-4.5B, held-out | |
|---|---|
| calibration error (ECE) | 0.015 |
| worst question family (ECE) | 0.101 (agent) |
| automated at the 5% error threshold | 33.2% at 2.39% observed error |
| 1% error threshold, shipped file | defers every decision (no 1% threshold certified on the full fit) |
On Werdykt (hidden, measured once, not used to choose the model) the calibration error is 0.060 (basal-1.5: 0.068). The calibration is a file, separate from the weights (CALIBRATION.json, one
option order; CALIBRATION.o2.json, two orders), so you can swap it or refit it on your own data with basal-calibrate.
Fitted with engine v2.0.0 (commit adc4c71).
One decision = one question, one option order. Hardware is named on every number.
--attn fa3.--deterministic; default minus --deterministic accuracy on dev v2: bf16 +0.0000, FP8 -0.0025 (run 1; 32 requests in flight); --deterministic itself: bf16 200/200 identical at 16 clients, FP8 200/200 identical at 16 clients.basal-1.5's other configuration. basal-1.5 also shipped FP8 checkpoints served by vLLM. On one RunPod H100 (median of 3 fresh servers; synthetic ~1.6k-token states and a 400-request mix, a different workload from the Werdykt speed pass) the two basal-1.5 configurations measured:
| basal-1.5 configuration | c1 p50 | decisions/s, 16 / 32 clients | mixed traffic, sequential / 32 clients (requests/s) | 16k-token state x 5 questions |
|---|---|---|---|---|
| bf16 on v1.5.0 | 59.0 ms | 17.8 / 18.7 | 2.1 / 2.2 | 20,441 ms |
| FP8 package on v1.5.0 vLLM | 59.1 ms | 32.0 / 36.5 | 11.3 / 46.0 | 620 ms |
basal-2.0 was not measured on that machine, so the speed-ups in the results table hold only against the basal-1.5 rows of the Werdykt speed pass. For mixed traffic with long, repeated documents, measure both on your own load before you switch.
model.safetensors, tokenizer, chat_template.jinja: the modelCALIBRATION.json (one option order) and CALIBRATION.o2.json (two orders): per-type temperatures and confidence thresholds for 1% / 5% errorthink/ (the think weights) and CALIBRATION.think.json: the experimental thinking modebasal.json: prompt format 2 and readout protocolThe model receives a fixed chat prompt with the state, the question and coded options; the decision is the softmax over the next-token logits of the option codes only (one forward pass, fp32 code logits, no text generation), then the per-type calibrated temperature from CALIBRATION.json. Confidence thresholds were chosen on calibration data, before testing, for 1% or 5% error among accepted decisions.
Fine-tuned from Bielik-4.5B-v3.0-Instruct for typed decisions in Polish and English, trained further from earlier basal checkpoints. LoRA stages, merged.
"facts": "auto")."max_error": 0.01
on an act or tool question defers every decision; use 0.05, or certify thresholds on your own data with basal-calibrate.act and multi over many labels are experimental. Up to 255 options per question (Ollama: 20); the context is
32,768 tokens. The 2.0 models ship no evidence head.@software{kinas2026basal20,
title = {basal-2.0: typed-decision models and inference engine for Polish and English},
author = {Kinas, Remigiusz},
year = {2026},
version = {2.0.0},
url = {https://github.com/rkinas/basal}
}
Apache-2.0. Fine-tuned from speakleash/Bielik-4.5B-v3.0-Instruct (Apache-2.0). Bielik is a project of SpeakLeash and ACK Cyfronet AGH.
basal-2.0 (4.5B) is part of basal-2.0, a family of typed-decision models for Polish and English; it is the main model: the best balance of accuracy and speed. Built on Bielik-4.5B-v3.0-Instruct. Trained further from earlier basal checkpoints.
What it is. The model reads a state (a message, a document, retrieved passages, an agent trace, a web page as
JSON) and answers a typed question about it (choice, yes/no noul, score, multi, act, tool) by returning a
calibrated probability for each allowed answer, in one forward pass, without generating text. The answer never falls
outside the options you give, and the probability says how sure the model is. Because the classes are described in the
request, one model serves many tasks without retraining: routing, rule and policy checks, questions about long
documents, RAG decisions, urgency and risk scores, judging answers, agent and tool decisions, and triage with a
confidence threshold.
| model | params | use |
|---|---|---|
| basal-2.0-mini | 1.5B | fastest |
| basal-2.0 (4.5B) | 4.5B | main model; experimental thinking mode |
| basal-2.0-max | 11B | most accurate |
Each model also comes as -FP8 (NVIDIA Hopper and Blackwell), -GGUF (Q8_0 and Q5_K_M; Ollama, llama.cpp) and -MLX-8bit (Apple Silicon).
tool question over OpenAI or MCP tool definitions picks the tool, not its arguments); triage that hands uncertain cases to a person (an act
question with a defer action and max_error).1. Install the basal engine v2.0.0 into a fresh uv environment (NVIDIA GPU; the cu128 torch needs a driver for CUDA 12.8 or newer; DGX Spark and B300 use the cu130 build, see docs/HARDWARE.md):
uv venv --python 3.12 --allow-existing ~/basal-env && source ~/basal-env/bin/activate
uv pip install torch==2.11.0 --index-url https://download.pytorch.org/whl/cu128
uv pip install "basal[fp8] @ https://github.com/rkinas/basal/archive/refs/tags/v2.0.0.tar.gz"
2. Start the server and wait for basal: model ... ready:
basal-serve --model Remek/basal-2.0-4.5B --mode fast --template-prefix off --port 8000
--template-prefix off is needed for this model (bf16 and -FP8): the prompt-template cache failed its check, and its numbers were measured without it.
3. Ask questions with curl. Several questions about one state go into one request and share the state:
curl -s localhost:8000/v1/systemone -H 'content-type: application/json' -d '{
"state": "Klient: od wczoraj nie mogę zalogować się do bankowości internetowej, system pokazuje błąd hasła.",
"questions": {
"dept": {"type": "choice", "instructions": "Do którego działu skierować zgłoszenie?",
"criteria": {"cards": "Reklamacje kart", "online": "Wsparcie bankowości elektronicznej", "loans": "Kredyty"}},
"urgent": {"type": "noul", "instructions": "Czy klient nie może korzystać z usługi?"}}}'
Each answer has a probability for every option, the chosen key and the confidence:
"dept": {"type": "choice", "choice": "<key>", "probabilities": {"cards": p, "online": p, "loans": p}, "confidence": p}.
To send uncertain decisions to a person, ask the same question as an act question with a defer action and
"max_error": 0.05 (max_error works on act and tool questions only):
curl -s localhost:8000/v1/systemone -H 'content-type: application/json' -d '{
"state": "Klient: od wczoraj nie mogę zalogować się do bankowości internetowej, system pokazuje błąd hasła.",
"questions": {
"dept": {"type": "act", "instructions": "Do którego działu skierować zgłoszenie?",
"criteria": {"cards": "Reklamacje kart", "online": "Wsparcie bankowości elektronicznej", "loans": "Kredyty"},
"option_keys": "hide", "costs": {"wrong": 19, "defer": 1}, "max_error": 0.05}}}'
The answer is the action with the lowest expected cost (an option key, or defer). Below the confidence threshold
certified for 5% error it returns defer, names the refused action and says "deferred": true. "option_keys": "hide" shows the model the option descriptions only, as the calibration was fitted (with the keys shown, answers say
"calibration": "unvalidated"); keep the cost of a wrong answer moderate, because a large one defers by expected cost
alone, before max_error is applied.
Or from Python (the package includes a small client):
from basal.client import Basal
b = Basal("http://127.0.0.1:8000")
state = "Klient: od wczoraj nie mogę zalogować się do bankowości internetowej, system pokazuje błąd hasła."
a = b.choice(state, "Do którego działu skierować zgłoszenie?",
{"cards": "Reklamacje kart", "online": "Wsparcie bankowości elektronicznej", "loans": "Kredyty"})
print(a["choice"], a["confidence"])
print(b.yes_no(state, "Czy klient nie może korzystać z usługi?")["noul"]) # P(yes)
Many items from a file: basal-run --input items.jsonl --output answers.jsonl. API and features: README.
The same basal engine v2.0.0 also serves the experimental basal-omni-1.0 models (images and audio): README.
Measured once on the frozen release weights, bf16 (basal-serve --mode fast) on one H100, and published as measured, including where basal-2.0 is not better.
| benchmark | basal-1.5 | basal-2.0 | 2.0 - 1.5 | note |
|---|---|---|---|---|
| Werdykt v1, macro [95% CI] | 0.721 [0.710, 0.733] | 0.729 [0.717, 0.739] | +0.7 pt | hidden PL + EN benchmark, 5,000 decisions |
| Werdykt v1, PL / EN | 0.724 / 0.718 | 0.729 / 0.728 | +0.4 / +1.0 pt | |
| Werdykt calibration error (ECE) | 0.068 | 0.060 | -0.008 | lower is better |
| Werdykt automated at 1% target error | 22.5% at 1.23% observed error | 11.9% at 0.66% observed error | share automated, error observed | |
| Werdykt automated at 5% target error | 50.1% at 5.04% observed error | 51.3% at 5.08% observed error | share automated, error observed | |
| Werdykt board rank | 46 | 45 | of 94 systems on the public board | |
| Werdykt speed pass, decisions/s at 16 clients (public board) | 23.0 (c1 p50 34 ms) | 32.2 (c1 p50 25 ms) | x1.4 | basal-1.5 on SGLang, basal-2.0 in basal-serve's default mode; bf16, one H100 80GB |
| Werdykt speed pass, both in basal-serve's default mode | 8.0 (c1 p50 31 ms) | 32.2 (c1 p50 25 ms) | x4.0 | basal-1.5 on its own basal-serve engine |
| Decision Index (JDI 0.2.1) | 27.65 | 39.86 | +12.21 | |
| JDI requests left unanswered | 17.2% (25,782 of 150,317) | 0.00% (0 of 150,317) | basal-1.5 answers questions with at most 10 options | |
| LEK, Polish medical licensing exam, PL / EN (% correct) | 60.5 / 59.7 | 62.0 / 63.0 | +1.5 / +3.3 pt | 5 options |
| PL-Holdout (4,000 items) | 0.8505 | 0.8560 | +0.0055 [-0.0018, +0.0130] | paired |
| Polish decisions (7,081 items) | 0.9223 | 0.9236 | +0.0013 [-0.0040, +0.0066] | paired |
| Polish general (3,079 items; dev label) | 0.7463 | 0.7619 | +0.0156 [+0.0042, +0.0276] | paired |
| English corpus test (1,479 items; dev label) | 0.6430 | 0.6444 | +0.0014 [-0.0169, +0.0189] | paired |
| English panel (10 tasks; mean accuracy) | 0.6881 | 0.6519 | -0.0362 (items -0.0362 [-0.0527, -0.0201]) | mean of tasks; items paired |
| PL-Real-300, doc-clean subset (158 items; primary) | 0.9494 | 0.9494 | +0.0000 [-0.0316, +0.0316] | paired |
| PL-Real-300, all 300 items | 0.9633 | 0.9433 | -0.0200 [-0.0433, +0.0033] | paired |
basal-1.5 is basal-1.5-4.5B, the previous release (Remek/basal-1.5-4.5B). Werdykt rows are the public board's rows (basal-1.5 served on SGLang, basal-2.0 by basal-serve v2.0.0 in its default mode), each release with its own calibration file, accuracy with --deterministic; automated at X% target error = a confidence threshold chosen on one half of the items for that error, applied to the other half. The speed pass is the board's (bf16, 16 clients, one H100 80GB; separate pods); the second speed row has basal-1.5 in basal-serve's default mode too. The rank is on the public Werdykt board (basal.si5.pl/benchmark), 94 systems, one row per system. All other rows: both models served by engine v2.0.0 (commit adc4c71) on the same items; differences are paired, with 95% bootstrap intervals. Polish general and the English corpus test were used to choose between training runs, so their numbers are development numbers. LEK questions: NASK-PIB Reassess, 2008-2026 sessions (CC BY-NC-SA 4.0), shown for the basal models only, with no comparison to other systems.
--template-prefix off: it failed this model's check). They need FlashAttention 3 (Hopper): the Werdykt rows on this card ran with FlashAttention 3; the release check, the port checks, the FP8 speed and the calibration ran with FlashAttention 2, without them. Engine v2.0.0 schedules long requests (over 2,048 tokens) behind short ones: in a mixed load at 32 clients short requests answer in 0.81 s instead of 1.38 s (p50), and long requests take about 7% longer (p50 1.96 s instead of 1.83 s); --scheduler off serves in arrival order.Off by default, and we recommend leaving it off. With --think auto the model writes a short reasoning note before
it decides, but only for questions whose calibrated confidence is below the type's certified 5% threshold; the decision is
then read out again over the state and the note, with its own calibration file (CALIBRATION.think.json). The note is
generated by a vLLM server that basal-serve starts in its own process on the think weights in think/. vLLM brings its
own torch (vLLM 0.31: torch built for CUDA 13.0, which needs an NVIDIA driver 580 or newer), so install it in its own
environment and point --think-python at that environment's Python; the server itself runs from the quick-start
environment:
uv venv --python 3.12 --allow-existing ~/basal-vllm
uv pip install --python ~/basal-vllm/bin/python "basal[vllm] @ https://github.com/rkinas/basal/archive/refs/tags/v2.0.0.tar.gz"
source ~/basal-env/bin/activate
basal-serve --model Remek/basal-2.0-4.5B --mode fast --template-prefix off --think auto --think-python ~/basal-vllm/bin/python --port 8000
basal-serve --model Remek/basal-2.0-4.5B --mode fast --template-prefix off --think auto --think-tokens 128 \
--think-python ~/basal-vllm/bin/python --port 8000 # shorter notes (default 256)
basal-serve --model Remek/basal-2.0-4.5B --mode fast --template-prefix off --port 8000 # off (the default: --think off)
Per request, on a --think auto server: "think": "auto" (the default), "off" (one-pass answer for this request) or
"force" (every question of a think type thinks); "think_text": true returns the note. A question that thought answers
with the second readout's probabilities and confidence plus "think": {"tokens", "ms", "calibration", "no_think": {...}};
every answer of a think server carries "meta": {"think": {"used", "tokens"}}. Multi questions, tool sets and requests
whose state was truncated never think. On a server started without --think auto (the default) there is no think path.
Measured once on Werdykt, both rows on the same pod (one H100 80GB, default mode, 16 clients for the speed column):
| server | macro | PL / EN | ECE | automated at 5% | questions that thought | c1 p50 | decisions/s |
|---|---|---|---|---|---|---|---|
--think off | 0.729 | 0.729 / 0.728 | 0.060 | 51.3% | - | 23 ms | 31.2 |
--think auto | 0.719 | 0.718 / 0.720 | 0.099 | 47.1% | 56.7% (mean 142 tokens) | 499 ms | 11.5 |
On Werdykt, thinking lowered the macro score by 0.9 points (0.719 against 0.729), raised the calibration error (0.099 against 0.060) and cut throughput to 11.5 decisions/s from 31.2. Every other number on this card is the default one-pass mode. Try --think auto only with your own measurement on
your own data.
Every port is checked against this bf16 model on the development set dev v2: agreement is the share of decisions where the port picks the same option. Bars for this tier: agreement 2-10 >= 0.9415, agreement at reference margin >= 0.2 >= 0.9863, accuracy within 1 point, ECE at most +0.01.
| package | agreement with bf16 (options 2-10) | agreement at margin >= 0.2 | accuracy diff | ECE diff | size | speed | gate |
|---|---|---|---|---|---|---|---|
| FP8 | 0.9478 | 0.9890 | -0.0008 | +0.0009 | 4.9 GB | 38.6 dec/s c16, c1 p50 29 ms (H100) | PASS |
| GGUF Q8_0 | 0.9839 | 0.9988 | +0.0002 | +0.0035 | 4.8 GB | not measured in this release | PASS |
| GGUF Q5_K_M | 0.9569 | 0.9920 | -0.0039 | +0.0040 | 3.2 GB | not measured in this release | PASS |
| MLX 8-bit | 0.9792 | 0.9989 | +0.0000 | -0.0009 | 4.9 GB | 278 ms c1 p50, 698 ms for 5 questions (Mac) | PASS |
For every question basal returns a probability for each answer; the confidence is the probability of the answer it picks. The probabilities are calibrated (a temperature per question type and option-count bucket) on question families that were never used for training, so they hold on new tasks but are cautious. Held-out numbers below are leave-one-family-out on the development and test splits.
| basal-2.0-4.5B, held-out | |
|---|---|
| calibration error (ECE) | 0.015 |
| worst question family (ECE) | 0.101 (agent) |
| automated at the 5% error threshold | 33.2% at 2.39% observed error |
| 1% error threshold, shipped file | defers every decision (no 1% threshold certified on the full fit) |
On Werdykt (hidden, measured once, not used to choose the model) the calibration error is 0.060 (basal-1.5: 0.068). The calibration is a file, separate from the weights (CALIBRATION.json, one
option order; CALIBRATION.o2.json, two orders), so you can swap it or refit it on your own data with basal-calibrate.
Fitted with engine v2.0.0 (commit adc4c71).
One decision = one question, one option order. Hardware is named on every number.
--attn fa3.--deterministic; default minus --deterministic accuracy on dev v2: bf16 +0.0000, FP8 -0.0025 (run 1; 32 requests in flight); --deterministic itself: bf16 200/200 identical at 16 clients, FP8 200/200 identical at 16 clients.basal-1.5's other configuration. basal-1.5 also shipped FP8 checkpoints served by vLLM. On one RunPod H100 (median of 3 fresh servers; synthetic ~1.6k-token states and a 400-request mix, a different workload from the Werdykt speed pass) the two basal-1.5 configurations measured:
| basal-1.5 configuration | c1 p50 | decisions/s, 16 / 32 clients | mixed traffic, sequential / 32 clients (requests/s) | 16k-token state x 5 questions |
|---|---|---|---|---|
| bf16 on v1.5.0 | 59.0 ms | 17.8 / 18.7 | 2.1 / 2.2 | 20,441 ms |
| FP8 package on v1.5.0 vLLM | 59.1 ms | 32.0 / 36.5 | 11.3 / 46.0 | 620 ms |
basal-2.0 was not measured on that machine, so the speed-ups in the results table hold only against the basal-1.5 rows of the Werdykt speed pass. For mixed traffic with long, repeated documents, measure both on your own load before you switch.
model.safetensors, tokenizer, chat_template.jinja: the modelCALIBRATION.json (one option order) and CALIBRATION.o2.json (two orders): per-type temperatures and confidence thresholds for 1% / 5% errorthink/ (the think weights) and CALIBRATION.think.json: the experimental thinking modebasal.json: prompt format 2 and readout protocolThe model receives a fixed chat prompt with the state, the question and coded options; the decision is the softmax over the next-token logits of the option codes only (one forward pass, fp32 code logits, no text generation), then the per-type calibrated temperature from CALIBRATION.json. Confidence thresholds were chosen on calibration data, before testing, for 1% or 5% error among accepted decisions.
Fine-tuned from Bielik-4.5B-v3.0-Instruct for typed decisions in Polish and English, trained further from earlier basal checkpoints. LoRA stages, merged.
"facts": "auto")."max_error": 0.01
on an act or tool question defers every decision; use 0.05, or certify thresholds on your own data with basal-calibrate.act and multi over many labels are experimental. Up to 255 options per question (Ollama: 20); the context is
32,768 tokens. The 2.0 models ship no evidence head.@software{kinas2026basal20,
title = {basal-2.0: typed-decision models and inference engine for Polish and English},
author = {Kinas, Remigiusz},
year = {2026},
version = {2.0.0},
url = {https://github.com/rkinas/basal}
}
Apache-2.0. Fine-tuned from speakleash/Bielik-4.5B-v3.0-Instruct (Apache-2.0). Bielik is a project of SpeakLeash and ACK Cyfronet AGH.