test(runtime): token-estimation accuracy benchmark with multi-tokenizer baselines#6269
Merged
Merged
Conversation
…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
force-pushed
the
bench/token-estimation-accuracy
branch
from
June 22, 2026 02:42
8c6dfef to
31f2249
Compare
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
compactor::estimate_token_countdrives 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 tokenizers — without 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
Messageobjects, 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*.jsonfile 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=1and prompt caching disabled, recordsusage.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.
--labelrecords 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 GLMglm-4-flash(efficient-CJK tokenizer).tokens_truth_gptoss.json—openai/gpt-oss-120bvia 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_countvs realinput_tokens(positive = overestimate):Reading the two tokenizers together is the point — it separates tokenizer-specific error from cross-provider error:
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.
tool_jsonunder-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
tokens_truth*.json).tool_json(JSON/escaping) estimation path, gated by a committed per-baseline MAE ceiling.