A 117M-parameter relation classifier for building knowledge graphs from documentation. Mark two entities inside a passage and it answers, in one forward pass, how they relate — one of three relation types, or no relation.
What makes it different: typed relation extraction is usually handed to a large generative model, token by token. This does it as a single classification, which makes it cheap enough to sweep a whole corpus — ~2 900 pairs/s on a laptop RTX 5060 — and its head reads the two marked entities directly instead of a whole-passage summary, so the relation is scored between them and in order. Multilingual: the XLM-R backbone reads prose and code comments in many languages.
The weights under this id were replaced on 2026-10-12. Same architecture, same inputs and outputs, same tokenizer; the backbone is now the MIT-licensed
hotchpotch/mMiniLMv2-L12-H384, and macro-F1 on the held-out set moved from 0.57 to 0.60. Engines built for the previous weights are gone from this repository.
Wrap the entities in [E1]…[/E1] and [E2]…[/E2] in their own context.
| idx | label | meaning |
|---|---|---|
| 0 | NO_RELATION |
they co-occur but are unrelated — abstain |
| 1 | semantically_similar_to |
same purpose or goal, different mechanism |
| 2 | supersedes_or_conflicts |
one replaces, supersedes or contradicts the other |
| 3 | depends_on |
one requires or configures the other |
The taxonomy is coarse on purpose: an earlier 8-way split (replaced_by vs contradicts,
depends_on vs configured_by, …) drew boundaries the context could not support.
Decision rule: accept when argmax != NO_RELATION and 1 − softmax[NO_RELATION] ≥ tau.
Gating on "is there any relation" holds up better than gating on the winning class, whose
probability sags when a real relation splits between two plausible types.
Same 691 held-out pairs, same 4-way mapping, same machine. The textbook alternative with no training data is a zero-shot NLI model that scores each class as a hypothesis:
| model | params | macro-F1 | "has a relation" F1 | P | R | pairs/s |
|---|---|---|---|---|---|---|
| this model | 117M | 0.598 | 0.798 | 0.786 | 0.811 | 351 |
| mDeBERTa-v3-xnli, zero-shot | 279M | 0.194 | 0.699 | 0.538 | 0.997 | 61 |
always NO_RELATION |
— | 0.158 | — | — | — | — |
NO_RELATION 0.76, semantically_similar_to 0.64, supersedes_or_conflicts 0.47,
depends_on 0.52). It is built for a high-precision operating point: spot-check the edges at
the tau you choose.Batch 16 × seq 256, laptop Intel Core Ultra 9 275HX + RTX 5060:
| lane | pairs/s |
|---|---|
| TensorRT FP16, RTX 5060 | 2 942 |
| OpenVINO FP16, Intel iGPU | 111 |
| OpenVINO FP16, CPU | 44 |
| Apple M4, Neural Engine (seq 64 / 128 / 256) | 1 909 / 829 / 275 |
Inside a real index, with wide batches, the daemon sustains ~2 160 pairs/s and creates 1 000–1 350 relations/s; at that width tokenisation, not the GPU, is the limit — give it threads.
tau ≈ 0.7 is a reasonable start; raise it for fewer, surer edges.import numpy as np, onnxruntime as ort
from transformers import AutoTokenizer
LABELS = ["NO_RELATION", "semantically_similar_to", "supersedes_or_conflicts", "depends_on"]
tok = AutoTokenizer.from_pretrained(".") # includes the [E1]/[E2] marker tokens
sess = ort.InferenceSession("model.onnx", providers=["CPUExecutionProvider"])
def classify(marked_text, max_len=256, tau=0.7):
enc = tok([""], [marked_text], padding="max_length", truncation=True,
max_length=max_len, return_tensors="np", return_token_type_ids=False)
logits = sess.run(None, {"input_ids": enc["input_ids"].astype(np.int64),
"attention_mask": enc["attention_mask"].astype(np.int64)})[0][0]
p = np.exp(logits - logits.max()); p /= p.sum()
if 1.0 - p[0] < tau: # gate on "any relation at all"
return "NO_RELATION", float(p[0])
i = int(p.argmax())
return (LABELS[i], float(p[i])) if i else ("NO_RELATION", float(p[0]))
classify("The [E1]FAISS[/E1] index was replaced by the [E2]native IVF[/E2] backend.")
# -> ('supersedes_or_conflicts', 0.7x)
Text goes in as an (empty query, marked passage) pair.
Not suited to: fine-grained relation ontologies; finding the entities (it classifies pairs you already have); entities more than a 256-token window apart.
hotchpotch/mMiniLMv2-L12-H384 — Microsoft's multilingual MiniLM v2 (12 layers ×
384, distilled from XLM-RoBERTa Large), a plain masked-language-model encoder with no
task-specific stage in front of it — with four marker tokens added to its vocabulary.[E1] and [E2] positions,
concatenated — not over [CLS]. Direction comes free: swap the markers and the input genuinely
changes.NO_RELATION is a trained class, not
an inferred one.| file | what it is |
|---|---|
model.onnx + model.onnx.data |
FP32 ONNX — the build source and the standalone path |
model.safetensors, config.json |
the same weights: encoder as roberta.*, head as classifier.weight [4, 768] + classifier.bias (see below); the Apple (MLX) build is prepared from this pair |
tokenizer.json, tokenizer_config.json, sentencepiece.bpe.model |
XLM-R tokenizer with the four marker tokens |
code-daemon-relation-v1-{s,m,l}_{win_x64,linux_x64}_trt11.3_sm_{75,80,86,89,90,120}.engine |
TensorRT FP16 per bucket × OS × GPU; sm_90 (H100/H200) Linux only. The set is filled per architecture as each is built — the file list is the truth |
code-daemon-relation-v1-{s,m,l}_ov2026.4_{cpu,igpu}_fp16_b16_s{64,128,256}.{xml,bin} |
OpenVINO 2026.4 FP16, Intel CPU / iGPU |
coreml_ane/embed.mlpackage/ |
Core ML for the Apple Neural Engine, functions b16 s64 / s128 / s256; load with cpuAndNeuralEngine |
⚠ The head is not the stock transformers one — AutoModelForSequenceClassification would
initialise its own and ignore this. Load the encoder with AutoModel, mean-pool the last hidden
state over the tokens equal to each marker id (config.json → ultracode.entity_marker_ids),
concatenate the two and apply the classifier; that matches the ONNX to 3e-6.
Released under the MIT license.
Backbone: hotchpotch/mMiniLMv2-L12-H384 (MIT), Microsoft's Multilingual-MiniLM-L12-H384 distilled from XLM-RoBERTa Large. Used by the Code-Daemon code assistant, though nothing about the model is specific to it.
model_gpu_mlx0.32/model.safetensors 470 MBmodel.safetensors 470 MBcoreml_ane/embed.mlpackage/Data/com.apple.CoreML/weights/weight.bin 235 MBcode-daemon-relation-v1-l_ov2026.4_cpu_fp16_b16_s256.bin 235 MBcode-daemon-relation-v1-l_ov2026.4_igpu_fp16_b16_s256.bin 235 MBcode-daemon-relation-v1-m_ov2026.4_cpu_fp16_b16_s128.bin 235 MBcode-daemon-relation-v1-m_ov2026.4_igpu_fp16_b16_s128.bin 235 MBcode-daemon-relation-v1-s_ov2026.4_cpu_fp16_b16_s64.bin 235 MBcode-daemon-relation-v1-s_ov2026.4_igpu_fp16_b16_s64.bin 235 MBmodel.onnx 2 MBhotchpotch/mMiniLMv2-L12-H384Have the files? Submit a torrent.
Submit a torrentHave the complete files and permission to share them? Replace the example folder path. The script checks the files, makes a torrent and submits its magnet and checksums. Keep your torrent client seeding. Get your community key from your settings, and read the script before running it.
curl -fsSL https://pirateface.co/package.sh | bash -s -- --repo faxenoff/code-daemon-relation-v1 ./model-folder