Token分词tiktokenLLM成本优化OpenAIClaude

什么是 Token,以及如何精确统计:分词原理、计数方法与成本优化

2026-06-305 Min Read

什么是 Token,以及如何精确统计:分词原理、计数方法与成本优化

做 AI 应用做到一定阶段,团队几乎都会撞上同一类问题:

  • 调一次大模型到底花了多少 token?账单为什么比预期高?
  • 同样一句话,为什么中文折算的 token 比英文多那么多?
  • 上下文窗口说 128K,我到底能塞多少字?超了会怎样?
  • 想在调用前预估成本,到底该用什么工具算 token?
  • 不同模型(GPT、Claude、Llama)的 token 是一回事吗?数字能通用吗?
  • "1 个 token ≈ 4 个字符" 这种说法,到底准不准?

这些问题之所以反复出现,不是因为 token 这个概念本身多复杂,而是因为很多文章只讲"1 个 token 大概是 3/4 个英文单词"这种粗略定义,不讲分词原理,不讲各家分词器的差异,也不讲工程上到底该怎么精确计数和省钱。

这篇文章不打算停留在"token 是文本片段"这种百科词条层面,而是按真实工程视角把 token 讲清楚:

  • token 到底是什么,模型是怎么把文本切成 token 的
  • 为什么 OpenAI、Anthropic、Meta 的 token 彼此不通用
  • 如何用 tiktoken、厂商 API、开源工具精确统计 token
  • 中文和英文的 token 效率为什么差很多,这对成本意味着什么
  • token 和计费、上下文窗口、缓存的关系
  • 在真实 AI 应用里,应该如何管理 token 预算、避免踩坑

如果你是做 LLM 应用、RAG、智能客服或 AI 中台的后端工程师,这篇文章应该能帮你建立一套从"理解 token"到"精确统计"再到"成本优化"的完整判断框架。


先立边界:Token 不是字符,也不是词

这是最值得先讲清的一段。很多成本估算出错,不是因为算错了,而是从一开始就把 token 和"字"或"词"搞混了。

三种"文本单位"的边界

单位 定义 例子("hello world") 谁在用
字符(Character) 一个字母/汉字算一个 h,e,l,l,o, ,w,o,r,l,d = 11 个 传统编程、len()
词(Word) 按空格/标点切分的单词 hello, world = 2 个 传统 NLP、分词统计
Token 模型分词器切出的子词单元 hello, world = 2 个(常见情况) 大模型、计费、上下文窗口

一句话概括:

  • 字符是给人看的长度单位。
  • 词是给传统 NLP 用的语义单位。
  • token 是给大模型用的、计费和上下文窗口都认的"硬通货"。

这三者数量接近,但绝不相等。最经典也最危险的错误,就是用 len(text)(字符数)去估算 token,然后据此做预算和报价。

为什么不能直接用字符数估算

以英文为例,经验规则是:

  • 1 个 token ≈ 4 个字符 ≈ 0.75 个英文单词

但这是"平均",不是"保证"。常见词(theing)经常被压成 1 个 token,而生僻词、专有名词、URL、代码反而会被拆成好几个 token。中文的情况更极端,后面会单独展开。

边界先清楚,token 才不会越算越乱。


一张总览表先建立认知

概念 核心问题 单位特征 擅长什么 不擅长什么
Token 模型如何计量文本 子词(subword),非字符非词 计费、上下文窗口、限流 直接换算成"字数/词数"
分词器(Tokenizer) 文本 ↔ token id 的转换 每家模型一套词表与算法 离线、快速、可复现 跨模型通用
BPE 如何从语料学出词表 高频字节对合并 兼顾频率与覆盖 处理罕见词/多语言需扩词表
计数(Counting) 调用前精确知道 token 数 tiktoken / 厂商 API / 估算 成本预估、截断、配额 只能针对特定模型精确
上下文窗口 一次能塞多少 token 输入 + 输出 ≤ 窗口 多轮对话、长文档 无限长记忆(仍需截断/检索)

Token 是什么:从文本到模型输入的最小单位

