OpenAI tiktoken
# tiktoken介绍
tiktoken 是OpenAI开源的Python库,一种用于 OpenAI 模型的快速 BPE 标记器。
👉 github 地址 (opens new window)
# tiktoken性能表现
tiktoken 比同类开源标记器快 3-6 倍;
基于 1GB 文本进行测试,使用 GPT-2 tokeniser,使用 GPT2TokenizerFast from tokenizers==0.13.2, transformers==4.24.0 and tiktoken==0.2.0。
# tiktoken 如何计算 token
# 介绍
给定一个文本字符:"tiktoken is great!",和一个 encoding,比如 "cl100k_base"。
一个 tokenizer 可以讲文本字符串分割成一系列 tokens,如:["t", "ik", "token", " is", " great", "!"]
GPT 模型使用这种类型的 token。
知道文本字符串中有多少 token,可以告诉你(a)字符串是否太长,文本模型无法处理,以及(b)OpenAI API调用的成本(因为使用是按token定价的)。
# 编码 Encodings
编码指定如何将文本转换为标记。不同的模型使用不同的编码。
OpenAI models 使用 tiktoken 支持下面三种编码:
Encoding name | OpenAI models |
---|---|
cl100k_base | gpt-4 , gpt-3.5-turbo , text-embedding-ada-002 |
p50k_base | Codex models, text-davinci-002 , text-davinci-003 |
r50k_base (or gpt2 ) | GPT-3 models like davinci |
您可以获取一个模型的编码 ,使用 tiktoken.encoding_for_model()
如下:
encoding = tiktoken.encoding_for_model('gpt-3.5-turbo')
注意,p50k_base
与 r50k_base
基本类似,对于非代码应用程序,它们通常会给出相同的token。
# Tokenizer libraries 对不同编程语言的支持
For cl100k_base
and p50k_base
encodings:
- Python: tiktoken (opens new window)
- .NET / C#: SharpToken (opens new window)
For r50k_base
(gpt2
) encodings, tokenizers are available in many languages.
- Python: tiktoken (opens new window) (or alternatively GPT2TokenizerFast (opens new window))
- JavaScript: gpt-3-encoder (opens new window)
- .NET / C#: GPT Tokenizer (opens new window)
- Java: gpt2-tokenizer-java (opens new window)
- PHP: GPT-3-Encoder-PHP (opens new window)
在英语中,tokens的长度通常从一个字符到一个单词(例如,t
或 great
),尽管在一些语言中,tokens 可以短于一个字符或长于一个单词。
空格通常以单词的开头分组(例如," is"
而不是"is "
或 " "
+"is"
。
您可以在[OpenAI Tokenizer]快速检查字符串是如何tokenize的。
OpenAI Tokenizer : https://platform.openai.com/tokenizer
# tiktoken安装使用
# 0. 安装
pip install --upgrade tiktoken
# 1. 导入 tiktoken
import tiktoken
# 2. 加载编码
方式1:使用 tiktoken.get_encoding()
按名称加载编码。
第一次运行时,需要互联网连接才能下载。以后的运行将不需要互联网连接。
encoding = tiktoken.get_encoding("cl100k_base")
方式:使用 tiktoken.encoding_for_model()
自动加载给定模型名称的正确编码。
encoding = tiktoken.encoding_for_model("gpt-3.5-turbo")
# 3.用 encoding.encode()
把文本变成token标记
.encode()
方法将文本字符串转换为token整数列表。
encoding.encode("tiktoken is great!")
结果
[83, 1609, 5963, 374, 2294, 0]
通过计算 .encode()
返回的列表的长度来计算token。
def num_tokens_from_string(string: str, encoding_name: str) -> int:
"""Returns the number of tokens in a text string."""
encoding = tiktoken.get_encoding(encoding_name)
num_tokens = len(encoding.encode(string))
return num_tokens
num_tokens_from_string("tiktoken is great!", "cl100k_base")
结果为6
# 4.使用 encoding.decode()
将token转为文本
.decode()
将token整数列表转换为字符串。
encoding.decode([83, 1609, 5963, 374, 2294, 0])
结果
'tiktoken is great!'
警告:尽管 .decode()
可以应用于单个token,但请注意它可能对不在 utf-8 边界上的token造成损失。
对于单个token, .decode_single_token_bytes()
将单个整数token安全地转换为其表示的字节。
[encoding.decode_single_token_bytes(token) for token in [83, 1609, 5963, 374, 2294, 0]]
结果
[b't', b'ik', b'token', b' is', b' great', b'!']
(字符串前面的 b
表示该字符串是字节串。)
# 5.比较编码
不同的编码在拆分单词、分组空格和处理非英语字符的方式上有所不同。使用上述方法,我们可以比较几个示例字符串的不同编码。
def compare_encodings(example_string: str) -> None:
"""Prints a comparison of three string encodings."""
# print the example string
print(f'\nExample string: "{example_string}"')
# for each encoding, print the # of tokens, the token integers, and the token bytes
for encoding_name in ["gpt2", "p50k_base", "cl100k_base"]:
encoding = tiktoken.get_encoding(encoding_name)
token_integers = encoding.encode(example_string)
num_tokens = len(token_integers)
token_bytes = [encoding.decode_single_token_bytes(token) for token in token_integers]
print()
print(f"{encoding_name}: {num_tokens} tokens")
print(f"token integers: {token_integers}")
print(f"token bytes: {token_bytes}")
#调用
compare_encodings("antidisestablishmentarianism")
结果
Example string: "antidisestablishmentarianism"
gpt2: 5 tokens
token integers: [415, 29207, 44390, 3699, 1042]
token bytes: [b'ant', b'idis', b'establishment', b'arian', b'ism']
p50k_base: 5 tokens
token integers: [415, 29207, 44390, 3699, 1042]
token bytes: [b'ant', b'idis', b'establishment', b'arian', b'ism']
cl100k_base: 6 tokens
token integers: [519, 85342, 34500, 479, 8997, 2191]
token bytes: [b'ant', b'idis', b'establish', b'ment', b'arian', b'ism']
#调用
compare_encodings("2 + 2 = 4")
结果
Example string: "2 + 2 = 4"
gpt2: 5 tokens
token integers: [17, 1343, 362, 796, 604]
token bytes: [b'2', b' +', b' 2', b' =', b' 4']
p50k_base: 5 tokens
token integers: [17, 1343, 362, 796, 604]
token bytes: [b'2', b' +', b' 2', b' =', b' 4']
cl100k_base: 7 tokens
token integers: [17, 489, 220, 17, 284, 220, 19]
token bytes: [b'2', b' +', b' ', b'2', b' =', b' ', b'4']
#调用
compare_encodings("お誕生日おめでとう")
结果
Example string: "お誕生日おめでとう"
gpt2: 14 tokens
token integers: [2515, 232, 45739, 243, 37955, 33768, 98, 2515, 232, 1792, 223, 30640, 30201, 29557]
token bytes: [b'\xe3\x81', b'\x8a', b'\xe8\xaa', b'\x95', b'\xe7\x94\x9f', b'\xe6\x97', b'\xa5', b'\xe3\x81', b'\x8a', b'\xe3\x82', b'\x81', b'\xe3\x81\xa7', b'\xe3\x81\xa8', b'\xe3\x81\x86']
p50k_base: 14 tokens
token integers: [2515, 232, 45739, 243, 37955, 33768, 98, 2515, 232, 1792, 223, 30640, 30201, 29557]
token bytes: [b'\xe3\x81', b'\x8a', b'\xe8\xaa', b'\x95', b'\xe7\x94\x9f', b'\xe6\x97', b'\xa5', b'\xe3\x81', b'\x8a', b'\xe3\x82', b'\x81', b'\xe3\x81\xa7', b'\xe3\x81\xa8', b'\xe3\x81\x86']
cl100k_base: 9 tokens
token integers: [33334, 45918, 243, 21990, 9080, 33334, 62004, 16556, 78699]
token bytes: [b'\xe3\x81\x8a', b'\xe8\xaa', b'\x95', b'\xe7\x94\x9f', b'\xe6\x97\xa5', b'\xe3\x81\x8a', b'\xe3\x82\x81', b'\xe3\x81\xa7', b'\xe3\x81\xa8\xe3\x81\x86']
# 6. 计算chat API 调用的token
gpt-3.5-turbo
和 gpt-4
等 ChatGPT 模型使用令牌的方式与旧的completions模型相同,但由于它们基于消息的格式,因此更难计算对话将使用多少token。
下面是一个示例函数,用于计算传递给 gpt-3.5-turbo-0301
或 gpt-4-0314
的消息的令牌。
请注意,从消息中计算token的确切方式可能因模型而异。考虑低于估计值的函数计数,而不是永恒的保证。
def num_tokens_from_messages(messages, model="gpt-3.5-turbo-0301"):
"""Returns the number of tokens used by a list of messages."""
try:
encoding = tiktoken.encoding_for_model(model)
except KeyError:
print("Warning: model not found. Using cl100k_base encoding.")
encoding = tiktoken.get_encoding("cl100k_base")
if model == "gpt-3.5-turbo":
print("Warning: gpt-3.5-turbo may change over time. Returning num tokens assuming gpt-3.5-turbo-0301.")
return num_tokens_from_messages(messages, model="gpt-3.5-turbo-0301")
elif model == "gpt-4":
print("Warning: gpt-4 may change over time. Returning num tokens assuming gpt-4-0314.")
return num_tokens_from_messages(messages, model="gpt-4-0314")
elif model == "gpt-3.5-turbo-0301":
tokens_per_message = 4 # every message follows <|start|>{role/name}\n{content}<|end|>\n
tokens_per_name = -1 # if there's a name, the role is omitted
elif model == "gpt-4-0314":
tokens_per_message = 3
tokens_per_name = 1
else:
raise NotImplementedError(f"""num_tokens_from_messages() is not implemented for model {model}. See https://github.com/openai/openai-python/blob/main/chatml.md for information on how messages are converted to tokens.""")
num_tokens = 0
for message in messages:
num_tokens += tokens_per_message
for key, value in message.items():
num_tokens += len(encoding.encode(value))
if key == "name":
num_tokens += tokens_per_name
num_tokens += 3 # every reply is primed with <|start|>assistant<|message|>
return num_tokens
让我们验证一下上面的函数是否与OpenAI的API响应相匹配
import openai
example_messages = [
{
"role": "system",
"content": "You are a helpful, pattern-following assistant that translates corporate jargon into plain English.",
},
{
"role": "system",
"name": "example_user",
"content": "New synergies will help drive top-line growth.",
},
{
"role": "system",
"name": "example_assistant",
"content": "Things working well together will increase revenue.",
},
{
"role": "system",
"name": "example_user",
"content": "Let's circle back when we have more bandwidth to touch base on opportunities for increased leverage.",
},
{
"role": "system",
"name": "example_assistant",
"content": "Let's talk later when we're less busy about how to do better.",
},
{
"role": "user",
"content": "This late pivot means we don't have time to boil the ocean for the client deliverable.",
},
]
for model in ["gpt-3.5-turbo-0301", "gpt-4-0314"]:
print(model)
# example token count from the function defined above
print(f"{num_tokens_from_messages(example_messages, model)} prompt tokens counted by num_tokens_from_messages().")
# example token count from the OpenAI API
response = openai.ChatCompletion.create(
model=model,
messages=example_messages,
temperature=0,
max_tokens=1 # we're only counting input tokens here, so let's not waste tokens on the output
)
print(f'{response["usage"]["prompt_tokens"]} prompt tokens counted by the OpenAI API.')
print()
结果
gpt-3.5-turbo-0301
127 prompt tokens counted by num_tokens_from_messages().
127 prompt tokens counted by the OpenAI API.
gpt-4-0314
129 prompt tokens counted by num_tokens_from_messages().
129 prompt tokens counted by the OpenAI API.
# BPE 简介 (拓展)
字节编码对(Byte Pair Encoder,BPE)是一种子词处理的方法。其主要的目的是为了压缩文本数据。主要是将数据中最常连续出现的字节(bytes)替换成数据中没有出现的字节的方法。该算法首先由Philip Gage在1994年提出。
其它一些流行的子词标记化算法包括WordPiece、Unigram和SentencePiece。而BPE用于GPT-2、RoBERTa、XLM、FlauBERT等语言模型中。这些模型中有几个使用空间标记化作为预标记化方法,而有几个使用Moses, spaCY, ftfy提供的更高级的预标记化方法。