spybyscript commited on
Commit
64426eb
·
verified ·
1 Parent(s): 6818442

A concise card: the files, use, validation and the Galaxy S23 Ultra results

Browse files
Files changed (2) hide show
  1. README.md +78 -125
  2. SHA256SUMS +1 -1
README.md CHANGED
@@ -14,47 +14,29 @@ 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 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
 
@@ -71,101 +53,72 @@ 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`:
112
- classification), packed by the library's collator, padded to the bucket and decoded by a Python mirror of an on-device host. ai-edge-litert
113
- 2.2.0 on arm64 Mac (Apple M5 Max, macOS 27.0.1): the encoder on the CPU and on the Mac's GPU through the same ML Drift
114
- accelerator the phone uses (Metal here); head and scorer on the CPU. Mac measurements, not phone numbers.
115
-
116
- | Encoder | Runtime | Same answers as the library | Span F1 (min) | Drift p99 (max) | Mac ms p50 per graph | Gate |
117
- |---|---|---|---|---|---|---|
118
- | `wfp16` | CPU (XNNPACK; encoder 2 threads, head and scorer 1) | facts 287/287, keywords.a 287/287, keywords.b 285/287, relation 1660/1660 | 0.9966 | 0.00135 | encoder 26.28, head 0.52, scorer 2.13 | pass |
119
- | `wfp16` | Mac GPU (Metal, ML Drift), FP32 precision | facts 287/287, keywords.a 287/287, keywords.b 285/287, relation 1660/1660 | 0.9966 | 0.00135 | encoder 10.03, head 0.65, scorer 2.49 | pass |
120
- | `wfp16` | Mac GPU (Metal, ML Drift), default precision (FP16) | facts 13/287, keywords.a 112/287, keywords.b 68/287, relation 1259/1660 | 0.0 | 0.58571 | encoder 8.09, head 0.72, scorer 3.47 | fail |
121
- | `fp16hw` | CPU (XNNPACK; encoder 2 threads, head and scorer 1) | facts 287/287, keywords.a 287/287, keywords.b 285/287, relation 1660/1660 | 0.9966 | 0.00135 | encoder 25.4, head 0.5, scorer 2.44 | pass |
122
- | `fp16hw` | Mac GPU (Metal, ML Drift), FP32 precision | facts 287/287, keywords.a 287/287, keywords.b 285/287, relation 1660/1660 | 0.9966 | 0.00135 | encoder 10.33, head 0.61, scorer 3.54 | pass |
123
- | `fp16hw` | Mac GPU (Metal, ML Drift), default precision (FP16) | facts 286/287, keywords.a 286/287, keywords.b 283/287, relation 1654/1660 | 0.9932 | 0.03023 | encoder 8.35, head 1.96, scorer 4.3 | pass |
124
- | `int8` | CPU (XNNPACK; encoder 2 threads, head and scorer 1) | facts 207/287, keywords.a 229/287, keywords.b 205/287, relation 1488/1660 | 0.8348 | 0.38601 | encoder 29.58, head 0.76, scorer 3.43 | fail |
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 | GPU FP32, NORMAL priority | NPU (compiled on the phone) |
138
- |---|---|---|---|---|
139
- | encoder, 128 tokens | 143 ms | 156 ms, 0.85 GB dirty | 67 ms, 0.85 GB dirty | 13 ms (`fp16hw`, 5.7 s compile) |
140
- | head | 1.2 ms | does not compile | does not compile | 1.2 ms |
141
- | scorer | 2.9 ms | does not compile | does not compile | 3.4 ms |
142
- | explicit spans | 5.6 ms | does not compile | does not compile | fails to run |
143
- | relations | 16 ms | does not compile | does not compile | 2.8 ms |
144
- | record assignment | 8.9 ms | 21.5 ms | not measured | 4.0 ms |
145
- | anchorless records | 10.8 ms | does not compile | 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), with or without FP32
153
- accumulation, so it is not an accumulation overflow: use the CPU, the GPU at FP32 or the NPU there.
154
- - **GPU priority:** LOW is meant to let other GPU work go first, but on this phone and LiteRT version it does not let an app's
155
- rendering in. An app rendering on the same GPU stopped for each whole call at either priority, and LOW made each call
156
- 2–3 times longer. That was measured with GLiNER2.5-Decide's encoder: see `spybyscript/gliner25-decide-litert`, "GPU
157
- priority beside a rendering app". On the GPU use NORMAL; the NPU keeps an app's frames.
158
-
159
- ### Galaxy Z Fold 8 (SM8850, 2026-10-29)
160
-
161
- 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:
162
-
163
- - the feature outputs and pair logits equal the Mac's LiteRT run (difference 0.0), with the same candidate pools (3 / 3);
164
- - the graphs take 39–61 ms a call, against 70 ms for the same call on ONNX Runtime (an FP32 export with an int8 token table);
165
- - a whole entity call through the host takes 46 ms.
166
 
167
  ## Files
168
 
169
- `LICENSE` (Apache-2.0), `NOTICE.txt`, `model-card.md` (the original card), `config.json` (contract), `validation.json`
170
- (the recorded-call validation), `validation-full.json` (every call type), `SHA256SUMS`, `tokenizer/`, `source/` (the
171
- checkpoint's configs), `runtime/`.
 
 
 
 
 
 
14
 
15
  # GLiNER2.5 base (gliner2.5-base-v1) — LiteRT (Android)
16
 
17
+ A LiteRT conversion of [fastino/gliner2.5-base-v1](https://huggingface.co/fastino/gliner2.5-base-v1) (revision
18
+ `1a8bc24e00dc7300b9017c81d63e3dcdabb26596`, Apache-2.0, by Fastino), converted by spybyscript. It carries every trained head:
19
+ entities, classification, structures, relations and records. gliner2's own code decodes the outputs. For the model itself
20
+ (training, labels, benchmarks, intended use), see the [original model card](https://huggingface.co/fastino/gliner2.5-base-v1),
21
+ copied here as `model-card.md`. The split into graphs follows FluidInference's Core ML port
22
+ ([gliner2-5-base-coreml](https://huggingface.co/FluidInference/gliner2-5-base-coreml), Apache-2.0).
23
 
24
+ ## Files
 
 
25
 
26
  | File | Size | Contents |
27
  |---|---|---|
28
+ | `gliner2_base_encoder_wfp16.tflite` | 189 MB | encoder for the CPU and the GPU at FP32 (`inputs_embeds`, `attention_mask` → `hidden_states`), float16-stored weights |
29
+ | `gliner2_base_encoder_fp16hw.tflite` | 189 MB | the same encoder rewritten for FP16 hardware (NPU): LayerNorm power-of-two pre-scale, first feed-forward layer split in two |
30
+ | `gliner2_base_head_fp32.tflite` | 8.5 MB | boundary head: routed states, boundary marginals, pool projections, null and count heads, classification logits |
31
+ | `gliner2_base_scorer_fp32.tflite` | 2.1 MB | candidate scorer and record candidate states |
32
+ | `gliner2_base_explicit_fp32.tflite` | 2.1 MB | explicit-span scorer (entity attributes, choice fields) |
33
  | `gliner2_base_relation_fp32.tflite` | 23.7 MB | relation scorer over head/tail pairs |
34
  | `gliner2_base_record_assignment_fp32.tflite` | 1.2 MB | record decoder: instance-to-field assignment, object and latent-seed scores |
35
+ | `gliner2_base_record_anchorless_fp32.tflite` | 2.9 MB | record decoder for anchorless records |
36
+ | `gliner2_base_embeddings_f16.bin` | 197 MB | token table, float16 [128011, 768], little-endian, row-major |
37
+ | `runtime/` | | `litert_boundary_runtime.py` (reference runtime), `verify_parity.py`, `parity_corpus.json` |
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
+ `config.json` lists every graph's signatures and shapes.
40
 
41
  ## Use
42
 
 
53
  model.extract("Ravi Shah is an engineer at Tidewater Systems.", schema, include_confidence=True)
54
  ```
