alexwengg commited on
Commit
c6e4872
·
verified ·
1 Parent(s): 89e0de9

Add validated W8 extraction packages and ANE profiling for multi

Browse files
Files changed (34) hide show
  1. README.md +25 -3
  2. benchmark-extraction.py +16 -3
  3. extraction-w8-assets.lock.json +161 -0
  4. extraction_runtime.py +55 -3
  5. gliner2_multi_explicit_fp16_W64_Q8_S64.mlpackage/Data/com.apple.CoreML/model.mlmodel +3 -0
  6. gliner2_multi_explicit_fp16_W64_Q8_S64.mlpackage/Data/com.apple.CoreML/weights/weight.bin +3 -0
  7. gliner2_multi_explicit_fp16_W64_Q8_S64.mlpackage/Manifest.json +18 -0
  8. gliner2_multi_extraction_features_w8_embedding_fp32_L128_W64_Q8.mlpackage/Data/com.apple.CoreML/model.mlmodel +3 -0
  9. gliner2_multi_extraction_features_w8_embedding_fp32_L128_W64_Q8.mlpackage/Data/com.apple.CoreML/weights/weight.bin +3 -0
  10. gliner2_multi_extraction_features_w8_embedding_fp32_L128_W64_Q8.mlpackage/Manifest.json +18 -0
  11. gliner2_multi_extraction_features_w8_embedding_linear_fp16_L128_W64_Q8.mlpackage/Data/com.apple.CoreML/model.mlmodel +3 -0
  12. gliner2_multi_extraction_features_w8_embedding_linear_fp16_L128_W64_Q8.mlpackage/Data/com.apple.CoreML/weights/weight.bin +3 -0
  13. gliner2_multi_extraction_features_w8_embedding_linear_fp16_L128_W64_Q8.mlpackage/Manifest.json +18 -0
  14. gliner2_multi_extraction_scorer_fp16_L128_W64_Q8.mlpackage/Data/com.apple.CoreML/model.mlmodel +3 -0
  15. gliner2_multi_extraction_scorer_fp16_L128_W64_Q8.mlpackage/Data/com.apple.CoreML/weights/weight.bin +3 -0
  16. gliner2_multi_extraction_scorer_fp16_L128_W64_Q8.mlpackage/Manifest.json +18 -0
  17. gliner2_multi_record_anchorless_fp16_F8_C192_I1536.mlpackage/Data/com.apple.CoreML/model.mlmodel +3 -0
  18. gliner2_multi_record_anchorless_fp16_F8_C192_I1536.mlpackage/Data/com.apple.CoreML/weights/weight.bin +3 -0
  19. gliner2_multi_record_anchorless_fp16_F8_C192_I1536.mlpackage/Manifest.json +18 -0
  20. gliner2_multi_record_assignment_fp16_F8_C192_I1536.mlpackage/Data/com.apple.CoreML/model.mlmodel +3 -0
  21. gliner2_multi_record_assignment_fp16_F8_C192_I1536.mlpackage/Data/com.apple.CoreML/weights/weight.bin +3 -0
  22. gliner2_multi_record_assignment_fp16_F8_C192_I1536.mlpackage/Manifest.json +18 -0
  23. gliner2_multi_relation_fp16_W64_R4_P256.mlpackage/Data/com.apple.CoreML/model.mlmodel +3 -0
  24. gliner2_multi_relation_fp16_W64_R4_P256.mlpackage/Data/com.apple.CoreML/weights/weight.bin +3 -0
  25. gliner2_multi_relation_fp16_W64_R4_P256.mlpackage/Manifest.json +18 -0
  26. quantize-extraction-coreml.py +95 -0
  27. reports/extraction-w8-adaptive.json +478 -0
  28. reports/extraction-w8-fp16_cpu_ne.json +470 -0
  29. reports/extraction-w8-fp32_all.json +465 -0
  30. reports/extraction-w8-validation.json +175 -0
  31. tests/test_extraction_routing.py +21 -0
  32. tests/test_quantize_extraction.py +21 -0
  33. verify-adaptive-extraction.py +63 -0
  34. verify-quantized-extraction.py +80 -0
README.md CHANGED
@@ -17,7 +17,7 @@ This repository contains fixed-shape Core ML exports of the trained classificati
17
 
18
  The FP32 extraction stage packages support entities, relations, entity attributes, enum choices, natural/latent/anchorless records, and schemas mixed with classification. In a small fixed manifest of real text and schema fixtures, FP32 matched the native structured output on **15/15** cases. The largest FP32 confidence difference was 0.00000489. The multilingual manifest includes Spanish, French, Chinese, and German text. These checks are selected parity fixtures, not a Decision Index score or a full dataset evaluation.
19
 
20
- FP16 result: 14/15 structures matched; the latent-record fixture differed (10 native records versus 9 Core ML records), so FP16 extraction is not published. The multilingual extraction FP16 packages remain local experiments because one latent-record fixture differed. They are not in this repository.
21
 
22
 
23
  ```bash
@@ -36,13 +36,35 @@ The extraction bucket holds up to 128 combined subword tokens, 64 text words, 8
36
 
37
  On an M5 Pro with macOS 27.0, FP32 end-to-end median 10.56 ms with All, 10.02 ms with CPU+GPU, 36.08 ms with CPU+Neural Engine for a selected three-label entity request after 20 warmups and over 200 Python calls. Those are local end-to-end measurements for this shape, not ANE-only latency or a device-wide benchmark.
38
 
39
- Under CPU+Neural Engine, the FP32 feature graph compute plan assigned all operations to CPU on this machine; CPU+GPU is the faster measured setting. A local FP32-source LUT8 attempt stalled in k-means with numerical warnings; no compressed extraction package is included.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
  ## Classification
42
 
43
  The original L128/K8 classification packages remain available, with a separate `runtime.py` entry point. Up to eight labels fit that bucket. 299/300 selected choices matched in FP16; 100/100 matched in an FP32 control. See the classification report JSONs for the exact selected samples and limits. The full checkpoint's task and dataset scores have not been reproduced here.
44
 
45
- An optional **embedding-only W8 with asymmetric scaling** classifier keeps the encoder and heads FP16. It reduces the package from **576,528,829 to 385,193,894 bytes** (33.2%). On an M5 Pro plugged into AC, it agreed with FP16 on 100/100 selected eligible application requests (largest probability difference 0.01941). It matched the native classifier on 99/100 requests; the one near-tie disagreement at `jev.ag_news` row 100 is also present in the FP16 export. In a paired 100-request check, automatic-device full-request p50 was 4.283 ms FP16 versus 4.265 ms W8, so there is no established speed gain. The W8 compute plan put 496/516 executable ops on ANE under forced CPU+ANE, with 20 CPU boundary/constant ops; forced CPU+ANE was slower than automatic device choice. Symmetric embedding W8 exceeded the 0.02 probability-difference gate on the first selected manifest and is omitted. Use `runtime.py --precision embedding_w8_linear` for the compressed classification package; extraction continues to use FP32 packages.
46
 
47
  When downloading with `huggingface_hub.snapshot_download`, pass `local_dir="./gliner2-multi-coreml"` and point the runtime there. Core ML compilation on the tested macOS release rejected the symlinked weight file in the default Hub cache snapshot; use a materialized local directory.
48
 
 
17
 
18
  The FP32 extraction stage packages support entities, relations, entity attributes, enum choices, natural/latent/anchorless records, and schemas mixed with classification. In a small fixed manifest of real text and schema fixtures, FP32 matched the native structured output on **15/15** cases. The largest FP32 confidence difference was 0.00000489. The multilingual manifest includes Spanish, French, Chinese, and German text. These checks are selected parity fixtures, not a Decision Index score or a full dataset evaluation.
19
 
20
+ Standalone FP16 result: 14/15 structures matched; the latent-record fixture differed (10 native records versus 9 Core ML records). The optional adaptive W8 runtime below uses FP32 for any schema containing a latent record and FP16 for the other schemas.
21
 
22
 
23
  ```bash
 
36
 
37
  On an M5 Pro with macOS 27.0, FP32 end-to-end median 10.56 ms with All, 10.02 ms with CPU+GPU, 36.08 ms with CPU+Neural Engine for a selected three-label entity request after 20 warmups and over 200 Python calls. Those are local end-to-end measurements for this shape, not ANE-only latency or a device-wide benchmark.
38
 
39
+ Under CPU+Neural Engine, the FP32 feature graph compute plan assigned all operations to CPU on this machine; CPU+GPU is the faster measured setting for the uncompressed FP32 path. A local FP32-source LUT8 attempt stalled in k-means with numerical warnings.
40
+
41
+ ### Extraction with embedding W8
42
+
43
+ The optional adaptive runtime uses two feature packages with per-channel int8 **weight-only** compression of the trained word embedding. The rest of each encoder and the corresponding five extraction heads retain their exported precision. It loads one precision at a time: FP16 with CPU+Neural Engine for schemas without latent records, then FP32 with All for schemas containing a latent record. Switching precision incurs a cold model load. The standalone FP16 path still differs on the latent-record fixture; use `CoreMLAdaptiveBoundaryExtractor` for complete selected parity.
44
+
45
+ | Feature package | Original bytes | W8 bytes | Selected native structure parity | Largest confidence difference |
46
+ | --- | ---: | ---: | ---: | ---: |
47
+ | `gliner2_multi_extraction_features_w8_embedding_linear_fp16_L128_W64_Q8.mlpackage` | 578,545,680 | 387,210,787 | 14/15 standalone | see adaptive result |
48
+ | `gliner2_multi_extraction_features_w8_embedding_fp32_L128_W64_Q8.mlpackage` | 1,155,993,314 | 580,986,690 | 15/15 standalone | 0.02455 |
49
+ | Adaptive FP16 plus FP32 | — | — | **15/15** | **0.01639** |
50
+
51
+ The W8 FP16 feature graph had 620/652 executable operations assigned to ANE with forced CPU+Neural Engine on an M5 Pro; the 32 CPU operations were mainly integer indexing around embeddings, plus `cumsum` and one decompression constant. This is a private profiler count, distinct from the public compute-plan grouping. On the same Mac, an adaptive W8 entity request took 9.61 ms median over 100 calls after 10 warmups, using the FP16 path. The uncompressed FP32 All baseline took 10.56 ms in a separate 200-call run. These are small local measurements, not a paired speedup claim or a Decision Index score.
52
+
53
+ ```python
54
+ from gliner2 import Schema
55
+ from extraction_runtime import CoreMLAdaptiveBoundaryExtractor
56
+
57
+ model = CoreMLAdaptiveBoundaryExtractor(".")
58
+ print(model.extract("Alice founded Acme in Toronto.", Schema().entities(["person", "organization", "location"])))
59
+ ```
60
+
61
+ Both W8 feature packages and the five FP16 head packages are included. `extraction-w8-assets.lock.json` records exact file hashes. `quantize-extraction-coreml.py` regenerates the W8 packages from the pinned feature exports; `verify-adaptive-extraction.py` checks the selected native-output manifest. Weight-only W8 does not imply int8 activation or matrix arithmetic.
62
 
63
  ## Classification
64
 
65
  The original L128/K8 classification packages remain available, with a separate `runtime.py` entry point. Up to eight labels fit that bucket. 299/300 selected choices matched in FP16; 100/100 matched in an FP32 control. See the classification report JSONs for the exact selected samples and limits. The full checkpoint's task and dataset scores have not been reproduced here.
66
 
67
+ An optional **embedding-only W8 with asymmetric scaling** classifier keeps the encoder and heads FP16. It reduces the package from **576,528,829 to 385,193,894 bytes** (33.2%). On an M5 Pro plugged into AC, it agreed with FP16 on 100/100 selected eligible application requests (largest probability difference 0.01941). It matched the native classifier on 99/100 requests; the one near-tie disagreement at `jev.ag_news` row 100 is also present in the FP16 export. In a paired 100-request check, automatic-device full-request p50 was 4.283 ms FP16 versus 4.265 ms W8, so there is no established speed gain. The W8 compute plan put 496/516 executable ops on ANE under forced CPU+ANE, with 20 CPU boundary/constant ops; forced CPU+ANE was slower than automatic device choice. Symmetric embedding W8 exceeded the 0.02 probability-difference gate on the first selected manifest and is omitted. Use `runtime.py --precision embedding_w8_linear` for the compressed classification package; extraction has its own packages and runtime.
68
 
69
  When downloading with `huggingface_hub.snapshot_download`, pass `local_dir="./gliner2-multi-coreml"` and point the runtime there. Core ML compilation on the tested macOS release rejected the symlinked weight file in the default Hub cache snapshot; use a materialized local directory.
70
 
benchmark-extraction.py CHANGED
@@ -11,7 +11,7 @@ import coremltools as ct
11
  import psutil
12
  from gliner2 import Schema
13
 
14
- from extraction_runtime import CoreMLBoundaryExtractor
15
 
16
  UNITS = {
17
  "cpu_only": ct.ComputeUnit.CPU_ONLY,
@@ -30,6 +30,8 @@ def main():
30
  parser = argparse.ArgumentParser()
31
  parser.add_argument("--model-dir", required=True)
32
  parser.add_argument("--precision", choices=["fp16", "fp32"], default="fp32")
 
 
33
  parser.add_argument("--units", choices=list(UNITS), default="all")
34
  parser.add_argument("--warmup", type=int, default=20)
35
  parser.add_argument("--iterations", type=int, default=200)
@@ -37,7 +39,15 @@ def main():
37
  text = "Alice founded Acme in Toronto in 2020."
38
  schema = Schema().entities(["person", "organization", "location"])
39
  started = time.perf_counter()
40
- runtime = CoreMLBoundaryExtractor(args.model_dir, precision=args.precision, compute_units=UNITS[args.units])
 
 
 
 
 
 
 
 
41
  load_ms = (time.perf_counter() - started) * 1000
42
  for _ in range(args.warmup):
43
  runtime.extract(text, schema)
@@ -55,6 +65,8 @@ def main():
55
  "shape": "L128/W64/Q8/C192",
56
  "precision": args.precision,
57
  "compute_units": args.units,
 
 
58
  "warmup": args.warmup,
59
  "iterations": args.iterations,
60
  "load_ms": load_ms,
@@ -67,7 +79,8 @@ def main():
67
  "coremltools": ct.__version__,
68
  }
69
  folder = Path(args.model_dir)
70
- path = folder / f"benchmark-{args.precision}-{args.units}.json"
 
71
  path.write_text(json.dumps(report, indent=2) + "\n")
72
  print(json.dumps(report, indent=2))
73
 
 
11
  import psutil
12
  from gliner2 import Schema
13
 
14
+ from extraction_runtime import CoreMLAdaptiveBoundaryExtractor, CoreMLBoundaryExtractor
15
 
16
  UNITS = {
17
  "cpu_only": ct.ComputeUnit.CPU_ONLY,
 
30
  parser = argparse.ArgumentParser()
31
  parser.add_argument("--model-dir", required=True)
32
  parser.add_argument("--precision", choices=["fp16", "fp32"], default="fp32")
33
+ parser.add_argument("--feature-package")
34
+ parser.add_argument("--adaptive-w8", action="store_true")
35
  parser.add_argument("--units", choices=list(UNITS), default="all")
36
  parser.add_argument("--warmup", type=int, default=20)
37
  parser.add_argument("--iterations", type=int, default=200)
 
39
  text = "Alice founded Acme in Toronto in 2020."
40
  schema = Schema().entities(["person", "organization", "location"])
41
  started = time.perf_counter()
42
+ if args.adaptive_w8:
43
+ runtime = CoreMLAdaptiveBoundaryExtractor(args.model_dir, fp16_compute_units=UNITS[args.units])
44
+ else:
45
+ runtime = CoreMLBoundaryExtractor(
46
+ args.model_dir,
47
+ precision=args.precision,
48
+ compute_units=UNITS[args.units],
49
+ feature_package=args.feature_package,
50
+ )
51
  load_ms = (time.perf_counter() - started) * 1000
52
  for _ in range(args.warmup):
53
  runtime.extract(text, schema)
 
65
  "shape": "L128/W64/Q8/C192",
66
  "precision": args.precision,
67
  "compute_units": args.units,
68
+ "feature_package": args.feature_package,
69
+ "adaptive_w8": args.adaptive_w8,
70
  "warmup": args.warmup,
71
  "iterations": args.iterations,
72
  "load_ms": load_ms,
 
79
  "coremltools": ct.__version__,
80
  }
81
  folder = Path(args.model_dir)
82
+ variant = "adaptive-w8" if args.adaptive_w8 else "w8-embedding" if args.feature_package else "baseline"
83
+ path = folder / f"benchmark-{args.precision}-{args.units}-{variant}.json"
84
  path.write_text(json.dumps(report, indent=2) + "\n")
85
  print(json.dumps(report, indent=2))
86
 
extraction-w8-assets.lock.json ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "source_model": "fastino/gliner2.5-multi-v1",
3
+ "source_revision": "a221b77a8baf4a613b8f8652661d41fa10a5641e",
4
+ "coremltools": "9.0",
5
+ "quantization_scope": "trained word embedding only; int8 per-channel weight-only; activations and remaining weights retain exported precision",
6
+ "package_files": [
7
+ {
8
+ "package": "gliner2_multi_extraction_features_w8_embedding_linear_fp16_L128_W64_Q8.mlpackage",
9
+ "bytes": 387210787,
10
+ "files": [
11
+ {
12
+ "path": "gliner2_multi_extraction_features_w8_embedding_linear_fp16_L128_W64_Q8.mlpackage/Data/com.apple.CoreML/model.mlmodel",
13
+ "bytes": 1046842,
14
+ "sha256": "25c0251fbbd22083eb156897c4dd075315f18326f6c2e5107463882175db8f5a"
15
+ },
16
+ {
17
+ "path": "gliner2_multi_extraction_features_w8_embedding_linear_fp16_L128_W64_Q8.mlpackage/Data/com.apple.CoreML/weights/weight.bin",
18
+ "bytes": 386163328,
19
+ "sha256": "04ec00914fcd74e7a6d6f827832621e8d4d8a317eb96a934c0ef93aa46955533"
20
+ },
21
+ {
22
+ "path": "gliner2_multi_extraction_features_w8_embedding_linear_fp16_L128_W64_Q8.mlpackage/Manifest.json",
23
+ "bytes": 617,
24
+ "sha256": "dbefec606e8e299c14baff9fd7aac685d4ee879bc16d8dd5a5c28d9a9cb5ffa1"
25
+ }
26
+ ],
27
+ "source_package": "gliner2_multi_extraction_features_fp16_L128_W64_Q8.mlpackage",
28
+ "source_bytes": 578545680,
29
+ "method": "linear int8 per-channel weight-only"
30
+ },
31
+ {
32
+ "package": "gliner2_multi_extraction_features_w8_embedding_fp32_L128_W64_Q8.mlpackage",
33
+ "bytes": 580986690,
34
+ "files": [
35
+ {
36
+ "path": "gliner2_multi_extraction_features_w8_embedding_fp32_L128_W64_Q8.mlpackage/Data/com.apple.CoreML/model.mlmodel",
37
+ "bytes": 1013141,
38
+ "sha256": "1be2896ededfe989d059b021a4b3213ce4156e9756bbe85b78e867d11a0ac65c"
39
+ },
40
+ {
41
+ "path": "gliner2_multi_extraction_features_w8_embedding_fp32_L128_W64_Q8.mlpackage/Data/com.apple.CoreML/weights/weight.bin",
42
+ "bytes": 579972932,
43
+ "sha256": "ddef5f5a28b195ce0e9ab5e4cd6f83b42e2e71c151d6d74583754e80807c8ebc"
44
+ },
45
+ {
46
+ "path": "gliner2_multi_extraction_features_w8_embedding_fp32_L128_W64_Q8.mlpackage/Manifest.json",
47
+ "bytes": 617,
48
+ "sha256": "e2aa08eaba4755b4bea21e0ca6a85bd7fa4f451a1988e697e0c637587a717df6"
49
+ }
50
+ ],
51
+ "source_package": "gliner2_multi_extraction_features_fp32_L128_W64_Q8.mlpackage",
52
+ "source_bytes": 1155993314,
53
+ "method": "linear_symmetric int8 per-channel weight-only"
54
+ },
55
+ {
56
+ "package": "gliner2_multi_explicit_fp16_W64_Q8_S64.mlpackage",
57
+ "bytes": 614019,
58
+ "files": [
59
+ {
60
+ "path": "gliner2_multi_explicit_fp16_W64_Q8_S64.mlpackage/Data/com.apple.CoreML/model.mlmodel",
61
+ "bytes": 61018,
62
+ "sha256": "3d31baa0cbe241bbdb574c313447ccd47856c8fd9aa10ee3f7b7887f62050a30"
63
+ },
64
+ {
65
+ "path": "gliner2_multi_explicit_fp16_W64_Q8_S64.mlpackage/Data/com.apple.CoreML/weights/weight.bin",
66
+ "bytes": 552384,
67
+ "sha256": "17e65c6985fd285abe612bf07fb1b282206c3070d1e229fd0bbf60285e21c169"
68
+ },
69
+ {
70
+ "path": "gliner2_multi_explicit_fp16_W64_Q8_S64.mlpackage/Manifest.json",
71
+ "bytes": 617,
72
+ "sha256": "a74bccb2e4a3be79751a07a57be2a9889daeff63b885fab6a76b2a228d6ddee1"
73
+ }
74
+ ]
75
+ },
76
+ {
77
+ "package": "gliner2_multi_extraction_scorer_fp16_L128_W64_Q8.mlpackage",
78
+ "bytes": 903163,
79
+ "files": [
80
+ {
81
+ "path": "gliner2_multi_extraction_scorer_fp16_L128_W64_Q8.mlpackage/Data/com.apple.CoreML/model.mlmodel",
82
+ "bytes": 42962,
83
+ "sha256": "7989c66ba0d0282b5359243006fdf007e3d996eb7ae8e7f9b10a46f8f0fd7add"
84
+ },
85
+ {
86
+ "path": "gliner2_multi_extraction_scorer_fp16_L128_W64_Q8.mlpackage/Data/com.apple.CoreML/weights/weight.bin",
87
+ "bytes": 859584,
88
+ "sha256": "524efc11ca823cad23cc996d34cc7b3baec66bd2bccdf5d3047f83998e77e234"
89
+ },
90
+ {
91
+ "path": "gliner2_multi_extraction_scorer_fp16_L128_W64_Q8.mlpackage/Manifest.json",
92
+ "bytes": 617,
93
+ "sha256": "8ff09daba5da43d903677168e46189e716589ab7276d6d6c50240d457fc1deb0"
94
+ }
95
+ ]
96
+ },
97
+ {
98
+ "package": "gliner2_multi_record_anchorless_fp16_F8_C192_I1536.mlpackage",
99
+ "bytes": 1443024,
100
+ "files": [
101
+ {
102
+ "path": "gliner2_multi_record_anchorless_fp16_F8_C192_I1536.mlpackage/Data/com.apple.CoreML/model.mlmodel",
103
+ "bytes": 6567,
104
+ "sha256": "1d84a0d996a5579fa1bb29da6ce2eec94e4c9e9c8da2265069f52dbc81aeec2c"
105
+ },
106
+ {
107
+ "path": "gliner2_multi_record_anchorless_fp16_F8_C192_I1536.mlpackage/Data/com.apple.CoreML/weights/weight.bin",
108
+ "bytes": 1435840,
109
+ "sha256": "2bd01de3587d6e381bfc7809b59d3555c7a06f2afad05ccfb4d02e6aec6c20e2"
110
+ },
111
+ {
112
+ "path": "gliner2_multi_record_anchorless_fp16_F8_C192_I1536.mlpackage/Manifest.json",
113
+ "bytes": 617,
114
+ "sha256": "4786e7ee95dd64ab0d6423f1c562d14aad65f4fbe46e49c5db67aa313721fcd9"
115
+ }
116
+ ]
117
+ },
118
+ {
119
+ "package": "gliner2_multi_record_assignment_fp16_F8_C192_I1536.mlpackage",
120
+ "bytes": 605167,
121
+ "files": [
122
+ {
123
+ "path": "gliner2_multi_record_assignment_fp16_F8_C192_I1536.mlpackage/Data/com.apple.CoreML/model.mlmodel",
124
+ "bytes": 9990,
125
+ "sha256": "e55b74b80ff515dca663ba4495d4cb0e1dfae0793f7cb85703b13dd6e237f3f8"
126
+ },
127
+ {
128
+ "path": "gliner2_multi_record_assignment_fp16_F8_C192_I1536.mlpackage/Data/com.apple.CoreML/weights/weight.bin",
129
+ "bytes": 594560,
130
+ "sha256": "4c3714dc4a90bd2cba48e355d0d37b4bec91c9e98c1d9015b107820d8809bf8c"
131
+ },
132
+ {
133
+ "path": "gliner2_multi_record_assignment_fp16_F8_C192_I1536.mlpackage/Manifest.json",
134
+ "bytes": 617,
135
+ "sha256": "83eefaaca5795ec7b668aa647d8c7e262038f3c87e2fa0c79fbdcdb7e0a582d7"
136
+ }
137
+ ]
138
+ },
139
+ {
140
+ "package": "gliner2_multi_relation_fp16_W64_R4_P256.mlpackage",
141
+ "bytes": 11848716,
142
+ "files": [
143
+ {
144
+ "path": "gliner2_multi_relation_fp16_W64_R4_P256.mlpackage/Data/com.apple.CoreML/model.mlmodel",
145
+ "bytes": 32419,
146
+ "sha256": "97177682648258bb98feea081f1159d8eaf90d083c8bb32b7149db253240ac3d"
147
+ },
148
+ {
149
+ "path": "gliner2_multi_relation_fp16_W64_R4_P256.mlpackage/Data/com.apple.CoreML/weights/weight.bin",
150
+ "bytes": 11815680,
151
+ "sha256": "d335d72da6645bad3de8c53f0f1cabaf872a1f8c59b24b4b88cd03fc30922e08"
152
+ },
153
+ {
154
+ "path": "gliner2_multi_relation_fp16_W64_R4_P256.mlpackage/Manifest.json",
155
+ "bytes": 617,
156
+ "sha256": "2fe43de8a585b5370f4e10abeffa9e4b361a198560428f091f32b2878ba6e2c9"
157
+ }
158
+ ]
159
+ }
160
+ ]
161
+ }
extraction_runtime.py CHANGED
@@ -1,6 +1,8 @@
1
  """GLiNER2.5 multilingual extraction runtime using only Core ML trained weights."""
2
 
 
3
  import json
 
4
  from contextvars import ContextVar
5
  from pathlib import Path
6
 
@@ -292,7 +294,13 @@ class CoreMLRecordHead(torch.nn.Module):
292
  class CoreMLBoundaryExtractor(BoundaryExtractor):
293
  """Native GLiNER2 schema/decoder with all trained extraction heads in Core ML."""
294
 
295
- def __init__(self, model_dir: str, precision: str = "fp32", compute_units=ct.ComputeUnit.CPU_ONLY):
 
 
 
 
 
 
296
  if precision not in ("fp16", "fp32"):
297
  raise ValueError("precision must be fp16 or fp32")
298
  torch.nn.Module.__init__(self)
@@ -306,9 +314,12 @@ class CoreMLBoundaryExtractor(BoundaryExtractor):
306
  self.length, self.max_words, self.max_queries = 128, 64, 8
307
  self._context = ContextVar("gliner2_coreml_extraction_context")
308
  suffix = f"{precision}_L128_W64_Q8"
309
- self.features_model = ct.models.MLModel(
310
- str(folder / f"{MODEL_PREFIX}_extraction_features_{suffix}.mlpackage"), compute_units=compute_units
311
  )
 
 
 
312
  self.scorer_model = ct.models.MLModel(
313
  str(folder / f"{MODEL_PREFIX}_extraction_scorer_{suffix}.mlpackage"), compute_units=compute_units
314
  )
@@ -523,3 +534,44 @@ class CoreMLBoundaryExtractor(BoundaryExtractor):
523
  return raw
524
  finally:
525
  self._context.reset(token)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  """GLiNER2.5 multilingual extraction runtime using only Core ML trained weights."""
2
 
3
+ import gc
4
  import json
5
+ import threading
6
  from contextvars import ContextVar
7
  from pathlib import Path
8
 
 
294
  class CoreMLBoundaryExtractor(BoundaryExtractor):
295
  """Native GLiNER2 schema/decoder with all trained extraction heads in Core ML."""
296
 
297
+ def __init__(
298
+ self,
299
+ model_dir: str,
300
+ precision: str = "fp32",
301
+ compute_units=ct.ComputeUnit.CPU_ONLY,
302
+ feature_package: str | None = None,
303
+ ):
304
  if precision not in ("fp16", "fp32"):
305
  raise ValueError("precision must be fp16 or fp32")
306
  torch.nn.Module.__init__(self)
 
314
  self.length, self.max_words, self.max_queries = 128, 64, 8
315
  self._context = ContextVar("gliner2_coreml_extraction_context")
316
  suffix = f"{precision}_L128_W64_Q8"
317
+ feature_path = (
318
+ Path(feature_package) if feature_package else Path(f"{MODEL_PREFIX}_extraction_features_{suffix}.mlpackage")
319
  )
320
+ if not feature_path.is_absolute():
321
+ feature_path = folder / feature_path
322
+ self.features_model = ct.models.MLModel(str(feature_path), compute_units=compute_units)
323
  self.scorer_model = ct.models.MLModel(
324
  str(folder / f"{MODEL_PREFIX}_extraction_scorer_{suffix}.mlpackage"), compute_units=compute_units
325
  )
 
534
  return raw
535
  finally:
536
  self._context.reset(token)