Token,中文常译为"词元"或"标记",是大语言模型处理文本时的最小单位。

它既不是一个完整的词,也不是单个字符,而是介于两者之间的"子词片段"(subword)。模型眼里没有文字,只有一串 token id(整数)。一段文本进模型前,必须先被分词器切成 token,再查表转成 id。

典型代表(分词实现)包括:

  • OpenAI 的 tiktoken(GPT 系列)
  • Anthropic Claude 的自有分词器
  • HuggingFace tokenizers / Google SentencePiece(Llama、Mistral 等开源模型)

它的核心不只是"把文本切碎",而是:

  • 按子词切分,让高频词省 token、低频词不丢信息
  • 词表大小固定(几万到几十万),任何文本都能被编码
  • 切分方式由训练语料统计学出来,不是人手定的规则
  • 同一个词在不同模型里,token 数量可能完全不同

它解决的根问题是什么

在语言模型里,有些需求是硬性的:

  • 词表必须有限,但又要能表示无限多的文本
  • 常见词不能浪费 token(the 不该拆成三个字母)
  • 罕见词、错别字、生僻字也要能编码,不能"认不出"
  • 多语言、代码、emoji 都要覆盖
  • 切分必须可复现、可逆(能从 token 还原回文本)

这类问题的共同特征是:

  • 要兼顾频率(高频短、低频长)
  • 要兼顾覆盖(不能有"无法表示"的文本)
  • 确定性(同一段文本,同一家模型永远切成同样的 token)

token 和分词器就是为这类问题设计的。

BPE:决定 token 怎么切的核心算法

绝大多数现代分词器(包括 GPT、Claude、Llama)的底层都是 Byte Pair Encoding(BPE,字节对编码)

BPE 的思路很朴素:

  1. 一开始,把文本拆成最小单位(字节或字符)。
  2. 统计语料里出现频率最高的相邻一对,把它们合并成一个新 token。
  3. 不断重复第 2 步,直到词表达到目标大小。

结果是:高频组合(ingtionhttp)被合并成单个 token,低频或罕见内容退回成更细的片段甚至单字节。这就是为什么"1 个 token ≈ 4 个字符"是平均值——常见片段远低于 4 字符/token,罕见片段远高于。

下面用 OpenAI 的 tiktoken 实际看看一段文本被切成了什么:

import tiktoken

# gpt-4o 使用 o200k_base 词表;gpt-4 / gpt-3.5 用 cl100k_base
enc = tiktoken.encoding_for_model("gpt-4o")

text = "Hello, world! 什么"

token_ids = enc.encode(text)
print("token 数:", len(token_ids))
print("token id 序列:", token_ids)
print("每个 token 还原回的文本:")
for tid in token_ids:
    print(repr(enc.decode([tid])))

运行后你会看到类似这样的结果:

token 数: 7
token id 序列: [13225, 11, 5905, 0, 24402, 104421, 104421]
每个 token 还原回的文本:
'Hello'
','
 'world'
'!'
'什'
'么'
'么'   # (不同模型/版本切法略有差异,以实际运行为准)

这个例子的价值在于:

  • Hello world! 这些常见片段是 1 个 token
  • 中文"什么"被拆成了多个 token——这就是后面要讲的"中文更贵"的根源
  • token id 是整数,模型实际处理的就是这串 id
  • encode / decode 是可逆的:enc.decode(enc.encode(text)) == text

为什么每个模型的 Token 不一样:分词器不统一

这是最容易踩的坑:不同模型的 token 彼此不通用。

同一段文本,在 GPT-4o、Claude、Llama 里切成多少个 token,数字可能差不少。原因不是谁"更先进",而是每家在训练模型时,用各自的语料、各自的目标词表大小,学出了各自的 BPE 词表。

三大主流分词器对比

分词器 代表模型 词表/编码 是否开源可离线
tiktoken(BPE) GPT-4o、GPT-4、GPT-3.5 o200k_basecl100k_basep50k_base 是(Python/Rust 库)
Claude 自有分词器 Claude 全系列 未公开词表 否(仅通过 API 计数)
SentencePiece / tokenizers Llama、Mistral、Qwen 等 各模型自带 .model / tokenizer 文件 是(需加载对应模型词表)

