为什么统计Token个数而非文字个数

为什么统计Token个数而非文字个数
改进方式是按照文字长度来截断历史对话不过字跟字又有所不同不如“猪”是个单字词而“鹦鹉”是个双字词。对于大模型来说“猪”和“鹦鹉”具有同等权重它们都占用一个Token也就是词元。所以更好的办法是统计Token数量而非统计文字数量。Token不是单个字、也不是单个字母而是大模型训练时固定好的“最小词块”。早期的AI库主要适配英文常用单词、词根、词缀直接就是一个Token。对于中文则没有完整常用中文预制词块大多只能拆成偏旁、笔画、字节片段。原因是传统的Token分词器采用BPE算法该算法属于字节级对子压缩它的底层是按UTF-8编码的字节来切分不是按中文语义切分。由于每个中文的UTF-8编码固定占用3个字节而BPE分词器是按字节流拆分、合并成词块因此3字节的中文很难刚好凑成1个Token。常用汉字还能凑成1个Token生僻字、复杂字基本拆成2个Token。可见传统的Token分词器极其违背常识竟然还能将1个中文拆成两个Token如此倒翻天罡敢情是歧视中文用户吧所以我们的国内开发者挺身而出开发了中文分词库能够依据习惯把中文句子切分为各个词语使得Token数量小于文字数量这才提高了中文大模型的效率。二、传统的Token数量统计方式传统的Token分词器依赖于tiktoken库在编写Python代码前要先在命令行执行下面的pip安装命令pip install tiktoken然后编写下面的Python分词测试代码import tiktoken text 猪和鹦鹉两种动物你更喜欢哪个 len_text len(text) print(文字长度为, len_text) encoder tiktoken.encoding_for_model(gpt-3.5-turbo) len_token len(encoder.encode(text)) print(token长度为, len_token)运行上面的Python代码输出日志结果如下文字长度为 16 token长度为 25总共16个字符的中文使用传统的Token分词器竟然统计得到25个token不合理简直太不合理了。三、中文Token数量的统计方式中文的Token分词器依赖于jieba库在编写Python代码前要先在命令行执行下面pip安装命令pip install jieba然后编写下面的Python分词测试代码import jieba text 猪和鹦鹉两种动物你更喜欢哪个 len_text len(text) print(文字长度为, len_text) words jieba.lcut(text) words [w for w in words if w.strip()] len_token len(words) print(token长度为, len_token) print(words)运行上面的Python代码输出日志结果如下文字长度为 16 token长度为 11 [猪, 和, 鹦鹉, 两种, 动物, , 你, 更, 喜欢, 哪个, ]总共16个字符的中文中文传统的Token分词器精简后统计得到11个Token可见相较传统分词器大幅瘦身。四、根据Token数量精简上下文接下来将以Python代码演示如何按照Token数量来截断早期的上下文即问答内容。下面是只保留100个Token的Python代码例子import jieba class ContextManager: def __init__(self): # 对话历史上下文 self.context [] # 上下文的最大Token数量设小一点方便看到截断效果 self.MAX_TOKENS 50 # 计算单条消息的Token数 def count_tokens(self, text): words jieba.lcut(text) words [w for w in words if w.strip()] return len(words) # 计算整个上下文总Token def total_context_tokens(self): return sum(self.count_tokens(msg[content]) for msg in self.context) # 核心按Token自动截断删掉最早的直到不超限 def truncate_context(self): while self.total_context_tokens() self.MAX_TOKENS and len(self.context) 0: removed_msg self.context.pop(0) # 删除最早一条 print(f[截断] 删掉最早对话{removed_msg[content][:20]}...) # 添加新消息 → 自动截断 → 返回完整上下文 def add_message(self, role, content): self.context.append({role: role, content: content}) self.truncate_context() # 截断 return self.context # 拼接成给模型的完整Prompt def get_full_prompt(self): prompt for msg in self.context: prompt f{msg[role]}{msg[content]}\n return prompt # 模拟多轮对话测试 if __name__ __main__: ai ContextManager() # 连续发长文本观察自动截断 chat_records [ (user, 推荐一本关于历史的书要内容详细、适合入门), (ai, 推荐《明朝那些事儿》通俗好读), (user, 有没有国外历史的比如欧洲史), (ai, 推荐《欧洲通史上下两千年》), (user, 太长了有没有更短的我只想快速了解核心脉络), (ai, 那看《极简欧洲史》精简版100页搞定), (user, 好再推荐一本类似风格的美国历史书), ] print( 开始对话 ) for role, content in chat_records: ai.add_message(role, content) print(f\n[{role}] {content}) print(f当前总Token{ai.total_context_tokens()} / {ai.MAX_TOKENS}) print(--- 当前保留的上下文 ---) print(ai.get_full_prompt())运行上面的Python代码观察到最后一轮的输出日志[user] 好再推荐一本类似风格的美国历史书 当前总Token44 / 50 --- 当前保留的上下文 --- ai推荐《欧洲通史上下两千年》 user太长了有没有更短的我只想快速了解核心脉络 ai那看《极简欧洲史》精简版100页搞定 user好再推荐一本类似风格的美国历史书