point1|如何使用 tiktoken 计数令牌
你好,我是悦创。
tiktoken
是 OpenAI 的一个快速开源分词器。
给定一个文本字符串(例如,"tiktoken is great!"
)和一个编码(例如,"cl100k_base"
),分词器可以将文本字符串分割成一个令牌列表(例如,["t", "ik", "token", " is", " great", "!"]
)。
将文本字符串分割成令牌是有用的,因为 GPT 模型以令牌的形式看到文本。知道一个文本字符串中有多少个令牌,可以告诉你:
(a)字符串是否对于一个文本模型来说太长,无法处理,以及(b)OpenAI API 调用的成本是多少(因为使用是按照令牌计价的)。
1. 编码
编码指定了如何将文本转换成令牌。不同的模型使用不同的编码。
tiktoken
支持 OpenAI 模型使用的三种编码:
编码名称 | OpenAI模型 |
---|---|
cl100k_base | gpt-4 , gpt-3.5-turbo , text-embedding-ada-002 |
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_base
与r50k_base
有很大的重叠,对于非代码应用,他们通常会给出相同的令牌。
2. 按语言划分的分词器库
对于 cl100k_base
和 p50k_base
编码:
- Python: tiktoken
- .NET / C#: SharpToken, TiktokenSharp
- Java: jtokkit
- Golang: tiktoken-go
对于 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
(OpenAI 对第三方库不做背书或保证。)
3. 字符串通常如何被分词
在英语中,令牌通常的长度范围从一个字符到一个单词(例如,"t"
或 " great"
),尽管在一些语言中,令牌可以比一个字符短或比一个单词长。空格通常与单词的开始部分一起分组(例如," is"
而不是 "is "
或 " "
+ "is"
)。你可以在OpenAI分词器快速检查一个字符串是如何被分词的。
4. 使用
4.1 Install tiktoken
安装 tiktoken 库:
pip install --upgrade tiktoken
4.2 导入库
import tiktoken
4.3 加载编码
使用 tiktoken.get_encoding()
来按名称加载编码。
此操作第一次运行时,需要互联网连接进行下载。以后的运行不需要互联网连接。
encoding = tiktoken.get_encoding("cl100k_base")
使用 tiktoken.encoding_for_model()
来自动加载给定模型名称的正确编码。
encoding = tiktoken.encoding_for_model("gpt-3.5-turbo")
4.4 使用 encoding.encode()
将文本转化为标记
.encode()
方法将文本字符串转换为一系列的令牌整数。
encoding.encode("tiktoken is great!")
# ---output---
[83, 1609, 5963, 374, 2294, 0]
通过计算 .encode()
返回的列表的长度来统计令牌数量。
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
n = num_tokens_from_string("tiktoken is great!", "cl100k_base")
print(n)
4.5 使用 encoding.decode()
将令牌转化为文本
.decode()
将一个令牌整数列表转换为字符串。
encoding.decode([83, 1609, 5963, 374, 2294, 0])
import tiktoken
def num_tokens_from_string(string: str, encoding_name: str) -> tuple:
"""Returns the number of tokens in a text string."""
encoding = tiktoken.get_encoding(encoding_name)
result = encoding.encode(string)
num_tokens = len(result)
return result, num_tokens
result, num_tokens = num_tokens_from_string("tiktoken is great!", "cl100k_base")
print(result, num_tokens)
decode_str = tiktoken.get_encoding("cl100k_base").decode(result)
print(decode_str)
注意
警告:尽管 .decode()
可以应用于单个令牌,但请注意,对于不在 utf-8 边界上的令牌,它可能会有损失。
对于单个令牌,.decode_single_token_bytes()
安全地将单个整数令牌转换为它所代表的字节。
[encoding.decode_single_token_bytes(token) for token in [83, 1609, 5963, 374, 2294, 0]]
import tiktoken
# encoding = tiktoken.get_encoding("cl100k_base")
# encoding = tiktoken.encoding_for_model("gpt-3.5-turbo")
# encoding_text = encoding.encode("tiktoken is great!")
# print(encoding_text)
def num_tokens_from_string(string: str, encoding_name: str) -> tuple:
"""Returns the number of tokens in a text string."""
encoding = tiktoken.get_encoding(encoding_name)
result = encoding.encode(string)
num_tokens = len(result)
return result, num_tokens
result, num_tokens = num_tokens_from_string("tiktoken is great!", "cl100k_base")
print(result, num_tokens)
decode_str = tiktoken.get_encoding("cl100k_base").decode(result)
print(decode_str)
r = [tiktoken.get_encoding("cl100k_base").decode_single_token_bytes(token) for token in result]
print(r)
(字符串前面的
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 completions API 调用的 tokens 数量
像 gpt-3.5-turbo
和 gpt-4
这样的 ChatGPT 模型与早期的 completions 模型以相同的方式使用 tokens,但由于其基于消息的格式,使得计算一个对话将使用多少 tokens 变得更加困难。
以下是一个示例函数,用于计算传递给 gpt-3.5-turbo
或 gpt-4
的消息的 tokens 数量。
请注意,从消息中计算 tokens 的确切方式可能会随模型而变化。请将下面函数的计数视为估计,而不是永恒的保证。
特别是,使用可选函数输入的请求将在下面计算的估计之上消耗额外的 tokens。
def num_tokens_from_messages(messages, model="gpt-3.5-turbo-0613"):
"""Return 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 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 # every message follows <|start|>{role/name}\n{content}<|end|>\n
tokens_per_name = -1 # if there's a name, the role is omitted
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() 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
# let's verify the function above matches the OpenAI API response
import openai, os
from dotenv import load_dotenv
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.",
},
]
load_dotenv()
openai.api_key = os.getenv("KEY")
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 = 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-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().
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.
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.
def num_tokens_from_messages(messages, model="gpt-3.5-turbo-0613"):
"""
计算一组消息在特定OpenAI模型中使用的token数量。
参数:
messages (list): 一个包含消息的列表。
model (str): 使用的OpenAI模型的名称。
返回:
num_tokens (int): 消息使用的token数量。
"""
# 尝试获取指定模型的编码方式
try:
encoding = tiktoken.encoding_for_model(model)
except KeyError:
# 如果模型未找到,使用默认的编码方式
print("Warning: model not found. Using cl100k_base encoding.")
encoding = tiktoken.get_encoding("cl100k_base")
# 针对不同的模型版本,定义tokens_per_message和tokens_per_name的值
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 # 每个消息的格式为{角色/名称}\n{内容}\n
tokens_per_name = -1 # 如果存在名称,则角色会被省略
elif "gpt-3.5-turbo" in model:
# 对于可能的gpt-3.5-turbo的未知版本,假设其为gpt-3.5-turbo-0613
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:
# 对于可能的gpt-4的未知版本,假设其为gpt-4-0613
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() 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 # 初始化token计数器
for message in messages:
num_tokens += tokens_per_message # 添加基础token计数
# 遍历消息中的键值对,并对值进行编码,然后加到token计数中
for key, value in message.items():
num_tokens += len(encoding.encode(value))
if key == "name":
num_tokens += tokens_per_name
# 每个回复都以助手开始,所以需要加3个tokens
num_tokens += 3
return num_tokens
# 定义一个函数,参数为消息列表和模型名
def num_tokens_from_messages(messages, model="gpt-3.5-turbo-0613"):
"""Return 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 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
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.")
# 如果模型名包含"gpt-3.5-turbo",但不是上面已经列出的模型,就假设模型是"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.")
# 如果模型名包含"gpt-4",但不是上面已经列出的模型,就假设模型是"gpt-4-0613"
return num_tokens_from_messages(messages, model="gpt-4-0613")
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."""
)
# 初始化令牌数为0
num_tokens = 0
# 遍历所有的消息
for message in messages:
# 每条消息需要tokens_per_message个令牌
num_tokens += tokens_per_message
# 遍历消息中的每个键值对
for key, value in message.items():
# 每个值需要的令牌数等于该值的编码长度
num_tokens += len(encoding.encode(value))
# 如果键是"name",就需要额外的tokens_per_name个令牌
if key == "name":
num_tokens += tokens_per_name
# 每个回复需要额外的3个令牌
num_tokens += 3
# 返回总的令牌数
return num_tokens
欢迎关注我公众号:AI悦创,有更多更好玩的等你发现!
公众号:AI悦创【二维码】

AI悦创·编程一对一
AI悦创·推出辅导班啦,包括「Python 语言辅导班、C++ 辅导班、java 辅导班、算法/数据结构辅导班、少儿编程、pygame 游戏开发、Linux、Web」,全部都是一对一教学:一对一辅导 + 一对一答疑 + 布置作业 + 项目实践等。当然,还有线下线上摄影课程、Photoshop、Premiere 一对一教学、QQ、微信在线,随时响应!微信:Jiabcdefh
C++ 信息奥赛题解,长期更新!长期招收一对一中小学信息奥赛集训,莆田、厦门地区有机会线下上门,其他地区线上。微信:Jiabcdefh
方法一:QQ
方法二:微信:Jiabcdefh
