跳转至

分词器

介绍

大模型通常使用令牌处理文本,这些令牌是在一组文本中找到的常见字符序列。模型学习理解这些令牌之间的统计关系,并擅长产生令牌序列中的下一个令牌。

需要注意的是,具体的分词过程在模型之间有所不同。像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文档