Token Classification
Transformers
ONNX
Safetensors
multilingual
bert
autofill
password-manager
form-classification
field-classification
browser-extension
Instructions to use ProCreations/Zap with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use ProCreations/Zap with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("token-classification", model="ProCreations/Zap")# pip install -U transformers accelerate # Load model directly from transformers import AutoTokenizer, AutoModelForTokenClassification tokenizer = AutoTokenizer.from_pretrained("ProCreations/Zap") model = AutoModelForTokenClassification.from_pretrained("ProCreations/Zap", device_map="auto") - Notebooks
- Google Colab
- Kaggle
File size: 4,972 Bytes
e4805d0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | """Export Zap: bf16 safetensors (the published weights) + ONNX (fp32, optional int8) with parity checks.
Usage: python export.py MODEL_DIR DATA_DIR OUT_DIR
"""
import json
import os
import shutil
import sys
import numpy as np
import torch
sys.path.insert(0, "/job/work")
from train.zapdata import N_FORM, windows # noqa: E402
from train.zaptrain import pad, read_jsonl # noqa: E402
def main(model_dir, data_dir, out):
from transformers import AutoTokenizer, BertForTokenClassification
os.makedirs(os.path.join(out, "onnx"), exist_ok=True)
tok = AutoTokenizer.from_pretrained(model_dir)
m32 = BertForTokenClassification.from_pretrained(model_dir, dtype=torch.float32).eval()
m_exp = BertForTokenClassification.from_pretrained(model_dir, dtype=torch.float32).eval() # tracing mutates it
# 1) bf16 weights
m16 = BertForTokenClassification.from_pretrained(model_dir, dtype=torch.bfloat16).eval()
m16.save_pretrained(out)
tok.save_pretrained(out)
# portable tokenizer class: loads tokenizer.json as-is in transformers 4.x/5.x and transformers.js
tc_path = os.path.join(out, "tokenizer_config.json")
tc = json.load(open(tc_path))
tc["tokenizer_class"] = "PreTrainedTokenizerFast"
json.dump(tc, open(tc_path, "w"), indent=1)
tok2 = AutoTokenizer.from_pretrained(out)
probe = ["[TITLE] Anmeldung – Café Müller [FLD] [TYPE] input password [NAME] passwort [LBL] Kennwort",
"[TITLE] 登录 - 账户 [FLD] [TYPE] input text [PH] 手机号/邮箱 [FLD] [TYPE] input password",
"[TITLE] Вход [FLD] [TYPE] input email [LBL] Электронная почта [REQ]"]
for t in probe:
assert tok(t)["input_ids"] == tok2(t)["input_ids"], "tokenizer round-trip mismatch"
print("tokenizer round-trip ok", tok2.__class__.__name__)
# 2) ONNX fp32
ids = torch.tensor([[tok.cls_token_id] + [5] * 30 + [tok.sep_token_id]] * 2)
att = torch.ones_like(ids)
class Wrap(torch.nn.Module):
def __init__(self, m):
super().__init__()
self.m = m
def forward(self, input_ids, attention_mask):
return self.m(input_ids=input_ids, attention_mask=attention_mask).logits
onnx_path = os.path.join(out, "onnx", "model.onnx")
torch.onnx.export(Wrap(m_exp), (ids, att), onnx_path, input_names=["input_ids", "attention_mask"],
output_names=["logits"], opset_version=17, dynamo=False,
dynamic_axes={"input_ids": {0: "batch", 1: "seq"}, "attention_mask": {0: "batch", 1: "seq"},
"logits": {0: "batch", 1: "seq"}})
import onnxruntime as ort
from onnxruntime.quantization import QuantType, quantize_dynamic
q_path = os.path.join(out, "onnx", "model_quantized.onnx")
quantize_dynamic(onnx_path, q_path, weight_type=QuantType.QInt8)
sess = {"fp32": ort.InferenceSession(onnx_path, providers=["CPUExecutionProvider"]),
"int8": ort.InferenceSession(q_path, providers=["CPUExecutionProvider"])}
# 3) parity on test windows: argmax agreement at [CLS] / [FLD] positions vs torch fp32
rows = read_jsonl(os.path.join(data_dir, "test.jsonl"))[:3000]
items = [w for r in rows for w in windows(tok, r["ctx"], r["fields"])]
agree = {k: [0, 0] for k in ("bf16_torch", "fp32_onnx", "int8_onnx")}
maxdiff = 0.0
for s in range(0, len(items), 64):
chunk = items[s:s + 64]
x = pad([c[0] for c in chunk], tok.pad_token_id)
a = (x != tok.pad_token_id).long()
with torch.no_grad():
ref = m32(input_ids=x, attention_mask=a).logits.float()
b16 = m16(input_ids=x, attention_mask=a).logits.float()
outs = {"bf16_torch": b16}
for k, ss in sess.items():
outs[f"{k}_onnx"] = torch.from_numpy(ss.run(None, {"input_ids": x.numpy().astype(np.int64),
"attention_mask": a.numpy().astype(np.int64)})[0])
maxdiff = max(maxdiff, float((outs["fp32_onnx"] - ref).abs().max()))
for b, (_, pos, _) in enumerate(chunk):
for p, sl in [(0, slice(0, N_FORM))] + [(p, slice(N_FORM, None)) for p in pos]:
r = int(ref[b, p, sl].argmax())
for k, o in outs.items():
agree[k][0] += int(o[b, p, sl].argmax()) == r
agree[k][1] += 1
report = {"positions": agree["fp32_onnx"][1], "fp32_onnx_max_abs_logit_diff": maxdiff,
**{f"{k}_argmax_agreement": v[0] / max(1, v[1]) for k, v in agree.items()}}
print(json.dumps(report, indent=1))
json.dump(report, open(os.path.join(out, "export_parity.json"), "w"), indent=1)
if report["int8_onnx_argmax_agreement"] < 0.995:
os.remove(q_path)
print("int8 ONNX dropped: agreement below 99.5%")
shutil.copy(os.path.join(model_dir, "val_metrics.json"), out)
if __name__ == "__main__":
main(*sys.argv[1:4])
|