☰
大模型的 Token 到底怎么算?一篇讲清
2026/10/15 15:22:57 网站建设 项目流程

大模型的 Token 到底怎么算?一篇讲清

上个月我的 Agent 已经能跑通 demo,上线后账单直接翻了 5 倍。查下来问题很朴素——他每次都把完整对话历史无脑塞回去,平均单次请求带 4 万多 token。我在想:「我也不知道为什么这么贵,代码就是照着教程写的。」

这就是我想写这篇的原因。做 AI Agent 开发,大模型教程能带你跑通 demo,但决定你成本和稳定性的,永远是那 4 个底层概念。我们把它们一次讲透。

一、Token:计费的真实单位

先说最反直觉的一点:你花钱买的不是字数,是 token。

token 是分词器把文本切出来的片段。它不等于一个字,也不等于一个词。我用tiktoken(OpenAI 的公开分词库)实测了一组数据:

类型样本字符数token 数每字成本
中文大模型的上下文窗口不是越大越好15181.20
英文The context window of an LLM is not always better49110.22
代码def cosine_similarity(a, b): return a @ b41110.27

看第三行和第一行的差距:中文每字要 1.2 个 token,英文只要 0.22。同样 200 个词,英文 201 token,中文 600 token,差了近 3 倍。

再看更狠的。「上下文窗口」这 5 个字,被切成了 6 个 token:

'上' -> b'\xe4\xb8\x8a' '下' -> b'\xe4\xb8\x8b' '文' -> b'\xe6\x96\x87' '�' -> b'\xe7\xaa' ← 碎片 '�' -> b'\x97' ← 碎片 '口' -> b'\xe5\x8f\xa3'

窗这个字被劈成了两个半截 token。为什么?因为分词器用的是字节级 BPE,一个汉字在 UTF-8 里占 3 个字节,词表里没有「窗」这个完整条目,就只能拆。

这件事的实际影响:**你的 Prompt 越「口语化」、越堆大白话,token 越贵。**给文档做摘要、抽取、分类时,写精简的指令词比写长句子更省钱。

二、温度与采样:输出不是模型说了算

很多人以为大模型的输出是确定的——同样的输入,输出必然一样。这是幻觉之外的第二个幻觉。

模型内部给每个候选 token 打一个分数(叫 logits),然后按概率抽样。temperature这个参数,就是把 logits 除以一个系数:

  • T < 1:分布变尖锐,更倾向于选最高分,输出更稳
  • T = 0:退化成贪心,永远选最高分,完全确定
  • T > 1:分布变平,输出更多样,但更容易跑偏

我写了一段不调 API 的演示,直接拿一个手写概率分布看效果:

importmath,randomfromcollectionsimportCounterdefsoftmax(logits):"""数值稳定的 softmax:先减最大值,避免 exp 溢出"""m=max(logits)exps=[math.exp(x-m)forxinlogits]s=sum(exps)return[e/sforeinexps]deftemperature_scale(logits,temperature):"""温度就是给 logits 除一个系数,不改别的"""return[x/temperatureforxinlogits]defsample(probs,rng):"""按累积概率抽一个下标"""r=rng.random()acc=0.0fori,pinenumerate(probs):acc+=pifr<=acc:returnireturnlen(probs)-1tokens=["北京","上海","广州","深圳","杭州","南京"]base_logits=[3.0,2.4,1.2,1.1,0.4,0.3]# 假装是模型内部算出的分数forTin(0.0,0.5,1.0,2.0):ifT==0.0:probs=[1.0ifi==0else0.0foriinrange(len(tokens))]else:probs=softmax(temperature_scale(base_logits,T))# 关键:rng 必须放在循环外创建。# 若写成 sample(probs, random.Random(42)) 嵌在推导式里,# 每轮都会新建一个同种子的 rng,抽出来的永远是同一个数,概率全失真。rng=random.Random(42)counts=Counter(sample(probs,rng)for_inrange(2000))pct=[counts.get(i,0)/20foriinrange(len(tokens))]print(f"T={T}: "+" ".join(f"{t}:{p:.1f}%"fort,pinzip(tokens,pct)))

实测 2000 次采样的结果:

T=0.0: 北京:100.0% 上海:0.0% ...(完全确定) T=0.5: 北京:73.3% 上海:22.1% 广州:2.0% ... T=1.0: 北京:48.4% 上海:29.2% 广州:8.3% ... T=2.0: 北京:32.1% 上海:24.1% 广州:13.6% 深圳:13.1% 杭州:8.8% 南京:8.3%

规律很清楚:T 越低越集中,T 越高越分散。

top_p(核采样)是另一个正交的旋钮,它不管分布形状,只做截断——按概率从大到小累加,保留刚好到 p 的最小集合,其余直接置零:

