分词器
介绍
大模型通常使用令牌处理文本,这些令牌是在一组文本中找到的常见字符序列。模型学习理解这些令牌之间的统计关系,并擅长产生令牌序列中的下一个令牌。
需要注意的是,具体的分词过程在模型之间有所不同。像MaaS-3.5 和 MaaS-4这样的新模型使用与以前的模型不同的分词器,并且会为相同的输入文本产生不同的令牌。
分词
不同的模型使用不同的分词器,分词过程在模型之间可能会有所不同。分词过程并非总是确定的,可能会受到模型训练数据和分词器具体实现的影响。
这里将提供使用 tiktoken计算令牌的不同方式。
例如,如果给定一个文本字符串(例如,“tiktoken is great!”)和一个编码(例如,“cl100k_base”),分词器可以将文本字符串分割成令牌(例如,["t", "ik", "token", " is", " great", "!"])。
一个有用的经验法则是,对于常见的英文文本,一个令牌通常对应大约4个字符的文本。这大致相当于3/4个单词(所以100个令牌大约等于75个单词)。
编码
编码指定了如何将文本转换为令牌。不同的模型使用不同的编码。 tiktoken支持模型使用的三种编码:
Encoding name | models |
---|---|
o200k_base | MaaS-4o |
cl100k_base | MaaS-4, MaaS-3.5-turbo, MaaS-embedding-ada-002, MaaS-embedding-3-small, MaaS-embedding-3-large |
p50k_base | Codex models, text-davinci-002, text-davinci-003 |
r50k_base (or gpt2) | MaaS-3 models like davinci |
无视觉的分词
对于没有视觉或多模态的模型,可以直接使用tiktoken计算令牌,编码方法可以在上面的编码表中找到。
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
如果您想了解更多关于分词过程的详细信息,可以参考 tiktoken文档。
具有视觉功能的分词
对于像MaaS-4-turbo或MaaS-4o这样具有视觉或多模态的模型,分词过程应该分为两部分:文本和图像。文本部分可以直接使用tiktoken进行分词,图像部分可以使用图像分词器进行分词。
视觉令牌计数: 图像输入也会被计量并以令牌进行计费,就像文本输入一样。给定图像的令牌成本由两个因素决定:其大小,以及每个image_url块上的detail选项。所有detail: low的图像每个都花费85个令牌。detail: high的图像首先会被缩放以适应2048 x 2048的正方形,同时保持它们的纵横比。然后,它们会被缩放,使得图像的最短边长为768px。最后,我们计算图像由多少个512px的正方形组成。每个这样的正方形花费170个令牌。最后总计还会再加上85个令牌。
以下是一些演示上述内容的示例。
-
一个1024 x 1024的正方形图像在detail: high模式下花费765个令牌 1024小于2048,所以没有初始的缩放。 最短的边是1024,所以我们将图像缩小到768 x 768。 需要4个512px的正方形瓷砖来表示图像,所以最终的令牌成本是170 * 4 + 85 = 765。
-
一个2048 x 4096的图像在detail: high模式下花费1105个令牌 我们将图像缩小到1024 x 2048,以适应2048的正方形。 最短的边是1024,所以我们进一步缩小到768 x 1536。 需要6个512px的瓷砖,所以最终的令牌成本是170 * 6 + 85 = 1105。
-
一个4096 x 8192的图像在detail: low模式下最多花费85个令牌 无论输入大小如何,低细节的图像都是固定的成本。
from math import ceil
from PIL import Image
import requests
from io import BytesIO
import base64
from token_calculation import num_tokens_from_messages
def base64_from_url(url):
return base64.b64encode(requests.get(url).content).decode()
def calculate_token_cost(image_url, detail_level='auto', detail_threshold=2048*2048):
# decide the image is url or base64 encoded image
if image_url.startswith("data:image"):
# convert base64 endcoded image to image
image_url = image_url.split(",")[1]
img = Image.open(BytesIO(base64.b64decode(image_url)))
else:
response = requests.get(image_url)
img = Image.open(BytesIO(response.content))
width, height = img.size
if detail_level == 'low':
return 85
if width > 2048 or height > 2048:
aspect_ratio = width / height
if aspect_ratio > 1:
width, height = 2048, int(2048 / aspect_ratio)
else:
width, height = int(2048 * aspect_ratio), 2048
if width >= height and height > 768:
width, height = int((768 / height) * width), 768
elif height > width and width > 768:
width, height = 768, int((768 / width) * height)
tiles_width = ceil(width / 512)
tiles_height = ceil(height / 512)
total_tokens = 85 + 170 * (tiles_width * tiles_height)
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "What's in this imagesdasds?"},
{
"type": "image_url",
"image_url": "IMAGE_URL or IMAGE_BASE64"
},
],
}
]
prompt_token = num_tokens_from_messages(messages=messages, model="gpt-4-32k-0613")
return total_tokens + prompt_token
# img = base64_from_url("https://zhuhq.oss-cn-beijing.aliyuncs.com/sdasd.jpeg")
# img = f'data:image/jpeg;base64,{img}'
# print(calculate_token_cost(img))
print(calculate_token_cost("https://zhuhq.oss-cn-beijing.aliyuncs.com/sdasd.jpeg"))
如果您想了解更多关于分词过程的详细信息,可以参考 tiktoken文档。