electroglyph's picture
Upload folder using huggingface_hub
a05d4d2 verified
|
Raw History Blame Contribute Delete
11.5 kB
---
license: apache-2.0
base_model: PelaAI/KnowLine-4B-Gen5.5
library_name: onnxruntime
pipeline_tag: text-classification
tags:
- onnx
- decision-model
- qwen3.5
---
# 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
```bash
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` β†’ `urgent` at 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:
```bash
python score_knowline.py --request examples/support.json --model ./model-cuda-int4 --cuda
```
```json
{
"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:
```bash
python score_knowline.py --request examples/moderation.json --model ./model-cuda-int4 --cuda
```
```json
{
"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-twice` for 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.
- `score` returns 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)
```bash
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`):
```bash
# 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?" β†’ `Paris` on 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-int8` and `model-cuda-int4` on CUDA (`--cuda`), `model-cpu` on CPU.
The CUDA exports use the `GatedDeltaNet` operator (no CPU kernel exists);
the CPU export uses `LinearAttention` instead. 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 only `model.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.json` in each
build dir; scoring tokenizes with `orig_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 β€” see `LICENSE`.