Add BPE tokenization with tokenizeBPE and detokenizeBPE - #119877
Open
mosya415 wants to merge 1 commit into
Open
Conversation
The OpenAI models bill by token and are bounded by a context window, and
there was no way to ask ClickHouse how many tokens a text costs. The two
functions added here tokenize text with a byte pair encoding vocabulary
and turn the ids back into text:
SELECT length(tokenizeBPE(prompt, 'cl100k_base')) FROM prompts;
Vocabularies are declared in the `bpe_vocabularies` section of the server
configuration, in the `.tiktoken` format, each with the pre-tokenizer it
goes with: `r50k` for the GPT-2, `r50k_base` and `p50k_base` vocabularies,
`cl100k` for `cl100k_base`, `o200k` for `o200k_base`. A vocabulary is
loaded when a query first names it and is shared from then on.
The pre-tokenizers are the regular expressions of `tiktoken`, written out
by hand: they need lookahead, which RE2 does not support, and the `o200k`
one needs backtracking. The character classes come from ICU, so `\s` is
the Unicode `White_Space` property and the word shapes see the real
general categories. Merging keeps its candidates in a heap rather than
rescanning the piece, because a piece is only bounded by the length of the
text: a megabyte of one letter is a single piece, and tokenizes in 0.4 s
instead of not finishing.
Text is tokenized as text: a vocabulary has no special tokens, so
`<|endoftext|>` in the input is tokenized the way any other text is. A
string that is not valid UTF-8 is tokenized too, with the bytes that are
not a valid sequence grouping with punctuation, and survives the round
trip byte for byte.
Checked against `tiktoken` over 26000 texts for each of the three
vocabularies: the 52 hand written cases of the test, 20000 random ones
(random code points, random ASCII, whitespace and word boundary shapes),
4000 chunks of the documentation and the source of this repository, and
the whole of CHANGELOG.md as one string. Every id matches, and so does
every decode. 3000 random byte strings per vocabulary round trip exactly.
Throughput on CHANGELOG.md in one thread is 26, 25 and 17 MB/s for the
three vocabularies, against 28, 29 and 39 MB/s for `tiktoken` itself.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related: #108247
Related: #110366
The OpenAI models bill by token and are bounded by a context window, and there was no way to ask ClickHouse how many tokens a text costs. This is the use case @alexey-milovidov singled out in #108247: "For the purpose of this task, I recommend focusing only on the first use-case as a motivation: Estimating the number of tokens to be processed by LLM."
Vocabularies are declared in the server configuration, in the
.tiktokenformat, each with the pre-tokenizer it goes with (r50kfor GPT-2,r50k_baseandp50k_base;cl100kforcl100k_base;o200kforo200k_base):A vocabulary is loaded when a query first names it and shared from then on, and reloaded when the file changes.
vocabularyholds the content inline instead ofpath, which is how the tests and the documentation examples declare one.Notes on the implementation:
tiktokenwritten out by hand: they need lookahead, which RE2 does not support, and theo200kone needs backtracking. Character classes come from ICU, so\sis the UnicodeWhite_Spaceproperty.<|endoftext|>in the input is tokenized like any other text. A string that is not valid UTF-8 is tokenized too and round trips byte for byte.Checked against
tiktokenover 26000 texts per vocabulary (52 hand-written cases, 20000 random ones, 4000 chunks of this repository's docs and source, and the whole ofCHANGELOG.mdas one string): every id matches and so does every decode. 3000 random byte strings per vocabulary round trip exactly. One thread onCHANGELOG.mddoes 26 / 25 / 17 MB/s for the three vocabularies, against 28 / 29 / 39 MB/s fortiktokenitself.Not covered here, and left to the issue: arbitrary dictionaries loaded from tables, the text-index tokenizer interface with a digest to keep an index consistent, and the
aiEstimateTokenCountwrapper of #110366, whose shape (a function or a parametric aggregate) is still open. If you would rather have vocabularies come from aDICTIONARYthan from the configuration, say so and I will move it.Changelog category (leave one):
Changelog entry (a user-readable short description of the changes that goes into CHANGELOG.md):
Added the functions
tokenizeBPEanddetokenizeBPE, which tokenize text with a byte pair encoding vocabulary in the.tiktokenformat and turn the token ids back into text. The number of tokens a text costs an OpenAI model islength(tokenizeBPE(text, 'cl100k_base')). Vocabularies are declared in thebpe_vocabulariessection of the server configuration.