Instructions to use lumierenoir/klev-0.8b with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use lumierenoir/klev-0.8b with PEFT:
Task type is invalid.
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- Unsloth Desktop
File size: 22,043 Bytes
bd07490 5901eb4 bd07490 5901eb4 bd07490 5901eb4 bd07490 5901eb4 bd07490 5901eb4 bd07490 5901eb4 bd07490 5901eb4 bd07490 5901eb4 bd07490 5901eb4 bd07490 5901eb4 bd07490 5901eb4 bd07490 5901eb4 bd07490 5901eb4 bd07490 ff3fb1b 504b4a9 ff3fb1b bd07490 d0bef41 bd07490 d0bef41 504b4a9 d0bef41 bd07490 5901eb4 d0bef41 5375c83 d0bef41 5375c83 d0bef41 5375c83 d0bef41 5901eb4 bd07490 5901eb4 bd07490 5901eb4 bd07490 5901eb4 bd07490 7835600 42ba04f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 | ---
base_model: Qwen/Qwen3.5-0.8B
library_name: peft
license: apache-2.0
language:
- en
tags:
- decision-making
- calibrated-probabilities
- qlora
- unsloth
- qwen
- pointer-head
---
# klev-0.8b
**klev** (KV + Jev) is a 0.8B **decision model**: given a typed question β a single choice, a
noul (true/false) judgement, a score, or a multi-choice item β it returns *calibrated
probabilities over the options plus an explicit rejection channel*. It is built on
`Qwen/Qwen3.5-0.8B`, fine-tuned with QLoRA, and read out through a small pointer head instead
of text generation. It runs 4-bit in **0.89 GB of weights, 1.39 GB peak** β a full 2-epoch
decision-v7 run finished in **192 min on an 8 GB AMD Radeon RX 6600M (gfx1032, ROCm 7)**, a card
the 4B arm cannot fit at all.
It was **built using [Unsloth](https://github.com/unslothai/unsloth)**: the 4-bit QLoRA
fine-tune, the delimiter embedding deltas, the pointer head and the teacher-KL cache all run
through Unsloth's patched Qwen 3.5 stack (`FastModel`). See "Built with Unsloth" below.
The name comes from KV + Jev: a Kev-lineage pointer readout serving Jev's typed decision
protocol. Working name during development was **M5**.
## Built on Kev
**klev-0.8b is based on [Kev](https://github.com/jaredpalmer/kev) and reuses part of its
code.** The record format, the pointer readout, the metrics and the decision-v7 data are
Kev's (Apache-2.0, Jared Palmer), vendored rather than reimplemented so that klev stays
byte-compatible with the Kev family β which is what makes the kev-0.8B column in Results a
like-for-like baseline:
| from Kev | reused here |
|---|---|
| `kev/model.py` (`PointerHead`) | `model/head.py` β the `q`/`k` readout below, plus klev's own garbage candidate |
| `kev/api.py`, `kev/data.py` | `data/format.py` β TypeSafe record format, `render()`, record builder |
| `kev/metrics.py` | `data/metrics.py` β the scorer used for every number in Results (verbatim) |
| `kev/suite.py` + `jaredpalmer/kev-suites` | `data/suites.py` β frozen, sha256-checked suites |
| `kev.calibrate` | `scripts/calibrate.py` β the fitted pointer temperature in `head.pt` |
| decision-v7 | the 15,576 training rows (the same partitions Kev-0.8B/4B/9B trained on) |
| `<\|fim_*\|>` reserved tokens | klev's delimiter set (see Architecture) β Qwen has no `<unusedN>` slots, so these are the `<\|fim_prefix\|>`-style slots Kev reuses |
Kev is Apache-2.0 and every vendored file carries a header naming its origin. **No Kev model
weights are used**: the base is Qwen3.5-0.8B, the head and LoRA are trained fresh. What is
klev's own is the base choice, the Unsloth QLoRA training stack, the KL anchor, the delimiter
embedding deltas, the rejection channel and the stitch below.
## Built with Unsloth
klev was **built using [Unsloth](https://github.com/unslothai/unsloth)** (2026.9.11 with
`transformers` 5.6.2 β Gemma 4 and Qwen 3.5 need `transformers` β₯5.5.0 for the architecture, and
5.6.2 is the revision that checkpoint was saved with).
| stage | Unsloth usage |
|---|---|
| base load | `unsloth.FastModel.from_pretrained(..., load_in_4bit=True)` |
| fine-tune | `FastModel.get_peft_model` for the rank-16 LoRA + `use_gradient_checkpointing="unsloth"`, driven by a `transformers.Trainer` subclass (`DistillTrainer`) |
| trainable tokens | delimiter rows via peft `trainable_token_indices` (5 rows, no vocabulary resize) |
| teacher KL | cached base top-k logits, read through Unsloth's patched hidden-state/logits access |
| inference / evals | `FastModel` for every decision and stitch eval |
Reproducing the run needs `unsloth` imported **before** `transformers` / `peft` / `trl`,
because it patches them at import time:
```bash
pip install unsloth # 2026.9.11
python -c "import unsloth, transformers, peft, trl" # unsloth first
```
`transformers` must be 5.5.0+ for the `qwen3_5` architecture, and `--no-deps` is required when
installing it alongside unsloth so the CUDA `xformers` dependency does not replace a ROCm torch.
## Files
| file | what it is |
|---|---|
| `adapter/` | rank-16 LoRA (alpha 32) + tokenizer with the 5 delimiter special tokens |
| `head.pt` | pointer head weights **and** the trainable delimiter-embedding deltas |
| `train_config.json` | training hyperparameters (decision-v7, 2 epochs, KL anchor 0.3) |
| `stitch/tweet-eval-lora/` | external LoRA (tweet_eval hate detection) trained on the same base, alpaca format |
| `stitch/rumoureval-lora/` | external LoRA (RumourEval 2019 responding-relationship), same base and format |
| `stitch/probe-tweet-eval.pt` | 32-shot nearest-class-mean probe in that LoRA's prompt space, + `T`, `beta` |
| `stitch/probe-rumoureval.pt` | same, for the RumourEval LoRA (4 classes) |
| `stitch/report-*.json` | full stitch reports incl. steering and fusion grids |
| **`base/`** | **the same recipe on `Qwen/Qwen3.5-0.8B-Base`**, the checkpoint kev-0.8B uses β adapter, head, config, the shared external LoRA and its fitted probe. See `base/README.md`. |
This is **not a chat model**: decisions are read from the pointer head over the
option/decision tokens. Reuse `model/delimiters.py`, `model/head.py` and
`eval/eval_decisions.py` from the code repo to load and score it.
## Architecture
- Base: Qwen3.5-0.8B (dense, 1024 hidden, 24 layers, vocab 248,320, tied embeddings,
**no PLE embedding table**), 4-bit NF4 QLoRA, loaded and trained with Unsloth (`FastModel`).
- Five Qwen reserved tokens mark state / option / decision positions:
`<|fim_prefix|>` (248060), `<|fim_suffix|>` (248062), `<|fim_middle|>` (248061),
`<|fim_pad|>` (248063), `<|repo_name|>` (248064). Their embedding rows are trained as
deltas, so there is **no vocabulary resize**. Qwen has no `<bos>`; the encoder asks the
tokenizer for its own `bos_token_id` and adjusts the state-length arithmetic accordingly.
- Pointer head (vendored from Kev, see "Built on Kev"): `q`/`k` dot product over option
boundary tokens, softmax over the K options plus a learned **garbage** candidate for
rejection.
- No `final_logit_softcapping` on this base (Gemma 4 has 30.0), so the KL's softcap branch is
skipped rather than approximated.
- Trainable: 6.9M params (6.39M LoRA + 525.1K head + 5.1K deltas).
## Training
- decision-v7: 15,576 decision rows, 2 epochs, **3,894 steps**, batch 1 Γ accum 8, 192 min on one
RX 6600M 8 GB.
- Objective: pointer CE over K+1 + **KL anchor 0.3 to the frozen base** on decision states
(top-k 32, T 1.0). Loss 3.70 β 0.89; the pointer term collapses to ~0 by step 645 while the
KL term holds at ~2.1β2.3, i.e. the base alignment is what moves.
- The teacher cache is 1,413,968 content positions (~30 min to build on this card).
### Two deviations from the E4B run, both forced, neither a choice
- **`lr_scheduler_type` is `linear`, not `cosine`.** On `transformers` 5.6.2 the Trainer fast
loop pins the learning rate at exactly 0 for every step when the scheduler is `cosine` and
`warmup_steps > 0` (verified: 6 steps at warmup 50 all report `learning_rate: 0`, where
1.2e-05 is correct). With `cosine` this recipe trains silently and does nothing.
- **Precision is fp16, not bf16.** The card this was trained on is gfx1032, where Unsloth
declines bf16 and the Triton bf16 path is unavailable. See `scripts/rocm_env.sh` in the code
repo for the full set of ROCm requirements, including a **hard sequence-length ceiling of
~195 tokens** past which Qwen 3.5's gated-deltanet attention autotunes into a bf16 kernel
gfx1030 cannot compile.
## Results
Own decision suite, klev-0.8b against kev-0.8B on **identical rows**, each model served with a
temperature fitted on decision-v7 calibration (1,148 rows) via `scripts/calibrate.py`.
decision-v7 development (n = 1,468, in-distribution):
| | klev-0.8b | kev-0.8B |
|---|---|---|
| accuracy | **0.8120** | **0.8120** |
| Brier | **0.2590** | 0.2685 |
| ECE | 0.0365 | **0.0274** |
| NLL | **0.4785** | 0.5102 |
| coverage @ 5 % error | **0.6158** | 0.4360 |
| AURC | **0.0592** | 0.0717 |
| mean `p_none` (rejection) | 0.016 | β |
transfer-v4 development (n = 764, six never-trained sources):
| | klev-0.8b | kev-0.8B |
|---|---|---|
| accuracy | **0.6636** | 0.6126 |
| Brier | **0.4372** | 0.4542 |
| ECE | **0.0207** | 0.0735 |
| NLL | 0.7538 | 0.7524 |
| AURC | **0.1719** | 0.1876 |
By question type (accuracy):
| suite | type | klev-0.8b | kev-0.8B |
|---|---|---|---|
| decision-v7 | choice / noul / score | 0.851 / 0.867 / 0.583 | 0.876 / 0.845 / 0.546 |
| transfer-v4 | choice / noul / score | 0.651 / 0.736 / 0.300 | 0.599 / 0.671 / 0.350 |
**Two bases are published here.** The top level is `Qwen/Qwen3.5-0.8B` (IT); **`base/` is the
identical recipe on `Qwen/Qwen3.5-0.8B-Base`**, which is what kev-0.8B uses. The base matters
more than it looks: it sets how strong an external task LoRA can be (tweet_eval probe 0.715 on
IT vs 0.660 on `-Base`), and the stitch can only import what the adapter holds, so an
IT-vs-`-Base` stitch comparison measures the adapter as much as the decision model. On the
shared `-Base` adapter the stitch gain is **+7.5 pts for klev against +3.0 for kev**, so about
half the original +8.0 gap was the adapter's base and half is the recipe. See `base/README.md`
and `docs/m13` in the code repo.
klev-0.8b **ties kev-0.8B in distribution and beats it by 5.1 points out of it**, with a
narrower LoRA (192 tensors / 6.4M against kev's `lora_targets="all"` at 372 / 11.3M) and less
data (15,576 rows against kev's 22,539, which included 16,539 from a separate joint mixture).
Two caveats worth stating plainly. First, kev-0.8B's own model card reports 0.827 / 0.648, but
on **smaller row sets** (1,264 / 656 questions); scored on these rows it measures 0.812 / 0.613,
so its card is ~1.5 and ~3.5 points optimistic against this eval set β do not compare across row
sets. Second, this is not a pure base-model ablation: the two arms use different base
checkpoints (`Qwen3.5-0.8B` vs `Qwen3.5-0.8B-Base`), which matters for the stitch numbers below.
## Stitch: importing an external LoRA with 32 examples
Because of the KL anchor, a LoRA trained *outside* this fine-tune composes with klev without
wrecking its decisions. Its knowledge is linearly readable from the hidden state of **its own
prompt format**, so fusing that readout into the pointer logits with two scalars imports it:
```
logits_final = logits_ptr(h_sys) + beta * log p_alp
p_alp = softmax(-d^2 / T) # NCM in the external LoRA's prompt space
```
tweet_eval hate detection (200 test rows, 32 balanced shots, 2 classes, ext weight 1.0):
| | klev-0.8b | kev-0.8B |
|---|---|---|
| klev/kev alone (pointer) | 0.605 | 0.645 |
| 32-shot probe, LoRA prompt space | **0.715** | 0.660 |
| 32-shot probe, decide state | 0.565 | 0.705 |
| **stitched** | **0.715** | 0.675 |
| external LoRA alone (generation) | 0.730 | β |
The fused klev-0.8b **equals** the external LoRA's own probe, recovering 100 % of the available
margin from 32 labelled rows and two scalars, while kev-0.8B gains 3.0 points. That gap is
confounded, though: the two arms use different base checkpoints, and the base determines how
good the external adapter can be (probe 0.715 on the IT base vs 0.660 on `-Base`, and 0.490 vs
0.430 on RumourEval), which caps what the stitch can import. The honest claim is that the
stitch works in both, not that klev is the better stitch target.
kev-0.8B's `probe, decide state` of 0.705 is the more interesting single number: a linear probe
on its **own** decide state already recovers the hate-speech signal with no external knowledge
at all, so there was little left to import. The stitch's value appears to depend on the
decision model not already having the capability.
RumourEval 2019 responding-relationship (351 test rows, 32 shots, 4 classes):
| | klev-0.8b |
|---|---|
| pointer alone | 0.362 |
| 32-shot probe, LoRA prompt space | **0.490** |
| 32-shot probe, decide state | 0.316 |
| **stitched** | **0.479** (+11.7 pts) |
**Steering the state does not work; conditioning the readout does** β reproducing that finding
independently. Adding the LoRA's class directions to the decide state scores 0.322 against a
0.605 baseline on TweetEval, and the single global prompt direction 0.530; the gated fusion
recovers everything. The knowledge is a separate linearly-readable signal in the LoRA's space,
so it has to enter as a score, not as a perturbation.
Probe hyperparameters (in `stitch/probe-*.pt`): TweetEval `T = 105.19`, `beta = 16.0`,
`ext_weight = 1.0`; RumourEval `T = 1305.8`, `beta = 16.0`, `ext_weight = 1.0`.
## Install
```bash
# klev is not on PyPI: install from the repository
pip install "klev @ git+https://github.com/felipepenhorate/klev.git"
```
`klev` is the library: it carries the loader, the pointer readout, the record format and both
usage paths, so a consumer does not clone this repo to use a checkpoint. Two version floors
matter and pip does not know they are related to the architecture:
| | why |
|---|---|
| `transformers >= 5.5.0` | the `qwen3_5` and `gemma4` architectures do not exist before it. On 5.3.0 `AutoConfig` raises "Transformers does not recognize this architecture", which unsloth then **misreports** as a generic "not supported yet" ValueError β it reads like a missing model, not a version. |
| `unsloth >= 2026.9.11` | first release carrying gemma4 support. |
Install unsloth with `--no-deps` if you are on ROCm, so its CUDA `xformers` dependency does not
replace your ROCm torch build.
On AMD/ROCm also `source scripts/rocm_env.sh` from the repo (shipped in the wheel as
`klev.scripts`) **before** importing β it sets the arch override, `TORCH_ROCM_AOTRITON_ENABLE_BF16=0`,
`UNSLOTH_COMPILE_DISABLE=1` and the rest. All of it is load-bearing; see
`docs/m11-qwen35-08b.md`.
Two console scripts come with it:
```bash
klev-system-one --ckpt lumierenoir/klev-0.8b --request my_request.json # no server
klev-serve --ckpt lumierenoir/klev-0.8b --port 8090 # optional HTTP
```
`load_klev()` reads the base model from the checkpoint's own `adapter_config.json` and picks the
matching delimiters, so you do not have to pass `base=` or `preset=`. Pairing a checkpoint with
another model's weights would otherwise be silent nonsense rather than an error.
## Usage
klev is a **library first**. A decision model is small and stateless, so the normal way to use
one is in-process: load it once, call it as many times as you like. No server, no port, no
daemon. `import unsloth` must come **before** `transformers` / `peft` (it patches them at import
time), which is why it is first.
```python
import unsloth # noqa: F401 β must precede transformers / peft
from klev import load_klev, answer # pip install "klev @ git+https://github.com/felipepenhorate/klev.git"
model, tokenizer, head = load_klev("lumierenoir/klev-0.8b") # or a local dir with adapter/ + head.pt
result = answer(model, tokenizer, head, {
"state": "My card was declined twice at a supermarket and the ATM refused it too.",
"questions": {
"is_card": {"type": "noul", "instructions": "Is the card being declined?",
"criteria": {"false": "no", "true": "yes"}},
"action": {"type": "choice", "instructions": "What should the agent do first?",
"criteria": {"usage": "ask how the card was used",
"atm": "raise an ATM fault",
"fraud": "run a fraud check"}},
"urgency": {"type": "score", "instructions": "How urgent is this?",
"criteria": ["no action needed", "next working day", "same day", "immediate"]},
}},
temperature=2.2974)
```
Verified output from that request:
```json
{"answers": {
"is_card": {"type": "noul", "noul": 0.9169, "none": 0.01314},
"action": {"type": "choice", "choice": "atm", "confidence": 0.4848,
"probabilities": {"usage": 0.1311, "atm": 0.6136, "fraud": 0.0870},
"none": 0.16828},
"urgency": {"type": "score", "score": 1.485, "legend": {"0": "no action needed",
"1": "next working day", "2": "same day", "3": "immediate"},
"probabilities": {"0": 0.2936, "1": 0.2397, "2": 0.1817, "3": 0.2580, "4": 0.0270},
"confidence": 0.6288, "none": 0.026994}}}
```
`none` is the **rejection channel**: the mass on "none of these apply", renormalised off the K
options so the returned distribution still sums to 1.
The three question types are `noul` (`criteria` = `{false, true}`), `choice` (`criteria` = an
option map) and `score` (`criteria` = an ordered level list). A request is validated by
klev's own `SystemOneRequest` before any GPU work, and labels / `target` / `_meta` are ignored β
so a labelled eval record can be passed straight through.
**Temperature matters.** `answer(..., temperature=1.0)` is the **raw** readout, which is what the
benchmark tables above score (ECE 0.113 on decision-v7 dev). The *served* numbers use the
per-model temperature from `scripts/calibrate.py`:
| arm | serve at |
|---|---|
| `lumierenoir/klev-0.8b` (IT) | **2.2974** |
| `lumierenoir/klev-0.8b` + `/base` | **2.2449** |
`head.pt` stores `1.0`, because it is written during training and the head only tempers in eval
mode β so a deployment has to supply the fitted value itself. That is the one thing easy to get
wrong, and it is worth 0.08 ECE.
### From a shell, still no server
```bash
klev-system-one --ckpt lumierenoir/klev-0.8b # built-in demo
klev-system-one --ckpt lumierenoir/klev-0.8b --request r.json # your request
echo '{"state":"...","questions":{...}}' | klev-system-one --ckpt ... --request -
```
which ends with a plain-language readback β the quickest way to sanity-check a checkpoint. The
built-in demo request (a card decline, with 2 options and a 4-level severity scale):
```
channel_issue noul p(true)=0.885 -> yes reject=0.015
next_step choice -> atm_fault conf=0.371 reject=0.193
severity score -> 1.52 reject=0.025
```
### If you would rather have HTTP
Worth it when several processes share one warm copy of the weights, or when the caller is not
Python. Otherwise it adds a port and a failure mode to save ~1 GB of load time.
```bash
klev-serve --ckpt lumierenoir/klev-0.8b --port 8090 --temperature 2.2974
curl -s localhost:8090/v1/systemone -H 'content-type: application/json' -d @request.json
```
| route | |
|---|---|
| `POST /v1/systemone` | `{"state", "questions"}` β `{"answers": {qid: typed answer}}` |
| `GET /v1/models` | loaded base, temperature, whether the rejection channel exists |
| `GET /health` | `{"ok": true, "model": ...}` |
Same routes and response shapes as `kev`'s own server, so `eval/eval_systemone_http.py` scores
it on the bench records **unmodified** β verified here over 120 decision-v7 development rows with
zero failures. It is stdlib `http.server` on purpose: no fastapi/uvicorn to install. One
Identical to `lumierenoir/klev-e4b` apart from the base and the temperature.
decision per request under a lock, since a forward is not re-entrant.
Validated locally: 703-token decision-v7 row served `200` with the correct answer, malformed
bodies `400`, unknown routes `404`, and rows over `--max-seq` `400` naming the token count.
## License and data
- Base model: Apache-2.0 (`Qwen/Qwen3.5-0.8B`).
- This release (adapter, head, probes): Apache-2.0.
- **Kev** (`github.com/jaredpalmer/kev`, Apache-2.0, Jared Palmer) β klev is built on it and
reuses its record format, `PointerHead`, metrics, suite loading, calibration procedure and
decision-v7 data. See "Built on Kev" above.
- **Unsloth** (`github.com/unslothai/unsloth`, Apache-2.0) β the training and inference stack
this model was built with. See "Built with Unsloth" above.
- Training data: decision-v7 records built from permissively licensed sets (Apache-2.0 /
MIT / CC-BY-4.0), attribution manifest in the code repo.
- `stitch/*-lora` are **not** klev's: they are external task adapters trained for this release
on `cardiffnlp/tweet_eval` (hate; a standard public benchmark that does contain slurs) and
`strombergnlp/rumoureval_2019`. Check those datasets' terms before redistribution.
## Code
Architecture, training, evals and the stitch experiment:
`github.com/felipepenhorate/klev` (`SPEC.md`, `docs/m10`β`m12`, `eval/eval_lora_stitch.py`).
The base model is a **preset** (`--preset qwen35-08b` / `e4b` / `e2b-qat`); see
`docs/m12-presets.md`.
### Predictions do not need unsloth
Unsloth is a *training* dependency. The default install is torch + transformers + peft +
bitsandbytes, and a published checkpoint is served through plain transformers β same
accuracy (0.410 vs 0.410 on 195 CoSt-BR rows for klev-e4b, 0.317 vs 0.317 for
klev-0.8b), 98 % identical choices. Unsloth is the `train` extra:
```bash
pip install "klev @ git+https://github.com/felipepenhorate/klev.git" # predictions
pip install "klev[train] @ git+https://github.com/felipepenhorate/klev.git" # + fine-tuning
```
Backend is explicit when you want the unsloth path: `load_klev(ckpt, backend="unsloth")`,
`KLEV_BACKEND=unsloth`, or `--backend unsloth` on `klev-system-one` / `klev-serve`.
See `docs/m15-plain-inference.md`.
### Same checkpoint, two loaders
The numbers above are the unsloth path (`--backend unsloth`, the stack this model was trained
with). klev can also be served with **plain** transformers + bitsandbytes and no unsloth at all β
the default install. Measured parity for this arm: 58 CoSt-BR rows, **0.317 unsloth vs 0.317
plain**, 98.3 % identical choices; the full nine-suite parity (max Β±0.4 pp on any suite) was
measured on the 4B arm and is in `klev-e4b`'s card and `docs/m15-plain-inference.md`.
```bash
pip install "klev @ git+https://github.com/felipepenhorate/klev.git" # plain (default)
pip install "klev[train] @ git+https://github.com/felipepenhorate/klev.git" # + unsloth
```
|