一句话概括:

  • OpenAI 的 token 用 tiktoken 离线就能精确算。
  • Claude 的 token 只能用官方 count_tokens 接口精确算。
  • 开源模型的 token 要加载它自己的 tokenizer 文件才算得准。

同一段文本,三家切法不同

import tiktoken

text = "人工智能(AI)正在改变软件开发"

# 仅以 OpenAI 的两套词表举例,体会"同公司不同模型"都不一样
enc_new = tiktoken.encoding_for_model("gpt-4o")      # o200k_base
enc_old = tiktoken.encoding_for_model("gpt-4")       # cl100k_base

print("gpt-4o :", len(enc_new.encode(text)), "tokens")
print("gpt-4  :", len(enc_old.encode(text)), "tokens")

连 OpenAI 自家两代模型的词表都不一样(GPT-4o 的 o200k_base 对多语言做了优化,中文比 cl100k_base 更省),跨厂商差异只会更大。

经验上的一句直白话:脱离具体模型谈 token 数,等于脱离汇率谈价格。 计数必须对着你真正要调用的那个模型来算。


如何精确统计 Token:四种方法

这是工程上最实用的一节。"如何统计"不是一句话能答完的,要分场景选方法。

方法一:tiktoken(OpenAI 模型,离线、精确、最快)

如果你调的是 OpenAI 模型,tiktoken 是首选:纯本地计算,不需要发请求,毫秒级,结果和官方计费一致。

import tiktoken

enc = tiktoken.encoding_for_model("gpt-4o")

def count_tokens(text: str, model: str = "gpt-4o") -> int:
    enc = tiktoken.encoding_for_model(model)
    return len(enc.encode(text))

# 但真实计费不是"把所有文本拼一起数",而是按 messages 结构数
# 下面是 OpenAI 官方推荐的对话 token 估算(含每条消息的固定开销)
def count_chat_tokens(messages: list[dict], model: str = "gpt-4o") -> int:
    enc = tiktoken.encoding_for_model(model)
    # 不同模型每条消息的模板开销不同,这里用 gpt-4o 近似值
    num = 0
    for msg in messages:
        num += 4  # 每条消息固定结构开销(role/分隔符等)
        for key in ("role", "content", "name"):
            if key in msg and msg[key]:
                num += len(enc.encode(msg[key]))
    num += 2  # priming
    return num

messages = [
    {"role": "system", "content": "你是一个简洁的助手。"},
    {"role": "user", "content": "什么是 token?"},
]
print("对话 input_tokens ≈", count_chat_tokens(messages))

这段代码的价值在于:

  • encoding_for_model 会自动选对词表,不要手动猜 cl100k 还是 o200k
  • 真实计费按 messages 结构算,不是把字符串拼起来数——每条消息有固定的模板开销
  • 这个估法和官方有少量出入(几 token),但做预算和截断足够稳

方法二:厂商 count_tokens 接口(Claude,精确)

调 Claude 不能用 tiktoken,因为它的词表没公开。Anthropic 提供了专门的计数接口,结果和计费一致:

curl https://api.anthropic.com/v1/messages/count_tokens \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-sonnet-4-5",
    "messages": [
      {"role": "user", "content": "什么是 token?如何统计?"}
    ]
  }'

返回:

{
  "input_tokens": 12
}

注意:

  • 这个接口不计费,专门用来预估
  • 它考虑了 messages 结构、system prompt、tools 定义的真实开销,比任何手算都准
  • 代价是要发一次网络请求,不适合在超高频路径上逐条调

方法三:HuggingFace tokenizers / SentencePiece(开源模型)

调 Llama、Mistral、Qwen 等开源模型时,要加载它们各自的 tokenizer 文件:

from transformers import AutoTokenizer

# 以某开源模型为例,加载它自带的 tokenizer
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Meta-Llama-3-8B")