deftop_p_filter(probs,p):idx=sorted(range(len(probs)),key=lambdai:-probs[i])kept,cum=[],0.0foriinidx:ifcum>=p:breakkept.append(i)cum+=probs[i]out=[0.0]*len(probs)foriinkept:out[i]=probs[i]s=sum(out)return[v/sforvinout]ifs>0elseout# 截断后必须重新归一化

实测:top_p=0.3只留北京;0.6留北京+上海;0.9留四个。上面那句「重新归一化」很关键,漏掉它,总概率不足 1,很多采样器会直接报错。

实践建议:做信息抽取、分类、格式转换这类要可复现的任务,温度给 0;做文案、起名、头脑风暴,温度往上调。评测场景尤其必须锁 0,否则你永远测不出模型改进没有。

三、上下文窗口:别把预算全花在历史

上下文窗口是模型一次能看进去的 token 总量。这个数字现在动辄 128K、200K,但窗口大,不等于塞满就好。

我算过一笔账,假设窗口 128K:

系统提示词 800 token █ 工具定义 3,000 token █ RAG 召回 12,000 token ███ 历史对话 40,000 token ████████████ 输出预留 4,000 token █ 合计 55,800 / 128,000,剩余 68,200 ✓

这里暴露了那个读者的典型问题:历史对话吃掉 40K,占了窗口的 31%,而真正需要模型回答的信息可能只有最后两轮。

除了贵,更麻烦的是准。塞满窗口会触发「注意力稀释」——信息越杂,模型对关键指令的遵循度越差。你会看到一个很迷惑的现象:把知识库全塞进去,答案反而不如只塞 5 条准。

知识库 B5 记忆系统那篇给了几个可直接用的经验值:

  • top_k=3–8 条:超过 8 条,模型会忽略其中大部分
  • 相似度阈值 =0.30–0.40:低于阈值就不注入,省 token 也减少干扰
  • 注入预算≤ context 的 10%:128K 窗口对应约 12.8K token

还有一条我特别想强调:召回后要按「最近更新」再排一次序。否则三条互相冲突的旧记忆会一起挤进 top-3,模型只能随机采信一个——这是幻觉的常见来源之一,而且极难排查。

四、KV Cache:延迟从哪来

前三个概念管成本,这个管延迟。

LLM 是自回归生成的:一次吐一个 token,每吐一个都要把前面的全看一遍。如果每次都从头算,生成 n 个 token 就是 O(n²)。KV Cache 的做法是把已经算过的 Key/Value 缓存下来,用空间换时间,把 O(n²) 压到 O(n)。

这就是为什么同样一个 Prompt:

  • 首 token 延迟(TTFT)慢 → 瓶颈在「读进去」,也就是 prefill 阶段
  • 后续每个 token 都慢→ 瓶颈在显存带宽或缓存没命中

但 KV Cache 是把双刃剑。知识库 B2_05 那篇讲得很直接:它既是加速的关键,也是显存的主要占用者,直接限制了你能开多大窗口、能并发多少请求。这也是我上面说「窗口大不等于该塞满」的底层原因。

调试时的实用做法:

  • 缩短输入 → 看 TTFT 是否下降,能判断是不是 prefill 瓶颈
  • 固定相同前缀(如系统提示词)→ 提高缓存命中率
  • 用流式输出 → 体感延迟大幅改善,用户不等全部生成完

五、常见坑清单

  • 坑 1:用字符数估算成本—— 表现:账单比预期高 2-3 倍,中文场景尤其明显。解法:上线前用len(enc.encode(text))实测,别用len(text)。
  • 坑 2:评测时没锁温度—— 表现:同一模型两次跑分差好几个点,你以为模型有提升,其实是随机波动。解法:temperature=0,且固定测试集版本。
  • 坑 3:只调 temperature 不调 top_p—— 表现:调低温度后,长尾内容里出现莫名其妙的话。解法:两个一起调,top_p管截断长尾。
  • 坑 4:无脑全量塞历史—— 表现:Agent 多轮对话后变慢变贵,答案质量还下降。解法:历史做摘要 + 只保留最近 N 轮原文。
  • 坑 5:截断后忘重新归一化—— 表现:top_p截断后采样器抛异常或概率和不足 1。解法:截断后除以剩余概率和。

六、小结

  1. Token 是计费单位,中文比英文贵 3 倍。学的时候:写 Prompt 尽量精简,中文长文先做切片再送。
  2. 温度决定输出的随机性。要可复现就锁 0,要创意就往上调。这条在做评测时是及格线。
  3. 上下文窗口要有预算思维。历史摘要 + RAG 只取 top 3-8 条,别拿窗口当仓库。
  4. KV Cache 决定延迟。首 token 慢看输入长度,逐 token 慢看显存和缓存命中。
  5. 各个厂商的Credits计费不固定Credits 没有固定的 Token 换算比例,1 个 Credits 不等于固定数量的 Token。Credits 是按模型分档抵扣系数动态计费的,单次消耗由模型类型、Token 用量、思考模式及工具调用等因素共同决定。有可能是 1 Credits = 1 Token / 倍率(如 x0.09)等

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询