Skip to content

test(runtime): token-estimation accuracy benchmark with multi-tokenizer baselines#6269

Merged
houko merged 1 commit into
librefang:mainfrom
maoxin1234:bench/token-estimation-accuracy
Jun 23, 2026
Merged

test(runtime): token-estimation accuracy benchmark with multi-tokenizer baselines#6269
houko merged 1 commit into
librefang:mainfrom
maoxin1234:bench/token-estimation-accuracy

Conversation

@maoxin1234

@maoxin1234 maoxin1234 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Problem

compactor::estimate_token_count drives compaction triggers, the context-usage report, and cost estimation, but it is a character-weighted heuristic (CJK ~1.5 tokens, other chars/4) with no ground truth behind it.
There is no way to know how far off it is, so any tuning would be a guess and any regression would be silent.

Change

Adds a token-estimation accuracy benchmark — an offline harness, a live capture tool, and two measured baselines with different tokenizerswithout changing the estimator or adding a dependency.

  • corpus.json — 16 message samples, 4 per content bucket (english_prose, cjk, mixed_cjk_latin, tool_json).
    Stored as plain content, not serialized Message objects, so it stays readable and decoupled from message-struct serde changes.
  • token_estimation_accuracy.rs — offline harness.
    Builds Messages from the corpus, asserts the estimator is positive and deterministic over every sample, and reports per-bucket signed error, MAE%, and MAE tokens against each captured baseline.
    It auto-discovers every tokens_truth*.json file and reports them separately, so multiple provider/tokenizer baselines are first-class.
    A regression ceiling is opt-in via LIBREFANG_TOKEN_EST_MAX_MAE_PCT (applied per baseline), so the benchmark is green offline and CI stays deterministic.
  • examples/capture_token_truth.rs — the live, human-run half.
    Sends each sample once with max_tokens=1 and prompt caching disabled, records usage.input_tokens.
    Mirrors the harness's message builder so the bytes scored match the bytes sent.
    Retries with backoff and paces requests so free-tier rate limits do not abort a run.
    --label records the real provider when an OpenAI-compatible backend (Zhipu/GLM, OpenRouter, Groq, Moonshot) is driven via --provider openai.
    CI never invokes it.
  • tokens_truth.json — Zhipu GLM glm-4-flash (efficient-CJK tokenizer).
  • tokens_truth_gptoss.jsonopenai/gpt-oss-120b via OpenRouter (o200k tokenizer family, a close stand-in for GPT-4o-class OpenAI models).

Baseline findings (16 samples, 4 per bucket)

Mean signed error of estimate_token_count vs real input_tokens (positive = overestimate):

bucket GLM (efficient CJK) gpt-oss (OpenAI o200k)
cjk +126% +18%
mixed_cjk_latin +76% -4%
english_prose +29% -15%
tool_json -14% -46%
ALL (signed) +54% -12%

Reading the two tokenizers together is the point — it separates tokenizer-specific error from cross-provider error:

  • The CJK error is strongly tokenizer-specific: +126% on GLM but only +18% on the OpenAI-style tokenizer.
    GLM tokenizes Han text very efficiently (large Chinese vocabulary), so the 1.5-tokens-per-CJK-char weight overshoots for GLM while being roughly reasonable for o200k.
    This PR therefore does not change the CJK weight — doing so on single-provider evidence would help GLM and hurt OpenAI.
  • The tool_json under-estimate is the cross-provider signal: both tokenizers undercount JSON-heavy tool steps (-14% GLM, -46% o200k).
    JSON structure and escaping in tool calls are weighted at the flat 0.25/char and undercount.
    Because the sign agrees across tokenizers, that JSON/escaping path is the safe, language-independent candidate for a later tuning change.

Scope

Infrastructure plus a measured finding.
It deliberately does not touch estimate_token_count; a tuning PR can use this harness to prove an improvement rather than assert one.