text = "什么是 token?如何统计?"
print("token 数:", len(tokenizer.encode(text)))
print("token id:", tokenizer.encode(text))

要点:

  • 必须用目标模型对应的 tokenizer,用错模型就是错数
  • AutoTokenizer 会自动从 Hub 拉对应文件,离线场景需提前下载
  • 这是本地部署开源模型时最权威的计数方式

方法四:估算规则(快速近似,不依赖模型)

很多时候你不需要精确,只需要一个量级(比如做配额、做产品报价)。这时用经验规则最快:

文本类型 经验规则 适用场景
英文 1 token ≈ 4 字符 ≈ 0.75 词 快速报价、粗略配额
中文 1 汉字 ≈ 0.6 ~ 1.5 token(差异大) 仅做上限预估,别拿来做精确账单
代码 比自然语言更费 token(符号多) 留足余量
结构化(JSON/工具定义) 额外加模板开销 按 messages 结构估
# 一个保守的、不依赖任何库的中文上限估算
def estimate_tokens_naive(text: str) -> int:
    # 中文按 1.5 倍向上取整做上限,英文按 4 字符
    chinese = sum(1 for c in text if "一" <= c <= "鿿")
    other = len(text) - chinese
    return int(chinese * 1.5 + other / 4) + 1

这种估法的价值是零依赖、零延迟,但一定要记住它只是上限,不能用它给客户开账单。

四种方法对比

方法 精确度 是否离线 速度 适用
tiktoken 精确(OpenAI) 极快 OpenAI 模型计费/截断
count_tokens API 精确(Claude) 否(需请求) Claude 模型预估
tokenizers/SentencePiece 精确(对应模型) 是(需词表) 开源模型本地部署
估算规则 近似(误差大) 极快 配额、报价、量级判断

别让"估算"去干"计费"的活,也别让"调 API 计数"去卡每一条请求的性能。 场景选对方法,这是这一节真正想说的。


中文与英文的 Token 效率差异

这是国内团队做 LLM 应用最容易"花冤枉钱"的地方。

同样意思,中文就是更费 token

import tiktoken
enc = tiktoken.encoding_for_model("gpt-4o")

def count(text: str) -> int:
    return len(enc.encode(text))

en = "The quick brown fox jumps over the lazy dog."
zh = "敏捷的棕色狐狸跳过了那只懒狗。"

print("英文:", count(en), "tokens /", len(en), "字符")
print("中文:", count(zh), "tokens /", len(zh), "字符")

运行后你会清楚地看到:

  • 英文 9 个词,平均下来"不到 1 token/词"
  • 中文十来个汉字,几乎每个汉字要 1 个甚至更多 token
维度 英文 中文
每 token 大约覆盖 ~4 字符 / ~0.75 词 0.71.5 字符(更费)
原因 分词器在英文语料上训练,英文片段高频 汉字组合在词表里覆盖不如英文密集
GPT-4o 后是否改善 有改善(o200k_base 优化了多语言),但仍不如英文
对成本的影响 基线 同样内容,中文输入/输出 token 更多 → 更贵

为什么这件事重要

这对工程和产品都有直接影响:

  • 成本:同一段产品文案,中文版 token 更多,长期调用成本更高
  • 上下文窗口:同样 128K 窗口,能塞的中文"篇幅"比英文小
  • GEO / AI 可见度:大模型是按 token 消费你的网页内容的,中文页面在同等窗口下能被"读完"的有效内容更少,结构清晰、信息密度高的内容更占优势
  • 截断策略:中文场景下,按字符数截断和按 token 截断的差距更大,必须按 token 截

一句话:中文做 LLM 应用,token 预算要按"更费"来估,留足余量。


Token 与计费:输入、输出、缓存

理解了 token,才能看懂账单。绝大多数大模型按 token 计费,而且输入和输出价格不同。

计费的三条基本规则

  • 输入 token(input):你发给模型的所有内容(system + 历史 + 用户输入 + 工具定义)。
  • 输出 token(output):模型生成的内容。输出通常比输入贵 3~5 倍。
  • 总费用 = 输入 token × 输入单价 + 输出 token × 输出单价。

