knife-gliner-large-v2.5

GLiNER large v2.5 (a 459M DeBERTa-v3-large encoder) fine-tuned to tag knife brands, knife models and knife steels in Reddit comments. From "picked up a Mazaki in white #2, way better than my old Fibrox" it returns Mazaki as a brand, Fibrox as a model and white #2 as a steel.

The labels it was trained on were written by Gemini 3.1 Pro, once, for $9. Nobody checked them by hand, so every score below is agreement with Gemini, not with the truth.

Write-up: https://petervijeh.com/projects/reddit-ner. Run log: https://new.knife.day/blog/fine-tuning-gliner-knife-ner. Training script and pipeline: https://github.com/pvijeh/reddit-scraper-project (sidecar/train_gliner_modal.py).

Labels

  • knife brand (Spyderco, Mazaki, Benchmade)
  • knife model (Paramilitary 2, 940, Fibrox)
  • knife steel (MagnaCut, S35VN, white #2)

Results

Validation set: 225 comments held out before the second training run and never changed after.

Model F1 vs Gemini labels
GLiNER large v2.5, zero-shot ~0.65 (est.)
GLiNER medium v2.5, fine-tuned (209M) 0.800
This model (run 10) 0.83

An earlier run scored 0.879 on a random split. That number is not the result: on random splits, two drops in F1 that looked like regressions came from which comments landed in the validation set.

Steel names score lower confidence than brands or models. One threshold per class instead of a global 0.45 took steel recall from 0.787 to 0.911. The thresholds used in production:

label_thresholds = {"knife brand": 0.35, "knife model": 0.30, "knife steel": 0.20}

Usage

from gliner import GLiNER

model = GLiNER.from_pretrained("P-s-v/knife-gliner-large-v2.5")
labels = ["knife brand", "knife model", "knife steel"]
thresholds = {"knife brand": 0.35, "knife model": 0.30, "knife steel": 0.20}

text = "picked up a Mazaki in white #2, way better than my old Fibrox"
entities = model.predict_entities(text, labels, threshold=min(thresholds.values()))
entities = [e for e in entities if e["score"] >= thresholds[e["label"]]]
for e in entities:
    print(e["text"], "=>", e["label"], round(e["score"], 2))

Training

  • 2,025 training examples, 225 validation, GLiNER JSONL format: tokenized_text plus ner spans. About 30% of examples have no entities. 51 of them are adversarial negatives, comments with generic words like "carbon steel" or "chef knife" and no product names; 510 made F1 worse (0.799).
  • Tesla T4 on Modal. The 459M encoder fits a T4 only with gradient accumulation. Best checkpoint at epoch 2; every large run overfits after that. The winning run took 24 minutes.
learning_rate = 1e-5
per_device_train_batch_size = 2
gradient_accumulation_steps = 8   # effective batch 16
max_steps = steps_per_epoch * epochs   # GLiNER's default max_steps=10000 overrides num_train_epochs

Two bugs cost more than any hyperparameter. GLiNER's tokenize_inputs crashes on some inputs with the Rust tokenizer's encode_batch; the script replaces it with per-example tokenizer.__call__. And words_mask is not a mask: it is a 1-based word index per token, 0 on special tokens. Filling it with ones trains a model that learns nothing, with a flat loss and no error.

Limits

  • Silver labels. Where Gemini was wrong, this model is graded right for copying the mistake.
  • Trained on eight knife subreddits. Brands it never saw in training (small makers) are the weak spot; no held-out-brand F1 has been measured yet.
  • English only. "PM2" is the Spyderco Paramilitary 2 in r/knives and just letters elsewhere.

Cost

$9 of Gemini labels through OpenRouter, about $2.50 of T4 time across ten runs.

Downloads last month
-
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for P-s-v/knife-gliner-large-v2.5

Finetuned
(4)
this model

Dataset used to train P-s-v/knife-gliner-large-v2.5