Decision-2.0-Lux-9B · GGUF for llama.cpp hidden-state serving
This repository is a llama.cpp build of vllm-sr/Decision-2.0-Lux-9B,
the 9B decision model of Decision 2.0
(Qwen3.5 backbone with a candidate head). It holds everything needed to serve the Jev/SystemOne-compatible
/v1/systemone API (choice, noul, score questions, a probability for every option) on a single 8 GB GPU,
including GPUs without bf16 such as Turing.
The original package runs in Transformers with Triton kernels under bf16 autocast and stores its backbone in fp32 (32 GB). Here the backbone is a 4-bit GGUF; the head, the prompt encoding and the answer format are the original package code, unchanged.
Not for text generation. The model has no trained LM head.
output.weightin the GGUF is a Q2_K copy of the token embeddings, present only so llama.cpp does not place a second copy of the embeddings on the GPU. It is never used.
Files
| file | size | contents |
|---|---|---|
Decision-2.0-Lux-9B-Q4_K_M-dyn.gguf |
6.10 GB | backbone, text only (no MTP layers). Recipe below. 5.0 GB of it on the GPU; the 1.08 GB of Q8_0 token embeddings stay in host RAM |
decision_head.safetensors, decision_config.json, MODEL_MANIFEST.json |
17 MB | the candidate head and its metadata, unchanged from vllm-sr |
decision2/ |
the original Decision 2.0 runtime package, unchanged. The server imports prompt encoding, the head and the answer format from it | |
tokenizer.json, tokenizer_config.json |
tokenizer of the original model | |
serving/lux_api.py |
HTTP server for /v1/systemone (reference implementation) |
|
serving/dec2_bridge.cpp |
small C++ program on libllama that returns the hidden states the head needs | |
serving/convert_lux_gguf.py, serving/quant_layout.tt, serving/render_calibration.py |
how the GGUF was made | |
Decision-2.0-Lux-9B-imatrix.gguf |
5 MB | the importance matrix used for the quantization |
LICENSE |
Apache-2.0 | |
SHA256SUMS |
sums of the files above |
How it runs
Decision 2.0 asks every question as its own prompt:
Context:\n<state>\n\nTask type: …\nQuestion:\n…\nOptions: + one <option> block per option + Decision:.
The head reads the backbone's final hidden state (after the final norm) at the last token of every option and at
the last token of the prompt, and scores the options from those vectors.
request → decision2 encode() (original code: one prompt per question, option end positions)
→ dec2_bridge llama.cpp; the questions' common token prefix (the state) is decoded once,
its sequence state is saved and restored for every question, which then
decodes only its own suffix → hidden states at the needed positions only
→ CandidateHead (fp32) original head → product_answer() (original answer format)
Decoding the shared prefix once is exact, not an approximation: the questions start with the same tokens, so the
backbone computes the same states for them either way. The original package's share_context option is a different,
approximate mechanism; with this server a 5-question request over a 2.5k-token state costs little more than a
1-question request.
Build and run. Tested with llama.cpp
8212c78
(Qwen3.5 support, nextn output, load_mode):
g++ -O2 -std=c++17 -I llama.cpp/include -I llama.cpp/src -I llama.cpp/ggml/include serving/dec2_bridge.cpp \
-L llama.cpp/build/bin -lllama -lggml -lggml-base -o dec2_bridge
pip install torch numpy safetensors "transformers>=5"
MODEL_DIR=. GGUF=./Decision-2.0-Lux-9B-Q4_K_M-dyn.gguf BRIDGE=./dec2_bridge PORT=8012 python3 serving/lux_api.py
curl -s localhost:8012/v1/systemone -d '{"model": "lux",
"state": "The order arrived damaged yesterday. The customer has a receipt and asks for a replacement today.",
"questions": {
"route": {"type": "choice", "instructions": "Which team should handle this request?",
"criteria": {"returns": "Refunds, replacements and damaged deliveries",
"billing": "Payments, invoices and charges", "technical": "Product setup and faults"}},
"receipt": {"type": "noul", "instructions": "Does the customer have a receipt?"},
"urgency": {"type": "score", "instructions": "How urgent is this request?", "criteria": ["Routine", "Soon", "Today"]}}}'
Hidden states come from llama.cpp's nextn output (llama_set_embeddings_nextn), not from embeddings = true, which
at this commit also reserves n_vocab logits per token in pinned host RAM. Weights are read into device buffers
(load_mode = none), so the 6 GB file is not kept mapped. The head runs in fp32 (it is 4.5M parameters).
Quantization
- Source: the original fp32 backbone, converted to a bf16 GGUF with llama.cpp's
convert_hf_to_gguf.py(serving/convert_lux_gguf.py:--no-mtp, tensor names mapped tomodel.*, separateoutput.weight). The original runtime itself holds the backbone's Linear weights in bf16, so the bf16 GGUF loses nothing that the original GPU path keeps. - Base type:
Q4_K_M, with these per-tensor overrides (serving/quant_layout.tt, the layout of current llama.cpp for Qwen3.5):ssm_alpha,ssm_beta(gated delta net gates): F32, unquantized.ssm_outand the full-attentionattn_k/attn_v/attn_output: Q8_0 (ssm_outof the middle layers Q6_K).- Extra bits in the first and last layers (
ffn_up0–7,attn_gate,attn_qkv,ffn_down0/27/31). token_embd: Q8_0. llama.cpp keeps it in host RAM, so it costs no VRAM.output: Q2_K. Unused, see above.
- Importance matrix: 1.5M tokens at context 2048, every one rendered in Decision 2.0's own prompt format
(
serving/render_calibration.py): 2,701 prompts from 5,018 decision records with 1–3 questions of all three types in English and Czech. States come from the train splits of BoolQ, AG News and SST-2, multi-article AG News digests, Czech news (CIIRC-NLP/czech_news_simple-cs, hynky/czech_news_dataset_v2) and bartowski'scalibration_datav3. None of the evaluation items below are in it.
Measured quality
The port. The bf16 GGUF through this server against the original package (Transformers, exact path) on 186 questions of all three types, including Czech and states up to 4k tokens: 100 % the same decisions, mean |Δp| 0.0007, max |Δp| 0.015, KL 0.00001.
The quantization. Each GGUF ran through the full pipeline above and was compared with the same pipeline on the bf16 GGUF. Items: 900 questions, 300 each from BoolQ (validation), AG News (test) and SST-2 (validation), plus 24 long synthetic states (2k–11k tokens) with 5 questions each. KL is the mean KL(bf16 ‖ quant) per question; agreement is the share of questions where both pick the same option. Measured on an NVIDIA GB10:
| GGUF | weights on GPU¹ | BoolQ / AG / SST-2 | agreement | mean |Δp| | |Δp| on long states | KL |
|---|---|---|---|---|---|---|
| bf16 (reference) | 14.2 GB | 88.3 / 88.7 / 93.3 | — | — | — | — |
| Q8_0 | 7.7 GB | 88.3 / 88.7 / 93.0 | 99.8 % | 0.0018 | 0.0031 | 0.00007 |
| Q5_K_M, same recipe | 5.6 GB | 88.0 / 88.7 / 94.0 | 99.5 % | 0.0036 | 0.0053 | 0.00027 |
| Q4_K_M-dyn (this repo) | 5.0 GB | 87.7 / 88.7 / 93.7 | 99.4 % | 0.0051 | 0.0072 | 0.00053 |
| IQ4_XS, same recipe | 4.7 GB | 88.0 / 88.7 / 93.7 | 99.3 % | 0.0052 | 0.0070 | 0.00064 |
| Q4_K_M with our imatrix, default layout | 4.55 GB | 88.3 / 89.0 / 94.3 | 98.5 % | 0.0088 | 0.0128 | 0.00169 |
| plain Q4_K_M without imatrix | 4.55 GB | 88.3 / 88.0 / 94.3 | 98.3 % | 0.0128 | 0.0217 | 0.00320 |
¹ Every tensor except the token embeddings (host RAM), with a Q2_K output.weight.
The layout and the decision-format imatrix make this Q4_K_M 6× closer to bf16 by KL than a plain Q4_K_M for 0.5 GB more VRAM. On our own decision tasks (entity-link validation, news routing, proofreading-finding filtering, translation checks; English and Czech) the Q4 build scored within noise of the original package: the same or ±0.01 AUC.
Running on an 8 GB GPU
GPU memory at a 16k context, one sequence, n_ubatch 512 (llama.cpp's buffer sizes): weights 5.0 GB, attention KV 0.5 GB (only 8 of 32 layers
use attention), gated-delta-net state 0.05 GB, compute buffer 0.5 GB, plus the CUDA context and the head: about
6.6 GB. Host RAM: the 1.08 GB of token embeddings plus the Python process.
Request latency on an NVIDIA GB10 (median, the whole HTTP request):
| state | 1 question | 3 questions | 5 questions |
|---|---|---|---|
| ~600 tokens | 0.27 s | 0.41 s | 0.51 s |
| ~2,500 tokens | 1.17 s | 1.25 s | 1.36 s |
How it compares with the other decision models we serve
We run three decision models behind the same /v1/systemone API (Rune and Clef-Flash in production, Lux-9B in
testing) and publish all three:
Livesport/rune-26b-a4b-v3-GGUF,
Livesport/clef-flash-GGUF and
Livesport/Decision-2.0-Lux-9B-GGUF.
This section is the same in all three cards.
How we measured. 7,580 questions from the validation and test splits of 12 public datasets, English and Czech,
sampled with fixed seeds. Every item is one /v1/systemone request with one question; the state and the question text
are the same for all three models. Each model ran from the exact file published in its repository, served as its card
describes (Rune: llama-server with our adapter, request-scoped prefix reuse; Clef and Lux: the hidden-state bridge),
one request at a time on an NVIDIA GB10. Accuracy is the share of questions where the most probable option is the gold
label (for the star rating: the exact number of stars).
| task | n | Rune IQ3_S-dyn | Clef-Flash Q5_K_M-dyn | Lux-9B Q4_K_M-dyn |
|---|---|---|---|---|
| SST-2 validation · sentiment, yes/no | 872 | 93.5 | 94.5 | 93.6 |
| AG News test · topic, 4 options | 800 | 86.8 | 90.1 | 89.2 |
| BoolQ validation · question over a passage, yes/no | 800 | 81.8 | 89.8 | 89.4 |
| ANLI test R1–R3 · adversarial NLI, 3 options | 750 | 73.5 | 54.9 | 61.2 |
| MNLI validation-matched · NLI, 3 options | 500 | 83.0 | 84.8 | 90.8 |
| Emotion test · 6 emotions | 500 | 57.6 | 57.2 | 57.6 |
| CLINC150 (plus) test · intent: the right one + 9 random + out-of-scope | 600 | 92.8 | 99.7 | 97.3 |
| CTKFacts NLI test (cs) · fact check, 3 options | 558 | 72.9 | 75.1 | 67.6 |
| CsFEVER NLI test (cs) · fact check, 3 options | 500 | 72.2 | 71.2 | 69.8 |
| ČSFD movie reviews test (cs) · star rating 0–5, score | 500 | 53.0 | 55.4 | 57.0 |
| Czech Facebook comments test (cs) · sentiment, 3 options | 600 | 73.2 | 72.5 | 68.8 |
| Mall.cz product reviews test (cs) · sentiment, 3 options | 600 | 56.7 | 50.5 | 57.7 |
| all English | 4822 | 82.4 | 82.6 | 83.6 |
| all Czech | 2758 | 65.7 | 64.9 | 64.2 |
| all 12 tasks | 7580 | 76.3 | 76.2 | 76.5 |
| calibration: ECE of the chosen option, all tasks (lower is better) | 7580 | 0.052 | 0.093 | 0.079 |
Reading the table.
- Overall the three are within half a point of each other; they differ by task.
- Rune is best on adversarial reasoning (ANLI, by 12–19 points), on Czech fact checking (CsFEVER) and Czech Facebook sentiment, and it is the best calibrated: its probabilities match observed accuracy most closely.
- Clef-Flash is best on English topic, yes/no-over-a-passage and intent tasks, and on CTKFacts.
- Lux-9B is best on MNLI, the Czech star rating and Czech product reviews, and is close to Clef on the English classification tasks.
- We do not know the training data of the three models in full. Decision models are commonly trained on public NLI, QA and intent data, so results on MNLI, BoolQ or CLINC may be partly in-distribution for some of them.
- On our own (unpublished) English and Czech decision tasks the picture was the same: Rune leads on Czech faithfulness and fact checks, Clef and Lux on English routing and matching.
Speed. Median latency of one request on an NVIDIA GB10, one request at a time:
| request | Rune IQ3_S-dyn | Clef-Flash Q5_K_M-dyn | Lux-9B Q4_K_M-dyn |
|---|---|---|---|
| ~300 tokens, 1 question | 0.17 s | 0.22 s | 0.17 s |
| ~300 tokens, 5 questions | 0.40 s | 0.37 s | 0.38 s |
| ~1k tokens, 1 question | 0.47 s | 0.56 s | 0.49 s |
| ~2.5k tokens, 1 question | 1.03 s | 1.39 s | 1.15 s |
| ~2.5k tokens, 5 questions | 1.33 s | 1.44 s | 1.42 s |
| ~10k tokens, 1 question | 4.84 s | 5.25 s | 4.66 s |
| ~10k tokens, 10 questions | 5.77 s | 5.72 s | 5.37 s |
| throughput, ~600-token requests with 1 question, 1–16 clients | 4.3 req/s | 3.1 req/s | 3.9 req/s |
Every server here runs one request at a time, so more parallel clients add queueing, not throughput.
On a Quadro RTX 4000 8 GB (Turing), measured in production:
- Rune IQ3_S-dyn on 2 GPUs: 0.49 s for ~730 tokens, 1.05 s for ~2.3k tokens, 4.12 s for ~8.9k tokens (1 question; 5 questions add 0.3–0.6 s).
- Clef-Flash Q5_K_M-dyn on 1 GPU: 0.57 s for ~600 tokens, 1.75 s for ~2k tokens, 6.58 s for ~7.4k tokens.
- Lux-9B Q4_K_M-dyn: not deployed on Turing yet. It has the same Qwen3.5-9B-class backbone and serving stack as Clef, so expect similar speed.
- The 9B dense backbones are about 1.6× slower than Rune's mixture of experts on Turing, but need one 8 GB GPU instead of two.
License and credits
- Decision-2.0-Lux-9B by the vLLM Semantic Router team,
Apache-2.0, built on Qwen3.5. The head, the
decision2package, the tokenizer and the metadata are their files, unchanged. - GGUF, quantization and serving code: Livesport, Apache-2.0, provided as is.
- Thanks: bartowski for calibration data.