示意价格(仅为说明结构,实际以各厂商官方定价为准):

模型(示意) 输入($/百万 token) 输出($/百万 token) 缓存命中输入
旗舰大模型 A 23 1015 ~0.3(约 1 折)
中端模型 B 0.51 24 ~0.1
小模型 C 0.10.3 0.40.6 更低

这张表想强调的不是具体数字,而是三件事:

  • 输出比输入贵很多——让模型少废话,是最直接的成本优化
  • 缓存命中极便宜——重复的长 prompt 走缓存,能省一个数量级
  • 不同档位模型价差巨大——能用小模型就别用旗舰

max_tokens:给输出戴上"紧箍咒"

调用时用 max_tokens 限制最大输出长度,是防止"成本失控"的第一道闸门:

from anthropic import Anthropic

client = Anthropic()

resp = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=512,          # 输出最多 512 token,硬上限
    messages=[{"role": "user", "content": "用一句话解释 token"}],
)
print(resp.usage)            # usage 里有 input_tokens / output_tokens
  • max_tokens输出上限,不影响输入计费
  • usage.input_tokensusage.output_tokens 是事后对账的权威数字
  • 生产环境永远要设 max_tokens,否则一个"话痨"回复可能把单次成本顶上去

Prompt 缓存:省最多的一招

如果你的请求里有大段重复内容(长 system prompt、固定知识库片段、few-shot 示例),用 prompt caching 能让这部分以极低价格命中:

# Anthropic:标记可缓存的块
curl https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-sonnet-4-5",
    "max_tokens": 256,
    "system": [
      {"type": "text", "text": "你是一个客服助手。...(超长固定人设和知识)...", "cache_control": {"type": "ephemeral"}}
    ],
    "messages": [{"role": "user", "content": "怎么退款?"}]
  }'

经验法则:

  • 固定、重复、够长的内容才值得缓存(一般要求达到最小缓存 token 阈值)
  • 缓存有写入成本(略高于普通输入),但只要被多次复用就稳赚
  • 高频客服、固定 prompt 的批处理,是缓存收益最大的场景

Token 与上下文窗口

上下文窗口(context window) 是模型一次能"看"到的 token 总量上限。关键认知是:

  • 窗口 = 输入 token + 输出 token,两者共享同一个额度
  • 窗口大不等于"可以随便塞",塞得越满,延迟越高、成本越高、还可能"中间内容被忽略"
  • 超出窗口,要么报错,要么被静默截断,取决于 SDK 和配置

超长历史怎么办:按 token 截断

多轮对话最常见的问题就是"聊着聊着历史超了"。正确做法是按 token 预算滚动裁剪,而不是按条数:

import tiktoken

enc = tiktoken.encoding_for_model("gpt-4o")

def token_of(text: str) -> int:
    return len(enc.encode(text))

def trim_history(messages: list[dict], budget: int, keep_system: bool = True) -> list[dict]:
    """按 token 预算从最早的非 system 消息开始丢弃,直到总量 <= budget。"""
    system = [m for m in messages if m["role"] == "system"] if keep_system else []
    rest = [m for m in messages if m["role"] != "system"]

    used = sum(token_of(m["content"]) for m in system + rest)
    # 从最旧的历史开始丢(rest 里靠前的多轮对话)
    while used > budget and rest:
        dropped = rest.pop(0)
        used -= token_of(dropped["content"])
    return system + rest

messages = [
    {"role": "system", "content": "你是助手。"},
    {"role": "user", "content": "第1轮长文本..."},
    {"role": "assistant", "content": "回复1..."},
    {"role": "user", "content": "第2轮..."},
]
print(trim_history(messages, budget=200))

这个写法想表达三件事:

  • 永远保留 system(人设/规则不能被裁掉)
  • 按 token 裁,不按条数裁——中文按条数裁误差极大
  • 丢最旧的,保留最近上下文,这是对话场景的标准策略

