Skip to content

fix(tokenizer): the unknown token is the model's, not BPE's — Qwen3.5 serves; a model with no vocabulary refuses by name (PMAT-3609) - #3678

Open
noahgift wants to merge 1 commit into
mainfrom
PMAT-3609-bpe-unk-optional
Open

noahgift wants to merge 1 commit into
mainfrom
PMAT-3609-bpe-unk-optional

Conversation

@noahgift

Copy link
Copy Markdown
Contributor

Closes #3609

BPETokenizer required a <unk> token. Qwen3.5 has none: it declares tokenizer.ggml.eos_token_id and no unknown_token_id key at all. So apr serve refused a model with a real, complete vocabulary, while a model with no vocabulary loaded, because a placeholder synthesised one. The less-specified input was the one accepted. This PR applies the operator's three constraints from the 2026-09-20 ruling, plus the cop's 2026-09-21 rulings.

What changes

  1. The unknown token is Option, and absent means absent. BPETokenizer::new(vocab, merges, unk_token: impl Into<Option<&str>>).
    • Some(t) / "t": t must be in the vocabulary, or the constructor errors. Existing "<unk>" callers compile unchanged (From<&str> for Option<&str>).
    • None: no unknown token. Construction refuses, by name, if any byte has neither a <0xNN> token nor a byte-level glyph token. with_merges also requires every glyph, because the merge path maps bytes to glyphs only. So encode never meets an unencodable byte. Nothing is dropped and nothing is emitted as a stand-in. This is the cop's "refuse, never drop" guard; it is enforced at construction because encode is infallible and has dozens of callers.
    • encode with an unknown token is unchanged (<0xNN>, then the unknown token). Without one, it falls back to the byte-level glyph.
  2. No vocabulary refuses by name, at all 13 placeholder sites: 7 in apr-cli serve, 6 in aprender-serve cli. It is ModelLoadFailed in apr-cli (exit 6) and UnsupportedOperation in aprender-serve: "… has no vocabulary to tokenize with, so there is nothing to serve; refusing to substitute placeholder tokens (The BPE tokenizer requires a '<unk>' token that Qwen3.5 does not have — a universal unknown-token is an assumption, not a fact #3609)". This removes QA: SmolLM2 systematic 2-test failure across all sizes (92.6% pass rate) #226's token{i} + vocab[0] = "<unk>" fallback.
  3. Where the unknown token comes from. apr serve's tokenizer.json path uses the declared model.unk_token (null for Qwen). The vocabulary-only constructors (the AppState::*_and_vocab family) use vocabulary_unk_token(&vocab): the vocabulary's own <unk> entry, or none. That is a by-name interim the cop approved. Threading the declared tokenizer.ggml.unknown_token_id through every loader is Thread the DECLARED unknown token (tokenizer.ggml.unknown_token_id / tokenizer.json unk_token) through every tokenizer loader — #3609 uses a by-name interim #3675, filed before this PR.
  4. Prose: the QA: SmolLM2 systematic 2-test failure across all sizes (92.6% pass rate) #226 doc comment that stated our constructor's requirement as a fact about vocabularies is rewritten, and the book (api-server.md) and examples/model_cache.rs now teach the unknown token as optional and the model's own.

Fixtures: real headers, truncation named

crates/aprender-serve/tests/fixtures/gguf-header-slices/: generate.py cuts a real GGUF down to its header. Every key and every scalar/string value is kept verbatim; only the per-token arrays are cut to a leading slice. MANIFEST.json records each source file's header sha256.

Fixture Source Keys Decisive values (verbatim)
qwen3.5-0.8b.gguf-header (39 KB) Qwen3.5-0.8B-IQ4_XS.gguf, header sha256 2eb5e86e… 46 eos_token_id = 248046; no unknown_token_id; no <unk> in the vocabulary
tinyllama-1.1b-chat.gguf-header (19 KB) tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf 23 unknown_token_id = 0 = <unk>

The .gguf-header extension is deliberate: .gitignore:47 ignores *.gguf, and a .gguf fixture would silently never be committed.

Evidence

cargo test -p aprender-serve --lib tokenizer         215 passed (9 new #3609 rows)
MUTANT (constraint 3): restore `token_to_id.get("<unk>").ok_or_else(..)` unconditionally in BPETokenizer::new
  → qwen35_real_vocabulary_builds_a_tokenizer_with_no_unknown_token  RED:
    "a real vocabulary without <unk> is not refused (#3609): … Unknown token '<unk>' not in vocabulary"
    (and 3 more no-unknown-token rows RED); file restored, 0 mutant markers left
cargo test -p aprender-serve --lib                   15892 passed, 0 failed
cargo test -p apr-cli --lib                          7283 passed, 0 failed
cargo clippy -p aprender-serve / -p apr-cli --lib -- -D warnings   rc=0 / rc=0
cargo test -p aprender-contracts --lib               1666 passed · cargo deny advisories+bans ok · check_include_files 1791 tracked
guard_tree.sh --no-cargo                             72/73: check_complexity_ratchet PASS (encode's byte fallback extracted to
                                                     byte_fallback_id, which keeps encode under cognitive 25). The one FAIL is
                                                     check_fleet_pv_shapes_gate: main's pre-andon gate on un-migrated parity
                                                     records (ENV rc=2), fixed by #3669's andon + #3600, and it fails the same on any branch off main
cargo check: aprender-serve (default = server,cli,gpu) rc=0, --features cuda rc=0; apr-cli default rc=0, --features cuda rc=0
  (apr-cli --features wgpu fails on main's finetune.rs/aprender-train wgpu items; none of its errors is in a file this PR touches)

Case rows (crates/aprender-serve/src/tokenizer_tests_unk_3609.rs): the real Qwen3.5 header declares EOS and no unknown token · the real Qwen3.5 vocabulary builds a tokenizer with no unknown token and round-trips ASCII/CJK/newline (the mutation target) · the real TinyLlama header's declared <unk> still resolves to id 0 (direction 1) · a declared unknown token is still emitted for an unencodable byte · a named unknown token missing from the vocabulary is refused · no unknown token + byte-level glyphs encode every byte · no unknown token + <0xNN> tokens encode every byte · no unknown token + a byte with no token is refused, naming the byte · vocabulary_unk_token names only a present <unk>.

The pmat query audit (constraint 2)

pmat query --literal '<unk>' (82 files) and --literal 'unknown_token' (13 files), plus git grep over docs (95 and 18 files). The union is 108 files. Every one is fixed or kept, with its reason:

Disposition Files Why
fixed 9 changed in this PR
kept 34 aprender-serve tests, benches and test helpers whose vocabularies DECLARE <unk>. Declared unknown tokens still resolve (direction 1), and these call sites compile unchanged via From<&str> for Option<&str>
kept 1 aprender-serve examples whose sample vocabulary declares <unk> / comments on token id 0
kept 5 SentencePiece/Unigram: the algorithm has an unknown piece by definition (unigram segmentation needs a fallback piece). Not a BPE requirement
kept 1 apr/tokenizer_loading.rs:237 builds a SentencePiece tokenizer with the literal "<unk>": legitimate for a unigram model, and the same by-name interim #3675 replaces with the declaration
kept 3 word-level Tokenizer (token.rs, tokenizer_vocabulary.rs, examples/tokenization.rs): a word vocabulary has no byte fallback, so OOV words need an unknown id. Different algorithm
kept 2 special-token classification (apr/special_tokens.rs, apr/mapped_apr_model.rs): recognises <unk> as a control token WHEN PRESENT; requires nothing
kept 1 parses a DECLARED unknown token (chat_template_template.rs: tokenizer_config unk_token)
kept 27 aprender-core tokenizers/converters/tests: its LLaMA tokenizer reads the DECLARED tokenizer.ggml.unknown_token_id (llama_tokenizer/gguf.rs:150); its BPE/Unigram trainers emit <unk> as training output; GGUF export/builder tests write <unk> as data. None requires <unk> of an input vocabulary
kept 2 aprender-train: its own trainable BPE (apr tokenize train config); <unk> is an output it defines, not an input requirement
kept 6 apr-cli test data and trainer config: CSV/vocab.json fixtures containing <unk> as a token, the apr tokenize trainer, and tests documenting <unk> for unseen characters
kept 2 aprender-orchestrate: its own inference demo tokenizer and migration example
kept 2 CodeBERT (RoBERTa BPE) tokenizer contract: that model declares <unk>
kept 1 documentation showing a vocabulary JSON sample that contains <unk> as data
kept 2 substring match, not an unknown TOKEN (parse_stage_list_rejects_unknown_token, an_unknown_tokenization_method_is_refused)
kept 8 historical records: evidence/, archived specs, quorum receipts, QA prompts, dogfood templates, and a tracked coverage-report HTML of pre-monorepo code. Append-only or generated; rewriting them would falsify the record
kept 2 pmat query index hit with no literal occurrence in the current file (stale index entry)

108 files, 0 unclassified.

fixed: changed in this PR (9)
  • crates/apr-cli/src/commands/serve/chat.rs
  • crates/apr-cli/src/commands/serve/handler_gpu_completion.rs
  • crates/apr-cli/src/commands/serve/handlers.rs
  • crates/apr-cli/src/commands/serve/handlers_include_01.rs
  • crates/aprender-serve/book/src/examples/api-server.md
  • crates/aprender-serve/examples/model_cache.rs
  • crates/aprender-serve/src/api/mod_app_state_gpu.rs
  • crates/aprender-serve/src/api/mod_app_state_new.rs
  • crates/aprender-serve/src/tokenizer.rs
kept: aprender-serve tests, benches and test helpers whose vocabularies DECL (34)
  • crates/aprender-serve/benches/cache.rs
  • crates/aprender-serve/benches/tokenizer.rs
  • crates/aprender-serve/src/api/chat_completions_stream.rs
  • crates/aprender-serve/src/api/openai_handlers.rs
  • crates/aprender-serve/src/api/tests/apr_model_routes_2609.rs
  • crates/aprender-serve/src/api/tests/batch_completions_tokenizer_2465.rs
  • crates/aprender-serve/src/api/tests/chat_template_contract.rs
  • crates/aprender-serve/src/api/tests/embed_and_envelope_2376.rs
  • crates/aprender-serve/src/api/tests/openai_compat_2375.rs
  • crates/aprender-serve/src/apr/tests_apr_header.rs
  • crates/aprender-serve/src/apr/tests_apr_metadata.rs
  • crates/aprender-serve/src/apr/tests_f16.rs
  • crates/aprender-serve/src/apr/tokenizer_tests.rs
  • crates/aprender-serve/src/cache_tests.rs
  • crates/aprender-serve/src/gguf/inference/forward/single_tests.rs
  • crates/aprender-serve/src/gguf/loader_gguf_model_02.rs
  • crates/aprender-serve/src/gguf/loader_gguf_read.rs
  • crates/aprender-serve/src/gguf/tests/decode_byte.rs
  • crates/aprender-serve/src/gguf/tests/phase35_rope.rs
  • crates/aprender-serve/src/gguf/tests/tests_35.rs
  • crates/aprender-serve/src/gguf/tests/vocabulary_single_gguf.rs
  • crates/aprender-serve/src/layers/tests/qa_012_latency.rs
  • crates/aprender-serve/src/registry_create.rs
  • crates/aprender-serve/src/registry_tests.rs
  • crates/aprender-serve/src/tokenizer_contract_tests.rs
  • crates/aprender-serve/src/tokenizer_tests_bpe_encode.rs
  • crates/aprender-serve/tests/apr_coverage.rs
  • crates/aprender-serve/tests/gguf_model_coverage.rs
  • crates/aprender-serve/tests/gguf_vocab_tests.rs
  • crates/aprender-serve/tests/integration_multi_model_api.rs
  • crates/aprender-serve/tests/property_tests.rs
  • crates/aprender-serve/tests/property_tokenizer.rs
  • crates/aprender-serve/tests/qwen3_moe_serve_dispatch_v1.rs
  • crates/aprender-serve/tests/tokenizer_stress.rs
kept: aprender-serve examples whose sample vocabulary declares `` / com (1)
  • crates/aprender-serve/examples/par_001_check_embeddings.rs
kept: SentencePiece/Unigram (5)
  • crates/aprender-core/src/text/llama_tokenizer/tests_gguf_sentencepiece.rs
  • crates/aprender-core/src/text/tokenize/tests_sentence.rs
  • crates/aprender-serve/src/tokenizer_sentence_piece.rs
  • crates/aprender-serve/src/tokenizer_sentencepiece_encode.rs
  • crates/aprender-serve/src/tokenizer_tests_sentencepiece_viterbi.rs
kept: `apr/tokenizer_loading.rs (1)
  • crates/aprender-serve/src/apr/tokenizer_loading.rs
kept: word-level `Tokenizer` (3)
  • crates/aprender-serve/examples/tokenization.rs
  • crates/aprender-serve/src/token.rs
  • crates/aprender-serve/src/tokenizer_vocabulary.rs
kept: special-token classification (2)
  • crates/aprender-serve/src/apr/mapped_apr_model.rs
  • crates/aprender-serve/src/apr/special_tokens.rs
kept: parses a DECLARED unknown token (1)
  • crates/aprender-serve/src/chat_template_template.rs
kept: aprender-core tokenizers/converters/tests (27)
  • crates/aprender-core/examples/create_test_transformer_apr.rs
  • crates/aprender-core/src/citl/neural/tests.rs
  • crates/aprender-core/src/format/converter/export_include.rs
  • crates/aprender-core/src/format/converter/gguf_export_config.rs
  • crates/aprender-core/src/format/converter/tests/tokenizer_parse.rs
  • crates/aprender-core/src/format/converter/tests/tokenizer_parse_vocab_padding.rs
  • crates/aprender-core/src/format/converter/tokenizer_loader.rs
  • crates/aprender-core/src/format/gguf/api_tests.rs
  • crates/aprender-core/src/format/gguf/builder.rs
  • crates/aprender-core/src/format/test_factory/harness_impl.rs
  • crates/aprender-core/src/format/v2_dequant_tests/tests_layout_writer_flags.rs
  • crates/aprender-core/src/text/bpe/mod.rs
  • crates/aprender-core/src/text/bpe/tests.rs
  • crates/aprender-core/src/text/bpe/tests_encode_decode.rs
  • crates/aprender-core/src/text/chat_template/tests.rs
  • crates/aprender-core/src/text/chat_template/tests_huggingface.rs
  • crates/aprender-core/src/text/llama_tokenizer/construction.rs
  • crates/aprender-core/src/text/llama_tokenizer/gguf.rs
  • crates/aprender-core/src/text/llama_tokenizer/mod.rs
  • crates/aprender-core/src/text/llama_tokenizer/tests.rs
  • crates/aprender-core/src/text/llama_tokenizer/tests_decode.rs
  • crates/aprender-core/src/text/llama_tokenizer/tests_gguf_parsing.rs
  • crates/aprender-core/src/text/tokenize/bpe_training.rs
  • crates/aprender-core/src/text/tokenize/mod.rs
  • crates/aprender-core/src/text/tokenize/tests.rs
  • crates/aprender-core/src/text/tokenize/tests_bpe_loading.rs
  • crates/aprender-core/src/text/tokenize/unigram_training.rs
kept: aprender-train (2)
  • crates/aprender-train/src/tokenizer/bpe.rs
  • crates/aprender-train/src/tokenizer/config.rs
kept: apr-cli test data and trainer config (6)
  • crates/apr-cli/src/commands/embed_viz_classifier.rs
  • crates/apr-cli/src/commands/rosetta_fail_closed_tests.rs
  • crates/apr-cli/src/commands/stamp.rs
  • crates/apr-cli/src/commands/tokenize.rs
  • crates/apr-cli/tests/falsification_crux_f_18.rs
  • crates/apr-cli/tests/falsification_tokenizer_data.rs
kept: aprender-orchestrate (2)
  • crates/aprender-orchestrate/src/serve/banco/inference.rs
  • crates/aprender-orchestrate/src/serve/banco/inference_tests.rs
kept: CodeBERT (2)
  • contracts/codebert-tokenizer-validation-v1.yaml
  • crates/aprender-contracts-staging/contracts/codebert-tokenizer-validation-v1.yaml
kept: documentation showing a vocabulary JSON sample that contains `` a (1)
  • book/src/tools/apr-spec.md
kept: substring match, not an unknown TOKEN (2)
  • crates/apr-cli/src/commands/test_llm_band.rs
  • crates/aprender-serve/src/inference_trace/save_tensor_stage.rs
kept: historical records (8)
  • crates/aprender-serve/coverage_report/html/coverage/home/noah/src/realizar/src/tokenizer.rs.html
  • docs/audits/quorum-PMAT-3571.json
  • docs/dogfood-templates/albor-370m-v1-dogfood-template.md
  • docs/qa/prompts/QA-MASTER-AUDIT-TRACE.md
  • docs/specifications/archive/APR-SPEC-v2-draft.md
  • docs/specifications/archive/qwen3-perf-parity.md
  • evidence/section-61-5g-1-re-encode-2026-05-10/README.md
  • evidence/serve/3571/LOADER.md
kept: `pmat query` index hit with no literal occurrence in the current file (2)
  • crates/aprender-orchestrate/examples/migrations/pytorch-inference/output.rs
  • crates/aprender-serve/examples/trace_layer0_detailed.rs

The new files (tokenizer_tests_unk_3609.rs, the fixtures) are this PR's additions. contracts/bpe-tokenization-v1.yaml already states the invariant this makes true: vocabulary_lookup says "unknown tokens handled by byte fallback", so no contract text changes.

Found, filed, not in this PR

Overlap

The stale PMAT-3041 PRs #3344 and #3349 (opened 09-16, both DIRTY) touch serve/handler_gpu_completion.rs and serve/handlers_include_01.rs in different functions. Whichever lands second resolves the text.

🤖 Generated with Claude Code

… serves; a model with no vocabulary refuses by name (PMAT-3609)

BPETokenizer::new required `<unk>` in every vocabulary. Qwen3.5 has none: it
declares tokenizer.ggml.eos_token_id and no unknown_token_id key at all. So
`apr serve` refused a real, complete vocabulary, while a GGUF/APR with NO
vocabulary loaded, because GH-226's placeholder synthesised `token{i}` with
`<unk>` in slot 0. The less-specified input was the one accepted.

This follows the operator's three constraints (#3609, 2026-09-20) and the
cop's rulings (2026-09-21):

- The unknown token is Option: `new(vocab, merges, impl Into<Option<&str>>)`.
  A named token must exist. With no unknown token, CONSTRUCTION refuses,
  naming the first byte that has neither a `<0xNN>` token nor a byte-level
  glyph token (with_merges also requires every glyph), so encode never meets
  an unencodable byte: nothing is dropped and nothing is synthesised. With
  an unknown token present, encode is unchanged.
- No vocabulary refuses by name at all 13 placeholder sites (apr-cli serve
  x7, aprender-serve cli x6).
- apr serve's tokenizer.json path uses the DECLARED model.unk_token. The
  vocabulary-only AppState constructors use `vocabulary_unk_token` (the
  vocabulary's own `<unk>`, else none), the by-name interim #3675 replaces.
- Prose: the GH-226 comment, the book's api-server example and
  examples/model_cache.rs no longer teach `<unk>` as a requirement.
- Fixtures: REAL Qwen3.5-0.8B and TinyLlama GGUF headers, every key and
  scalar verbatim, only the per-token arrays sliced (generate.py,
  MANIFEST.json with each source header's sha256). They use the
  `.gguf-header` extension because .gitignore ignores `*.gguf`.

Verified: aprender-serve lib 15892 passed (tokenizer 215, 9 new); apr-cli
lib 7283 passed; clippy -D warnings clean on both; cargo check with cuda;
contracts lib 1666; deny ok. MUTANT: restoring the unconditional
`get("<unk>").ok_or_else(..)` turns the real-Qwen3.5 row RED with the
original error "Unknown token '<unk>' not in vocabulary".

Found and filed separately: #3677 (greedy encode reads Latin-1 characters
as byte-level glyph tokens, so `é` decodes to U+FFFD).

Closes #3609

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@noahgift

Copy link
Copy Markdown
Contributor Author

quorum-review (AD-04): NOT agreed (auto_merge: checked=true was_armed=false disarmed=false)

{
 "ticket": "PMAT-3609",
 "head": "e5b2e75410f4f3af6da07baeb5388dba47ae8572",
 "width": 3,
 "executor": "agy",
 "agreed": false,
 "auto_merge": {
  "checked": true,
  "was_armed": false,
  "disarmed": false,
  "note": "auto-merge not armed"
 },
 "lanes": [
  {
   "lane": 1,
   "verdict": "NO-VERDICT",
   "findings": 0
  },
  {
   "lane": 2,
   "verdict": "PASS",
   "findings": 0
  },
  {
   "lane": 3,
   "verdict": "PASS",
   "findings": 4
  }
 ]
}

@noahgift

Copy link
Copy Markdown
Contributor Author

quorum-review (AD-04): NOT agreed (auto_merge: checked=true was_armed=false disarmed=false)

{
 "ticket": "PMAT-3609",
 "head": "e5b2e75410f4f3af6da07baeb5388dba47ae8572",
 "width": 3,
 "executor": "agy",
 "agreed": false,
 "auto_merge": {
  "checked": true,
  "was_armed": false,
  "disarmed": false,
  "note": "auto-merge not armed"
 },
 "lanes": [
  {
   "lane": 1,
   "verdict": "FAIL",
   "findings": 4
  },
  {
   "lane": 2,
   "verdict": "PASS",
   "findings": 0
  },
  {
   "lane": 3,
   "verdict": "PASS",
   "findings": 4
  }
 ]
}

@github-actions

Copy link
Copy Markdown

§13.11 rung 1 — quorum shadow verdict

S13-SHADOW pr=3678 head=e5b2e75410f4f3af6da07baeb5388dba47ae8572 verdict=REFUSE class=Q1 arm_rc=1

Shadow mode: this records a verdict and merges nothing. A refusal
to arm is not a block (§13 adds zero rows to §7) — the pull request is
exactly as green as it was.

@noahgift

noahgift commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

Seat-fill request for aprender-b3 (cop ruling 2026-09-21): all three quorum seats on PMAT-3609 need a non-author measured fill.

Round 3 recorded AGREED 3/3 PASS. None of the three lanes judged this tree:

  • lane 3 read the stale agy scratch clone (paiml-implement#317);
  • lanes 1 and 2 read no repo tree at all and judged only the diff in their prompt.

So none of the three PASSes counts as a measurement.

Judged sha 9cc89e3a9 (diff_sha256 516acb17e9e2…)
Receipt docs/audits/quorum-PMAT-3609.json, committed as 3e7a88cd0
Lanes /mnt/nvme-raid0/agent-wt/pmat-3609/docs/audits/quorum-PMAT-3609.json.lanes/
Branch PMAT-3609-bpe-unk-optional (local)
Spec docs/roadmaps/entries/PMAT-3609.yaml, whose acceptance_criteria are the operator's three constraints verbatim, and whose notes hold the cop's encode guard plus three repo-layout facts

Both commits are local and not pushed. The PR head on GitHub is still e5b2e7541, and it stays there until v0.69.0 is on crates.io. The commits are in the shared object store, so from any aprender worktree on this box:

git worktree add /mnt/nvme-raid0/agent-wt/seat-3609 9cc89e3a9 && cd /mnt/nvme-raid0/agent-wt/seat-3609

Acceptance commands. Please run each one at 9cc89e3a9, compare the result with the claim beside it, and post your verdict per constraint.

  1. Constraint 1, direction 1 (a declared unknown token still resolves) and the cop's encode guard.
    Command: cargo test -p aprender-serve --lib tokenizer
    Claim: all pass, including the 9 rows in src/tokenizer_tests_unk_3609.rs. The count was 215 at e5b2e7541.
    The guard's row is no_unknown_token_and_a_byte_with_no_token_is_refused_by_name: remove byte 0xA9's glyph, with no unknown token, and construction errors naming 0xA9.
  2. Constraint 1, direction 2 (no vocabulary refuses by name).
    Command: git grep -nE 'token\{i\}|format!\("token\{' -- crates/apr-cli/src/commands/serve crates/aprender-serve/src/cli
    Claim: 1 hit at 9cc89e3a9, down from 14 on origin/main. The 13 removed are QA: SmolLM2 systematic 2-test failure across all sizes (92.6% pass rate) #226's vocabulary placeholders.
    The one left, routes.rs:222, is not a vocabulary placeholder. generate_streaming emits a fabricated SSE stream ("token0", "token1", then done) for any model. That is the same class as apr showcase's [1,2,3,4,5], but outside The BPE tokenizer requires a '<unk>' token that Qwen3.5 does not have — a universal unknown-token is an assumption, not a fact #3609's scope. R13: apr serve has three different HTTP surfaces and which one you get depends on the format of the file you passed #2507 (three HTTP surfaces) may already own it; check there before filing.
    Command: git grep -n 'no_vocabulary(' -- crates/apr-cli/src/commands/serve crates/aprender-serve/src/cli
    Claim: 2 definitions and 13 call sites (7 in apr-cli serve, 6 in aprender-serve cli).
    This direction has no behavioural unit row. Its evidence is the call sites plus cargo check. If you judge that insufficient, say so. That is a real finding, not a layout misread.
  3. Constraint 3 (a mutation turns the real Qwen3.5 fixture RED).
    In crates/aprender-serve/src/tokenizer.rs, BPETokenizer::new, make the unknown-token lookup unconditional again: token_to_id.get("<unk>").copied().ok_or_else(..)?.
    Command: cargo test -p aprender-serve --lib qwen35_real_vocabulary_builds_a_tokenizer_with_no_unknown_token
    Claim: RED.
    Then run git checkout -- crates/aprender-serve/src/tokenizer.rs.
  4. The fixture is a real header. python3 crates/aprender-serve/tests/fixtures/gguf-header-slices/generate.py ~/models/Qwen3.5-0.8B-IQ4_XS.gguf "$SCRATCH/q.gguf-header" --keep 1024 && sha256sum "$SCRATCH/q.gguf-header"
    Claim: the sha256 equals MANIFEST.json's output_sha256 (0855f961…), and the source header sha256 is 2eb5e86e….
    Also check: the header has 46 keys, eos_token_id = 248046, and no unknown_token_id key.
  5. Constraint 2 (prose and the query audit).
    Commands: pmat query --literal '<unk>' --files-with-matches and pmat query --literal 'unknown_token' --files-with-matches
    Claim: every hit is fixed or listed as kept in the PR body's audit table. book/src/examples/api-server.md and examples/model_cache.rs no longer say the vocabulary "must include <unk>".
  6. Builds. cargo check -p aprender-serve, cargo check -p aprender-serve --features cuda, cargo check -p apr-cli, cargo check -p apr-cli --features cuda
    Claim: all rc=0.
    apr-cli --features wgpu fails on main's finetune.rs; none of those errors is in a touched file.
  7. Complexity. Run pmat analyze complexity on crates/aprender-serve/src/tokenizer.rs.
    Claim: no function above cognitive 25. byte_fallback_id was extracted to keep encode under it.

Layout facts the lanes misread, all checked at 9cc89e3a9:

  • realizar is aprender-serve's [lib] name (Cargo.toml:42-43).
  • apr-cli's no_vocabulary is defined once, at handler_gpu_completion.rs:482, and reaches every serve site through include! (handlers.rs:1618-1619, handler_apr_cpu_completion.rs:869).

Disk note: / on this box hit 100% at about 10:06Z. I freed 42 GB of stale cargo target dirs under /tmp (no open handles, no writes in 6 h). Set CARGO_TARGET_DIR on the raid volume for these builds.

@noahgift

noahgift commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

Non-author measured seat-fill for PMAT-3609 (aprender-b3, 10:19Z): judged sha 9cc89e3a9, local compute only

Everything below was run in my own detached worktree at 9cc89e3a9 (/mnt/nvme-raid0/agent-wt/seat-3609), with CARGO_TARGET_DIR on the raid volume. Nothing was pushed and no CI was used.

# Constraint / check Command Measured vs claim
1 C1 dir. 1: a declared unknown token resolves; the cop's encode guard cargo test -p aprender-serve --lib tokenizer 215 passed, 0 failed. All 9 tokenizer_tests_unk_3609 rows ok, incl. no_unknown_token_and_a_byte_with_no_token_is_refused_by_name and tinyllama_real_header_declares_unk_and_it_still_resolves ✅ matches (215, 9 rows)
2a C1 dir. 2: placeholders gone git grep -nE 'token\{i\}|format!\("token\{' -- crates/apr-cli/src/commands/serve crates/aprender-serve/src/cli 1 hit (routes.rs:222) at 9cc89e3a9, 14 on origin/main ✅ matches
2b C1 dir. 2: refuses by name git grep -n 'no_vocabulary(' … 2 definitions (handler_gpu_completion.rs:482, mod_server_commands.rs:13), 13 call sites (apr-cli 7, aprender-serve 6) ✅ matches, but see finding A
3 C3: mutation turns the real Qwen3.5 fixture RED In BPETokenizer::new, replace the match unk_token.into() with the pre-#3609 unconditional token_to_id.get("<unk>").ok_or_else(..)?, then cargo test -p aprender-serve --lib qwen35_real_vocabulary_builds_a_tokenizer_with_no_unknown_token RED, rc=101: a real vocabulary without <unk> is not refused (#3609): UnsupportedOperation { … "vocabulary must include <unk> token" } (panic at tokenizer_tests_unk_3609.rs:71). File restored with git checkout --, tree clean ✅ the row discriminates
4 The fixture is a real header generate.py ~/models/Qwen3.5-0.8B-IQ4_XS.gguf … --keep 1024; sha256sum output 0855f961…cb082, equal to MANIFEST output_sha256 and to the tracked qwen3.5-0.8b.gguf-header. Source header (first 10,943,082 bytes) 2eb5e86e…5d36, equal to MANIFEST. Parsed independently: GGUF v3, 46 keys, eos_token_id = 248046, no tokenizer.ggml.unknown_token_id ✅ matches
5 C2: prose and the query audit pmat query --literal '<unk>' --files-with-matches (81 files); … 'unknown_token' (17) Union 87 files. Diffed against every path the body's audit lists: 2 unlisted, docs/roadmaps/entries/PMAT-3609.yaml and docs/roadmaps/roadmap.yaml. Both are this ticket's own text, added by 9cc89e3a9 after the audit was written. api-server.md and examples/model_cache.rs: 0 "must include <unk>" sentences ✅ holds (the counts drifted 82→81 and 13→17 because of those 2 files and the index)
6 Builds cargo check -p aprender-serve [--features cuda], cargo check -p apr-cli [--features cuda] all four rc=0 ✅ matches
7 Complexity bash scripts/check_complexity_ratchet.sh at 9cc89e3a9 PASS (D2): 79a3af79d vs 9cc89e3a9 … none new, none grown ✅ on the ratchet, but see finding B

Verdict: PASS on all three constraints, with two findings.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The BPE tokenizer requires a '<unk>' token that Qwen3.5 does not have — a universal unknown-token is an assumption, not a fact

1 participant