537
+
538
+
539
+ class CoreMLAdaptiveBoundaryExtractor:
540
+ """Use an ANE-capable FP16 path and load FP32 for sensitive latent records."""
541
+
542
+ def __init__(
543
+ self,
544
+ model_dir: str,
545
+ fp16_feature_package: str = "gliner2_multi_extraction_features_w8_embedding_linear_fp16_L128_W64_Q8.mlpackage",
546
+ fp32_feature_package: str = "gliner2_multi_extraction_features_w8_embedding_fp32_L128_W64_Q8.mlpackage",
547
+ fp16_compute_units=ct.ComputeUnit.CPU_AND_NE,
548
+ fp32_compute_units=ct.ComputeUnit.ALL,
549
+ ):
550
+ self.model_dir = model_dir
551
+ self.feature_packages = {"fp16": fp16_feature_package, "fp32": fp32_feature_package}
552
+ self.compute_units = {"fp16": fp16_compute_units, "fp32": fp32_compute_units}
553
+ self._lock = threading.Lock()
554
+ self._active_precision = None
555
+ self._runtime = None
556
+
557
+ @staticmethod
558
+ def selected_precision(schema) -> str:
559
+ built = schema.build() if hasattr(schema, "build") else schema
560
+ records = built.get("record_metadata", {})
561
+ return "fp32" if any(details.get("mode") == "latent" for details in records.values()) else "fp16"
562
+
563
+ def extract(self, text: str, schema, **kwargs):
564
+ precision = self.selected_precision(schema)
565
+ with self._lock:
566
+ if self._active_precision != precision:
567
+ self._runtime = None
568
+ self._active_precision = None
569
+ gc.collect()
570
+ self._runtime = CoreMLBoundaryExtractor(
571
+ self.model_dir,
572
+ precision=precision,
573
+ compute_units=self.compute_units[precision],
574
+ feature_package=self.feature_packages[precision],
575
+ )
576
+ self._active_precision = precision
577
+ return self._runtime.extract(text, schema, **kwargs)
gliner2_multi_explicit_fp16_W64_Q8_S64.mlpackage/Data/com.apple.CoreML/model.mlmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3d31baa0cbe241bbdb574c313447ccd47856c8fd9aa10ee3f7b7887f62050a30
3
+ size 61018
gliner2_multi_explicit_fp16_W64_Q8_S64.mlpackage/Data/com.apple.CoreML/weights/weight.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:17e65c6985fd285abe612bf07fb1b282206c3070d1e229fd0bbf60285e21c169
3
+ size 552384
gliner2_multi_explicit_fp16_W64_Q8_S64.mlpackage/Manifest.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "fileFormatVersion": "1.0.0",
3
+ "itemInfoEntries": {
4
+ "068DC8E0-90EB-42DD-8F33-7982DACB8D19": {
5
+ "author": "com.apple.CoreML",
6
+ "description": "CoreML Model Weights",
7
+ "name": "weights",
8
+ "path": "com.apple.CoreML/weights"
9
+ },
10
+ "50E9D2C3-E3A2-4606-84DA-545DE72B9175": {
11
+ "author": "com.apple.CoreML",
12
+ "description": "CoreML Model Specification",
13
+ "name": "model.mlmodel",
14
+ "path": "com.apple.CoreML/model.mlmodel"
15
+ }
16
+ },
17
+ "rootModelIdentifier": "50E9D2C3-E3A2-4606-84DA-545DE72B9175"
18
+ }
gliner2_multi_extraction_features_w8_embedding_fp32_L128_W64_Q8.mlpackage/Data/com.apple.CoreML/model.mlmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1be2896ededfe989d059b021a4b3213ce4156e9756bbe85b78e867d11a0ac65c
3
+ size 1013141
gliner2_multi_extraction_features_w8_embedding_fp32_L128_W64_Q8.mlpackage/Data/com.apple.CoreML/weights/weight.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ddef5f5a28b195ce0e9ab5e4cd6f83b42e2e71c151d6d74583754e80807c8ebc
3
+ size 579972932
gliner2_multi_extraction_features_w8_embedding_fp32_L128_W64_Q8.mlpackage/Manifest.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "fileFormatVersion": "1.0.0",
3
+ "itemInfoEntries": {
4
+ "4F0F74D8-656E-47AF-A5D6-AB711305E757": {
5
+ "author": "com.apple.CoreML",
6
+ "description": "CoreML Model Weights",
7
+ "name": "weights",
8
+ "path": "com.apple.CoreML/weights"
9
+ },
10
+ "C3D0DBEC-B355-4E71-A511-43CD347367D2": {
11
+ "author": "com.apple.CoreML",
12
+ "description": "CoreML Model Specification",
13
+ "name": "model.mlmodel",
14
+ "path": "com.apple.CoreML/model.mlmodel"
15
+ }
16
+ },
17
+ "rootModelIdentifier": "C3D0DBEC-B355-4E71-A511-43CD347367D2"
18
+ }
gliner2_multi_extraction_features_w8_embedding_linear_fp16_L128_W64_Q8.mlpackage/Data/com.apple.CoreML/model.mlmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:25c0251fbbd22083eb156897c4dd075315f18326f6c2e5107463882175db8f5a
3
+ size 1046842
gliner2_multi_extraction_features_w8_embedding_linear_fp16_L128_W64_Q8.mlpackage/Data/com.apple.CoreML/weights/weight.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:04ec00914fcd74e7a6d6f827832621e8d4d8a317eb96a934c0ef93aa46955533
3
+ size 386163328
gliner2_multi_extraction_features_w8_embedding_linear_fp16_L128_W64_Q8.mlpackage/Manifest.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "fileFormatVersion": "1.0.0",
3
+ "itemInfoEntries": {
4
+ "937DBF58-428C-4184-8844-12BD2D883346": {
5
+ "author": "com.apple.CoreML",
6
+ "description": "CoreML Model Weights",
7
+ "name": "weights",
8
+ "path": "com.apple.CoreML/weights"
9
+ },
10
+ "9651876E-BF7E-47F1-ACB3-1FEC7310D019": {
11
+ "author": "com.apple.CoreML",
12
+ "description": "CoreML Model Specification",
13
+ "name": "model.mlmodel",
14
+ "path": "com.apple.CoreML/model.mlmodel"
15
+ }
16
+ },
17
+ "rootModelIdentifier": "9651876E-BF7E-47F1-ACB3-1FEC7310D019"
18
+ }
gliner2_multi_extraction_scorer_fp16_L128_W64_Q8.mlpackage/Data/com.apple.CoreML/model.mlmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7989c66ba0d0282b5359243006fdf007e3d996eb7ae8e7f9b10a46f8f0fd7add
3
+ size 42962
gliner2_multi_extraction_scorer_fp16_L128_W64_Q8.mlpackage/Data/com.apple.CoreML/weights/weight.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:524efc11ca823cad23cc996d34cc7b3baec66bd2bccdf5d3047f83998e77e234
3
+ size 859584
gliner2_multi_extraction_scorer_fp16_L128_W64_Q8.mlpackage/Manifest.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "fileFormatVersion": "1.0.0",
3
+ "itemInfoEntries": {
4
+ "862F0AAB-6907-4EFE-B162-BCD0DC04BCC9": {
5
+ "author": "com.apple.CoreML",
6
+ "description": "CoreML Model Specification",
7
+ "name": "model.mlmodel",
8
+ "path": "com.apple.CoreML/model.mlmodel"
9
+ },
10
+ "F75CC32C-8F22-467F-9870-F7793C4AB9F3": {
11
+ "author": "com.apple.CoreML",
12
+ "description": "CoreML Model Weights",
13
+ "name": "weights",
14
+ "path": "com.apple.CoreML/weights"
15
+ }
16
+ },
17
+ "rootModelIdentifier": "862F0AAB-6907-4EFE-B162-BCD0DC04BCC9"
18
+ }
gliner2_multi_record_anchorless_fp16_F8_C192_I1536.mlpackage/Data/com.apple.CoreML/model.mlmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1d84a0d996a5579fa1bb29da6ce2eec94e4c9e9c8da2265069f52dbc81aeec2c
3
+ size 6567
gliner2_multi_record_anchorless_fp16_F8_C192_I1536.mlpackage/Data/com.apple.CoreML/weights/weight.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2bd01de3587d6e381bfc7809b59d3555c7a06f2afad05ccfb4d02e6aec6c20e2
3
+ size 1435840
gliner2_multi_record_anchorless_fp16_F8_C192_I1536.mlpackage/Manifest.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "fileFormatVersion": "1.0.0",
3
+ "itemInfoEntries": {
4
+ "7A4F6CCD-69F2-458F-8FCD-B670B28DC042": {
5
+ "author": "com.apple.CoreML",
6
+ "description": "CoreML Model Weights",
7
+ "name": "weights",
8
+ "path": "com.apple.CoreML/weights"
9
+ },
10
+ "8FBF52FE-F3B3-4501-9097-AE8B00E84BEF": {
11
+ "author": "com.apple.CoreML",
12
+ "description": "CoreML Model Specification",
13
+ "name": "model.mlmodel",
14
+ "path": "com.apple.CoreML/model.mlmodel"
15
+ }
16
+ },
17
+ "rootModelIdentifier": "8FBF52FE-F3B3-4501-9097-AE8B00E84BEF"
18
+ }
gliner2_multi_record_assignment_fp16_F8_C192_I1536.mlpackage/Data/com.apple.CoreML/model.mlmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e55b74b80ff515dca663ba4495d4cb0e1dfae0793f7cb85703b13dd6e237f3f8
3
+ size 9990
gliner2_multi_record_assignment_fp16_F8_C192_I1536.mlpackage/Data/com.apple.CoreML/weights/weight.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4c3714dc4a90bd2cba48e355d0d37b4bec91c9e98c1d9015b107820d8809bf8c
3
+ size 594560
gliner2_multi_record_assignment_fp16_F8_C192_I1536.mlpackage/Manifest.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "fileFormatVersion": "1.0.0",
3
+ "itemInfoEntries": {
4
+ "CC8E1A07-36C7-4543-98F7-03F7A93F2AE2": {
5
+ "author": "com.apple.CoreML",
6
+ "description": "CoreML Model Specification",
7
+ "name": "model.mlmodel",
8
+ "path": "com.apple.CoreML/model.mlmodel"
9
+ },
10
+ "D1C61619-5A81-4BDB-BB3B-0AD1A79495E6": {
11
+ "author": "com.apple.CoreML",
12
+ "description": "CoreML Model Weights",
13
+ "name": "weights",
14
+ "path": "com.apple.CoreML/weights"
15
+ }
16
+ },
17
+ "rootModelIdentifier": "CC8E1A07-36C7-4543-98F7-03F7A93F2AE2"
18
+ }
gliner2_multi_relation_fp16_W64_R4_P256.mlpackage/Data/com.apple.CoreML/model.mlmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:97177682648258bb98feea081f1159d8eaf90d083c8bb32b7149db253240ac3d
3
+ size 32419
gliner2_multi_relation_fp16_W64_R4_P256.mlpackage/Data/com.apple.CoreML/weights/weight.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d335d72da6645bad3de8c53f0f1cabaf872a1f8c59b24b4b88cd03fc30922e08
3
+ size 11815680
gliner2_multi_relation_fp16_W64_R4_P256.mlpackage/Manifest.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "fileFormatVersion": "1.0.0",
3
+ "itemInfoEntries": {
4
+ "60DAEA11-225A-4AD1-8A6E-767B703A328B": {
5
+ "author": "com.apple.CoreML",
6
+ "description": "CoreML Model Specification",
7
+ "name": "model.mlmodel",
8
+ "path": "com.apple.CoreML/model.mlmodel"
9
+ },
10
+ "906F9000-F74E-4C25-9045-91571D6214F4": {
11
+ "author": "com.apple.CoreML",
12
+ "description": "CoreML Model Weights",
13
+ "name": "weights",
14
+ "path": "com.apple.CoreML/weights"
15
+ }
16
+ },
17
+ "rootModelIdentifier": "60DAEA11-225A-4AD1-8A6E-767B703A328B"
18
+ }
quantize-extraction-coreml.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Quantize the trained GLiNER2.5 encoder embedding without changing other heads."""
2
+
3
+ import argparse
4
+ import hashlib
5
+ import json
6
+ import shutil
7
+ import time
8
+ from pathlib import Path
9
+
10
+ import coremltools as ct
11
+ from coremltools.optimize.coreml import (
12
+ OpLinearQuantizerConfig,
13
+ OptimizationConfig,
14
+ get_weights_metadata,
15
+ linear_quantize_weights,
16
+ )
17
+
18
+
19
+ def package_files(package: Path):
20
+ """Return exact byte sizes and hashes for the files in a Core ML package."""
21
+ files = []
22
+ for file in sorted(item for item in package.rglob("*") if item.is_file()):
23
+ digest = hashlib.sha256()
24
+ with file.open("rb") as stream:
25
+ for chunk in iter(lambda: stream.read(8 * 1024 * 1024), b""):
26
+ digest.update(chunk)
27
+ files.append(
28
+ {"path": str(file.relative_to(package)), "bytes": file.stat().st_size, "sha256": digest.hexdigest()}
29
+ )
30
+ return files
31
+
32
+
33
+ def embedding_weight_name(model) -> tuple[str, tuple[int, ...], str]:
34
+ """Locate the real checkpoint's word embedding constant in an ML Program."""
35
+ metadata = get_weights_metadata(model)
36
+ matches = [name for name in metadata if name.startswith("encoder_embeddings_word_embeddings_weight")]
37
+ if len(matches) != 1:
38
+ raise ValueError(f"Expected one trained word embedding constant, found {matches}")
39
+ name = matches[0]
40
+ weight = metadata[name].val
41
+ if weight.ndim != 2 or weight.shape[0] < 65_536:
42
+ raise ValueError(f"Unexpected word embedding shape: {weight.shape}")
43
+ return name, tuple(weight.shape), str(weight.dtype)
44
+
45
+
46
+ def main():
47
+ parser = argparse.ArgumentParser()
48
+ parser.add_argument("--source", required=True, type=Path)
49
+ parser.add_argument("--output", required=True, type=Path)
50
+ parser.add_argument("--mode", choices=["linear_symmetric", "linear"], default="linear_symmetric")
51
+ args = parser.parse_args()
52
+ source, output = args.source.resolve(), args.output.resolve()
53
+ if source == output:
54
+ raise ValueError("Quantized output must differ from the source package")
55
+ started = time.perf_counter()
56
+ model = ct.models.MLModel(str(source), skip_model_load=True)
57
+ name, shape, dtype = embedding_weight_name(model)
58
+ config = OptimizationConfig(
59
+ op_name_configs={name: OpLinearQuantizerConfig(mode=args.mode, dtype="int8", granularity="per_channel")}
60
+ )
61
+ compressed = linear_quantize_weights(model, config=config)
62
+ compressed.short_description = f"{model.short_description}; W8 {args.mode} per-channel word embedding"
63
+ compressed.author = model.author
64
+ compressed.license = model.license
65
+ compressed.user_defined_metadata.update(model.user_defined_metadata)
66
+ compressed.user_defined_metadata["weight_compression"] = f"W8 {args.mode} per-channel word embedding"
67
+ compressed.user_defined_metadata["quantized_weight"] = name
68
+ output.parent.mkdir(parents=True, exist_ok=True)
69
+ if output.exists():
70
+ shutil.rmtree(output)
71
+ compressed.save(str(output))
72
+ source_files, output_files = package_files(source), package_files(output)
73
+ source_bytes = sum(file["bytes"] for file in source_files)
74
+ output_bytes = sum(file["bytes"] for file in output_files)
75
+ if output_bytes >= source_bytes:
76
+ raise RuntimeError("Quantized package is not smaller than its source")
77
+ report = {
78
+ "source_package": source.name,
79
+ "output_package": output.name,
80
+ "selected_weight": name,
81
+ "weight_shape": shape,
82
+ "source_dtype": dtype,
83
+ "method": f"{args.mode} int8 per-channel weight-only",
84
+ "source_bytes": source_bytes,
85
+ "output_bytes": output_bytes,
86
+ "seconds": time.perf_counter() - started,
87
+ "coremltools": ct.__version__,
88
+ "output_files": output_files,
89
+ }
90
+ output.with_suffix(".json").write_text(json.dumps(report, indent=2) + "\n")
91
+ print(json.dumps({key: value for key, value in report.items() if key != "output_files"}, indent=2))
92
+
93
+
94
+ if __name__ == "__main__":
95
+ main()
reports/extraction-w8-adaptive.json ADDED
@@ -0,0 +1,478 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "source_revision": "a221b77a8baf4a613b8f8652661d41fa10a5641e",
3
+ "selected_manifest": "fifteen pinned real-text schema fixtures, not a Decision Index score",
4
+ "variant": "adaptive-w8-embedding",
5
+ "matched": 15,
6
+ "total": 15,
7
+ "cases": [
8
+ {
9
+ "name": "entities",
10
+ "route": "fp16",
11
+ "structure_match": true,
12
+ "maximum_confidence_error": 4.106760025024414e-05,
13
+ "coreml": {
14
+ "entities": {
15
+ "person": [
16
+ {
17
+ "text": "Alice",
18
+ "confidence": 0.9971439242362976,
19
+ "start": 0,
20
+ "end": 5
21
+ }
22
+ ],
23
+ "organization": [
24
+ {
25
+ "text": "Acme",
26
+ "confidence": 0.9936610460281372,
27
+ "start": 14,
28
+ "end": 18
29
+ }
30
+ ],
31
+ "location": [
32
+ {
33
+ "text": "Toronto",
34
+ "confidence": 0.9991735816001892,
35
+ "start": 22,
36
+ "end": 29
37
+ }
38
+ ]
39
+ }
40
+ }
41
+ },
42
+ {
43
+ "name": "entities_multi",
44
+ "route": "fp16",
45
+ "structure_match": true,
46
+ "maximum_confidence_error": 5.6624412536621094e-05,
47
+ "coreml": {
48
+ "entities": {
49
+ "person": [
50
+ {
51
+ "text": "Marie Curie",
52
+ "confidence": 0.9977656602859497,
53
+ "start": 0,
54
+ "end": 11
55
+ }
56
+ ],
57
+ "city": [
58
+ {
59
+ "text": "Paris",
60
+ "confidence": 0.9993040561676025,
61
+ "start": 45,
62
+ "end": 50
63
+ },
64
+ {
65
+ "text": "Warsaw",
66
+ "confidence": 0.998807430267334,
67
+ "start": 24,
68
+ "end": 30
69
+ }
70
+ ]
71
+ }
72
+ }
73
+ },
74
+ {
75
+ "name": "relations",
76
+ "route": "fp16",
77
+ "structure_match": true,
78
+ "maximum_confidence_error": 0.003583967685699463,
79
+ "coreml": {
80
+ "relation_extraction": {
81
+ "founded": [
82
+ {
83
+ "head": {
84
+ "text": "Alice",
85
+ "start": 0,
86
+ "end": 5,
87
+ "confidence": 0.6171665787696838
88
+ },
89
+ "tail": {
90
+ "text": "Acme",
91
+ "start": 14,
92
+ "end": 18,
93
+ "confidence": 0.6171665787696838
94
+ }
95
+ }
96
+ ]
97
+ }
98
+ }
99
+ },
100
+ {
101
+ "name": "relations_two",
102
+ "route": "fp16",
103
+ "structure_match": true,
104
+ "maximum_confidence_error": 0.0041626691818237305,
105
+ "coreml": {
106
+ "relation_extraction": {
107
+ "founded": [
108
+ {
109
+ "head": {
110
+ "text": "Steve Jobs",
111
+ "start": 0,
112
+ "end": 10,
113
+ "confidence": 0.684580385684967
114
+ },
115
+ "tail": {
116
+ "text": "Apple",
117
+ "start": 19,
118
+ "end": 24,
119
+ "confidence": 0.684580385684967
120
+ }
121
+ }
122
+ ]
123
+ }
124
+ }
125
+ },
126
+ {
127
+ "name": "record_natural",
128
+ "route": "fp16",
129
+ "structure_match": true,
130
+ "maximum_confidence_error": 0.016394853591918945,
131
+ "coreml": {
132
+ "employment": [
133
+ {
134
+ "person": {
135
+ "text": "Alice",
136
+ "confidence": 0.9884026646614075,
137
+ "start": 0,
138
+ "end": 5
139
+ },
140
+ "company": {
141
+ "text": "Acme",
142
+ "confidence": 0.8344069123268127,
143
+ "start": 15,
144
+ "end": 19
145
+ }
146
+ },
147
+ {
148
+ "person": {
149
+ "text": "Bob",
150
+ "confidence": 0.9633687138557434,
151
+ "start": 21,
152
+ "end": 24
153
+ },
154
+ "company": {
155
+ "text": "Beta",
156
+ "confidence": 0.6842640042304993,
157
+ "start": 34,
158
+ "end": 38
159
+ }
160
+ }
161
+ ]
162
+ }
163
+ },
164
+ {
165
+ "name": "record_latent",
166
+ "route": "fp32",
167
+ "structure_match": true,
168
+ "maximum_confidence_error": 0.004612326622009277,
169
+ "coreml": {
170
+ "employment": [
171
+ {
172
+ "person": null,
173
+ "company": {
174
+ "text": "Beta",
175
+ "confidence": 0.5833683609962463,
176
+ "start": 34,
177
+ "end": 38
178
+ }
179
+ },
180
+ {
181
+ "person": null,
182
+ "company": {
183
+ "text": "Acme",
184
+ "confidence": 0.6160218119621277,
185
+ "start": 15,
186
+ "end": 19
187
+ }
188
+ },
189
+ {
190
+ "person": {
191
+ "text": "Alice",
192
+ "confidence": 0.9673200249671936,
193
+ "start": 0,
194
+ "end": 5
195
+ },
196
+ "company": null
197
+ },
198
+ {
199
+ "person": null,
200
+ "company": {
201
+ "text": "Beta",
202
+ "confidence": 0.8807083368301392,
203
+ "start": 34,
204
+ "end": 38
205
+ }
206
+ },
207
+ {
208
+ "person": null,
209
+ "company": {
210
+ "text": "Beta",
211
+ "confidence": 0.8964732885360718,
212
+ "start": 34,
213
+ "end": 38
214
+ }
215
+ },
216
+ {
217
+ "person": null,
218
+ "company": {
219
+ "text": "Beta",
220
+ "confidence": 0.7156234383583069,
221
+ "start": 34,
222
+ "end": 38
223
+ }
224
+ },
225
+ {
226
+ "person": null,
227
+ "company": {
228
+ "text": "Acme",
229
+ "confidence": 0.6515812277793884,
230
+ "start": 15,
231
+ "end": 19
232
+ }
233
+ },
234
+ {
235
+ "person": {
236
+ "text": "Bob",
237
+ "confidence": 0.5077791213989258,
238
+ "start": 21,
239
+ "end": 24
240
+ },
241
+ "company": null
242
+ },
243
+ {
244
+ "person": null,
245
+ "company": {
246
+ "text": "Beta",
247
+ "confidence": 0.534905731678009,
248
+ "start": 34,
249
+ "end": 38
250
+ }
251
+ },
252
+ {
253
+ "person": null,
254
+ "company": {
255
+ "text": "Acme",
256
+ "confidence": 0.6060499548912048,
257
+ "start": 15,
258
+ "end": 19
259
+ }
260
+ }
261
+ ]
262
+ }
263
+ },
264
+ {
265
+ "name": "record_anchorless",
266
+ "route": "fp16",
267
+ "structure_match": true,
268
+ "maximum_confidence_error": null,
269
+ "coreml": {}
270
+ },
271
+ {
272
+ "name": "attributes",
273
+ "route": "fp16",
274
+ "structure_match": true,
275
+ "maximum_confidence_error": 0.0003001689910888672,
276
+ "coreml": {
277
+ "entities": {
278
+ "person": [
279
+ {
280
+ "text": "Alice",
281
+ "confidence": 0.9936610460281372,
282
+ "start": 0,
283
+ "end": 5,
284
+ "role": {
285
+ "label": "founder",
286
+ "confidence": 0.9998525381088257
287
+ }
288
+ }
289
+ ],
290
+ "organization": [
291
+ {
292
+ "text": "Acme",
293
+ "confidence": 0.9911182522773743,
294
+ "start": 14,
295
+ "end": 18,
296
+ "role": {
297
+ "label": "founder",
298
+ "confidence": 0.9902054667472839
299
+ }
300
+ }
301
+ ]
302
+ }
303
+ }
304
+ },
305
+ {
306
+ "name": "mixed_classification",
307
+ "route": "fp16",
308
+ "structure_match": true,
309
+ "maximum_confidence_error": 0.00011724233627319336,
310
+ "coreml": {
311
+ "entities": {
312
+ "person": [
313
+ {
314
+ "text": "Alice",
315
+ "confidence": 0.9987744688987732,
316
+ "start": 0,
317
+ "end": 5
318
+ }
319
+ ],
320
+ "organization": [
321
+ {
322
+ "text": "Acme",
323
+ "confidence": 0.996292769908905,
324
+ "start": 14,
325
+ "end": 18
326
+ }
327
+ ]
328
+ },
329
+ "sentiment": {
330
+ "label": "positive",
331
+ "confidence": 0.995216965675354
332
+ }
333
+ }
334
+ },
335
+ {
336
+ "name": "classification_only",
337
+ "route": "fp16",
338
+ "structure_match": true,
339
+ "maximum_confidence_error": 0.011062800884246826,
340
+ "coreml": {
341
+ "topic": {
342
+ "label": "sports",
343
+ "confidence": 0.49501627683639526
344
+ }
345
+ }
346
+ },
347
+ {
348
+ "name": "enum_choice",
349
+ "route": "fp16",
350
+ "structure_match": true,
351
+ "maximum_confidence_error": 1.0728836059570312e-06,
352
+ "coreml": {
353
+ "product": [
354
+ {
355
+ "category": {
356
+ "text": "electronics",
357
+ "confidence": 0.9997695088386536
358
+ }
359
+ }
360
+ ]
361
+ }
362
+ },
363
+ {
364
+ "name": "es_entities",
365
+ "route": "fp16",
366
+ "structure_match": true,
367
+ "maximum_confidence_error": 0.00017154216766357422,
368
+ "coreml": {
369
+ "entities": {
370
+ "persona": [
371
+ {
372
+ "text": "María",
373
+ "confidence": 0.9971216320991516,
374
+ "start": 0,
375
+ "end": 5
376
+ }
377
+ ],
378
+ "organización": [
379
+ {
380
+ "text": "Telefónica",
381
+ "confidence": 0.9901782274246216,
382
+ "start": 29,
383
+ "end": 39
384
+ }
385
+ ],
386
+ "ciudad": [
387
+ {
388
+ "text": "Madrid",
389
+ "confidence": 0.9994946718215942,
390
+ "start": 17,
391
+ "end": 23
392
+ }
393
+ ]
394
+ }
395
+ }
396
+ },
397
+ {
398
+ "name": "fr_entities",
399
+ "route": "fp16",
400
+ "structure_match": true,
401
+ "maximum_confidence_error": 3.17692756652832e-05,
402
+ "coreml": {
403
+ "entities": {
404
+ "personne": [
405
+ {
406
+ "text": "Jean Dupont",
407
+ "confidence": 0.9973061084747314,
408
+ "start": 0,
409
+ "end": 11
410
+ }
411
+ ],
412
+ "entreprise": [
413
+ {
414
+ "text": "Airbus",
415
+ "confidence": 0.9904406070709229,
416
+ "start": 35,
417
+ "end": 41
418
+ }
419
+ ],
420
+ "ville": [
421
+ {
422
+ "text": "Paris",
423
+ "confidence": 0.9991990923881531,
424
+ "start": 24,
425
+ "end": 29
426
+ }
427
+ ]
428
+ }
429
+ }
430
+ },
431
+ {
432
+ "name": "zh_entities",
433
+ "route": "fp16",
434
+ "structure_match": true,
435
+ "maximum_confidence_error": 0.00035566091537475586,
436
+ "coreml": {
437
+ "entities": {
438
+ "人物": [
439
+ {
440
+ "text": "张伟在北京大学工作",
441
+ "confidence": 0.9429286122322083,
442
+ "start": 0,
443
+ "end": 9
444
+ }
445
+ ],
446
+ "机构": [],
447
+ "地点": []
448
+ }
449
+ }
450
+ },
451
+ {
452
+ "name": "de_entities",
453
+ "route": "fp16",
454
+ "structure_match": true,
455
+ "maximum_confidence_error": 3.6716461181640625e-05,
456
+ "coreml": {
457
+ "entities": {
458
+ "Person": [
459
+ {
460
+ "text": "Angela Merkel",
461
+ "confidence": 0.9967142343521118,
462
+ "start": 0,
463
+ "end": 13
464
+ }
465
+ ],
466
+ "Stadt": [
467
+ {
468
+ "text": "Berlin",
469
+ "confidence": 0.9987056255340576,
470
+ "start": 23,
471
+ "end": 29
472
+ }
473
+ ]
474
+ }
475
+ }
476
+ }
477
+ ]
478
+ }
reports/extraction-w8-fp16_cpu_ne.json ADDED
@@ -0,0 +1,470 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "source_revision": "a221b77a8baf4a613b8f8652661d41fa10a5641e",
3
+ "selected_manifest": "pinned real-text schema fixtures, not a Decision Index score",
4
+ "feature_package": "gliner2_multi_extraction_features_w8_embedding_linear_fp16_L128_W64_Q8.mlpackage",
5
+ "precision": "fp16",
6
+ "compute_units": "cpu_and_neural_engine",
7
+ "matched": 14,
8
+ "total": 15,
9
+ "cases": [
10
+ {
11
+ "name": "entities",
12
+ "structure_match": true,
13
+ "maximum_confidence_error": 4.106760025024414e-05,
14
+ "coreml": {
15
+ "entities": {
16
+ "person": [
17
+ {
18
+ "text": "Alice",
19
+ "confidence": 0.9971439242362976,
20
+ "start": 0,
21
+ "end": 5
22
+ }
23
+ ],
24
+ "organization": [
25
+ {
26
+ "text": "Acme",
27
+ "confidence": 0.9936610460281372,
28
+ "start": 14,
29
+ "end": 18
30
+ }
31
+ ],
32
+ "location": [
33
+ {
34
+ "text": "Toronto",
35
+ "confidence": 0.9991735816001892,
36
+ "start": 22,
37
+ "end": 29
38
+ }
39
+ ]
40
+ }
41
+ }
42
+ },
43
+ {
44
+ "name": "entities_multi",
45
+ "structure_match": true,
46
+ "maximum_confidence_error": 5.6624412536621094e-05,
47
+ "coreml": {
48
+ "entities": {
49
+ "person": [
50
+ {
51
+ "text": "Marie Curie",
52
+ "confidence": 0.9977656602859497,
53
+ "start": 0,
54
+ "end": 11
55
+ }
56
+ ],
57
+ "city": [
58
+ {
59
+ "text": "Paris",
60
+ "confidence": 0.9993040561676025,
61
+ "start": 45,
62
+ "end": 50
63
+ },
64
+ {
65
+ "text": "Warsaw",
66
+ "confidence": 0.998807430267334,
67
+ "start": 24,
68
+ "end": 30
69
+ }
70
+ ]
71
+ }
72
+ }
73
+ },
74
+ {
75
+ "name": "relations",
76
+ "structure_match": true,
77
+ "maximum_confidence_error": 0.003583967685699463,
78
+ "coreml": {
79
+ "relation_extraction": {
80
+ "founded": [
81
+ {
82
+ "head": {
83
+ "text": "Alice",
84
+ "start": 0,
85
+ "end": 5,
86
+ "confidence": 0.6171665787696838
87
+ },
88
+ "tail": {
89
+ "text": "Acme",
90
+ "start": 14,
91
+ "end": 18,
92
+ "confidence": 0.6171665787696838
93
+ }
94
+ }
95
+ ]
96
+ }
97
+ }
98
+ },
99
+ {
100
+ "name": "relations_two",
101
+ "structure_match": true,
102
+ "maximum_confidence_error": 0.0041626691818237305,
103
+ "coreml": {
104
+ "relation_extraction": {
105
+ "founded": [
106
+ {
107
+ "head": {
108
+ "text": "Steve Jobs",
109
+ "start": 0,
110
+ "end": 10,
111
+ "confidence": 0.684580385684967
112
+ },
113
+ "tail": {
114
+ "text": "Apple",
115
+ "start": 19,
116
+ "end": 24,
117
+ "confidence": 0.684580385684967
118
+ }
119
+ }
120
+ ]
121
+ }
122
+ }
123
+ },
124
+ {
125
+ "name": "record_natural",
126
+ "structure_match": true,
127
+ "maximum_confidence_error": 0.016394853591918945,
128
+ "coreml": {
129
+ "employment": [
130
+ {
131
+ "person": {
132
+ "text": "Alice",
133
+ "confidence": 0.9884026646614075,
134
+ "start": 0,
135
+ "end": 5
136
+ },
137
+ "company": {
138
+ "text": "Acme",
139
+ "confidence": 0.8344069123268127,
140
+ "start": 15,
141
+ "end": 19
142
+ }
143
+ },
144
+ {
145
+ "person": {
146
+ "text": "Bob",
147
+ "confidence": 0.9633687138557434,
148
+ "start": 21,
149
+ "end": 24
150
+ },
151
+ "company": {
152
+ "text": "Beta",
153
+ "confidence": 0.6842640042304993,
154
+ "start": 34,
155
+ "end": 38
156
+ }
157
+ }
158
+ ]
159
+ }
160
+ },
161
+ {
162
+ "name": "record_latent",
163
+ "structure_match": false,
164
+ "maximum_confidence_error": 0.025238752365112305,
165
+ "coreml": {
166
+ "employment": [
167
+ {
168
+ "person": null,
169
+ "company": {
170
+ "text": "Beta",
171
+ "confidence": 0.596550703048706,
172
+ "start": 34,
173
+ "end": 38
174
+ }
175
+ },
176
+ {
177
+ "person": null,
178
+ "company": {
179
+ "text": "Beta",
180
+ "confidence": 0.6208515167236328,
181
+ "start": 34,
182
+ "end": 38
183
+ }
184
+ },
185
+ {
186
+ "person": {
187
+ "text": "Alice",
188
+ "confidence": 0.9655764698982239,
189
+ "start": 0,
190
+ "end": 5
191
+ },
192
+ "company": null
193
+ },
194
+ {
195
+ "person": null,
196
+ "company": {
197
+ "text": "Beta",
198
+ "confidence": 0.8875949382781982,
199
+ "start": 34,
200
+ "end": 38
201
+ }
202
+ },
203
+ {
204
+ "person": null,
205
+ "company": {
206
+ "text": "Beta",
207
+ "confidence": 0.9059898257255554,
208
+ "start": 34,
209
+ "end": 38
210
+ }
211
+ },
212
+ {
213
+ "person": null,
214
+ "company": {
215
+ "text": "Beta",
216
+ "confidence": 0.7203682661056519,
217
+ "start": 34,
218
+ "end": 38
219
+ }
220
+ },
221
+ {
222
+ "person": null,
223
+ "company": {
224
+ "text": "Acme",
225
+ "confidence": 0.6446726322174072,
226
+ "start": 15,
227
+ "end": 19
228
+ }
229
+ },
230
+ {
231
+ "person": {
232
+ "text": "Bob",
233
+ "confidence": 0.5009422302246094,
234
+ "start": 21,
235
+ "end": 24
236
+ },
237
+ "company": {
238
+ "text": "Beta",
239
+ "confidence": 0.5094440579414368,
240
+ "start": 34,
241
+ "end": 38
242
+ }
243
+ },
244
+ {
245
+ "person": null,
246
+ "company": {
247
+ "text": "Beta",
248
+ "confidence": 0.5506979823112488,
249
+ "start": 34,
250
+ "end": 38
251
+ }
252
+ },
253
+ {
254
+ "person": null,
255
+ "company": {
256
+ "text": "Acme",
257
+ "confidence": 0.6359010338783264,
258
+ "start": 15,
259
+ "end": 19
260
+ }
261
+ }
262
+ ]
263
+ }
264
+ },
265
+ {
266
+ "name": "record_anchorless",
267
+ "structure_match": true,
268
+ "maximum_confidence_error": null,
269
+ "coreml": {}
270
+ },
271
+ {
272
+ "name": "attributes",
273
+ "structure_match": true,
274
+ "maximum_confidence_error": 0.0003001689910888672,
275
+ "coreml": {
276
+ "entities": {
277
+ "person": [
278
+ {
279
+ "text": "Alice",
280
+ "confidence": 0.9936610460281372,
281
+ "start": 0,
282
+ "end": 5,
283
+ "role": {
284
+ "label": "founder",
285
+ "confidence": 0.9998525381088257
286
+ }
287
+ }
288
+ ],
289
+ "organization": [
290
+ {
291
+ "text": "Acme",
292
+ "confidence": 0.9911182522773743,
293
+ "start": 14,
294
+ "end": 18,
295
+ "role": {
296
+ "label": "founder",
297
+ "confidence": 0.9902054667472839
298
+ }
299
+ }
300
+ ]
301
+ }
302
+ }
303
+ },
304
+ {
305
+ "name": "mixed_classification",
306
+ "structure_match": true,
307
+ "maximum_confidence_error": 0.00011724233627319336,
308
+ "coreml": {
309
+ "entities": {
310
+ "person": [
311
+ {
312
+ "text": "Alice",
313
+ "confidence": 0.9987744688987732,
314
+ "start": 0,
315
+ "end": 5
316
+ }
317
+ ],
318
+ "organization": [
319
+ {
320
+ "text": "Acme",
321
+ "confidence": 0.996292769908905,
322
+ "start": 14,
323
+ "end": 18
324
+ }
325
+ ]
326
+ },
327
+ "sentiment": {
328
+ "label": "positive",
329
+ "confidence": 0.995216965675354
330
+ }
331
+ }
332
+ },
333
+ {
334
+ "name": "classification_only",
335
+ "structure_match": true,
336
+ "maximum_confidence_error": 0.011062800884246826,
337
+ "coreml": {
338
+ "topic": {
339
+ "label": "sports",
340
+ "confidence": 0.49501627683639526
341
+ }
342
+ }
343
+ },
344
+ {
345
+ "name": "enum_choice",
346
+ "structure_match": true,
347
+ "maximum_confidence_error": 1.0728836059570312e-06,
348
+ "coreml": {
349
+ "product": [
350
+ {
351
+ "category": {
352
+ "text": "electronics",
353
+ "confidence": 0.9997695088386536
354
+ }
355
+ }
356
+ ]
357
+ }
358
+ },
359
+ {
360
+ "name": "es_entities",
361
+ "structure_match": true,
362
+ "maximum_confidence_error": 0.00017154216766357422,
363
+ "coreml": {
364
+ "entities": {
365
+ "persona": [
366
+ {
367
+ "text": "María",
368
+ "confidence": 0.9971216320991516,
369
+ "start": 0,
370
+ "end": 5
371
+ }
372
+ ],
373
+ "organización": [
374
+ {
375
+ "text": "Telefónica",
376
+ "confidence": 0.9901782274246216,
377
+ "start": 29,
378
+ "end": 39
379
+ }
380
+ ],
381
+ "ciudad": [
382
+ {
383
+ "text": "Madrid",
384
+ "confidence": 0.9994946718215942,
385
+ "start": 17,
386
+ "end": 23
387
+ }
388
+ ]
389
+ }
390
+ }
391
+ },
392
+ {
393
+ "name": "fr_entities",
394
+ "structure_match": true,
395
+ "maximum_confidence_error": 3.17692756652832e-05,
396
+ "coreml": {
397
+ "entities": {
398
+ "personne": [
399
+ {
400
+ "text": "Jean Dupont",
401
+ "confidence": 0.9973061084747314,
402
+ "start": 0,
403
+ "end": 11
404
+ }
405
+ ],
406
+ "entreprise": [
407
+ {
408
+ "text": "Airbus",
409
+ "confidence": 0.9904406070709229,
410
+ "start": 35,
411
+ "end": 41
412
+ }
413
+ ],
414
+ "ville": [
415
+ {
416
+ "text": "Paris",
417
+ "confidence": 0.9991990923881531,
418
+ "start": 24,
419
+ "end": 29
420
+ }
421
+ ]
422
+ }
423
+ }
424
+ },
425
+ {
426
+ "name": "zh_entities",
427
+ "structure_match": true,
428
+ "maximum_confidence_error": 0.00035566091537475586,
429
+ "coreml": {
430
+ "entities": {
431
+ "人物": [
432
+ {
433
+ "text": "张伟在北京大学工作",
434
+ "confidence": 0.9429286122322083,
435
+ "start": 0,
436
+ "end": 9
437
+ }
438
+ ],
439
+ "机构": [],
440
+ "地点": []
441
+ }
442
+ }
443
+ },
444
+ {
445
+ "name": "de_entities",
446
+ "structure_match": true,
447
+ "maximum_confidence_error": 3.6716461181640625e-05,
448
+ "coreml": {
449
+ "entities": {
450
+ "Person": [
451
+ {
452
+ "text": "Angela Merkel",
453
+ "confidence": 0.9967142343521118,
454
+ "start": 0,
455
+ "end": 13
456
+ }
457
+ ],
458
+ "Stadt": [
459
+ {
460
+ "text": "Berlin",
461
+ "confidence": 0.9987056255340576,
462
+ "start": 23,
463
+ "end": 29
464
+ }
465
+ ]
466
+ }
467
+ }
468
+ }
469
+ ]
470
+ }
reports/extraction-w8-fp32_all.json ADDED
@@ -0,0 +1,465 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "source_revision": "a221b77a8baf4a613b8f8652661d41fa10a5641e",
3
+ "selected_manifest": "pinned real-text schema fixtures, not a Decision Index score",
4
+ "feature_package": "gliner2_multi_extraction_features_w8_embedding_fp32_L128_W64_Q8.mlpackage",
5
+ "precision": "fp32",
6
+ "compute_units": "all",
7
+ "matched": 15,
8
+ "total": 15,
9
+ "cases": [
10
+ {
11
+ "name": "entities",
12
+ "structure_match": true,
13
+ "maximum_confidence_error": 6.54458999633789e-05,
14
+ "coreml": {
15
+ "entities": {
16
+ "person": [
17
+ {
18
+ "text": "Alice",
19
+ "confidence": 0.997123658657074,
20
+ "start": 0,
21
+ "end": 5
22
+ }
23
+ ],
24
+ "organization": [
25
+ {
26
+ "text": "Acme",
27
+ "confidence": 0.9936854243278503,
28
+ "start": 14,
29
+ "end": 18
30
+ }
31
+ ],
32
+ "location": [
33
+ {
34
+ "text": "Toronto",
35
+ "confidence": 0.9991651773452759,
36
+ "start": 22,
37
+ "end": 29
38
+ }
39
+ ]
40
+ }
41
+ }
42
+ },
43
+ {
44
+ "name": "entities_multi",
45
+ "structure_match": true,
46
+ "maximum_confidence_error": 1.4603137969970703e-05,
47
+ "coreml": {
48
+ "entities": {
49
+ "person": [
50
+ {
51
+ "text": "Marie Curie",
52
+ "confidence": 0.9977265000343323,
53
+ "start": 0,
54
+ "end": 11
55
+ }
56
+ ],
57
+ "city": [
58
+ {
59
+ "text": "Paris",
60
+ "confidence": 0.9992579817771912,
61
+ "start": 45,
62
+ "end": 50
63
+ },
64
+ {
65
+ "text": "Warsaw",
66
+ "confidence": 0.9987547397613525,
67
+ "start": 24,
68
+ "end": 30
69
+ }
70
+ ]
71
+ }
72
+ }
73
+ },
74
+ {
75
+ "name": "relations",
76
+ "structure_match": true,
77
+ "maximum_confidence_error": 0.0022187232971191406,
78
+ "coreml": {
79
+ "relation_extraction": {
80
+ "founded": [
81
+ {
82
+ "head": {
83
+ "text": "Alice",
84
+ "start": 0,
85
+ "end": 5,
86
+ "confidence": 0.6158013343811035
87
+ },
88
+ "tail": {
89
+ "text": "Acme",
90
+ "start": 14,
91
+ "end": 18,
92
+ "confidence": 0.6158013343811035
93
+ }
94
+ }
95
+ ]
96
+ }
97
+ }
98
+ },
99
+ {
100
+ "name": "relations_two",
101
+ "structure_match": true,
102
+ "maximum_confidence_error": 0.005145668983459473,
103
+ "coreml": {
104
+ "relation_extraction": {
105
+ "founded": [
106
+ {
107
+ "head": {
108
+ "text": "Steve Jobs",
109
+ "start": 0,
110
+ "end": 10,
111
+ "confidence": 0.6938887238502502
112
+ },
113
+ "tail": {
114
+ "text": "Apple",
115
+ "start": 19,
116
+ "end": 24,
117
+ "confidence": 0.6938887238502502
118
+ }
119
+ }
120
+ ]
121
+ }
122
+ }
123
+ },
124
+ {
125
+ "name": "record_natural",
126
+ "structure_match": true,
127
+ "maximum_confidence_error": 0.000523686408996582,
128
+ "coreml": {
129
+ "employment": [
130
+ {
131
+ "person": {
132
+ "text": "Alice",
133
+ "confidence": 0.9879591464996338,
134
+ "start": 0,
135
+ "end": 5
136
+ },
137
+ "company": {
138
+ "text": "Acme",
139
+ "confidence": 0.8242685198783875,
140
+ "start": 15,
141
+ "end": 19
142
+ }
143
+ },
144
+ {
145
+ "person": {
146
+ "text": "Bob",
147
+ "confidence": 0.9649046659469604,
148
+ "start": 21,
149
+ "end": 24
150
+ },
151
+ "company": {
152
+ "text": "Beta",
153
+ "confidence": 0.6673454642295837,
154
+ "start": 34,
155
+ "end": 38
156
+ }
157
+ }
158
+ ]
159
+ }
160
+ },
161
+ {
162
+ "name": "record_latent",
163
+ "structure_match": true,
164
+ "maximum_confidence_error": 0.004612326622009277,
165
+ "coreml": {
166
+ "employment": [
167
+ {
168
+ "person": null,
169
+ "company": {
170
+ "text": "Beta",
171
+ "confidence": 0.5833683609962463,
172
+ "start": 34,
173
+ "end": 38
174
+ }
175
+ },
176
+ {
177
+ "person": null,
178
+ "company": {
179
+ "text": "Acme",
180
+ "confidence": 0.6160218119621277,
181
+ "start": 15,
182
+ "end": 19
183
+ }
184
+ },
185
+ {
186
+ "person": {
187
+ "text": "Alice",
188
+ "confidence": 0.9673200249671936,
189
+ "start": 0,
190
+ "end": 5
191
+ },
192
+ "company": null
193
+ },
194
+ {
195
+ "person": null,
196
+ "company": {
197
+ "text": "Beta",
198
+ "confidence": 0.8807083368301392,
199
+ "start": 34,
200
+ "end": 38
201
+ }
202
+ },
203
+ {
204
+ "person": null,
205
+ "company": {
206
+ "text": "Beta",
207
+ "confidence": 0.8964732885360718,
208
+ "start": 34,
209
+ "end": 38
210
+ }
211
+ },
212
+ {
213
+ "person": null,
214
+ "company": {
215
+ "text": "Beta",
216
+ "confidence": 0.7156234383583069,
217
+ "start": 34,
218
+ "end": 38
219
+ }
220
+ },
221
+ {
222
+ "person": null,
223
+ "company": {
224
+ "text": "Acme",
225
+ "confidence": 0.6515812277793884,
226
+ "start": 15,
227
+ "end": 19
228
+ }
229
+ },
230
+ {
231
+ "person": {
232
+ "text": "Bob",
233
+ "confidence": 0.5077791213989258,
234
+ "start": 21,
235
+ "end": 24
236
+ },
237
+ "company": null
238
+ },
239
+ {
240
+ "person": null,
241
+ "company": {
242
+ "text": "Beta",
243
+ "confidence": 0.534905731678009,
244
+ "start": 34,
245
+ "end": 38
246
+ }
247
+ },
248
+ {
249
+ "person": null,
250
+ "company": {
251
+ "text": "Acme",
252
+ "confidence": 0.6060499548912048,
253
+ "start": 15,
254
+ "end": 19
255
+ }
256
+ }
257
+ ]
258
+ }
259
+ },
260
+ {
261
+ "name": "record_anchorless",
262
+ "structure_match": true,
263
+ "maximum_confidence_error": null,
264
+ "coreml": {}
265
+ },
266
+ {
267
+ "name": "attributes",
268
+ "structure_match": true,
269
+ "maximum_confidence_error": 0.0002243518829345703,
270
+ "coreml": {
271
+ "entities": {
272
+ "person": [
273
+ {
274
+ "text": "Alice",
275
+ "confidence": 0.993528425693512,
276
+ "start": 0,
277
+ "end": 5,
278
+ "role": {
279
+ "label": "founder",
280
+ "confidence": 0.9998435974121094
281
+ }
282
+ }
283
+ ],
284
+ "organization": [
285
+ {
286
+ "text": "Acme",
287
+ "confidence": 0.9913756251335144,
288
+ "start": 14,
289
+ "end": 18,
290
+ "role": {
291
+ "label": "founder",
292
+ "confidence": 0.9896809458732605
293
+ }
294
+ }
295
+ ]
296
+ }
297
+ }
298
+ },
299
+ {
300
+ "name": "mixed_classification",
301
+ "structure_match": true,
302
+ "maximum_confidence_error": 0.00013393163681030273,
303
+ "coreml": {
304
+ "entities": {
305
+ "person": [
306
+ {
307
+ "text": "Alice",
308
+ "confidence": 0.9987138509750366,
309
+ "start": 0,
310
+ "end": 5
311
+ }
312
+ ],
313
+ "organization": [
314
+ {
315
+ "text": "Acme",
316
+ "confidence": 0.996292233467102,
317
+ "start": 14,
318
+ "end": 18
319
+ }
320
+ ]
321
+ },
322
+ "sentiment": {
323
+ "label": "positive",
324
+ "confidence": 0.9952002763748169
325
+ }
326
+ }
327
+ },
328
+ {
329
+ "name": "classification_only",
330
+ "structure_match": true,
331
+ "maximum_confidence_error": 0.02455449104309082,
332
+ "coreml": {
333
+ "topic": {
334
+ "label": "sports",
335
+ "confidence": 0.5306335687637329
336
+ }
337
+ }
338
+ },
339
+ {
340
+ "name": "enum_choice",
341
+ "structure_match": true,
342
+ "maximum_confidence_error": 4.291534423828125e-06,
343
+ "coreml": {
344
+ "product": [
345
+ {
346
+ "category": {
347
+ "text": "electronics",
348
+ "confidence": 0.9997748732566833
349
+ }
350
+ }
351
+ ]
352
+ }
353
+ },
354
+ {
355
+ "name": "es_entities",
356
+ "structure_match": true,
357
+ "maximum_confidence_error": 2.181529998779297e-05,
358
+ "coreml": {
359
+ "entities": {
360
+ "persona": [
361
+ {
362
+ "text": "María",
363
+ "confidence": 0.9971477389335632,
364
+ "start": 0,
365
+ "end": 5
366
+ }
367
+ ],
368
+ "organización": [
369
+ {
370
+ "text": "Telefónica",
371
+ "confidence": 0.990371584892273,
372
+ "start": 29,
373
+ "end": 39
374
+ }
375
+ ],
376
+ "ciudad": [
377
+ {
378
+ "text": "Madrid",
379
+ "confidence": 0.9995085000991821,
380
+ "start": 17,
381
+ "end": 23
382
+ }
383
+ ]
384
+ }
385
+ }
386
+ },
387
+ {
388
+ "name": "fr_entities",
389
+ "structure_match": true,
390
+ "maximum_confidence_error": 3.0159950256347656e-05,
391
+ "coreml": {
392
+ "entities": {
393
+ "personne": [
394
+ {
395
+ "text": "Jean Dupont",
396
+ "confidence": 0.9973365664482117,
397
+ "start": 0,
398
+ "end": 11
399
+ }
400
+ ],
401
+ "entreprise": [
402
+ {
403
+ "text": "Airbus",
404
+ "confidence": 0.9904905557632446,
405
+ "start": 35,
406
+ "end": 41
407
+ }
408
+ ],
409
+ "ville": [
410
+ {
411
+ "text": "Paris",
412
+ "confidence": 0.9991647005081177,
413
+ "start": 24,
414
+ "end": 29
415
+ }
416
+ ]
417
+ }
418
+ }
419
+ },
420
+ {
421
+ "name": "zh_entities",
422
+ "structure_match": true,
423
+ "maximum_confidence_error": 0.0006085038185119629,
424
+ "coreml": {
425
+ "entities": {
426
+ "人物": [
427
+ {
428
+ "text": "张伟在北京大学工作",
429
+ "confidence": 0.9431814551353455,
430
+ "start": 0,
431
+ "end": 9
432
+ }
433
+ ],
434
+ "机构": [],
435
+ "地点": []
436
+ }
437
+ }
438
+ },
439
+ {
440
+ "name": "de_entities",
441
+ "structure_match": true,
442
+ "maximum_confidence_error": 2.1457672119140625e-06,
443
+ "coreml": {
444
+ "entities": {
445
+ "Person": [
446
+ {
447
+ "text": "Angela Merkel",
448
+ "confidence": 0.9966753721237183,
449
+ "start": 0,
450
+ "end": 13
451
+ }
452
+ ],
453
+ "Stadt": [
454
+ {
455
+ "text": "Berlin",
456
+ "confidence": 0.9987298846244812,
457
+ "start": 23,
458
+ "end": 29
459
+ }
460
+ ]
461
+ }
462
+ }
463
+ }
464
+ ]
465
+ }
reports/extraction-w8-validation.json ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "purpose": "Selected real-text extraction parity and local latency; not a Decision Index score",
3
+ "source_revision": "a221b77a8baf4a613b8f8652661d41fa10a5641e",
4
+ "hardware": {
5
+ "device": "arm64",
6
+ "chip": "Apple M5 Pro",
7
+ "ram": "24GB",
8
+ "os_version": "macOS 27.0",
9
+ "timestamp": "2026-09-22T23:15:03.794400+00:00"
10
+ },
11
+ "quantization": "int8 per-channel weight-only word embedding; other weights and activations retain exported precision",
12
+ "native_parity": {
13
+ "fp16_cpu_ne": {
14
+ "report": "reports/extraction-w8-fp16_cpu_ne.json",
15
+ "matched": 14,
16
+ "total": 15,
17
+ "maximum_confidence_error_on_matching_cases": 0.016394853591918945,
18
+ "mismatched_fixtures": [
19
+ "record_latent"
20
+ ]
21
+ },
22
+ "fp32_all": {
23
+ "report": "reports/extraction-w8-fp32_all.json",
24
+ "matched": 15,
25
+ "total": 15,
26
+ "maximum_confidence_error_on_matching_cases": 0.02455449104309082,
27
+ "mismatched_fixtures": []
28
+ },
29
+ "adaptive": {
30
+ "report": "reports/extraction-w8-adaptive.json",
31
+ "matched": 15,
32
+ "total": 15,
33
+ "maximum_confidence_error_on_matching_cases": 0.016394853591918945,
34
+ "mismatched_fixtures": []
35
+ }
36
+ },
37
+ "fp16_w8_feature_private_profile": {
38
+ "compute_units": "cpu_and_neural_engine",
39
+ "total_ops": 652,
40
+ "ane_ops": 620,
41
+ "gpu_ops": 0,
42
+ "cpu_ops": 32,
43
+ "ane_percent": 95.1,
44
+ "reasons": [
45
+ {
46
+ "reason": "Unsupported tensor data type: int32",
47
+ "count": 26,
48
+ "estimated_cpu_runtime_ms": 5.6572,
49
+ "op_types": {
50
+ "ios17.expand_dims": 8,
51
+ "ios17.cast": 8,
52
+ "tile": 3,
53
+ "ios17.add": 2,
54
+ "select": 2,
55
+ "ios17.gather": 1,
56
+ "ios17.mul": 1,
57
+ "ios17.reshape": 1
58
+ },
59
+ "ops": [
60
+ "add_0",
61
+ "select_0",
62
+ "add_0_1",
63
+ "select_0_1",
64
+ "inputs_embeds_cast_fp16",
65
+ "mask_3",
66
+ "mask_3_to_fp16",
67
+ "var_104",
68
+ "extended_attention_mask",
69
+ "extended_attention_mask_to_uint16",
70
+ "var_106_cast_int16_to_int32",
71
+ "var_107",
72
+ "attention_mask_1",
73
+ "var_290",
74
+ "var_2555",
75
+ "text_idx",
76
+ "var_2571",
77
+ "query_idx",
78
+ "text_idx_to_int16",
79
+ "query_idx_to_uint16",
80
+ "var_2599",
81
+ "cls_idx",
82
+ "cls_idx_to_uint16",
83
+ "var_2674_cast_fp16_to_int32",
84
+ "var_2705",
85
+ "var_2734"
86
+ ]
87
+ },
88
+ {
89
+ "reason": "Unable to resolve operation input \"y\".",
90
+ "count": 2,
91
+ "estimated_cpu_runtime_ms": 0.002,
92
+ "op_types": {
93
+ "ios17.greater_equal": 2
94
+ },
95
+ "ops": [
96
+ "greater_equal_0",
97
+ "greater_equal_0_1"
98
+ ]
99
+ },
100
+ {
101
+ "reason": "Unable to resolve operation input \"x\".",
102
+ "count": 2,
103
+ "estimated_cpu_runtime_ms": 0.002,
104
+ "op_types": {
105
+ "ios17.equal": 1,
106
+ "ios17.less_equal": 1
107
+ },
108
+ "ops": [
109
+ "var_2706",
110
+ "mask"
111
+ ]
112
+ },
113
+ {
114
+ "reason": "ANE not available for this op",
115
+ "count": 1,
116
+ "estimated_cpu_runtime_ms": 0,
117
+ "op_types": {
118
+ "ios16.constexpr_affine_dequantize": 1
119
+ },
120
+ "ops": [
121
+ "encoder_embeddings_word_embeddings_weight_to_fp16_quantized"
122
+ ]
123
+ },
124
+ {
125
+ "reason": "Unsupported MIL operation \"cumsum\".",
126
+ "count": 1,
127
+ "estimated_cpu_runtime_ms": 0.001,
128
+ "op_types": {
129
+ "ios16.cumsum": 1
130
+ },
131
+ "ops": [
132
+ "var_2968_cast_fp16"
133
+ ]
134
+ }
135
+ ]
136
+ },
137
+ "end_to_end_entity_latency": {
138
+ "fp32_baseline_all": {
139
+ "compute_units": "all",
140
+ "warmup": 20,
141
+ "iterations": 200,
142
+ "p50_ms": 10.563604475464672,
143
+ "p95_ms": 12.338125030510128,
144
+ "machine": "arm64",
145
+ "macos": "27.0"
146
+ },
147
+ "fp16_w8_cpu_ne": {
148
+ "compute_units": "cpu_and_neural_engine",
149
+ "warmup": 10,
150
+ "iterations": 100,
151
+ "p50_ms": 9.476874489337206,
152
+ "p95_ms": 11.658000003080815,
153
+ "machine": "arm64",
154
+ "macos": "27.0"
155
+ },
156
+ "adaptive_w8_cpu_ne": {
157
+ "compute_units": "cpu_and_neural_engine",
158
+ "warmup": 10,
159
+ "iterations": 100,
160
+ "p50_ms": 9.61008301237598,
161
+ "p95_ms": 10.649124975316226,
162
+ "machine": "arm64",
163
+ "macos": "27.0"
164
+ },
165
+ "fp32_w8_all": {
166
+ "compute_units": "all",
167
+ "warmup": 10,
168
+ "iterations": 100,
169
+ "p50_ms": 10.522104013944045,
170
+ "p95_ms": 11.859667021781206,
171
+ "machine": "arm64",
172
+ "macos": "27.0"
173
+ }
174
+ }
175
+ }
tests/test_extraction_routing.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Schema routing for the quantized multilingual extraction runtime."""
2
+
3
+ from gliner2 import Schema
4
+
5
+ from extraction_runtime import CoreMLAdaptiveBoundaryExtractor
6
+
7
+
8
+ def record_schema(mode: str):
9
+ schema = Schema()
10
+ schema.structure("employment", mode=mode).field("person", dtype="str").field("company", dtype="str")
11
+ return schema
12
+
13
+
14
+ def test_latent_record_uses_fp32():
15
+ assert CoreMLAdaptiveBoundaryExtractor.selected_precision(record_schema("latent")) == "fp32"
16
+
17
+
18
+ def test_other_selected_schemas_use_fp16():
19
+ assert CoreMLAdaptiveBoundaryExtractor.selected_precision(Schema().entities(["person"])) == "fp16"
20
+ assert CoreMLAdaptiveBoundaryExtractor.selected_precision(record_schema("natural")) == "fp16"
21
+ assert CoreMLAdaptiveBoundaryExtractor.selected_precision(record_schema("anchorless")) == "fp16"
tests/test_quantize_extraction.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Inspect the trained embedding selected for weight-only quantization."""
2
+
3
+ import os
4
+ import runpy
5
+ from pathlib import Path
6
+
7
+ import coremltools as ct
8
+ import pytest
9
+
10
+
11
+ def test_quantizer_selects_real_word_embedding():
12
+ package = os.environ.get("GLINER2_EXTRACTION_FEATURE_PACKAGE")
13
+ if not package:
14
+ pytest.skip("Set GLINER2_EXTRACTION_FEATURE_PACKAGE to a pinned real Core ML feature package")
15
+ namespace = runpy.run_path(str(Path(__file__).parents[1] / "quantize-extraction-coreml.py"))
16
+ model = ct.models.MLModel(package, skip_model_load=True)
17
+ name, shape, dtype = namespace["embedding_weight_name"](model)
18
+ assert name.startswith("encoder_embeddings_word_embeddings_weight")
19
+ assert shape[0] >= 250_112
20
+ assert shape[1] == 768
21
+ assert dtype in ("float16", "float32")
verify-adaptive-extraction.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Check the adaptive W8 runtime against stored pinned native outputs."""
2
+
3
+ import argparse
4
+ import json
5
+ import runpy
6
+ from pathlib import Path
7
+
8
+ from extraction_runtime import CoreMLAdaptiveBoundaryExtractor
9
+
10
+ MODEL_REVISION = "a221b77a8baf4a613b8f8652661d41fa10a5641e"
11
+
12
+
13
+ def main():
14
+ parser = argparse.ArgumentParser()
15
+ parser.add_argument("--model-dir", required=True)
16
+ parser.add_argument("--reference", default=str(Path(__file__).parent / "reports/extraction-verify-fp32.json"))
17
+ args = parser.parse_args()
18
+ reference = json.loads(Path(args.reference).read_text())
19
+ if reference["source_revision"] != MODEL_REVISION or reference["matched"] != reference["total"]:
20
+ raise ValueError("Reference must be the complete pinned native/FP32 parity manifest")
21
+ helpers = runpy.run_path(str(Path(__file__).parent / "verify-full-extraction.py"))
22
+ fixtures = helpers["fixtures"]()
23
+ without_confidence = helpers["without_confidence"]
24
+ confidence_errors = helpers["confidence_errors"]
25
+ reference_cases = {case["name"]: case for case in reference["cases"]}
26
+ if {name for name, _, _ in fixtures} != set(reference_cases):
27
+ raise ValueError("Fixture names differ from the pinned native reference")
28
+ runtime = CoreMLAdaptiveBoundaryExtractor(args.model_dir)
29
+ cases = []
30
+ for name, text, schema in fixtures:
31
+ baseline = reference_cases[name]
32
+ if text != baseline["text"]:
33
+ raise ValueError(f"Fixture text changed for {name}")
34
+ expected = baseline["native"]
35
+ actual = runtime.extract(text, schema, include_confidence=True, include_spans=True)
36
+ errors = confidence_errors(expected, actual)
37
+ cases.append(
38
+ {
39
+ "name": name,
40
+ "route": runtime.selected_precision(schema),
41
+ "structure_match": without_confidence(expected) == without_confidence(actual),
42
+ "maximum_confidence_error": max(errors, default=None),
43
+ "coreml": actual,
44
+ }
45
+ )
46
+ print(f"{name}: {cases[-1]['route']} {cases[-1]['structure_match']}", flush=True)
47
+ report = {
48
+ "source_revision": MODEL_REVISION,
49
+ "selected_manifest": "fifteen pinned real-text schema fixtures, not a Decision Index score",
50
+ "variant": "adaptive-w8-embedding",
51
+ "matched": sum(case["structure_match"] for case in cases),
52
+ "total": len(cases),
53
+ "cases": cases,
54
+ }
55
+ output = Path(args.model_dir) / "verify-adaptive-w8.json"
56
+ output.write_text(json.dumps(report, indent=2, ensure_ascii=False) + "\n")
57
+ print(json.dumps({"matched": report["matched"], "total": report["total"]}, indent=2))
58
+ if report["matched"] != report["total"]:
59
+ raise SystemExit(1)
60
+
61
+
62
+ if __name__ == "__main__":
63
+ main()
verify-quantized-extraction.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Compare a quantized extraction package with stored pinned native outputs."""
2
+
3
+ import argparse
4
+ import json
5
+ import runpy
6
+ from pathlib import Path
7
+
8
+ import coremltools as ct
9
+
10
+ from extraction_runtime import CoreMLBoundaryExtractor
11
+
12
+ MODEL_REVISION = "a221b77a8baf4a613b8f8652661d41fa10a5641e"
13
+ UNITS = {
14
+ "cpu_only": ct.ComputeUnit.CPU_ONLY,
15
+ "cpu_and_gpu": ct.ComputeUnit.CPU_AND_GPU,
16
+ "cpu_and_neural_engine": ct.ComputeUnit.CPU_AND_NE,
17
+ "all": ct.ComputeUnit.ALL,
18
+ }
19
+
20
+
21
+ def main():
22
+ parser = argparse.ArgumentParser()
23
+ parser.add_argument("--model-dir", required=True)
24
+ parser.add_argument("--feature-package", required=True)
25
+ parser.add_argument("--precision", choices=["fp16", "fp32"], required=True)
26
+ parser.add_argument("--units", choices=list(UNITS), default="cpu_and_neural_engine")
27
+ parser.add_argument("--reference", default=str(Path(__file__).parent / "reports/extraction-verify-fp32.json"))
28
+ args = parser.parse_args()
29
+ reference = json.loads(Path(args.reference).read_text())
30
+ if reference["source_revision"] != MODEL_REVISION or reference["matched"] != reference["total"]:
31
+ raise ValueError("Reference must be the complete pinned native/FP32 parity manifest")
32
+ helpers = runpy.run_path(str(Path(__file__).parent / "verify-full-extraction.py"))
33
+ fixtures = helpers["fixtures"]()
34
+ without_confidence = helpers["without_confidence"]
35
+ confidence_errors = helpers["confidence_errors"]
36
+ reference_cases = {case["name"]: case for case in reference["cases"]}
37
+ if {name for name, _, _ in fixtures} != set(reference_cases):
38
+ raise ValueError("Fixture names differ from the pinned native reference")
39
+ runtime = CoreMLBoundaryExtractor(
40
+ args.model_dir,
41
+ precision=args.precision,
42
+ compute_units=UNITS[args.units],
43
+ feature_package=args.feature_package,
44
+ )
45
+ cases = []
46
+ for name, text, schema in fixtures:
47
+ baseline = reference_cases[name]
48
+ if text != baseline["text"]:
49
+ raise ValueError(f"Fixture text changed for {name}")
50
+ expected = baseline["native"]
51
+ actual = runtime.extract(text, schema, include_confidence=True, include_spans=True)
52
+ errors = confidence_errors(expected, actual)
53
+ cases.append(
54
+ {
55
+ "name": name,
56
+ "structure_match": without_confidence(expected) == without_confidence(actual),
57
+ "maximum_confidence_error": max(errors, default=None),
58
+ "coreml": actual,
59
+ }
60
+ )
61
+ print(f"{name}: {cases[-1]['structure_match']}", flush=True)
62
+ report = {
63
+ "source_revision": MODEL_REVISION,
64
+ "selected_manifest": "pinned real-text schema fixtures, not a Decision Index score",
65
+ "feature_package": args.feature_package,
66
+ "precision": args.precision,
67
+ "compute_units": args.units,
68
+ "matched": sum(case["structure_match"] for case in cases),
69
+ "total": len(cases),
70
+ "cases": cases,
71
+ }
72
+ output = Path(args.model_dir) / f"verify-quantized-{args.precision}-{args.units}.json"
73
+ output.write_text(json.dumps(report, indent=2, ensure_ascii=False) + "\n")
74
+ print(json.dumps({"matched": report["matched"], "total": report["total"]}, indent=2))
75
+ if report["matched"] != report["total"]:
76
+ raise SystemExit(1)
77
+
78
+
79
+ if __name__ == "__main__":
80
+ main()