From 86c75bbd079edd1d0914bb019b9a25891a9e0396 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 9 Apr 2024 14:55:11 -0700 Subject: [PATCH 01/11] add a notebook with performance baselines --- tokenizer_ts/package.json | 1 + tokenizer_ts/perf/.gitignore | 3 + tokenizer_ts/perf/benchmark-folder.js | 65 +++++++++ tokenizer_ts/perf/notebook.ipynb | 195 ++++++++++++++++++++++++++ 4 files changed, 264 insertions(+) create mode 100644 tokenizer_ts/perf/.gitignore create mode 100644 tokenizer_ts/perf/benchmark-folder.js create mode 100644 tokenizer_ts/perf/notebook.ipynb diff --git a/tokenizer_ts/package.json b/tokenizer_ts/package.json index 478dd01..fc09608 100644 --- a/tokenizer_ts/package.json +++ b/tokenizer_ts/package.json @@ -34,6 +34,7 @@ "scripts": { "test": "mocha -u tdd --require ts-node/register test/**/*.ts", "build": "tsc -p ./tsconfig.json", + "watch": "tsc -p ./tsconfig.json --watch", "eslint": "eslint src --ext ts", "format": "prettier --write \"./**/*.{ts,tsx}\"" }, diff --git a/tokenizer_ts/perf/.gitignore b/tokenizer_ts/perf/.gitignore new file mode 100644 index 0000000..ff67559 --- /dev/null +++ b/tokenizer_ts/perf/.gitignore @@ -0,0 +1,3 @@ +package.json +package-lock.json +*.cpuprofile diff --git a/tokenizer_ts/perf/benchmark-folder.js b/tokenizer_ts/perf/benchmark-folder.js new file mode 100644 index 0000000..1047065 --- /dev/null +++ b/tokenizer_ts/perf/benchmark-folder.js @@ -0,0 +1,65 @@ +const fs = require('fs/promises'); +const path = require('path'); +const inspector = require('inspector'); +const { promisify } = require('util'); + +const [,, encoderName, folderPath, method, modulePath] = process.argv; +const { createByEncoderName } = require(modulePath); +const minTime = 10_000; +const minCycles = 5; + +const fileExtensions = ['.ts', '.js', '.py']; + +async function readAllFilesInFolder(folderPath) { + const files = await fs.readdir(folderPath, { withFileTypes: true }); + const fileContents = await Promise.all(files.map(async (file) => { + const res = path.resolve(folderPath, file.name); + if (file.isDirectory()) { + return readAllFilesInFolder(res); + } else if (fileExtensions.some(f => res.endsWith(f))) { + return fs.readFile(res, 'utf8'); + } else { + return []; + } + })); + + return fileContents.flat(); +} + +Promise.all([ + readAllFilesInFolder(folderPath), + createByEncoderName(encoderName) +]).then(async ([files, tokenizer]) => { + let totalSize = 0; + for (const file of files) { + totalSize += file.length; + } + + const session = new inspector.Session(); + session.connect(); + const post = promisify(session.post).bind(session); + await post('Profiler.enable'); + await post('Profiler.start'); + + const start = performance.now(); + let cycles = []; + while (performance.now() - start < minTime || cycles.length < minCycles) { + const cycleStart = performance.now(); + switch (method) { + case 'encode': + files.forEach(file => tokenizer.encode(file)); + break; + case 'encodeTrimSuffix': + files.forEach(file => tokenizer.encodeTrimSuffix(file, 1337)); + break; + default: + throw new Error(`unknown method ${method}`); + } + cycles.push(performance.now() - cycleStart); + } + + const data = await post('Profiler.stop'); + await fs.writeFile('profile.cpuprofile', JSON.stringify(data.profile)); + + process.stdout.write(JSON.stringify({ totalSize, cycles })); +}); diff --git a/tokenizer_ts/perf/notebook.ipynb b/tokenizer_ts/perf/notebook.ipynb new file mode 100644 index 0000000..807e536 --- /dev/null +++ b/tokenizer_ts/perf/notebook.ipynb @@ -0,0 +1,195 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# TS Tiktokenizer Performance\n", + "\n", + "This notebook is used for analyzing the performance of and performance improvements to the Tokenizer. It uses the VS Code repo as its corpus. First, let's grab the last released version of `@microsoft/tiktokenizer`, and get a baseline." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import os\n", + "import subprocess\n", + "import json\n", + "\n", + "vscode_repo_path = \"../../../vscode\"\n", + "if not os.path.exists(vscode_repo_path):\n", + " print(\"The repo does not exist.\")\n", + "\n", + "def run_benchmark(module_path, encoder_name = 'cl100k_base', method = 'encode'):\n", + " command = f\"node --prof ./benchmark-folder.js {encoder_name} {vscode_repo_path}/src {method} {module_path}\"\n", + " result = subprocess.check_output(command, shell=True)\n", + " parsed = json.loads(result)\n", + " return parsed\n", + "\n", + "os.system('npm install @microsoft/tiktokenizer --prefix ./')\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Performance can vary machine to machine, make sure to collect a baseline before you start working. Every time you run a benchmark, there'll be a `profile.cpuprofile` written out that you can inspect." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [], + "source": [ + "# This can take a minute, make some tea 🍵\n", + "baseline = run_benchmark('@microsoft/tiktokenizer')" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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zFGDBg0UGxvrzW/atKlmzJihPXv2aMOGDcrMzNQ999xT6LqqVaum7777rsi8mZmZqlu3rvfY399fYWFhysjIUGZmpmrXri0/Pz9v/unL7tq1S1OmTCmw73bv3q3MzMwitwd3Ako7AFBSwsLCVKFCBR04cEABAcW/1E//JSZJoaGhOnjwoH766SevdNLT01W7du2zHr97927l5+d7pZOenq5GjRp5y9x22206dOiQunfvrvfff1+VK1dWWFiY2rdvrw8//PCcnm9htm7dqkGDBmnevHkKCwvzpoeFhWn06NFnfVPG5ZdfriuvvFKzZs3SkiVLNHDgwCKXbdKkiQYPHqwXX3yx0PmdO3fW3LlzdeuttxY6PzQ0VOvXr/cem5l2797tFU1GRobMzNvf6enpioiIKPC8Ro8efVbPC25xhIMyq1atWurSpYvuv/9+HTlyRPn5+dq+fbuWL19e6PI1atRQWlqa8vPzJZ385dW2bVs98sgjys7O1rp16zR9+vQif0nXqFGjwN+/tGrVSpUrV9bkyZOVk5OjZcuWadGiRerXr1+BcdOmTVPjxo3Vo0cPHTt2TD169NC2bdv0+uuvKycnRzk5OVq9erU2b958Ts//yJEjuuGGG/TEE0+oXbt2BeYNHz5cEydO1MaNGyXJu1PsTAYNGqRp06bps88+K7APtmzZoilTpmjPnj2SpN27d2v27Nlq3bp1oesZO3asPv/8cz344IPeUdu3336rAQMG6Mcff1SfPn20ePFiffTRR8rJydGUKVNUoUIFtW3bVm3atFFAQICef/555ebmat68efryyy+9dd9222164YUXtGrVKpmZjh49qsWLF3P3m4+gcFCmzZw5UydOnFCzZs0UHBys3r17F3k65+abb5Z08pRPXFycJGn27NlKS0tTaGioevbsqbFjx+qaa64pdPzQoUO1adMmValSRTfeeKMuuugiLVy4UEuWLFFISIhGjBihmTNnqkmTJgXG+fn56aWXXlJYWJhuuOEGlS9fXsnJyXrrrbcUGhqqmjVr6qGHHtLx48fP6bmvWbNGW7du1X333Vfgb50kqWfPnnrooYfUr18/XXrppYqKitKSJUvOuL7evXvr0KFD6tSpk2rVquVNv+SSS7Rq1SqvYFu3bq2oqChNmTKl0PVERERo5cqVSktLU2RkpIKCgnTTTTcpPj5el1xy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7Dl1us7M9Mgjj3jr+fbbb71zkwAAafTo0Ro6dKiWLl2qnJwcLV26VEOHDtXo0aMv2DaKLZzBgwera9euyszMlCQ1atRIzz777FmtPD09XStXrpR08oLUqcO1kJAQZWVleddY8vPztXv3bnXs2FGTJ0/Wjz/+qKysLHXt2lV///vfveL46quvzvkJSif/puiVV17xbunOyMjQ999/f17rAoCyqH///ho/frxGjRqliy++WKNGjdL48eMv2A0D0lncpXbgwAH16dNHEydOPDkgIEDlypU7q5U3bdpUr732mm6//XY1bNhQd9xxhw4dOqTo6GiFh4crISFBkpSXl6cBAwbo8OHDMjPde++9qlKlih577DHdc889iomJkZkpPDzcu+ZzLrp06aLNmzerTZs2kqTAwEC98cYbuuyyy855XQBQVvXv3/+CFswv+VkxH3LfoUMHzZ07V9dcc43WrFmjL774Qg899JCWL19eYqF8QXx8vFJSUs5r7KlTicXsWvfGBEljDpd2ikL5+fn53v4CcM7O9Luz2COcqVOn6vrrr9f27dt1xRVXaP/+/QVuNwYA4GwUWzhxcXFavny5tm7dKjNT48aNVb58eRfZAABlSLGFk5eXp/fee09paWnKzc1VcnKyJOm+++4r8XAAgLKj2MK57rrrdPHFFys6Olr+/nyawdk49THcAID/U2zh7NmzR+vWrXORBQBQhhV7yNKtWzfvNBoAAOer2COc1q1bq2fPnsrPz1f58uW900VHjhxxkQ8AUEYUWzj333+/Vq5cqejoaK5LAADOW7Gn1Bo2bKioqCjKBgDwmxR7hFOrVi116NBB3bp1K/DGmNwWDQA4F8UWTr169VSvXj2dOHFCJ06ccJGpzPC1o0JLutTnMp0SHBxc2hEAlLBiC4dPxzw/vvq+YDamtBMA+L0qsnBGjhypadOm6brrriv0f8ULFy4s0WAAgLKlyMKZOXOmpk2bpgceeMBlHgBAGVVk4UREREiS2rdv7ywMAKDsKrJw9u/fr6lTpxY5kLvUAADnosjCycvLU1ZWls9e/AYA/G8psnBq1aqlv/3tby6zAADKsCLfaYAjGwDAhVRk4Xz00UcucwAAyrgiC6dq1aoucwAAyjg+whMA4ASFAwBwgsIBADhB4QAAnKBwAABOUDgAACcoHACAExQOAMAJCgcA4ASFAwBwgsIBADhB4QAAnKBwAABOUDgAACcoHACAExQOAMAJCgcA4ASFAwBwgsIBADhB4QAAnKBwAABOUDgAACcoHACAExQOAMAJCgcA4ASFAwBwgsIBADhB4QAAnKBwAABOUDgAACcoHACAExQOAMAJCgcA4ASFAwBwgsIBADhB4QAAnKBwAABOUDgAACcoHACAExQOAMAJCgcA4ASFAwBwgsIBADhB4QAAnKBwAABOUDgAACcoHACAExQOAMAJCgcA4ASFAwBwgsIBADhB4QAAnKBwAABOUDgAACcoHACAExQOAMAJCgcA4ASFAwBwgsIBADhB4QAAnKBwAABOUDgAACcoHACAExQOAMAJCgcA4ASFAwBwgsIBADhB4QAAnKBwAABOUDgAACcoHACAExQOAMAJCgcA4ASFAwBwgsIBADhB4QAAnKBwAABOUDgAACcoHACAExQOAMAJCgcA4ASFAwBwws/MrLRD+KKQkBCFh4d7j/fv36/q1auXXqAzINv5Idv5Idv5+b1kS0tL04EDBwqdR+Gcpfj4eKWkpJR2jEKR7fyQ7fyQ7fyQjVNqAABHKBwAgBMUzlkaNmxYaUcoEtnOD9nOD9nOD9m4hgMAcIQjHACAExQOAMCJ31XhDBkyRJdddpmioqK8aY899phiYmLUokULdenSRZmZmZKkEydO6NZbb1V0dLSaN2+uZcuWeWNSU1MVHR2tBg0a6K677tKps5LHjx9X37591aBBA7Vq1UppaWm/KdspTz/9tPz8/Arc2z5x4kQ1aNBAjRs31gcffOAz2X744Qd17NhRgYGBGjlyZIFlSzvbhx9+qJYtWyo6OlotW7bUxx9/7DPZvvzyS7Vo0UItWrRQ8+bNNX/+fJ/Jdkp6eroCAwP19NNP+0y2tLQ0VaxY0dt3w4cP95lskrRu3Tq1adNGkZGRio6OVnZ2dollO9d8s2bN8vZbixYt5O/vr7Vr15ZoPtnvyPLlyy01NdUiIyO9aYcPH/a+f+655+z22283M7Np06bZ4MGDzcxs3759FhcXZ3l5eWZmlpCQYJ9//rnl5+fbtddea++9956Zmf3jH//wxs+ePdv69Onzm7KZmaWnp1uXLl3s8ssvt/3795uZ2caNGy0mJsays7Ntx44dVr9+fcvNzfWJbFlZWfbpp5/av/71L7vzzjsLLF/a2dasWWMZGRlmZrZ+/XoLDQ31mWxHjx61nJwcMzPLzMy06tWre49LO9spvXr1st69e9tTTz3lTSvtbDt37vzVcr6SLScnx6Kjo23t2rVmZnbgwIES/Tk913ynW7dundWrV897XFL5fleFY3bmF+iECRNs+PDhZmY2YsQIe/311715V199ta1atcoyMzOtcePG3vQ333zThg0bZmZmXbp0sc8//9zMTr7YqlWrZvn5+b8p20033WRr1661unXrei+UCRMm2IQJE7xlTm3XF7Kd8uqrrxYoHF/KZmaWn59vVatWtezsbJ/LtmPHDrvsssssJyfHZ7LNnz/fHnjgAUtKSvIKxxeyFfXz7AvZFi9ebImJiU6znUu+0z3yyCP217/+tcTz/a5OqRVl9OjRCgsL06xZszRu3DhJUvPmzbVgwQLl5uZq586dSk1N1e7du5WRkaE6dep4Y+vUqaOMjAxJUkZGhsLCwiRJAQEBCgoK0g8//HDeuRYuXKjatWurefPmBaafvp3TM/hCtqL4Wra5c+cqNjZWFSpU8Jlsq1at8k69vPDCCwoICPCJbEePHtWTTz6ppKSkAtN9IZsk7dy5U7GxsWrfvr0+/fRTn8m2bds2+fn5qWvXroqLi9PkyZOdZztTvtO9/fbb6t+/f4nnCzifJ1DWjB8/XuPHj9fEiRM1bdo0jR07VkOGDNHmzZsVHx+vunXrqm3btgoICPDOZZ7Oz89Pks4471z9/PPPGj9+vJKTk381r6jt+EK2ovhSto0bN+qhhx7ylvGVbK1atdLGjRu1efNmDRo0SN26dfOJbElJSbr33nsVGBhYYLovZKtVq5bS09NVrVo1paam6sYbb9TGjRt9Iltubq5WrFih1atXq1KlSurUqZNatmypSy+91Em24vKdsmrVKlWqVMm77lOS+44jnNPccsstmjt3rqST7f3MM89o7dq1WrBggX788Uc1bNhQderU0Z49e7wxe/bsUWhoqKST/xPYvXu3pJMvtsOHD6tq1arnlWX79u3auXOnmjdvrvDwcO3Zs0dxcXHau3dvge2cnsEXshXFV7Lt2bNHPXv21MyZMxUREeFT2U5p2rSpKleurA0bNvhEtlWrVukvf/mLwsPD9eyzz2rChAmaNm2aT2SrUKGCqlWrJklq2bKlIiIitG3bNp/IVqdOHbVv314hISGqVKmSunfvrjVr1jjLVly+U9566y3v6OZUhhLLd9Yn38qIX57f3LZtm/f9888/bzfddJOZnbyIm5WVZWZmycnJduWVV3rLxcfH28qVK70LaosXLzazkzcanH5B7eabb/5N2U53+rnXDRs2FLhpoF69et7FyNLOdsovr+H4QrZDhw5ZTEyMzZkz51fLlXa2HTt2eDcJpKWlWa1atbx5pZ3tdKdfw/GFbN9//7332t++fbuFhobaDz/84BPZDh48aLGxsd4NIZ06dbJ33323RLOdSz4zs7y8PKtdu7Zt3769wHIlle93VTj9+vWzmjVrWkBAgNWuXdtefvll69Wrl0VGRlp0dLT16NHD9uzZY2Yn/9EaNWpkTZo0sU6dOllaWpq3ntWrV1tkZKTVr1/f7rzzTu+i2bFjx6x3794WERFhCQkJv/pHPNdsp/vlC+WJJ56w+vXrW6NGjbw7SHwlW926dS04ONgqV65stWvXto0bN/pEtscff9wqVapkzZs397727dvnE9lmzpxpzZo1s+bNm1tsbKzNnz/fW660s53ul4VT2tnmzJljzZo1s5iYGIuNjbWFCxf6TDYzs9dff92aNWtmkZGR9uCDD5ZotvPJt3TpUmvVqtWv1lNS+XhrGwCAE1zDAQA4QeEAAJygcAAATlA4AAAnKBwAgBMUDgDACQoHAODE/wOF0/2h3aMtkAAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "cycles = baseline['cycles']\n", + "fig, ax = plt.subplots()\n", + "ax.boxplot(cycles, vert=False, labels=[\"baseline\"])\n", + "ax.set_title('Time to tokenize VS Code')\n", + "ax.set_ylabel('Time / ms')\n", + "fig.patch.set_facecolor('white')\n", + "ax.set_facecolor('white')\n", + "plt.show()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here's the current performance of the repo. Make sure to `npm run build` or `npm run watch` first!" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [], + "source": [ + "updated = run_benchmark('../')" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "fig, ax = plt.subplots()\n", + "ax.boxplot([baseline['cycles'], updated['cycles']], vert=False, labels=[\"baseline\", \"updated\"])\n", + "ax.set_title('Time to tokenize VS Code')\n", + "ax.set_ylabel('Time / ms')\n", + "fig.patch.set_facecolor('white')\n", + "ax.set_facecolor('white')\n", + "plt.show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, ax = plt.subplots()\n", + "\n", + "# Calculate average time in seconds\n", + "baseline_avg_time = sum(baseline['cycles']) / len(baseline['cycles']) / 1000\n", + "updated_avg_time = sum(updated['cycles']) / len(updated['cycles']) / 1000\n", + "\n", + "# Calculate total size in MB\n", + "total_size_MB = baseline['totalSize'] / (1024 * 1024)\n", + "\n", + "# Calculate average speed in MB/s\n", + "baseline_speed = total_size_MB / baseline_avg_time\n", + "updated_speed = total_size_MB / updated_avg_time\n", + "\n", + "# Plot the bar chart\n", + "ax.bar(['Baseline', 'Updated'], [baseline_speed, updated_speed])\n", + "ax.set_ylabel('Tokenization Speed / MBs^-1')\n", + "fig.patch.set_facecolor('white')\n", + "ax.set_facecolor('white')\n", + "plt.title('Tokenization Speed')\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 53cfc00970880bbe7d7044619b20c7b6e4fe0c96 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 9 Apr 2024 15:39:19 -0700 Subject: [PATCH 02/11] ts: initial perf improvements I was actually wrong about the input to the key being <=3 bytes. In some cases it can be much larger, so this goes with the multimap approach @andreamah first suggested. However it encodes as much data per level as it can to try to keep to the hot path. ![](https://memes.peet.io/img/24-04-3f98a5b5-fd1a-4165-bd06-7b531b1736d6.png) --- tokenizer_ts/perf/notebook.ipynb | 12 ++--- tokenizer_ts/src/bytePairEncode.ts | 79 ++++++++++++++++++++++++------ tokenizer_ts/src/tikTokenizer.ts | 16 +++--- 3 files changed, 79 insertions(+), 28 deletions(-) diff --git a/tokenizer_ts/perf/notebook.ipynb b/tokenizer_ts/perf/notebook.ipynb index 807e536..e8342aa 100644 --- a/tokenizer_ts/perf/notebook.ipynb +++ b/tokenizer_ts/perf/notebook.ipynb @@ -35,7 +35,7 @@ " print(\"The repo does not exist.\")\n", "\n", "def run_benchmark(module_path, encoder_name = 'cl100k_base', method = 'encode'):\n", - " command = f\"node --prof ./benchmark-folder.js {encoder_name} {vscode_repo_path}/src {method} {module_path}\"\n", + " command = f\"node ./benchmark-folder.js {encoder_name} {vscode_repo_path}/src {method} {module_path}\"\n", " result = subprocess.check_output(command, shell=True)\n", " parsed = json.loads(result)\n", " return parsed\n", @@ -97,7 +97,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 36, "metadata": {}, "outputs": [], "source": [ @@ -106,12 +106,12 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 37, "metadata": {}, "outputs": [ { "data": { - "image/png": "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", + "image/png": "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", "text/plain": [ "
" ] @@ -133,12 +133,12 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 38, "metadata": {}, "outputs": [ { "data": { - "image/png": "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", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXgAAAEICAYAAABVv+9nAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/MnkTPAAAACXBIWXMAAAsTAAALEwEAmpwYAAAjoUlEQVR4nO3deVxU5f4H8M+wiAurCgqCIPpyQwEBFQkFJRCvaJLXVCARNdQopX6WleZuWelNMcsmDbxl2M3MHU1JXFEcFa+45QIooDiIgMgOz+8PX82NdBxgmBGmz/v18qVnmef5zjR9ODznnOdIhBACRESkc/SedwFERKQZDHgiIh3FgCci0lEMeCIiHcWAJyLSUQx4IiIdxYAnrUtKSoKtrW2jtztjxgwsXbq00dsdMWIENm3a1OjtNhWLFi1CWFjY8y6DNIABT2oxNjZW/NHT00OrVq0Uy5s3b9ZqLevXr8eHH36oVhtPC7uEhASEh4er1e7TZGVlYezYsWjfvj3MzMzQt29fxMXFNXo/9Pdl8LwLoOatuLhY8W8HBwds2LABL7744nOsqPl49dVX4eLigszMTBgZGeHChQu4e/fu8y6LdAiP4EkjysvLER0dDRsbG9jY2CA6Ohrl5eVP3TcmJga9e/dGVlYWysvLMWfOHHTu3BkdOnTAjBkzUFpaCuB/QzurVq2ClZUVrK2tERsbq2hn8uTJmD9/PgBg1KhRT/x28cfR8ezZs2FnZwdTU1O4u7vj6NGjAIB9+/bho48+wo8//ghjY2O4uLgAAHx9fbFhwwYAQE1NDZYtWwZ7e3tYWVlh0qRJKCwsBABkZGRAIpFg06ZN6Ny5M9q3b4/ly5cr/YxOnz6NyZMno02bNjAwMEC/fv0wYsSIWm1JpVLY2NjA2toaq1atUry2pqYGK1asQNeuXdGuXTu88soryM/PV2w/efIkvLy8YG5uDhcXFyQlJSm2paenw8fHByYmJvD390deXp7q/6DULDHgSSOWL1+OkydPIjU1FefPn0dKSgqWLVv2xH5Lly5FXFwcDh8+DFtbW8ydOxe///47UlNTcf36dWRnZ2PJkiWK/e/evYvCwkJkZ2dj48aNiIqKwoMHD55od9euXSguLkZxcTG2bt2Kjh07ws/PDwDQv39/pKamIj8/HyEhIRg3bhzKysoQGBiIDz74AOPHj0dxcTHOnz//RLtxcXGIi4vDoUOHcPPmTRQXF+ONN96otc+xY8dw9epVJCYmYsmSJbh8+fJTPyNPT09ERUVhy5YtuHXr1lP3OXToEK5du4Zff/0VK1aswMGDBwE8/qG4fft2HD58GDk5ObCwsEBUVBQAIDs7GyNHjsT8+fORn5+PlStXYuzYsZDL5QCAkJAQuLu7Iy8vDx9++KFOn1/42xNEjcTe3l4cOHBACCGEo6Oj2LNnj2Lbvn37hL29vRBCiEOHDgkbGxvx1ltviRdeeEEUFBQIIYSoqakRrVu3FtevX1e87sSJE8LBwUHxupYtW4rKykrFdktLS5GcnCyEECI8PFzMmzevVk1Xr14VlpaW4siRI0rrNjc3F6mpqUIIIRYuXChCQ0Nrbffx8RHffPONEEKIYcOGiXXr1im2XblyRRgYGIjKykqRnp4uAIjbt28rtvfv31/Ex8c/td/8/Hwxd+5c0bt3b6GnpydcXFxESkqKEEIo2rp8+bJi/3feeUdMmTJFCCFEz549xcGDBxXbcnJyFHWsWLFChIWF1eorICBAxMXFiczMTKGvry+Ki4sV2yZOnPjEeybdwCN40oicnBzY29srlu3t7ZGTk6NYLigogFQqxfvvvw8zMzMAgFwuR0lJCdzd3WFubg5zc3MEBgYqjjwBoF27djAw+N+po9atW9c6D/BnhYWFeOmll7B06VIMHjxYsX7VqlXo1asXzMzMYG5ujsLCwjoPUzztfVVVVSE3N1exrmPHjnWqz8LCAitWrMDFixeRm5sLV1dXjBkzBuJP8//Z2dnV6uuPzzAzMxPBwcGKz6lXr17Q19dHbm4uMjMz8dNPPym2mZub49ixY7hz547iaL9Nmza12iXdxIAnjbCxsUFmZqZi+datW7CxsVEsW1hYYPfu3YiIiMDx48cBAO3bt0erVq1w8eJFFBQUoKCgAIWFhUoD8llqamoQEhKCoUOHYvr06Yr1R48exSeffIL//Oc/ePDgAQoKCmBmZqYIVYlEUu/3ZWBggA4dOtS7xj9r37495syZg5ycnFpj6bdv367V1x+foZ2dHRISEhSfU0FBAcrKytCpUyfY2dnh1VdfrbXt0aNHeO+992BtbY0HDx7g0aNHtdol3cSAJ42YOHEili1bBrlcjry8PCxZsuSJyw99fX2xefNmBAcH49SpU9DT08Nrr72Gt956C/fu3QPweDx5//799e5/3rx5ePToEdasWVNr/cOHD2FgYABLS0tUVVVhyZIlKCoqUmzv0KEDMjIyUFNTo/R9ff7550hPT0dxcbFizP7Pv1XU1dy5c5GWloaqqio8fPgQX331Fbp164Z27dop9lm6dClKSkpw8eJFxMbGYvz48QAeX/M/b948xQ8buVyOHTt2AADCwsKwa9cu7N+/H9XV1SgrK0NSUhKysrJgb28PDw8PLFy4EBUVFTh27Bh27dpV79qpeWDAk0bMnz8fHh4ecHZ2Rt++feHm5qa4wuXP/P39ERsbi9GjR+PMmTP45JNP0K1bN3h6esLU1BQvvvgirl69Wu/+4+PjcfLkSVhYWNS6Ln/48OEYMWIEunfvDnt7e7Rs2bLWMMi4ceMAPB4KcnNze6LdKVOm4NVXX8WQIUPQpUsXtGzZEmvXrq13fQBQUlKiGGZxdHREZmYmdu7cWWsfHx8fdOvWDX5+fpgzZw4CAgIAPL4SaPTo0QgICICJiQk8PT1x6tQpAI+P7nfs2IGPPvoIlpaWsLOzw2effab4ofXDDz/g1KlTaNu2LRYvXoxJkyY1qH5q+iRC8IEfRE1NRkYGunTpgsrKygb9dkAE8AieiEhnMeCJiHQUh2iIiHQUj+CJiHRUkzp70759ezg4ODzvMoiImo2MjAylN+o1qYB3cHCATCZ73mUQETUbHh4eSrdxiIaISEcx4ImIdBQDnohIRzHgiYh0FAOeiEhHMeCJiHQUA56ISEcx4ImIdBQDnohIRzWpO1mJdJnDe3uedwnURGWsGKmRdnkET0SkoxjwREQ6SmMBf/XqVbi6uir+mJqaYvXq1ZrqjoiI/kJjY/A9evRAamoqAKC6uhqdOnVCcHCwprojIqK/0MoQTWJiIrp27Qp7e3ttdEdERNDSVTRbtmzBxIkTn7pNKpVCKpUCAORyuTbKISL6W9D4EXxFRQV27tyJcePGPXV7ZGQkZDIZZDIZLC0tNV0OEdHfhsYDPiEhAW5ubujQoYOmuyIioj/ReMDHx8crHZ4hIiLN0WjAl5SU4MCBA3j55Zc12Q0RET2FRk+ytm7dGvfv39dkF0REpATvZCUi0lEMeCIiHcWAJyLSUQx4IiIdxYAnItJRDHgiIh3FgCci0lEMeCIiHcWAJyLSUQx4IiIdxYAnItJRDHgiIh3FgCci0lEMeCIiHcWAJyLSUQx4IiId1aCAj42Nbew6iIiokTUo4BcuXNjYdRARUSNT+sg+Z2fnp64XQiA3N1djBRERUeNQGvC5ubnYv38/LCwsaq0XQsDLy0vjhRERkXqUBnxQUBCKi4vh6ur6xDZfX986NV5QUIBp06YhLS0NEokE3377LQYNGtTQWomIqB6UBvzGjRuVvuiHH36oU+OzZ89GYGAgtm7dioqKCpSUlNS/QiIiapB6nWSVSqV13reoqAhHjhzB1KlTAQAtWrSAubl5vYojIqKGq1fAr1+/vs773rx5E5aWloiIiEC/fv0wbdo0PHr06In9pFIpPDw84OHhAblcXp9yiIjoGeoV8EKIOu9bVVWFs2fPYubMmTh37hzatGmDFStWPLFfZGQkZDIZZDIZLC0t61MOERE9Q70CfteuXXXe19bWFra2thg4cCAA4J///CfOnj1bv+qIiKjBVAb8w4cPFf+2tbWtc8MdO3aEnZ0drl69CgBITExE7969G1AiERE1hNKraAAgOzsbISEhOHz4cIMaX7t2LUJDQ1FRUQFHR0dOcUBEpEVKA/7ixYuYMGECvvnmmwY37urqCplM1uDXExFRwykN+KFDh2LHjh3w9PTUZj1ERNRIlI7B9+/fHz///LM2ayEiokakNOB37tyJoqIivPvuu9qsh4iIGonSgNfX14dUKoWxsbE26yEiokai8jLJBQsWaKMOIiJqZCoD/saNGygvLwcAJCUlISYmBgUFBZqui4iI1KQy4MeOHQt9fX1cv34dU6dORXp6OkJCQrRRGxERqUFlwOvp6cHAwAC//PILoqOj8fnnn+POnTvaqI2IiNSgMuANDQ0RHx+PTZs2ISgoCABQWVmp8cKIiEg9KgM+NjYWycnJmDdvHrp06YL09HSEhYVpozYiIlLDM+eiAYDevXsjJiZGsdylSxe89957Gi2KiIjUp/QI/tq1a5g8eTLefvttZGVlYcSIEWjTpg1cXFw4vwwRUTOgNOAjIiLg5eUFGxsbDBw4EFOmTMH9+/excuVKREVFabNGIiJqAKUBX1xcjMjISMyZMwetWrXCuHHj0LJlS/j7+yuuiycioqZLacDr6f1vk6mpqdJtRETUNCk9yXrlyhU4OztDCIEbN27A2dkZwOPnst68eVNrBRIRUcMoDfjLly9rsw4iImpkSgPe3t5em3UQEVEjUxrwJiYmkEgkimUhBCQSieLvoqIirRRIREQNozTg/fz8cPfuXbz88suYMGECOnfuXO/GHRwcYGJiAn19fRgYGPD6eSIiLVIa8Nu3b0dhYSG2bduG1157DWVlZRg/fjwmTJiAtm3b1rmDQ4cOoX379o1SLBER1d0zr3c0MzNDREQEEhISMGPGDCxYsABxcXFaKo2IiNTxzLloTpw4gfj4eBw9ehTe3t745ZdfMHjw4Do3LpFIEBAQAIlEgunTpyMyMvKJfaRSKaRSKQBALpfXs3wiIlJGacA7ODjA3NwcEyZMgFQqhYHB413Pnj0LAHBzc1PZ+PHjx2FjY4N79+7B398fPXv2xJAhQ2rtExkZqQh+Dw+PBr8RIiKq7ZkBL5FIsH//fvz6668QQii2SSQS/Pbbbyobt7GxAQBYWVkhODgYKSkpTwQ8ERFphtKAT0pKUqvhR48eoaamBiYmJnj06BF+/fVXPsCbiEiLVM4H31C5ubkIDg4GAFRVVSEkJASBgYGa6o6IiP5CYwHv6OiI8+fPa6p5IiJSQellklVVVdqsg4iIGpnSI3hPT0/Y2toiMDAQgYGBcHBw0GJZRESkLqUBL5PJkJmZiYSEBERHRyM7Oxve3t4YMWIEfHx8YGRkpM06iYionp55J6u9vT1mzJiB7du348SJExg1ahQOHjyIwYMHY+TIkdqqkYiIGqDOJ1kNDQ0xbNgwDBs2DACQnZ2tsaKIiEh9DX72XqdOnRqzDiIiamR8uCoRkY5iwBMR6SilY/CjRo2q9USnv9q5c6dGCiIiosahNODnzJkDANi2bRvu3r2LsLAwAEB8fDyviSciagaUBryPjw8A4MMPP8SRI0cU60eNGsUZIYmImgGVY/ByuRw3b95ULKenp/PBHEREzYDK6+A///xz+Pr6wtHREQCQkZGBr7/+WuOFERGRelQGfGBgIK5du4YrV64AAHr27MlpCoiImgGVQzQlJSX47LPP8MUXX8DFxQW3bt3C7t27tVEbERGpQWXAR0REoEWLFkhOTgYA2NraYv78+RovjIiI1KMy4G/cuIF3330XhoaGAIBWrVrVej4rERE1TSoDvkWLFigtLVXc9HTjxg2OwRMRNQMqT7IuXrwYgYGBuH37NkJDQ3H8+HHExcVpoTQiIlKHyoD39/eHm5sbTp48CSEE1qxZg/bt22ujNiIiUoPKIRohBBISEnDmzBkEBQWhpKQEKSkpde6guroa/fr1Q1BQkFqFEhFR/agM+Ndffx3JycmIj48HAJiYmCAqKqrOHaxZswa9evVqeIVERNQgKgP+1KlTWLduHVq2bAkAsLCwQEVFRZ0az8rKwp49ezBt2jT1qiQionpTGfCGhoaorq5WXEUjl8uhp1e3aeSjo6Px6aefPnN/qVQKDw8PeHh4cI4bIqJGpDKpZ82aheDgYOTm5mLevHnw9vbGBx98oLLh3bt3w8rKCu7u7s/cLzIyEjKZDDKZDJaWlnWvnIiInknlVTShoaFwd3dHYmIiAGD79u11GlM/fvw4du7cib1796KsrAxFRUUICwvD999/r37VRESkUp3GWkpKSlBdXY2amhqUlpbWqeGPP/4YWVlZyMjIwJYtWzBs2DCGOxGRFqkM+CVLliA8PBz5+fnIy8tDREQEli1bpo3aiIhIDRKhYmKZXr164dy5c4qraEpLS+Hm5obLly83ejEeHh6QyWSN3i5RU+Dw3p7nXQI1URkrRjb4tc/KTZVH8A4ODigrK1Msl5eXo2vXrg0uhoiItEPlSVYjIyM4OTnB398fEokEBw4cgLe3N2bNmgUAiImJ0XiRRERUfyoDPjg4GMHBwYplX19fTdZDRESNRGXAh4eHAwAqKyuRlpaGTp06wcrKSuOFERGRepSOwc+YMQMXL14EABQWFsLFxQWTJk1Cv379FPPSEBFR06U04I8ePQonJycAQGxsLLp3744LFy7gzJkz+PTTT7VWIBERNYzSgG/RooXi3wcOHMCYMWMAAB07dtR4UUREpD6lAW9ubo7du3fj3LlzOH78OAIDAwEAVVVVdb6blYiInh+lJ1m//vprzJo1C3fv3sXq1asVR+6JiYkYObLhF+UTEZF2KA347t27Y9++fU+sHz58OIYPH67RooiISH11m9idiIiaHQY8EZGOYsATEekolXeylpeX4+eff0ZGRgaqqqoU6xcsWKDRwoiISD0qA/6ll16CmZkZ3N3dYWRkpI2aiIioEagM+KysrKdeTdPUcK5tUkadubaJmjOVY/BeXl64cOGCNmohIqJGpPII/tixY4iLi0OXLl1gZGQEIQQkEgn++9//aqM+IiJqIJUBn5CQoI06iIiokakMeHt7e5w/fx5Hjx4FAAwePBguLi4aL4yIiNSjcgx+zZo1CA0Nxb1793Dv3j2EhYVh7dq1KhsuKyvDgAED4OLiAicnJyxcuLBRCiYiorpReQS/ceNGnDp1Cm3atAEAzJ07F4MGDcKbb775zNcZGRnht99+g7GxMSorK+Ht7Y0RI0bA09OzcSonIqJnUnkEL4SAvr6+YllfXx9CCJUNSyQSGBsbA3j8uL/KykpIJBI1SiUiovpQeQQfERGBgQMHKh68vX37dkydOrVOjVdXV8Pd3R3Xr19HVFQUBg4c+MQ+UqkUUqkUACCXy+tTOxERPYPKI/i3334bsbGxaNu2LSwsLBAbG4vo6Og6Na6vr4/U1FRkZWUhJSUFaWlpT+wTGRkJmUwGmUwGS0vLer8BIiJ6OqVH8EVFRTA1NUV+fj4cHBzg4OCg2Jafn4+2bdvWuRNzc3P4+vpi37596NOnj1oFExFR3SgN+JCQEOzevRvu7u61xs7/uNHp5s2bz2xYLpfD0NAQ5ubmKC0txcGDBzF37tzGq5yIiJ5JacDv3r0bAJCent6ghu/cuYPw8HBUV1ejpqYGr7zyCoKCghpWJRER1ZvKk6x+fn5ITExUue6vnJ2dce7cOfWqIyKiBlMa8GVlZSgpKUFeXh4ePHiguDSyqKgIOTk5WiuQiIgaRmnAf/3111i9ejVycnLg7u6uCHhTU1NERUVprUAiImoYpQE/e/ZszJ49G2vXrlV51yoRETU9Ksfg33zzTaSlpeHSpUsoKytTrJ80aZJGCyMiIvWoDPjFixcjKSkJly5dwj/+8Q8kJCTA29ubAU9E1MSpvJN169atSExMRMeOHREbG4vz58+jvLxcG7UREZEaVAZ8q1atoKenBwMDAxQVFcHKykrlTU5ERPT8qRyi8fDwQEFBAV577TW4u7vD2NgYAwYM0EZtRESkBpUB/+WXXwIAZsyYgcDAQBQVFcHZ2VnjhRERkXpUDtH4+flh7969AAAHBwc4OzsjMjJS44UREZF6VAZ8eno6PvnkEyxevFixTiaTabQoIiJSn8qANzc3R2JiInJzczFq1CgUFhZqoy4iIlJTnR7ZZ2BggC+//BJjx46Ft7c37t27p43aiIhIDSpPss6YMUPx78mTJ6Nv375Yt26dRosiIiL1qXyi07hx45Cfn69Y36VLF6xcuVIrxRERUcPV+YlOf8wmCaBOT3QiIqLnS2NPdCIioudL5Rg8AGRnZyMzMxNVVVWKdUOGDNFYUUREpD6VAT937lz8+OOP6N27N/T19QE8HqJhwBMRNW0qA3779u24evUqjIyM6tXw7du3MWnSJNy9exd6enqIjIzE7NmzG1woERHVj8qAd3R0RGVlZb0D3sDAAKtWrYKbmxsePnwId3d3+Pv7o3fv3g0uloiI6k5lwLdu3Rqurq7w8/OrFfIxMTHPfJ21tTWsra0BACYmJujVqxeys7MZ8EREWqIy4EePHo3Ro0er1UlGRgbOnTuHgQMHPrFNKpVCKpUCAORyuVr9EBHR/6gM+PDwcJSWluLWrVvo0aNHvTsoLi7G2LFjsXr1apiamj6xPTIyUjE7pYeHR73bJyKip1M5F82uXbvg6uqKwMBAAEBqamqdj+grKysxduxYhIaG4uWXX1avUiIiqheVAb9o0SKkpKTA3NwcAODq6lqnm5+EEJg6dSp69eqFt99+W+1CiYioflQGvIGBAczMzGqtk0gkKhs+fvw4vvvuO/z2229wdXWFq6ur4sEhRESkeSrH4Pv06YMffvgB1dXVuHbtGmJiYuDl5aWyYW9v71rz1xARkXapPIJfu3YtLl68CCMjI4SEhMDU1BRr1qzRRm1ERKQGlQEfHx+P5cuX4/Tp0zh9+jSWL1+OhQsXaqM2IiJSg8ohmq1bt6Jly5YIDQ0FAERFRaGsrEzjhRERkXpUBvy2bdswevRo6OnpISEhAW3btuUTnYiImgGlAf/npzht2LABY8aMwQsvvIAFCxYgPz8fbdu21UqBRETUMEoD/s9Pcvrj7z179mDPnj18ohMRUTOgNOD5JCciouZN5Rh8ZWUlvvrqKxw5cgQA4Ovri+nTp8PQ0FDjxRERUcOpDPiZM2eisrISr7/+OgDgu+++w8yZM7FhwwaNF0dERA2nNOCrqqpgYGCA06dP4/z584r1w4YNg4uLi1aKIyKihlN6o9OAAQMAAPr6+rhx44Zi/c2bNxXPZiUioqZL6RH8H/PIrFy5EkOHDoWjoyOAxw/viI2N1U51RETUYEoDXi6X41//+hcAYPr06aiurkabNm1QVlaGc+fOYejQoVorkoiI6k9pwFdXV6O4uLjWjJDFxcUAgIcPH2q+MiIiUovSgLe2tsaCBQu0WQsRETUipSdZOZc7EVHzpjTgExMTtVkHERE1MqUBz8nEiIiaN5UP/CAiouaJAU9EpKM0FvBTpkyBlZUV+vTpo6kuiIjoGTQW8JMnT8a+ffs01TwREamgsYAfMmQIT9QSET1HKqcL1jSpVAqpVArg8fQIRETUOJ77SdbIyEjIZDLIZDJYWlo+73KIiHTGcw94IiLSDAY8EZGO0ljAT5w4EYMGDcLVq1dha2uLjRs3aqorIiJ6Co2dZI2Pj9dU00REVAccoiEi0lEMeCIiHcWAJyLSUQx4IiIdxYAnItJRDHgiIh3FgCci0lEMeCIiHcWAJyLSUQx4IiIdxYAnItJRDHgiIh3FgCci0lEMeCIiHcWAJyLSUQx4IiIdxYAnItJRDHgiIh3FgCci0lEMeCIiHaXRgN+3bx969OiBbt26YcWKFZrsioiI/kJjAV9dXY2oqCgkJCTg0qVLiI+Px6VLlzTVHRER/YXGAj4lJQXdunWDo6MjWrRogQkTJmDHjh2a6o6IiP7CQFMNZ2dnw87OTrFsa2uLU6dOPbGfVCqFVCoFAFy5cgUeHh4N6q99w8rUSXK5HJaWls+7jCbDw2Ph8y4BAL+jf8Xv6f+o8x3NyMhQuk1jAS+EeGKdRCJ5Yl1kZCQiIyM1VcbfkoeHB2Qy2fMug+iZ+D3VPI0N0dja2uL27duK5aysLNjY2GiqOyIi+guNBXz//v1x7do1pKeno6KiAlu2bMHo0aM11R0REf2FxoZoDAwM8MUXX2D48OGorq7GlClT4OTkpKnu6E845EXNAb+nmicRTxssJyKiZo93shIR6SgGPBGRjmLANwH6+vpwdXWFi4sL3NzccOLEiUZtf/Lkydi6dSsAYNq0abyjmOotIyMDffr0qbVu0aJFWLlyZZ3bcHBwQF5e3jP3+eijj+pdW1xcHN544416v+7vgAHfBLRq1Qqpqak4f/48Pv74Y7z//vsa62vDhg3o3bu3xtonUkdDAp6UY8A3MUVFRbCwsAAAFBcXw8/PD25ubujbt69iqodHjx5h5MiRcHFxQZ8+ffDjjz8CAM6cOQMfHx+4u7tj+PDhuHPnzhPt+/r6Km4uMTY2xrx58+Di4gJPT0/k5uYCeHyH4dixY9G/f3/0798fx48f18Zbp2bK19cX0dHR8PLyQp8+fZCSkgIAuH//PgICAtCvXz9Mnz691s2PY8aMgbu7O5ycnBR3sr/33nsoLS2Fq6srQkNDAQDff/89BgwYAFdXV0yfPh3V1dUAgNjYWHTv3h0+Pj78fj6LoOdOT09PuLi4iB49eghTU1Mhk8mEEEJUVlaKwsJCIYQQcrlcdO3aVdTU1IitW7eKadOmKV5fUFAgKioqxKBBg8S9e/eEEEJs2bJFRERECCGECA8PFz/99JMQQggfHx9x+vRpIYQQAMTOnTuFEEK88847YunSpUIIISZOnCiOHj0qhBAiMzNT9OzZU9MfATVx6enpwsnJqda6hQsXis8++0z4+Pgovo+HDx9W7Pfmm2+KxYsXCyGE2L17twAg5HK5EEKI+/fvCyGEKCkpEU5OTiIvL08IIUSbNm0U7V+6dEkEBQWJiooKIYQQM2fOFJs2bRI5OTnCzs5O3Lt3T5SXlwsvLy8RFRWlwXfffGnsOniquz+GaAAgOTkZkyZNQlpaGoQQ+OCDD3DkyBHo6ekhOzsbubm56Nu3L+bMmYO5c+ciKCgIgwcPRlpaGtLS0uDv7w/g8Wye1tbWz+y3RYsWCAoKAgC4u7vjwIEDAICDBw/WGqcvKirCw4cPYWJiooF3T83B06YZ+fP6iRMnAgCGDBmCoqIiFBQU4MiRI9i2bRsAYOTIkYrfTAEgJiYGv/zyCwDg9u3buHbtGtq1a1er7cTERJw5cwb9+/cHAJSWlsLKygqnTp2Cr6+vYh6b8ePH4/fff2/Ed6s7GPBNzKBBg5CXlwe5XI69e/dCLpfjzJkzMDQ0hIODA8rKytC9e3ecOXMGe/fuxfvvv4+AgAAEBwfDyckJycnJde7L0NBQ8T+ovr4+qqqqAAA1NTVITk5Gq1atNPIeqflp164dHjx4UGtdfn4+unTpAuDJHwB/LD/tB0NSUhIOHjyI5ORktG7dGr6+vigrK3tiPyEEwsPD8fHHH9dav337dqU/cKg2jsE3MVeuXEF1dTXatWuHwsJCWFlZwdDQEIcOHUJmZiYAICcnB61bt0ZYWBjmzJmDs2fPokePHpDL5YqAr6ysxMWLFxtUQ0BAAL744gvF8h+/XdDfl7GxMaytrZGYmAjgcbjv27cP3t7eAKA4D3Ts2DGYmZnBzMwMQ4YMwebNmwEACQkJih8QhYWFsLCwQOvWrXHlyhWcPHlS0Y+hoSEqKysBAH5+fti6dSvu3bun6DMzMxMDBw5EUlIS7t+/j8rKSvz000/a+RCaIR7BNwF/nFgCHh+1bNq0Cfr6+ggNDcWoUaPg4eEBV1dX9OzZEwBw4cIFvPPOO9DT04OhoSG++uortGjRAlu3bsWsWbNQWFiIqqoqREdHN2h6iJiYGERFRcHZ2RlVVVUYMmQI1q9f35hvmZqhf//734iKisL//d//AQAWLlyIrl27AgAsLCzg5eWFoqIifPvtt4rtEydOhJubG3x8fNC5c2cAQGBgINavXw9nZ2f06NEDnp6eij4iIyPh7OwMNzc3bN68GcuWLUNAQABqampgaGiIdevWwdPTE4sWLcKgQYNgbW0NNzc3xclXqo1TFRCRWnx9fbFy5coGP8uBNIdDNEREOopH8EREOopH8EREOooBT0SkoxjwREQ6igFPRKSjGPBERDrq/wG33UZznLCxlwAAAABJRU5ErkJggg==", "text/plain": [ "
" ] diff --git a/tokenizer_ts/src/bytePairEncode.ts b/tokenizer_ts/src/bytePairEncode.ts index d74acd6..0d6d795 100644 --- a/tokenizer_ts/src/bytePairEncode.ts +++ b/tokenizer_ts/src/bytePairEncode.ts @@ -1,15 +1,66 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -/** - * Convert a Uint8Array to a string - * @param uint8Array - * @returns string - */ -export function uint8ArrayToString(uint8Array: Uint8Array): string { - return Array.from(uint8Array) - .map(num => num.toString()) - .join("_"); +const enum Constant { + // we have 48 bits per level, we can safely bitwise encode 32 bits at a time, + // so this works in two passes + BytesPerLevel = 6, +} + +const binaryMapKey = (k: Uint8Array): number => { + const lower = k[0] | (k[1] << 8) | (k[2] << 16); + const upper = 0xFFFFFF * (k[3] | (k[4] << 8) | (k[5] << 16)); + return lower + upper; +}; + +export class BinaryMap { + private readonly map: Map | V> = new Map(); + private thisValue?: V; + + public get(key: Uint8Array): V | undefined { + const value = this.map.get(binaryMapKey(key)); + const isFinal = key.length < Constant.BytesPerLevel; + + if (isFinal) { + return value instanceof BinaryMap ? value.thisValue : value; + } else if (value instanceof BinaryMap) { + return value.get(key.subarray(Constant.BytesPerLevel)); + } else { + return undefined; + } + } + + public set(key: Uint8Array, value: V): void { + const k = binaryMapKey(key); + const existing = this.map.get(k); + const isFinal = key.length < Constant.BytesPerLevel; + + if (existing === undefined) { + if (isFinal) { + this.map.set(k, value); + } else { + const newMap = new BinaryMap(); + newMap.set(key.subarray(Constant.BytesPerLevel), value); + this.map.set(k, newMap); + } + } else if (isFinal) { + if (existing instanceof BinaryMap) { + existing.thisValue = value; + } else { + this.map.set(k, value); + } + } else { + if (existing instanceof BinaryMap) { + existing.set(key.subarray(Constant.BytesPerLevel), value); + } else { + const newMap = new BinaryMap(); + newMap.set(key.subarray(Constant.BytesPerLevel), value); + newMap.thisValue = existing; + this.map.set(k, newMap); + } + + } + } } /** @@ -20,10 +71,10 @@ export function uint8ArrayToString(uint8Array: Uint8Array): string { */ export function bytePairEncode( mergingBytes: Uint8Array, - ranks: ReadonlyMap + ranks: BinaryMap ): number[] { if (mergingBytes.length === 1) { - return [ranks.get(mergingBytes[0].toString())!]; + return [ranks.get(mergingBytes)!]; } const byteIndicesAndRanks: [number, number][] = []; @@ -37,7 +88,7 @@ export function bytePairEncode( byteIndicesAndRanks[startIndex][0], byteIndicesAndRanks[startIndex + skip + 2][0] ); - const rank = ranks.get(uint8ArrayToString(slice)); + const rank = ranks.get(slice); if (rank !== undefined) { return rank; } @@ -75,12 +126,12 @@ export function bytePairEncode( for (let i = 0; i < byteIndicesAndRanks.length - 1; i++) { outList.push( ranks.get( - uint8ArrayToString( + mergingBytes.slice( byteIndicesAndRanks[i][0], byteIndicesAndRanks[i + 1][0] ) - ) + )! ); } diff --git a/tokenizer_ts/src/tikTokenizer.ts b/tokenizer_ts/src/tikTokenizer.ts index 4c50bb6..f3586c0 100644 --- a/tokenizer_ts/src/tikTokenizer.ts +++ b/tokenizer_ts/src/tikTokenizer.ts @@ -4,7 +4,7 @@ import * as fs from "fs"; import { LRUCache } from "lru-cache"; import { TextDecoder, TextEncoder } from "util"; -import { bytePairEncode, uint8ArrayToString } from "./bytePairEncode"; +import { BinaryMap, bytePairEncode } from "./bytePairEncode"; /** * Load BPE ranks from a file @@ -59,7 +59,7 @@ function escapeRegExp(regex: string) { */ export class TikTokenizer { private regex?: RegExp; - private encoder?: Map; + private encoder?: BinaryMap; private decoder?: Map; private specialTokensRegex?: RegExp; private specialTokensEncoder?: ReadonlyMap; @@ -94,9 +94,9 @@ export class TikTokenizer { specialTokensEncoder: ReadonlyMap, regexPattern: string ): void { - this.encoder = new Map(); + this.encoder = new BinaryMap(); for (const [key, value] of bpeDict) { - this.encoder.set(uint8ArrayToString(key), value); + this.encoder.set(key, value); } this.regex = new RegExp(regexPattern, "gu"); this.specialTokensRegex = new RegExp( @@ -111,7 +111,7 @@ export class TikTokenizer { this.decoder.set(value, key); } - if (this.encoder.size !== this.decoder.size) { + if (bpeDict.size !== this.decoder.size) { throw new Error("Encoder and decoder sizes do not match"); } @@ -210,7 +210,7 @@ export class TikTokenizer { } else { // cache miss const bytes = this.textEncoder.encode(match[0]); - const token = this.encoder?.get(uint8ArrayToString(bytes)); + const token = this.encoder?.get(bytes); if (token !== undefined) { tokenIds.push(token); this.cache.set(match[0], [token]); @@ -255,7 +255,7 @@ export class TikTokenizer { } else { // cache miss const bytes = this.textEncoder.encode(piece); - const token = this.encoder!.get(uint8ArrayToString(bytes)); + const token = this.encoder!.get(bytes); if (token !== undefined) { this.cache.set(piece, [token]); if (tokenCount + 1 <= maxTokenCount) { @@ -404,7 +404,7 @@ export class TikTokenizer { tokenCountMap.set(tokenCount, encodeLength); } else { const bytes = new TextEncoder().encode(piece); - const token = this.encoder!.get(uint8ArrayToString(bytes)); + const token = this.encoder!.get(bytes); if (token !== undefined) { this.cache.set(piece, [token]); tokenCount++; From ad05ee8d9061fac8a2b43c2ad7dce30970c98efd Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 9 Apr 2024 20:16:30 -0700 Subject: [PATCH 03/11] avoid extra loop iterations in BPE This gets about another 15%: ![](https://memes.peet.io/img/24-04-7ea60cad-8bb7-42c8-9268-9af3541c0f44.png) --- tokenizer_ts/perf/notebook.ipynb | 16 ++++---- tokenizer_ts/src/bytePairEncode.ts | 61 ++++++++++++++++-------------- tokenizer_ts/src/tikTokenizer.ts | 5 ++- 3 files changed, 43 insertions(+), 39 deletions(-) diff --git a/tokenizer_ts/perf/notebook.ipynb b/tokenizer_ts/perf/notebook.ipynb index e8342aa..e6cb759 100644 --- a/tokenizer_ts/perf/notebook.ipynb +++ b/tokenizer_ts/perf/notebook.ipynb @@ -52,7 +52,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 50, "metadata": {}, "outputs": [], "source": [ @@ -62,12 +62,12 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 51, "metadata": {}, "outputs": [ { "data": { - "image/png": "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zFGDBg0UGxvrzW/atKlmzJihPXv2aMOGDcrMzNQ999xT6LqqVaum7777rsi8mZmZqlu3rvfY399fYWFhysjIUGZmpmrXri0/Pz9v/unL7tq1S1OmTCmw73bv3q3MzMwitwd3Ako7AFBSwsLCVKFCBR04cEABAcW/1E//JSZJoaGhOnjwoH766SevdNLT01W7du2zHr97927l5+d7pZOenq5GjRp5y9x22206dOiQunfvrvfff1+VK1dWWFiY2rdvrw8//PCcnm9htm7dqkGDBmnevHkKCwvzpoeFhWn06NFnfVPG5ZdfriuvvFKzZs3SkiVLNHDgwCKXbdKkiQYPHqwXX3yx0PmdO3fW3LlzdeuttxY6PzQ0VOvXr/cem5l2797tFU1GRobMzNvf6enpioiIKPC8Ro8efVbPC25xhIMyq1atWurSpYvuv/9+HTlyRPn5+dq+fbuWL19e6PI1atRQWlqa8vPzJZ385dW2bVs98sgjys7O1rp16zR9+vQif0nXqFGjwN+/tGrVSpUrV9bkyZOVk5OjZcuWadGiRerXr1+BcdOmTVPjxo3Vo0cPHTt2TD169NC2bdv0+uuvKycnRzk5OVq9erU2b958Ts//yJEjuuGGG/TEE0+oXbt2BeYNHz5cEydO1MaNGyXJu1PsTAYNGqRp06bps88+K7APtmzZoilTpmjPnj2SpN27d2v27Nlq3bp1oesZO3asPv/8cz344IPeUdu3336rAQMG6Mcff1SfPn20ePFiffTRR8rJydGUKVNUoUIFtW3bVm3atFFAQICef/555ebmat68efryyy+9dd9222164YUXtGrVKpmZjh49qsWLF3P3m4+gcFCmzZw5UydOnFCzZs0UHBys3r17F3k65+abb5Z08pRPXFycJGn27NlKS0tTaGioevbsqbFjx+qaa64pdPzQoUO1adMmValSRTfeeKMuuugiLVy4UEuWLFFISIhGjBihmTNnqkmTJgXG+fn56aWXXlJYWJhuuOEGlS9fXsnJyXrrrbcUGhqqmjVr6qGHHtLx48fP6bmvWbNGW7du1X333Vfgb50kqWfPnnrooYfUr18/XXrppYqKitKSJUvOuL7evXvr0KFD6tSpk2rVquVNv+SSS7Rq1SqvYFu3bq2oqChNmTKl0PVERERo5cqVSktLU2RkpIKCgnTTTTcpPj5el1xy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7Dl1us7M9Mgjj3jr+fbbb71zkwAAafTo0Ro6dKiWLl2qnJwcLV26VEOHDtXo0aMv2DaKLZzBgwera9euyszMlCQ1atRIzz777FmtPD09XStXrpR08oLUqcO1kJAQZWVleddY8vPztXv3bnXs2FGTJ0/Wjz/+qKysLHXt2lV///vfveL46quvzvkJSif/puiVV17xbunOyMjQ999/f17rAoCyqH///ho/frxGjRqliy++WKNGjdL48eMv2A0D0lncpXbgwAH16dNHEydOPDkgIEDlypU7q5U3bdpUr732mm6//XY1bNhQd9xxhw4dOqTo6GiFh4crISFBkpSXl6cBAwbo8OHDMjPde++9qlKlih577DHdc889iomJkZkpPDzcu+ZzLrp06aLNmzerTZs2kqTAwEC98cYbuuyyy855XQBQVvXv3/+CFswv+VkxH3LfoUMHzZ07V9dcc43WrFmjL774Qg899JCWL19eYqF8QXx8vFJSUs5r7KlTicXsWvfGBEljDpd2ikL5+fn53v4CcM7O9Luz2COcqVOn6vrrr9f27dt1xRVXaP/+/QVuNwYA4GwUWzhxcXFavny5tm7dKjNT48aNVb58eRfZAABlSLGFk5eXp/fee09paWnKzc1VcnKyJOm+++4r8XAAgLKj2MK57rrrdPHFFys6Olr+/nyawdk49THcAID/U2zh7NmzR+vWrXORBQBQhhV7yNKtWzfvNBoAAOer2COc1q1bq2fPnsrPz1f58uW900VHjhxxkQ8AUEYUWzj333+/Vq5cqejoaK5LAADOW7Gn1Bo2bKioqCjKBgDwmxR7hFOrVi116NBB3bp1K/DGmNwWDQA4F8UWTr169VSvXj2dOHFCJ06ccJGpzPC1o0JLutTnMp0SHBxc2hEAlLBiC4dPxzw/vvq+YDamtBMA+L0qsnBGjhypadOm6brrriv0f8ULFy4s0WAAgLKlyMKZOXOmpk2bpgceeMBlHgBAGVVk4UREREiS2rdv7ywMAKDsKrJw9u/fr6lTpxY5kLvUAADnosjCycvLU1ZWls9e/AYA/G8psnBq1aqlv/3tby6zAADKsCLfaYAjGwDAhVRk4Xz00UcucwAAyrgiC6dq1aoucwAAyjg+whMA4ASFAwBwgsIBADhB4QAAnKBwAABOUDgAACcoHACAExQOAMAJCgcA4ASFAwBwgsIBADhB4QAAnKBwAABOUDgAACcoHACAExQOAMAJCgcA4ASFAwBwgsIBADhB4QAAnKBwAABOUDgAACcoHACAExQOAMAJCgcA4ASFAwBwgsIBADhB4QAAnKBwAABOUDgAACcoHACAExQOAMAJCgcA4ASFAwBwgsIBADhB4QAAnKBwAABOUDgAACcoHACAExQOAMAJCgcA4ASFAwBwgsIBADhB4QAAnKBwAABOUDgAACcoHACAExQOAMAJCgcA4ASFAwBwgsIBADhB4QAAnKBwAABOUDgAACcoHACAExQOAMAJCgcA4ASFAwBwgsIBADhB4QAAnKBwAABOUDgAACcoHACAExQOAMAJCgcA4ASFAwBwgsIBADhB4QAAnKBwAABOUDgAACcoHACAExQOAMAJCgcA4ASFAwBwgsIBADhB4QAAnKBwAABOUDgAACcoHACAExQOAMAJCgcA4ASFAwBwws/MrLRD+KKQkBCFh4d7j/fv36/q1auXXqAzINv5Idv5Idv5+b1kS0tL04EDBwqdR+Gcpfj4eKWkpJR2jEKR7fyQ7fyQ7fyQjVNqAABHKBwAgBMUzlkaNmxYaUcoEtnOD9nOD9nOD9m4hgMAcIQjHACAExQOAMCJ31XhDBkyRJdddpmioqK8aY899phiYmLUokULdenSRZmZmZKkEydO6NZbb1V0dLSaN2+uZcuWeWNSU1MVHR2tBg0a6K677tKps5LHjx9X37591aBBA7Vq1UppaWm/KdspTz/9tPz8/Arc2z5x4kQ1aNBAjRs31gcffOAz2X744Qd17NhRgYGBGjlyZIFlSzvbhx9+qJYtWyo6OlotW7bUxx9/7DPZvvzyS7Vo0UItWrRQ8+bNNX/+fJ/Jdkp6eroCAwP19NNP+0y2tLQ0VaxY0dt3w4cP95lskrRu3Tq1adNGkZGRio6OVnZ2dollO9d8s2bN8vZbixYt5O/vr7Vr15ZoPtnvyPLlyy01NdUiIyO9aYcPH/a+f+655+z22283M7Np06bZ4MGDzcxs3759FhcXZ3l5eWZmlpCQYJ9//rnl5+fbtddea++9956Zmf3jH//wxs+ePdv69Onzm7KZmaWnp1uXLl3s8ssvt/3795uZ2caNGy0mJsays7Ntx44dVr9+fcvNzfWJbFlZWfbpp5/av/71L7vzzjsLLF/a2dasWWMZGRlmZrZ+/XoLDQ31mWxHjx61nJwcMzPLzMy06tWre49LO9spvXr1st69e9tTTz3lTSvtbDt37vzVcr6SLScnx6Kjo23t2rVmZnbgwIES/Tk913ynW7dundWrV897XFL5fleFY3bmF+iECRNs+PDhZmY2YsQIe/311715V199ta1atcoyMzOtcePG3vQ333zThg0bZmZmXbp0sc8//9zMTr7YqlWrZvn5+b8p20033WRr1661unXrei+UCRMm2IQJE7xlTm3XF7Kd8uqrrxYoHF/KZmaWn59vVatWtezsbJ/LtmPHDrvsssssJyfHZ7LNnz/fHnjgAUtKSvIKxxeyFfXz7AvZFi9ebImJiU6znUu+0z3yyCP217/+tcTz/a5OqRVl9OjRCgsL06xZszRu3DhJUvPmzbVgwQLl5uZq586dSk1N1e7du5WRkaE6dep4Y+vUqaOMjAxJUkZGhsLCwiRJAQEBCgoK0g8//HDeuRYuXKjatWurefPmBaafvp3TM/hCtqL4Wra5c+cqNjZWFSpU8Jlsq1at8k69vPDCCwoICPCJbEePHtWTTz6ppKSkAtN9IZsk7dy5U7GxsWrfvr0+/fRTn8m2bds2+fn5qWvXroqLi9PkyZOdZztTvtO9/fbb6t+/f4nnCzifJ1DWjB8/XuPHj9fEiRM1bdo0jR07VkOGDNHmzZsVHx+vunXrqm3btgoICPDOZZ7Oz89Pks4471z9/PPPGj9+vJKTk381r6jt+EK2ovhSto0bN+qhhx7ylvGVbK1atdLGjRu1efNmDRo0SN26dfOJbElJSbr33nsVGBhYYLovZKtVq5bS09NVrVo1paam6sYbb9TGjRt9Iltubq5WrFih1atXq1KlSurUqZNatmypSy+91Em24vKdsmrVKlWqVMm77lOS+44jnNPccsstmjt3rqST7f3MM89o7dq1WrBggX788Uc1bNhQderU0Z49e7wxe/bsUWhoqKST/xPYvXu3pJMvtsOHD6tq1arnlWX79u3auXOnmjdvrvDwcO3Zs0dxcXHau3dvge2cnsEXshXFV7Lt2bNHPXv21MyZMxUREeFT2U5p2rSpKleurA0bNvhEtlWrVukvf/mLwsPD9eyzz2rChAmaNm2aT2SrUKGCqlWrJklq2bKlIiIitG3bNp/IVqdOHbVv314hISGqVKmSunfvrjVr1jjLVly+U9566y3v6OZUhhLLd9Yn38qIX57f3LZtm/f9888/bzfddJOZnbyIm5WVZWZmycnJduWVV3rLxcfH28qVK70LaosXLzazkzcanH5B7eabb/5N2U53+rnXDRs2FLhpoF69et7FyNLOdsovr+H4QrZDhw5ZTEyMzZkz51fLlXa2HTt2eDcJpKWlWa1atbx5pZ3tdKdfw/GFbN9//7332t++fbuFhobaDz/84BPZDh48aLGxsd4NIZ06dbJ33323RLOdSz4zs7y8PKtdu7Zt3769wHIlle93VTj9+vWzmjVrWkBAgNWuXdtefvll69Wrl0VGRlp0dLT16NHD9uzZY2Yn/9EaNWpkTZo0sU6dOllaWpq3ntWrV1tkZKTVr1/f7rzzTu+i2bFjx6x3794WERFhCQkJv/pHPNdsp/vlC+WJJ56w+vXrW6NGjbw7SHwlW926dS04ONgqV65stWvXto0bN/pEtscff9wqVapkzZs397727dvnE9lmzpxpzZo1s+bNm1tsbKzNnz/fW660s53ul4VT2tnmzJljzZo1s5iYGIuNjbWFCxf6TDYzs9dff92aNWtmkZGR9uCDD5ZotvPJt3TpUmvVqtWv1lNS+XhrGwCAE1zDAQA4QeEAAJygcAAATlA4AAAnKBwAgBMUDgDACQoHAODE/wOF0/2h3aMtkAAAAABJRU5ErkJggg==", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAZwAAAEICAYAAABrtkJsAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/MnkTPAAAACXBIWXMAAAsTAAALEwEAmpwYAAAbKUlEQVR4nO3deVTVdf7H8dcllEwKUVxASRR3ltJgEmvUyqVFTRsrChPb1BptN5uxGc0sGxudpvHMKWc0tRzrlJYokTalNpmpaJMpiWXhguYyYoSpbO/fH56+vyiuiMoHpOfjHM7xftfX/Rzuffld7sVnZiYAAKpYQHUHAAD8MlA4AAAnKBwAgBMUDgDACQoHAOAEhQMAcILCQY0XExOjFStWVHeMM27YsGF6/PHHz/h2g4OD9dVXX53x7dZUVTWOOPMoHFS74OBg7ycgIED16tXzHs+bN0+bN29Wz549qzzHhAkTNGTIkFNev6a88RUUFKh169ZnZFtHjx5VgwYN9P777/9s3oMPPqjBgwdLkj788EN169ZNISEhatiwoS677DKtW7fO73a3bt2qG2+8UWFhYQoJCVF8fLymTZumkpKSM5IbNROFg2pXUFDg/Vx44YVavHix9zglJaW64/2inXvuubr55ps1d+7cMtNLSko0f/58paamKj8/X/369dPo0aN18OBB5ebmavz48QoKCip3m9u2bdOll16qyMhIffbZZ/r222/1+uuvKzMzU999952Lp4XqYkAN0rJlS3v33Xf9Ths/frwNHjzYUlJSLDg42GJjYy07O9uefvppa9y4sbVo0cKWLl3qrXvo0CG74447rFmzZhYREWHjxo2z4uLin+03IyPD6tSpY4GBgVa/fn2Lj483M7Pc3Fzr37+/hYaGWnR0tM2YMaPc3C+++KIFBgZanTp1rH79+tavXz8zM8vKyrIePXpYSEiIderUyRYtWuStk5qaauPGjTMzs/z8fOvZs6eNHj3aSktL7fPPP7devXpZaGiotWvXzl577bUy691777127bXXWnBwsP3qV7+yL7/80psvyb744gvLzc21+vXrez/16tWzH7/kZ86caR06dLAGDRpYnz59LCcnp9zntmrVKgsODrbDhw9709LT061x48ZWVFRk69ats5CQkHLXLU9KSopde+21J1xm0aJF1qlTJwsJCbEePXpYVlaWN2/Dhg3WuXNnCw4OtptuusluvvlmbxzNzBYvXmwXXXSRhYSEWFJSkn366acnnQ1Vi8JBjXIyhRMUFGTvvPOOFRUV2W233WZRUVE2adIkKywstBkzZlhUVJS37vXXX2/Dhw+3goIC27t3ryUmJtoLL7xQ7r7Hjx9vKSkpZaZ1797d7rnnHjty5Ih98sknFhYWZv/+97/LXf/HBWJmVlhYaNHR0fbUU0/ZsWPH7L333rPg4GDbsmVLmeUPHDhgiYmJ3roFBQXWokULmzVrlhUVFdn69eutUaNGtmnTJm+90NBQW7NmjRUVFdmtt95qN998s7ffHwrnp2699VZLTk42M7M333zToqOjLSsry4qKiuzJJ5+0pKSkcp+XmVnbtm3t5Zdf9h4nJyfb/fffb2Zm3377rTVs2NCGDh1qb7/9th08eNDvdszMmjZtarNmzfI7Pzs728477zxbtmyZFRYW2p/+9CeLjo62Y8eO2bFjx+zCCy+0adOmWWFhob3++usWGBjojd369eutcePG9vHHH1txcbHNnj3bWrZsaUePHj1hJrhB4aBGOZnC6dWrlzcvLS3N6tev7x215OfnmyTLy8uzb775xurWrWvff/+9t/y//vUv69mzZ7n7/mnh7NixwwICAiw/P9+b9thjj1lqamq56/+0cD744ANr2rSplZSUeNOSk5Nt/Pjx3vK33367xcTE2JQpU7xlXn31Vbv88svLbHv48OE2YcIEb70777zTm5eenm7t27f3HpdXOM8884x16dLFG4urr77a/vnPf3rzS0pKrF69en6Pcp588knr3bu3mR0vmHr16tmGDRu8+VlZWZaammrNmze3c845x/r372/ffPNNudsKDAy0jIyMcueZmU2cONFuvPHGMtkiIiJs+fLltnLlSgsPD7fS0lJvflJSkjfuI0eOtMcff7zM9tq1a2crVqzwuz+4wzUcnHWaNm3q/btevXoKCwvTOeec4z2Wjl8X2r59u4qKihQeHq4GDRqoQYMGGjFihPbt23dS+9m9e7caNmyo888/35vWsmVL5ebmnvT6kZGRCgj4/5fZT9dPT0/XkSNHNHLkSG/a9u3btWbNGi9zgwYNNG/ePH3zzTfeMs2aNfP+fd5556mgoMBvjoyMDP31r3/VW2+95Y3P9u3bdf/993vbb9iwoczM73MbOnSoli9frtzcXL3xxhtq06aNOnfu7M3v2LGjZs+erV27dmnTpk3avXu3HnjggXK31ahRI+3Zs8dv3t27d6tly5be44CAAEVGRio3N1e7d+9W8+bN5fP5vPk/Xnb79u2aOnVqmbHbuXOndu/e7Xd/cCewugMAVSUyMlJBQUE6cOCAAgMr/lX/8ZuYJEVEROjgwYP67rvvvNLZsWOHmjdvftLr79y5U6WlpV7p7NixQ+3atfOWufvuu5WXl6drr71W77zzjurXr6/IyEj16NFD7777bqWeb3mys7OVmpqqhQsXKjIy0pseGRmpcePGnfRNGRdeeKF+/etfa968ecrIyNDQoUP9LtuhQwcNGzZML774Yrnze/XqpQULFuj2228vd35ERIQ+++wz77GZaefOnV7R5Obmysy88d6xY4eio6PLPK9x48ad1POCWxzhoNYKDw9Xnz599PDDDys/P1+lpaXatm2bVq5cWe7yTZs2VU5OjkpLSyUdf/Pq1q2bfve73+no0aPauHGjZs6c6fdNumnTpmU+/3LppZeqfv36mjJlioqKirRixQotXrxYycnJZdabPn262rdvr379+unIkSPq16+ftm7dqpdffllFRUUqKirSunXr9Pnnn1fq+efn5+v666/XpEmTdPnll5eZN3LkSE2ePFmbN2+WJO9OsRNJTU3V9OnTtWrVqjJjsGXLFk2dOlW7du2SJO3cuVPz589X165dy93OE088oY8++khjxozxjtq+/PJLDRkyRIcOHdJNN92k9PR0vffeeyoqKtLUqVMVFBSkbt26KSkpSYGBgXr++edVXFyshQsXau3atd627777br3wwgtas2aNzEyHDx9Weno6d7/VEBQOarW5c+eqsLBQnTp1UmhoqAYPHuz3dM6NN94o6fgpny5dukiS5s+fr5ycHEVERGjQoEF64okn1Lt373LXv/POO5WVlaUGDRpo4MCBqlu3rtLS0pSRkaGwsDDde++9mjt3rjp06FBmPZ/PpxkzZigyMlLXX3+96tSpo2XLlunVV19VRESEmjVrprFjx+rYsWOVeu4bNmxQdna2HnrooTKfdZKkQYMGaezYsUpOTtYFF1yg2NhYZWRknHB7gwcPVl5enq666iqFh4d7088//3ytWbPGK9iuXbsqNjZWU6dOLXc70dHRWr16tXJychQTE6OQkBD95je/UUJCgs4//3y1b99er7zy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", "text/plain": [ "
" ] @@ -97,7 +97,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 64, "metadata": {}, "outputs": [], "source": [ @@ -106,12 +106,12 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 65, "metadata": {}, "outputs": [ { "data": { - "image/png": "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", + "image/png": "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", "text/plain": [ "
" ] @@ -133,12 +133,12 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 66, "metadata": {}, "outputs": [ { "data": { - "image/png": "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", + "image/png": "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", "text/plain": [ "
" ] diff --git a/tokenizer_ts/src/bytePairEncode.ts b/tokenizer_ts/src/bytePairEncode.ts index 0d6d795..d1e9398 100644 --- a/tokenizer_ts/src/bytePairEncode.ts +++ b/tokenizer_ts/src/bytePairEncode.ts @@ -63,6 +63,8 @@ export class BinaryMap { } } +const maxRank = 0x7FFFFFFF; // max int32, try and keep things in integer space + /** * This function implements the byte pair encoding algorithm. * @param mergingBytes: bytes to be merged @@ -77,14 +79,25 @@ export function bytePairEncode( return [ranks.get(mergingBytes)!]; } + let minRank = maxRank; + let minIndex = -1; + const byteIndicesAndRanks: [number, number][] = []; - for (let i = 0; i < mergingBytes.length + 1; i++) { - byteIndicesAndRanks.push([i, Number.MAX_SAFE_INTEGER]); + for (let i = 0; i < mergingBytes.length - 1; i++) { + const rank = ranks.get(mergingBytes.subarray(i, i + 2)) ?? maxRank; + if (rank < minRank) { + minRank = rank; + minIndex = i; + } + + byteIndicesAndRanks.push([i, rank]); } + byteIndicesAndRanks.push([mergingBytes.length - 1, maxRank]); + byteIndicesAndRanks.push([mergingBytes.length, maxRank]); function getRank(startIndex: number, skip = 0): number { if (startIndex + skip + 2 < byteIndicesAndRanks.length) { - const slice = mergingBytes.slice( + const slice = mergingBytes.subarray( byteIndicesAndRanks[startIndex][0], byteIndicesAndRanks[startIndex + skip + 2][0] ); @@ -93,45 +106,35 @@ export function bytePairEncode( return rank; } } - return Number.MAX_SAFE_INTEGER; + return maxRank; } - for (let i = 0; i < byteIndicesAndRanks.length - 2; i++) { - const rank = getRank(i); - if (rank !== Number.MAX_SAFE_INTEGER) { - byteIndicesAndRanks[i][1] = rank; + while (minRank !== maxRank) { + byteIndicesAndRanks[minIndex][1] = getRank(minIndex, 1); + if (minIndex > 0) { + byteIndicesAndRanks[minIndex - 1][1] = getRank(minIndex - 1, 1); } - } + byteIndicesAndRanks.splice(minIndex + 1, 1); + - while (byteIndicesAndRanks.length > 1) { - let minRank: [number, number] = [0, Number.MAX_SAFE_INTEGER]; + minIndex = -1; + minRank = maxRank; for (let i = 0; i < byteIndicesAndRanks.length - 1; i++) { - if (byteIndicesAndRanks[i][1] < minRank[1]) { - minRank = [i, byteIndicesAndRanks[i][1]]; + if (byteIndicesAndRanks[i][1] < minRank) { + minRank = byteIndicesAndRanks[i][1]; + minIndex = i; } } - if (minRank[1] !== Number.MAX_SAFE_INTEGER) { - const j = minRank[0]; - byteIndicesAndRanks[j][1] = getRank(j, 1); - if (j > 0) { - byteIndicesAndRanks[j - 1][1] = getRank(j - 1, 1); - } - byteIndicesAndRanks.splice(j + 1, 1); - } else { - break; - } } const outList: number[] = []; for (let i = 0; i < byteIndicesAndRanks.length - 1; i++) { outList.push( ranks.get( - - mergingBytes.slice( - byteIndicesAndRanks[i][0], - byteIndicesAndRanks[i + 1][0] - ) - + mergingBytes.subarray( + byteIndicesAndRanks[i][0], + byteIndicesAndRanks[i + 1][0] + ) )! ); } diff --git a/tokenizer_ts/src/tikTokenizer.ts b/tokenizer_ts/src/tikTokenizer.ts index fb4fee9..001a2ff 100644 --- a/tokenizer_ts/src/tikTokenizer.ts +++ b/tokenizer_ts/src/tikTokenizer.ts @@ -200,8 +200,9 @@ export class TikTokenizer { const substring = text.substring(start, end); this.regex!.lastIndex = 0; while ((match = this.regex!.exec(substring))) { - if (this.cache.has(match[0])) { - tokenIds.push(...this.cache.get(match[0])!); + const cached = this.cache.get(match[0]); + if (cached) { + tokenIds.push(...cached); } else { // cache miss const bytes = this.textEncoder.encode(match[0]); From 4483cee96a7d3f4874fb52d70dd7b5e84752b75d Mon Sep 17 00:00:00 2001 From: andreamah Date: Thu, 11 Apr 2024 10:16:47 -0700 Subject: [PATCH 04/11] start on adding start/end to BinaryMap get --- tokenizer_ts/src/bytePairEncode.ts | 43 +++++++++++++++++++----------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/tokenizer_ts/src/bytePairEncode.ts b/tokenizer_ts/src/bytePairEncode.ts index d1e9398..19771fb 100644 --- a/tokenizer_ts/src/bytePairEncode.ts +++ b/tokenizer_ts/src/bytePairEncode.ts @@ -7,31 +7,43 @@ const enum Constant { BytesPerLevel = 6, } -const binaryMapKey = (k: Uint8Array): number => { - const lower = k[0] | (k[1] << 8) | (k[2] << 16); - const upper = 0xFFFFFF * (k[3] | (k[4] << 8) | (k[5] << 16)); - return lower + upper; +const binaryMapKey = (k: Uint8Array, start: number, end: number): number => { + const length = end-start; + + // 'lower' and 'upper' are both 24-bit integers, like + // 0xFF FF FF + // ^3 ^2 ^1 + // If we say have a length of 2, we should disregard the last "3" byte, so we + // create a mask like + // 0x00 FF FF (started at 0xFF FF FF and shifted over by 8 bits) + // ^3 ^2 ^1 + // so that we discard the data outside our range + const lowerMask = 0xFFFFFF >>> Math.max(0, (3 - length) * 8); + const lower = (k[start + 0] | (k[start + 1] << 8) | (k[start + 2] << 16)) & lowerMask; + const upperMask = 0xFFFFFF >>> Math.max(0, (6 - length) * 8); + const upper = (k[start + 3] | (k[start + 4] << 8) | (k[start + 5] << 16)) & upperMask; + return lower + (0xFFFFFF * upper); }; export class BinaryMap { private readonly map: Map | V> = new Map(); private thisValue?: V; - public get(key: Uint8Array): V | undefined { - const value = this.map.get(binaryMapKey(key)); - const isFinal = key.length < Constant.BytesPerLevel; + public get(key: Uint8Array, start: number = 0, end: number = key.length): V | undefined { + const value = this.map.get(binaryMapKey(key,start, end)); + const isFinal = end < Constant.BytesPerLevel+start; if (isFinal) { return value instanceof BinaryMap ? value.thisValue : value; } else if (value instanceof BinaryMap) { - return value.get(key.subarray(Constant.BytesPerLevel)); + return value.get(key, Constant.BytesPerLevel+start, end); } else { return undefined; } } public set(key: Uint8Array, value: V): void { - const k = binaryMapKey(key); + const k = binaryMapKey(key, 0, key.length); const existing = this.map.get(k); const isFinal = key.length < Constant.BytesPerLevel; @@ -84,7 +96,7 @@ export function bytePairEncode( const byteIndicesAndRanks: [number, number][] = []; for (let i = 0; i < mergingBytes.length - 1; i++) { - const rank = ranks.get(mergingBytes.subarray(i, i + 2)) ?? maxRank; + const rank = ranks.get(mergingBytes,i, i + 2) ?? maxRank; if (rank < minRank) { minRank = rank; minIndex = i; @@ -97,11 +109,11 @@ export function bytePairEncode( function getRank(startIndex: number, skip = 0): number { if (startIndex + skip + 2 < byteIndicesAndRanks.length) { - const slice = mergingBytes.subarray( + const rank = ranks.get( + mergingBytes, byteIndicesAndRanks[startIndex][0], byteIndicesAndRanks[startIndex + skip + 2][0] ); - const rank = ranks.get(slice); if (rank !== undefined) { return rank; } @@ -131,10 +143,9 @@ export function bytePairEncode( for (let i = 0; i < byteIndicesAndRanks.length - 1; i++) { outList.push( ranks.get( - mergingBytes.subarray( - byteIndicesAndRanks[i][0], - byteIndicesAndRanks[i + 1][0] - ) + mergingBytes, + byteIndicesAndRanks[i][0], + byteIndicesAndRanks[i + 1][0] )! ); } From 138897b92b228ee2654c76f815eb9aef00ed2717 Mon Sep 17 00:00:00 2001 From: andreamah Date: Thu, 11 Apr 2024 10:18:04 -0700 Subject: [PATCH 05/11] format --- tokenizer_ts/src/bytePairEncode.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tokenizer_ts/src/bytePairEncode.ts b/tokenizer_ts/src/bytePairEncode.ts index 19771fb..625c586 100644 --- a/tokenizer_ts/src/bytePairEncode.ts +++ b/tokenizer_ts/src/bytePairEncode.ts @@ -8,8 +8,8 @@ const enum Constant { } const binaryMapKey = (k: Uint8Array, start: number, end: number): number => { - const length = end-start; - + const length = end - start; + // 'lower' and 'upper' are both 24-bit integers, like // 0xFF FF FF // ^3 ^2 ^1 @@ -30,13 +30,13 @@ export class BinaryMap { private thisValue?: V; public get(key: Uint8Array, start: number = 0, end: number = key.length): V | undefined { - const value = this.map.get(binaryMapKey(key,start, end)); - const isFinal = end < Constant.BytesPerLevel+start; + const value = this.map.get(binaryMapKey(key, start, end)); + const isFinal = end < Constant.BytesPerLevel + start; if (isFinal) { return value instanceof BinaryMap ? value.thisValue : value; } else if (value instanceof BinaryMap) { - return value.get(key, Constant.BytesPerLevel+start, end); + return value.get(key, Constant.BytesPerLevel + start, end); } else { return undefined; } @@ -96,7 +96,7 @@ export function bytePairEncode( const byteIndicesAndRanks: [number, number][] = []; for (let i = 0; i < mergingBytes.length - 1; i++) { - const rank = ranks.get(mergingBytes,i, i + 2) ?? maxRank; + const rank = ranks.get(mergingBytes, i, i + 2) ?? maxRank; if (rank < minRank) { minRank = rank; minIndex = i; From c32cc40c3cbb2e7025bfd1d7d4e87f6787ec986e Mon Sep 17 00:00:00 2001 From: andreamah Date: Thu, 11 Apr 2024 17:06:39 -0700 Subject: [PATCH 06/11] add some binaryMap tests --- tokenizer_ts/test/binaryMap.test.ts | 40 +++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 tokenizer_ts/test/binaryMap.test.ts diff --git a/tokenizer_ts/test/binaryMap.test.ts b/tokenizer_ts/test/binaryMap.test.ts new file mode 100644 index 0000000..12fd80a --- /dev/null +++ b/tokenizer_ts/test/binaryMap.test.ts @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import * as assert from "assert"; +import { BinaryMap } from "../src/bytePairEncode"; +suite("BinaryMap Test Suite", function() { + test("Test basic input to binary map - one level", done => { + const binMap: BinaryMap = new BinaryMap(); + binMap.set(new Uint8Array([1, 50, 24]), 1); + assert(binMap.get(new Uint8Array([1, 50, 24])) === 1); + assert(binMap.get(new Uint8Array([1, 50])) === undefined); + assert(binMap.get(new Uint8Array([1, 50, 24,100])) === undefined); + + binMap.set(new Uint8Array([1, 50, 24,100]), 100); + assert(binMap.get(new Uint8Array([1, 50, 24,100])) === 100); + done(); + }); + test("Test basic input to binary map - one or two levels", done => { + const binMap: BinaryMap = new BinaryMap(); + binMap.set(new Uint8Array([1, 50, 24, 34, 64, 23]), 1); + binMap.set(new Uint8Array([1, 50, 24, 34, 64, 23, 60, 120, 40]), 2); + binMap.set(new Uint8Array([1, 50, 24, 34, 64, 23, 60, 120, 40, 21 ,54, 232]), 3); + assert(binMap.get(new Uint8Array([1, 50, 24, 34, 64, 23])) === 1); + assert(binMap.get(new Uint8Array([1, 50, 24, 34, 64, 23, 60, 120, 40])) === 2); + assert(binMap.get(new Uint8Array([1, 50, 24, 34, 64, 23, 60, 120, 40, 21 ,54, 232])) === 3); + done(); + }); + test("Test `get` with start and end specified", done => { + const binMap: BinaryMap = new BinaryMap(); + binMap.set(new Uint8Array([1, 50, 24]), 1); + binMap.set(new Uint8Array([24, 34, 64]), 2); + binMap.set(new Uint8Array([23, 60, 120, 1, 50, 24]), 255); + const mainArray = new Uint8Array([ 64, 23, 60, 120, 1, 50, 24, 34, 64]); + assert(binMap.get(mainArray, 4, 7) === 1); + assert(binMap.get(mainArray, 6, 9) === 2); + assert(binMap.get(mainArray, 1, 9) === 2); + done(); + }); + }); + \ No newline at end of file From 79cefee6d1dc065968a2e59db0ac6a50c1e5f3ea Mon Sep 17 00:00:00 2001 From: andreamah Date: Fri, 12 Apr 2024 13:05:16 -0700 Subject: [PATCH 07/11] fix bug with binary map --- tokenizer_ts/src/bytePairEncode.ts | 5 +++++ tokenizer_ts/test/binaryMap.test.ts | 12 ++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/tokenizer_ts/src/bytePairEncode.ts b/tokenizer_ts/src/bytePairEncode.ts index 625c586..f2d78f9 100644 --- a/tokenizer_ts/src/bytePairEncode.ts +++ b/tokenizer_ts/src/bytePairEncode.ts @@ -20,6 +20,11 @@ const binaryMapKey = (k: Uint8Array, start: number, end: number): number => { // so that we discard the data outside our range const lowerMask = 0xFFFFFF >>> Math.max(0, (3 - length) * 8); const lower = (k[start + 0] | (k[start + 1] << 8) | (k[start + 2] << 16)) & lowerMask; + + if (length <= 3) { + return lower; + } + const upperMask = 0xFFFFFF >>> Math.max(0, (6 - length) * 8); const upper = (k[start + 3] | (k[start + 4] << 8) | (k[start + 5] << 16)) & upperMask; return lower + (0xFFFFFF * upper); diff --git a/tokenizer_ts/test/binaryMap.test.ts b/tokenizer_ts/test/binaryMap.test.ts index 12fd80a..3594403 100644 --- a/tokenizer_ts/test/binaryMap.test.ts +++ b/tokenizer_ts/test/binaryMap.test.ts @@ -4,7 +4,7 @@ import * as assert from "assert"; import { BinaryMap } from "../src/bytePairEncode"; suite("BinaryMap Test Suite", function() { - test("Test basic input to binary map - one level", done => { + test("Test basic input to map - one level", done => { const binMap: BinaryMap = new BinaryMap(); binMap.set(new Uint8Array([1, 50, 24]), 1); assert(binMap.get(new Uint8Array([1, 50, 24])) === 1); @@ -15,7 +15,7 @@ suite("BinaryMap Test Suite", function() { assert(binMap.get(new Uint8Array([1, 50, 24,100])) === 100); done(); }); - test("Test basic input to binary map - one or two levels", done => { + test("Test basic input to map - one or two levels", done => { const binMap: BinaryMap = new BinaryMap(); binMap.set(new Uint8Array([1, 50, 24, 34, 64, 23]), 1); binMap.set(new Uint8Array([1, 50, 24, 34, 64, 23, 60, 120, 40]), 2); @@ -27,13 +27,17 @@ suite("BinaryMap Test Suite", function() { }); test("Test `get` with start and end specified", done => { const binMap: BinaryMap = new BinaryMap(); + binMap.set(new Uint8Array([64, 23]), 100); binMap.set(new Uint8Array([1, 50, 24]), 1); binMap.set(new Uint8Array([24, 34, 64]), 2); binMap.set(new Uint8Array([23, 60, 120, 1, 50, 24]), 255); - const mainArray = new Uint8Array([ 64, 23, 60, 120, 1, 50, 24, 34, 64]); + const mainArray = new Uint8Array([ 64, 23, 60, 120, 1, 50, 24, 34, 64]); assert(binMap.get(mainArray, 4, 7) === 1); assert(binMap.get(mainArray, 6, 9) === 2); - assert(binMap.get(mainArray, 1, 9) === 2); + assert(binMap.get(mainArray, 1, 7) === 255); + assert(binMap.get(mainArray, 7, 7) === undefined); + assert(binMap.get(mainArray, 6, 10) === 2); + assert(binMap.get(mainArray, 0, 2) === 100); done(); }); }); From 056aaaeac7a01d5386161242e686dbb513de104c Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 12 Apr 2024 14:09:39 -0700 Subject: [PATCH 08/11] exclude perf from npm module --- tokenizer_ts/.npmignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tokenizer_ts/.npmignore b/tokenizer_ts/.npmignore index ed5cb3e..8b1b19a 100644 --- a/tokenizer_ts/.npmignore +++ b/tokenizer_ts/.npmignore @@ -7,4 +7,6 @@ dist/debug.* dist/test/* debug.ts *.tiktoken -.eslintrc.js \ No newline at end of file +.eslintrc.js +/perf/* +*.map From 3399b6b2d964d801c1b4ef2863754fd007913bd0 Mon Sep 17 00:00:00 2001 From: andreamah Date: Fri, 12 Apr 2024 14:54:58 -0700 Subject: [PATCH 09/11] remove conditional and replace with bitwise --- tokenizer_ts/src/bytePairEncode.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tokenizer_ts/src/bytePairEncode.ts b/tokenizer_ts/src/bytePairEncode.ts index f2d78f9..a767695 100644 --- a/tokenizer_ts/src/bytePairEncode.ts +++ b/tokenizer_ts/src/bytePairEncode.ts @@ -20,12 +20,8 @@ const binaryMapKey = (k: Uint8Array, start: number, end: number): number => { // so that we discard the data outside our range const lowerMask = 0xFFFFFF >>> Math.max(0, (3 - length) * 8); const lower = (k[start + 0] | (k[start + 1] << 8) | (k[start + 2] << 16)) & lowerMask; - - if (length <= 3) { - return lower; - } - const upperMask = 0xFFFFFF >>> Math.max(0, (6 - length) * 8); + const upperMask = 0xFFFFFF >>> Math.min(31, Math.max(0, (6 - length) * 8)); const upper = (k[start + 3] | (k[start + 4] << 8) | (k[start + 5] << 16)) & upperMask; return lower + (0xFFFFFF * upper); }; From 81d8d01a7f93031b117b9f3f17cea1ed52adf58e Mon Sep 17 00:00:00 2001 From: andreamah Date: Fri, 12 Apr 2024 16:44:51 -0700 Subject: [PATCH 10/11] change multiplier to 0x1000000 and add extra binaryMap tests --- tokenizer_ts/src/bytePairEncode.ts | 5 +- tokenizer_ts/test/binaryMap.test.ts | 82 ++++++++++++++++++++++++----- 2 files changed, 72 insertions(+), 15 deletions(-) diff --git a/tokenizer_ts/src/bytePairEncode.ts b/tokenizer_ts/src/bytePairEncode.ts index a767695..61324c2 100644 --- a/tokenizer_ts/src/bytePairEncode.ts +++ b/tokenizer_ts/src/bytePairEncode.ts @@ -7,7 +7,8 @@ const enum Constant { BytesPerLevel = 6, } -const binaryMapKey = (k: Uint8Array, start: number, end: number): number => { +// exported for testing +export const binaryMapKey = (k: Uint8Array, start: number, end: number): number => { const length = end - start; // 'lower' and 'upper' are both 24-bit integers, like @@ -23,7 +24,7 @@ const binaryMapKey = (k: Uint8Array, start: number, end: number): number => { const upperMask = 0xFFFFFF >>> Math.min(31, Math.max(0, (6 - length) * 8)); const upper = (k[start + 3] | (k[start + 4] << 8) | (k[start + 5] << 16)) & upperMask; - return lower + (0xFFFFFF * upper); + return lower + (0x1000000 * upper); }; export class BinaryMap { diff --git a/tokenizer_ts/test/binaryMap.test.ts b/tokenizer_ts/test/binaryMap.test.ts index 3594403..6d6d3e7 100644 --- a/tokenizer_ts/test/binaryMap.test.ts +++ b/tokenizer_ts/test/binaryMap.test.ts @@ -2,36 +2,36 @@ // Licensed under the MIT License. import * as assert from "assert"; -import { BinaryMap } from "../src/bytePairEncode"; -suite("BinaryMap Test Suite", function() { +import { BinaryMap, binaryMapKey } from "../src/bytePairEncode"; +suite("BinaryMap Test Suite", function () { test("Test basic input to map - one level", done => { - const binMap: BinaryMap = new BinaryMap(); + const binMap: BinaryMap = new BinaryMap(); binMap.set(new Uint8Array([1, 50, 24]), 1); assert(binMap.get(new Uint8Array([1, 50, 24])) === 1); assert(binMap.get(new Uint8Array([1, 50])) === undefined); - assert(binMap.get(new Uint8Array([1, 50, 24,100])) === undefined); + assert(binMap.get(new Uint8Array([1, 50, 24, 100])) === undefined); - binMap.set(new Uint8Array([1, 50, 24,100]), 100); - assert(binMap.get(new Uint8Array([1, 50, 24,100])) === 100); + binMap.set(new Uint8Array([1, 50, 24, 100]), 100); + assert(binMap.get(new Uint8Array([1, 50, 24, 100])) === 100); done(); }); test("Test basic input to map - one or two levels", done => { - const binMap: BinaryMap = new BinaryMap(); + const binMap: BinaryMap = new BinaryMap(); binMap.set(new Uint8Array([1, 50, 24, 34, 64, 23]), 1); binMap.set(new Uint8Array([1, 50, 24, 34, 64, 23, 60, 120, 40]), 2); - binMap.set(new Uint8Array([1, 50, 24, 34, 64, 23, 60, 120, 40, 21 ,54, 232]), 3); + binMap.set(new Uint8Array([1, 50, 24, 34, 64, 23, 60, 120, 40, 21, 54, 232]), 3); assert(binMap.get(new Uint8Array([1, 50, 24, 34, 64, 23])) === 1); assert(binMap.get(new Uint8Array([1, 50, 24, 34, 64, 23, 60, 120, 40])) === 2); - assert(binMap.get(new Uint8Array([1, 50, 24, 34, 64, 23, 60, 120, 40, 21 ,54, 232])) === 3); + assert(binMap.get(new Uint8Array([1, 50, 24, 34, 64, 23, 60, 120, 40, 21, 54, 232])) === 3); done(); }); test("Test `get` with start and end specified", done => { - const binMap: BinaryMap = new BinaryMap(); + const binMap: BinaryMap = new BinaryMap(); binMap.set(new Uint8Array([64, 23]), 100); binMap.set(new Uint8Array([1, 50, 24]), 1); binMap.set(new Uint8Array([24, 34, 64]), 2); binMap.set(new Uint8Array([23, 60, 120, 1, 50, 24]), 255); - const mainArray = new Uint8Array([ 64, 23, 60, 120, 1, 50, 24, 34, 64]); + const mainArray = new Uint8Array([64, 23, 60, 120, 1, 50, 24, 34, 64]); assert(binMap.get(mainArray, 4, 7) === 1); assert(binMap.get(mainArray, 6, 9) === 2); assert(binMap.get(mainArray, 1, 7) === 255); @@ -40,5 +40,61 @@ suite("BinaryMap Test Suite", function() { assert(binMap.get(mainArray, 0, 2) === 100); done(); }); - }); - \ No newline at end of file +}); +suite("Binary Map Key Function Test", function () { + test("First 3 Max Bytes", done => { + const arr = new Uint8Array([0xFF, 0xFF, 0xFF, 0xAB, 0xCD, 0xEF]); + const result = binaryMapKey(arr, 0, arr.length); + assert.strictEqual(result, 0xEFCDABFFFFFF); + done(); + }); + + test("All 6 Max Bytes", done => { + const arr = new Uint8Array([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]); + const result = binaryMapKey(arr, 0, arr.length); + assert.strictEqual(result, 0xFFFFFFFFFFFF); + done(); + }); + + test("First 3 Min Bytes", done => { + const arr = new Uint8Array([0x00, 0x00, 0x00, 0xAB, 0xCD, 0xEF]); + const result = binaryMapKey(arr, 0, arr.length); + assert.strictEqual(result, 0xEFCDAB000000); + done(); + }); + + test("Last 3 Min Bytes", done => { + const arr = new Uint8Array([0xAB, 0xCD, 0xEF, 0x00, 0x00, 0x00]); + const result = binaryMapKey(arr, 0, arr.length); + assert.strictEqual(result, 0x000000EFCDAB); + done(); + }); + + test("Assorted Bytes", done => { + const arr = new Uint8Array([0xBA, 0xDC, 0xFE, 0xEF, 0xCD, 0xAB]); + const result = binaryMapKey(arr, 0, arr.length); + assert.strictEqual(result, 0xABCDEFFEDCBA); + done(); + }); + + test("Assorted Bytes with start/end defined in lower bits", done => { + const arr = new Uint8Array([0xBA, 0xDC, 0xFE, 0xEF, 0xCD, 0xAB]); + const result = binaryMapKey(arr, 1, 3); + assert.strictEqual(result, 0x00000000FEDC); + done(); + }); + + test("Assorted Bytes with start/end defined in upper bits", done => { + const arr = new Uint8Array([0xBA, 0xDC, 0xFE, 0xEF, 0xCD, 0xAB]); + const result = binaryMapKey(arr, 3, 6); + assert.strictEqual(result, 0x000000ABCDEF); + done(); + }); + + test("Assorted Bytes with start/end defined across upper and lower bits", done => { + const arr = new Uint8Array([0xBA, 0xDC, 0xFE, 0xEF, 0xCD, 0xAB]); + const result = binaryMapKey(arr, 2, 5); + assert.strictEqual(result, 0x000000CDEFFE); + done(); + }); +}); From 82b878bcecb4106a324b9b5c9512f00834470929 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Sun, 14 Apr 2024 17:54:52 -0700 Subject: [PATCH 11/11] perf: reduce allocations when encoding text The tokenizer encodes each substring after regex splitting. Allocating an entirely new array for each substring is wasteful. Instead, because each encoded string is only used internally, we can reuse the same buffer in Node.js environments. This reduces tokenization time by about 13%: ![](https://memes.peet.io/img/24-04-7e16113b-f7bd-41d1-85bb-dad1a7b6b9c6.png) Builds on https://github.com/microsoft/Tokenizer/pull/35 --- tokenizer_ts/perf/notebook.ipynb | 35 ++++++++----------- tokenizer_ts/src/bytePairEncode.ts | 11 +++--- tokenizer_ts/src/textEncoder.ts | 56 ++++++++++++++++++++++++++++++ tokenizer_ts/src/tikTokenizer.ts | 20 ++++++----- 4 files changed, 87 insertions(+), 35 deletions(-) create mode 100644 tokenizer_ts/src/textEncoder.ts diff --git a/tokenizer_ts/perf/notebook.ipynb b/tokenizer_ts/perf/notebook.ipynb index e6cb759..89c926f 100644 --- a/tokenizer_ts/perf/notebook.ipynb +++ b/tokenizer_ts/perf/notebook.ipynb @@ -11,20 +11,11 @@ }, { "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": 1, + "metadata": { + "metadata": {} + }, + "outputs": [], "source": [ "import os\n", "import subprocess\n", @@ -40,7 +31,7 @@ " parsed = json.loads(result)\n", " return parsed\n", "\n", - "os.system('npm install @microsoft/tiktokenizer --prefix ./')\n" + "#os.system('npm install @microsoft/tiktokenizer --prefix ./')\n" ] }, { @@ -52,8 +43,10 @@ }, { "cell_type": "code", - "execution_count": 50, - "metadata": {}, + "execution_count": 2, + "metadata": { + "metadata": {} + }, "outputs": [], "source": [ "# This can take a minute, make some tea 🍵\n", @@ -62,7 +55,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -97,7 +90,7 @@ }, { "cell_type": "code", - "execution_count": 64, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -106,7 +99,7 @@ }, { "cell_type": "code", - "execution_count": 65, + "execution_count": null, "metadata": {}, "outputs": [ { @@ -133,7 +126,7 @@ }, { "cell_type": "code", - "execution_count": 66, + "execution_count": null, "metadata": {}, "outputs": [ { diff --git a/tokenizer_ts/src/bytePairEncode.ts b/tokenizer_ts/src/bytePairEncode.ts index 61324c2..fc0f353 100644 --- a/tokenizer_ts/src/bytePairEncode.ts +++ b/tokenizer_ts/src/bytePairEncode.ts @@ -87,9 +87,10 @@ const maxRank = 0x7FFFFFFF; // max int32, try and keep things in integer space */ export function bytePairEncode( mergingBytes: Uint8Array, - ranks: BinaryMap + ranks: BinaryMap, + length: number, ): number[] { - if (mergingBytes.length === 1) { + if (length === 1) { return [ranks.get(mergingBytes)!]; } @@ -97,7 +98,7 @@ export function bytePairEncode( let minIndex = -1; const byteIndicesAndRanks: [number, number][] = []; - for (let i = 0; i < mergingBytes.length - 1; i++) { + for (let i = 0; i < length - 1; i++) { const rank = ranks.get(mergingBytes, i, i + 2) ?? maxRank; if (rank < minRank) { minRank = rank; @@ -106,8 +107,8 @@ export function bytePairEncode( byteIndicesAndRanks.push([i, rank]); } - byteIndicesAndRanks.push([mergingBytes.length - 1, maxRank]); - byteIndicesAndRanks.push([mergingBytes.length, maxRank]); + byteIndicesAndRanks.push([length - 1, maxRank]); + byteIndicesAndRanks.push([length, maxRank]); function getRank(startIndex: number, skip = 0): number { if (startIndex + skip + 2 < byteIndicesAndRanks.length) { diff --git a/tokenizer_ts/src/textEncoder.ts b/tokenizer_ts/src/textEncoder.ts new file mode 100644 index 0000000..a5393cd --- /dev/null +++ b/tokenizer_ts/src/textEncoder.ts @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * A text encoder interface. + */ +export interface ITextEncoder { + /** + * Number of bytes written in the last call to {@link encode} + */ + length: number; + + /** + * Encodes the text and returns the Uint8Array it was written to. The length + * of data written to the array can be found in {@link length}. + * + * The data returned in the array is only valid until the next call to encode. + */ + encode(text: string): Uint8Array; +} + +class UniversalTextEncoder implements ITextEncoder { + public length = 0; + private encoder = new TextEncoder(); + + public encode(text: string): Uint8Array { + const arr = this.encoder.encode(text); + this.length = arr.length; + return arr; + } +} + +class NodeTextEncoder implements ITextEncoder { + private buffer = Buffer.alloc(256); + public length = 0; + + public encode(text: string): Uint8Array { + while (true) { + this.length = this.buffer.write(text, 'utf8'); + + // buffer.write returns the number of bytes written and can write less + // than the length of the string if the buffer is too small. If this + // might have happened (4 bytes is the longest utf8 codepoint), make + // the buffer bigger and try again. + if (this.length < this.buffer.length - 4) { + return this.buffer; + } + + this.buffer = Buffer.alloc(this.length * 2); + this.length = this.buffer.write(text); + } + } +} + +export const makeTextEncoder = (): ITextEncoder => + typeof Buffer !== 'undefined' ? new NodeTextEncoder() : new UniversalTextEncoder(); diff --git a/tokenizer_ts/src/tikTokenizer.ts b/tokenizer_ts/src/tikTokenizer.ts index 001a2ff..15eea14 100644 --- a/tokenizer_ts/src/tikTokenizer.ts +++ b/tokenizer_ts/src/tikTokenizer.ts @@ -3,8 +3,9 @@ import * as fs from "fs"; import { LRUCache } from "lru-cache"; -import { TextDecoder, TextEncoder } from "util"; +import { TextDecoder } from "util"; import { BinaryMap, bytePairEncode } from "./bytePairEncode"; +import { makeTextEncoder } from './textEncoder'; /** * Load BPE ranks from a file @@ -64,7 +65,7 @@ export class TikTokenizer { private specialTokensRegex?: RegExp; private specialTokensEncoder?: ReadonlyMap; private specialTokensDecoder?: Map; - private textEncoder = new TextEncoder(); + private textEncoder = makeTextEncoder(); private textDecoder = new TextDecoder("utf-8"); public readonly cache: LRUCache; @@ -206,12 +207,12 @@ export class TikTokenizer { } else { // cache miss const bytes = this.textEncoder.encode(match[0]); - const token = this.encoder?.get(bytes); + const token = this.encoder?.get(bytes, 0, this.textEncoder.length); if (token !== undefined) { tokenIds.push(token); this.cache.set(match[0], [token]); } else { - const encodedTokens = bytePairEncode(bytes, this.encoder!); + const encodedTokens = bytePairEncode(bytes, this.encoder!, this.textEncoder.length); tokenIds.push(...encodedTokens); this.cache.set(match[0], encodedTokens); } @@ -249,7 +250,7 @@ export class TikTokenizer { } else { // cache miss const bytes = this.textEncoder.encode(piece); - const token = this.encoder!.get(bytes); + const token = this.encoder!.get(bytes, 0, bytes.length); if (token !== undefined) { this.cache.set(piece, [token]); if (tokenCount + 1 <= maxTokenCount) { @@ -260,7 +261,7 @@ export class TikTokenizer { break; } } else { - const encodedTokens = bytePairEncode(bytes, this.encoder!); + const encodedTokens = bytePairEncode(bytes, this.encoder!, this.textEncoder.length); this.cache.set(piece, encodedTokens); if (tokenCount + encodedTokens.length <= maxTokenCount) { tokenCount += encodedTokens.length; @@ -395,7 +396,7 @@ export class TikTokenizer { tokenIds.push(...tokens!); tokenCountMap.set(tokenCount, encodeLength); } else { - const bytes = new TextEncoder().encode(piece); + const bytes = this.textEncoder.encode(piece); const token = this.encoder!.get(bytes); if (token !== undefined) { this.cache.set(piece, [token]); @@ -404,7 +405,7 @@ export class TikTokenizer { tokenIds.push(token); tokenCountMap.set(tokenCount, encodeLength); } else { - const encodedTokens = bytePairEncode(bytes, this.encoder!); + const encodedTokens = bytePairEncode(bytes, this.encoder!, this.textEncoder.length); this.cache.set(piece, encodedTokens); tokenCount += encodedTokens.length; encodeLength += piece.length; @@ -474,7 +475,8 @@ export class TikTokenizer { } else { const specialTokenValue = this.specialTokensDecoder?.get(token); if (specialTokenValue !== undefined) { - tokenBytes = Array.from(this.textEncoder.encode(specialTokenValue)); + const bytes = this.textEncoder.encode(specialTokenValue); + tokenBytes = Array.from(bytes.subarray(0, this.textEncoder.length)); } } decoded.push(...tokenBytes);