char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
dd1ea5243
char/aliceai-t5-moe
1#include "reasoning-budget.h" 2#include "common.h" 3#include "trie.h" 4#include "unicode.h" 5 6#include "log.h" 7 8#include <algorithm> 9#include <cmath> 10#include <cstdint> 11#include <string> 12#include <vector> 13 14struct token_matcher { 15 std::vector < llama_tokens > seqs ; 16common_aho_corasick ac ; 17size_t state = 0 ; 18 19token_matcher (const std::vector < llama_tokens > & seqs ) :seqs (collect (seqs )),ac (build_trie (this -> seqs )) {} 20 21static std::vector < llama_tokens > collect (const std::vector < llama_tokens > & seqs ) { 22 std::vector < llama_tokens > res ; 23for (const auto & seq :seqs ) { 24if (!seq .empty ()&& std::find (res .begin (),res .end (),seq )== res .end ()) { 25res .push_back (seq ); 26 } 27 } 28return res ; 29 } 30 31static common_trie build_trie (const std::vector < llama_tokens > & seqs ) { 32common_trie t ; 33for (const auto & seq :seqs ) { 34t .insert (std::vector < uint32_t > (seq .begin (),seq .end ())); 35 } 36return t ; 37 } 38 39// returns the index into seqs of the longest sequence ending at this token, or -1 40int32_t advance (llama_token token ) { 41state = ac .next (state , (uint32_t )token ); 42const int32_t p = ac .match_pattern (state ); 43if (p >=0 ) { 44state = 0 ; 45 } 46return p ; 47 } 48 49void reset () {state = 0 ; } 50}; 51 52struct common_reasoning_budget_ctx { 53const llama_vocab * vocab ; 54 55token_matcher start_matcher ; 56token_matcher end_matcher ; 57llama_tokens forced_tokens ; 58 59int32_t budget ;// maximum tokens in reasoning block 60int32_t remaining ;// tokens remaining in budget 61 62common_reasoning_budget_state state ; 63 64// for forcing 65size_t force_pos ;// next position in forced_tokens to force 66 67int32_t end_match ;// index into end_matcher.seqs of the sequence that transitioned to DONE, -1 if none 68}; 69 70static const char * common_reasoning_budget_name (const struct llama_sampler * /*smpl*/ ) { 71return "reasoning-budget" ; 72} 73 74static void common_reasoning_budget_accept (struct llama_sampler * smpl ,llama_token token ) { 75auto * ctx = (common_reasoning_budget_ctx * )smpl -> ctx ; 76 77switch (ctx -> state ) { 78case REASONING_BUDGET_IDLE : 79 { 80if (ctx -> start_matcher .advance (token ) >=0 ) { 81ctx -> state = REASONING_BUDGET_COUNTING ; 82ctx -> remaining = ctx -> budget ; 83COM_TRC ("activated, budget=%d tokens\n" ,ctx -> budget ); 84 85if (ctx -> remaining <=0 ) { 86ctx -> state = REASONING_BUDGET_FORCING ; 87ctx -> force_pos = 0 ; 88COM_TRC ("%s" ,"budget=0, forcing immediately\n" ); 89 } 90 } 91break ; 92 } 93case REASONING_BUDGET_COUNTING : 94case REASONING_BUDGET_WAITING_UTF8 : 95 { 96const int32_t match = ctx -> end_matcher .advance (token ); 97if (match >=0 ) { 98ctx -> state = REASONING_BUDGET_DONE ; 99ctx -> end_match = match ; 100COM_TRC ("%s" ,"deactivated (natural end)\n" ); 101break ; 102 } 103 104bool utf8_complete = true; 105if (ctx -> vocab != nullptr ) { 106const std::string piece = common_token_to_piece (ctx -> vocab ,token , false); 107utf8_complete = common_utf8_is_complete (piece ); 108 } 109 110if (ctx -> state == REASONING_BUDGET_WAITING_UTF8 ) { 111if (utf8_complete ) { 112ctx -> state = REASONING_BUDGET_FORCING ; 113ctx -> force_pos = 0 ; 114ctx -> end_matcher .reset (); 115COM_TRC ("%s" ,"UTF-8 complete, now forcing end sequence\n" ); 116 } 117 }else if (ctx -> state == REASONING_BUDGET_COUNTING ) { 118ctx -> remaining -- ; 119if (ctx -> remaining <=0 ) { 120if (utf8_complete ) { 121ctx -> state = REASONING_BUDGET_FORCING ; 122ctx -> force_pos = 0 ; 123ctx -> end_matcher .reset (); 124COM_TRC ("%s" ,"budget exhausted, forcing end sequence\n" ); 125 }else { 126ctx -> state = REASONING_BUDGET_WAITING_UTF8 ; 127ctx -> end_matcher .reset (); 128COM_TRC ("%s" ,"budget exhausted, waiting for UTF-8 completion\n" ); 129 } 130 } 131 } 132break ; 133 } 134case REASONING_BUDGET_FORCING : 135 { 136// track the end sequence within forced_tokens so it is also reported on DONE 137const int32_t match = ctx -> end_matcher .advance (token ); 138ctx -> force_pos ++ ; 139if (ctx -> force_pos >=ctx -> forced_tokens .size ()) { 140ctx -> state = REASONING_BUDGET_DONE ; 141ctx -> end_match = match ; 142COM_TRC ("%s" ,"forced sequence complete, done\n" ); 143 } 144break ; 145 } 146case REASONING_BUDGET_DONE : 147// Re-arm on a new start tag: some models emit multiple <think> blocks 148// per response, and each should get a fresh budget window. 149if (ctx -> start_matcher .advance (token ) >=0 ) { 150ctx -> state = REASONING_BUDGET_COUNTING ; 151ctx -> remaining = ctx -> budget ; 152ctx -> end_matcher .reset (); 153ctx -> end_match = -1 ; 154COM_TRC ("re-activated on new start tag, budget=%d tokens\n" ,ctx -> budget ); 155 156if (ctx -> remaining <=0 ) { 157ctx -> state = REASONING_BUDGET_FORCING ; 158ctx -> force_pos = 0 ; 159COM_TRC ("%s" ,"budget=0, forcing immediately\n" ); 160 } 161 } 162break ; 163 } 164} 165 166static void common_reasoning_budget_apply (struct llama_sampler * smpl ,llama_token_data_array * cur_p ) { 167auto * ctx = (common_reasoning_budget_ctx * )smpl -> ctx ; 168 169if (ctx -> state != REASONING_BUDGET_FORCING ) { 170// passthrough — don't modify logits 171return ; 172 } 173 174if (ctx -> force_pos >=ctx -> forced_tokens .size ()) { 175return ; 176 } 177 178const llama_token forced = ctx -> forced_tokens [ctx -> force_pos ]; 179 180// set all logits to -inf except the forced token 181for (size_t i = 0 ;i < cur_p -> size ;i ++ ) { 182if (cur_p -> data [i ].id != forced ) { 183cur_p -> data [i ].logit = - INFINITY ; 184 } 185 } 186} 187 188static void common_reasoning_budget_reset (struct llama_sampler * smpl ) { 189auto * ctx = (common_reasoning_budget_ctx * )smpl -> ctx ; 190ctx -> state = REASONING_BUDGET_IDLE ; 191ctx -> remaining = ctx -> budget ; 192ctx -> start_matcher .reset (); 193ctx -> end_matcher .reset (); 194ctx -> force_pos = 0 ; 195ctx -> end_match = -1 ; 196} 197 198static struct llama_sampler * common_reasoning_budget_init_state ( 199const struct llama_vocab * vocab ,const std::vector < llama_tokens > & start_seqs , 200const std::vector < llama_tokens > & end_seqs ,const llama_tokens & forced_tokens , 201int32_t budget ,common_reasoning_budget_state initial_state ); 202 203static struct llama_sampler * common_reasoning_budget_clone (const struct llama_sampler * smpl ); 204 205static void common_reasoning_budget_free (struct llama_sampler * smpl ) { 206delete (common_reasoning_budget_ctx * )smpl -> ctx ; 207} 208 209static struct llama_sampler_i common_reasoning_budget_i = { 210/* .name = */ common_reasoning_budget_name , 211/* .accept = */ common_reasoning_budget_accept , 212/* .apply = */ common_reasoning_budget_apply , 213/* .reset = */ common_reasoning_budget_reset , 214/* .clone = */ common_reasoning_budget_clone , 215/* .free = */ common_reasoning_budget_free , 216/* .backend_init = */ nullptr , 217/* .backend_accept = */ nullptr , 218/* .backend_apply = */ nullptr , 219/* .backend_set_input = */ nullptr , 220/* .backend_reset = */ nullptr , 221/* .copy_state = */ nullptr , 222}; 223 224static struct llama_sampler * common_reasoning_budget_clone (const struct llama_sampler * smpl ) { 225const auto * ctx = (const common_reasoning_budget_ctx * )smpl -> ctx ; 226 227return llama_sampler_init ( 228/* .iface = */ & common_reasoning_budget_i , 229/* .ctx = */ new common_reasoning_budget_ctx (* ctx ) 230 ); 231} 232 233static struct llama_sampler * common_reasoning_budget_init_state ( 234const struct llama_vocab * vocab , 235const std::vector < llama_tokens > & start_seqs , 236const std::vector < llama_tokens > & end_seqs , 237const llama_tokens & forced_tokens , 238int32_t budget , 239common_reasoning_budget_state initial_state ) { 240// promote COUNTING with budget <= 0 to FORCING 241if (initial_state == REASONING_BUDGET_COUNTING && budget <=0 ) { 242initial_state = REASONING_BUDGET_FORCING ; 243 } 244 245return llama_sampler_init ( 246/* .iface = */ & common_reasoning_budget_i , 247/* .ctx = */ new common_reasoning_budget_ctx { 248/* .vocab = */ vocab , 249/* .start_matcher = */ token_matcher (start_seqs ), 250/* .end_matcher = */ token_matcher (end_seqs ), 251/* .forced_tokens = */ forced_tokens , 252/* .budget = */ budget , 253/* .remaining = */ budget , 254/* .state = */ initial_state , 255/* .force_pos = */ 0 , 256/* .end_match = */ -1 , 257 } 258 ); 259} 260 261struct llama_sampler * common_reasoning_budget_init ( 262const struct llama_vocab * vocab , 263const std::vector < llama_tokens > & start_seqs , 264const std::vector < llama_tokens > & end_seqs , 265const llama_tokens & forced_tokens , 266int32_t budget , 267common_reasoning_budget_state initial_state ) { 268return common_reasoning_budget_init_state (vocab ,start_seqs ,end_seqs ,forced_tokens ,budget ,initial_state ); 269} 270 271common_reasoning_budget_state common_reasoning_budget_get_state (const struct llama_sampler * smpl ) { 272if (!smpl ) { 273return REASONING_BUDGET_IDLE ; 274 } 275return ((const common_reasoning_budget_ctx * )smpl -> ctx )-> state ; 276} 277 278const llama_tokens * common_reasoning_budget_get_end_match (const struct llama_sampler * smpl ) { 279if (!smpl ) { 280return nullptr ; 281 } 282 283const auto * ctx = (const common_reasoning_budget_ctx * )smpl -> ctx ; 284if (ctx -> end_match < 0 ) { 285return nullptr ; 286 } 287 288return & ctx -> end_matcher .seqs [ctx -> end_match ]; 289} 290 291bool common_reasoning_budget_force (struct llama_sampler * smpl ) { 292if (!smpl ) { 293return false; 294 } 295 296auto * ctx = (common_reasoning_budget_ctx * )smpl -> ctx ; 297 298// only a sampler that is actively counting down the budget may be forced; 299// any other state (idle, already forcing/waiting, or done) is left untouched 300if (ctx -> state != REASONING_BUDGET_COUNTING ) { 301return false; 302 } 303 304ctx -> state = REASONING_BUDGET_FORCING ; 305ctx -> force_pos = 0 ; 306ctx -> end_matcher .reset (); 307COM_TRC ("%s" ,"forced into forcing state (manual transition)\n" ); 308 309return true; 310}