主题
Token 与上下文窗口:模型为什么会「忘事」
接大模型 API 的前端,最先撞上的两个词就是 token 和上下文窗口:账单按它算,报错也因它起。这篇把它们讲清楚——token 到底怎么切、中文为什么更贵、窗口是谁和谁共用的、以及模型「忘事」时你能查什么、改什么。
Token 是子词,不是字也不是词
模型不认识字符串,只认识整数 ID。分词器(tokenizer)负责把文本切成 token 再映射成 ID,常见算法是 BPE(字节对编码)。
- 切法不按语法:
unhappiness可能被切成un+happi+ness。 - 空格、标点、代码缩进都会被算进去,一个都跑不掉。
- 一句话:token 是模型眼里的最小拼装块,比字大、比词小,全凭训练时统计出来的词表决定。
换一家厂商,token 数就变了
同一个字符串,不同分词器切出的数量并不一样。
- GPT 系列用 tiktoken:
cl100k_base词表约 10 万,o200k_base约 20 万,对非英文更友好。 - Gemini 用 SentencePiece;Claude 的分词器官方未公开。
- 所以别拿一家的 token 数去套另一家的账,要准就调各家的计数接口。
ts
// 示意代码:前端本地数 token
import { getEncoding } from "js-tiktoken";
const enc = getEncoding("o200k_base");
const n = enc.encode("你好,前端").length; // token 数中文为什么更费 token
词表大多在英文语料上练成,常用汉字和词往往被拆得更碎。
- 经验值:英文 1 token ≈ 4 个字符 ≈ 0.75 个单词;中文 1 token ≈ 1 到 2 个汉字。
- 同样一段话,中文通常花掉比英文更多的 token,账单和窗口占用都更高。
- 代码和 JSON 也别忽略:缩进、引号、重复的字段名都会被逐块计入。
上下文窗口:输入和输出共用一个额度
- 上下文窗口是模型单次请求能同时「看到」的 token 上限,包含你的输入加模型生成的输出。
- 它不是「输入一份再加输出一份」,而是一个总池子:输入占得多,留给输出的就少。
- 请求里设的输出上限(max tokens)同样吃这份预算,加起来超了就报错。
上下文被谁吃掉了
窗口没满却已经开始「忘事」,通常是这些在抢位置:
- 系统提示词(system prompt)和工具、函数定义。
- 历史对话全文,每一轮都原样重发。
- 检索塞进来的文档(RAG)和长网页正文。
- 图片等多模态输入,以及你没注意的调试日志。
「忘事」是工程问题,不是玄学
模型没有记忆,也不会真「逐渐忘」;本质是应用层每次往 prompt 里拼了多少历史。
- 超出窗口时,要么请求直接失败,要么上游做了截断或摘要,把早期内容丢掉——你看到的就是「忘事」。
- 所以历史得在客户端管好:滚动窗口、对话摘要、把稳定内容交给缓存,都是常规手段。
- 和状态管理一个道理:别把整棵 store 每轮全量序列化再发出去。
怎么估算,以及超窗会怎样
- 粗估:英文可按「字符数 ÷ 4」;中文按「汉字数 × 1 到 2」。这只是量级,不是精确值。
- 要准就用厂商的计数接口,或本地 tiktoken;具体模型和参数以官方文档为准。
- 超窗两种表现:一是直接报错,例如 OpenAI 返回 400、
invalid_request_error,提示context_length_exceeded这类语义;二是静默截断,内容悄悄丢了。 - 工程上留余量:估算值离上限留出一截,再用响应里的 usage 字段回头校准。
警告
别把「字符数 ÷ 4」当万能公式,那是英文经验值,中文明显偏离,一不留神就超窗、超预算。也别以为所有厂商超窗都会报错——不少产品是静默截断,你以为历史都在,其实前面早被丢了。分词方式、词表大小、免费额度这类信息会变,以官方文档为准。