From d7ff074c87ecacd57d5760e2f678866ba9fe7149 Mon Sep 17 00:00:00 2001 From: Berk Idem <55372926+berkidem@users.noreply.github.com> Date: Fri, 10 Apr 2026 05:49:14 -0400 Subject: [PATCH] common : enable reasoning budget sampler for gemma4 (#21697) * fix: enable reasoning budget sampler for gemma4 Add thinking_start_tag and thinking_end_tag to common_chat_params_init_gemma4(). Without these, the reasoning budget sampler never activates for gemma4. Make the newline after "thought" optional in the PEG parser to handle budget=0 (sampler forces end tag before the newline). Add test case for empty thinking block. Fixes #21487 * use p.space() instead of p.optional(p.literal("\n")) in gemma4 thought parser --- common/chat.cpp | 8 +++++--- tests/test-chat.cpp | 7 +++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/common/chat.cpp b/common/chat.cpp index f79fe703db..03cd9e25f4 100644 --- a/common/chat.cpp +++ b/common/chat.cpp @@ -1083,7 +1083,9 @@ static common_chat_params common_chat_params_init_gemma4(const common_chat_templ data.prompt = common_chat_template_direct_apply_impl(tmpl, inputs); data.format = COMMON_CHAT_FORMAT_PEG_GEMMA4; - data.supports_thinking = true; + data.supports_thinking = true; + data.thinking_start_tag = "<|channel>thought"; + data.thinking_end_tag = ""; data.preserved_tokens = { "<|channel>", @@ -1102,9 +1104,9 @@ static common_chat_params common_chat_params_init_gemma4(const common_chat_templ auto start = p.rule("start", p.prefix(inputs.generation_prompt, "<|channel>")); if (extract_reasoning) { - p.rule("thought", p.literal("<|channel>thought\n") + p.reasoning(p.until("")) + p.literal("")); + p.rule("thought", p.literal("<|channel>thought") + p.space() + p.reasoning(p.until("")) + p.literal("")); } else { - p.rule("thought", p.content(p.literal("<|channel>thought\n") + p.until("") + p.literal(""))); + p.rule("thought", p.content(p.literal("<|channel>thought") + p.space() + p.until("") + p.literal(""))); } auto thought = (p.peek(p.literal("<|channel>")) + p.ref("thought")) | p.negate(p.literal("<|channel>")); diff --git a/tests/test-chat.cpp b/tests/test-chat.cpp index f8b2b584e2..1d3a77bc05 100644 --- a/tests/test-chat.cpp +++ b/tests/test-chat.cpp @@ -1988,6 +1988,13 @@ static void test_template_output_peg_parsers(bool detailed_debug) { .expect(message_assist_thoughts) .run(); + // Empty reasoning (budget=0: sampler forces end tag before newline) + tst.test( + "<|channel>thoughtHello, world!\nWhat's up?") + .reasoning_format(COMMON_REASONING_FORMAT_AUTO) + .expect(simple_assist_msg("Hello, world!\nWhat's up?", "")) + .run(); + // Reasoning and content with reasoning_format = none tst.test( "<|channel>thought\nI'm\nthinkingHello, world!\nWhat's up?")