更成熟的系统会用摘要压缩或**向量检索(RAG)**替代简单截断,但前提是"简单截断已经扛不住"——别一上来就过度设计。


如何在真实 AI 应用里管理 Token

把前面的点组合起来,就是一个真实应用的 token 管理链路。

一张分工对照表

环节 目标 推荐方法 不该做什么
入参预估 调用前估输入 token tiktoken / count_tokens 用字符数估后直接报价
预算控制 防止超窗口、超成本 按 token 截断历史 按消息条数截断
输出控制 防止输出失控 max_tokens 不设上限
成本复用 重复 prompt 省钱 prompt caching 对一次性短 prompt 也缓存
事后对账 真实成本 usage.input/output_tokens 用预估数字当账单
模型选择 同质量降本 小模型优先 所有任务都上旗舰

什么时候精确计数,什么时候估算

  • 精确计数:调费率高、面向客户报价、做硬性配额限制、截断临界点。
  • 估算:内部粗略限流、产品早期量级判断、非计费路径。

判断标准很简单:这个数字会不会直接变成钱或直接决定用户体验?会,就精确;不会,就估算。


实战架构:一个聊天应用的 Token 预算链路

技术分工

  • 入口服务:接收请求,拼装 messages
  • 计数模块:用 tiktoken(OpenAI)或 count_tokens(Claude)算输入 token
  • 预算控制器:超预算则截断历史或触发摘要
  • 缓存层:固定 prompt 走 prompt caching
  • 调用层:带 max_tokens 调用模型
  • 对账模块:从 usage 取真实 token 写入计费表

架构示意

flowchart LR
    A[用户输入] --> B[拼装 messages]
    B --> C[计数 input_tokens]
    C --> D{是否超预算?}
    D -- 是 --> E[截断/摘要历史]
    E --> B
    D -- 否 --> F[固定 prompt 走缓存]
    F --> G[调用模型 max_tokens]
    G --> H[输出 output_tokens]
    H --> I[usage 对账写计费表]

链路说明

  1. 用户发送一条消息,入口服务把 system、历史、当前输入拼成 messages。
  2. 计数模块按目标模型算出 input_tokens。
  3. 预算控制器判断是否超出上下文预算。
  4. 若超预算,截断或摘要最旧的历史,回到第 2 步重新计数。
  5. 固定的长 prompt 走 prompt 缓存,命中则按缓存价计费。
  6. 调用模型,带上 max_tokens 限制输出。
  7. 模型返回,usage.output_tokens 即输出 token。
  8. 对账模块用 usage 的真实数字写入计费表,与预估核对。

这条链路的关键在于:

  • 计数在调用前,截断在调用前
  • 输出有上限,成本有缓存
  • 对账用真实 usage,不信任预估

职责清晰之后,token 相关的线上事故会大幅下降。


同一句请求,Token 是怎么算出来的

以"用户问了一句话,模型回答"这个动作为例,看 token 到底由哪几部分组成。

1. System prompt:固定人设和规则

你是一个简洁的技术助手,回答不超过 3 句话。

它通常是重复、固定的——最适合走缓存。

2. Tools / 函数定义:容易被忽略的开销

[
  {"name": "search", "description": "搜索知识库", "input_schema": {"type": "object", "properties": {"q": {"type": "string"}}}}
]

工具定义算进 input_tokens,而且 JSON 结构比自然语言更费 token。工具越多,固定开销越大。

3. 历史对话:随轮次线性增长

user: 什么是 token?
assistant: token 是大模型处理文本的最小单位...
user: 那怎么统计?

多轮对话的 input 会越滚越大,这是要截断/检索的根本原因。

4. 当前用户输入:通常占比最小

中文和英文哪个更费 token?

5. 模型输出:按输出价计费

中文更费 token,因为分词器对汉字的覆盖不如英文密集...

看清这五部分之后,很多"为什么账单这么高"的疑问会自然消失——往往是 system + tools + 历史 占了大头,而不是用户那句输入。


常见误区集中澄清

误区一:1 个 token 永远等于 4 个字符

