File size: 5,698 Bytes
30bafb7 | 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 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | // Byte-level BPE, read straight from a repo's tokenizer.json.
//
// The three families this build supports — GPT-2, Llama-style (SmolLM,
// TinyLlama), and Qwen2 — all use byte-level BPE, so one implementation covers
// them. A repo using anything else (SentencePiece/Unigram, WordPiece) is
// refused by name rather than approximated: a tokenizer that is merely close
// produces text that looks right and is subtly not, which is the worst
// possible failure mode to ship quietly.
(function (root) {
"use strict";
// GPT-2's byte<->unicode table. Byte-level BPE maps raw bytes into printable
// codepoints so the merge table can be plain text; every byte round-trips.
function byteTables() {
const bs = [];
for (let i = 33; i <= 126; i++) bs.push(i);
for (let i = 161; i <= 172; i++) bs.push(i);
for (let i = 174; i <= 255; i++) bs.push(i);
const cs = bs.slice();
let n = 0;
for (let b = 0; b < 256; b++)
if (!bs.includes(b)) { bs.push(b); cs.push(256 + n); n++; }
const b2u = new Map(), u2b = new Map();
for (let i = 0; i < bs.length; i++) {
const ch = String.fromCodePoint(cs[i]);
b2u.set(bs[i], ch); u2b.set(ch, bs[i]);
}
return { b2u, u2b };
}
const { b2u, u2b } = byteTables();
// GPT-2 / Llama pretokenizer split. Keeps leading spaces attached to the
// following word, which is what the merge table was built against.
const SPLIT = /'s|'t|'re|'ve|'m|'ll|'d| ?[\p{L}]+| ?[\p{N}]+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+/gu;
function build(json) {
const model = json.model || {};
if (String(model.type || "").toUpperCase() !== "BPE")
throw new Error(`tokenizer type "${model.type}" is not supported — this build implements byte-level BPE ` +
`(GPT-2, Llama-style and Qwen2 repos). A different tokenizer would decode to subtly wrong text.`);
const vocab = model.vocab || {};
const ids = [];
for (const [tokStr, id] of Object.entries(vocab)) ids[id] = tokStr;
// merges may be "a b" strings or ["a","b"] pairs depending on the version
const ranks = new Map();
(model.merges || []).forEach((m, i) => {
const pair = Array.isArray(m) ? m : String(m).split(" ");
if (pair.length === 2) ranks.set(pair[0] + " |