Guides & How-tos 8 min read
How to Build Named Entity Recognition With Open Models (2026 Guide)
A practical guide to token classification and NER: which open models to pick, how to extract custom entities with GLiNER, and how to deploy and evaluate it.

Quick answer: Token classification assigns a label to every token in a text, and named entity recognition (NER) is its most common form. The shortest path today: use a GLiNER model with the gliner library when you want to name your own entity types at run time, or a fine-tuned BERT-style NER model through the Hugging Face transformers pipeline when a fixed label set (person, organization, location) is enough.
Most information-extraction features start here. Pulling customer names and order numbers out of support tickets, tagging drugs and dosages in clinical notes, finding company names in news, and masking personal data before text goes to another system are all token classification problems.
This guide covers how the task works, which open models to choose in 2026, working code, deployment options, evaluation and the mistakes that cost teams the most time.
What is token classification?
The input is text; the output is a label for each token or, after grouping, a list of spans with a type, character offsets and a confidence score. The Hugging Face token classification task page lists named entity recognition and part-of-speech tagging as the main subtasks. Labels are usually written in the BIO scheme: B-PER starts a person mention, I-PER continues it and O marks tokens outside any entity.
There are two model families. Classic NER models put a classification head on a bidirectional encoder such as BERT, RoBERTa or DeBERTa and predict one of a fixed set of tags per token; the transformers token classification guide shows how to fine-tune one. Open-label models such as GLiNER encode the text and your label names together and score candidate spans against each label, so you can ask for "invoice number" or "medication" without retraining. The approach is described in the GLiNER paper (NAACL 2024).
What can you build with it?
- PII redaction: detect names, emails, phone numbers and IDs, then mask them before logging, analytics or sending text to an external API.
- Document field extraction: pull dates, amounts, company names and reference numbers from contracts, invoices or OCR output.
- CRM and ticket enrichment: tag products, competitors and people mentioned in emails or call notes and write them to structured fields.
- Search facets and knowledge graphs: index entities so users can filter by company, location or person, or link mentions to records.
- Domain tagging: drugs, symptoms, legal citations or financial instruments, usually with a fine-tuned or open-label model.
- Pre-processing for LLM pipelines: cheap entity extraction ahead of retrieval or summarization, so the expensive model sees cleaner input.
Which models should you use?
Start with an open-label model if your entity types are custom or likely to change, and with a fixed-label model if you only need the classic person, organization, location set and want the smallest, fastest option.
- GLiNER multi v2.1 (urchade/gliner_multi-v2.1): multilingual open-label NER; the model card's release table lists it at 209M parameters under Apache-2.0. A good default for custom labels in several languages.
- GLiNER small v2.5 (gliner-community/gliner_small-v2.5): the lightweight checkpoint used in the GLiNER README, aimed at resource-constrained environments. Check the card for its licence before shipping.
- GLiNER2 base (fastino/gliner2-base-v1): a 205M-parameter Apache-2.0 model that does NER, text classification and structured extraction in one CPU-friendly encoder, loaded with the
gliner2package. - GLiNER multi PII (urchade/gliner_multi_pii-v1): fine-tuned from gliner_multi-v2.1 on synthetic PII data, with labels such as phone number, passport number, email and credit card number.
- BERT base NER (dslim/bert-base-NER): bert-base-cased fine-tuned on English CoNLL-2003 with PER, ORG, LOC and MISC labels. Small, fast and well understood, but limited to those four types.
- XLM-RoBERTa large CoNLL-03 (FacebookAI/xlm-roberta-large-finetuned-conll03-english): a larger (about 0.6B parameters) fixed-label model with a multilingual backbone. Its card lists the licence as "more information needed", so confirm terms first.
- mBERT NER HRL (Davlan/bert-base-multilingual-cased-ner-hrl): PER, ORG and LOC for 10 high-resource languages including Arabic, German, Spanish, French, Portuguese and Chinese.
There is no single public arena for NER. Compare candidates on a sample of your own data, and use the zero-shot results in the GLiNER and GLiNER2 papers as a starting point. You can try GLiNER interactively in the GLiNER demo Space and the GLiNER PII Space.
How do you build it, step by step?
- Write the entity schema. List each entity type with a one-line definition and two or three examples. This becomes your label list for GLiNER and your annotation guide.
- Collect 100-300 real samples. Annotate them by hand. You need them to choose a model and to catch regressions later.
- Pick a model. Try an open-label GLiNER model first with your label names; if your labels are the standard four, compare against a fixed-label BERT model.
- Chunk long text. Encoder models have a fixed context window (512 tokens for BERT-style models), so split documents by sentence or paragraph and keep character offsets.
- Run extraction and post-process. Merge sub-word pieces, drop low-confidence spans, deduplicate and map spans back to the original document offsets.
- Fine-tune if needed. If zero-shot accuracy is not enough, fine-tune on your annotated data with the transformers Trainer or the GLiNER training scripts.

The first example follows the dslim/bert-base-NER model card, with the pipeline's aggregation_strategy option added so sub-word tokens are grouped into whole entities:
# pip install transformers torch (pattern from the dslim/bert-base-NER model card)
from transformers import pipeline
ner = pipeline(
"token-classification",
model="dslim/bert-base-NER",
aggregation_strategy="simple", # merge sub-word pieces into whole entities
)
text = "My name is Wolfgang and I live in Berlin. I work for Siemens."
for ent in ner(text):
print(ent["entity_group"], ent["word"], round(float(ent["score"]), 3), ent["start"], ent["end"])For custom labels, this example is taken from the urchade/GLiNER GitHub README. Swap in urchade/gliner_multi-v2.1 for multilingual text:
# pip install gliner (example from the urchade/GLiNER README)
from gliner import GLiNER
model = GLiNER.from_pretrained("gliner-community/gliner_small-v2.5")
text = """
Cristiano Ronaldo dos Santos Aveiro (born 5 February 1985) is a Portuguese
professional footballer who plays as a forward for and captains both Saudi Pro
League club Al Nassr and the Portugal national team.
"""
labels = ["person", "date", "organization", "location"]
entities = model.predict_entities(text, labels, threshold=0.5)
for entity in entities:
print(entity["text"], "=>", entity["label"])If you also need classification or structured fields from the same text, the fastino-ai/GLiNER2 repository documents a schema-based API built on the same idea.
How do you serve and deploy it?
Encoder NER models are small, so a CPU is often enough. A BERT-base model or a GLiNER small or base model can serve interactive requests on a standard cloud VM; batch jobs over millions of documents benefit from a single GPU.
- API: wrap the pipeline or the GLiNER model in a FastAPI endpoint that returns spans with
start,end,labelandscore. Load the model once at startup. - Demo: a Gradio app with a text box and highlighted output is the fastest way to collect feedback from domain experts.
- Speed-ups: export to ONNX (several ONNX conversions of these models exist on the Hub), run INT8 quantization, and batch inputs of similar length.
- Managed hosting: Hugging Face Inference Endpoints or Spaces work for transformers models; for GLiNER, run your own container with the
glinerpackage installed.
The model code lives in huggingface/transformers and the GLiNER repository; pin library versions in production because pipeline defaults change between releases.
How do you evaluate it?
Report entity-level precision, recall and F1, not token accuracy: a model that tags most tokens as O gets high token accuracy while missing entities. A predicted entity counts as correct only when both its boundaries and its type match. The seqeval library computes these scores in the same way as the CoNLL evaluation script:
# pip install seqeval (example from the chakki-works/seqeval README)
from seqeval.metrics import classification_report, f1_score
y_true = [["O", "O", "O", "B-MISC", "I-MISC", "I-MISC", "O"], ["B-PER", "I-PER", "O"]]
y_pred = [["O", "O", "B-MISC", "I-MISC", "I-MISC", "I-MISC", "O"], ["B-PER", "I-PER", "O"]]
print(f1_score(y_true, y_pred)) # entity-level F1
print(classification_report(y_true, y_pred)) # per-entity-type precision/recall/F1Common public datasets are CoNLL-2003 (English news), WikiANN (multilingual) and WNUT 17 (noisy social text). They are useful sanity checks, but your own annotated sample is the number that matters. For GLiNER outputs, convert spans to BIO tags or compare spans directly with exact-match and overlap scoring. Track per-label F1, because one weak label is easy to hide in a micro average.
Pitfalls and tips
- Sub-word fragments. Without an aggregation strategy you get pieces like
##mens. Useaggregation_strategy="simple"or higher and test multi-word entities; a forum thread on aggregation strategies shows how phrase splitting shows up in practice. - Tagging scheme mismatch. The pipeline's grouping assumes BIO tags. If you train with BILOU tags, see this discussion on BILOU aggregation before you ship.
- Domain shift. CoNLL-trained models learned from news text and often miss entities in chat logs, medical notes or code.
- Label wording matters for GLiNER. "company" and "organization" can return different spans. Test a few phrasings and tune the threshold per label.
- Licences. Licences differ between releases of the same family: the original multilingual GLiNER release was CC BY-NC, while v2.1 is Apache-2.0. Read each card.
- Privacy. PII detection is never perfect. Combine model output with regex rules for structured IDs and keep a human review step for high-risk data.
Where to get help
- The Hugging Face forum has many threads on token classification pipelines, aggregation and fine-tuning.
- GLiNER GitHub Discussions for questions on open-label NER, training and ONNX export.
- GLiNER2 model discussions on Hugging Face for the GLiNER2 family.
- The transformers GitHub issues for pipeline bugs, with your library version and a minimal example.
Frequently asked questions
Is NER the same as token classification?
NER is one type of token classification. The same setup also covers part-of-speech tagging, chunking and any task where each token or span gets a label.
Should I use an LLM instead of an NER model?
An LLM can extract entities from a prompt, but it is slower, costs more per document and can return text that does not appear in the input. Encoder models return exact character offsets and run on CPU, so they are usually the better default for high-volume extraction.
How much labelled data do I need to fine-tune?
Try zero-shot GLiNER first. If it falls short, a few hundred to a few thousand annotated sentences per entity type is a common starting point for fine-tuning; measure on a held-out set to see when more data stops helping.
Can these models handle languages other than English?
Yes. gliner_multi-v2.1 is multilingual, and Davlan/bert-base-multilingual-cased-ner-hrl covers 10 languages. Always evaluate on text in your target language.
Related guides in this series: text and zero-shot classification, extractive question answering and semantic search with embeddings. To see entity and action-item extraction applied to meetings, look at Acta's features.


