Downloads · 30 days
11
31% of all-time downloads
MannSingh/qwen2.5-7b-theme-kg-lora
qwen2.5-7b-theme-kg-lora is a text generation model from MannSingh. Use it when you need the model to write or continue text. It is set up for peft. The card lists the license as cc-by-nc-sa-4.0.
A LoRA adapter for Qwen/Qwen2.5-7B-Instruct that reads a financial news article and returns a single JSON object describing the article's themes and a knowledge graph anchored to them.
Downloads · 30 days
11
31% of all-time downloads
All-time downloads
36
Public
Repo size
173 MB
Likes
0
Public
Click a slice to open those files.
.safetensors162 MB · 91%
From the Hugging Face model README
A LoRA adapter for Qwen/Qwen2.5-7B-Instruct that reads a
financial news article and returns a single JSON object describing the article's
themes and a knowledge graph anchored to them.
Adapter only
This repo contains the ~162 MB LoRA adapter and its tokenizer — not merged weights. Load the base model and apply the adapter with
peft; see Usage.
The output is one JSON object, one key, no prose:
{"knowledge_graph": [{"triplet": ["head", "HEAD_TYPE", "RELATION", "tail", "TAIL_TYPE"], "theme": "...", "dimension": "..."}]}
Themes are not a separate key: every triplet carries its theme in the theme
field, so an article's themes are the distinct (theme, dimension) pairs across its
triplets.
A theme is a topic tag — the label a financial analyst would file the article under, as a 2-5 word noun phrase. It is not a fact and not a name: a phrase only counts as a theme if it could tag a different article, months later, about different companies ("central bank rate tightening", never "Fed hiked 50bps"), and it must not contain a company, person, place, product or number. Most articles yield 2-3 themes, each filed under one of six dimensions:
economic_monetary_event · geopolitical_factor · sector_or_industry ·
policy_or_regulation · technology_concept · macro_trend
The knowledge graph exists to describe those themes — nothing else. Each theme
phrase becomes a CONCEPT node, linked out to the actors, targets and context that
make it matter, plus entity-to-entity edges one hop away. Facts that connect to no
theme are dropped.
Held-out article d8f9793317cc — a market report on AI in cybersecurity.
The gold graph has 19 triplets across 3 themes;
here are the 6 anchored to cybersecurity threat growth:
{
"knowledge_graph": [
{
"triplet": [
"cybersecurity threat growth",
"CONCEPT",
"HAS_CONTEXT",
"Cybersecurity",
"SECTOR"
],
"theme": "cybersecurity threat growth",
"dimension": "sector_or_industry"
},
{
"triplet": [
"cybersecurity threat growth",
"CONCEPT",
"HAS_ACTOR",
"Cybercriminals",
"ORG"
],
"theme": "cybersecurity threat growth",
"dimension": "sector_or_industry"
},
{
"triplet": [
"cybersecurity threat growth",
"CONCEPT",
"HAS_TARGET",
"Cloud computing ecosystem",
"PRODUCT"
],
"theme": "cybersecurity threat growth",
"dimension": "sector_or_industry"
},
{
"triplet": [
"cybersecurity threat growth",
"CONCEPT",
"AFFECTS",
"AI security market",
"FIN_INSTRUMENT"
],
"theme": "cybersecurity threat growth",
"dimension": "sector_or_industry"
},
{
"triplet": [
"Cybercriminals",
"ORG",
"ACTS_ON",
"Cloud computing ecosystem",
"PRODUCT"
],
"theme": "cybersecurity threat growth",
"dimension": "sector_or_industry"
},
{
"triplet": [
"Cloud computing ecosystem",
"PRODUCT",
"BELONGS_TO",
"Cybersecurity",
"SECTOR"
],
"theme": "cybersecurity threat growth",
"dimension": "sector_or_industry"
}
]
}

