33import ctypes
44import enum
55import os
6+ import sys
67
78from typing import (
89 Callable ,
@@ -1566,6 +1567,9 @@ def __init__(
15661567 sorted = False ,
15671568 )
15681569
1570+ # Active Python reasoning-budget sampler for this sampling context.
1571+ self .reasoning_budget_sampler : Optional [ReasoningBudgetSampler ] = None
1572+
15691573 # Sampler chain
15701574 if _existing_sampler :
15711575 self .sampler_chain = _existing_sampler
@@ -1583,9 +1587,6 @@ def __init__(
15831587 params .grammar_triggers ,
15841588 )
15851589
1586- # Active Python reasoning-budget sampler for this sampling context.
1587- self .reasoning_budget_sampler : Optional [ReasoningBudgetSampler ] = None
1588-
15891590 def _build_sampler_chain (self ):
15901591 """
15911592 Build sampler chain aligned with llama.cpp common_sampler_init
@@ -1682,6 +1683,7 @@ def _build_sampler_chain(self):
16821683 ),
16831684 start_max_tokens = p .reasoning_start_max_tokens ,
16841685 wait_utf8 = True ,
1686+ verbose = getattr (m , "verbose" , False ),
16851687 )
16861688
16871689 # Keep a direct Python reference so force_reasoning_budget() can
@@ -2131,6 +2133,7 @@ def __init__(
21312133 initial_state : ReasoningBudgetState = ReasoningBudgetState .IDLE ,
21322134 start_max_tokens : Optional [int ] = 32 ,
21332135 wait_utf8 : bool = True ,
2136+ verbose : bool = False ,
21342137 ):
21352138 """
21362139 Initialize the reasoning budget sampler.
@@ -2182,6 +2185,11 @@ def __init__(
21822185 If True, when the budget is exhausted on an incomplete UTF-8
21832186 token piece, wait until a complete UTF-8 boundary before forcing
21842187 the end sequence.
2188+
2189+ verbose:
2190+ If True, print high-level reasoning-budget state transitions to
2191+ stderr. Logging is intentionally limited to transitions instead
2192+ of per-token events to avoid noisy generation output.
21852193 """
21862194 if model is None :
21872195 raise ValueError ("model must not be None" )
@@ -2242,6 +2250,10 @@ def __init__(
22422250 # Whether to delay forcing until a complete UTF-8 boundary.
22432251 self .wait_utf8 = wait_utf8
22442252
2253+ # Whether to print high-level state transition logs.
2254+ # This follows the model/runtime verbose flag and avoids per-token spam.
2255+ self .verbose = verbose
2256+
22452257 # Keep cloned Python sampler objects alive when llama.cpp clones the
22462258 # sampler chain. Without this, cloned Python callbacks could be garbage
22472259 # collected while C still holds function pointers to them.
@@ -2258,6 +2270,19 @@ def __init__(
22582270 name = "reasoning-budget" ,
22592271 )
22602272
2273+ if self .verbose :
2274+ print (
2275+ f"ReasoningBudgetSampler: initialized "
2276+ f"(state={ self .state .name } , budget={ self .reasoning_budget } , "
2277+ f"start_max_tokens={ self .start_max_tokens } , wait_utf8={ self .wait_utf8 } )." ,
2278+ file = sys .stderr ,
2279+ )
2280+
2281+ def _log (self , message : str ) -> None :
2282+ """Print a verbose reasoning-budget state transition message."""
2283+ if self .verbose :
2284+ print (f"ReasoningBudgetSampler: { message } " , file = sys .stderr )
2285+
22612286 def force (self ) -> bool :
22622287 """
22632288 Manually transition the active reasoning block into forced ending.
@@ -2278,6 +2303,7 @@ def force(self) -> bool:
22782303 self .state = ReasoningBudgetState .FORCING
22792304 self .force_pos = 0
22802305 self .end_matcher .reset ()
2306+ self ._log ("manual force requested; entering FORCING state." )
22812307 return True
22822308
22832309 def _token_utf8_complete (self , token : int ) -> bool :
@@ -2313,9 +2339,11 @@ def _start_counting(self) -> None:
23132339 self .remaining = self .reasoning_budget
23142340 self .end_matcher .reset ()
23152341 self .force_pos = 0
2342+ self ._log (f"reasoning_start matched; entering COUNTING state (budget={ self .reasoning_budget } )." )
23162343
23172344 if self .remaining <= 0 :
23182345 self .state = ReasoningBudgetState .FORCING
2346+ self ._log ("budget is 0; entering FORCING state immediately." )
23192347
23202348 def _accept (self , token : int ) -> None :
23212349 """
@@ -2345,6 +2373,10 @@ def _accept(self, token: int) -> None:
23452373 and self .generated_tokens >= self .start_max_tokens
23462374 ):
23472375 self .state = ReasoningBudgetState .DONE
2376+ self ._log (
2377+ f"reasoning_start not found within { self .start_max_tokens } generated tokens; "
2378+ "switching to DONE passthrough."
2379+ )
23482380 return
23492381
23502382 if self .state in (
@@ -2353,6 +2385,7 @@ def _accept(self, token: int) -> None:
23532385 ):
23542386 if self .end_matcher .advance (token ):
23552387 self .state = ReasoningBudgetState .DONE
2388+ self ._log ("reasoning_end matched naturally; switching to DONE passthrough." )
23562389 return
23572390
23582391 utf8_complete = self ._token_utf8_complete (token )
@@ -2362,6 +2395,7 @@ def _accept(self, token: int) -> None:
23622395 self .state = ReasoningBudgetState .FORCING
23632396 self .force_pos = 0
23642397 self .end_matcher .reset ()
2398+ self ._log ("UTF-8 boundary reached; entering FORCING state." )
23652399 return
23662400
23672401 self .remaining -= 1
@@ -2370,15 +2404,18 @@ def _accept(self, token: int) -> None:
23702404 self .state = ReasoningBudgetState .FORCING
23712405 self .force_pos = 0
23722406 self .end_matcher .reset ()
2407+ self ._log ("reasoning budget exhausted; entering FORCING state." )
23732408 else :
23742409 self .state = ReasoningBudgetState .WAITING_UTF8
23752410 self .end_matcher .reset ()
2411+ self ._log ("reasoning budget exhausted; waiting for UTF-8 boundary before forcing." )
23762412 return
23772413
23782414 if self .state == ReasoningBudgetState .FORCING :
23792415 self .force_pos += 1
23802416 if self .force_pos >= len (self .forced_tokens ):
23812417 self .state = ReasoningBudgetState .DONE
2418+ self ._log ("forced end sequence completed; switching to DONE passthrough." )
23822419 return
23832420
23842421 if self .state == ReasoningBudgetState .DONE :
@@ -2448,6 +2485,8 @@ def _reset(self) -> None:
24482485 if self .state == ReasoningBudgetState .COUNTING and self .remaining <= 0 :
24492486 self .state = ReasoningBudgetState .FORCING
24502487
2488+ self ._log (f"reset to { self .state .name } state." )
2489+
24512490 def _clone (self ):
24522491 """
24532492 Clone the full runtime state.
@@ -2464,6 +2503,7 @@ def _clone(self):
24642503 initial_state = self .initial_state ,
24652504 start_max_tokens = self .start_max_tokens ,
24662505 wait_utf8 = self .wait_utf8 ,
2506+ verbose = self .verbose ,
24672507 )
24682508
24692509 cloned .remaining = self .remaining
0 commit comments