用 Java 接入大模型(九):token 计数与上下文预算

简介: 本文详解大模型中 token 的精准计算与预算管理:为何必须自主统计、估算与精确计数的分工策略、中文/代码的特殊偏差、如何实测专属系数、上下文窗口的倒算公式、超长截断的5条实操规则,以及流式调用中 usage 的正确获取方式。掌握它,才能让成本可控、行为可预测。

前八篇里,token 这个词反复出现:max_tokens 会预扣额度、重试会放大成本、上下文太长会导致回答退化。这一篇专门把这个数讲清楚——怎么算准它,以及预算不够的时候该砍哪一段。


一、为什么必须自己数

大多数人是等到账单超出预期,才开始关心 token。但有几件事,不数就一定会踩:

输入成本会随对话轮次递增。 第五篇讲过,多轮对话的本质是每次把历史重发一遍。也就是说第一轮的输入是一句话,第十轮的输入是九轮对话加一句话。输出长度基本稳定,输入却在不断膨胀。 长对话里,输入成本反超输出成本是常态,而不是例外。

max_tokens 的预扣会造成误解。 第三篇提过:很多厂商按你声明的 max_tokens 做额度预扣,而不是按实际用量。你写 max_tokens = 8000,即使这次只生成 50 个 token,也可能先占 8000 的额度。高并发下就会出现"明明没发几个请求,却报额度不足"——原因是你自己把额度占住了。

上下文超限的表现不一致。 有的实现直接报错,有的悄悄从中间截掉一段。后者最麻烦:请求成功了,但模型看到的历史缺了一块,回答莫名其妙。这类问题只能靠主动控制长度来避免,不能等着报错。

所以数 token 不是为了省钱,它是"让行为可预测"的前提。


二、估算还是精确:两条路各有代价

估算:按字符数乘一个系数。中文大致按字符数估,英文按 4 字符约 1 token,代码介于两者之间。

优点是快——纯本地计算,零开销,可以放在请求入口做粗筛。

缺点是对中文、代码、混合文本的偏差很大,而且是系统性偏移,不是随机误差。这意味着它不会"平均下来差不多",而会稳定地偏某个方向,导致你按估算留的余量在某些场景下根本不够。

精确:用厂商提供的计数接口或 SDK 来算。

优点是准。缺点是多一次调用开销(有网络往返),高频场景下这个开销不能忽略。

务实做法是分场景用:

  • 入口处粗筛用估算——判断"这个请求大概是不是太大了",快速拒绝明显超限的输入;
  • 真正决定截断位置时用精确值——这里估错一点,就可能导致请求被上游拒绝,或者截多了丢信息;
  • 记录成本时用接口返回的 usage(真实值),不要用估算。

一句话:估算用来快速判断,精确值用来做决定,usage 用来记账。


三、中文和代码为什么特别难估

这是本文最需要动手验证的一段,因为"偏差系数"这件事没有通用答案。

几类差异的来源:

  • 中文:一个汉字常常不是 1 个 token。常用字可能 1 个 token,生僻字、专业术语、专有名词可能被切成 2~3 个。所以"字数乘 1"会低估。
  • 代码:缩进、括号、运算符、下划线命名,切分方式极其不规律。一个长变量名可能被切成好几段,一段 JSON 可能比同样字符数的自然语言多出不少 token。
  • 标点与空白:换行、缩进、连续空格都可能单独占 token。
  • Emoji 与特殊符号:往往占多个 token,用字符数估会严重低估。

这些差异加起来,会让同一个"字符数"在不同内容上对应完全不同的 token 数。 所以下面这个方法值得真跑一次。


四、测出你自己的偏差系数

拿三段内容各测一次,对比估算值与精确值:

  • 一段纯中文(比如 500 字的产品说明)
  • 一段代码(比如 200 行的 Java 文件)
  • 一段混合文本(中文里夹杂英文术语和代码片段)

精确值用厂商的计数接口拿。以通义千问的 OpenAI 兼容接口为例:

