跳到主要内容

如何使用tiktoken计算标记数

nbviewer

tiktoken 是由OpenAI开发的快速开源分词器。

给定一个文本字符串(例如,"tiktoken is great!")和一个编码(例如,"cl100k_base"),分词器可以将文本字符串拆分为标记列表(例如,["t", "ik", "token", " is", " great", "!"])。

将文本字符串拆分为标记对于GPT模型很有用,因为这些模型以标记的形式看待文本。知道文本字符串中有多少标记可以告诉您(a)该字符串是否过长而无法被文本模型处理,以及(b)OpenAI API调用的成本(因为使用是按标记计费的)。

编码

编码指定了文本如何转换为标记。不同的模型使用不同的编码。

tiktoken 支持OpenAI模型使用的三种编码:

编码名称 OpenAI模型
cl100k_base gpt-4, gpt-3.5-turbo, text-embedding-ada-002, text-embedding-3-small, text-embedding-3-large
p50k_base Codex模型, text-davinci-002, text-davinci-003
r50k_base (或 gpt2) GPT-3模型如 davinci

您可以使用以下方式通过 tiktoken.encoding_for_model() 获取模型的编码:

encoding = tiktoken.encoding_for_model('gpt-3.5-turbo')

请注意,p50k_baser50k_base 有很大重叠,在非代码应用中,它们通常会给出相同的标记。

根据语言的分词器库

对于 cl100k_basep50k_base 编码: - Python: tiktoken - .NET / C#: SharpToken, TiktokenSharp - Java: jtokkit - Golang: tiktoken-go - Rust: tiktoken-rs

对于 r50k_base (gpt2) 编码,分词器在许多语言中都可用。 - Python: tiktoken(或者 GPT2TokenizerFast) - JavaScript: gpt-3-encoder - .NET / C#: GPT Tokenizer - Java: gpt2-tokenizer-java - PHP: GPT-3-Encoder-PHP - Golang: tiktoken-go - Rust: tiktoken-rs

(OpenAI不对第三方库作任何认可或保证。)

典型的字符串如何被分词

在英语中,标记通常的长度范围从一个字符到一个单词(例如,"t"" great"),尽管在一些语言中,标记可以比一个字符更短或比一个单词更长。空格通常与单词的开头分组(例如," is" 而不是 "is "" "+"is")。您可以快速检查字符串如何被分词在 OpenAI Tokenizer 或第三方 Tiktokenizer 网页应用中。

0. 安装 tiktoken

如果需要,可以使用 pip 安装 tiktoken

%pip install --upgrade tiktoken
%pip install --upgrade openai

1. 导入 tiktoken

import tiktoken

2. 加载一个编码

使用 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()将文本转换为标记符号

.encode() 方法将一个文本字符串转换为一个令牌整数列表。

encoding.encode("tiktoken is great!")


[83, 1609, 5963, 374, 2294, 0]

通过计算.encode()返回的列表的长度来计算令牌的数量。

def num_tokens_from_string(string: str, encoding_name: str) -> int:
"""返回文本字符串中的标记数。"""
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()将标记转换为文本

.decode()将一个令牌整数列表转换为字符串。

encoding.decode([83, 1609, 5963, 374, 2294, 0])


'tiktoken is great!'

警告:虽然.decode()可以应用于单个标记,但要注意对于不在utf-8边界上的标记,可能会有信息丢失。

对于单个令牌,.decode_single_token_bytes() 可以安全地将单个整数令牌转换为其表示的字节。

[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:
"""打印三种字符串编码的比较结果。"""
# 打印示例字符串
print(f'\nExample string: "{example_string}"')
# 对于每种编码,请输出其标记数量、标记整数以及标记字节。
for encoding_name in ["r50k_base", "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"

r50k_base: 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"

r50k_base: 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: "お誕生日おめでとう"

r50k_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']

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. 统计用于聊天完成API调用的标记

ChatGPT模型如gpt-3.5-turbogpt-4与旧的完成模型一样使用标记,但由于其基于消息的格式,更难以计算对话中将使用多少标记。

以下是一个用于计算传递给gpt-3.5-turbogpt-4的消息标记数量的示例函数。

请注意,从消息中计算标记的确切方式可能因模型而异。请将下面函数中的计数视为估计,而非永恒的保证。

特别是,使用可选功能输入的请求将消耗额外的标记,这些额外的标记不包括下面计算的估计值。

def num_tokens_from_messages(messages, model="gpt-3.5-turbo-0613"):
"""返回由消息列表使用的令牌数量。"""
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 in {
"gpt-3.5-turbo-0613",
"gpt-3.5-turbo-16k-0613",
"gpt-4-0314",
"gpt-4-32k-0314",
"gpt-4-0613",
"gpt-4-32k-0613",
}:
tokens_per_message = 3
tokens_per_name = 1
elif model == "gpt-3.5-turbo-0301":
tokens_per_message = 4 # 每条消息都遵循以下格式:<|start|>{角色/名称}\n{内容}<|end|>\n
tokens_per_name = -1 # 如果存在名称,则角色会被省略。
elif "gpt-3.5-turbo" in model:
print("Warning: gpt-3.5-turbo may update over time. Returning num tokens assuming gpt-3.5-turbo-0613.")
return num_tokens_from_messages(messages, model="gpt-3.5-turbo-0613")
elif "gpt-4" in model:
print("Warning: gpt-4 may update over time. Returning num tokens assuming gpt-4-0613.")
return num_tokens_from_messages(messages, model="gpt-4-0613")
else:
raise NotImplementedError(
f"""num_tokens_from_messages() 函数尚未针对模型 {model} 实现。"""
)
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 # 每条回复都以<|start|>assistant<|message|>作为开头。
return num_tokens


# let's verify the function above matches the OpenAI API response

from openai import OpenAI
import os

client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY", "<your OpenAI API key if not set as env var>"))

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-3.5-turbo-0613",
"gpt-3.5-turbo",
"gpt-4-0314",
"gpt-4-0613",
"gpt-4",
]:
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 = client.chat.completions.create(model=model,
messages=example_messages,
temperature=0,
max_tokens=1)
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-3.5-turbo-0613
129 prompt tokens counted by num_tokens_from_messages().
129 prompt tokens counted by the OpenAI API.

gpt-3.5-turbo
Warning: gpt-3.5-turbo may update over time. Returning num tokens assuming gpt-3.5-turbo-0613.
129 prompt tokens counted by num_tokens_from_messages().
129 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.

gpt-4-0613
129 prompt tokens counted by num_tokens_from_messages().
129 prompt tokens counted by the OpenAI API.

gpt-4
Warning: gpt-4 may update over time. Returning num tokens assuming gpt-4-0613.
129 prompt tokens counted by num_tokens_from_messages().
129 prompt tokens counted by the OpenAI API.