Entity types (exactly one per entity):
| type | meaning |
|---|---|
ORG | Organizations (non-government, non-regulatory) |
ORG/GOV | Government bodies |
ORG/REG | Regulatory bodies |
PERSON | Individuals |
GPE | Countries, cities, geopolitical entities |
COMP | Companies |
PRODUCT | Products or services |
EVENT | Specific material events |
SECTOR | Industries or company sectors |
ECON_INDICATOR | Economic indicators (not raw numbers) |
FIN_INSTRUMENT | Financial instruments, markets |
CONCEPT | Theme anchor nodes only |
CONCEPT is reserved: the only CONCEPT nodes are the theme phrases themselves. The
model is trained never to invent a free-floating concept.
Relations — theme to entity, where the theme is always the head:
| relation | meaning |
|---|---|
HAS_ACTOR | who initiates or drives the theme |
HAS_TARGET | what the theme is directed at |
HAS_CONTEXT | the sector, region, indicator, instrument or product it operates in |
AFFECTS | who or what the theme impacts |
Relations — entity to entity, one hop from a theme:
| relation | meaning |
|---|---|
ACTS_ON | one entity does something to another |
BELONGS_TO | membership, ownership, sector placement or location |
Entity labels are capped at 4 words and disambiguated ("BOE" becomes "Bank of England"). Numeric, temporal and generic entities are excluded by design.
Distilled from a larger teacher model's theme/KG annotations over the annotated split
of MannSingh/financial-news-theme-kg, which is built
on FelixDrinkall/financial-news-dataset (Yahoo Finance, 2017-2023),
licensed CC BY-NC-SA 4.0.
Articles were truncated to 12,000 characters, and examples whose prompt plus target exceeded 6,144 tokens were dropped rather than cut mid-graph. Trained for 3 epochs with QLoRA (r=16) on a single 4090 desktop GPU.
The adapter was trained with a fixed system prompt and greedy decoding. Reproduce both or output quality drops sharply. The tokenizer in this repo carries the right chat template, so load it from here rather than from the base model.
import json
import torch
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
BASE = "Qwen/Qwen2.5-7B-Instruct"
ADAPTER = "MannSingh/qwen2.5-7b-theme-kg-lora"
# Same 4-bit setup used for training — fits comfortably on a 16 GB GPU.
bnb = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_use_double_quant=True,
bnb_4bit_compute_dtype=torch.bfloat16,
)
tok = AutoTokenizer.from_pretrained(ADAPTER)
tok.padding_side = "left" # generation
if tok.pad_token is None:
tok.pad_token = tok.eos_token
model = AutoModelForCausalLM.from_pretrained(
BASE, quantization_config=bnb, torch_dtype=torch.bfloat16, device_map="auto"
)
model = PeftModel.from_pretrained(model, ADAPTER)
model.eval()
MAX_ARTICLE_CHARS = 12000
MAX_NEW_TOKENS = 4096
SYSTEM_PROMPT = """You are an expert financial news analyst and knowledge graph extractor.
Given the news article below, produce a single JSON object with exactly
one key: "knowledge_graph".
Process in this strict order:
Step 1 → Identify themes (working step — not output)
Step 2 → Build knowledge_graph anchored to those themes
═══════════════════════════════════════════════════
STEP 1 — IDENTIFY THEMES
═══════════════════════════════════════════════════
A theme is a TOPIC TAG — the kind of label a financial analyst would file
the article under. It is not a fact, and it is not a name.
Two tests. A phrase must pass both:
TEST 1 — Could this same phrase tag a different article, six months from
now, about different companies? If no, it is a fact. Reject it.
TEST 2 — Does it contain a company, person, place, product, or number?
If yes, it is an entity. Reject it.
Shape: 2–5 words. A noun phrase. Understandable on its own.
Go through the 6 dimensions below. For each one, write ONE theme phrase if
that dimension is present in the article — otherwise skip it. Do not force
a phrase. Most articles will yield 2–3 themes, not 6.
DIMENSION GOOD BAD
───────────────────────── ────────────────────────────── ─────────────────────────
economic_monetary_event "central bank rate tightening" "Fed hiked 50bps"
geopolitical_factor "cross border trade tension" "Russia invaded Ukraine"
sector_or_industry "semiconductor supply shortage" "TSMC fab delay"
policy_or_regulation "import tariff escalation" "Section 301 tariffs"
technology_concept "large language model adoption" "GPT-4 launch"
macro_trend "persistent inflation pressure" "CPI rose 3.2%"
The BAD column fails because each one names a specific company, person,
number, or one-off occurrence. The GOOD column would still make sense as a
tag on an article you have never seen.
Write each theme phrase down once, exactly as you will use it. You will
repeat it verbatim in every triplet in Step 2 — same words, same order,
same spelling, every time. Do not shorten or reword it later.
═══════════════════════════════════════════════════
STEP 2 — KNOWLEDGE GRAPH
═══════════════════════════════════════════════════
The knowledge graph exists to describe the themes from Step 1 — nothing else.
Every triplet is built AROUND one of those themes and must trace back to it,
directly or in one hop. If a fact in the article does not connect to any
discovered theme, leave it out. No free-floating facts; no theme without at
least one triplet.
Take the themes from Step 1 one at a time. For each theme:
a) The theme phrase is a node of type CONCEPT
b) Link the theme to real-world entities from the article using the
theme-to-entity relationships below
c) Expand outward: add entity-to-entity triplets that explain why that
theme matters in the article
d) Drop any triplet that cannot be connected — directly or in one hop —
back to a theme
e) Tag every triplet with its owning theme phrase and dimension
Finish one theme completely before starting the next.
RELATIONSHIPS — use only these:
Theme-to-entity (the theme is always the HEAD):
HAS_ACTOR → who initiates or drives the theme
HAS_TARGET → what the theme is directed at
HAS_CONTEXT → the sector, region, indicator, instrument, or product
the theme operates in
AFFECTS → who or what the theme impacts
Entity-to-entity (support, one hop from the theme):
ACTS_ON → one entity does something to another (announces,
introduces, controls, invests in, impacts)
BELONGS_TO → membership, ownership, sector placement, or location
ENTITY TYPES — use exactly one per entity:
ORG → Organizations (non-government, non-regulatory)
ORG/GOV → Government bodies
ORG/REG → Regulatory bodies
PERSON → Individuals
GPE → Countries, cities, geopolitical entities
COMP → Companies
PRODUCT → Products or services
EVENT → Specific material events
SECTOR → Industries or company sectors
ECON_INDICATOR → Economic indicators (not raw numbers/percentages)
FIN_INSTRUMENT → Financial instruments, markets
CONCEPT → Theme anchor nodes ONLY — never create a CONCEPT node
that is not a theme phrase from Step 1
CONSTRAINTS:
• No generic, numerical, or temporal entities
• No redundant triplets
• No strictly past-tense events
• Disambiguate entities (e.g. "BOE" → "Bank of England")
• Max 4 words per entity label
• Every triplet must belong to exactly one discovered theme — either a
theme-to-entity edge, or an entity-to-entity edge one hop from that theme
• The ONLY CONCEPT nodes allowed are the Step 1 theme phrases; never invent
an extra CONCEPT node (e.g. do not link one theme to another new concept)
• Give every discovered theme at least one triplet
═══════════════════════════════════════════════════
OUTPUT FORMAT — return ONLY valid JSON, no extra text
═══════════════════════════════════════════════════
Do not output the Step 1 theme list as its own key. Themes appear only in
the "theme" field of each triplet.
{
"knowledge_graph": [
{
"triplet": ["head_entity", "head_type", "relationship", "tail_entity", "tail_type"],
"theme": "the Step 1 theme phrase — identical in every triplet that belongs to it",
"dimension": "economic_monetary_event | geopolitical_factor | sector_or_industry | policy_or_regulation | technology_concept | macro_trend"
}
]
}
═══════════════════════════════════════════════════
INPUT_TEXT:
"""
def extract(article: str) -> dict:
# The prompt ends with its own INPUT_TEXT: header, so the user message is the
# bare article — do not prefix it with "Article:".
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": article[:MAX_ARTICLE_CHARS]},
]
text = tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
inputs = tok(text, return_tensors="pt").to(model.device)
with torch.no_grad():
out = model.generate(
**inputs,
max_new_tokens=MAX_NEW_TOKENS,
do_sample=False, # greedy, as evaluated
pad_token_id=tok.pad_token_id,
)
completion = tok.decode(
out[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True
)
return parse_model_output(completion)
def parse_model_output(text: str) -> dict:
"""Lenient parser: strips code fences and brace-matches the first JSON object.
Output is not always clean JSON — it may be fenced, prefixed with prose, or
truncated at the token limit. Parse defensively rather than calling json.loads
on the raw string; an unrecoverable output yields an empty graph.
"""
text = text.strip()
if text.startswith("```"):
text = text.split("```")[1]
text = text[4:] if text.startswith("json") else text
start = text.find("{")
if start == -1:
return {"knowledge_graph": []}
depth = 0
for i, ch in enumerate(text[start:], start):
depth += (ch == "{") - (ch == "}")
if depth == 0:
try:
return json.loads(text[start : i + 1])
except json.JSONDecodeError:
return {"knowledge_graph": []}
return {"knowledge_graph": []}
kg = extract("Chip supplier Foo Corp warned that export controls ...")
# Themes are derived from the triplets, not returned separately.
themes = {(t["theme"], t["dimension"]) for t in kg["knowledge_graph"]}
print(themes)
If you use this model, cite the thesis, corpus, and the base model.
@mastersthesis{mann2026finnewskglora,
author = {Mann, Navneet},
title = {Knowledge Graph Canonicalization for Financial Domain},
school = {University College London (UCL)},
year = {2026}
}
@misc{qwen2.5,
title = {Qwen2.5: A Party of Foundation Models},
author = {Qwen Team},
year = {2024},
url = {https://huggingface.co/Qwen/Qwen2.5-7B-Instruct}
}