JEVnovel 2.5 · 4B · llama.cpp (GGUF)
⚠️ This is a sentence-quality classifier, not a chat model. There is no
lm_headin the GGUF. It produces nothing meaningful in LM Studio / Ollama /llama-cligeneration mode — please don't use it as an LLM.
llama.cpp computes per-token hidden states. The two heads (head_pos, head_clause)
and the piece-level readout are applied by the bundled jev_gguf.py, which mirrors the
official PyTorch infer.py line by line.
中文说明:README.zh.md
What it does
For a sentence it returns a per-piece quality curve plus a 3-way label per piece:
head_pos— a linear head read at the last token of each piece;sigmoid()of it is P("needs revision so far"). Chained over pieces, that is the quality curve.head_clause— a linear head applied to the mean-pooled hidden states inside a piece; 3-way softmax over 拖后腿 (hurts) / 中性 (neutral) / 有价值 (helps).
整段 P=0.536 判定:需要修改
话一出口她自己先红了脸, P=0.186 Δ=+0.186 中性(45%) ★判烂
像是嫌自己说得太明白, P=0.604 Δ=+0.418 拖后腿(91%) ★判烂
转身去开冰箱。 P=0.536 Δ=-0.068 中性(55%) ★判烂
Requirements
- a llama.cpp build with
qwen35support that shipsllama-embedding - Python 3.8+ with
pip install numpy tokenizers— torch is not needed
Quick start
python jev_gguf.py \
--model jev-novel-2.5-4b-Q4_K_M.gguf \
--heads heads.npz \
--tokenizer tokenizer.json \
--text "话一出口她自己先红了脸,像是嫌自己说得太明白,转身去开冰箱。"
Batch mode (one JSON object per line, sentence field, optional id):
python jev_gguf.py --model ... --heads heads.npz --tokenizer tokenizer.json \
--input input.jsonl --output pred.jsonl
--llama-embedding /path/to/llama.cpp/build/bin/llama-embedding if it is not on PATH.
Add --json for machine-readable output in single-sentence mode.
Output fields
| Field | Meaning |
|---|---|
p |
P(the whole sentence needs revision) |
bad |
sentence-level verdict (p >= 0.050) |
pieces[] |
per piece: text, p, delta (= p − previous piece p), clause, clause_prob (3), flag (flagged as bad) |
Limitations of running 2.5 through llama.cpp
Please read this before filing an issue — it is all by design, not a bug:
- It is not an LLM. There is no
lm_head; generation is meaningless. It will not work as a drop-in chat model in LM Studio / Ollama /llama-cli. - The GGUF alone does not produce JEV output. llama.cpp has no graph for these heads and
no operator for "read at each piece's last token / mean-pool within a piece". You must run
the bundled
jev_gguf.py(or reimplement ~100 lines of numpy yourself). llama-embeddingis required, used with--pooling noneto emit per-token hidden states. Not every llama.cpp distribution ships it.- Scoring requires the fixed prompt. The model was trained with a rubric prepended to the
sentence;
jev_gguf.pyprepends it fromheads.npz. Feeding the raw sentence tollama-embeddingyourself will give wrong scores. - One
llama-embeddingprocess per sentence in this simple runner, so every sentence pays a model-load cost again (measured: ~2 s per sentence on the 4B Q8_0 with a warm page cache; a cold cache is slower). For high throughput, keep allama-serverembedding endpoint up, or batch many sentences through a single call. - Piece-level output is the whole point of 2.5 and it cannot be expressed inside
llama.cpp's compute graph. Making the GGUF emit a single sentence score would require
patching llama.cpp (adding a classifier head to the
qwen35arch) — not something we ship. - Max input is 1024 tokens including the fixed prompt; longer input is rejected.
- Trained on Chinese web-fiction text; other genres are not validated.
Quantization loss
Measured on 33 sentences (including edge cases such as “是。” and a chapter heading),
comparing each GGUF against the official bf16 infer.py (PyTorch) run.
This is agreement with the bf16 reference, not accuracy against human labels.
| Quant | Sentence verdict | Per-piece flag | Per-piece clause | Max abs Δp |
|---|---|---|---|---|
| f16 | 33/33 (100.0%) | 102/102 (100.0%) | 102/102 (100.0%) | 0.0035 |
| Q8_0 | 33/33 (100.0%) | 102/102 (100.0%) | 102/102 (100.0%) | 0.0035 |
| Q4_K_M | 33/33 (100.0%) | 101/102 (99.0%) | 98/102 (96.1%) | 0.0317 |
Reading it: the sentence-level verdict stays identical even at Q4_K_M. Quantization loss
shows up as occasional per-piece flips right around the thresholds, plus a small drift in p.
Underlying hidden states match PyTorch at cosine similarity 0.99995+ (f16).
Files
| File | Notes |
|---|---|
jev-novel-2.5-4b-f16.gguf |
backbone, f16 (most faithful, largest) |
jev-novel-2.5-4b-Q8_0.gguf |
8-bit (essentially lossless) |
jev-novel-2.5-4b-Q4_K_M.gguf |
4-bit (smallest — try this first) |
heads.npz |
the two heads + rubric/thresholds (required by the script) |
tokenizer.json |
tokenizer (the script uses it for character offsets) |
jev_gguf.py |
the runner |
BUILD.json |
how this GGUF was produced (conversion flags) |
Thresholds (from the official config — do not change)
Sentence: p >= 0.050 → needs revision.
Piece flagged bad: (Δ >= 0.05 and P >= 0.15 and piece length >= 2 chars) or P >= 0.5.
Troubleshooting
libllama-common.so.0: cannot open shared object file— thellama-embeddingon yourPATHis a copy/symlink without its sibling shared libraries. Point--llama-embeddingat the real binary inllama.cpp/build/bin/(the script also prepends that directory toLD_LIBRARY_PATH).token count mismatch— your llama.cpp build tokenizes/splitsqwen35differently from HF; try a newer build.- Newlines in the rubric are handled for you:
llama-embeddingsplits input into one sequence per newline by default, and the rubric contains newlines. The script passes a separator that never appears in the text so the whole prompt stays a single sequence.