KnowLine-4B-Gen5.5 β ONNX
ONNX builds of /home/anon/LLM/KnowLine-4B-Gen5.5
(PelaAI KnowLine-4B-Gen5.5 = Qwen3.5-4B fine-tune, Qwen3_5ForConditionalGeneration),
runnable with the latest onnxruntime. No torch needed at inference.
This is a decision model, not a chatbot: you send a state plus typed
questions (yes/no, pick-one, scored rubric) and get back a probability
distribution per question β one prefill each, no text generation. The main
script is score_knowline.py; generate.py (free text) is secondary.
Decision scoring
pip install -r requirements.txt # GPU line, or swap to the CPU line
python score_knowline.py --request examples/support.json --model ./model-cuda-int4 --cuda
A request is a /v1/systemone body: {"state": ..., "questions": {...}}.
Three question types:
noulβ yes/no (Should the agent offer a refund?β{"noul": 0.40})choiceβ pick one of k (priorityβurgentat 0.88)scoreβ rubric levels 0..k-1, returns expectation + full distribution
Example 1: support triage (examples/support.json)
State: customer whose laptop shipping stalled, promised callback never came, needs it for work Monday. Four questions, one request:
python score_knowline.py --request examples/support.json --model ./model-cuda-int4 --cuda
{
"answers": {
"refund": {"type": "noul", "noul": 0.5312},
"priority": {"type": "choice", "choice": "urgent",
"probabilities": {"low": 0.052, "normal": 0.105, "urgent": 0.842}},
"tone": {"type": "choice", "choice": "angry",
"probabilities": {"angry": 0.948, "neutral": 0.050, "happy": 0.002}},
"satisfaction": {"type": "score", "score": 0.401,
"probabilities": {"0": 0.680, "1": 0.254, "2": 0.053, "3": 0.010, "4": 0.003},
"legend": {"0": "very dissatisfied", "1": "dissatisfied", "2": "neutral",
"3": "satisfied", "4": "very satisfied"}}
},
"usage": {"input_tokens": 1196, "output_tokens": 4}
}
Read: refund is a coin flip leaning yes (0.53 β escalate rather than deciding either way), ticket is urgent, customer is angry, expected satisfaction if handled normally is 0.40/4.
Example 2: content moderation (examples/moderation.json)
State: 2-day-old account posting a crypto "secret system" fee scam. Three questions:
python score_knowline.py --request examples/moderation.json --model ./model-cuda-int4 --cuda
{
"answers": {
"violates": {"type": "noul", "noul": 0.9987},
"category": {"type": "choice", "choice": "scam",
"probabilities": {"legit": 0.0004, "spam": 0.006, "scam": 0.993, "abuse": 0.001}},
"severity": {"type": "score", "score": 3.62,
"probabilities": {"0": 0.004, "1": 0.006, "2": 0.039, "3": 0.261, "4": 0.689}}
},
"usage": {"input_tokens": 1028, "output_tokens": 3}
}
Read: violates policy (0.999), it's a scam not mere spam (0.993), severity 3.62/4 β auto-remove territory. High confidence on category (0.97), middling on exact severity level (0.51): act on the category, have a human confirm severe-vs-critical.
Notes for decision use
- Prompts use the Gen5.5 serving layout (chat template, read-twice on by
default,
--no-read-twicefor Gen5 prompts) and the label-token softmax, so outputs are comparable with the PyTorch/SGLang serving. No calibration file needed: T = 1.50 is folded into the weights. scorereturns the full distribution, not just the mean β use the spread (e.g. severity 3-vs-4 mass) to route to humans, not just the expectation.- Up to 64 questions per request, 255 options per question. Text only
(same limit as
--backend hf; images need SGLang in the source repo). - If your use depends on probability thresholds, re-check calibration on your own data (see the model card); INT4 shifts probabilities slightly β measured drift below.
Chat generation (secondary)
python generate.py --model ./model-cuda-int4 --cuda --prompt "What is the capital of France?"
python generate.py --model ./model-cpu --interactive
Contents
| path | what |
|---|---|
model-cuda-int8/ |
CUDA int8 build (most faithful): model.onnx + model.onnx.data (~5.0 GB). Full int8 weights, chunks of 32, running math untouched |
model-cuda-int4/ |
CUDA int4 build: model.onnx + model.onnx.data (~2.7 GB). True int4, k-quant, block 32, only the LM head at int8 |
model-cpu-int8/ |
CPU int8 build: model.onnx + model.onnx.data (~6.9 GB). Same recipe as the CUDA int8, with 8-bit running math |
model-cpu/ |
CPU int4 build: model.onnx + model.onnx.data (~2.6 GB) |
examples/ |
ready-made decision requests (support.json, moderation.json) |
onnx_raw.py |
shared runner: OnnxLM (prefill / step / generate) |
generate.py |
chat / text generation |
score_knowline.py |
decision scoring (this README's main script) |
verify_parity.py |
torch-bf16 vs ONNX scoring comparison (needs torch) |
compare_all.py |
accuracy + speed check of every build vs the original (needs torch) |
knowline_server.py, knowline_layout.py |
copies from the source repo (MIT) used for rendering |
orig_tokenizer/ |
pristine tokenizer β scoring prompts stay byte-identical to PyTorch serving |
fix_tokenizer.py |
tokenizer fix for the GenAI C++ tokenizer (applied to all four builds) |
requirements.txt |
runtime deps (pinned latest versions) |
export*.log, parity*.log, fp16_cpu_session_check.log |
build + verification logs |
config.json (in each build) |
source architecture config (Hub convention) |
.gitattributes |
LFS tracking for *.onnx* (Hub convention) |
.gitignore |
ignores __pycache__/ etc. |
LICENSE |
MIT for the runner scripts (weights Apache-2.0, serving files MIT β see below) |
How it was built
Plain torch.onnx.export / optimum cannot handle this model: 24 of the 32
decoder layers are GatedDeltaNet linear-attention (recurrent + conv state),
which has no standard ONNX representation. The supported path is Microsoft's
onnxruntime-genai ModelBuilder, which supports Qwen3.5 (see the official
microsoft/olive-recipes example Qwen-Qwen3.5-4B/builtin).
Export (onnxruntime-genai-cuda 0.19.0, onnxruntime-gpu 1.31.1,
transformers 5.22.1, torch 2.13; logs in export*.log):
# CUDA int8 (most faithful): full int8 weights, chunks of 32, running math
# untouched, smarter packing
python -m onnxruntime_genai.models.builder \
-i /home/anon/LLM/KnowLine-4B-Gen5.5 -o model-cuda-int8 -p int8 -e cuda \
--extra_options block_size=32 algo_config=k_quant
# CUDA int4: true int4, k-quant, block 32, only the LM head at int8
python -m onnxruntime_genai.models.builder \
-i /home/anon/LLM/KnowLine-4B-Gen5.5 -o model-cuda-int4 -p int4 -e cuda \
--extra_options block_size=32 algo_config=k_quant \
matmul_mixed_precision=last_matmul:int8
# CPU int4, for machines without CUDA
python -m onnxruntime_genai.models.builder \
-i /home/anon/LLM/KnowLine-4B-Gen5.5 -o model-cpu -p int4 -e cpu \
--extra_options accuracy_level=4 block_size=32
# CPU int8 (most faithful on CPU): int8 weights + smarter packing
python -m onnxruntime_genai.models.builder \
-i /home/anon/LLM/KnowLine-4B-Gen5.5 -o model-cpu-int8 -p int8 -e cpu \
--extra_options accuracy_level=4 block_size=32 algo_config=k_quant
python fix_tokenizer.py model-cuda-int8 model-cuda-int4 model-cpu-int8 model-cpu # C++ std::regex compat, per Olive recipe
All builds are text-only decoders (qwen3_5_text); the vision tower is not
exported.
Running it: raw onnxruntime (not onnxruntime-genai)
The runners drive the graph with plain onnxruntime (onnx_raw.py:
prefill + greedy decode with manual KV/conv/recurrent state). Reason:
onnxruntime-genai 0.19.0 fails at Model load for this export on CPU with
GetMemoryInfo for CPU should not be used (its Qwen3.5 recurrent-state setup),
while onnxruntime 1.31.1 runs the identical graph fine. genai_config.json
is kept as exported, so the GenAI path can be retried with future releases.
Verification
Measured on this machine (RTX 3090 Ti graphics card + processor) with
compare_all.py: the original (full precision, graphics card) against each
build on the same 4 requests / 10 questions. Times are per request, after
warmup.
| model | file size | support request | moderation request | biggest miss vs original | same answers |
|---|---|---|---|---|---|
| original (full precision) | ~8.8 GB | 0.36 s | 0.29 s | β | β |
model-cuda-int8 |
5.0 GB | 0.61 s | 0.50 s | 0.019 | 10 of 10 |
model-cuda-int4 |
2.7 GB | 0.60 s | 0.49 s | 0.093 | 9 of 10 |
model-cpu-int8 |
6.9 GB | 5.8 s | 4.9 s | 0.023 | 10 of 10 |
model-cpu |
2.6 GB | 6.5 s | 5.4 s | 0.172 | 10 of 10 |
- The 8-bit builds are the faithful ones: worst miss 0.019 on graphics, 0.023 on processor. The coin-flip refund lands 0.43 on both vs 0.44 original, same answer (no) β the processor 8-bit fixes the old 4-bit processor's 0.27 wobble on that question.
- The 4-bit graphics build's one weak spot: the support refund question reads 0.53 vs 0.44 original β a coin-flip question landing across 0.5. Treat near-0.5 scores as "escalate", not as decisions.
- The 4-bit processor build gets every answer right but its numbers are the softest (worst miss 0.17); prefer the 8-bit processor build wherever thresholds matter.
- "Capital of France?" β
Parison the graphics-card builds (first word in ~0.2 s on the 8-bit, peak 4.4 GB graphics memory). - Weight files checked present and size-consistent in all four builds (8-bit weights, 5.0 GB; 710 4-bit graphics weights, 2.7 GB; 8-bit processor weights, 6.9 GB; 557 4-bit processor weights, 2.6 GB).
Notes / limits
- Provider matrix is strict β each build runs only on its target EP:
model-cuda-int8andmodel-cuda-int4on CUDA (--cuda),model-cpuon CPU. The CUDA exports use theGatedDeltaNetoperator (no CPU kernel exists); the CPU export usesLinearAttentioninstead. Cross combinations fail at load with an explicit message. Verified: all four builds live-run (CUDA on a 3090 Ti, CPU on CPU). - MTP draft heads (
mtp.onnx*) were deleted from all four builds: nothing here uses speculative decoding β the runners load onlymodel.onnx. Re-running the export commands above regenerates them. - The small builds shift probabilities slightly vs full precision. Measured worst miss: 0.019 (graphics 8-bit), 0.093 (graphics 4-bit), 0.023 (processor 8-bit), 0.172 (processor 4-bit).
- On
accuracy_level: 0 = activations untouched (most faithful, the CUDA default β used for the CUDA int4 build); 4 = activations quantized to int8 (the CPU default). Higher number does not mean more accurate. - onnxruntime-genai's C++ tokenizer uses the fixed
tokenizer.jsonin each build dir; scoring tokenizes withorig_tokenizer/instead, so prompts are unaffected by that fix.
License
- Weights (
*.onnx.data): Apache-2.0, same as the base model (Qwen3.5-4B). knowline_server.py/knowline_layout.py: MIT, PelaAI (byte-identical copies of the source repo files).- Runner scripts (
onnx_raw.py,generate.py,score_knowline.py,verify_parity.py,fix_tokenizer.py): MIT β seeLICENSE.