Downloads · 30 days
42
19% of all-time downloads
rst0070/tiny-graph-extractor-qwen3.5-0.8b-qlora
tiny-graph-extractor-qwen3.5-0.8b-qlora is a text generation model from rst0070. Use it when you need the model to write or continue text. It is set up for peft. The card lists the license as apache-2.0.
A LoRA adapter for unsloth/Qwen3.5-0.8B, trained with GRPO (on-policy RL with a reference-free reward) to extract entities and (head, relation, tail) triplets from text for knowledge graph ingestion.
Downloads · 30 days
42
19% of all-time downloads
All-time downloads
218
Public
Repo size
71.2 MB
Likes
0
Public
Click a slice to open those files.
.safetensors25.6 MB · 56%
From the Hugging Face model README
A LoRA adapter for unsloth/Qwen3.5-0.8B, trained with GRPO
(on-policy RL with a reference-free reward) to extract entities and
(head, relation, tail) triplets from text for knowledge graph ingestion.
This repository contains adapter weights only — the checkpoint from GRPO run 5, step 2000.
The goal of this project is to replace expensive frontier-LLM calls in the knowledge graph extraction pipeline of rst0070/knowledge-base with a tiny model that runs locally.
unsloth/Qwen3.5-0.8B (Instruct), loaded 4-bitFixed evaluation reward over a 200-item test set (144 CrossRE + 30 curated + 26 real-news items), same reward function as training but with frozen weights/ramp so runs stay comparable. "Mean total incl. sentinels" counts unparseable outputs as −1.0 and is the quantity GRPO optimizes.
| Gemini 2.5 Flash Lite (reference) | Qwen3.5-0.8B pretrained | This adapter (GRPO 5) | |
|---|---|---|---|
| mean total incl. sentinels | 0.858 | 0.422 | 0.796 |
| weighted total (parsed only) | 0.935 | 0.788 | 0.871 |
| parse failures / 200 | 8 | 41 | 8 |
The adapter closes ~86% of the pretrained → Gemini gap on the optimized metric and matches Gemini on parse reliability. Note the reward has a ceiling below 1.0: the gold answers themselves score ~0.885 on NLI correctness, so a perfect extractor tops out around 0.93–0.94 (parsed-only) — Gemini sits on that ceiling.
Full per-component breakdowns and per-run analysis: data/result/README.md.
"was founded by", not Wikidata predicate
labels. If your pipeline needs a fixed relation ontology, map these
strings downstream.The model emits a single fenced JSON code block:
```json
{
"entities": ["Entity A", "Entity B"],
"relations": [
{"head": "Entity A", "relation": "relation_type", "tail": "Entity B"}
]
}
```
Every entity referenced in a relation appears in the entities list.
Use the exact system prompt and user-message format the adapter was trained and evaluated with:
import re, json
import torch
from transformers import BitsAndBytesConfig, pipeline
SYSTEM_PROMPT = (
"You are a knowledge graph extraction assistant.\n\n"
"Given a text, extract entities and their relations. Output a single "
"fenced JSON code block with this schema:\n"
'- "entities": list of unique entity strings mentioned in the text\n'
'- "relations": list of objects, each with "head", "relation", "tail" '
"(all strings)\n\n"
'Every entity referenced in a relation must appear in the "entities" list. '
"Do not include dates or numerical values as entities. "
"Output only the fenced JSON code block — no other text, preamble, or "
"explanation."
)
text = (
"Marie Curie was born in Warsaw and later moved to Paris, where she conducted her research at the University of Paris. She discovered polonium and radium."
)
# The adapter is a QLoRA checkpoint: it was trained and evaluated on a
# 4-bit NF4-quantized base, so load the base the same way.
pipe = pipeline(
"text-generation",
model="rst0070/tiny-graph-extractor-qwen3.5-0.8b-qlora",
model_kwargs={
"quantization_config": BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_use_double_quant=True,
bnb_4bit_compute_dtype=torch.bfloat16,
),
},
max_new_tokens=1024,
)
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"Extract entities and relations from the following text:\n\n{text}"},
]
response = pipe(messages)[0]["generated_text"][-1]["content"]
match = re.search(r"```(?:json)?\s*\n(.*?)\n```", response, re.DOTALL)
if match:
print(json.loads(match.group(1)))
The BitsAndBytesConfig above matches the training/eval setup (Unsloth,
load_in_4bit=True, i.e. bitsandbytes NF4). Loading the base without it
(plain fp16/bf16) also works, but the reported numbers were measured with
the 4-bit base, so quantization noise is part of them.
GRPO — on-policy RL where the model learns directly from a reward on its own outputs:
This checkpoint is the result of 5 sequential runs: run 1 started GRPO from the pretrained model, and each later run continue-trained the previous run's best adapter on a fresh, disjoint 2000-sample REBEL slice, changing exactly one thing (reward weights, NLI acceptance ramp) so its effect is attributable. Progression on the optimized metric: 0.422 (pretrained) → 0.581 → 0.663 → 0.775 → 0.792 → 0.796 (run 5), vs. Gemini 2.5 Flash Lite at 0.858.
Apache 2.0, inheriting from the base model and dataset license terms.