glm5.2: preserve input marking in prompt tokenization (fix output stalls on stray message tags) - #148
Conversation
|
Please retarget this PR from I re-audited the resulting patch against
Please also add focused regression tests covering:
Once the PR targets |
…lls on stray message tags)
Port of llama.cpp commit f72dba3ad ('glm5.2 fixed stray message tags in input'),
targeted at v0.4.7.
- Thread jinja string parts (with is_input metadata) through chat template
application: rendered prompt, BOS/EOS stripping, and the continuation
generation prompt in the automatic and all specialized parser paths.
The parts mirror data.prompt exactly (position-based edits keep them in
sync even when a token spans a part boundary).
- common_tokenize_parts(): request-provided content (is_input) is
tokenized with parse_special=false so stray message/special tags in
user/tool content or continuation content cannot be injected as real
tokens; template parts keep parse_special=true. When no is_input part
contains special-token text, the prompt is tokenized in a single pass
over the concatenated text (merging adjacent same-type parts), so the
token ids are identical to the legacy whole-prompt tokenization.
- GPT-OSS: the <|return|> -> <|end|> compatibility replacement is applied
to the prompt parts at the same global position as in the prompt string,
so server-side tokenization of prompt_parts sees the same text.
- OAI-compat path serializes prompt_parts; the inference route
(handle_completions_impl) and the token counting route
(handle_count_tokens) share one input-marking-aware tokenization path
(server_tokenize_prompt_parts), including media interleaving for MTMD
contexts. The prompt_parts branch runs before the MTMD branch, so chat
requests served by multimodal-capable models receive the protection even
without attached media.
- Regression tests:
- tests/test-prompt-parts.cpp (ctest, vocab-only GGUF + chat templates):
special-token injection in ordinary user and tool input, assistant
continuation provenance in the automatic and specialized (gpt-oss,
qwen3-coder, gemma4) parser paths, GPT-OSS <|return|> replacement,
completion/token-count consistency through the OAI serialization,
unchanged token ids for normal prompts without injected special tokens
(incl. a BPE merge straddling a part boundary), MTMD part layout
- tests/test-prompt-parts-mtmd.py (opt-in, live MTMD context):
completion/token-count agreement with and without media, and
special-token protection in user content in an MTMD context
9427726 to
27d15b1
Compare
|
|
Thanks! |
2&3. Assistant continuation content marked as template text, now handles all specialize parsers.(incl gpt-oss)
common_tokenize_parts() (chat.cpp) now has a fast path: common_chat_parts_have_special_input() (mirroring the tokenizer's own parse_special=false rule for control/unknown tokens) checks whether any is_input part contains special-token text; if not, the whole concatenated prompt is tokenized in a single parse_special=true pass — token IDs identical to legacy. The protection path additionally merges adjacent same-type parts (the jinja runtime merges them at render, but continuation parts appended later didn't) so normal merges across post-render template boundaries are kept.
Verified: test_normal_prompt_token_ids_unchanged — real multi-turn Qwen3 prompt: parts tokenization == legacy whole-prompt tokenization; plus a synthetic BPE case ("Hel"|"lo" straddling a boundary) that provably fails under naive per-part tokenization and passes here, and the same for the protection path.