Verification

  • cargo test -p librefang-runtime --test token_estimation_accuracy (2 passed)
  • cargo check -p librefang-llm-drivers --example capture_token_truth (clean)
  • cargo clippy -p librefang-runtime --test token_estimation_accuracy -- -D warnings (clean)
  • cargo clippy -p librefang-llm-drivers --example capture_token_truth -- -D warnings (clean)

Out-of-scope follow-ups

  • Add OpenAI / Anthropic ground-truth files (the harness already picks up any tokens_truth*.json).
  • Tune the tool_json (JSON/escaping) estimation path, gated by a committed per-baseline MAE ceiling.

@github-actions github-actions Bot added size/L 250-999 lines changed area/runtime Agent loop, LLM drivers, WASM sandbox labels Jun 22, 2026
…enizer baselines

`compactor::estimate_token_count` drives compaction triggers, the
context-usage report, and cost estimation, but it is a character-weighted
heuristic (CJK ~1.5 tokens, other chars/4) with no ground truth behind it.
There was no way to know how far off it is, so any tuning would be a guess.

This adds an accuracy benchmark — offline harness, live capture tool, and two
measured baselines with different tokenizers — without changing the estimator
or adding a dependency.

What's here:

- corpus.json — 16 message samples, 4 per content bucket (english_prose, cjk,
  mixed_cjk_latin, tool_json). Stored as plain content, not serialized Message
  objects, so it stays readable and decoupled from message-struct serde.
- token_estimation_accuracy.rs — offline harness. Builds Messages from the
  corpus, asserts the estimator is positive and deterministic over every
  sample, and reports per-bucket signed error, MAE%, and MAE tokens against
  each captured baseline. It auto-discovers every tokens_truth*.json file and
  reports them separately, so multiple provider/tokenizer baselines are all
  first-class. A regression ceiling is opt-in via
  LIBREFANG_TOKEN_EST_MAX_MAE_PCT, applied per baseline, so CI stays green and
  deterministic.
- examples/capture_token_truth.rs — live, human-run half. Sends each sample
  once with max_tokens=1 and prompt caching disabled, records
  usage.input_tokens, writes a tokens_truth file. Mirrors the harness's message
  builder so the bytes scored match the bytes sent. Retries with backoff and
  paces requests so free-tier rate limits do not abort a run. --label records
  the real provider when an OpenAI-compatible backend (Zhipu/GLM, OpenRouter,
  Groq, Moonshot) is driven via --provider openai. CI never invokes it.
- tokens_truth.json — Zhipu GLM glm-4-flash baseline (efficient-CJK tokenizer).
- tokens_truth_gptoss.json — openai/gpt-oss-120b via OpenRouter (o200k
  tokenizer family, a close stand-in for GPT-4o-class OpenAI models).
- README — capture procedure, baseline findings, methodology caveats.

Baseline findings (16 samples), mean signed error (positive = overestimate):

  bucket            GLM      gpt-oss(o200k)
  cjk              +126%      +18%
  mixed_cjk_latin   +76%       -4%
  english_prose     +29%      -15%
  tool_json         -14%      -46%
  ALL (signed)      +54%      -12%

Reading the two tokenizers together separates tokenizer-specific error from
cross-provider error. CJK error is strongly tokenizer-specific (+126% on GLM
vs +18% on o200k), so the 1.5-per-CJK-char weight must NOT be changed on
single-provider evidence. The tool_json undercount is the cross-provider
signal: both tokenizers underestimate JSON-heavy tool steps (-14% / -46%), so
that JSON/escaping path is the safe, language-independent tuning candidate.

This PR is infrastructure plus a measured finding; it deliberately does not
touch the estimator.