curl -s https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen3.8-max",
    "max_tokens": 1,
    "messages": [{"role": "user", "content": "<把你的一段测试文本放这里>"}]
  }'

把返回体里的 usage.prompt_tokens 记下来,就是这段文本的精确输入 token 数。

(说明:官方也提供专门的 token 计数接口或 SDK 方法,比上面这种"发一次请求只为看 usage"的做法更省。具体接口名以你所用平台文档为准。)

然后估算用一段本地代码:

public final class TokenEstimator {
   

    /**
     * 粗糙的 token 估算,仅用于入口粗筛。
     * 中文按 0.7 个字符/token 估,英文与符号按 4 字符/token 估。
     * 真实系数请用第四节的测量方法自己标定。
     */
    public static int estimate(String text) {
   
        if (text == null || text.isEmpty()) {
   
            return 0;
        }
        int cjk = 0;
        int other = 0;
        for (int i = 0; i < text.length(); i++) {
   
            char c = text.charAt(i);
            // CJK 统一表意文字基本区与扩展区
            if ((c >= 0x4E00 && c <= 0x9FFF)
                    || (c >= 0x3400 && c <= 0x4DBF)
                    || (c >= 0xF900 && c <= 0xFAFF)) {
   
                cjk++;
            } else {
   
                other++;
            }
        }
        return (int) Math.ceil(cjk / 0.7) + (int) Math.ceil(other / 4.0);
    }
}

跑完你得到的是一张对照关系:同一段文本,估算是多少、精确是多少、偏差百分比是多少。

这张表就是你后续所有预算计算的依据。 别用别人给的系数——它取决于你的内容构成,别人的系数放在你的语料上可能就是错的。

一个必须接受的结论:任何估算都会有偏差,所以预算里必须留余量。 这也是下一节的内容。


五、预算怎么分配

关键认知:上下文窗口是输入和输出共享的。 窗口不是"输入上限",而是"输入加输出的总额度"。你多带一段历史,就少一截输出空间。

所以预算要从窗口里倒着算:

可用输入额度 = 窗口大小 − max_tokens − 安全余量

安全余量必须留,建议 10%~15%。 三个理由:

  • token 估算本身有偏差(上一节刚测过);
  • 部分实现在内部会加结构性的 token(角色标记、分隔符),你的估算里没有;
  • 你偶尔会漏算某个字段。留余量比事后排查"为什么偶尔超长"要省事。

举例说明这个计算为什么重要。假设窗口是 32K,你设 max_tokens = 4000,那么:

可用输入 ≈ 32000 − 4000 − 4000(余量) = 24000

这 24000 才是你带历史的上限,而不是 32K。如果按"窗口 32K"去塞输入,一旦输入接近 32K,输出就没有空间了,回答会被立刻截断——表现是"它突然只说了半句话就停了"。


六、超长了砍哪一段

历史必然有一天会超出预算。砍在哪,直接决定回答质量。第五篇讲过策略(滑窗、摘要压缩、按重要性挑),这里补上按 token 计的落地要点。

第一,用 token 数判断,不要用消息条数。 三条短消息和三条长消息,长度可能差十倍。按条数截必然出现时好时坏。

第二,别把成对结构截断。 这是最容易踩的坑:

  • user 和 assistant 要成对删除,否则会出现连续两条 user,或者历史以 assistant 开头;
  • 用了工具调用时,assistant 消息里的 tool_calls 和紧随其后的 tool 结果消息必须一起保留或一起删除。

从中间硬切会让结构不合法,部分接口直接拒绝请求。

第三,从最老的一轮开始删,system 永远保留。 system 是全部规则的来源,删掉之后模型行为会突然不受约束。

第四,超长输入先摘要,不要硬截。 如果用户粘贴了一份很长的文档,硬截会切掉后半部分的信息,模型基于残缺内容给出自信的错误答案。更稳的做法是先让模型对文档分段摘要,再用摘要去做真正的任务。 多花一次调用,但避免了"看起来在答,其实在编"。

