urchade commited on
Commit
8c39374
·
verified ·
1 Parent(s): 5693482

Add GLiNER2.5 boundary architecture model card and banner.

Browse files
Files changed (1) hide show
  1. README.md +476 -0
README.md ADDED
@@ -0,0 +1,476 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ library_name: gliner2
3
+ license: apache-2.0
4
+ language:
5
+ - en
6
+ pipeline_tag: token-classification
7
+ tags:
8
+ - gliner2
9
+ - Text classification
10
+ - Named Entity Recognition
11
+ - Relation Extraction
12
+ - Intent classification
13
+ - Sentiment Analysis
14
+ - Topic classification
15
+ - Structured extraction
16
+ - Json extraction
17
+ - information-extraction
18
+ - boundary-extraction
19
+ ---
20
+ <div align="center">
21
+ <a href="https://fastino.ai" target="_blank" rel="noopener noreferrer">
22
+ <img src="GitHub_new.jpg" alt="Pioneer AI - Fine-tune GLiNER with a single prompt" width="100%"/>
23
+ </a>
24
+ </div>
25
+
26
+ <div style="display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px;">
27
+ <a href="https://fastino.ai?utm_source=huggingface" target="_blank" rel="noreferrer" style="text-decoration:none;">
28
+ <img src="https://img.shields.io/badge/Deploy-GLiNER2-EA4335" alt="Fine-tune and Deploy GLiNER2 with Fastino" style="vertical-align:middle;">
29
+ </a>
30
+ <a href="https://arxiv.org/abs/2507.18546" target="_blank" rel="noreferrer" style="text-decoration:none;">
31
+ <img src="https://img.shields.io/badge/arXiv-2507.18546-b31b1b.svg?logo=arxiv" alt="arXiv Paper" style="vertical-align:middle;">
32
+ </a>
33
+ <a href="https://github.com/fastino-ai/GLiNER2" target="_blank" rel="noreferrer" style="text-decoration:none;">
34
+ <img src="https://img.shields.io/badge/GitHub-GLiNER2-black?logo=github" alt="GitHub" style="vertical-align:middle;">
35
+ </a>
36
+ <a href="https://x.com/fastinoAI" target="_blank" rel="noreferrer" style="text-decoration:none;">
37
+ <img src="https://img.shields.io/twitter/follow/:fastinoAI" alt="Follow @fastinoAI" style="vertical-align:middle;">
38
+ </a>
39
+ </div>
40
+
41
+ # GLiNER2.5 Small: Unified Schema-Based Information Extraction
42
+
43
+ > *Extract entities, classify text, parse structured records, score span attributes, and extract relations — all in one boundary architecture.*
44
+
45
+ GLiNER2.5 Small is the compact English boundary checkpoint. It keeps the same public API as the larger 2.5 models while running faster on CPU and edge-style deployments. Load it with `AutoExtractor`: the checkpoint's `architecture` field selects `BoundaryExtractor` automatically.
46
+
47
+ Fine-tune via [Fastino](https://fastino.ai). Join discussions on [Discord](https://discord.gg/fastino) and [Reddit](https://www.reddit.com/r/GLiNER/).
48
+
49
+ ## ✨ Why GLiNER2.5?
50
+
51
+ - **🎯 One model, many tasks**: entities, classification, structured records, relations, and span attributes in a single schema
52
+ - **📐 Boundary architecture**: sparse start/end pairing instead of a fixed span-width grid — any span length that fits in the encoded window
53
+ - **🔗 Constrained decoding**: `Classifier` for cross-task label constraints, `JointIE` for typed entity–relation graphs
54
+ - **💻 Local inference**: CPU, CUDA, or MPS through `gliner2[local]` — no external API required
55
+
56
+ ## GLiNER2.5 family
57
+
58
+ | Model | Parameters | Encoder | Language | Use case |
59
+ |-------|------------|---------|----------|----------|
60
+ | [`fastino/gliner2.5-small-v1`](https://huggingface.co/fastino/gliner2.5-small-v1) | 74M | DeBERTa-v3-xsmall | English | Fast CPU extraction / classification |
61
+ | [`fastino/gliner2.5-base-v1`](https://huggingface.co/fastino/gliner2.5-base-v1) | 194M | DeBERTa-v3-base | English | Default English multi-task checkpoint |
62
+ | [`fastino/gliner2.5-multi-v1`](https://huggingface.co/fastino/gliner2.5-multi-v1) | 287M | mDeBERTa-v3-base | Multilingual | Default multilingual multi-task checkpoint |
63
+
64
+ This card is for **`fastino/gliner2.5-small-v1`**. All three checkpoints share the same public API.
65
+
66
+ ## Installation
67
+
68
+ ```bash
69
+ pip install "gliner2[local]"
70
+ ```
71
+
72
+ Python 3.10 or newer is required. The `[local]` extra pulls in PyTorch so you can load Hub checkpoints.
73
+
74
+ ## Load the model
75
+
76
+ Always use `AutoExtractor` for GLiNER2.5. `GLiNER2.from_pretrained(...)` is the legacy **span** loader and will not dispatch this checkpoint.
77
+
78
+ ```python
79
+ from gliner2 import AutoExtractor
80
+
81
+ model = AutoExtractor.from_pretrained("fastino/gliner2.5-small-v1")
82
+
83
+ print(type(model).__name__) # BoundaryExtractor
84
+ print(model.config.architecture) # "boundary"
85
+ ```
86
+
87
+ Optional device, fp16, and compile flags:
88
+
89
+ ```python
90
+ model = AutoExtractor.from_pretrained(
91
+ "fastino/gliner2.5-small-v1",
92
+ map_location="cuda", # or "cpu" / "mps"
93
+ quantize=True, # fp16 weights on GPU
94
+ compile=True, # torch.compile after the first tracing call
95
+ )
96
+ ```
97
+
98
+ ## Usage
99
+
100
+ ### Entity extraction
101
+
102
+ ```python
103
+ text = "Apple CEO Tim Cook announced iPhone 15 in Cupertino yesterday."
104
+
105
+ result = model.extract_entities(
106
+ text,
107
+ ["company", "person", "product", "location"],
108
+ include_confidence=True,
109
+ include_spans=True,
110
+ )
111
+ print(result)
112
+ # {
113
+ # "entities": {
114
+ # "company": [{"text": "Apple", "start": 0, "end": 5, "confidence": ...}],
115
+ # "person": [{"text": "Tim Cook", "start": 10, "end": 18, "confidence": ...}],
116
+ # "product": [{"text": "iPhone 15", "start": 29, "end": 38, "confidence": ...}],
117
+ # "location": [{"text": "Cupertino", "start": 42, "end": 51, "confidence": ...}],
118
+ # }
119
+ # }
120
+ ```
121
+
122
+ Returned offsets are half-open character spans into the original string: `text[start:end] == entity["text"]`.
123
+
124
+ Add descriptions when labels are domain-specific:
125
+
126
+ ```python
127
+ result = model.extract_entities(
128
+ "Patient received 400mg ibuprofen for severe headache at 2 PM.",
129
+ {
130
+ "medication": "Names of drugs or pharmaceutical substances",
131
+ "dosage": "Amounts such as 400mg, 2 tablets, or 5ml",
132
+ "symptom": "Reported symptoms or conditions",
133
+ "time": "Clock times or relative times",
134
+ },
135
+ include_spans=True,
136
+ )
137
+ ```
138
+
139
+ ### Text classification
140
+
141
+ Independent per-task decoding with `classify_text`:
142
+
143
+ ```python
144
+ result = model.classify_text(
145
+ "This laptop has amazing performance but terrible battery life!",
146
+ {"sentiment": ["positive", "negative", "neutral"]},
147
+ )
148
+ print(result)
149
+ # {"sentiment": "negative"}
150
+
151
+ result = model.classify_text(
152
+ "Great camera quality, decent performance, but poor battery life.",
153
+ {
154
+ "aspects": {
155
+ "labels": ["camera", "performance", "battery", "display", "price"],
156
+ "multi_label": True,
157
+ "cls_threshold": 0.4,
158
+ }
159
+ },
160
+ )
161
+ print(result)
162
+ # {"aspects": ["camera", "performance", "battery"]}
163
+ ```
164
+
165
+ ### Constrained classification
166
+
167
+ Use `gliner2.classification.Classifier` when labels on one task legally constrain another. `classify_text` will not enforce those rules.
168
+
169
+ ```python
170
+ from gliner2.classification import (
171
+ Classifier,
172
+ ClassificationSchema,
173
+ ClassificationConfig,
174
+ )
175
+ from gliner2.classification import constraints as C
176
+
177
+ clf = Classifier.from_pretrained("fastino/gliner2.5-small-v1")
178
+
179
+ schema = (
180
+ ClassificationSchema()
181
+ .single("intent", ["read", "write", "delete"])
182
+ .multi("effects", ["read_only", "create", "modify", "delete"], min_labels=1)
183
+ .constrain(
184
+ C.implies(("intent", "delete"), ("effects", "delete")),
185
+ C.excludes(("intent", "read"), ("effects", "delete")),
186
+ )
187
+ )
188
+
189
+ result = clf.classify("Delete the temporary file from /tmp", schema)
190
+ print(result.value("intent")) # "delete"
191
+ print(result.value("effects")) # includes "delete"
192
+ ```
193
+
194
+ Prediction knobs belong in `ClassificationConfig` on the call, not in `from_pretrained`:
195
+
196
+ ```python
197
+ result = clf.classify(
198
+ "Preview the report",
199
+ schema,
200
+ config=ClassificationConfig(decoder="beam", beam_size=16),
201
+ )
202
+ ```
203
+
204
+ ### Relation extraction
205
+
206
+ This checkpoint was trained with `enable_relations=True`. Independent decoding:
207
+
208
+ ```python
209
+ text = "Alice works for Acme in Paris."
210
+ result = model.extract_relations(
211
+ text,
212
+ ["works_for", "located_in"],
213
+ include_spans=True,
214
+ include_confidence=True,
215
+ )
216
+ ```
217
+
218
+ Or through a schema:
219
+
220
+ ```python
221
+ schema = model.create_schema().relations(
222
+ {"works_for": {"threshold": 0.6}, "located_in": {"threshold": 0.6}}
223
+ )
224
+ result = model.extract(text, schema, include_spans=True)
225
+ ```
226
+
227
+ Independent extraction does **not** guarantee that `works_for` heads are people and tails are organizations.
228
+
229
+ ### Joint information extraction
230
+
231
+ `JointIE` scores mention and relation candidates, then searches a globally consistent graph with typed endpoints and uniqueness constraints.
232
+
233
+ ```python
234
+ from gliner2.joint_ie import JointIE, JointIEConfig
235
+
236
+ joint = JointIE.from_pretrained("fastino/gliner2.5-small-v1")
237
+
238
+ schema = (
239
+ joint.create_schema()
240
+ .entities(["person", "organization", "location"])
241
+ .relation("works_for", "person", "organization", unique_head=True)
242
+ .relation("located_in", "organization", "location")
243
+ .no_self_loops()
244
+ )
245
+
246
+ result = joint.extract(
247
+ "Alice works for Acme in Paris. Bob joined Acme last year.",
248
+ schema,
249
+ config=JointIEConfig(optimizer="beam", beam_size=32),
250
+ )
251
+
252
+ print(result.feasible)
253
+ print(result.to_dict())
254
+ # entities have ids (e1, e2, ...); relations refer to those ids
255
+ ```
256
+
257
+ Always check `result.feasible`. `False` means the hard constraints could not be satisfied (distinct from “the text contains no facts”).
258
+
259
+ ```python
260
+ for rel in result.relations:
261
+ head = result.entity(rel.head)
262
+ tail = result.entity(rel.tail)
263
+ print(f"{head.text} -{rel.type}-> {tail.text}")
264
+ ```
265
+
266
+ ### Span attributes: people with sentiment
267
+
268
+ Attributes are **span-conditioned**. The model finds entities first, then scores attribute labels at those exact spans. They are not extra entity types and they are not document-level classification.
269
+
270
+ ```python
271
+ from gliner2 import AutoExtractor, AttributeGroup
272
+
273
+ model = AutoExtractor.from_pretrained("fastino/gliner2.5-small-v1")
274
+
275
+ text = (
276
+ "Alice was delighted with the promotion, "
277
+ "but Bob sounded frustrated about the delay."
278
+ )
279
+
280
+ schema = (
281
+ model.create_schema()
282
+ .entities(["person"])
283
+ .entity_attributes({
284
+ "sentiment": AttributeGroup(
285
+ ["positive", "negative", "neutral"],
286
+ applies_to=["person"],
287
+ qualify_labels=True,
288
+ )
289
+ })
290
+ )
291
+
292
+ result = model.extract(
293
+ text,
294
+ schema,
295
+ include_spans=True,
296
+ include_confidence=True,
297
+ )
298
+ print(result)
299
+ # {
300
+ # "entities": {
301
+ # "person": [
302
+ # {
303
+ # "text": "Alice", "start": 0, "end": 5, "confidence": ...,
304
+ # "sentiment": {"label": "positive", "confidence": ...},
305
+ # },
306
+ # {
307
+ # "text": "Bob", "start": ..., "end": ..., "confidence": ...,
308
+ # "sentiment": {"label": "negative", "confidence": ...},
309
+ # },
310
+ # ]
311
+ # }
312
+ # }
313
+ ```
314
+
315
+ `applies_to=["person"]` keeps sentiment off other entity types. `qualify_labels=True` encodes model-facing queries as `sentiment: positive` while returning the short label `positive`.
316
+
317
+ Restrict sentiment to people while still extracting companies:
318
+
319
+ ```python
320
+ schema = (
321
+ model.create_schema()
322
+ .entities(["person", "organization"])
323
+ .entity_attributes({
324
+ "sentiment": AttributeGroup(
325
+ ["positive", "negative", "neutral"],
326
+ applies_to=["person"],
327
+ qualify_labels=True,
328
+ )
329
+ })
330
+ )
331
+ ```
332
+
333
+ Organization spans have no `sentiment` field. Person spans do.
334
+
335
+ ### Structured records
336
+
337
+ Record mode keeps instance identity (who bought what) instead of flattening fields into unrelated lists. Enable `natural` mode with an anchor field:
338
+
339
+ ```python
340
+ schema = (
341
+ model.create_schema()
342
+ .structure("purchase", mode="natural", anchor="buyer")
343
+ .field("buyer", dtype="str", cardinality="required_one")
344
+ .field("item", dtype="str", cardinality="required_one")
345
+ )
346
+
347
+ result = model.extract(
348
+ "Alice bought apples and Bob bought oranges.",
349
+ schema,
350
+ )
351
+ # {
352
+ # "purchase": [
353
+ # {"buyer": "Alice", "item": "apples"},
354
+ # {"buyer": "Bob", "item": "oranges"},
355
+ # ]
356
+ # }
357
+ ```
358
+
359
+ This checkpoint was trained with `enable_records=True`.
360
+
361
+ ### Task combination
362
+
363
+ Compose entities, span attributes, classification, relations, and structures in **one** `extract` call:
364
+
365
+ ```python
366
+ from gliner2 import AttributeGroup
367
+
368
+ schema = (
369
+ model.create_schema()
370
+ .entities({
371
+ "person": "Named people",
372
+ "organization": "Companies or teams",
373
+ "product": "Named products or services",
374
+ })
375
+ .entity_attributes({
376
+ "sentiment": AttributeGroup(
377
+ ["positive", "negative", "neutral"],
378
+ applies_to=["person"],
379
+ qualify_labels=True,
380
+ )
381
+ })
382
+ .classification("topic", ["technology", "business", "sports", "politics"])
383
+ .relations(["works_for", "announced"])
384
+ .structure("announcement", mode="natural", anchor="product")
385
+ .field("company", dtype="str")
386
+ .field("product", dtype="str", cardinality="required_one")
387
+ )
388
+
389
+ text = "Apple CEO Tim Cook unveiled the iPhone 15 Pro for $999."
390
+ result = model.extract(text, schema, include_spans=True, include_confidence=True)
391
+ ```
392
+
393
+ Document-level `topic` is independent of per-person `sentiment`.
394
+
395
+ ### Batch inference
396
+
397
+ ```python
398
+ texts = [
399
+ "Google hired Jane Doe in London.",
400
+ "Tesla launched the Model 3 in California.",
401
+ ]
402
+ results = model.batch_extract_entities(
403
+ texts,
404
+ ["company", "person", "product", "location"],
405
+ batch_size=8,
406
+ include_spans=True,
407
+ )
408
+ ```
409
+
410
+ `batch_extract` accepts one schema or a list of schemas (one per document).
411
+
412
+ ### Long documents
413
+
414
+ `extract(...)` with `max_len` **truncates**. Long-context helpers scan overlapping word chunks and remap spans to document offsets.
415
+
416
+ ```python
417
+ result = model.extract_entities_long(
418
+ open("report.txt").read(),
419
+ ["person", "organization", "location"],
420
+ chunk_size=384,
421
+ chunk_overlap=64,
422
+ include_spans=True,
423
+ )
424
+
425
+ result = model.extract_long(long_text, schema, chunk_size=384, chunk_overlap=64)
426
+ ```
427
+
428
+ The same idea applies to `Classifier.classify_long` and `JointIE.extract_long`.
429
+
430
+ Limits:
431
+
432
+ - A span is kept only if its start and end fall in the **same chunk**.
433
+ - A relation is kept only if both endpoints were extracted in the same chunk.
434
+ - Boundary models can represent arbitrarily long spans **inside one encoded window**; they do not stitch a mention whose endpoints never co-occur.
435
+
436
+ ## Model details
437
+
438
+ - **Architecture:** GLiNER2 **boundary** extractor (`BoundaryExtractor`)
439
+ - **Candidate search:** sparse start/end pairing (not a dense `[L, W]` width grid)
440
+ - **Span length:** any length that fits in the encoded window (`max_len=4096`)
441
+ - **Encoder:** `microsoft/deberta-v3-xsmall`
442
+ - **Parameters:** 74M
443
+ - **Weights:** ~296 MB (FP32)
444
+ - **Language:** English
445
+ - **Heads enabled:** classification, records (`enable_records=True`), relations (`enable_relations=True`)
446
+ - **Overlap default:** `flat` (weighted interval scheduling); override per call with `overlap_policy`
447
+ - **Input / output:** text → entities, labels, span attributes, records, and relation edges
448
+
449
+ Do not load this checkpoint with `GLiNER2` / `SpanExtractor`. Those classes expect the legacy span architecture.
450
+
451
+ ## Citation
452
+
453
+ If you use this model, please cite:
454
+
455
+ ```bibtex
456
+ @misc{zaratiana2025gliner2efficientmultitaskinformation,
457
+ title={GLiNER2: An Efficient Multi-Task Information Extraction System with Schema-Driven Interface},
458
+ author={Urchade Zaratiana and Gil Pasternak and Oliver Boyd and George Hurn-Maloney and Ash Lewis},
459
+ year={2025},
460
+ eprint={2507.18546},
461
+ archivePrefix={arXiv},
462
+ primaryClass={cs.CL},
463
+ url={https://arxiv.org/abs/2507.18546},
464
+ }
465
+ ```
466
+
467
+ ## License
468
+
469
+ Apache License 2.0.
470
+
471
+ ## Links
472
+
473
+ - **Repository:** https://github.com/fastino-ai/GLiNER2
474
+ - **Paper:** https://arxiv.org/abs/2507.18546
475
+ - **Docs:** [boundary architecture](https://github.com/fastino-ai/GLiNER2/blob/main/docs/boundary_architecture.md) · [span attributes](https://github.com/fastino-ai/GLiNER2/blob/main/tutorial/13-span_attributes.md) · [constrained classification](https://github.com/fastino-ai/GLiNER2/blob/main/tutorial/14-constrained_classification.md) · [joint IE](https://github.com/fastino-ai/GLiNER2/blob/main/tutorial/15-joint_ie.md)
476
+ - **Organization:** [Fastino AI](https://fastino.ai)