55
 
56
+ Every public gliner2 call works: `extract`, `extract_entities`, `classify_text`, `extract_json` and `extract_relations`,
57
+ plus their `batch_*` and `*_long` forms. The runtime runs gliner2's BoundaryExtractor with every trained module on
58
+ LiteRT. Schema handling, the candidate pool and the decoders are the library's own code.
59
 
60
+ **On a phone,** run the same graphs with LiteRT's `CompiledModel`; the runtime shows the host steps.
61
+ - **The encoder** is the only graph worth an accelerator: the NPU with `fp16hw`, the GPU at FP32 with `wfp16` (NORMAL
62
+ priority), or the CPU.
63
+ - **The six head graphs** run on the CPU in 1–16 ms each.
64
+
65
+ **Fixed sizes.** The original runs with dynamic shapes; these are FluidInference's bucket sizes. One request holds at most:
66
+ - 128 subword tokens (schema and text) and 64 text words;
67
+ - 8 queries (entity labels, structure fields, relation roles) and 8 classification choices;
68
+ - a candidate pool of 192 (the checkpoint's own);
69
+ - 4 relation types with 256 head/tail pairs;
70
+ - 64 explicit spans per query;
71
+ - records of up to 8 fields with 192 candidates each.
72
 
73
+ A larger request is refused with a ValueError, never truncated. Run long text through the `*_long` calls, for example
74
+ `extract_entities_long(text, labels, chunk_size=40, chunk_overlap=8)`. The weights change only in storage and compute
75
+ precision.
76
+
77
+ ## Validation
78
 
79
  `runtime/verify_parity.py` runs each request through gliner2 with the original weights and through the reference runtime
80
+ (LiteRT CPU), then compares the structured results:
81
+ - **The corpus** (`runtime/parity_corpus.json`): 296 requests over 16 call types (entities, relations, structures, the
82
+ three record modes, attributes, single- and multi-label classification, mixed schemas). All 296 are identical, with
83
+ span F1 1.0 and confidences within 0.0014.
84
+ - **FluidInference's eleven schema-path fixtures:** 11 of 11.
85
+
86
+ `validation-full.json` and `validation.json` hold the numbers.
87
+
88
+ ## Galaxy S23 Ultra (SM8550)
89
+
90
+ Snapdragon 8 Gen 2, Android 16, LiteRT 2.3.2 with `qnn-runtime` 2.50.0. Each graph ran on a request recorded from the
91
+ corpus: 10 calls after a 30 s rest, at thermal status 0. The CPU ran 4 threads; the GPU ran at NORMAL priority, timed as
92
+ whole calls. The NPU compiled the graphs on the phone; for the encoder that takes 5.7 s, once.
93
+
94
+ | Graph | CPU | GPU FP32 | NPU |
95
+ |---|---|---|---|
96
+ | encoder, 128 tokens | 143 ms | 67 ms (0.85 GB dirty) | 13 ms (`fp16hw`) |
97
+ | head | 1.2 ms | not supported by LiteRT's GPU | 1.2 ms |
98
+ | scorer | 2.9 ms | not supported | 3.4 ms |
99
+ | explicit spans | 5.6 ms | not supported | does not run |
100
+ | relations | 16 ms | not supported | 2.8 ms |
101
+ | record assignment | 8.9 ms | runs (21.5 ms at LOW priority) | 4.0 ms |
102
+ | anchorless records | 10.8 ms | not supported | 1.3 ms |
103
+
104
+ - **A request** takes about 150 ms on the CPU. With the encoder on the NPU and the heads on the CPU, it takes about
105
+ 20 ms; relations add 16 ms on the CPU, or 3 ms on the NPU.
106
+ - **Answers with the phone's encoder outputs.** 32 recorded requests, two per call type, ran through the rest of the
107
+ pipeline. At GPU FP32 they were all unchanged. On the NPU, 30 were unchanged; the two that changed were latent-record
108
+ requests (span F1 0.98).
109
+ - **`fp16hw` at GPU FP16 gives non-finite outputs on this phone's Adreno 740**, with or without FP32 accumulation. Use
110
+ the GPU at FP32 there.
111
+ - **GPU priority:** use NORMAL. On this phone, LOW priority does not let an app's rendering run any sooner; it only makes
112
+ each call 2–3 times longer. The measurement is with GLiNER2.5-Decide's encoder: see
113
+ [spybyscript/gliner25-decide-litert](https://huggingface.co/spybyscript/gliner25-decide-litert).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
 
115
  ## Files
116
 
117
+ - `LICENSE` (Apache-2.0) and `NOTICE.txt`
118
+ - `model-card.md`, the original card
119
+ - `config.json`, the contract
120
+ - `validation.json` and `validation-full.json`
121
+ - `SHA256SUMS`
122
+ - `tokenizer/`
123
+ - `source/` (the checkpoint's configs)
124
+ - `runtime/`
SHA256SUMS CHANGED
@@ -1,6 +1,6 @@
1
  cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30 LICENSE
2
  a4134d01e6b89380d84cfcde424328aaa717edadef0a6958c3129f206d91dc37 NOTICE.txt
3
- be689aee1556e9966f5476887227777c2c9ea296a9f395028771dc97b3b8bad6 README.md
4
  251e71c11dd57b4106d0d5eb102a984abebc5415f4e0eb392a4966e3293b189f config.json
5
  22c2e937cfc17e1ee9c8f3ec6f1449d44dc9230cfcbb8737c9bae8492628a3e8 gliner2_base_embeddings_f16.bin
6
  6f5241586176d6b123f75392bedd66a24c63f4105f8a45181dbc02385e95e5bb gliner2_base_encoder_fp16hw.tflite
 
1
  cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30 LICENSE
2
  a4134d01e6b89380d84cfcde424328aaa717edadef0a6958c3129f206d91dc37 NOTICE.txt
3
+ de378412e30d392c2437c17ab94c8a953cd192d4d8c2e7e8e93fd2a57d025b7d README.md
4
  251e71c11dd57b4106d0d5eb102a984abebc5415f4e0eb392a4966e3293b189f config.json
5
  22c2e937cfc17e1ee9c8f3ec6f1449d44dc9230cfcbb8737c9bae8492628a3e8 gliner2_base_embeddings_f16.bin
6
  6f5241586176d6b123f75392bedd66a24c63f4105f8a45181dbc02385e95e5bb gliner2_base_encoder_fp16hw.tflite