Spaces:
Runtime error
Runtime error
| """Export the desklib DeBERTa-v3 AI-detector to a small int8 ONNX so it runs on | |
| onnxruntime (no torch at inference) and frees ~1.3GB of disk. | |
| Disk-safe ordering on a near-full disk: copy the tokenizer out, load the torch | |
| model into RAM, DELETE the 1.7GB HF cache (model is already in RAM), THEN write | |
| the ONNX — so the disk never holds the torch cache and the fp32 ONNX at once. | |
| Finally verify the int8 ONNX matches torch on sample texts before we rely on it. | |
| Output: models/desklib_onnx/{model.onnx, tokenizer files, config.json, meta.json} | |
| Run: python scripts/export_desklib_onnx.py | |
| """ | |
| import glob, json, os, shutil, sys | |
| import numpy as np | |
| ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | |
| DST = os.path.join(ROOT, "models", "desklib_onnx") | |
| NAME = "desklib/ai-text-detector-v1.01" | |
| TOK_FILES = ["spm.model", "tokenizer.json", "tokenizer_config.json", | |
| "special_tokens_map.json", "added_tokens.json", "config.json"] | |
| def main(): | |
| import torch, torch.nn as nn | |
| from transformers import AutoConfig, AutoModel, AutoTokenizer, PreTrainedModel | |
| os.makedirs(DST, exist_ok=True) | |
| class Desklib(PreTrainedModel): | |
| config_class = AutoConfig | |
| def __init__(self, config): | |
| super().__init__(config) | |
| self.model = AutoModel.from_config(config) | |
| self.classifier = nn.Linear(config.hidden_size, 1) | |
| self.init_weights() | |
| def forward(self, input_ids, attention_mask=None, **kw): | |
| out = self.model(input_ids, attention_mask=attention_mask)[0] | |
| mask = attention_mask.unsqueeze(-1).expand(out.size()).float() | |
| pooled = (out * mask).sum(1) / mask.sum(1).clamp(min=1e-9) | |
| return self.classifier(pooled) | |
| tok = AutoTokenizer.from_pretrained(NAME) | |
| model = Desklib.from_pretrained(NAME).eval() | |
| print("torch model loaded into RAM") | |
| # copy tokenizer/config out of the (now-populated) cache before we delete it | |
| snap = glob.glob(os.path.join( | |
| os.path.expanduser("~"), ".cache", "huggingface", "hub", | |
| "models--desklib--ai-text-detector-v1.01", "snapshots", "*")) | |
| if snap: | |
| for f in TOK_FILES: | |
| src = os.path.join(snap[0], f) | |
| if os.path.exists(src): | |
| shutil.copy(src, os.path.join(DST, f)) | |
| print("copied tokenizer/config to", DST) | |
| # sample texts for verification (computed BEFORE we delete anything) | |
| samples = ["The proposed framework leverages a comprehensive and multifaceted " | |
| "approach to optimize performance across diverse benchmarks, underscoring " | |
| "its pivotal role in advancing the field.", | |
| "we ran the test three times and honestly the numbers were all over the " | |
| "place, not sure why, maybe the sensor was loose or we messed up the wiring"] | |
| def torch_p(t): | |
| e = tok(t, truncation=True, max_length=512, return_tensors="pt") | |
| return float(torch.sigmoid(model(**{k: e[k] for k in | |
| ("input_ids", "attention_mask")})[0])) | |
| torch_ref = [torch_p(t) for t in samples] | |
| # free the 1.7GB disk cache now that the model is in RAM | |
| cache_dir = os.path.join(os.path.expanduser("~"), ".cache", "huggingface", | |
| "hub", "models--desklib--ai-text-detector-v1.01") | |
| shutil.rmtree(cache_dir, ignore_errors=True) | |
| print("freed HF cache (model stays in RAM)") | |
| # export fp32 ONNX | |
| fp32 = os.path.join(DST, "model_fp32.onnx") | |
| dummy = tok(samples[0], return_tensors="pt", truncation=True, max_length=64) | |
| torch.onnx.export( | |
| model, (dummy["input_ids"], dummy["attention_mask"]), fp32, | |
| input_names=["input_ids", "attention_mask"], output_names=["logits"], | |
| dynamic_axes={"input_ids": {0: "b", 1: "s"}, | |
| "attention_mask": {0: "b", 1: "s"}, "logits": {0: "b"}}, | |
| opset_version=14, do_constant_folding=True, dynamo=False) | |
| print(f"exported fp32 ONNX ({os.path.getsize(fp32)//1024//1024} MB)") | |
| # dynamic int8 quantization | |
| from onnxruntime.quantization import quantize_dynamic, QuantType | |
| out = os.path.join(DST, "model.onnx") | |
| quantize_dynamic(fp32, out, weight_type=QuantType.QInt8) | |
| os.remove(fp32) | |
| print(f"quantized int8 ONNX ({os.path.getsize(out)//1024//1024} MB); removed fp32") | |
| # verify ONNX matches torch | |
| import onnxruntime as ort | |
| sess = ort.InferenceSession(out, providers=["CPUExecutionProvider"]) | |
| inn = [i.name for i in sess.get_inputs()] | |
| def onnx_p(t): | |
| e = tok(t, truncation=True, max_length=512) | |
| feed = {"input_ids": np.array([e["input_ids"]], np.int64), | |
| "attention_mask": np.array([e["attention_mask"]], np.int64)} | |
| logit = sess.run(None, {k: v for k, v in feed.items() if k in inn})[0] | |
| return float(1 / (1 + np.exp(-logit.reshape(-1)[0]))) | |
| print("\nVERIFY (torch vs int8 ONNX):") | |
| ok = True | |
| for t, ref in zip(samples, torch_ref): | |
| o = onnx_p(t) | |
| d = abs(o - ref) | |
| ok &= d < 0.06 | |
| print(f" torch={ref:.3f} onnx={o:.3f} |Δ|={d:.3f}") | |
| json.dump({"model": NAME, "format": "onnx-int8-dynamic", "max_len": 512, | |
| "verified": ok}, open(os.path.join(DST, "meta.json"), "w"), indent=1) | |
| print("\nVERIFIED OK" if ok else "\nWARNING: ONNX deviates from torch — check") | |
| if __name__ == "__main__": | |
| main() | |