publish_model.py: ship tokenizer_config.json + special_tokens_map.json (Gate 5 could not load a bare tokenizer.json)
Browse files- build/publish_model.py +32 -0
build/publish_model.py
CHANGED
|
@@ -266,6 +266,38 @@ def main():
|
|
| 266 |
# Gate 5 could never run (found by running it, 2026-09-21T13:40Z).
|
| 267 |
shutil.copy(os.path.join(a.src, "tokenizer.json"), src) # ship the tokenizer that cut the mix
|
| 268 |
have.add("tokenizer.json")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
cfg = json.load(open(os.path.join(src, "config.json")))
|
| 270 |
run = {}
|
| 271 |
if os.path.exists(os.path.join(src, "run_summary.json")):
|
|
|
|
| 266 |
# Gate 5 could never run (found by running it, 2026-09-21T13:40Z).
|
| 267 |
shutil.copy(os.path.join(a.src, "tokenizer.json"), src) # ship the tokenizer that cut the mix
|
| 268 |
have.add("tokenizer.json")
|
| 269 |
+
# CARD_FILES already promised tokenizer_config.json and special_tokens_map.json, but nothing ever put
|
| 270 |
+
# them in the export, so the repo shipped a bare tokenizer.json. Without them AutoTokenizer falls back
|
| 271 |
+
# to a literal unknown-token name that this BPE vocab does not contain, so decoding raised
|
| 272 |
+
# "Unk token not found in the vocabulary" and Gate 5 could not load the tokenizer (2026-09-21T13:48Z).
|
| 273 |
+
# They are fetched from the tokenizer's own hub repo, so the published tokenizer is byte-complete.
|
| 274 |
+
TOK_SOURCE = "HuggingFaceTB/SmolLM2-135M"
|
| 275 |
+
tk = man.get("tokenizer") # manifest may hold a string or a mapping
|
| 276 |
+
if isinstance(tk, dict):
|
| 277 |
+
TOK_SOURCE = tk.get("hub") or tk.get("repo") or TOK_SOURCE
|
| 278 |
+
elif isinstance(tk, str) and "/" in tk:
|
| 279 |
+
TOK_SOURCE = tk
|
| 280 |
+
print("tokenizer source for config files:", TOK_SOURCE, flush=True)
|
| 281 |
+
for extra in ("tokenizer_config.json", "special_tokens_map.json"):
|
| 282 |
+
if extra in have:
|
| 283 |
+
continue
|
| 284 |
+
blob = urllib.request.urlopen(f"https://huggingface.co/{TOK_SOURCE}/resolve/main/{extra}",
|
| 285 |
+
timeout=120).read()
|
| 286 |
+
dec = json.loads(blob.decode("utf-8"))
|
| 287 |
+
u = dec.get("unk_token")
|
| 288 |
+
u = (u.get("content") if isinstance(u, dict) else u)
|
| 289 |
+
if u and u not in json.load(open(os.path.join(src, "tokenizer.json"), encoding="utf-8")
|
| 290 |
+
)["model"]["vocab"]:
|
| 291 |
+
dec["unk_token"] = None # never declare a token the vocab lacks
|
| 292 |
+
blob = json.dumps(dec, indent=2, ensure_ascii=False).encode("utf-8")
|
| 293 |
+
with open(os.path.join(src, extra), "wb") as fh:
|
| 294 |
+
fh.write(blob)
|
| 295 |
+
have.add(extra)
|
| 296 |
+
print("tokenizer config", extra, len(blob), "bytes sha256",
|
| 297 |
+
sha(os.path.join(src, extra))[:16], flush=True)
|
| 298 |
+
for required in ("tokenizer.json", "tokenizer_config.json", "special_tokens_map.json"):
|
| 299 |
+
if required not in have:
|
| 300 |
+
raise SystemExit(f"tokenizer is incomplete: {required} is missing from the export")
|
| 301 |
cfg = json.load(open(os.path.join(src, "config.json")))
|
| 302 |
run = {}
|
| 303 |
if os.path.exists(os.path.join(src, "run_summary.json")):
|