全量编码 + token 报告
B1 tokenization 支线到今天才接上「为什么要自写 tokenizer」这个动机:token 数直接决定 OpenRouter 计费(cost ≈ token × 单价)。
阶段: B1 · evals 方法论 + tokenization 起步(Day 1-10) 标签: #tokenization #token-report #cost-estimation #no-api-key
今日导引(由浅入深)
B1 tokenization 支线到今天才接上「为什么要自写 tokenizer」这个动机:token 数直接决定 OpenRouter 计费(cost ≈ token × 单价)。
Day 7 写了编码器、Day 8 训了 merge 表,今天把两者用起来——用自写 tokenizer 编码全部 29 个 eval prompt,导出每条 + 总计的 token 计量,即可在花一分钱前预估成本。
这是「implemented, not noted」的高光时刻:机制是代码、数字是报告,全程不需要任何 API key。
最小可判定产出:29 prompt 的 token 报告,实测 totalChars=3536 → totalTokens=1760,meanCharsPerToken≈2.01,测试绿。
明天 Day 10 把这个估算与真实 eval cost 并排,算偏差% 收口 B1。
1. 机理精读
token 计量是成本估算的前置,而成本估算是 eval 工程的现实约束。
跑一轮 agent eval 要对几十条任务各发请求、各拿几百 token 的输出,OpenRouter 按 input/output token 分别计价。
如果不知道每条任务多少 token,就只能跑完看账单——而跑完才知道贵,对迭代是灾难。先估后跑 才能做预算、做任务取舍、做「这条任务值不值得放进套件」的决策。
核心等式:cost ≈ token × 单价。
input token 用自写 tokenizer 在发请求前可精确算出(prompt 是已知的);output token 不可预估(模型生成多少由模型决定),只能用历史均值或上界估。
所以自写 tokenizer 解决的是等式里 input 那一半——已经能把「这套 29 任务的输入成本量级」算清楚。
token 报告输出什么。
本仓 tokenReport 输出每条一行(index / chars / tokens / charsPerToken)+ 总计(totalChars / totalTokens / meanCharsPerToken)。
meanCharsPerToken(c/t 比)是压缩率的直观度量:c/t 越高,单 token 装的字符越多,越省。
为什么本批 c/t 偏低(≈2.01)?
因为语料是英文 + JSON(eval prompt 里有结构化字段、标点、数字)。
JSON 的大括号、引号、逗号大量是单字节单 token,数字也常被细切,所以平均每 token 只装约 2 个字符。
c/t 比随语料而变——纯英文散文会更高(3-4+),中文按字节切会更低。这正是「不能用一个语料的 c/t 外推另一个」的原因。
关键边界:自写 tokenizer 的切分与模型官方 tokenizer(DeepSeek-V4 / Qwen3 各自的 tokenizer)不同。
算出的 token 数只是量级估算,不等于 OpenRouter 实际计费 token。两者差异是 Day 10 要量化的「偏差%」。
所以今天的 1760 是一个有用的估算锚点,不是计费真值。
2. 推导 / 手算 / 代码走读
代码日。Read src/agent/eval/tokenizer.ts 的 tokenReport,真实走读:
TokenReportRow(interface):index/chars/tokens/charsPerToken——每条 prompt 一行。TokenReport(interface):rows[]+totalChars+totalTokens+meanCharsPerToken。tokenReport(texts, model):rows = texts.map((t, index) => {...}):对每条文本tokens = encode(t, model).length(用 Day 7 的 encode)、chars = t.length、charsPerToken = tokens ? chars / tokens : 0(防 0 除)。totalChars = rows.reduce((s, r) => s + r.chars, 0);totalTokens同理。meanCharsPerToken = totalTokens ? totalChars / totalTokens : 0——注意是 total/total(加权),不是逐行 c/t 的平均,更准确反映整体压缩率。
- 文件头注释:「used to measure tokens-per-task (→ cost) over the eval prompts WITHOUT any API key」——直接点明今天的动机。
手算验证 c/t(逐字对上 seed):
meanCharsPerToken = totalChars / totalTokens
= 3536 / 1760
= 2.0090…
≈ 2.01 c/t
逐字对上 seed 的「meanCharsPerToken=2.009(≈2.01 c/t)」。
压缩有效的证据:totalTokens(1760) < totalChars(3536)——merge 把 3536 个字符压成了 1760 个 token,压缩比约 2:1。
测试走读(tokenizer.test.ts 第 3 个用例 'compresses'):
const rep = tokenReport(corpus, model)(corpus = 29 个 prompt)。expect(rep.rows.length).toBe(EVAL_TASKS.length)——29 行,逐条覆盖。expect(rep.totalTokens).toBeGreaterThan(0)、expect(rep.totalTokens).toBeLessThan(rep.totalChars)——压缩生效(1760 < 3536)。expect(rep.meanCharsPerToken).toBeGreaterThan(1)——c/t > 1(实测 2.01)。
input 成本估算骨架(待 key 填单价):
cost_input估算 ≈ totalTokens(1760) × input单价(/Mtok) / 1e6
注意这只是 input 半边;output 半边不可预估,留 Day 10 与实测对齐时讨论。
3. 今日实战
照 seed:用 tokenizer.ts 的 tokenReport 编码全部 29 prompt,导出每条 token 数 + 总计,存为 token 报告(CSV 29 行 + 总计)。
corpus = EVAL_TASKS.map(t => t.prompt);model = trainBpe(corpus, 200)。const rep = tokenReport(corpus, model)。- 把
rep.rows(index/chars/tokens/charsPerToken)导成 CSV 29 行 + 一行总计(totalChars=3536 / totalTokens=1760 / meanCharsPerToken=2.01)。 - 进 vitest 全量绿(
'compresses'用例)。
4. 今日实测 / 产出
- 已完成——实测 totalChars=3536 → totalTokens=1760,meanCharsPerToken=2.009(≈2.01 c/t),覆盖 29 prompt,测试绿。
- 产出:token 报告(CSV 29 行 + 总计),全程无需 API key。
- 状态:已完成(非待跑/待建);与真实 eval cost 的偏差% 校验留 Day 10。
5. 常见误区 / 陷阱
- 把自写 c/t 当 OpenRouter 计费真值:切分不同,1760 是量级估算,不是计费 token。Day 10 量化偏差。
- 用逐行 c/t 平均代替 total/total:本仓
meanCharsPerToken是加权(总字符/总 token),逐行平均会被短 prompt 拉偏。 - 忽略 output token:cost = input + output token × 单价,自写 tokenizer 只覆盖 input;output 不可预估,别只报 input 就当总成本。
- 拿英文+JSON 的 2.01 c/t 套别的语料:中文按字节切更低、英文散文更高,c/t 必须按语料实测。
6. 学习资源(每条带 YYYY-MM)
- OpenRouter 计费文档(input/output token 分别计价,2026-06 当周重验)— cost ≈ token × 单价 的出处。
- OpenAI tiktoken(byte-level BPE token 计量,2024 起维护)— 生产侧 token 计量参照,对照自写偏差。
- Karpathy minbpe(Zero-to-Hero, 2024)—
encode→ token 计数的实现心智。 - 本仓
src/agent/eval/tokenizer.tstokenReport(2026-06,repo 内)— 真实 3536→1760、2.01 c/t 的产出代码 +tokenizer.test.ts压缩断言。
SOTA检查 (2026-06 更新)
- 当前主流:用模型自带 tokenizer 精确计费仍是生产标准;自写 tokenizer 仅作量级估算,仍是有效的 no-key 成本预估手段。
- 过时黑名单:避免用自写 c/t 精确等同 OpenRouter 计费 token——c/t 比随语料而变(此处英文+JSON 偏低 2.01),真实计费以模型自带 tokenizer 为准。
- 下次复查点:OpenRouter 单价/模型在架状态执行当周重验;DeepSeek-V4-Flash / V4-Pro、Qwen3 的官方 tokenizer 若更新则估算偏差需重测。
衔接
- 昨天:Day 8 — byte-level 编码器 (2)(merge 训练算法,vocab=456)。
- 今天:用训好的表对 29 prompt 出 token 报告,实测 totalTokens=1760、≈2.01 c/t,no-key 成本估算锚点就位。
- 明天:Day 10 — 双数对齐收口,把 token 估算(1760×单价)与真实 eval cost 并排算偏差%,合并 commit 收束 B1。