Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 58 additions & 39 deletions js/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,55 @@ import base64 from "base64-js";
import type { TiktokenModel } from "./ranks/ranks";
import { never } from "./utils";

function bytePairMerge(
piece: Uint8Array,
ranks: Map<string, number>
): Array<{ start: number; end: number }> {
let parts: Array<{ start: number; end: number }> = Array.from(
{ length: piece.length },
(_, i) => ({ start: i, end: i + 1 })
);

while (parts.length > 1) {
let minRank: [number, number] | null = null;

for (let i = 0; i < parts.length - 1; i++) {
const slice = piece.slice(parts[i].start, parts[i + 1].end);
const rank = ranks.get(slice.join(","));
if (rank == null) continue;

if (minRank == null || rank < minRank[0]) {
minRank = [rank, i];
function bytesToKey(bytes: Uint8Array, start: number, end: number): string {
let key = "";
for (let i = start; i < end; i++) key += String.fromCharCode(bytes[i]);
return key;
}

function bytePairEncode(piece: Uint8Array, ranks: Map<string, number>): number[] {
const n = piece.length;
if (n === 1) return [ranks.get(bytesToKey(piece, 0, 1))!];

const starts = new Int32Array(n + 1);
for (let i = 0; i <= n; i++) starts[i] = i;
let count = n + 1;

const rank = new Float64Array(n + 1).fill(Infinity);
const rankAt = (i: number): number => {
if (i + 2 >= count) return Infinity;
const r = ranks.get(bytesToKey(piece, starts[i], starts[i + 2]));
return r === undefined ? Infinity : r;
};
for (let i = 0; i + 2 < count; i++) rank[i] = rankAt(i);

while (count > 2) {
let minRank = Infinity;
let minIdx = -1;
for (let i = 0; i + 2 < count; i++) {
if (rank[i] < minRank) {
minRank = rank[i];
minIdx = i;
}
}
if (minIdx === -1) break;

if (minRank != null) {
const i = minRank[1];
parts[i] = { start: parts[i].start, end: parts[i + 1].end };
parts.splice(i + 1, 1);
} else {
break;
for (let i = minIdx + 1; i + 1 < count; i++) {
starts[i] = starts[i + 1];
rank[i] = rank[i + 1];
}
}
return parts;
}
count--;

function bytePairEncode(piece: Uint8Array, ranks: Map<string, number>) {
if (piece.length === 1) return [ranks.get(piece.join(","))!];
rank[minIdx] = rankAt(minIdx);
if (minIdx > 0) rank[minIdx - 1] = rankAt(minIdx - 1);
}

return bytePairMerge(piece, ranks)
.map((p) => ranks.get(piece.slice(p.start, p.end).join(",")))
.filter((x): x is number => x != null);
const out: number[] = [];
for (let i = 0; i + 1 < count; i++) {
const r = ranks.get(bytesToKey(piece, starts[i], starts[i + 1]));
if (r != null) out.push(r);
}
return out;
}

function escapeRegex(str: string) {
Expand All @@ -63,6 +73,12 @@ export class Tiktoken {
/** @internal */
protected patStr: string;

/** @internal */
protected pattern: RegExp;

/** @internal */
protected specialTokensRegex: RegExp;

/** @internal */
protected textEncoder = new TextEncoder();

Expand Down Expand Up @@ -93,7 +109,7 @@ export class Tiktoken {

for (const [token, rank] of Object.entries(uncompressed)) {
const bytes = base64.toByteArray(token);
this.rankMap.set(bytes.join(","), rank);
this.rankMap.set(bytesToKey(bytes, 0, bytes.length), rank);
this.textMap.set(rank, bytes);
}

Expand All @@ -104,6 +120,11 @@ export class Tiktoken {
memo[rank] = this.textEncoder.encode(text);
return memo;
}, {});

this.pattern = new RegExp(this.patStr, "ug");
this.specialTokensRegex = Tiktoken.specialTokenRegex(
Object.keys(this.specialTokens)
);
}

private static specialTokenRegex = (tokens: string[]) => {
Expand All @@ -115,10 +136,8 @@ export class Tiktoken {
allowedSpecial: Array<string> | "all" = [],
disallowedSpecial: Array<string> | "all" = "all"
) {
const regexes = new RegExp(this.patStr, "ug");
const specialRegex = Tiktoken.specialTokenRegex(
Object.keys(this.specialTokens)
);
const regexes = this.pattern;
const specialRegex = this.specialTokensRegex;

const ret: number[] = [];

Expand Down Expand Up @@ -164,7 +183,7 @@ export class Tiktoken {
const end = nextSpecial?.index ?? text.length;
for (const match of text.substring(start, end).matchAll(regexes)) {
const piece = this.textEncoder.encode(match[0]);
const token = this.rankMap.get(piece.join(","));
const token = this.rankMap.get(bytesToKey(piece, 0, piece.length));

if (token != null) {
ret.push(token);
Expand Down