char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
910196f6b
char/aliceai-t5-moe
1#include "reasoning-budget.h" 2#include "unicode.h" 3 4#include "llama.h" 5#include "ggml.h" 6 7#ifdef NDEBUG 8#undef NDEBUG 9#endif 10 11#include <cmath> 12#include <cstddef> 13#include <cstdio> 14#include <string> 15#include <vector> 16 17// Reasoning budget sampler test helper 18// These tests use nullptr vocab which safely falls back to treating all tokens as complete 19// (The UTF-8 boundary detection logic is tested separately in test_utf8_boundary_detection) 20static void test_reasoning_budget ( 21const char * test_name , 22const std::vector < llama_token > & sequence , 23const std::vector < llama_tokens > & start_seqs , 24const std::vector < llama_tokens > & end_seqs , 25const std::vector < llama_token > & forced_tokens , 26int32_t budget , 27common_reasoning_budget_state initial_state , 28size_t expected_force_start ,// token index where forcing should start (SIZE_MAX = never) 29size_t expected_force_end // token index where forcing should end (after this, no more forcing) 30) { 31// Find the maximum token ID to ensure our vocab covers all tokens 32llama_token max_token = 0 ; 33for (auto t :sequence )max_token = std::max (max_token ,t ); 34for (const auto & seq :start_seqs ) { 35for (auto t :seq )max_token = std::max (max_token ,t ); 36 } 37for (const auto & seq :end_seqs ) { 38for (auto t :seq )max_token = std::max (max_token ,t ); 39 } 40for (auto t :forced_tokens )max_token = std::max (max_token ,t ); 41 42// Create a minimal sampler with mock vocabulary 43// For this test, we use nullptr as vocab since we're testing state transitions 44// The UTF-8 boundary check will treat all tokens as complete (safe fallback) 45auto * sampler = common_reasoning_budget_init ( 46nullptr ,// vocab - not used for basic state machine tests 47start_seqs , 48end_seqs , 49forced_tokens , 50budget , 51initial_state 52 ); 53 54// Create a test token data array for checking forcing behavior 55// Vocab size must be large enough to include all tokens (start, end, forced, sequence) 56 std::vector < llama_token_data > cur ; 57const size_t n_vocab = (size_t )max_token + 1 ; 58for (size_t i = 0 ;i < n_vocab ;i ++ ) { 59cur .emplace_back (llama_token_data {(llama_token )i ,logf ((float )(i + 1 )),0.0f }); 60 } 61llama_token_data_array cur_p = {cur .data (),cur .size (),-1 , false }; 62 63size_t actual_force_start = SIZE_MAX ; 64size_t actual_force_end = SIZE_MAX ; 65 66// Feed the sequence and track when forcing occurs 67for (size_t i = 0 ;i < sequence .size ();i ++ ) { 68// Check if we're in forcing state by applying and seeing if logits are modified 69cur_p .selected = -1 ; 70for (size_t j = 0 ;j < cur .size ();j ++ ) { 71cur [j ].logit = logf ((float )(j + 1 ));// reset logits 72 } 73 74llama_sampler_apply (sampler ,& cur_p ); 75 76// Check if forcing is active (all logits except one should be -INFINITY) 77size_t finite_count = 0 ; 78llama_token finite_token = -1 ; 79for (size_t j = 0 ;j < cur .size ();j ++ ) { 80if (std::isfinite (cur [j ].logit )) { 81finite_count ++ ; 82finite_token = cur [j ].id ; 83 } 84 } 85 86llama_sampler_accept (sampler ,sequence [i ]); 87 88fprintf (stderr ," i=%zu: token=%d, finite_count=%zu, finite_token=%d\n" ,i , (int )sequence [i ],finite_count , (int )finite_token ); 89 90if (finite_count == 1 ) { 91if (actual_force_start == SIZE_MAX ) { 92actual_force_start = i ; 93 } 94actual_force_end = i ; 95 }else if (actual_force_start != SIZE_MAX && actual_force_end != SIZE_MAX ) { 96// Forcing stopped 97break ; 98 } 99 } 100 101llama_sampler_free (sampler ); 102 103// Verify forcing occurred at expected positions 104if (expected_force_start == SIZE_MAX ) { 105if (actual_force_start != SIZE_MAX ) { 106fprintf (stderr ,"Test '%s' FAILED: Expected no forcing, but forcing occurred at %zu\n" ,test_name ,actual_force_start ); 107GGML_ASSERT (false&& "Expected no forcing, but forcing occurred" ); 108 } 109 }else { 110if (actual_force_start == SIZE_MAX ) { 111fprintf (stderr ,"Test '%s' FAILED: Expected forcing but none occurred\n" ,test_name ); 112GGML_ASSERT (false&& "Expected forcing but none occurred" ); 113 } 114if (actual_force_start != expected_force_start ) { 115fprintf (stderr ,"Test '%s' FAILED: Forcing started at %zu, expected %zu\n" ,test_name ,actual_force_start ,expected_force_start ); 116GGML_ASSERT (false&& "Forcing started at wrong position" ); 117 } 118 } 119 120if (expected_force_end != SIZE_MAX ) { 121if (actual_force_end < expected_force_end ) { 122fprintf (stderr ,"Test '%s' FAILED: Forcing ended at %zu, expected >= %zu\n" ,test_name ,actual_force_end ,expected_force_end ); 123GGML_ASSERT (false&& "Forcing ended too early" ); 124 } 125 } 126 127fprintf (stderr ," Test '%s' passed (force_start=%zu, force_end=%zu)\n" ,test_name ,actual_force_start ,actual_force_end ); 128 (void )sequence ; 129} 130 131static llama_token get_forced_token (struct llama_sampler * sampler ,llama_token max_token ) { 132 std::vector < llama_token_data > cur ; 133const size_t n_vocab = (size_t )max_token + 1 ; 134for (size_t i = 0 ;i < n_vocab ;i ++ ) { 135cur .emplace_back (llama_token_data {(llama_token )i ,logf ((float ) (i + 1 )),0.0f }); 136 } 137 138llama_token_data_array cur_p = {cur .data (),cur .size (),-1 , false }; 139llama_sampler_apply (sampler ,& cur_p ); 140 141size_t finite_count = 0 ; 142llama_token finite_token = LLAMA_TOKEN_NULL ; 143for (size_t i = 0 ;i < cur .size ();i ++ ) { 144if (std::isfinite (cur [i ].logit )) { 145finite_count ++ ; 146finite_token = cur [i ].id ; 147 } 148 } 149 150GGML_ASSERT (finite_count == 1 && "sampler is not forcing exactly one token" ); 151return finite_token ; 152} 153 154static void test_reasoning_budget_clone_mid_counting () { 155const std::vector < llama_token > start = {100 }; 156const std::vector < llama_token > end = {101 }; 157const std::vector < llama_token > forced = {102 ,101 }; 158 159auto * sampler = common_reasoning_budget_init (nullptr , {start }, {end },forced ,2 ,REASONING_BUDGET_IDLE ); 160 161llama_sampler_accept (sampler ,100 );// COUNTING, remaining=2 162llama_sampler_accept (sampler ,50 );// COUNTING, remaining=1 163 164auto * clone = llama_sampler_clone (sampler ); 165llama_sampler_accept (clone ,51 );// should exhaust the cloned remaining budget 166 167GGML_ASSERT (get_forced_token (clone ,102 )== 102 && "cloned counting state lost remaining budget" ); 168 169llama_sampler_free (clone ); 170llama_sampler_free (sampler ); 171} 172 173static void test_reasoning_budget_clone_mid_forcing () { 174const std::vector < llama_token > start = {100 }; 175const std::vector < llama_token > end = {101 }; 176const std::vector < llama_token > forced = {102 ,101 }; 177 178auto * sampler = common_reasoning_budget_init (nullptr , {start }, {end },forced ,0 ,REASONING_BUDGET_FORCING ); 179 180GGML_ASSERT (get_forced_token (sampler ,102 )== 102 ); 181llama_sampler_accept (sampler ,102 );// advance to the second forced token 182 183auto * clone = llama_sampler_clone (sampler ); 184 185GGML_ASSERT (get_forced_token (clone ,102 )== 101 && "cloned forcing state lost force position" ); 186 187llama_sampler_free (clone ); 188llama_sampler_free (sampler ); 189} 190 191static void test_reasoning_budget_force_manual () { 192const std::vector < llama_token > start = {100 }; 193const std::vector < llama_token > end = {101 }; 194const std::vector < llama_token > forced = {102 ,101 }; 195 196// if COUNTING, force() succeeds and begins forcing the end sequence from the start 197 { 198auto * sampler = common_reasoning_budget_init (nullptr , {start }, {end },forced ,5 ,REASONING_BUDGET_IDLE ); 199 200llama_sampler_accept (sampler ,100 );// COUNTING, remaining=5 201llama_sampler_accept (sampler ,50 );// COUNTING, remaining=4 202GGML_ASSERT (common_reasoning_budget_get_state (sampler )== REASONING_BUDGET_COUNTING ); 203 204GGML_ASSERT (common_reasoning_budget_force (sampler )&& "force() should succeed from COUNTING" ); 205GGML_ASSERT (common_reasoning_budget_get_state (sampler )== REASONING_BUDGET_FORCING ); 206 207// forces the configured sequence from force_pos=0, then transitions to DONE 208GGML_ASSERT (get_forced_token (sampler ,102 )== 102 ); 209llama_sampler_accept (sampler ,102 ); 210GGML_ASSERT (get_forced_token (sampler ,102 )== 101 ); 211llama_sampler_accept (sampler ,101 ); 212GGML_ASSERT (common_reasoning_budget_get_state (sampler )== REASONING_BUDGET_DONE ); 213 214llama_sampler_free (sampler ); 215 } 216 217// if IDLE, force() is a no-op 218 { 219auto * sampler = common_reasoning_budget_init (nullptr , {start }, {end },forced ,5 ,REASONING_BUDGET_IDLE ); 220 221GGML_ASSERT (!common_reasoning_budget_force (sampler )&& "force() must not transition from IDLE" ); 222GGML_ASSERT (common_reasoning_budget_get_state (sampler )== REASONING_BUDGET_IDLE ); 223 224llama_sampler_free (sampler ); 225 } 226 227// if DONE, force() is a no-op 228 { 229auto * sampler = common_reasoning_budget_init (nullptr , {start }, {end },forced ,5 ,REASONING_BUDGET_IDLE ); 230 231llama_sampler_accept (sampler ,100 );// COUNTING 232llama_sampler_accept (sampler ,101 );// natural end -> DONE 233GGML_ASSERT (common_reasoning_budget_get_state (sampler )== REASONING_BUDGET_DONE ); 234 235GGML_ASSERT (!common_reasoning_budget_force (sampler )&& "force() must not transition from DONE" ); 236GGML_ASSERT (common_reasoning_budget_get_state (sampler )== REASONING_BUDGET_DONE ); 237 238llama_sampler_free (sampler ); 239 } 240 241// if FORCING, force() is a no-op and must not rewind the force position 242 { 243auto * sampler = common_reasoning_budget_init (nullptr , {start }, {end },forced ,0 ,REASONING_BUDGET_FORCING ); 244 245GGML_ASSERT (get_forced_token (sampler ,102 )== 102 ); 246llama_sampler_accept (sampler ,102 );// advance to the second forced token (force_pos=1) 247 248GGML_ASSERT (!common_reasoning_budget_force (sampler )&& "force() must not transition from FORCING" ); 249GGML_ASSERT (common_reasoning_budget_get_state (sampler )== REASONING_BUDGET_FORCING ); 250GGML_ASSERT (get_forced_token (sampler ,102 )== 101 && "force() must not rewind the force position" ); 251 252llama_sampler_free (sampler ); 253 } 254 255// a null sampler is safely ignored 256GGML_ASSERT (!common_reasoning_budget_force (nullptr )); 257 258fprintf (stderr ," Test 'manual force transition' passed\n" ); 259} 260 261static void test_reasoning_budget_end_match () { 262const std::vector < llama_tokens > start = {{100 }}; 263const std::vector < llama_tokens > end = {{101 }, {103 ,104 }}; 264 265// natural end records the sequence that matched; re-arming clears it 266 { 267auto * sampler = common_reasoning_budget_init (nullptr ,start ,end , {102 ,101 },5 ,REASONING_BUDGET_IDLE ); 268 269GGML_ASSERT (common_reasoning_budget_get_end_match (sampler )== nullptr ); 270 271llama_sampler_accept (sampler ,100 );// COUNTING 272llama_sampler_accept (sampler ,50 ); 273llama_sampler_accept (sampler ,103 ); 274llama_sampler_accept (sampler ,104 );// end matched via {103, 104}, DONE 275 276const llama_tokens * matched = common_reasoning_budget_get_end_match (sampler ); 277GGML_ASSERT (matched != nullptr ); 278GGML_ASSERT (* matched == llama_tokens ({103 ,104 })); 279 280llama_sampler_accept (sampler ,100 );// re-arm, COUNTING 281GGML_ASSERT (common_reasoning_budget_get_end_match (sampler )== nullptr ); 282 283llama_sampler_free (sampler ); 284 } 285 286// overlapping end sequences: the longest one ending at the position wins 287 { 288const std::vector < llama_tokens > end_overlap = {{104 }, {103 ,104 }}; 289 290auto * sampler = common_reasoning_budget_init (nullptr ,start ,end_overlap , {102 ,104 },5 ,REASONING_BUDGET_IDLE ); 291 292llama_sampler_accept (sampler ,100 );// COUNTING 293llama_sampler_accept (sampler ,103 ); 294llama_sampler_accept (sampler ,104 );// both {104} and {103, 104} end here 295 296const llama_tokens * matched = common_reasoning_budget_get_end_match (sampler ); 297GGML_ASSERT (matched != nullptr ); 298GGML_ASSERT (* matched == llama_tokens ({103 ,104 })); 299 300llama_sampler_free (sampler ); 301 } 302 303// forcing records the end sequence terminating forced_tokens 304 { 305auto * sampler = common_reasoning_budget_init (nullptr ,start ,end , {102 ,103 ,104 },0 ,REASONING_BUDGET_FORCING ); 306 307llama_sampler_accept (sampler ,102 ); 308llama_sampler_accept (sampler ,103 ); 309GGML_ASSERT (common_reasoning_budget_get_end_match (sampler )== nullptr ); 310llama_sampler_accept (sampler ,104 );// forced sequence complete, DONE 311 312const llama_tokens * matched = common_reasoning_budget_get_end_match (sampler ); 313GGML_ASSERT (matched != nullptr ); 314GGML_ASSERT (* matched == llama_tokens ({103 ,104 })); 315 316llama_sampler_free (sampler ); 317 } 318 319// forced_tokens not ending with a known end sequence records nothing 320 { 321auto * sampler = common_reasoning_budget_init (nullptr ,start ,end , {102 },0 ,REASONING_BUDGET_FORCING ); 322 323llama_sampler_accept (sampler ,102 );// forced sequence complete, DONE 324GGML_ASSERT (common_reasoning_budget_get_state (sampler )== REASONING_BUDGET_DONE ); 325GGML_ASSERT (common_reasoning_budget_get_end_match (sampler )== nullptr ); 326 327llama_sampler_free (sampler ); 328 } 329 330// a null sampler is safely ignored 331GGML_ASSERT (common_reasoning_budget_get_end_match (nullptr )== nullptr ); 332 333fprintf (stderr ," Test 'matched end sequence' passed\n" ); 334} 335 336// UTF-8 boundary detection unit test 337// Tests common_utf8_is_complete() from reasoning-budget.h 338static void test_utf8_boundary_detection () { 339// Complete sequences 340GGML_ASSERT (common_utf8_is_complete ("hello" )); 341GGML_ASSERT (common_utf8_is_complete ("" )); 342GGML_ASSERT (common_utf8_is_complete ("\xC2\xA0" ));// complete 2-byte UTF-8 (U+00A0) 343GGML_ASSERT (common_utf8_is_complete ("\xE2\x80\x9C" ));// complete 3-byte UTF-8 (left double quote) 344GGML_ASSERT (common_utf8_is_complete ("\xF0\x9F\x98\x80" ));// complete 4-byte UTF-8 (emoji) 345GGML_ASSERT (common_utf8_is_complete ("abc\xC3\xA9" ));// ASCII + complete 2-byte 346 347// Incomplete sequences 348GGML_ASSERT (!common_utf8_is_complete (std::string ("\xC2" ,1 )));// 2-byte start, missing continuation 349GGML_ASSERT (!common_utf8_is_complete (std::string ("\xE2\x80" ,2 )));// 3-byte start + 1 cont, missing 1 350GGML_ASSERT (!common_utf8_is_complete (std::string ("\xE2" ,1 )));// 3-byte start, missing 2 351GGML_ASSERT (!common_utf8_is_complete (std::string ("\xF0\x9F\x98" ,3 )));// 4-byte start + 2 cont, missing 1 352GGML_ASSERT (!common_utf8_is_complete (std::string ("\xF0\x9F" ,2 )));// 4-byte start + 1 cont, missing 2 353GGML_ASSERT (!common_utf8_is_complete (std::string ("\xF0" ,1 )));// 4-byte start, missing 3 354GGML_ASSERT (!common_utf8_is_complete (std::string ("\x80" ,1 )));// orphan continuation byte 355 356// Mixed: ASCII followed by start of multi-byte 357GGML_ASSERT (!common_utf8_is_complete (std::string ("hello\xC3" ,6 )));// ASCII + incomplete 2-byte 358GGML_ASSERT (common_utf8_is_complete (std::string ("hello\xC3\xA9" ,7 )));// ASCII + complete 2-byte 359} 360 361int main (void ) { 362// Reasoning budget sampler tests 363printf ("Testing reasoning budget sampler... " ); 364 365// Test 1: Basic budget with start/end tokens - no forcing (natural end before budget exhausted) 366 { 367const std::vector < llama_token > start = {100 };// start token 368const std::vector < llama_token > end = {101 };// end token 369const std::vector < llama_token > forced = {102 };// forced token (not used in this test) 370const std::vector < llama_token > sequence = {100 ,50 ,51 ,101 ,52 };// start, two tokens, end, one more 371 372test_reasoning_budget ("natural end before budget exhausted" ,sequence , {start }, {end },forced , 3735 ,// budget of 5 tokens 374REASONING_BUDGET_IDLE , 375SIZE_MAX ,SIZE_MAX );// no forcing expected (natural end) 376 } 377 378// Test 2: Budget exhausted, forcing should occur 379// Flow: i=0 apply()->passthrough, accept(100)->COUNTING; i=1 accept(50)->remaining=1 380// i=2 accept(51)->remaining=0->FORCING; i=3 apply() forces token[0]; i=4 apply() forces token[1] 381// At i=4, accept() advances force_pos to 2 which equals forced_tokens.size(), so state becomes DONE 382 { 383const std::vector < llama_token > start = {100 }; 384const std::vector < llama_token > end = {101 }; 385const std::vector < llama_token > forced = {102 ,101 };// forced message + end 386const std::vector < llama_token > sequence = {100 ,50 ,51 ,52 ,53 };// start + 4 tokens (budget=2) 387 388test_reasoning_budget ("budget exhausted forcing" ,sequence , {start }, {end },forced , 3892 ,// budget of 2 tokens 390REASONING_BUDGET_IDLE , 3913 ,// forcing starts at i=3 (accept at i=2 depletes budget, apply at i=3 forces) 3924 );// forcing continues through i=4 (accept at i=4 transitions to DONE) 393 } 394 395// Test 3: Activate immediately with budget=0, forcing should start right away 396// Flow: init promotes COUNTING+budget=0 to FORCING, so apply() sees FORCING at i=0 397 { 398const std::vector < llama_token > start = {100 }; 399const std::vector < llama_token > end = {101 }; 400const std::vector < llama_token > forced = {102 ,101 }; 401const std::vector < llama_token > sequence = {100 ,50 ,51 ,52 };// start token first, then 3 tokens 402 403test_reasoning_budget ("activate immediately budget=0" ,sequence , {start }, {end },forced , 4040 ,// budget of 0 tokens 405REASONING_BUDGET_COUNTING ,// starts counting, promoted to FORCING since budget=0 4060 ,// forcing starts at i=0 (initialized in FORCING, apply forces immediately) 4071 );// forcing continues through i=1 (accept at i=1 transitions to DONE) 408 } 409 410// Test 4: No start/end tokens configured - passthrough (no forcing) 411 { 412const std::vector < llama_token > start = {}; 413const std::vector < llama_token > end = {}; 414const std::vector < llama_token > forced = {102 }; 415const std::vector < llama_token > sequence = {50 ,51 ,52 ,53 }; 416 417test_reasoning_budget ("no start/end configured" ,sequence , {start }, {end },forced , 4182 ,// budget 419REASONING_BUDGET_IDLE , 420SIZE_MAX ,SIZE_MAX );// no forcing (no start/end configured) 421 } 422 423// Test 5: Activate immediately with budget > 0, count down then force 424// Flow: i=0 accept(50)->remaining=1, i=1 accept(51)->remaining=0->FORCING 425// Forcing starts at i=2 (apply sees FORCING after accept at i=1 transitioned) 426 { 427const std::vector < llama_token > start = {100 }; 428const std::vector < llama_token > end = {101 }; 429const std::vector < llama_token > forced = {102 ,101 }; 430const std::vector < llama_token > sequence = {50 ,51 ,52 ,53 }; 431 432test_reasoning_budget ("activate immediately with budget" ,sequence , {start }, {end },forced , 4332 ,// budget of 2 tokens 434REASONING_BUDGET_COUNTING , 4352 ,// forcing starts at i=2 (after 2 accepts deplete budget, apply at i=2 forces) 4363 );// forcing continues through i=3 437 } 438 439// Test 6: Multi-block thinking. First block ends naturally at i=2, second 440// start tag at i=3 re-arms the budget, which then exhausts at i=5. 441// Regression: before this fix, DONE absorbed all subsequent tokens and a 442// second <think> block ran unbudgeted. 443// Flow: i=0 accept(100)->COUNTING rem=2; i=1 accept(50)->rem=1; 444// i=2 accept(101)->end_matcher matches, DONE; 445// i=3 accept(100)->re-arm, COUNTING rem=2; 446// i=4 accept(60)->rem=1; i=5 accept(61)->rem=0->FORCING; 447// i=6 apply()->forces token[0]=102, accept(62)->force_pos=1, stay FORCING; 448// i=7 apply()->forces token[1]=101, accept(63)->force_pos=2->DONE. 449 { 450const std::vector < llama_token > start = {100 }; 451const std::vector < llama_token > end = {101 }; 452const std::vector < llama_token > forced = {102 ,101 }; 453const std::vector < llama_token > sequence = {100 ,50 ,101 ,100 ,60 ,61 ,62 ,63 }; 454 455test_reasoning_budget ("multi-block re-arms budget after DONE" ,sequence , {start }, {end },forced , 4562 ,// budget of 2 tokens (per block) 457REASONING_BUDGET_IDLE , 4586 ,// forcing starts at i=6 (after second block exhausts at i=5) 4597 );// forcing continues through i=7 460 } 461 462// Test 7: Multiple start sequences - the second sequence activates counting 463// Flow: i=0 accept(110), i=1 accept(111)->COUNTING rem=2; i=2 accept(50)->rem=1; 464// i=3 accept(51)->rem=0->FORCING; i=4..5 apply() forces the end sequence 465 { 466const std::vector < llama_tokens > start = {{100 }, {110 ,111 }}; 467const std::vector < llama_tokens > end = {{101 }}; 468const std::vector < llama_token > forced = {102 ,101 }; 469const std::vector < llama_token > sequence = {110 ,111 ,50 ,51 ,52 ,53 }; 470 471test_reasoning_budget ("multiple start sequences" ,sequence ,start ,end ,forced , 4722 ,// budget of 2 tokens 473REASONING_BUDGET_IDLE , 4744 ,// forcing starts at i=4 (accept at i=3 depletes budget) 4755 );// forcing continues through i=5 476 } 477 478// Test 8: Multiple end sequences - natural end via the second sequence 479// Flow: i=0 accept(100)->COUNTING rem=5; i=1 accept(50)->rem=4; 480// i=2 accept(103)->partial end, rem=3; i=3 accept(104)->end matched, DONE 481 { 482const std::vector < llama_tokens > start = {{100 }}; 483const std::vector < llama_tokens > end = {{101 }, {103 ,104 }}; 484const std::vector < llama_token > forced = {102 ,101 }; 485const std::vector < llama_token > sequence = {100 ,50 ,103 ,104 ,52 }; 486 487test_reasoning_budget ("multiple end sequences" ,sequence ,start ,end ,forced , 4885 ,// budget of 5 tokens 489REASONING_BUDGET_IDLE , 490SIZE_MAX ,SIZE_MAX );// no forcing expected (natural end) 491 } 492 493test_reasoning_budget_clone_mid_counting (); 494test_reasoning_budget_clone_mid_forcing (); 495test_reasoning_budget_force_manual (); 496test_reasoning_budget_end_match (); 497 498printf ("OK (12 tests passed)\n" ); 499 500printf ("Testing UTF-8 boundary detection... " ); 501test_utf8_boundary_detection (); 502printf ("OK\n" ); 503 504return 0 ; 505}