Skip to content

§4.4.6's FATAL tokenization rule has no implementation: perf_gate.sh:79 checks only that method is a non-empty string, and the gate's own OK fixture uses an illegal value #2816

Closed

Description

@noahgift

§4.4.6 is the spec's answer to SGLang RFC #9808 — "a throughput ratio between two servers that count tokens differently is not a ratio of anything". It is specified as FATAL. Nothing implements it.

This is this repo's most common defect class: an assertion that exists, is believed by readers, and cannot fire.

What the rule says

docs/specifications/APR-PERF-GATE-001-v2.2.md §4.4.6:

tokenization:
  method: server_usage | client_tokenizer   # REQUIRED, no default
  tokenizer_sha256: "<REQUIRED when method = client_tokenizer>"
  counts_special_tokens: true | false        # REQUIRED
  counts_prompt_echo: true | false           # REQUIRED

Any mismatch in the tokenization block between the measured lane and its comparator baseline is FATAL — the ratio is refused, not annotated.

What is implemented

1. The only check is that method is a non-empty string. scripts/perf_gate.sh:79-80:

if not (r.get("tokenization") or {}).get("method"):
    bad.append(f"tokenization.method absent{why('tokenization.method')}")

Any truthy string passes. The two legal values are never enumerated.

2. The gate's own OK fixture carries an illegal value. scripts/perf_gate.sh:427:

"tokenization":{"method":"hf-tokenizers"},

hf-tokenizers is neither server_usage nor client_tokenizer. The selftest's negative case (line 574) only blanks the field to "" — it tests emptiness, which is the only thing the check tests. The fixture and the check agree with each other and both disagree with the spec.

3. counts_special_tokens and counts_prompt_echo are read by nothing. They are plumbed from CLI args (extended_commands.rs:1822/1825) through test_llm_band.rs into the receipt writer (perf_gate/receipt.rs:339-340) and serialized. Verified over origin/main: every other occurrence is a struct field, a constructor, or a test literal. No assertion anywhere consumes either value.

4. There is no comparator-vs-measured comparison anywhere in the gate. The word FATAL in §4.4.6 describes a comparison that does not exist in any file. Arm C compares completed/requested; nothing compares two tokenization blocks.

5. tokenizer_sha256 is validated for shape, never computed. perf_gate/receipt.rs:286-294 checks 64 lowercase hex characters. Nothing hashes a tokenizer file. Verified: no tokenizers dependency in crates/aprender-test-lib/Cargo.toml, and zero Tokenizer::from_file or .encode( anywhere in src/llm/ or src/perf_gate/ (both greps rc=1). The declared canonical method — client_tokenizer — has no implementation to be canonical of.

6. TokenizationBlock::require_counter(has_client_counter: bool) takes the answer as a parameter. receipt.rs:307. Its only callers are its own tests (protocol.rs:281-289), which pass the bool literally. A poka-yoke that is told what it is checking is a function, not a guard.

The sharp point: as SPECIFIED, this rule would still have been blind

This is the part that matters for the fix, and it is not obvious from reading §4.4.6.

Measured on W1, same corpus, same model, same client, deterministic (min == median == max), n=32 llama / n=16 apr:

lane prompt_tokens inside 512 ± 8
raw corpus text, client-tokenized 505 32/32
apr usage.prompt_tokens 513 16/16
llama-server usage.prompt_tokens 534 0/32

Constant delta −21, ratio 0.9607 — 4.09% more prefill work on the comparator, on identical input.

Cause: llama-server applies the GGUF's embedded jinja chat template, which injects Qwen's default system message. apr uses its own hardcoded ChatML builder (crates/aprender-serve/src/api/realize_handlers.rs:200 format_chat_messages), which injects no system message and adds a constant 8-token wrapper. Reconstructing each with the canonical tokenizer reproduces both figures exactly.

Now write the §4.4.6 block for both lanes as the spec defines it:

tokenization: {method: server_usage, counts_special_tokens: <..>, counts_prompt_echo: false}

They are identical. Both servers report their own usage; the block has no field that can express which template produced the ids being counted. So the FATAL rule, even fully implemented exactly as written, would have declared these two lanes conformant while they differed by 4.09% on the quantity the ratio divides by.

The divergence is also not correctable by an offset. Shape-varied, apr vs llama:

corpus shape apr llama delta
short 25 43 −18
W1 513 534 −21
special-token text 76 57 +19 (ratio 1.333)
natural 16 34 −18

Not a constant offset, not a constant ratio, and the sign flips. There is no post-hoc correction factor.

Suggested

Implementing §4.4.6 as literally specified would close items 1–6 and still leave the gate blind to the thing it exists to catch. The rule needs new join-key fields before it is worth implementing:

  1. template_identity — a digest of the resolved chat template actually applied (the GGUF's embedded jinja on the comparator lane, the hardcoded builder on apr's), plus whether a default system message was injected. This is the field that separates 513 from 534.
  2. prefill_tokens_measured — the observed prompt length per lane, so a 4.09% divergence is a visible number in the receipt rather than an inference from two receipts nobody diffed.
  3. Then make the FATAL comparison real, over the block including those two fields.
  4. Enumerate method against {server_usage, client_tokenizer} and fix the OK fixture, which is currently asserting the illegal value is fine.
  5. Mutation-verify each one: flip the fixture's method to a third string, observe RED. Give the two lanes different template_identity, observe RED. Per this repo's ratchet rule, a guard that has not been reverted-to-RED is not yet a guard.

Also worth deciding what counts_special_tokens is for now that it is known the two servers sit on opposite sides of it: llama-server counts the stop token in completion_tokens and apr does not — exactly +1 on llama for every naturally-terminated request, 0 when truncated at max_tokens. That is precisely the axis §4.4.6 named, with the two lanes on opposite sides of it, and the field that records it is read by nothing.

Related

Same investigation as #2754 (a streaming receipt declares server_usage while the tokens are client-counted — the mirror of item 5 here: the method is a caller assertion, never cross-checked) and #2743 (llama_pin.toml declares a threshold nothing reads). Epic #2706.


Sibling findings from this investigation

One tokenization/counting investigation against the perf-gate comparator lane produced five findings. Each is self-contained; together they are why no Arm B ratio is currently trustworthy.

# finding class
#2815 apr mis-tokenizes `< im_end
#2816 §4.4.6's FATAL tokenization rule has no implementation; the gate's own OK fixture carries an illegal method gate cannot fire
#2817 BLOCKER — no W1 cell is producible on the comparator lane in either mode campaign blocked
#2818 llama-server 39173bcac accepts, echoes, then ignores ignore_eos, which W1 mandates comparator defect
#2819 canonical tokenizer.json absent on gx10 and intel; only a token-identical, byte-different variant fleet distribution

Shared setup: apr 0.64.0 (745fa8588) CPU build · llama-server 7746 (39173bcac) CUDA, 29/29 layers offloaded · model qwen2.5-coder-7b-instruct-q4_k_m.gguf sha256 509287f78cb4d4cf6b3843734733b914b2c158e43e22a7f4bf5e963800894d3c · canonical tokenizer sha256 c0382117ea329cdf097041132f6d735924b697924d6f6fc3945713e96ce87539 (7,031,645 bytes). All measurements reproduced twice.

Epic: #2706.

Activity

  1. noahgift commented on Sep 5, 2026

    @noahgift
    ContributorAuthor

    Blocks PP-066 master row 7 (spec §5 Track I). Also: C1, master row 18.

    PP-11 (tokenization.method absent is fatal) is ARMED and PP-22 puts tokenization in the join key, but on origin/main perf_gate.sh l.178-179 still checks only that method is non-empty: the two legal values are not enumerated, counts_special_tokens/counts_prompt_echo are read by nothing, and tokenizer_sha256 is validated for shape, never computed. The JOIN (master row 7) cannot refuse a tokenization mismatch it never compares, and the issue's own measurement (apr 513 vs llama-server 534 prompt_tokens on the same corpus) is exactly the mismatch that makes any Arm B ratio incomparable — C1's reference cell needs the rule real.

    Instance of class #2876 — gate answers empty/degenerate input with a PASS or a crash instead of a named refusal.

    PP-066 triage 2026-09-05 (issue #2873); ledger docs/audits/pp-066-triage-2026-09-05.md.

  2. added this to the 0.69.0 milestone on Sep 10, 2026
  3. added
    automationBot-maintained dashboard, not a defect — skip in triage
    P1High priority
    on Sep 11, 2026
  4. modified the milestones: 0.69.0, 0.73.0, 0.73.0 on Sep 17, 2026
  5. noahgift commented on Sep 23, 2026

    @noahgift
    ContributorAuthor

    triage 2026-09-23 (aprender-dd, standing triage owner): milestoned to 0.73.0, theme llama.cpp Parity (performance, kernels, parity benches); it moved out of backlog (the 2026-09-17 overflow bucket, not a release).

  6. noahgift commented on Sep 24, 2026

    @noahgift
    ContributorAuthor

    Closed by paiml-implement — folded into epic #3999 (triage evidence/triage-1717.md)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High priorityautomationBot-maintained dashboard, not a defect — skip in triage

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions