Zero-Shot Classification
Core ML
GLiNER
GLiNER2
English
coremltools
deberta-v3
apple-silicon
fp16
multifunction
Instructions to use augustoFranke/GLiNER2.5-Decide-CoreML-FP16-MultiFn-L64-512 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- GLiNER
How to use augustoFranke/GLiNER2.5-Decide-CoreML-FP16-MultiFn-L64-512 with GLiNER:
from gliner import GLiNER model = GLiNER.from_pretrained("augustoFranke/GLiNER2.5-Decide-CoreML-FP16-MultiFn-L64-512") text = "Cristiano Ronaldo dos Santos Aveiro was born on 5 February 1985 in Funchal, Madeira, Portugal." labels = ["person", "date", "location"] entities = model.predict_entities(text, labels) for entity in entities: print(entity["text"], "=>", entity["label"]) - GLiNER2
How to use augustoFranke/GLiNER2.5-Decide-CoreML-FP16-MultiFn-L64-512 with GLiNER2:
from gliner2 import AutoExtractor extractor = AutoExtractor.from_pretrained("augustoFranke/GLiNER2.5-Decide-CoreML-FP16-MultiFn-L64-512") # Extract entities text = "Apple CEO Tim Cook announced iPhone 15 in Cupertino yesterday." result = extractor.extract_entities(text, ["company", "person", "product", "location"]) print(result) - Notebooks
- Google Colab
- Kaggle
Download scripts/build/convert_bucket.py from augustoFranke/GLiNER2.5-Decide-CoreML-FP16-MultiFn-L64-512: direct link, hf CLI and curl.
- Browser
- Download file 9.65 kB
-
https://huggingface.co/augustoFranke/GLiNER2.5-Decide-CoreML-FP16-MultiFn-L64-512/resolve/cb96101a108e3e756eb581e6be780eac3a93b2f2/scripts/build/convert_bucket.py
- Command line
-
hf download hf://augustoFranke/GLiNER2.5-Decide-CoreML-FP16-MultiFn-L64-512@cb96101a108e3e756eb581e6be780eac3a93b2f2/scripts/build/convert_bucket.py
-
curl -L -o convert_bucket.py https://huggingface.co/augustoFranke/GLiNER2.5-Decide-CoreML-FP16-MultiFn-L64-512/resolve/cb96101a108e3e756eb581e6be780eac3a93b2f2/scripts/build/convert_bucket.py
9.65 kB
| """Convert and verify the pinned GLiNER2.5-Decide multi-head classification path. | |
| Modified from Fluid Inference's gliner2-5-decide-coreml `convert-coreml.py` (Apache-2.0): | |
| traces with an example that fits the requested length and verifies every example that fits, | |
| so buckets shorter than the original examples (L64) can be built and checked. | |
| """ | |
| import argparse | |
| import json | |
| import math | |
| from pathlib import Path | |
| import coremltools as ct | |
| import numpy as np | |
| import torch | |
| from gliner2 import AutoExtractor | |
| from huggingface_hub import snapshot_download | |
| from transformers.models.deberta_v2 import modeling_deberta_v2 | |
| from convert_names import MODEL_ID, MODEL_REVISION | |
| from export_model import GLiNER2DecideExport, coreml_safe_attention_forward | |
| from preprocessing import classification_schema, prepare_decision | |
| from runtime import decode, package_name | |
| EXAMPLES = [ | |
| ("My subscription renewed on April 15 for ¥5,400 after the service was already down. " | |
| "Can I get that charge refunded?", | |
| {"intent": ["order_status", "refund_request", "cancel_subscription", "update_payment", "login_problem", | |
| "shipping_delay", "bug_report", "speak_to_human"]}), | |
| ("Battery dies before lunch, but the keyboard and the screen are the best I have used on a laptop.", | |
| {"sentiment": ["positive", "negative", "mixed", "neutral"], | |
| "aspects": {"labels": ["battery", "keyboard", "screen", "camera", "price", "support"], | |
| "multi_label": True, "cls_threshold": 0.4}}), | |
| ("From: compliance@group.example\nSubject: Protocol update — action required today\n\n" | |
| "Please confirm the new retention rule is applied before Friday's audit.", | |
| {"intent": ["fyi", "request", "approval", "complaint", "newsletter", "security_alert"], | |
| "urgency": ["low", "normal", "high", "critical"], | |
| "route": ["support", "billing", "legal", "security", "finance", "archive"]}), | |
| ("My subscription renewed on April 15 for 5,400 yen after the service was already down. " | |
| "Can I get that charge refunded?", | |
| {"intent": ["order_status", "refund_request", "cancel_subscription", "update_payment", "other"], | |
| "urgency": ["low", "normal", "high"]}), | |
| ] | |
| def fits(processor, text, tasks, bucket): | |
| try: | |
| prepare_decision(processor, text, tasks, *bucket) | |
| return True | |
| except ValueError: | |
| return False | |
| def native_logits(native, text, tasks): | |
| """Per-head native logits via the upstream span collator, encoder and shared classifier.""" | |
| from gliner2.training.trainer import ExtractorCollator | |
| collator = ExtractorCollator(native.processor, is_training=False, max_len=None, architecture="span") | |
| batch = collator([(text, classification_schema(tasks).build())]) | |
| _, schema_embs = native._encode_batch(batch) | |
| return [native.classifier(torch.stack(embs[1:])).squeeze(-1) for embs in schema_embs[0]] | |
| def main(): | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("--output-dir", default="build") | |
| parser.add_argument("--length", type=int, default=128) | |
| parser.add_argument("--max-heads", type=int, default=4) | |
| parser.add_argument("--max-options", type=int, default=8) | |
| parser.add_argument("--precision", choices=["fp16", "fp32"], default="fp16") | |
| args = parser.parse_args() | |
| torch.set_num_threads(4) | |
| source = snapshot_download( | |
| MODEL_ID, revision=MODEL_REVISION, | |
| allow_patterns=[ | |
| "config.json", "encoder_config/*", "model.safetensors", "tokenizer.json", "tokenizer_config.json", | |
| "special_tokens_map.json", | |
| ], | |
| ) | |
| native = AutoExtractor.from_pretrained(source, map_location="cpu").eval() | |
| wrapper = GLiNER2DecideExport(native).eval() | |
| bucket = (args.length, args.max_heads, args.max_options) | |
| fitting = [e for e in EXAMPLES if fits(native.processor, *e, bucket)] | |
| if not fitting: | |
| raise RuntimeError(f"no example fits bucket {bucket}") | |
| print(f"examples fitting L{args.length}: {len(fitting)}/{len(EXAMPLES)}") | |
| text, tasks = fitting[-1] if args.length < 128 else (EXAMPLES[2] if EXAMPLES[2] in fitting else fitting[-1]) | |
| arrays = prepare_decision(native.processor, text, tasks, *bucket) | |
| tensors = tuple(torch.from_numpy(value) for value in arrays.values()) | |
| def wrapper_error(): | |
| with torch.no_grad(): | |
| expected = native_logits(native, text, tasks) | |
| actual = wrapper(*tensors)[0][0] | |
| return max(float((row - actual[h, : len(row)]).abs().max()) for h, row in enumerate(expected)) | |
| error = wrapper_error() | |
| if error > 1e-4: | |
| raise RuntimeError(f"Wrapper/native logit mismatch: {error}") | |
| # The upstream scale is a constant for a fixed DeBERTa attention head width. | |
| # Its traced int32 sqrt is rejected by Core ML; freeze the identical float32 | |
| # value while tracing, and restore the upstream implementation immediately. | |
| original_scale = modeling_deberta_v2.scaled_size_sqrt | |
| original_rpos = modeling_deberta_v2.build_rpos | |
| original_attention = modeling_deberta_v2.DisentangledSelfAttention.forward | |
| def static_scale(query_layer, scale_factor): | |
| value = math.sqrt(float(query_layer.shape[-1] * scale_factor)) | |
| return torch.tensor(value, dtype=torch.float32, device=query_layer.device) | |
| modeling_deberta_v2.scaled_size_sqrt = static_scale | |
| # The encoder only uses self-attention: query and key sequence lengths are | |
| # identical, so the scripted build_rpos returns relative_pos unchanged. | |
| modeling_deberta_v2.build_rpos = lambda query, key, relative_pos, buckets, max_pos: relative_pos | |
| modeling_deberta_v2.DisentangledSelfAttention.forward = coreml_safe_attention_forward | |
| try: | |
| frozen_error = wrapper_error() | |
| if frozen_error > 1e-4: | |
| raise RuntimeError(f"Frozen attention scale changed native logits: {frozen_error}") | |
| with torch.no_grad(): | |
| traced = torch.jit.trace(wrapper, tensors, check_trace=False) | |
| finally: | |
| modeling_deberta_v2.scaled_size_sqrt = original_scale | |
| modeling_deberta_v2.build_rpos = original_rpos | |
| modeling_deberta_v2.DisentangledSelfAttention.forward = original_attention | |
| grid = (1, args.max_heads, args.max_options) | |
| converted = ct.convert( | |
| traced, convert_to="mlprogram", minimum_deployment_target=ct.target.iOS17, | |
| compute_precision=ct.precision.FLOAT16 if args.precision == "fp16" else ct.precision.FLOAT32, | |
| compute_units=ct.ComputeUnit.CPU_ONLY, | |
| inputs=[ | |
| ct.TensorType(name="input_ids", shape=(1, args.length), dtype=np.int32), | |
| ct.TensorType(name="attention_mask", shape=(1, args.length), dtype=np.int32), | |
| ct.TensorType(name="marker_indices", shape=grid, dtype=np.int32), | |
| ct.TensorType(name="marker_mask", shape=grid, dtype=np.float32), | |
| ], | |
| outputs=[ct.TensorType(name="logits", dtype=np.float32), ct.TensorType(name="probabilities", dtype=np.float32)], | |
| ) | |
| converted.short_description = "GLiNER2.5-Decide multi-head schema classification path" | |
| converted.author = "Fastino (original); Fluid Inference (Core ML conversion)" | |
| converted.license = "Apache-2.0" | |
| converted.user_defined_metadata.update({ | |
| "source_model": MODEL_ID, "source_revision": MODEL_REVISION, | |
| "scope": "classification only; span and count heads not exported", | |
| "length": str(args.length), "max_heads": str(args.max_heads), "max_options": str(args.max_options), | |
| }) | |
| out = Path(args.output_dir) | |
| out.mkdir(parents=True, exist_ok=True) | |
| package = out / package_name(args.precision, *bucket) | |
| converted.save(str(package)) | |
| runtime = ct.models.MLModel(str(package), compute_units=ct.ComputeUnit.ALL) | |
| cases = [] | |
| for text, tasks in fitting: | |
| native_output = native.classify_text(text, tasks, include_confidence=True) | |
| logits = np.asarray(runtime.predict(prepare_decision(native.processor, text, tasks, *bucket))["logits"])[0] | |
| coreml_output = decode(tasks, logits) | |
| if json.dumps(labels_only(native_output)) != json.dumps(labels_only(coreml_output)): | |
| raise RuntimeError(f"Core ML/native mismatch: {coreml_output} != {native_output}") | |
| cases.append({"text": text, "native": native_output, "coreml": coreml_output, | |
| "max_confidence_error": confidence_error(native_output, coreml_output)}) | |
| report = { | |
| "source_model": MODEL_ID, "source_revision": MODEL_REVISION, "package": str(package), | |
| "package_bytes": sum(f.stat().st_size for f in package.rglob("*") if f.is_file()), | |
| "native_total_parameters": sum(p.numel() for p in native.parameters()), | |
| "exported_parameters": sum(p.numel() for p in wrapper.parameters()), | |
| "wrapper_max_logit_error": error, "coremltools": ct.__version__, "torch": torch.__version__, | |
| "cases": cases, | |
| } | |
| (out / f"conversion-{package.stem}.json").write_text(json.dumps(report, indent=2, ensure_ascii=False) + "\n") | |
| print(json.dumps(report, indent=2, ensure_ascii=False)) | |
| def _entries(value): | |
| return value if isinstance(value, list) else [value] | |
| def labels_only(result: dict) -> dict: | |
| return {task: sorted(entry["label"] for entry in _entries(value)) for task, value in result.items()} | |
| def confidence_error(native: dict, coreml: dict) -> float: | |
| errors = [] | |
| for task, value in native.items(): | |
| predicted = {entry["label"]: entry["confidence"] for entry in _entries(coreml[task])} | |
| errors += [abs(entry["confidence"] - predicted[entry["label"]]) for entry in _entries(value)] | |
| return max(errors) | |
| if __name__ == "__main__": | |
| main() | |