第五,截断位置尽量落在语义边界。 按 token 数算出的位置可能正好切在一句话中间。往前退到最近的句子或段落结束处,读起来更完整,模型理解也更好。


七、流式下的 usage 要显式开启

第二篇提过这一点,这里给完整处理方式。

流式响应默认不返回 usage。如果你在做成本统计,这个数丢了就等于记账断了。开启方式是在请求里加上:

String body = """
    {
   
      "model": "qwen3.8-max",
      "stream": true,
      "stream_options": {
    "include_usage": true },
      "messages": [
        {
   "role": "user", "content": "用三句话介绍杭州西湖"}
      ]
    }
    """;

开启后,usage 会在最后一个数据块里给出。所以读取循环里要单独处理一下:

Usage usage = null;

// 在读取每个数据块时
JsonNode node = mapper.readTree(data);
if (node.has("usage") && !node.get("usage").isNull()) {
   
    usage = mapper.treeToValue(node.get("usage"), Usage.class);
}

// 流结束后落库
if (finished && usage != null) {
   
    recordCost(usage.getPromptTokens(), usage.getCompletionTokens());
}

有两点要注意:

  • 不是所有兼容实现都支持这个参数。 接之前先用一个真实请求试一次,确认 usage 真的回来了,别默认它有。
  • 流中断的情况下拿不到 usage。 因为它在最后一块里。这时候要么按估算记账,要么就不计——但要明确选一个,别让成本统计里出现大量静默缺失的记录。

八、成本预估该怎么做

有了准确的 token 数,成本预估就变成了简单的乘法。但它有几个容易被忽略的系数:

  • 输入和输出分开计价,而且输出通常更贵。所以"减少输出"和"减少输入"的收益不一样,要分别看。
  • 重试会放大成本。 第四篇算过:重试最多 2 次的情况下,最坏消耗是请求数的 3 倍。而且重试的是失败请求,用户什么都没拿到。做预估时要把这个系数算进去。
  • 多轮对话的输入是累积的。 不能按单轮输入乘轮数,那样会低估。
  • 是否开启了思考/推理模式会影响输出 token,同一句话可能生成数倍的 token。如果你的模型有这个开关,预估时要单独看待。

一个够用的估算模板:

单次成本 ≈ (输入 token × 输入单价) + (输出 token × 输出单价)
月度成本 ≈ 单次成本 × 日请求量 × 30 × 重试系数 × (1 + 余量)

余量建议留 20%~30%——因为用户的实际用法一定比你预估的更费。


九、小结

把这一篇收成几句可操作的:

  • 必须自己数:输入随轮次递增、max_tokens 会预扣、超限可能被静默截断
  • 估算与精确分工:粗筛用估算、决定截断用精确值、记账用 usage
  • 偏差系数自己测:中文、代码、混合文本各测一次,别用别人的系数
  • 预算倒着算:可用输入 = 窗口 − max_tokens − 10%~15% 余量
  • 截断守规则:按 token 不按条数、成对删除、保留 system、超长先摘要
  • 流式记 usage:显式开启,中断时要有明确处理

最后一句判断标准:如果你说不出自己当前请求的输入大概占了多少 token,那这个系统的成本和行为就都是不可控的。


下一篇

token 能算准之后,下一个话题是并发。单请求跑通了,把它放进一个每秒几十次调用的服务里,会冒出三类新问题:连接池、线程池、上游配额。还有一个很隐蔽的坑——线程池复用时 ThreadLocal 没清理,导致数据串到别的请求上。下一篇讲并发与限流。


本文是「用 Java 接入大模型」系列第九篇。上一篇讲了提示词的模板化与版本管理,建议按顺序看。文中 token 估算系数与预算比例为实践起点,真实数值请按第四节的方法自行标定;具体接口与参数以你使用的平台文档为准。