Verification:
- cargo test -p librefang-runtime --test token_estimation_accuracy (2 passed)
- cargo check -p librefang-llm-drivers --example capture_token_truth (clean)
- cargo clippy -p librefang-runtime --test token_estimation_accuracy -- -D warnings (clean)
- cargo clippy -p librefang-llm-drivers --example capture_token_truth -- -D warnings (clean)
@maoxin1234
maoxin1234 force-pushed the bench/token-estimation-accuracy branch from 8c6dfef to 31f2249 Compare June 22, 2026 02:42
@github-actions github-actions Bot added size/XL 1000+ lines changed and removed size/L 250-999 lines changed labels Jun 22, 2026
@maoxin1234 maoxin1234 changed the title test(runtime): token-estimation accuracy benchmark with GLM baseline test(runtime): token-estimation accuracy benchmark with multi-tokenizer baselines Jun 22, 2026
@houko
houko merged commit 5c4f3ae into librefang:main Jun 23, 2026
33 checks passed
maoxin1234 added a commit to maoxin1234/librefang that referenced this pull request Jun 23, 2026
estimate_token_count weighted every non-CJK character at a flat 0.25,
which systematically undercounts JSON-heavy tool steps: serialized
tool-call inputs, tool results, and tool-definition schemas are dense in
structural punctuation ({}[]":,) and escapes that real tokenizers split
into their own tokens.

The token-estimation accuracy benchmark (librefang#6269) measured this as the
cross-provider signal: the tool_json bucket is undercounted by both
committed baselines and the sign agrees (-14% GLM, -46% o200k), unlike
the tokenizer-specific CJK error.

Add estimate_json_str_tokens, which counts JSON structural punctuation at
JSON_STRUCT_TOKEN_WEIGHT (0.5) while keeping CJK/alphanumeric runs at
their normal weights, and route the three tool paths through it. Prose
estimation is unchanged.

0.5 is calibrated against both baselines as the value that improves or
holds each without regressing any other bucket:

  tool_json   before (signed / MAE tok)   after
  GLM         -14% / 21.0                  -3% / 20.2
  o200k       -46% / 103.8                 -39% / 88.5

cjk/english_prose/mixed_cjk_latin are byte-identical before/after. A
single global weight is a compromise because GLM tokenizes JSON structure
more efficiently than o200k; the residual o200k undercount is the known
limit of a tokenizer-independent heuristic.

Verification:
- cargo clippy -p librefang-runtime --all-targets -- -D warnings (clean)
- cargo test -p librefang-runtime --lib compactor::tests (46 passed,
  incl. 2 new: structural-weight and tool-call-vs-prose)
- cargo test -p librefang-runtime --test token_estimation_accuracy
  --nocapture (after-table above)
houko pushed a commit that referenced this pull request Jun 23, 2026
estimate_token_count weighted every non-CJK character at a flat 0.25,
which systematically undercounts JSON-heavy tool steps: serialized
tool-call inputs, tool results, and tool-definition schemas are dense in
structural punctuation ({}[]":,) and escapes that real tokenizers split
into their own tokens.

The token-estimation accuracy benchmark (#6269) measured this as the
cross-provider signal: the tool_json bucket is undercounted by both
committed baselines and the sign agrees (-14% GLM, -46% o200k), unlike
the tokenizer-specific CJK error.

Add estimate_json_str_tokens, which counts JSON structural punctuation at
JSON_STRUCT_TOKEN_WEIGHT (0.5) while keeping CJK/alphanumeric runs at
their normal weights, and route the three tool paths through it. Prose
estimation is unchanged.

0.5 is calibrated against both baselines as the value that improves or
holds each without regressing any other bucket:

  tool_json   before (signed / MAE tok)   after
  GLM         -14% / 21.0                  -3% / 20.2
  o200k       -46% / 103.8                 -39% / 88.5

cjk/english_prose/mixed_cjk_latin are byte-identical before/after. A
single global weight is a compromise because GLM tokenizes JSON structure
more efficiently than o200k; the residual o200k undercount is the known
limit of a tokenizer-independent heuristic.

Verification:
- cargo clippy -p librefang-runtime --all-targets -- -D warnings (clean)
- cargo test -p librefang-runtime --lib compactor::tests (46 passed,
  incl. 2 new: structural-weight and tool-call-vs-prose)
- cargo test -p librefang-runtime --test token_estimation_accuracy
  --nocapture (after-table above)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/runtime Agent loop, LLM drivers, WASM sandbox size/XL 1000+ lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants