Skip to content

serve: GGUF prompts tokenized greedily, <|im_end|> after text splits into 6 ids (+3 per turn vs llama.cpp) #4804

Closed as not planned

Description

@noahgift

Sub-issue of #4662 (B6). Refs #4662.

Defect. apr serve run <model.gguf> tokenizes prompts with a greedy longest-match BPETokenizer that has no merges and no special-token split, so <|im_end|> straight after text is not one token. .<|im_end|>\n encodes as .< |i m _end | >\n (6 ids) where llama.cpp gives . <|im_end|> \n (3 ids). The model sees a turn end it never saw in training, which feeds the degenerate tail in #4662.

Where. Both GGUF serve states build the tokenizer as BPETokenizer::new(vocab, vec![], unk):

  • AppState::with_quantized_model_and_vocab (crates/aprender-serve/src/api/mod_app_state_gpu.rs, CPU serve via crates/apr-cli/src/commands/serve/server.rs)
  • AppState::with_cuda_model_and_vocab (same file, CUDA serve when the GGUF has no merges handed over)

With empty merges, BPETokenizer::encode (crates/aprender-serve/src/tokenizer.rs) takes the greedy loop, which never consults special tokens. GGUFModel::encode (crates/aprender-serve/src/gguf/token.rs, canonical byte-level BPE with tokenizer_st_partition, #3726/#3993) is not used by these routes.

Measured (main build 6192aa9, CPU serve vs llama.cpp server, same GGUF, llama.cpp's /apply-template text fed to both /tokenize):

Model Prompt llama.cpp apr Diff
Qwen2.5-0.5B-Instruct f16 4 prompts 35, 35, 38, 32 41, 41, 44, 38 +6 each: two `<
Qwen3-Coder-30B-A3B Q4_K_M 4 prompts 14, 14, 17, 11 17, 17, 20, 14 +3 each: one `<

/v1/chat/completions usage.prompt_tokens moves by the same amount (0.5B: 42/36, 42/36, 45/39, 39/33). <|im_start|> after a newline happens to survive (greedy longest match from a line start). Prompt 2 on the 30B also shows ordinary text off the merges (2 ids differ in "haiku about rust."), the #3726 class.

Done when. The GGUF serve routes encode with the same tokenizer as GGUFModel::encode, and a test asserts the ids of a ChatML prompt with <|im_end|> right after punctuation equal llama.cpp's (e.g. ?<|im_end|>\n → 30 151645 198 on Qwen2.5).

Owner aprender-a7n

Activity

  1. added this to the backlog milestone on Oct 8, 2026
  2. noahgift commented on Oct 13, 2026

    @noahgift
    ContributorAuthor

    park proposal: parked, sub-issue of #4662; no commit on main @7f9a15a52b since 2026-10-04 touches the special-token split (git log --grep on im_end / special token: none). It waits with its parent.

  3. added
    parkedClosed over epic budget (APR-EPIC-001 rule 5). Reopen = pull.
    on Oct 13, 2026
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

    parkedClosed over epic budget (APR-EPIC-001 rule 5). Reopen = pull.

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions