Instructions to use spybyscript/gliner25-base-litert with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- LiteRT
How to use spybyscript/gliner25-base-litert with LiteRT:
# No code snippets available yet for this library. # To use this model, check the repository files and the library's documentation. # Want to help? PRs adding snippets are welcome at: # https://github.com/huggingface/huggingface.js
- GLiNER2
How to use spybyscript/gliner25-base-litert with GLiNER2:
from gliner2 import AutoExtractor extractor = AutoExtractor.from_pretrained("spybyscript/gliner25-base-litert") # Extract entities text = "Apple CEO Tim Cook announced iPhone 15 in Cupertino yesterday." result = extractor.extract_entities(text, ["company", "person", "product", "location"]) print(result) - Notebooks
- Google Colab
- Kaggle
Every trained head: explicit spans, relations and records; reference runtime; phone results
Browse files- NOTICE.txt +1 -1
- README.md +117 -35
- SHA256SUMS +12 -3
- config.json +327 -7
- gliner2_base_explicit_fp32.tflite +3 -0
- gliner2_base_record_anchorless_fp32.tflite +3 -0
- gliner2_base_record_assignment_fp32.tflite +3 -0
- gliner2_base_relation_fp32.tflite +3 -0
- runtime/litert_boundary_runtime.py +490 -0
- runtime/parity_corpus.json +101 -0
- runtime/requirements.txt +5 -0
- runtime/verify_parity.py +139 -0
- validation-full.json +201 -0
NOTICE.txt
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
GLiNER2.5 base (gliner2.5-base-v1, fastino/gliner2.5-base-v1 @ 1a8bc24e00dc7300b9017c81d63e3dcdabb26596), by Fastino, licensed under the Apache License 2.0 (LICENSE). Its encoder is DeBERTa-v3-base (Microsoft, MIT License).
|
| 2 |
|
| 3 |
-
Changes: converted to LiteRT by spybyscript (encoder wfp16, fp16hw; head and scorer FP32). The head and scorer wrappers and trace patches are adapted from FluidInference's Core ML conversion (https://huggingface.co/FluidInference/gliner2-5-base-coreml @ ca8ddf6c, Apache-2.0); the encoder rewrite is ported from litert-community/GLiNER2.5-Decide-LiteRT @ c687e899 (Apache-2.0). The token embedding table ships as a float16 file outside the graphs. Weights are the original apart from the precision each variant states.
|
| 4 |
|
| 5 |
model-card.md is the original model card; README.md describes this conversion.
|
|
|
|
| 1 |
GLiNER2.5 base (gliner2.5-base-v1, fastino/gliner2.5-base-v1 @ 1a8bc24e00dc7300b9017c81d63e3dcdabb26596), by Fastino, licensed under the Apache License 2.0 (LICENSE). Its encoder is DeBERTa-v3-base (Microsoft, MIT License).
|
| 2 |
|
| 3 |
+
Changes: converted to LiteRT by spybyscript (encoder wfp16, fp16hw; head and scorer FP32). The head and scorer wrappers and trace patches are adapted from FluidInference's Core ML conversion (https://huggingface.co/FluidInference/gliner2-5-base-coreml @ ca8ddf6c, Apache-2.0); the encoder rewrite is ported from litert-community/GLiNER2.5-Decide-LiteRT @ c687e899 (Apache-2.0). The token embedding table ships as a float16 file outside the graphs. Weights are the original apart from the precision each variant states. Every trained module is exported: the encoder, the boundary head with the classifier, the candidate scorer, the explicit-span scorer, the relation scorer and the record decoder; the wrappers for the last three are adapted from FluidInference's extraction_export.py. runtime/litert_boundary_runtime.py is ported from FluidInference's extraction_runtime.py, preprocessing.py and extraction_pool.py (Apache-2.0).
|
| 4 |
|
| 5 |
model-card.md is the original model card; README.md describes this conversion.
|
README.md
CHANGED
|
@@ -14,44 +14,98 @@ tags:
|
|
| 14 |
|
| 15 |
# GLiNER2.5 base (gliner2.5-base-v1) — LiteRT (Android)
|
| 16 |
|
| 17 |
-
An on-device conversion of gliner2.5-base-v1 for Android's LiteRT
|
|
|
|
|
|
|
| 18 |
|
| 19 |
Converted from [fastino/gliner2.5-base-v1](https://huggingface.co/fastino/gliner2.5-base-v1) at revision `1a8bc24e00dc7300b9017c81d63e3dcdabb26596` (Apache-2.0, by Fastino).
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
its thirteen outputs) and the candidate scorer; the host's pool and decoders between and after them. `config.json` is the full contract.
|
| 23 |
|
| 24 |
| File | Size | Contents |
|
| 25 |
|---|---|---|
|
| 26 |
-
| `gliner2_base_encoder_wfp16.tflite` | 189 MB | encoder
|
| 27 |
-
| `gliner2_base_encoder_fp16hw.tflite` | 189 MB |
|
| 28 |
-
| `gliner2_base_head_fp32.tflite` | 8.5 MB | head,
|
| 29 |
-
| `gliner2_base_scorer_fp32.tflite` | 2.1 MB | scorer
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
| `gliner2_base_embeddings_f16.bin` | 197 MB | float16 [128011, 768], little-endian, row-major |
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
-
|
| 42 |
-
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
##
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
|
| 56 |
2,521 base-v1 calls recorded from the gliner2 2.0.0 library on a synthetic English corpus of short conversational statements
|
| 57 |
(`facts`: structure extraction; `keywords.a` and `keywords.b`: entity extraction with two label sets; `relation`:
|
|
@@ -71,7 +125,34 @@ accelerator the phone uses (Metal here); head and scorer on the CPU. Mac measure
|
|
| 71 |
|
| 72 |
The acceptance gate: per call type, same answers ≥ 95 %, span F1 ≥ 0.95, confidence drift p99 ≤ 0.05. `validation.json` holds every number.
|
| 73 |
|
| 74 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
|
| 76 |
Snapdragon 8 Elite Gen 5 (SM8850), Android 17, LiteRT 2.2.0. The encoder runs wfp16 on 4 CPU threads (XNNPACK weight cache); the head and the scorer run on one thread each:
|
| 77 |
|
|
@@ -81,5 +162,6 @@ Snapdragon 8 Elite Gen 5 (SM8850), Android 17, LiteRT 2.2.0. The encoder runs wf
|
|
| 81 |
|
| 82 |
## Files
|
| 83 |
|
| 84 |
-
`LICENSE` (Apache-2.0), `NOTICE.txt`, `model-card.md` (the original card), `config.json` (contract), `validation.json`
|
| 85 |
-
`SHA256SUMS`, `tokenizer/`, `source/` (the
|
|
|
|
|
|
| 14 |
|
| 15 |
# GLiNER2.5 base (gliner2.5-base-v1) — LiteRT (Android)
|
| 16 |
|
| 17 |
+
An on-device conversion of gliner2.5-base-v1 for Android's LiteRT with every trained head: entities, classification,
|
| 18 |
+
structures, relations and records, decoded by gliner2's own code, validated against the library and measured on phones.
|
| 19 |
+
We know of no other LiteRT conversion of gliner2.5-base-v1.
|
| 20 |
|
| 21 |
Converted from [fastino/gliner2.5-base-v1](https://huggingface.co/fastino/gliner2.5-base-v1) at revision `1a8bc24e00dc7300b9017c81d63e3dcdabb26596` (Apache-2.0, by Fastino).
|
| 22 |
+
The split into graphs follows FluidInference's Core ML port ([gliner2-5-base-coreml](https://huggingface.co/FluidInference/gliner2-5-base-coreml), Apache-2.0),
|
| 23 |
+
with the token table on the host. `config.json` is the full contract.
|
|
|
|
| 24 |
|
| 25 |
| File | Size | Contents |
|
| 26 |
|---|---|---|
|
| 27 |
+
| `gliner2_base_encoder_wfp16.tflite` | 189 MB | encoder (`inputs_embeds`, `attention_mask` → `hidden_states`): FULLY_CONNECTED weights stored float16 behind DEQUANTIZE; FP32 activations, position tables and norms; finite -1e4 mask |
|
| 28 |
+
| `gliner2_base_encoder_fp16hw.tflite` | 189 MB | the same encoder rewritten for FP16 hardware: LayerNorm power-of-two pre-scale (x*2^-k, eps*4^-k), first feed-forward FULLY_CONNECTED split in two halves |
|
| 29 |
+
| `gliner2_base_head_fp32.tflite` | 8.5 MB | boundary head: routed text and query states, boundary marginals, pool projections, null and count heads, classification logits |
|
| 30 |
+
| `gliner2_base_scorer_fp32.tflite` | 2.1 MB | candidate scorer and the record candidate states |
|
| 31 |
+
| `gliner2_base_explicit_fp32.tflite` | 2.1 MB | explicit-span scorer: the boundary proposer's prior and the pair scorer over forced spans (entity attributes, choice fields) |
|
| 32 |
+
| `gliner2_base_relation_fp32.tflite` | 23.7 MB | relation scorer over head/tail pairs |
|
| 33 |
+
| `gliner2_base_record_assignment_fp32.tflite` | 1.2 MB | record decoder: instance-to-field assignment, object and latent-seed scores |
|
| 34 |
+
| `gliner2_base_record_anchorless_fp32.tflite` | 2.9 MB | record decoder: learned instances attending over the field candidates (anchorless records) |
|
| 35 |
| `gliner2_base_embeddings_f16.bin` | 197 MB | float16 [128011, 768], little-endian, row-major |
|
| 36 |
+
| `runtime/` | | `litert_boundary_runtime.py` (the reference runtime), `verify_parity.py`, `parity_corpus.json` |
|
| 37 |
+
|
| 38 |
+
## What it runs
|
| 39 |
+
|
| 40 |
+
`runtime/litert_boundary_runtime.py` is gliner2's BoundaryExtractor with every trained module on LiteRT: the schema
|
| 41 |
+
handling, the candidate pool and the decoders are the library's own code, and no PyTorch weights are loaded. Every
|
| 42 |
+
public call works: `extract`, `extract_entities`, `classify_text`, `extract_json`, `extract_relations`, and their
|
| 43 |
+
`batch_*` and `*_long` forms.
|
| 44 |
+
|
| 45 |
+
- Entities, with label descriptions, thresholds and the null head's abstention; entity attributes.
|
| 46 |
+
- Classification, single- and multi-label.
|
| 47 |
+
- Structures (`extract_json`), with choice fields and the natural, latent and anchorless record modes.
|
| 48 |
+
- Relations.
|
| 49 |
+
|
| 50 |
+
**Fixed sizes.** One request holds at most 128 subword tokens (schema and text), 64 text words, 8 queries (entity
|
| 51 |
+
labels, structure fields, relation roles) and 8 classification choices, with a candidate pool of 192 (the checkpoint's
|
| 52 |
+
own pool size). Relations take up to 4 types and 256 head/tail pairs per request; explicit spans up to 64 per query;
|
| 53 |
+
records up to 8 fields with 192 candidates each. These are FluidInference's bucket sizes. A larger request is refused
|
| 54 |
+
with a ValueError, never truncated: run long text through the `*_long` calls with a chunk that fits, e.g.
|
| 55 |
+
`extract_entities_long(text, labels, chunk_size=40, chunk_overlap=8)`. The original runs with dynamic shapes.
|
| 56 |
+
|
| 57 |
+
**Weights:** only their storage and compute precision change; nothing is retrained or pruned.
|
| 58 |
+
|
| 59 |
+
## Use
|
| 60 |
+
|
| 61 |
+
```python
|
| 62 |
+
# pip install gliner2==2.0.0 torch numpy ai-edge-litert
|
| 63 |
+
import sys; sys.path.insert(0, "runtime")
|
| 64 |
+
from litert_boundary_runtime import LiteRTBoundaryExtractor
|
| 65 |
+
from gliner2 import Schema
|
| 66 |
+
|
| 67 |
+
model = LiteRTBoundaryExtractor(".") # this repository's folder
|
| 68 |
+
model.extract_entities("Alice founded Acme in Toronto in 2020.", ["person", "organization", "city"])
|
| 69 |
+
model.extract_relations("Alice founded Acme in Toronto.", ["founded", "located_in"])
|
| 70 |
+
schema = Schema().entities(["person", "organization"]).relations(["works_for"])
|
| 71 |
+
model.extract("Ravi Shah is an engineer at Tidewater Systems.", schema, include_confidence=True)
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
On a phone, run the same graphs with LiteRT's `CompiledModel` and port the library's decoding steps the runtime uses
|
| 75 |
+
(`config.json` lists every graph's signature).
|
| 76 |
+
|
| 77 |
+
## Validation
|
| 78 |
+
|
| 79 |
+
### Every call type against the library
|
| 80 |
+
|
| 81 |
+
`runtime/verify_parity.py` runs each request through gliner2 with the original weights and through the reference runtime
|
| 82 |
+
(LiteRT 2.2.0 on the CPU, `wfp16` encoder), and compares the structured results. The corpus
|
| 83 |
+
(`runtime/parity_corpus.json`) is 48 short synthetic texts with schema templates for every call type, 296 requests.
|
| 84 |
+
|
| 85 |
+
| Call type | Requests | Identical results | Span F1 (lowest) | Largest confidence difference |
|
| 86 |
+
|---|---|---|---|---|
|
| 87 |
+
| `entities` | 40 | 40 | 1.0 | 0.000228 |
|
| 88 |
+
| `entities_single` | 8 | 8 | 1.0 | 5e-06 |
|
| 89 |
+
| `relations_work` | 24 | 24 | 1.0 | 0.000883 |
|
| 90 |
+
| `record_natural` | 16 | 16 | 1.0 | 0.00013 |
|
| 91 |
+
| `record_latent` | 24 | 24 | 1.0 | 0.000641 |
|
| 92 |
+
| `attributes` | 16 | 16 | 1.0 | 0.000298 |
|
| 93 |
+
| `classification_topic` | 40 | 40 | 1.0 | 0.001305 |
|
| 94 |
+
| `mixed` | 16 | 16 | 1.0 | 0.001358 |
|
| 95 |
+
| `relations_family` | 8 | 8 | 1.0 | 0.00033 |
|
| 96 |
+
| `classification_described` | 24 | 24 | 1.0 | 0.001187 |
|
| 97 |
+
| `record_anchorless` | 8 | 8 | 1.0 | 0.000258 |
|
| 98 |
+
| `structure_event` | 24 | 24 | 1.0 | 0.000897 |
|
| 99 |
+
| `classification_multi` | 16 | 16 | 1.0 | 0.000356 |
|
| 100 |
+
| `entities_described` | 16 | 16 | 1.0 | 0.000411 |
|
| 101 |
+
| `structure_order` | 8 | 8 | 1.0 | 0.001252 |
|
| 102 |
+
| `relations_event` | 8 | 8 | 1.0 | 0.0002 |
|
| 103 |
+
|
| 104 |
+
All 296 of 296 requests give identical results. FluidInference's eleven fixtures (one per schema path: entities, relations,
|
| 105 |
+
the three record modes, attributes, mixed and plain classification, choice fields) also match, 11 of 11, with
|
| 106 |
+
confidences within 0.0004. `validation-full.json` holds the numbers.
|
| 107 |
+
|
| 108 |
+
### The recorded calls (Mac, not a phone)
|
| 109 |
|
| 110 |
2,521 base-v1 calls recorded from the gliner2 2.0.0 library on a synthetic English corpus of short conversational statements
|
| 111 |
(`facts`: structure extraction; `keywords.a` and `keywords.b`: entity extraction with two label sets; `relation`:
|
|
|
|
| 125 |
|
| 126 |
The acceptance gate: per call type, same answers ≥ 95 %, span F1 ≥ 0.95, confidence drift p99 ≤ 0.05. `validation.json` holds every number.
|
| 127 |
|
| 128 |
+
|
| 129 |
+
## On phones
|
| 130 |
+
|
| 131 |
+
### Galaxy S23 Ultra (SM8550, 2026-11-02)
|
| 132 |
+
|
| 133 |
+
Android 16, LiteRT 2.3.2 with `qnn-runtime` 2.50.0; each graph on a request recorded from the corpus, 10 calls after a
|
| 134 |
+
30 s rest, thermal status 0 throughout; 4 CPU threads (XNNPACK). For the GPU the time is the whole call (it runs
|
| 135 |
+
asynchronously).
|
| 136 |
+
|
| 137 |
+
| Graph | CPU | GPU (FP32, low priority) | NPU (compiled on the phone) |
|
| 138 |
+
|---|---|---|---|
|
| 139 |
+
| encoder, 128 tokens | 143 ms | 156 ms, 0.85 GB dirty | 13 ms (`fp16hw`, 5.7 s compile) |
|
| 140 |
+
| head | 1.2 ms | does not compile | 1.2 ms |
|
| 141 |
+
| scorer | 2.9 ms | does not compile | 3.4 ms |
|
| 142 |
+
| explicit spans | 5.6 ms | does not compile | fails to run |
|
| 143 |
+
| relations | 16 ms | does not compile | 2.8 ms |
|
| 144 |
+
| record assignment | 8.9 ms | 21.5 ms | 4.0 ms |
|
| 145 |
+
| anchorless records | 10.8 ms | does not compile | 1.3 ms |
|
| 146 |
+
|
| 147 |
+
- **The encoder is the cost:** a request on the CPU takes about 150 ms (relations add 16 ms); with the encoder on the NPU,
|
| 148 |
+
about 20 ms.
|
| 149 |
+
- **Answers on the phone's accelerators** (32 recorded requests, two per call type; the phone's encoder outputs run
|
| 150 |
+
through the rest of the pipeline): the GPU at FP32 changed none; the NPU changed two, both latent-record requests (span
|
| 151 |
+
F1 0.98, confidence up to 0.28), and none of the other call types.
|
| 152 |
+
- **`fp16hw` on this phone's GPU at FP16 gives non-finite outputs** (LiteRT 2.3.2, Adreno 740): use the CPU, the GPU at
|
| 153 |
+
FP32 or the NPU there.
|
| 154 |
+
|
| 155 |
+
### Galaxy Z Fold 8 (SM8850, 2026-10-29)
|
| 156 |
|
| 157 |
Snapdragon 8 Elite Gen 5 (SM8850), Android 17, LiteRT 2.2.0. The encoder runs wfp16 on 4 CPU threads (XNNPACK weight cache); the head and the scorer run on one thread each:
|
| 158 |
|
|
|
|
| 162 |
|
| 163 |
## Files
|
| 164 |
|
| 165 |
+
`LICENSE` (Apache-2.0), `NOTICE.txt`, `model-card.md` (the original card), `config.json` (contract), `validation.json`
|
| 166 |
+
(the recorded-call validation), `validation-full.json` (every call type), `SHA256SUMS`, `tokenizer/`, `source/` (the
|
| 167 |
+
checkpoint's configs), `runtime/`.
|
SHA256SUMS
CHANGED
|
@@ -1,15 +1,24 @@
|
|
| 1 |
cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30 LICENSE
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
22c2e937cfc17e1ee9c8f3ec6f1449d44dc9230cfcbb8737c9bae8492628a3e8 gliner2_base_embeddings_f16.bin
|
| 6 |
6f5241586176d6b123f75392bedd66a24c63f4105f8a45181dbc02385e95e5bb gliner2_base_encoder_fp16hw.tflite
|
| 7 |
053cc6376843ee052a01f2c78e813e1f273ad73e71b95558a4b29804d6250e3c gliner2_base_encoder_wfp16.tflite
|
|
|
|
| 8 |
1dd500e1c321acd7e84c1bec7fba5afb4211f41081f8f6dc0f8a4a78bffc3bd9 gliner2_base_head_fp32.tflite
|
|
|
|
|
|
|
|
|
|
| 9 |
b828fed73d474ef01f4608f6f6a892cb9a1619c1ca3899f86f6ce33c2ebbc4ee gliner2_base_scorer_fp32.tflite
|
| 10 |
b498995df84d4b6780cc25b2be763dc554eab173fed80134e738b2105be2bce6 model-card.md
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
0eb92d00584d613aab32b2178f84a85176b62c87ae3689ce9084e83f6eba64d1 source/config.json
|
| 12 |
d36a845b9f25dcaf1ec45a1c4bdf65ea4ac20596537e14530ec9f660a63aeca4 source/encoder_config/config.json
|
| 13 |
cbc8ae6037812709c9c26f2a160f8dc48b0440bcb79c8141804259ae2d6adac3 tokenizer/tokenizer.json
|
| 14 |
0bf3ea0873234bd9bfdd3853c440395009ac6365a925b91654daed5396d655e1 tokenizer/tokenizer_config.json
|
|
|
|
| 15 |
fcdbb9325f3a84f338156b04e0def258fd16efe02195ec12bdb96c185cfc253b validation.json
|
|
|
|
| 1 |
cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30 LICENSE
|
| 2 |
+
a4134d01e6b89380d84cfcde424328aaa717edadef0a6958c3129f206d91dc37 NOTICE.txt
|
| 3 |
+
f3d539b48e161356bae4ee2b7acf9e0126a0bce850743cf3179bc84130f6fe13 README.md
|
| 4 |
+
251e71c11dd57b4106d0d5eb102a984abebc5415f4e0eb392a4966e3293b189f config.json
|
| 5 |
22c2e937cfc17e1ee9c8f3ec6f1449d44dc9230cfcbb8737c9bae8492628a3e8 gliner2_base_embeddings_f16.bin
|
| 6 |
6f5241586176d6b123f75392bedd66a24c63f4105f8a45181dbc02385e95e5bb gliner2_base_encoder_fp16hw.tflite
|
| 7 |
053cc6376843ee052a01f2c78e813e1f273ad73e71b95558a4b29804d6250e3c gliner2_base_encoder_wfp16.tflite
|
| 8 |
+
03a08c79fdd6ffc6de3e881af5cea7a369d7b3205017915396b9203d7a982051 gliner2_base_explicit_fp32.tflite
|
| 9 |
1dd500e1c321acd7e84c1bec7fba5afb4211f41081f8f6dc0f8a4a78bffc3bd9 gliner2_base_head_fp32.tflite
|
| 10 |
+
dcc066dce8b721f39f4870459efb0abd3c5366f58498a9ff062c4065f9ee24d3 gliner2_base_record_anchorless_fp32.tflite
|
| 11 |
+
e6169de37236a4d056edefcaabfb1e9bba4df1ee34d45f8d6436dc94e22195f5 gliner2_base_record_assignment_fp32.tflite
|
| 12 |
+
dcf3528768b742dc6328c4441ae58694606a3fe15b3500ab429e13bf1caf4ed3 gliner2_base_relation_fp32.tflite
|
| 13 |
b828fed73d474ef01f4608f6f6a892cb9a1619c1ca3899f86f6ce33c2ebbc4ee gliner2_base_scorer_fp32.tflite
|
| 14 |
b498995df84d4b6780cc25b2be763dc554eab173fed80134e738b2105be2bce6 model-card.md
|
| 15 |
+
14811c155fd1fe6caf235276c83952ec4934225cfcf9eb3765ecf7fa2cef29ab runtime/litert_boundary_runtime.py
|
| 16 |
+
a683dc6408f8ea3ec8fbe7a925f1c4bd61ee387944a5ee4793153bedbaeba057 runtime/parity_corpus.json
|
| 17 |
+
f84dbbdf154d3dbcfdb6e979a4ed8c6d2ab2cded4ed9d6f6990df0828c60d9a5 runtime/requirements.txt
|
| 18 |
+
b530aaa4b5dc9fdddcae4549457b8d0b2eb534044f10a1e2600515e475b9ac52 runtime/verify_parity.py
|
| 19 |
0eb92d00584d613aab32b2178f84a85176b62c87ae3689ce9084e83f6eba64d1 source/config.json
|
| 20 |
d36a845b9f25dcaf1ec45a1c4bdf65ea4ac20596537e14530ec9f660a63aeca4 source/encoder_config/config.json
|
| 21 |
cbc8ae6037812709c9c26f2a160f8dc48b0440bcb79c8141804259ae2d6adac3 tokenizer/tokenizer.json
|
| 22 |
0bf3ea0873234bd9bfdd3853c440395009ac6365a925b91654daed5396d655e1 tokenizer/tokenizer_config.json
|
| 23 |
+
81973c2e176ab0e6e3bd283185f46a2daf73bd83b7dff75aaca9dc1581ee58df validation-full.json
|
| 24 |
fcdbb9325f3a84f338156b04e0def258fd16efe02195ec12bdb96c185cfc253b validation.json
|
config.json
CHANGED
|
@@ -106,7 +106,7 @@
|
|
| 106 |
{
|
| 107 |
"path": "gliner2_base_head_fp32.tflite",
|
| 108 |
"role": "head",
|
| 109 |
-
"about": "the
|
| 110 |
"bytes": 8517024,
|
| 111 |
"signatures": [
|
| 112 |
{
|
|
@@ -291,7 +291,7 @@
|
|
| 291 |
{
|
| 292 |
"path": "gliner2_base_scorer_fp32.tflite",
|
| 293 |
"role": "scorer",
|
| 294 |
-
"about": "the
|
| 295 |
"bytes": 2126928,
|
| 296 |
"signatures": [
|
| 297 |
{
|
|
@@ -424,6 +424,325 @@
|
|
| 424 |
]
|
| 425 |
}
|
| 426 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 427 |
}
|
| 428 |
],
|
| 429 |
"table": {
|
|
@@ -446,16 +765,16 @@
|
|
| 446 |
"choices": 8,
|
| 447 |
"pool": 192,
|
| 448 |
"boundaries": 65,
|
| 449 |
-
"about": "the host pads to T=128 tokens, W=64 words, Q=8 queries, C=8 choices, P=192 candidates
|
| 450 |
},
|
| 451 |
"host": {
|
| 452 |
"steps": "as the gliner2 library does: pack and pad the request, then encoder -> head -> the shared candidate pool on the host -> scorer -> thresholds, flat overlap resolution and spans; entities abstain when sigmoid(null_logits) > 0.5",
|
| 453 |
"changes": [
|
| 454 |
"input_ids no longer enter a graph: the host looks them up in the float16 table into inputs_embeds",
|
| 455 |
"attention_mask is float32 (1.0 / 0.0) instead of int32",
|
| 456 |
-
"
|
| 457 |
-
"the scorer
|
| 458 |
-
"bind inputs by name; signatures: encoder `t128`, head `t128`, scorer `p192`"
|
| 459 |
]
|
| 460 |
},
|
| 461 |
"accelerators": {
|
|
@@ -484,5 +803,6 @@
|
|
| 484 |
},
|
| 485 |
"provenance": {
|
| 486 |
"graphs": "encoder graph code ported from litert-community/GLiNER2.5-Decide-LiteRT (Apache-2.0); head and scorer wrappers adapted from FluidInference/gliner2-5-base-coreml (Apache-2.0)"
|
| 487 |
-
}
|
|
|
|
| 488 |
}
|
|
|
|
| 106 |
{
|
| 107 |
"path": "gliner2_base_head_fp32.tflite",
|
| 108 |
"role": "head",
|
| 109 |
+
"about": "the boundary head after the encoder (FluidInference's Core ML features graph without its encoder): FP32, CPU",
|
| 110 |
"bytes": 8517024,
|
| 111 |
"signatures": [
|
| 112 |
{
|
|
|
|
| 291 |
{
|
| 292 |
"path": "gliner2_base_scorer_fp32.tflite",
|
| 293 |
"role": "scorer",
|
| 294 |
+
"about": "the candidate scorer and the record candidate states (FluidInference's Core ML scorer): FP32, CPU",
|
| 295 |
"bytes": 2126928,
|
| 296 |
"signatures": [
|
| 297 |
{
|
|
|
|
| 424 |
]
|
| 425 |
}
|
| 426 |
]
|
| 427 |
+
},
|
| 428 |
+
{
|
| 429 |
+
"path": "gliner2_base_explicit_fp32.tflite",
|
| 430 |
+
"role": "explicit",
|
| 431 |
+
"about": "explicit-span scorer: the boundary proposer's prior and the pair scorer over up to 64 forced spans per query (attributes, choice fields); FP32, CPU",
|
| 432 |
+
"bytes": 2051844,
|
| 433 |
+
"signatures": [
|
| 434 |
+
{
|
| 435 |
+
"key": "w64_q8_s64",
|
| 436 |
+
"inputs": [
|
| 437 |
+
{
|
| 438 |
+
"name": "text_states",
|
| 439 |
+
"dtype": "float32",
|
| 440 |
+
"shape": [
|
| 441 |
+
1,
|
| 442 |
+
64,
|
| 443 |
+
768
|
| 444 |
+
]
|
| 445 |
+
},
|
| 446 |
+
{
|
| 447 |
+
"name": "text_mask",
|
| 448 |
+
"dtype": "float32",
|
| 449 |
+
"shape": [
|
| 450 |
+
1,
|
| 451 |
+
64
|
| 452 |
+
]
|
| 453 |
+
},
|
| 454 |
+
{
|
| 455 |
+
"name": "query_states",
|
| 456 |
+
"dtype": "float32",
|
| 457 |
+
"shape": [
|
| 458 |
+
1,
|
| 459 |
+
8,
|
| 460 |
+
768
|
| 461 |
+
]
|
| 462 |
+
},
|
| 463 |
+
{
|
| 464 |
+
"name": "query_mask",
|
| 465 |
+
"dtype": "float32",
|
| 466 |
+
"shape": [
|
| 467 |
+
1,
|
| 468 |
+
8
|
| 469 |
+
]
|
| 470 |
+
},
|
| 471 |
+
{
|
| 472 |
+
"name": "boundary_states",
|
| 473 |
+
"dtype": "float32",
|
| 474 |
+
"shape": [
|
| 475 |
+
1,
|
| 476 |
+
65,
|
| 477 |
+
128
|
| 478 |
+
]
|
| 479 |
+
},
|
| 480 |
+
{
|
| 481 |
+
"name": "start_logits",
|
| 482 |
+
"dtype": "float32",
|
| 483 |
+
"shape": [
|
| 484 |
+
1,
|
| 485 |
+
8,
|
| 486 |
+
65
|
| 487 |
+
]
|
| 488 |
+
},
|
| 489 |
+
{
|
| 490 |
+
"name": "end_logits",
|
| 491 |
+
"dtype": "float32",
|
| 492 |
+
"shape": [
|
| 493 |
+
1,
|
| 494 |
+
8,
|
| 495 |
+
65
|
| 496 |
+
]
|
| 497 |
+
},
|
| 498 |
+
{
|
| 499 |
+
"name": "inside_prefix",
|
| 500 |
+
"dtype": "float32",
|
| 501 |
+
"shape": [
|
| 502 |
+
1,
|
| 503 |
+
8,
|
| 504 |
+
65
|
| 505 |
+
]
|
| 506 |
+
},
|
| 507 |
+
{
|
| 508 |
+
"name": "inside_prefix_mean",
|
| 509 |
+
"dtype": "float32",
|
| 510 |
+
"shape": [
|
| 511 |
+
1,
|
| 512 |
+
8,
|
| 513 |
+
1
|
| 514 |
+
]
|
| 515 |
+
},
|
| 516 |
+
{
|
| 517 |
+
"name": "span_indices",
|
| 518 |
+
"dtype": "int32",
|
| 519 |
+
"shape": [
|
| 520 |
+
1,
|
| 521 |
+
8,
|
| 522 |
+
64,
|
| 523 |
+
2
|
| 524 |
+
]
|
| 525 |
+
},
|
| 526 |
+
{
|
| 527 |
+
"name": "span_mask",
|
| 528 |
+
"dtype": "float32",
|
| 529 |
+
"shape": [
|
| 530 |
+
1,
|
| 531 |
+
8,
|
| 532 |
+
64
|
| 533 |
+
]
|
| 534 |
+
}
|
| 535 |
+
],
|
| 536 |
+
"outputs": [
|
| 537 |
+
{
|
| 538 |
+
"name": "span_logits",
|
| 539 |
+
"dtype": "float32",
|
| 540 |
+
"shape": [
|
| 541 |
+
1,
|
| 542 |
+
8,
|
| 543 |
+
64
|
| 544 |
+
]
|
| 545 |
+
}
|
| 546 |
+
]
|
| 547 |
+
}
|
| 548 |
+
]
|
| 549 |
+
},
|
| 550 |
+
{
|
| 551 |
+
"path": "gliner2_base_relation_fp32.tflite",
|
| 552 |
+
"role": "relation",
|
| 553 |
+
"about": "relation scorer over up to 256 head/tail pairs of up to 4 relation types (pairs chosen by gliner2's typed pair generator); FP32, CPU",
|
| 554 |
+
"bytes": 23658524,
|
| 555 |
+
"signatures": [
|
| 556 |
+
{
|
| 557 |
+
"key": "w64_r4_p256",
|
| 558 |
+
"inputs": [
|
| 559 |
+
{
|
| 560 |
+
"name": "text_states",
|
| 561 |
+
"dtype": "float32",
|
| 562 |
+
"shape": [
|
| 563 |
+
1,
|
| 564 |
+
64,
|
| 565 |
+
768
|
| 566 |
+
]
|
| 567 |
+
},
|
| 568 |
+
{
|
| 569 |
+
"name": "text_length",
|
| 570 |
+
"dtype": "int32",
|
| 571 |
+
"shape": [
|
| 572 |
+
1
|
| 573 |
+
]
|
| 574 |
+
},
|
| 575 |
+
{
|
| 576 |
+
"name": "relation_states",
|
| 577 |
+
"dtype": "float32",
|
| 578 |
+
"shape": [
|
| 579 |
+
1,
|
| 580 |
+
4,
|
| 581 |
+
1536
|
| 582 |
+
]
|
| 583 |
+
},
|
| 584 |
+
{
|
| 585 |
+
"name": "batch_index",
|
| 586 |
+
"dtype": "int32",
|
| 587 |
+
"shape": [
|
| 588 |
+
256
|
| 589 |
+
]
|
| 590 |
+
},
|
| 591 |
+
{
|
| 592 |
+
"name": "relation_index",
|
| 593 |
+
"dtype": "int32",
|
| 594 |
+
"shape": [
|
| 595 |
+
256
|
| 596 |
+
]
|
| 597 |
+
},
|
| 598 |
+
{
|
| 599 |
+
"name": "head_start",
|
| 600 |
+
"dtype": "int32",
|
| 601 |
+
"shape": [
|
| 602 |
+
256
|
| 603 |
+
]
|
| 604 |
+
},
|
| 605 |
+
{
|
| 606 |
+
"name": "head_end",
|
| 607 |
+
"dtype": "int32",
|
| 608 |
+
"shape": [
|
| 609 |
+
256
|
| 610 |
+
]
|
| 611 |
+
},
|
| 612 |
+
{
|
| 613 |
+
"name": "tail_start",
|
| 614 |
+
"dtype": "int32",
|
| 615 |
+
"shape": [
|
| 616 |
+
256
|
| 617 |
+
]
|
| 618 |
+
},
|
| 619 |
+
{
|
| 620 |
+
"name": "tail_end",
|
| 621 |
+
"dtype": "int32",
|
| 622 |
+
"shape": [
|
| 623 |
+
256
|
| 624 |
+
]
|
| 625 |
+
},
|
| 626 |
+
{
|
| 627 |
+
"name": "pair_mask",
|
| 628 |
+
"dtype": "float32",
|
| 629 |
+
"shape": [
|
| 630 |
+
256
|
| 631 |
+
]
|
| 632 |
+
}
|
| 633 |
+
],
|
| 634 |
+
"outputs": [
|
| 635 |
+
{
|
| 636 |
+
"name": "relation_logits",
|
| 637 |
+
"dtype": "float32",
|
| 638 |
+
"shape": [
|
| 639 |
+
256
|
| 640 |
+
]
|
| 641 |
+
}
|
| 642 |
+
]
|
| 643 |
+
}
|
| 644 |
+
]
|
| 645 |
+
},
|
| 646 |
+
{
|
| 647 |
+
"path": "gliner2_base_record_assignment_fp32.tflite",
|
| 648 |
+
"role": "record_assignment",
|
| 649 |
+
"about": "record decoder: instance-to-field assignment over 8 fields with up to 192 candidates each, for up to 1536 instances, plus object and latent-seed scores; FP32, CPU",
|
| 650 |
+
"bytes": 1193288,
|
| 651 |
+
"signatures": [
|
| 652 |
+
{
|
| 653 |
+
"key": "f8_c192_i1536",
|
| 654 |
+
"inputs": [
|
| 655 |
+
{
|
| 656 |
+
"name": "instance_states",
|
| 657 |
+
"dtype": "float32",
|
| 658 |
+
"shape": [
|
| 659 |
+
1536,
|
| 660 |
+
768
|
| 661 |
+
]
|
| 662 |
+
},
|
| 663 |
+
{
|
| 664 |
+
"name": "field_queries",
|
| 665 |
+
"dtype": "float32",
|
| 666 |
+
"shape": [
|
| 667 |
+
8,
|
| 668 |
+
768
|
| 669 |
+
]
|
| 670 |
+
},
|
| 671 |
+
{
|
| 672 |
+
"name": "field_candidate_states",
|
| 673 |
+
"dtype": "float32",
|
| 674 |
+
"shape": [
|
| 675 |
+
8,
|
| 676 |
+
192,
|
| 677 |
+
768
|
| 678 |
+
]
|
| 679 |
+
}
|
| 680 |
+
],
|
| 681 |
+
"outputs": [
|
| 682 |
+
{
|
| 683 |
+
"name": "assignment_logits",
|
| 684 |
+
"dtype": "float32",
|
| 685 |
+
"shape": [
|
| 686 |
+
1536,
|
| 687 |
+
8,
|
| 688 |
+
193
|
| 689 |
+
]
|
| 690 |
+
},
|
| 691 |
+
{
|
| 692 |
+
"name": "object_logits",
|
| 693 |
+
"dtype": "float32",
|
| 694 |
+
"shape": [
|
| 695 |
+
1536
|
| 696 |
+
]
|
| 697 |
+
},
|
| 698 |
+
{
|
| 699 |
+
"name": "latent_seed_logits",
|
| 700 |
+
"dtype": "float32",
|
| 701 |
+
"shape": [
|
| 702 |
+
1536
|
| 703 |
+
]
|
| 704 |
+
}
|
| 705 |
+
]
|
| 706 |
+
}
|
| 707 |
+
]
|
| 708 |
+
},
|
| 709 |
+
{
|
| 710 |
+
"path": "gliner2_base_record_anchorless_fp32.tflite",
|
| 711 |
+
"role": "record_anchorless",
|
| 712 |
+
"about": "record decoder: its 32 learned instances attending over up to 1536 field candidates (anchorless records); FP32, CPU",
|
| 713 |
+
"bytes": 2875604,
|
| 714 |
+
"signatures": [
|
| 715 |
+
{
|
| 716 |
+
"key": "c1536",
|
| 717 |
+
"inputs": [
|
| 718 |
+
{
|
| 719 |
+
"name": "context_states",
|
| 720 |
+
"dtype": "float32",
|
| 721 |
+
"shape": [
|
| 722 |
+
1536,
|
| 723 |
+
768
|
| 724 |
+
]
|
| 725 |
+
},
|
| 726 |
+
{
|
| 727 |
+
"name": "context_mask",
|
| 728 |
+
"dtype": "float32",
|
| 729 |
+
"shape": [
|
| 730 |
+
1536
|
| 731 |
+
]
|
| 732 |
+
}
|
| 733 |
+
],
|
| 734 |
+
"outputs": [
|
| 735 |
+
{
|
| 736 |
+
"name": "instance_states",
|
| 737 |
+
"dtype": "float32",
|
| 738 |
+
"shape": [
|
| 739 |
+
32,
|
| 740 |
+
768
|
| 741 |
+
]
|
| 742 |
+
}
|
| 743 |
+
]
|
| 744 |
+
}
|
| 745 |
+
]
|
| 746 |
}
|
| 747 |
],
|
| 748 |
"table": {
|
|
|
|
| 765 |
"choices": 8,
|
| 766 |
"pool": 192,
|
| 767 |
"boundaries": 65,
|
| 768 |
+
"about": "the host pads to T=128 tokens, W=64 words, Q=8 queries, C=8 choices, P=192 candidates and refuses larger requests"
|
| 769 |
},
|
| 770 |
"host": {
|
| 771 |
"steps": "as the gliner2 library does: pack and pad the request, then encoder -> head -> the shared candidate pool on the host -> scorer -> thresholds, flat overlap resolution and spans; entities abstain when sigmoid(null_logits) > 0.5",
|
| 772 |
"changes": [
|
| 773 |
"input_ids no longer enter a graph: the host looks them up in the float16 table into inputs_embeds",
|
| 774 |
"attention_mask is float32 (1.0 / 0.0) instead of int32",
|
| 775 |
+
"FluidInference's features graph is two graphs: encoder (inputs_embeds, attention_mask -> hidden_states) and head (hidden_states plus the six other feature inputs -> the same thirteen outputs)",
|
| 776 |
+
"the scorer, explicit-span, relation and record graphs take FluidInference's inputs and return its outputs, FP32",
|
| 777 |
+
"bind inputs by name; signatures: encoder `t128`, head `t128`, scorer `p192`, explicit `w64_q8_s64`, relation `w64_r4_p256`, record assignment `f8_c192_i1536`, anchorless records `c1536`"
|
| 778 |
]
|
| 779 |
},
|
| 780 |
"accelerators": {
|
|
|
|
| 803 |
},
|
| 804 |
"provenance": {
|
| 805 |
"graphs": "encoder graph code ported from litert-community/GLiNER2.5-Decide-LiteRT (Apache-2.0); head and scorer wrappers adapted from FluidInference/gliner2-5-base-coreml (Apache-2.0)"
|
| 806 |
+
},
|
| 807 |
+
"referenceRuntime": "runtime/litert_boundary_runtime.py (gliner2 2.0.0's BoundaryExtractor over these graphs)"
|
| 808 |
}
|
gliner2_base_explicit_fp32.tflite
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:03a08c79fdd6ffc6de3e881af5cea7a369d7b3205017915396b9203d7a982051
|
| 3 |
+
size 2051844
|
gliner2_base_record_anchorless_fp32.tflite
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:dcc066dce8b721f39f4870459efb0abd3c5366f58498a9ff062c4065f9ee24d3
|
| 3 |
+
size 2875604
|
gliner2_base_record_assignment_fp32.tflite
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e6169de37236a4d056edefcaabfb1e9bba4df1ee34d45f8d6436dc94e22195f5
|
| 3 |
+
size 1193288
|
gliner2_base_relation_fp32.tflite
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:dcf3528768b742dc6328c4441ae58694606a3fe15b3500ab429e13bf1caf4ed3
|
| 3 |
+
size 23658524
|
runtime/litert_boundary_runtime.py
ADDED
|
@@ -0,0 +1,490 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""gliner2.5-base-v1 on LiteRT with every trained head: gliner2's own schema handling and decoders over seven graphs.
|
| 2 |
+
|
| 3 |
+
Ported from FluidInference/gliner2-5-base-coreml@ca8ddf6c (Apache-2.0): extraction_runtime.py (the Core ML runtime),
|
| 4 |
+
preprocessing.py (prepare_extraction) and extraction_pool.py (select_candidates). Changes: LiteRT CompiledModel graphs in
|
| 5 |
+
place of Core ML packages; the features stage is two graphs (the encoder over the host-mapped float16 token table, then
|
| 6 |
+
the head); the bucket sizes come from the graphs.
|
| 7 |
+
|
| 8 |
+
from litert_boundary_runtime import LiteRTBoundaryExtractor
|
| 9 |
+
from gliner2 import Schema
|
| 10 |
+
model = LiteRTBoundaryExtractor("path/to/bundle")
|
| 11 |
+
model.extract("Alice founded Acme in Toronto.", Schema().entities(["person", "organization"]).relations(["founded"]))
|
| 12 |
+
model.extract_relations("Alice founded Acme in Toronto.", ["founded", "located_in"])
|
| 13 |
+
|
| 14 |
+
Every public call of gliner2's extractor works: extract, batch_extract, extract_entities, classify_text, extract_json,
|
| 15 |
+
extract_relations and their batch_* and *_long forms. The graphs have fixed sizes (128 subword tokens, 64 text words,
|
| 16 |
+
8 queries and 8 classification choices per request); a larger request is refused with a ValueError, never truncated.
|
| 17 |
+
For long text use the *_long calls with a chunk_size that fits, e.g. extract_entities_long(text, labels, chunk_size=40,
|
| 18 |
+
chunk_overlap=8).
|
| 19 |
+
|
| 20 |
+
Needs gliner2==2.0.0 (schema, collator and decoders; no PyTorch weights are loaded), torch (the library's tensor
|
| 21 |
+
types), numpy and ai-edge-litert. The bundle folder holds the .tflite graphs, the float16 table, tokenizer/ and
|
| 22 |
+
source/config.json (the checkpoint's configuration).
|
| 23 |
+
"""
|
| 24 |
+
from __future__ import annotations
|
| 25 |
+
|
| 26 |
+
import json
|
| 27 |
+
import math
|
| 28 |
+
from contextvars import ContextVar
|
| 29 |
+
from pathlib import Path
|
| 30 |
+
|
| 31 |
+
import numpy as np
|
| 32 |
+
import torch
|
| 33 |
+
from gliner2.configuration import BoundaryHeadSettings
|
| 34 |
+
from gliner2.models.boundary.constants import MASK_LOGIT
|
| 35 |
+
from gliner2.models.boundary.engine import BoundaryExtractor
|
| 36 |
+
from gliner2.models.boundary.indexing import gather_rows
|
| 37 |
+
from gliner2.models.boundary.pool import PooledCandidates, _deduplicate_pool
|
| 38 |
+
from gliner2.models.boundary.proposal import select_top_boundaries
|
| 39 |
+
from gliner2.models.boundary.records import RecordGroupOutput
|
| 40 |
+
from gliner2.models.boundary.relations import RelationProposalSettings, RelationTypeSpec, TypedRelationPairGenerator
|
| 41 |
+
from gliner2.models.base import load_extractor_tokenizer
|
| 42 |
+
from gliner2.models.outputs import CandidateTensorBatch, ExtractorOutput
|
| 43 |
+
from gliner2.processor import SchemaTransformer
|
| 44 |
+
from gliner2.training.trainer import ExtractorCollator
|
| 45 |
+
|
| 46 |
+
FEATURE_NAMES = ("text_states", "query_states", "boundary_states", "boundary_mask", "start_logits", "end_logits", "inside_prefix",
|
| 47 |
+
"inside_prefix_mean", "pool_start_projection", "pool_end_projection", "null_logits", "count_log_rates",
|
| 48 |
+
"classification_logits")
|
| 49 |
+
HEAD_INPUTS = ("hidden_states", "text_indices", "text_mask", "query_indices", "query_mask", "cls_indices", "cls_mask")
|
| 50 |
+
SCORER_INPUTS = ("text_states", "text_mask", "query_states", "query_mask", "boundary_states", "start_logits", "end_logits",
|
| 51 |
+
"inside_prefix", "inside_prefix_mean", "candidate_indices", "candidate_mask", "candidate_compatibility")
|
| 52 |
+
TABLE = "gliner2_base_embeddings_f16.bin"
|
| 53 |
+
FILES = {
|
| 54 |
+
"head": "gliner2_base_head_fp32.tflite", "scorer": "gliner2_base_scorer_fp32.tflite",
|
| 55 |
+
"explicit": "gliner2_base_explicit_fp32.tflite", "relation": "gliner2_base_relation_fp32.tflite",
|
| 56 |
+
"record_assignment": "gliner2_base_record_assignment_fp32.tflite", "record_anchorless": "gliner2_base_record_anchorless_fp32.tflite",
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
class Graph:
|
| 61 |
+
"""One signature of a LiteRT CompiledModel: run(feed) -> {output name: float32 array}."""
|
| 62 |
+
|
| 63 |
+
def __init__(self, path: Path, signature: str | None = None, threads: int = 4, accelerator: str = "cpu", cache: str = ""):
|
| 64 |
+
from ai_edge_litert.compiled_model import CompiledModel, CpuOptions, GpuOptions, HardwareAccelerator, Options
|
| 65 |
+
if accelerator == "cpu":
|
| 66 |
+
options = Options(HardwareAccelerator.CPU, cpu_options=CpuOptions(num_threads=threads, xnnpack_weight_cache_path=cache))
|
| 67 |
+
else: # "gpu" (FP32) or "gpu-fp16"
|
| 68 |
+
options = Options(HardwareAccelerator.GPU,
|
| 69 |
+
gpu_options=GpuOptions(enforce_f32=accelerator == "gpu", constant_tensor_sharing=True))
|
| 70 |
+
self.model = CompiledModel.from_file(str(path), options=options)
|
| 71 |
+
if signature is None:
|
| 72 |
+
signature = self.model.get_signature_list()[0] if hasattr(self.model, "get_signature_list") else None
|
| 73 |
+
self.index = self.model.get_signature_index(signature) if signature else 0
|
| 74 |
+
sig = self.model.get_signature_by_index(self.index)
|
| 75 |
+
self.signature = signature or sig.get("key")
|
| 76 |
+
self.inputs, self.outputs = sig["inputs"], sig["outputs"]
|
| 77 |
+
self.ins, self.outs = self.model.create_input_buffers(self.index), self.model.create_output_buffers(self.index)
|
| 78 |
+
details = self.model.get_input_tensor_details(self.signature)
|
| 79 |
+
self.dtypes = {k: (np.int32 if "int" in str(v.get("dtype", "")).lower() else np.float32) for k, v in details.items()}
|
| 80 |
+
self.input_shapes = {k: tuple(int(x) for x in v["shape"]) for k, v in details.items()}
|
| 81 |
+
self.shapes = {k: tuple(int(x) for x in v["shape"]) for k, v in self.model.get_output_tensor_details(self.signature).items()}
|
| 82 |
+
|
| 83 |
+
def __call__(self, feed: dict) -> dict:
|
| 84 |
+
for buffer, name in zip(self.ins, self.inputs):
|
| 85 |
+
buffer.write(np.ascontiguousarray(np.asarray(feed[name]), self.dtypes[name]))
|
| 86 |
+
self.model.run_by_index(self.index, self.ins, self.outs)
|
| 87 |
+
return {name: buffer.read(int(np.prod(self.shapes[name])), np.float32).reshape(self.shapes[name])
|
| 88 |
+
for buffer, name in zip(self.outs, self.outputs)}
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def signature_of(path: Path) -> str:
|
| 92 |
+
from ai_edge_litert.compiled_model import CompiledModel
|
| 93 |
+
model = CompiledModel.from_file(str(path))
|
| 94 |
+
key = model.get_signature_by_index(0)["key"]
|
| 95 |
+
model.close()
|
| 96 |
+
return key
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def to_tensor(value):
|
| 100 |
+
return torch.from_numpy(np.asarray(value).copy())
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def padded(value, size):
|
| 104 |
+
if value.shape[0] > size:
|
| 105 |
+
raise ValueError(f"Request exceeds the bucket capacity {size}")
|
| 106 |
+
result = value.new_zeros((size, *value.shape[1:]))
|
| 107 |
+
result[: value.shape[0]] = value
|
| 108 |
+
return result
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def prepare_extraction(processor, text: str, schema, length: int, max_words: int, max_queries: int, max_choices: int):
|
| 112 |
+
"""gliner2's collator on one request, padded to the bucket; refuses requests that do not fit (never truncates)."""
|
| 113 |
+
built = schema.build() if hasattr(schema, "build") else schema
|
| 114 |
+
batch = ExtractorCollator(processor, is_training=False, max_len=None, architecture="boundary")([(text, built)])
|
| 115 |
+
if batch.input_ids.shape[1] > length:
|
| 116 |
+
raise ValueError(f"Schema and text require {batch.input_ids.shape[1]} subwords; bucket holds {length}")
|
| 117 |
+
if batch.text_word_indices.shape[1] > max_words:
|
| 118 |
+
raise ValueError(f"Text requires {batch.text_word_indices.shape[1]} words; bucket holds {max_words}")
|
| 119 |
+
if batch.query_marker_indices.shape[1] > max_queries:
|
| 120 |
+
raise ValueError(f"Schema requires {batch.query_marker_indices.shape[1]} queries; bucket holds {max_queries}")
|
| 121 |
+
if batch.cls_marker_indices.shape[1] > max_choices:
|
| 122 |
+
raise ValueError(f"Schema requires {batch.cls_marker_indices.shape[1]} choices; bucket holds {max_choices}")
|
| 123 |
+
if batch.query_marker_indices.shape[1] == 0 and batch.cls_marker_indices.shape[1] == 0:
|
| 124 |
+
raise ValueError("Schema has no extraction or classification queries")
|
| 125 |
+
|
| 126 |
+
def pad(values, width, fill=0):
|
| 127 |
+
array = values.numpy()
|
| 128 |
+
return np.pad(array, ((0, 0), (0, width - array.shape[1])), constant_values=fill)
|
| 129 |
+
|
| 130 |
+
arrays = {
|
| 131 |
+
"input_ids": pad(batch.input_ids, length, processor.tokenizer.pad_token_id).astype(np.int32),
|
| 132 |
+
"attention_mask": pad(batch.attention_mask, length).astype(np.int32),
|
| 133 |
+
"text_indices": pad(batch.text_word_indices, max_words).astype(np.int32),
|
| 134 |
+
"text_mask": pad(batch.text_word_mask, max_words).astype(np.float32),
|
| 135 |
+
"query_indices": pad(batch.query_marker_indices, max_queries).astype(np.int32),
|
| 136 |
+
"query_mask": pad(batch.query_marker_mask, max_queries).astype(np.float32),
|
| 137 |
+
"cls_indices": pad(batch.cls_marker_indices, max_choices).astype(np.int32),
|
| 138 |
+
"cls_mask": pad(batch.cls_marker_mask, max_choices).astype(np.float32),
|
| 139 |
+
}
|
| 140 |
+
return arrays, batch
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
def select_candidates(start_projection, end_projection, boundary_mask, query_mask, start_logits, end_logits, *, boundary_top_k: int,
|
| 144 |
+
pool_size: int, min_pool_per_query: int) -> PooledCandidates:
|
| 145 |
+
"""gliner2 2.0.0's shared candidate pool (stable ranking and deduplication) from the head graph's projections."""
|
| 146 |
+
batch, n_boundaries, dim = start_projection.shape
|
| 147 |
+
n_queries = query_mask.shape[1]
|
| 148 |
+
floor = torch.full_like(start_logits, MASK_LOGIT)
|
| 149 |
+
valid_boundary = boundary_mask.unsqueeze(1) & query_mask.unsqueeze(-1)
|
| 150 |
+
union_start = torch.where(valid_boundary, start_logits, floor).amax(1)
|
| 151 |
+
union_end = torch.where(valid_boundary, end_logits, floor).amax(1)
|
| 152 |
+
union_valid = boundary_mask & query_mask.any(-1, keepdim=True)
|
| 153 |
+
_, starts, starts_valid = select_top_boundaries(union_start.unsqueeze(1), union_valid.unsqueeze(1), boundary_top_k)
|
| 154 |
+
_, ends, ends_valid = select_top_boundaries(union_end.unsqueeze(1), union_valid.unsqueeze(1), boundary_top_k)
|
| 155 |
+
starts, ends = starts[:, 0], ends[:, 0]
|
| 156 |
+
starts_valid, ends_valid = starts_valid[:, 0], ends_valid[:, 0]
|
| 157 |
+
n_starts, n_ends = starts.shape[1], ends.shape[1]
|
| 158 |
+
pair_start = starts.unsqueeze(-1).expand(batch, n_starts, n_ends).reshape(batch, -1)
|
| 159 |
+
pair_end = ends.unsqueeze(1).expand(batch, n_starts, n_ends).reshape(batch, -1)
|
| 160 |
+
pair_valid = (starts_valid.unsqueeze(-1) & ends_valid.unsqueeze(1) & (ends.unsqueeze(1) > starts.unsqueeze(-1))).reshape(batch, -1)
|
| 161 |
+
compatibility = (gather_rows(start_projection, pair_start) * gather_rows(end_projection, pair_end)).sum(-1) / math.sqrt(dim)
|
| 162 |
+
union_pair_score = (compatibility + union_start.gather(1, pair_start.clamp(0, n_boundaries - 1))
|
| 163 |
+
+ union_end.gather(1, pair_end.clamp(0, n_boundaries - 1)))
|
| 164 |
+
quota = min(min_pool_per_query, pair_start.shape[-1])
|
| 165 |
+
quota_keys = pair_start.new_zeros((batch, 0))
|
| 166 |
+
quota_scores = union_pair_score.new_zeros((batch, 0))
|
| 167 |
+
quota_valid = pair_valid.new_zeros((batch, 0))
|
| 168 |
+
if quota:
|
| 169 |
+
start_idx = pair_start.clamp(0, start_logits.shape[2] - 1).unsqueeze(1).expand(batch, n_queries, -1)
|
| 170 |
+
end_idx = pair_end.clamp(0, end_logits.shape[2] - 1).unsqueeze(1).expand(batch, n_queries, -1)
|
| 171 |
+
per_query = start_logits.gather(2, start_idx) + end_logits.gather(2, end_idx) + compatibility.unsqueeze(1)
|
| 172 |
+
per_query_valid = pair_valid.unsqueeze(1) & query_mask.unsqueeze(-1)
|
| 173 |
+
ranked = torch.argsort(per_query.masked_fill(~per_query_valid, MASK_LOGIT), dim=-1, descending=True, stable=True)[..., :quota]
|
| 174 |
+
quota_start, quota_end = start_idx.gather(-1, ranked), end_idx.gather(-1, ranked)
|
| 175 |
+
quota_valid = per_query_valid.gather(-1, ranked).reshape(batch, -1)
|
| 176 |
+
quota_keys = (quota_start * n_boundaries + quota_end).reshape(batch, -1)
|
| 177 |
+
rank_bonus = torch.arange(quota, 0, -1, device=start_projection.device, dtype=union_pair_score.dtype)
|
| 178 |
+
quota_scores = (union_pair_score.new_full((batch, n_queries, quota), -MASK_LOGIT * 0.5) + rank_bonus.view(1, 1, quota)).reshape(batch, -1)
|
| 179 |
+
all_keys = torch.cat((quota_keys, pair_start * n_boundaries + pair_end), -1)
|
| 180 |
+
all_scores = torch.cat((quota_scores, union_pair_score.detach()), -1)
|
| 181 |
+
all_valid = torch.cat((quota_valid, pair_valid), -1)
|
| 182 |
+
selected_keys, selected_valid = _deduplicate_pool(all_keys, all_scores, all_valid, pool_size, n_boundaries)
|
| 183 |
+
selected_keys = torch.where(selected_valid, selected_keys, torch.zeros_like(selected_keys))
|
| 184 |
+
selected_s = torch.div(selected_keys, n_boundaries, rounding_mode="floor")
|
| 185 |
+
selected_e = selected_keys - selected_s * n_boundaries
|
| 186 |
+
indices = torch.where(selected_valid.unsqueeze(-1), torch.stack((selected_s, selected_e), -1), torch.zeros_like(torch.stack((selected_s, selected_e), -1)))
|
| 187 |
+
selected_compat = (gather_rows(start_projection, selected_s) * gather_rows(end_projection, selected_e)).sum(-1) / math.sqrt(dim)
|
| 188 |
+
selected_score = (selected_compat + union_start.gather(1, selected_s.clamp(0, n_boundaries - 1))
|
| 189 |
+
+ union_end.gather(1, selected_e.clamp(0, n_boundaries - 1))).masked_fill(~selected_valid, MASK_LOGIT)
|
| 190 |
+
selected_compat = torch.where(selected_valid, selected_compat, torch.zeros_like(selected_compat))
|
| 191 |
+
return PooledCandidates(indices=indices, mask=selected_valid, proposal_logits=selected_score, gold_mask=None,
|
| 192 |
+
compat_logits=selected_compat, stats=None)
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
class LiteRTBoundaryHead(torch.nn.Module):
|
| 196 |
+
"""Gives gliner2's decoder the tensors the boundary head would have produced, from the LiteRT stages."""
|
| 197 |
+
|
| 198 |
+
def __init__(self, context: ContextVar, explicit: Graph):
|
| 199 |
+
super().__init__()
|
| 200 |
+
self.context, self.explicit = context, explicit
|
| 201 |
+
self.max_queries, self.max_spans = explicit.input_shapes["span_indices"][1], explicit.input_shapes["span_indices"][2]
|
| 202 |
+
|
| 203 |
+
def forward(self, text, text_mask, query, query_mask, return_candidates=True):
|
| 204 |
+
state = self.context.get()
|
| 205 |
+
features, full, count = state["features"], state["candidates"], query.shape[1]
|
| 206 |
+
selected = CandidateTensorBatch(indices=full.indices[:, :count], proposal_logits=full.proposal_logits[:, :count],
|
| 207 |
+
pair_logits=full.pair_logits[:, :count], valid_mask=full.valid_mask[:, :count],
|
| 208 |
+
query_mask=full.query_mask[:, :count], candidate_states=full.candidate_states[:, :count])
|
| 209 |
+
return ExtractorOutput(candidates=selected if return_candidates else None, start_logits=features["start_logits"][:, :count],
|
| 210 |
+
end_logits=features["end_logits"][:, :count], null_logits=features["null_logits"][:, :count],
|
| 211 |
+
count_log_rates=features["count_log_rates"][:, :count], batch_size=1)
|
| 212 |
+
|
| 213 |
+
def score_explicit_spans(self, text, text_mask, query, query_mask, indices, valid_mask=None):
|
| 214 |
+
state = self.context.get()
|
| 215 |
+
features = state["features"]
|
| 216 |
+
full_queries = features["query_states"][0]
|
| 217 |
+
active = int(state["arrays"]["query_mask"].sum())
|
| 218 |
+
query_count, span_count = query.shape[1], indices.shape[2]
|
| 219 |
+
if query_count > self.max_queries or span_count > self.max_spans:
|
| 220 |
+
raise ValueError("Explicit-span request exceeds the bucket capacity")
|
| 221 |
+
selected = []
|
| 222 |
+
for row in query[0]:
|
| 223 |
+
matches = torch.isclose(full_queries[:active], row, atol=1e-6, rtol=0).all(-1).nonzero(as_tuple=False).flatten()
|
| 224 |
+
if matches.numel() != 1:
|
| 225 |
+
raise ValueError("Explicit-span query cannot be mapped to the encoded schema")
|
| 226 |
+
selected.append(int(matches[0]))
|
| 227 |
+
selection = torch.tensor(selected, dtype=torch.long)
|
| 228 |
+
|
| 229 |
+
def feature(name):
|
| 230 |
+
return padded(features[name][0].index_select(0, selection), self.max_queries).unsqueeze(0)
|
| 231 |
+
|
| 232 |
+
span_indices = torch.zeros(1, self.max_queries, self.max_spans, 2, dtype=torch.int32)
|
| 233 |
+
span_mask = torch.zeros(1, self.max_queries, self.max_spans, dtype=torch.float32)
|
| 234 |
+
span_indices[:, :query_count, :span_count] = indices.int()
|
| 235 |
+
span_mask[:, :query_count, :span_count] = valid_mask.float() if valid_mask is not None else 1.0
|
| 236 |
+
feed = {"text_states": text, "text_mask": text_mask.float(), "query_states": padded(query[0], self.max_queries).unsqueeze(0),
|
| 237 |
+
"query_mask": padded(query_mask[0].float(), self.max_queries).unsqueeze(0), "boundary_states": features["boundary_states"],
|
| 238 |
+
"start_logits": feature("start_logits"), "end_logits": feature("end_logits"), "inside_prefix": feature("inside_prefix"),
|
| 239 |
+
"inside_prefix_mean": feature("inside_prefix_mean"), "span_indices": span_indices, "span_mask": span_mask}
|
| 240 |
+
output = self.explicit({k: v.numpy() for k, v in feed.items()})["span_logits"]
|
| 241 |
+
return to_tensor(output)[:, :query_count, :span_count]
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
class LiteRTRelationScorer(torch.nn.Module):
|
| 245 |
+
"""Runs the relation graph after gliner2's own typed pair selection."""
|
| 246 |
+
|
| 247 |
+
def __init__(self, context: ContextVar, graph: Graph):
|
| 248 |
+
super().__init__()
|
| 249 |
+
self.context, self.graph = context, graph
|
| 250 |
+
self.max_words = graph.input_shapes["text_states"][1]
|
| 251 |
+
self.max_relations = graph.input_shapes["relation_states"][1]
|
| 252 |
+
self.pair_cap = graph.input_shapes["pair_mask"][0]
|
| 253 |
+
|
| 254 |
+
def forward(self, text, relation, candidates, pairs):
|
| 255 |
+
count = len(pairs)
|
| 256 |
+
if text.shape[1] != self.max_words or relation.shape[1] > self.max_relations or count > self.pair_cap:
|
| 257 |
+
raise ValueError("Relation request exceeds the bucket capacity")
|
| 258 |
+
relation_states = torch.zeros(1, self.max_relations, relation.shape[-1], dtype=relation.dtype)
|
| 259 |
+
relation_states[:, : relation.shape[1]] = relation
|
| 260 |
+
text_length = int(self.context.get()["arrays"]["text_mask"].sum())
|
| 261 |
+
feed = {"text_states": text, "text_length": torch.tensor([text_length], dtype=torch.int32), "relation_states": relation_states,
|
| 262 |
+
"batch_index": padded(pairs.batch_index.int(), self.pair_cap), "relation_index": padded(pairs.relation_index.int(), self.pair_cap),
|
| 263 |
+
"head_start": padded(pairs.head_start.int(), self.pair_cap), "head_end": padded(pairs.head_end.int(), self.pair_cap),
|
| 264 |
+
"tail_start": padded(pairs.tail_start.int(), self.pair_cap), "tail_end": padded(pairs.tail_end.int(), self.pair_cap),
|
| 265 |
+
"pair_mask": padded(pairs.pair_mask.float(), self.pair_cap)}
|
| 266 |
+
return to_tensor(self.graph({k: v.numpy() for k, v in feed.items()})["relation_logits"])[:count]
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
class LiteRTRecordHead(torch.nn.Module):
|
| 270 |
+
"""Keeps gliner2's instance ordering while the record decoder's learned layers run on LiteRT."""
|
| 271 |
+
|
| 272 |
+
def __init__(self, assignment: Graph, anchorless: Graph):
|
| 273 |
+
super().__init__()
|
| 274 |
+
self.assignment, self.anchorless = assignment, anchorless
|
| 275 |
+
self.max_instances = assignment.input_shapes["instance_states"][0]
|
| 276 |
+
self.max_fields, self.max_candidates = assignment.input_shapes["field_candidate_states"][:2]
|
| 277 |
+
|
| 278 |
+
def forward_group(self, spec, query_states, candidates, sample_index):
|
| 279 |
+
field_specs = list(spec.fields)
|
| 280 |
+
field_query_ids = [field.query_id for field in field_specs]
|
| 281 |
+
if len(field_specs) > self.max_fields:
|
| 282 |
+
raise ValueError("Record has more fields than the bucket holds")
|
| 283 |
+
if any(q < 0 or q >= query_states.shape[0] for q in field_query_ids):
|
| 284 |
+
raise ValueError("Record field query is outside the encoded schema")
|
| 285 |
+
field_states, field_spans, field_logits, field_masks = [], [], [], []
|
| 286 |
+
for query_id in field_query_ids:
|
| 287 |
+
mask = candidates.valid_mask[sample_index, query_id]
|
| 288 |
+
if int(mask.sum()) > self.max_candidates:
|
| 289 |
+
raise ValueError("Record candidate count exceeds the bucket")
|
| 290 |
+
field_states.append(candidates.candidate_states[sample_index, query_id][mask])
|
| 291 |
+
field_spans.append(candidates.indices[sample_index, query_id][mask])
|
| 292 |
+
field_logits.append(candidates.pair_logits[sample_index, query_id][mask])
|
| 293 |
+
field_masks.append(torch.ones(int(mask.sum()), dtype=torch.bool))
|
| 294 |
+
instance_seed, instance_spans = [], []
|
| 295 |
+
if spec.mode == "natural":
|
| 296 |
+
anchor = field_query_ids.index(spec.anchor_query_id)
|
| 297 |
+
instances = field_states[anchor]
|
| 298 |
+
for index, span in enumerate(field_spans[anchor]):
|
| 299 |
+
instance_seed.append((anchor, index))
|
| 300 |
+
instance_spans.append((int(span[0]), int(span[1])))
|
| 301 |
+
elif spec.mode == "latent":
|
| 302 |
+
instances = torch.cat(field_states, 0) if field_states else query_states.new_zeros((0, query_states.shape[-1]))
|
| 303 |
+
for field_index, spans in enumerate(field_spans):
|
| 304 |
+
for index, span in enumerate(spans):
|
| 305 |
+
instance_seed.append((field_index, index))
|
| 306 |
+
instance_spans.append((int(span[0]), int(span[1])))
|
| 307 |
+
else:
|
| 308 |
+
context = torch.cat(field_states, 0) if field_states else query_states.new_zeros((0, query_states.shape[-1]))
|
| 309 |
+
capacity = self.anchorless.input_shapes["context_mask"][0]
|
| 310 |
+
context_mask = torch.zeros(capacity, dtype=torch.float32)
|
| 311 |
+
context_mask[: context.shape[0]] = 1.0
|
| 312 |
+
out = self.anchorless({"context_states": padded(context, capacity).numpy(), "context_mask": context_mask.numpy()})
|
| 313 |
+
instances = to_tensor(out["instance_states"])
|
| 314 |
+
instance_seed = [None] * instances.shape[0]
|
| 315 |
+
instance_spans = [None] * instances.shape[0]
|
| 316 |
+
count = instances.shape[0]
|
| 317 |
+
if count > self.max_instances:
|
| 318 |
+
raise ValueError("Record instance count exceeds the bucket")
|
| 319 |
+
candidate_states = torch.zeros(self.max_fields, self.max_candidates, query_states.shape[-1])
|
| 320 |
+
for index, states in enumerate(field_states):
|
| 321 |
+
candidate_states[index, : states.shape[0]] = states
|
| 322 |
+
predicted = self.assignment({"instance_states": padded(instances, self.max_instances).numpy(),
|
| 323 |
+
"field_queries": padded(query_states[field_query_ids], self.max_fields).numpy(),
|
| 324 |
+
"field_candidate_states": candidate_states.numpy()})
|
| 325 |
+
assignment = to_tensor(predicted["assignment_logits"])
|
| 326 |
+
assignment_by_field = [assignment[:count, i, : 1 + states.shape[0]] for i, states in enumerate(field_states)]
|
| 327 |
+
if spec.mode == "natural":
|
| 328 |
+
object_logits = field_logits[field_query_ids.index(spec.anchor_query_id)]
|
| 329 |
+
elif spec.mode == "latent":
|
| 330 |
+
object_logits = to_tensor(predicted["latent_seed_logits"])[:count]
|
| 331 |
+
else:
|
| 332 |
+
object_logits = to_tensor(predicted["object_logits"])[:count]
|
| 333 |
+
return RecordGroupOutput(spec=spec, object_logits=object_logits, assign_logits=assignment_by_field, field_query_ids=field_query_ids,
|
| 334 |
+
field_specs=field_specs, field_spans=field_spans, field_cand_mask=field_masks, field_cand_logits=field_logits,
|
| 335 |
+
instance_seed=instance_seed, instance_spans=instance_spans)
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
class LiteRTBoundaryExtractor(BoundaryExtractor):
|
| 339 |
+
"""gliner2's BoundaryExtractor (schemas, candidate selection, decoders) with every trained module on LiteRT."""
|
| 340 |
+
|
| 341 |
+
def __init__(self, bundle: str, encoder: str = "gliner2_base_encoder_wfp16.tflite", accelerator: str = "cpu", threads: int = 4,
|
| 342 |
+
cache: str = ""):
|
| 343 |
+
torch.nn.Module.__init__(self)
|
| 344 |
+
folder = Path(bundle)
|
| 345 |
+
config = json.loads((folder / "source" / "config.json").read_text())
|
| 346 |
+
self.boundary_settings = BoundaryHeadSettings(**config["boundary_head"])
|
| 347 |
+
self.processor = SchemaTransformer(tokenizer=load_extractor_tokenizer(str(folder / "tokenizer")), token_pooling="first")
|
| 348 |
+
self.enable_records = self.boundary_settings.enable_records
|
| 349 |
+
self.enable_relations = self.boundary_settings.enable_relations
|
| 350 |
+
self.strict_extraction = True
|
| 351 |
+
self._device_anchor = torch.nn.Parameter(torch.zeros(1), requires_grad=False) # the runtime reads device and dtype from parameters()
|
| 352 |
+
self._context = ContextVar("gliner2_litert_extraction_context")
|
| 353 |
+
self.encoder_graph = Graph(folder / encoder, signature_of(folder / encoder), threads, accelerator, cache)
|
| 354 |
+
self.head_graph = Graph(folder / FILES["head"], signature_of(folder / FILES["head"]), 1)
|
| 355 |
+
self.scorer_graph = Graph(folder / FILES["scorer"], signature_of(folder / FILES["scorer"]), 1)
|
| 356 |
+
graphs = {k: Graph(folder / FILES[k], signature_of(folder / FILES[k]), 1)
|
| 357 |
+
for k in ("explicit", "relation", "record_assignment", "record_anchorless")}
|
| 358 |
+
self.length = self.encoder_graph.input_shapes["attention_mask"][1]
|
| 359 |
+
self.max_words = self.head_graph.input_shapes["text_indices"][1]
|
| 360 |
+
self.max_queries = self.head_graph.input_shapes["query_indices"][1]
|
| 361 |
+
self.max_choices = self.head_graph.input_shapes["cls_indices"][1]
|
| 362 |
+
hidden = self.encoder_graph.input_shapes["inputs_embeds"][2]
|
| 363 |
+
rows = (folder / TABLE).stat().st_size // (2 * hidden)
|
| 364 |
+
self.table = np.memmap(folder / TABLE, dtype="<f2", mode="r", shape=(rows, hidden))
|
| 365 |
+
self.boundary_head = LiteRTBoundaryHead(self._context, graphs["explicit"])
|
| 366 |
+
self.relation_scorer = LiteRTRelationScorer(self._context, graphs["relation"])
|
| 367 |
+
self.record_decoder = LiteRTRecordHead(graphs["record_assignment"], graphs["record_anchorless"])
|
| 368 |
+
self.relation_pair_generator = TypedRelationPairGenerator(RelationProposalSettings(
|
| 369 |
+
heads_per_relation=self.boundary_settings.relation_heads_per_type, tails_per_relation=self.boundary_settings.relation_tails_per_type,
|
| 370 |
+
pair_cap=self.boundary_settings.relation_pair_cap, argument_threshold=self.boundary_settings.relation_argument_proposal_threshold))
|
| 371 |
+
|
| 372 |
+
def features(self, arrays: dict) -> dict:
|
| 373 |
+
ids = arrays["input_ids"].reshape(-1).astype(np.int64)
|
| 374 |
+
hidden = self.encoder_graph({"inputs_embeds": self.table[ids].astype(np.float32)[None],
|
| 375 |
+
"attention_mask": arrays["attention_mask"].astype(np.float32).reshape(1, -1)})["hidden_states"]
|
| 376 |
+
feed = {k: arrays[k] for k in HEAD_INPUTS if k != "hidden_states"}
|
| 377 |
+
feed["hidden_states"] = hidden
|
| 378 |
+
return {name: to_tensor(value) for name, value in self.head_graph(feed).items()}
|
| 379 |
+
|
| 380 |
+
def _encode_core(self, batch):
|
| 381 |
+
state = self._context.get()
|
| 382 |
+
features = state["features"]
|
| 383 |
+
query_mask = to_tensor(state["arrays"]["query_mask"]).bool()
|
| 384 |
+
query_count = int(query_mask.sum())
|
| 385 |
+
query_states = features["query_states"][:, :query_count]
|
| 386 |
+
query_mask = query_mask[:, :query_count]
|
| 387 |
+
text_mask = to_tensor(state["arrays"]["text_mask"]).bool()
|
| 388 |
+
ext_specs, cls_specs, rel_specs, word_offsets = [], [], [], []
|
| 389 |
+
for sample in range(len(batch)):
|
| 390 |
+
specs = [{"group_index": q.task_index, "field_index": q.role_index, "task_type": q.task_type, "task_name": q.task_name,
|
| 391 |
+
"field_name": q.role_name} for q in batch.query_layouts[sample].queries]
|
| 392 |
+
ext_specs.append(specs)
|
| 393 |
+
classifications, choice_offset = [], 0
|
| 394 |
+
for group in range(batch.schema_counts[sample]):
|
| 395 |
+
if batch.task_types[sample][group] != "classifications":
|
| 396 |
+
continue
|
| 397 |
+
count = max(len(batch.schema_special_indices[sample][group]) - 1, 0)
|
| 398 |
+
tokens = batch.schema_tokens_list[sample][group]
|
| 399 |
+
if count:
|
| 400 |
+
classifications.append({"group_index": group, "task_name": tokens[2], "schema_tokens": tokens,
|
| 401 |
+
"group_embs": features["classification_logits"][sample, choice_offset: choice_offset + count]})
|
| 402 |
+
choice_offset += count
|
| 403 |
+
cls_specs.append(classifications)
|
| 404 |
+
word_offsets.append(max(int(batch.text_word_counts[sample]) - len(batch.start_mappings[sample]), 0))
|
| 405 |
+
groups = {}
|
| 406 |
+
for query_id, spec in enumerate(specs):
|
| 407 |
+
groups.setdefault(spec["group_index"], []).append(query_id)
|
| 408 |
+
relations = []
|
| 409 |
+
for group, role_ids in groups.items():
|
| 410 |
+
if batch.task_types[sample][group] != "relations" or len(role_ids) < 2:
|
| 411 |
+
continue
|
| 412 |
+
head_id, tail_id = role_ids[:2]
|
| 413 |
+
roles = query_states[sample, [head_id, tail_id]]
|
| 414 |
+
state_vec = torch.cat((roles[0], roles[1]), -1) if self.boundary_settings.directional_relation_states else roles.mean(0)
|
| 415 |
+
relations.append({"group_index": group, "relation_type": specs[head_id]["task_name"],
|
| 416 |
+
"spec": RelationTypeSpec(specs[head_id]["task_name"], head_query_ids=(head_id,), tail_query_ids=(tail_id,)),
|
| 417 |
+
"query_state": state_vec})
|
| 418 |
+
rel_specs.append(relations)
|
| 419 |
+
return {"text_states": features["text_states"], "text_mask": text_mask, "text_lengths": text_mask.sum(-1).long(),
|
| 420 |
+
"query_states": query_states, "query_mask": query_mask, "ext_specs": ext_specs, "cls_specs": cls_specs,
|
| 421 |
+
"rel_specs": rel_specs, "word_offsets": word_offsets}
|
| 422 |
+
|
| 423 |
+
def _extract_classification_result(self, results, schema_name, schema, embs, schema_tokens, temperature=1.0):
|
| 424 |
+
cls_config = self._resolve_classification_config(schema_tokens[2], schema.get("classifications", []))
|
| 425 |
+
if cls_config is None:
|
| 426 |
+
return
|
| 427 |
+
if temperature <= 0:
|
| 428 |
+
raise ValueError("Classification temperature must be positive")
|
| 429 |
+
logits = embs / temperature
|
| 430 |
+
activation, multi_label = cls_config.get("class_act", "auto"), cls_config.get("multi_label", False)
|
| 431 |
+
probabilities = torch.sigmoid(logits) if activation == "sigmoid" or (activation != "softmax" and multi_label) else torch.softmax(logits, dim=-1)
|
| 432 |
+
labels = cls_config["labels"]
|
| 433 |
+
if multi_label:
|
| 434 |
+
threshold = cls_config.get("cls_threshold", 0.5)
|
| 435 |
+
chosen = [(labels[i], float(probabilities[i])) for i in range(len(labels)) if float(probabilities[i]) >= threshold]
|
| 436 |
+
if not chosen:
|
| 437 |
+
best = int(probabilities.argmax())
|
| 438 |
+
chosen = [(labels[best], float(probabilities[best]))]
|
| 439 |
+
results[cls_config["task"]] = chosen
|
| 440 |
+
return
|
| 441 |
+
best = int(probabilities.argmax())
|
| 442 |
+
results[cls_config["task"]] = (labels[best], float(probabilities[best]))
|
| 443 |
+
|
| 444 |
+
def batch_extract(self, texts, schemas, batch_size: int = 8, threshold: float = 0.5, num_workers: int = 0, format_results: bool = True,
|
| 445 |
+
include_confidence: bool = False, include_spans: bool = False, max_len=None, overlap_policy=None):
|
| 446 |
+
"""One request at a time through the fixed-shape graphs (gliner2's batch API, so the *_long calls work too)."""
|
| 447 |
+
if isinstance(schemas, list):
|
| 448 |
+
if len(schemas) != len(texts):
|
| 449 |
+
raise ValueError(f"Schema count ({len(schemas)}) != text count ({len(texts)})")
|
| 450 |
+
schema_list = schemas
|
| 451 |
+
else:
|
| 452 |
+
schema_list = [schemas] * len(texts)
|
| 453 |
+
return [self.extract(text, schema, threshold=threshold, format_results=format_results, include_confidence=include_confidence,
|
| 454 |
+
include_spans=include_spans, overlap_policy=overlap_policy) for text, schema in zip(texts, schema_list)]
|
| 455 |
+
|
| 456 |
+
def extract(self, text: str, schema, threshold: float = 0.5, format_results: bool = True, include_confidence: bool = False,
|
| 457 |
+
include_spans: bool = False, max_len=None, overlap_policy=None):
|
| 458 |
+
schema_dicts, metadata_list = self._build_schema_dicts_and_metadata([schema])
|
| 459 |
+
if overlap_policy is not None:
|
| 460 |
+
metadata_list[0]["_overlap_policy"] = self._resolved_overlap_policy(overlap_policy)
|
| 461 |
+
arrays, batch = prepare_extraction(self.processor, text, schema_dicts[0], self.length, self.max_words, self.max_queries, self.max_choices)
|
| 462 |
+
features = self.features(arrays)
|
| 463 |
+
query_mask = to_tensor(arrays["query_mask"]).bool()
|
| 464 |
+
candidates = None
|
| 465 |
+
if bool(query_mask.any()):
|
| 466 |
+
head = self.boundary_settings
|
| 467 |
+
pool = select_candidates(features["pool_start_projection"], features["pool_end_projection"], features["boundary_mask"].bool(),
|
| 468 |
+
query_mask, features["start_logits"], features["end_logits"], boundary_top_k=head.pool_boundary_top_k,
|
| 469 |
+
pool_size=head.pool_size, min_pool_per_query=head.min_pool_per_query)
|
| 470 |
+
feed = {"text_states": features["text_states"], "text_mask": to_tensor(arrays["text_mask"]), "query_states": features["query_states"],
|
| 471 |
+
"query_mask": to_tensor(arrays["query_mask"]), "boundary_states": features["boundary_states"],
|
| 472 |
+
"start_logits": features["start_logits"], "end_logits": features["end_logits"], "inside_prefix": features["inside_prefix"],
|
| 473 |
+
"inside_prefix_mean": features["inside_prefix_mean"], "candidate_indices": pool.indices.int(),
|
| 474 |
+
"candidate_mask": pool.mask.float(), "candidate_compatibility": pool.compat_logits}
|
| 475 |
+
scored = self.scorer_graph({k: v.numpy() for k, v in feed.items()})
|
| 476 |
+
q = self.max_queries
|
| 477 |
+
candidates = CandidateTensorBatch(indices=pool.indices.unsqueeze(1).expand(1, q, -1, 2),
|
| 478 |
+
proposal_logits=pool.proposal_logits.unsqueeze(1).expand(1, q, -1),
|
| 479 |
+
pair_logits=to_tensor(scored["pair_logits"]), valid_mask=pool.mask.unsqueeze(1).expand(1, q, -1),
|
| 480 |
+
query_mask=query_mask,
|
| 481 |
+
candidate_states=to_tensor(scored["candidate_states"]).unsqueeze(1).expand(1, q, -1, -1))
|
| 482 |
+
token = self._context.set({"arrays": arrays, "features": features, "candidates": candidates})
|
| 483 |
+
try:
|
| 484 |
+
raw = self._extract_from_batch(batch, threshold, metadata_list, include_confidence, include_spans)[0]
|
| 485 |
+
if format_results:
|
| 486 |
+
return self.format_results(raw, include_confidence, metadata_list[0].get("relation_order", []),
|
| 487 |
+
metadata_list[0].get("classification_tasks", []))
|
| 488 |
+
return raw
|
| 489 |
+
finally:
|
| 490 |
+
self._context.reset(token)
|
runtime/parity_corpus.json
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"about": "Parity corpus for LiteRT ports of GLiNER2 checkpoints: short general-domain texts with invented people and companies, and schema templates covering every call type gliner2 2.0.0 offers (entities with and without descriptions, relations, plain structures, choice fields, natural/latent/anchorless records, entity attributes, single- and multi-label classification, mixed schemas). Only agreement with the library is measured; there are no gold labels. Each text stays under 40 words so a call fits a 128-token bucket with its schema.",
|
| 3 |
+
"templates": {
|
| 4 |
+
"entities": {"entities": ["person", "organization", "location", "date"]},
|
| 5 |
+
"entities_described": {"entities": {"person": "a named individual", "product": "a named product, device or service", "money": "an amount of money"}},
|
| 6 |
+
"entities_single": {"entities": ["person", "organization"], "dtype": "str"},
|
| 7 |
+
"relations_work": {"relations": ["works_for", "founded", "located_in"]},
|
| 8 |
+
"relations_family": {"relations": {"parent_of": "the head is a parent of the tail", "married_to": "the two people are married"}},
|
| 9 |
+
"relations_event": {"relations": ["won", "held_in"]},
|
| 10 |
+
"structure_event": {"structures": [{"name": "event", "fields": [{"name": "name", "dtype": "str"}, {"name": "date", "dtype": "str"}, {"name": "place", "dtype": "str"}]}]},
|
| 11 |
+
"structure_order": {"structures": [{"name": "order", "fields": [{"name": "item", "dtype": "str"}, {"name": "category", "dtype": "str", "choices": ["electronics", "clothing", "furniture", "food"]}, {"name": "quantity", "dtype": "str"}]}]},
|
| 12 |
+
"record_natural": {"structures": [{"name": "employment", "mode": "natural", "anchor": "person", "fields": [{"name": "person", "dtype": "str"}, {"name": "company", "dtype": "str"}, {"name": "role", "dtype": "str"}]}]},
|
| 13 |
+
"record_latent": {"structures": [{"name": "employment", "mode": "latent", "fields": [{"name": "person", "dtype": "str"}, {"name": "company", "dtype": "str"}]}]},
|
| 14 |
+
"record_anchorless": {"structures": [{"name": "trip", "mode": "anchorless", "fields": [{"name": "traveler", "dtype": "str"}, {"name": "destination", "dtype": "str"}]}]},
|
| 15 |
+
"attributes": {"entities": ["person", "organization"], "attributes": {"role": {"labels": ["founder", "employee", "customer"]}}},
|
| 16 |
+
"classification_topic": {"classifications": [{"task": "topic", "labels": ["business", "family", "travel", "shopping", "science", "sports"]}]},
|
| 17 |
+
"classification_multi": {"classifications": [{"task": "tags", "labels": ["money", "people", "places", "dates", "products"], "multi_label": true}]},
|
| 18 |
+
"classification_described": {"classifications": [{"task": "tone", "labels": {"positive": "good news or praise", "negative": "bad news or complaints", "neutral": "plain facts"}}]},
|
| 19 |
+
"mixed": {"entities": ["person", "organization"], "relations": ["works_for"], "classifications": [{"task": "sentiment", "labels": ["positive", "negative", "neutral"]}]}
|
| 20 |
+
},
|
| 21 |
+
"groups": {
|
| 22 |
+
"business": {
|
| 23 |
+
"templates": ["entities", "entities_single", "relations_work", "record_natural", "record_latent", "attributes", "classification_topic", "mixed"],
|
| 24 |
+
"texts": [
|
| 25 |
+
"Nora Lindqvist founded Brightwell Labs in Oslo in 2019 and still runs it as chief executive.",
|
| 26 |
+
"Tomas Ferreira joined Halcyon Freight in March as head of logistics, moving from a rival in Lisbon.",
|
| 27 |
+
"Priya Raman works at Cobalt Analytics in Pune, where she leads a team of twelve engineers.",
|
| 28 |
+
"Marco Bellini and Ines Duarte started Verde Foods together; Marco handles sales while Ines manages the kitchen in Porto.",
|
| 29 |
+
"Westgate Mills hired Daniel Okafor as an accountant last week, and Sara Klein left the company after ten years.",
|
| 30 |
+
"Kwame Mensah is the founder of Sunline Solar, a startup based in Accra that installs rooftop panels.",
|
| 31 |
+
"After the merger, Lena Hoffmann became chief financial officer of Northpeak Group in Munich.",
|
| 32 |
+
"Ravi Shah, an engineer at Tidewater Systems, presented the new billing platform to customers in Boston on Tuesday."
|
| 33 |
+
]
|
| 34 |
+
},
|
| 35 |
+
"family": {
|
| 36 |
+
"templates": ["entities", "relations_family", "record_latent", "classification_topic", "classification_described"],
|
| 37 |
+
"texts": [
|
| 38 |
+
"Helen Ward married Peter Ward in 1998, and their daughter Emma was born two years later in Leeds.",
|
| 39 |
+
"Carlos Mendes is the father of Lucia and Mateo, who both moved to Madrid for university.",
|
| 40 |
+
"My grandmother Rosa raised four children on a farm near Cork before moving to Dublin.",
|
| 41 |
+
"Aiko Tanaka and her husband Kenji celebrated their anniversary in Kyoto with their son Haruto.",
|
| 42 |
+
"Fatima Diallo is the mother of twins, Amina and Yusuf, who turned nine on Sunday.",
|
| 43 |
+
"James Porter's sister, Claire, lives in Toronto with her partner Mark and their dog.",
|
| 44 |
+
"Olga Petrova married Ivan Sokolov last spring; Ivan's brother Dmitri was the best man.",
|
| 45 |
+
"The Nguyen family, Linh, Bao and their son Minh, spent the holidays with relatives in Hanoi."
|
| 46 |
+
]
|
| 47 |
+
},
|
| 48 |
+
"travel": {
|
| 49 |
+
"templates": ["entities", "relations_work", "record_anchorless", "structure_event", "classification_topic", "classification_multi"],
|
| 50 |
+
"texts": [
|
| 51 |
+
"Last July, Elena flew from Rome to Reykjavik and spent a week hiking near Vik.",
|
| 52 |
+
"Ben and Chloe drove from Denver to Santa Fe, stopping overnight in Taos on the way.",
|
| 53 |
+
"Our tour group arrived in Marrakech on Friday and leaves for Fez on Monday morning.",
|
| 54 |
+
"Hugo booked a train from Paris to Geneva for the conference on 14 May.",
|
| 55 |
+
"Mia visited her cousin in Cape Town and then took a ferry to Robben Island.",
|
| 56 |
+
"The museum in Bilbao reopened in April after a two-year renovation led by the city council.",
|
| 57 |
+
"Sam and Priya plan to backpack through Peru, starting in Cusco and ending in Lima.",
|
| 58 |
+
"Grace missed her connection in Frankfurt, so she reached Warsaw a day late."
|
| 59 |
+
]
|
| 60 |
+
},
|
| 61 |
+
"shopping": {
|
| 62 |
+
"templates": ["entities_described", "structure_order", "record_natural", "classification_multi", "classification_described", "attributes"],
|
| 63 |
+
"texts": [
|
| 64 |
+
"I ordered two oak chairs from Hearthwood for 180 dollars, but only one arrived.",
|
| 65 |
+
"Lucas bought a Zenith X2 laptop and a wireless mouse at Volt Electronics for 1,240 euros.",
|
| 66 |
+
"Please send three boxes of green tea and a ceramic teapot to the Seattle store.",
|
| 67 |
+
"The customer returned a wool coat to Northline Apparel and asked for a refund of 95 pounds.",
|
| 68 |
+
"Amara paid 12 dollars for a loaf of sourdough and a jar of honey at the Saturday market.",
|
| 69 |
+
"Our office ordered forty desk lamps from Brightspace, and the invoice came to 2,000 dollars.",
|
| 70 |
+
"Jonah's new Aurora headphones broke after a week, and the shop offered him a replacement.",
|
| 71 |
+
"Ten kilograms of rice and five bags of lentils were delivered to the community kitchen."
|
| 72 |
+
]
|
| 73 |
+
},
|
| 74 |
+
"science": {
|
| 75 |
+
"templates": ["entities", "entities_described", "relations_work", "structure_event", "classification_topic", "classification_described"],
|
| 76 |
+
"texts": [
|
| 77 |
+
"Dr. Yusuf Kaya and his team at the Halden Institute published a study on coral bleaching in June.",
|
| 78 |
+
"The Meridian telescope in Chile recorded a supernova on 3 February, according to astronomer Lucia Ortiz.",
|
| 79 |
+
"Researchers at Brookfield University tested a new battery that charges in six minutes.",
|
| 80 |
+
"Marta Silva, a biologist at the Lisbon Aquarium, tagged forty sea turtles last summer.",
|
| 81 |
+
"The probe Helios-4 entered orbit around Mars after a seven-month journey from Earth.",
|
| 82 |
+
"Engineers at Polaris Robotics in Seoul unveiled a robot that sorts recycling by material.",
|
| 83 |
+
"A trial run by Kestrel Health in Nairobi found the vaccine safe for children over five.",
|
| 84 |
+
"Professor Anil Mehta left Crestview College to lead the climate lab at Northgate University."
|
| 85 |
+
]
|
| 86 |
+
},
|
| 87 |
+
"sports": {
|
| 88 |
+
"templates": ["entities", "relations_event", "structure_event", "record_latent", "classification_topic", "mixed"],
|
| 89 |
+
"texts": [
|
| 90 |
+
"Sofia Marin won the Vienna Marathon in April, finishing ahead of last year's champion Ruth Abebe.",
|
| 91 |
+
"The Harbor City Hawks beat the Ridgeview Bears 3 to 1 in the final held in Glasgow.",
|
| 92 |
+
"Coach Andre Lopes signed a new contract with Atletico Verde until 2028.",
|
| 93 |
+
"Kenji Watanabe took gold in the 200 metres at the championships in Nagoya on Saturday.",
|
| 94 |
+
"The tennis open in Melbourne ended with Clara Weiss lifting her first trophy.",
|
| 95 |
+
"Liam O'Brien scored twice as Dunmore Rovers won the cup at their home ground.",
|
| 96 |
+
"The cycling race from Lyon to Annecy was won by Pieter de Vries after a late sprint.",
|
| 97 |
+
"Amira Haddad set a national record in the long jump at the meet in Doha."
|
| 98 |
+
]
|
| 99 |
+
}
|
| 100 |
+
}
|
| 101 |
+
}
|
runtime/requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gliner2==2.0.0
|
| 2 |
+
torch
|
| 3 |
+
numpy
|
| 4 |
+
ai-edge-litert>=2.2.0
|
| 5 |
+
huggingface_hub # verify_parity.py
|
runtime/verify_parity.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Check a LiteRT GLiNER2 port against the original checkpoint on the bundled parity corpus.
|
| 3 |
+
|
| 4 |
+
pip install gliner2==2.0.0 torch numpy ai-edge-litert
|
| 5 |
+
python runtime/verify_parity.py --bundle . # from the repository folder
|
| 6 |
+
|
| 7 |
+
Runs every corpus request twice, through gliner2 with the original weights (downloaded from Fastino at the pinned
|
| 8 |
+
revision) and through this repository's LiteRT runtime, and prints per call type how many structured results are
|
| 9 |
+
identical, the lowest span F1 and the largest confidence difference. The corpus (runtime/parity_corpus.json) is short
|
| 10 |
+
synthetic texts with schema templates for every call type gliner2 2.0.0 offers; it has no gold labels.
|
| 11 |
+
"""
|
| 12 |
+
from __future__ import annotations
|
| 13 |
+
|
| 14 |
+
import argparse
|
| 15 |
+
import collections
|
| 16 |
+
import json
|
| 17 |
+
import pathlib
|
| 18 |
+
import sys
|
| 19 |
+
|
| 20 |
+
HERE = pathlib.Path(__file__).resolve().parent
|
| 21 |
+
SOURCES = {"boundary": ("fastino/gliner2.5-base-v1", "1a8bc24e00dc7300b9017c81d63e3dcdabb26596"),
|
| 22 |
+
"span": ("fastino/GLiNER2.5-Decide", "bbe10ff77ebb238777c17d3a8ac9260e30929057")}
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def build_schema(t: dict):
|
| 26 |
+
from gliner2 import AttributeGroup, Schema
|
| 27 |
+
s = Schema()
|
| 28 |
+
if "entities" in t:
|
| 29 |
+
s.entities(t["entities"], dtype=t.get("dtype", "list"))
|
| 30 |
+
if "attributes" in t:
|
| 31 |
+
s.entity_attributes({k: AttributeGroup(labels=v["labels"], multi_label=v.get("multi_label", False)) for k, v in t["attributes"].items()})
|
| 32 |
+
if "relations" in t:
|
| 33 |
+
s.relations(t["relations"])
|
| 34 |
+
for c in t.get("classifications", []):
|
| 35 |
+
s.classification(c["task"], c["labels"], multi_label=c.get("multi_label", False))
|
| 36 |
+
for st in t.get("structures", []):
|
| 37 |
+
builder = s.structure(st["name"], mode=st.get("mode"), anchor=st.get("anchor"))
|
| 38 |
+
for f in st["fields"]:
|
| 39 |
+
builder.field(f["name"], dtype=f.get("dtype", "list"), choices=f.get("choices"))
|
| 40 |
+
return s
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def without_confidence(value):
|
| 44 |
+
if isinstance(value, dict):
|
| 45 |
+
return {k: without_confidence(v) for k, v in value.items() if k != "confidence"}
|
| 46 |
+
if isinstance(value, list):
|
| 47 |
+
return [without_confidence(v) for v in value]
|
| 48 |
+
return value
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def confidence_errors(reference, actual):
|
| 52 |
+
if isinstance(reference, dict) and isinstance(actual, dict):
|
| 53 |
+
out = []
|
| 54 |
+
for key, value in reference.items():
|
| 55 |
+
if key == "confidence" and key in actual:
|
| 56 |
+
out.append(abs(float(value) - float(actual[key])))
|
| 57 |
+
elif key in actual:
|
| 58 |
+
out.extend(confidence_errors(value, actual[key]))
|
| 59 |
+
return out
|
| 60 |
+
if isinstance(reference, (list, tuple)) and isinstance(actual, (list, tuple)):
|
| 61 |
+
return [e for a, b in zip(reference, actual) for e in confidence_errors(a, b)]
|
| 62 |
+
return []
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def answers(value, path: str = "") -> list[str]:
|
| 66 |
+
out = []
|
| 67 |
+
if isinstance(value, dict):
|
| 68 |
+
if {"text", "start", "end"} <= set(value):
|
| 69 |
+
return [f"{path}|{value['text']}|{value['start']}|{value['end']}"]
|
| 70 |
+
if "head" in value and "tail" in value:
|
| 71 |
+
return [f"{path}|H:{'/'.join(answers(value['head']))}|T:{'/'.join(answers(value['tail']))}"]
|
| 72 |
+
if "label" in value:
|
| 73 |
+
return [f"{path}|{value['label']}"]
|
| 74 |
+
for key, item in value.items():
|
| 75 |
+
if key != "confidence":
|
| 76 |
+
out += answers(item, f"{path}.{key}" if path else key)
|
| 77 |
+
elif isinstance(value, (list, tuple)):
|
| 78 |
+
if len(value) == 2 and isinstance(value[0], str) and isinstance(value[1], float):
|
| 79 |
+
return [f"{path}|{value[0]}"]
|
| 80 |
+
for item in value:
|
| 81 |
+
out += answers(item, path)
|
| 82 |
+
elif value is not None:
|
| 83 |
+
out.append(f"{path}|{value}")
|
| 84 |
+
return out
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def f1(reference: list[str], actual: list[str]) -> float:
|
| 88 |
+
a, b = collections.Counter(reference), collections.Counter(actual)
|
| 89 |
+
if not a and not b:
|
| 90 |
+
return 1.0
|
| 91 |
+
return 2 * sum((a & b).values()) / (sum(a.values()) + sum(b.values()))
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def main() -> int:
|
| 95 |
+
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
| 96 |
+
parser.add_argument("--bundle", default=str(HERE.parent))
|
| 97 |
+
parser.add_argument("--corpus", default=str(HERE / "parity_corpus.json"))
|
| 98 |
+
args = parser.parse_args()
|
| 99 |
+
sys.path.insert(0, str(HERE))
|
| 100 |
+
import torch
|
| 101 |
+
from gliner2 import AutoExtractor
|
| 102 |
+
from huggingface_hub import snapshot_download
|
| 103 |
+
config = json.loads((pathlib.Path(args.bundle) / "source" / "config.json").read_text())
|
| 104 |
+
architecture = config.get("architecture", "span")
|
| 105 |
+
if architecture == "boundary":
|
| 106 |
+
from litert_boundary_runtime import LiteRTBoundaryExtractor as Runtime
|
| 107 |
+
else:
|
| 108 |
+
from litert_span_runtime import LiteRTSpanExtractor as Runtime
|
| 109 |
+
repo, revision = SOURCES[architecture]
|
| 110 |
+
native = AutoExtractor.from_pretrained(snapshot_download(repo, revision=revision), map_location="cpu").eval()
|
| 111 |
+
torch.set_num_threads(4)
|
| 112 |
+
runtime = Runtime(args.bundle)
|
| 113 |
+
corpus = json.loads(pathlib.Path(args.corpus).read_text())
|
| 114 |
+
by_kind = {}
|
| 115 |
+
for group in corpus["groups"].values():
|
| 116 |
+
for text in group["texts"]:
|
| 117 |
+
for kind in group["templates"]:
|
| 118 |
+
schema = build_schema(corpus["templates"][kind])
|
| 119 |
+
expected = native.extract(text, schema, include_confidence=True, include_spans=True)
|
| 120 |
+
k = by_kind.setdefault(kind, {"calls": 0, "identical": 0, "refused": 0, "minSpanF1": 1.0, "maxConfidenceDifference": 0.0})
|
| 121 |
+
k["calls"] += 1
|
| 122 |
+
try:
|
| 123 |
+
actual = runtime.extract(text, schema, include_confidence=True, include_spans=True)
|
| 124 |
+
except ValueError: # larger than the graphs' fixed sizes: refused, never truncated
|
| 125 |
+
k["refused"] += 1
|
| 126 |
+
continue
|
| 127 |
+
k["identical"] += without_confidence(expected) == without_confidence(actual)
|
| 128 |
+
k["minSpanF1"] = round(min(k["minSpanF1"], f1(answers(expected), answers(actual))), 4)
|
| 129 |
+
k["maxConfidenceDifference"] = round(max([k["maxConfidenceDifference"], *confidence_errors(expected, actual)]), 6)
|
| 130 |
+
for kind, k in by_kind.items():
|
| 131 |
+
print(f"{kind:26s} {json.dumps(k)}")
|
| 132 |
+
total = sum(k["calls"] - k["refused"] for k in by_kind.values())
|
| 133 |
+
same = sum(k["identical"] for k in by_kind.values())
|
| 134 |
+
print(f"identical structured results: {same} / {total}")
|
| 135 |
+
return 0 if same == total else 1
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
if __name__ == "__main__":
|
| 139 |
+
sys.exit(main())
|
validation-full.json
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"about": "Every call type against gliner2 2.0.0 with the original weights (runtime/verify_parity.py): LiteRT 2.2.0 on the CPU, wfp16 encoder.",
|
| 3 |
+
"source": "fastino/gliner2.5-base-v1@1a8bc24e00dc7300b9017c81d63e3dcdabb26596",
|
| 4 |
+
"corpus": {
|
| 5 |
+
"file": "runtime/parity_corpus.json",
|
| 6 |
+
"requests": 296,
|
| 7 |
+
"identical": 296,
|
| 8 |
+
"byCallType": {
|
| 9 |
+
"entities": {
|
| 10 |
+
"calls": 40,
|
| 11 |
+
"refused": 0,
|
| 12 |
+
"matched": 40,
|
| 13 |
+
"items": 148,
|
| 14 |
+
"minSpanF1": 1.0,
|
| 15 |
+
"maxConfidenceDifference": 0.000228
|
| 16 |
+
},
|
| 17 |
+
"entities_single": {
|
| 18 |
+
"calls": 8,
|
| 19 |
+
"refused": 0,
|
| 20 |
+
"matched": 8,
|
| 21 |
+
"items": 16,
|
| 22 |
+
"minSpanF1": 1.0,
|
| 23 |
+
"maxConfidenceDifference": 5e-06
|
| 24 |
+
},
|
| 25 |
+
"relations_work": {
|
| 26 |
+
"calls": 24,
|
| 27 |
+
"refused": 0,
|
| 28 |
+
"matched": 24,
|
| 29 |
+
"items": 62,
|
| 30 |
+
"minSpanF1": 1.0,
|
| 31 |
+
"maxConfidenceDifference": 0.000883
|
| 32 |
+
},
|
| 33 |
+
"record_natural": {
|
| 34 |
+
"calls": 16,
|
| 35 |
+
"refused": 0,
|
| 36 |
+
"matched": 16,
|
| 37 |
+
"items": 28,
|
| 38 |
+
"minSpanF1": 1.0,
|
| 39 |
+
"maxConfidenceDifference": 0.00013
|
| 40 |
+
},
|
| 41 |
+
"record_latent": {
|
| 42 |
+
"calls": 24,
|
| 43 |
+
"refused": 0,
|
| 44 |
+
"matched": 24,
|
| 45 |
+
"items": 429,
|
| 46 |
+
"minSpanF1": 1.0,
|
| 47 |
+
"maxConfidenceDifference": 0.000641
|
| 48 |
+
},
|
| 49 |
+
"attributes": {
|
| 50 |
+
"calls": 16,
|
| 51 |
+
"refused": 0,
|
| 52 |
+
"matched": 16,
|
| 53 |
+
"items": 31,
|
| 54 |
+
"minSpanF1": 1.0,
|
| 55 |
+
"maxConfidenceDifference": 0.000298
|
| 56 |
+
},
|
| 57 |
+
"classification_topic": {
|
| 58 |
+
"calls": 40,
|
| 59 |
+
"refused": 0,
|
| 60 |
+
"matched": 40,
|
| 61 |
+
"items": 40,
|
| 62 |
+
"minSpanF1": 1.0,
|
| 63 |
+
"maxConfidenceDifference": 0.001305
|
| 64 |
+
},
|
| 65 |
+
"mixed": {
|
| 66 |
+
"calls": 16,
|
| 67 |
+
"refused": 0,
|
| 68 |
+
"matched": 16,
|
| 69 |
+
"items": 68,
|
| 70 |
+
"minSpanF1": 1.0,
|
| 71 |
+
"maxConfidenceDifference": 0.001358
|
| 72 |
+
},
|
| 73 |
+
"relations_family": {
|
| 74 |
+
"calls": 8,
|
| 75 |
+
"refused": 0,
|
| 76 |
+
"matched": 8,
|
| 77 |
+
"items": 15,
|
| 78 |
+
"minSpanF1": 1.0,
|
| 79 |
+
"maxConfidenceDifference": 0.00033
|
| 80 |
+
},
|
| 81 |
+
"classification_described": {
|
| 82 |
+
"calls": 24,
|
| 83 |
+
"refused": 0,
|
| 84 |
+
"matched": 24,
|
| 85 |
+
"items": 24,
|
| 86 |
+
"minSpanF1": 1.0,
|
| 87 |
+
"maxConfidenceDifference": 0.001187
|
| 88 |
+
},
|
| 89 |
+
"record_anchorless": {
|
| 90 |
+
"calls": 8,
|
| 91 |
+
"refused": 0,
|
| 92 |
+
"matched": 8,
|
| 93 |
+
"items": 2,
|
| 94 |
+
"minSpanF1": 1.0,
|
| 95 |
+
"maxConfidenceDifference": 0.000258
|
| 96 |
+
},
|
| 97 |
+
"structure_event": {
|
| 98 |
+
"calls": 24,
|
| 99 |
+
"refused": 0,
|
| 100 |
+
"matched": 24,
|
| 101 |
+
"items": 45,
|
| 102 |
+
"minSpanF1": 1.0,
|
| 103 |
+
"maxConfidenceDifference": 0.000897
|
| 104 |
+
},
|
| 105 |
+
"classification_multi": {
|
| 106 |
+
"calls": 16,
|
| 107 |
+
"refused": 0,
|
| 108 |
+
"matched": 16,
|
| 109 |
+
"items": 22,
|
| 110 |
+
"minSpanF1": 1.0,
|
| 111 |
+
"maxConfidenceDifference": 0.000356
|
| 112 |
+
},
|
| 113 |
+
"entities_described": {
|
| 114 |
+
"calls": 16,
|
| 115 |
+
"refused": 0,
|
| 116 |
+
"matched": 16,
|
| 117 |
+
"items": 40,
|
| 118 |
+
"minSpanF1": 1.0,
|
| 119 |
+
"maxConfidenceDifference": 0.000411
|
| 120 |
+
},
|
| 121 |
+
"structure_order": {
|
| 122 |
+
"calls": 8,
|
| 123 |
+
"refused": 0,
|
| 124 |
+
"matched": 8,
|
| 125 |
+
"items": 22,
|
| 126 |
+
"minSpanF1": 1.0,
|
| 127 |
+
"maxConfidenceDifference": 0.001252
|
| 128 |
+
},
|
| 129 |
+
"relations_event": {
|
| 130 |
+
"calls": 8,
|
| 131 |
+
"refused": 0,
|
| 132 |
+
"matched": 8,
|
| 133 |
+
"items": 15,
|
| 134 |
+
"minSpanF1": 1.0,
|
| 135 |
+
"maxConfidenceDifference": 0.0002
|
| 136 |
+
}
|
| 137 |
+
}
|
| 138 |
+
},
|
| 139 |
+
"fixtures": {
|
| 140 |
+
"about": "FluidInference's eleven verify-full-extraction fixtures",
|
| 141 |
+
"identical": 11,
|
| 142 |
+
"total": 11,
|
| 143 |
+
"rows": [
|
| 144 |
+
{
|
| 145 |
+
"case": "entities",
|
| 146 |
+
"structureMatch": true,
|
| 147 |
+
"maxConfidenceDifference": 1e-06
|
| 148 |
+
},
|
| 149 |
+
{
|
| 150 |
+
"case": "entities_multi",
|
| 151 |
+
"structureMatch": true,
|
| 152 |
+
"maxConfidenceDifference": 3e-06
|
| 153 |
+
},
|
| 154 |
+
{
|
| 155 |
+
"case": "relations",
|
| 156 |
+
"structureMatch": true,
|
| 157 |
+
"maxConfidenceDifference": 2.2e-05
|
| 158 |
+
},
|
| 159 |
+
{
|
| 160 |
+
"case": "relations_two",
|
| 161 |
+
"structureMatch": true,
|
| 162 |
+
"maxConfidenceDifference": 6e-06
|
| 163 |
+
},
|
| 164 |
+
{
|
| 165 |
+
"case": "record_natural",
|
| 166 |
+
"structureMatch": true,
|
| 167 |
+
"maxConfidenceDifference": 0.000134
|
| 168 |
+
},
|
| 169 |
+
{
|
| 170 |
+
"case": "record_latent",
|
| 171 |
+
"structureMatch": true,
|
| 172 |
+
"maxConfidenceDifference": 0.000352
|
| 173 |
+
},
|
| 174 |
+
{
|
| 175 |
+
"case": "record_anchorless",
|
| 176 |
+
"structureMatch": true,
|
| 177 |
+
"maxConfidenceDifference": null
|
| 178 |
+
},
|
| 179 |
+
{
|
| 180 |
+
"case": "attributes",
|
| 181 |
+
"structureMatch": true,
|
| 182 |
+
"maxConfidenceDifference": 2.8e-05
|
| 183 |
+
},
|
| 184 |
+
{
|
| 185 |
+
"case": "mixed_classification",
|
| 186 |
+
"structureMatch": true,
|
| 187 |
+
"maxConfidenceDifference": 1e-06
|
| 188 |
+
},
|
| 189 |
+
{
|
| 190 |
+
"case": "classification_only",
|
| 191 |
+
"structureMatch": true,
|
| 192 |
+
"maxConfidenceDifference": 6.4e-05
|
| 193 |
+
},
|
| 194 |
+
{
|
| 195 |
+
"case": "enum_choice",
|
| 196 |
+
"structureMatch": true,
|
| 197 |
+
"maxConfidenceDifference": 0.0
|
| 198 |
+
}
|
| 199 |
+
]
|
| 200 |
+
}
|
| 201 |
+
}
|