Offlin33er commited on
Commit
6acd90a
·
verified ·
1 Parent(s): f80a3f2

tokenizer.json compatibility fixup for TrOCR on transformers 5.x

Browse files
Files changed (1) hide show
  1. trocr_compat.py +134 -0
trocr_compat.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Compatibility fixup: make TrOCRProcessor.from_pretrained work on transformers
2
+ 5.x for microsoft/trocr-base-printed with a cold/legacy HF cache.
3
+
4
+ transformers 5.x dropped support for slow RoBERTa tokenizers, so TrOCR
5
+ checkpoints (vocab.json + merges.txt, no tokenizer.json) fail to load. Fix:
6
+ locate the cached snapshot dir; if tokenizer.json is missing, fetch
7
+ vocab.json + merges.txt via hf_hub_download, build an equivalent fast-tokenizer
8
+ tokenizer.json (GPT2-style BPE with the RoBERTa special tokens and a
9
+ TemplateProcessing <s> $A </s> post-processor), write it into the snapshot dir,
10
+ and clear any .no_exist marker for tokenizer.json. Idempotent: if
11
+ tokenizer.json already exists, the function returns immediately (no-op).
12
+ """
13
+ import glob
14
+ import json
15
+ import os
16
+
17
+
18
+ def _hub_cache_dir():
19
+ for var in ("HF_HUB_CACHE", "HUGGINGFACE_HUB_CACHE"):
20
+ if os.environ.get(var):
21
+ return os.path.expanduser(os.environ[var])
22
+ hf_home = os.environ.get("HF_HOME") or os.path.expanduser("~/.cache/huggingface")
23
+ return os.path.join(hf_home, "hub")
24
+
25
+
26
+ def _repo_dirname(model_id):
27
+ return "models--" + model_id.replace("/", "--")
28
+
29
+
30
+ def _find_snapshot_dir(repo_dir, revision="main"):
31
+ """Pick the snapshot matching refs/<revision>, else the newest one."""
32
+ ref_file = os.path.join(repo_dir, "refs", revision)
33
+ if os.path.isfile(ref_file):
34
+ with open(ref_file) as f:
35
+ commit = f.read().strip()
36
+ cand = os.path.join(repo_dir, "snapshots", commit)
37
+ if os.path.isdir(cand):
38
+ return cand
39
+ snaps = sorted(
40
+ glob.glob(os.path.join(repo_dir, "snapshots", "*")), key=os.path.getmtime
41
+ )
42
+ return snaps[-1] if snaps else None
43
+
44
+
45
+ def ensure_trocr_tokenizer(model_id="microsoft/trocr-base-printed"):
46
+ """Patch the HF cache so TrOCRProcessor.from_pretrained(model_id) works on
47
+ transformers 5.x. Idempotent; raises RuntimeError if it cannot fix the
48
+ cache. Returns the path to the (existing or newly built) tokenizer.json."""
49
+ try:
50
+ from huggingface_hub import hf_hub_download, snapshot_download
51
+ from tokenizers import Tokenizer, decoders, pre_tokenizers, processors
52
+ from tokenizers.models import BPE
53
+ except ImportError as e:
54
+ raise RuntimeError(
55
+ "ensure_trocr_tokenizer needs the 'huggingface_hub' and "
56
+ f"'tokenizers' packages installed: {e}"
57
+ )
58
+
59
+ repo_dir = os.path.join(_hub_cache_dir(), _repo_dirname(model_id))
60
+ snapshot_dir = None
61
+ if os.path.isdir(repo_dir):
62
+ snapshot_dir = _find_snapshot_dir(repo_dir)
63
+
64
+ if snapshot_dir is None:
65
+ # Cold cache: fetch only the small config/tokenizer files (not the
66
+ # weights); this also creates the standard snapshot layout.
67
+ snapshot_dir = snapshot_download(model_id, allow_patterns=["*.json", "*.txt"])
68
+
69
+ tok_path = os.path.join(snapshot_dir, "tokenizer.json")
70
+ if os.path.exists(tok_path):
71
+ return tok_path # already fixed -> idempotent no-op
72
+
73
+ vocab_file = hf_hub_download(model_id, "vocab.json")
74
+ merges_file = hf_hub_download(model_id, "merges.txt")
75
+ if not (os.path.isfile(vocab_file) and os.path.isfile(merges_file)):
76
+ raise RuntimeError(
77
+ "could not fetch vocab.json/merges.txt for %s; cannot build " % model_id
78
+ + "tokenizer.json"
79
+ )
80
+
81
+ with open(vocab_file, encoding="utf-8") as f:
82
+ vocab = json.load(f)
83
+ merges = []
84
+ with open(merges_file, encoding="utf-8") as f:
85
+ for line in f:
86
+ line = line.rstrip("\n")
87
+ if not line or line.startswith("#version"):
88
+ continue
89
+ parts = line.split(" ")
90
+ if len(parts) == 2:
91
+ merges.append(tuple(parts))
92
+
93
+ specials = ["<s>", "<pad>", "</s>", "<unk>", "<mask>"]
94
+ missing = [t for t in specials if t not in vocab]
95
+ if missing:
96
+ raise RuntimeError(
97
+ "vocab.json for %s lacks special tokens %s; cannot build a "
98
+ "compatible tokenizer.json" % (model_id, missing)
99
+ )
100
+
101
+ bpe = BPE(vocab=vocab, merges=merges, unk_token="<unk>")
102
+ tok = Tokenizer(bpe)
103
+ # <s> <pad> </s> <unk> <mask> are already plain vocab entries in TrOCR's
104
+ # vocab.json, so no add_special_tokens()/added_tokens are needed
105
+ # (byte-identical behavior to the manual build).
106
+ tok.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False, use_regex=True)
107
+ tok.decoder = decoders.ByteLevel()
108
+ tok.post_processor = processors.TemplateProcessing(
109
+ single="<s> $A </s>",
110
+ pair="<s> $A </s> </s> $B",
111
+ special_tokens=[("<s>", vocab["<s>"]), ("</s>", vocab["</s>"])],
112
+ )
113
+
114
+ # Write atomically so a crash never leaves a partial tokenizer.json.
115
+ tmp_path = tok_path + ".tmp"
116
+ with open(tmp_path, "w", encoding="utf-8") as f:
117
+ f.write(tok.to_str())
118
+ os.replace(tmp_path, tok_path)
119
+
120
+ # Clear cached .no_exist markers so transformers does not conclude
121
+ # tokenizer.json is absent in the repo.
122
+ no_exist = os.path.join(repo_dir, ".no_exist")
123
+ if os.path.isdir(no_exist):
124
+ for marker in glob.glob(os.path.join(no_exist, "*", "tokenizer.json")):
125
+ try:
126
+ os.remove(marker)
127
+ except OSError:
128
+ pass
129
+
130
+ return tok_path
131
+
132
+
133
+ if __name__ == "__main__":
134
+ print(ensure_trocr_tokenizer())