目录
相关文章
|
6天前
|
云安全 人工智能 运维
当攻击全面Agent化,你的安全防御能否跟上AI Speed?
多起客户侧AK泄露事件应急的真实观察
1289 6
|
4天前
|
人工智能 API 开发工具
DeepSeek Harness 神插件:白嫖 13 家大厂模型
`dsh-our-free-model` 是 DeepSeek Harness 神级插件:零配置、零API Key、零账号,聚合13家大厂免费模型(含DeepSeek V4.1 Flash、Kimi K3等),支持本地网关、用量看板与自动降级,真正实现“白嫖即用”。
|
9天前
|
存储 人工智能 安全
千问办公官网入口网页版登录:qwenwork.cn 注册送2000积分、登录送100积分
阿里云千问办公(QwenWork)是AI智能工作平台,支持一句话生成PPT、表格、网页、视频及数据分析。个人免费版送2000积分+每日登录赠100分,含1GB存储、5个发布页;企业版198元/席/月起。阿里千问办公QwenWork官网:https://t.aliyun.com/U/0VCTGt 阿里AI工作平台,一句话完成数据分析、PPT 生成、视频剪辑、网页搭建等复杂任务
|
29天前
|
人工智能 JSON API
全网刷屏的 Jev 模型正式开放!一手实战测评 + 保姆级教程
全网爆火的 Jev 模型是什么?有什么用?怎么使用?怎么接入 AI 编程工具?效果真的好么?傻子可懂的 Jev 保姆级实战教程 + 项目实战测评来啦
9860 33
|
15天前
|
存储 人工智能 并行计算
大模型本地部署终端选型方法论:以 Qwen3.8-27B 为例的四档分层完整流程
本文提出一套大模型本地部署终端选型方法论:定约束、定档位、定框架、定参数四步决策法,配合入门、主力、质量、无损四档分层模型。以 Qwen3.8-27B 实测数据为例,逐环节解读显存、带宽、存储、散热、系统、预算等要素,给出面向不同预算的优选方案、决策自查清单与市场观察框架。文末前瞻 AI 笔记本的 CPU+GPU 与统一内存两条路线,论证四步决策法在新品类上的延续性。
|
6天前
|
Web App开发 人工智能 JavaScript
太猛了,一个月干到15W+,给大家推荐一个最近爆火的开源逆向工具:REA
REA(Reverse Engineer Anything)是近期爆火的开源AI逆向工具,4月发布,1个月内斩获15万+ Star。它让AI Agent无需源码即可分析二进制、APK、JS/Electron、HAR包等17类目标,自动反编译、生成可运行代码,并强制返回带证据与局限的结论。全本地执行,MIT协议,支持Hopper/Ghidra/IDA等引擎,一行命令即可上手。
679 2
太猛了,一个月干到15W+,给大家推荐一个最近爆火的开源逆向工具:REA
|
4天前
|
人工智能 开发者
Qoder CN 大使计划正式开启|无限创造,等你一起
全年免费积分、优先内测、活动支持与更多共创机会,邀请用 Qoder 把想法变成现实的你。
448 0
|
13天前
|
人工智能 JSON 自然语言处理
2026 年 Jev 决策模型深度拆解:原理解读、实战测评与保姆级落地教程
有一款特殊AI模型在开发者圈子刷屏,它摒弃传统大模型擅长的对话聊天能力,专注做高速结构化决策,它就是TypeSafe AI推出的Jev模型。该模型由ChatGPT共同发明人Diogo Almeida主导研发,定位为**System One Model(系统一模型)**,对标人类大脑快速直觉判断的思维模式,在响应延迟、调用成本、结构化输出稳定性上相比传统生成式大模型有着巨大差异。本文会完整拆解Jev底层原理、三大核心原语能力、适用业务场景,同时提供可直接运行的curl、Python代码示例,并且结合多组实测数据,客观分析模型优势与能力边界,帮助普通开发者和AI应用从业者快速上手落地。
987 1

热门文章

最新文章