不是。这是英文的平均值,中文、代码、JSON 都不是这个比例,而且不同模型差异很大。

误区二:不同模型的 token 数可以通用

不可以。GPT、Claude、Llama 各有各的词表,同一段文本 token 数不同,计数必须对着目标模型来

误区三:用 len(text) 字符数估算就够准

对英文粗略够用,对中文误差极大。凡是涉及计费和截断,都不能用字符数。

误区四:上下文窗口 128K,就是能放 128K 字符

不是。128K 是 token,且是输入 + 输出共享。中文实际能放的篇幅远小于 128K 字符。

误区五:输出 token 和输入 token 一个价

不是。输出通常贵 3~5 倍。让模型少废话,比压缩输入更省钱。

误区六:缓存只省一点点

错。缓存命中 input 可低到一折左右。对固定长 prompt 的高频场景,缓存是收益最大的一招。

误区七:token 优化做得越细越好

大多数时候正好相反。过度优化(逐条精确计数、激进压缩、到处缓存)会增加复杂度和延迟,收益却递减。先把 max_tokens、按 token 截断、固定 prompt 缓存这三件事做对,再谈精细优化。


反过度设计:Token 优化别走火入魔

这一段必须讲。很多团队不是 token 管错了,而是"优化过度"。

一个实用的反过度设计检查表

检查项 应该问自己的问题
计数是否必要 这个路径真的需要精确计数,还是估算就够?
缓存是否值得 这段 prompt 够长、够固定、会被复用吗?达不到阈值就是负收益
截断是否过早 是真的超窗口了,还是只是"担心超"?
模型是否选大 这个任务真的需要旗舰模型吗?小模型够不够?
是否引入双写 自己维护一套 token 计数,会不会和官方 usage 对不上?
优化收益是否大于复杂度 省的钱,够不够覆盖多写的代码和排查成本?

几条经验法则

  • 如果一个需求只需要量级判断,就先用估算规则,别急着全链路接 tiktoken
  • 如果一个 prompt 不够长、不复用,就先别开缓存,写入成本可能吃掉收益。
  • 如果只是怕超窗口,就先做朴素的按 token 截断,别一上来上摘要+向量检索。
  • 如果是高频调用,就先换小模型 + 开缓存,这比任何"提示词极限压缩"都有效。

token 优化最难的不是"知道有哪些招",而是知道什么时候不要用那些招。


选型速查表

优先用 tiktoken 的场景

  • 调 OpenAI 模型
  • 需要离线、毫秒级计数
  • 做输入预算、历史截断

优先用厂商 count_tokens 接口的场景

  • 调 Claude(词表未公开)
  • 需要和官方计费一致的精确预估
  • 计数频率不高,能接受一次网络请求

优先用 tokenizers / SentencePiece 的场景

  • 本地部署开源模型(Llama、Mistral、Qwen 等)
  • 需要离线且对应该模型词表

优先用估算规则的场景

  • 内部限流、配额量级判断
  • 产品早期报价、不直接计费
  • 零依赖、零延迟的快速判断

总结

如果把整篇文章压缩成几句话,结论并不复杂:

  • Token 是大模型处理文本的最小单位,按子词切分,不是字符也不是词。
  • 分词器 每家一套,OpenAI、Claude、Llama 的 token 彼此不通用。
  • tiktoken 是 OpenAI 模型离线精确计数的首选。
  • count_tokens 接口 是 Claude 精确计数的唯一权威方式。
  • 中文比英文更费 token,成本和窗口都要按"更费"估。
  • 输出比输入贵max_tokens 和 prompt caching 是最实用的两把刀。
  • 上下文窗口是输入 + 输出共享,超了要按 token 截断,不是按条数。

真正成熟的 token 管理,不是把每个请求都精确到个位数,也不是把提示词压缩到极致。

真正成熟的做法是:

  • 计数场景分得清(精确 vs 估算)
  • 成本闸门守得住(max_tokens + 缓存)
  • 截断按 token、对账用 usage
  • 不为边际收益过度设计

做到这些,token 相关的成本和稳定性通常就不会失控。