char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
afd439df1
char/aliceai-t5-moe
1#include "unicode.h" 2#include "unicode-data.h" 3 4#include <algorithm> 5#include <cassert> 6#include <cstddef> 7#include <cstdint> 8#include <map> 9#include <regex> 10#include <stdexcept> 11#include <string> 12#include <unordered_map> 13#include <utility> 14#include <vector> 15 16size_t unicode_len_utf8 (char src ) { 17const size_t lookup []= {1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,1 ,2 ,2 ,3 ,4 }; 18uint8_t highbits = static_cast < uint8_t > (src ) >>4 ; 19return lookup [highbits ]; 20} 21 22static std::string unicode_cpts_to_utf8 (const std::vector < uint32_t > & cps ) { 23 std::string result ; 24for (size_t i = 0 ;i < cps .size ();++ i ) { 25result .append (unicode_cpt_to_utf8 (cps [i ])); 26 } 27return result ; 28} 29 30uint32_t unicode_cpt_from_utf8 (const std::string & utf8 ,size_t & offset ) { 31assert (offset < utf8 .size ()); 32if (!(utf8 [offset + 0 ]& 0x80 )) { 33auto result = utf8 [offset + 0 ]; 34offset += 1 ; 35return result ; 36 } 37if (!(utf8 [offset + 0 ]& 0x40 )) { 38throw std::invalid_argument ("invalid character" ); 39 } 40if (!(utf8 [offset + 0 ]& 0x20 )) { 41if (offset + 1 >=utf8 .size ()|| ! ((utf8 [offset + 1 ]& 0xc0 )== 0x80 )) { 42throw std::invalid_argument ("invalid character" ); 43 } 44auto result = ((utf8 [offset + 0 ]& 0x1f ) <<6 ) | (utf8 [offset + 1 ]& 0x3f ); 45offset += 2 ; 46return result ; 47 } 48if (!(utf8 [offset + 0 ]& 0x10 )) { 49if (offset + 2 >=utf8 .size ()|| ! ((utf8 [offset + 1 ]& 0xc0 )== 0x80 )|| ! ((utf8 [offset + 2 ]& 0xc0 )== 0x80 )) { 50throw std::invalid_argument ("invalid character" ); 51 } 52auto result = ((utf8 [offset + 0 ]& 0x0f ) <<12 ) | ((utf8 [offset + 1 ]& 0x3f ) <<6 ) | (utf8 [offset + 2 ]& 0x3f ); 53offset += 3 ; 54return result ; 55 } 56if (!(utf8 [offset + 0 ]& 0x08 )) { 57if (offset + 3 >=utf8 .size ()|| ! ((utf8 [offset + 1 ]& 0xc0 )== 0x80 )|| ! ((utf8 [offset + 2 ]& 0xc0 )== 0x80 )|| !((utf8 [offset + 3 ]& 0xc0 )== 0x80 )) { 58throw std::invalid_argument ("invalid character" ); 59 } 60auto result = ((utf8 [offset + 0 ]& 0x07 ) <<18 ) | ((utf8 [offset + 1 ]& 0x3f ) <<12 ) | ((utf8 [offset + 2 ]& 0x3f ) <<6 ) | (utf8 [offset + 3 ]& 0x3f ); 61offset += 4 ; 62return result ; 63 } 64throw std::invalid_argument ("failed to convert utf8 to codepoint" ); 65} 66 67//static std::vector<uint16_t> unicode_cpt_to_utf16(uint32_t cpt) { 68// std::vector<uint16_t> result; 69// if (/* 0x0000 <= cpt && */ cpt <= 0xffff) { 70// result.emplace_back(cpt); 71// return result; 72// } 73// if (0x10000 <= cpt && cpt <= 0x10ffff) { 74// result.emplace_back(0xd800 | ((cpt - 0x10000) >> 10)); 75// result.emplace_back(0xdc00 | ((cpt - 0x10000) & 0x03ff)); 76// return result; 77// } 78// throw std::invalid_argument("failed to convert codepoint to utf16"); 79//} 80 81//static std::vector<uint16_t> unicode_cpts_to_utf16(const std::vector<uint32_t> & cps) { 82// std::vector<uint16_t> result; 83// for (size_t i = 0; i < cps.size(); ++i) { 84// auto temp = unicode_cpt_to_utf16(cps[i]); 85// result.insert(result.end(), temp.begin(), temp.end()); 86// } 87// return result; 88//} 89 90//static uint32_t unicode_cpt_from_utf16(const std::vector<uint16_t> & utf16, size_t & offset) { 91// assert(offset < utf16.size()); 92// if (((utf16[0] >> 10) << 10) != 0xd800) { 93// auto result = utf16[offset + 0]; 94// offset += 1; 95// return result; 96// } 97// 98// if (offset + 1 >= utf16.size() || !((utf16[1] & 0xdc00) == 0xdc00)) { 99// throw std::invalid_argument("invalid character"); 100// } 101// 102// auto result = 0x10000 + (((utf16[0] & 0x03ff) << 10) | (utf16[1] & 0x03ff)); 103// offset += 2; 104// return result; 105//} 106 107//static std::vector<uint32_t> unicode_cpts_from_utf16(const std::vector<uint16_t> & utf16) { 108// std::vector<uint32_t> result; 109// size_t offset = 0; 110// while (offset < utf16.size()) { 111// result.push_back(unicode_cpt_from_utf16(utf16, offset)); 112// } 113// return result; 114//} 115 116static std::vector < unicode_cpt_flags > unicode_cpt_flags_array () { 117 std::vector < unicode_cpt_flags > cpt_flags (MAX_CODEPOINTS , unicode_cpt_flags::UNDEFINED ); 118 119assert (unicode_ranges_flags .begin ()[0 ].first == 0 ); 120assert (unicode_ranges_flags .begin ()[unicode_ranges_flags .size ()- 1 ].first == MAX_CODEPOINTS ); 121for (size_t i = 1 ;i < unicode_ranges_flags .size ();++ i ) { 122const auto range_ini = unicode_ranges_flags .begin ()[i - 1 ];// codepoint_ini, flags 123const auto range_end = unicode_ranges_flags .begin ()[i ];// codepoint_end, flags 124for (uint32_t cpt = range_ini .first ;cpt < range_end .first ;++ cpt ) { 125cpt_flags [cpt ]= range_ini .second ; 126 } 127 } 128 129for (auto cpt :unicode_set_whitespace ) { 130cpt_flags [cpt ].is_whitespace = true; 131 } 132 133for (auto p :unicode_map_lowercase ) { 134cpt_flags [p .second ].is_lowercase = true; 135 } 136 137for (auto p :unicode_map_uppercase ) { 138cpt_flags [p .second ].is_uppercase = true; 139 } 140 141for (auto & range :unicode_ranges_nfd ) {// start, last, nfd 142cpt_flags [range .nfd ].is_nfd = true; 143 } 144 145return cpt_flags ; 146} 147 148static std::unordered_map < uint8_t , std::string > unicode_byte_to_utf8_map () { 149 std::unordered_map < uint8_t , std::string > map ; 150for (int ch = 0x21 ;ch <=0x7E ;++ ch ) {// u'!' to u'~' 151assert (0 <=ch && ch < 256 ); 152map [ch ]= unicode_cpt_to_utf8 (ch ); 153 } 154for (int ch = 0xA1 ;ch <=0xAC ;++ ch ) {// u'¡' to u'¬' 155assert (0 <=ch && ch < 256 ); 156map [ch ]= unicode_cpt_to_utf8 (ch ); 157 } 158for (int ch = 0xAE ;ch <=0xFF ;++ ch ) {// u'®' to u'ÿ' 159assert (0 <=ch && ch < 256 ); 160map [ch ]= unicode_cpt_to_utf8 (ch ); 161 } 162auto n = 0 ; 163for (int ch = 0 ;ch < 256 ;++ ch ) { 164if (map .find (ch )== map .end ()) { 165map [ch ]= unicode_cpt_to_utf8 (256 + n ); 166++ n ; 167 } 168 } 169return map ; 170} 171 172static std::unordered_map < std::string ,uint8_t > unicode_utf8_to_byte_map () { 173 std::unordered_map < std::string ,uint8_t > map ; 174for (int ch = 0x21 ;ch <=0x7E ;++ ch ) {// u'!' to u'~' 175assert (0 <=ch && ch < 256 ); 176map [unicode_cpt_to_utf8 (ch )]= ch ; 177 } 178for (int ch = 0xA1 ;ch <=0xAC ;++ ch ) {// u'¡' to u'¬' 179assert (0 <=ch && ch < 256 ); 180map [unicode_cpt_to_utf8 (ch )]= ch ; 181 } 182for (int ch = 0xAE ;ch <=0xFF ;++ ch ) {// u'®' to u'ÿ' 183assert (0 <=ch && ch < 256 ); 184map [unicode_cpt_to_utf8 (ch )]= ch ; 185 } 186auto n = 0 ; 187for (int ch = 0 ;ch < 256 ;++ ch ) { 188if (map .find (unicode_cpt_to_utf8 (ch ))== map .end ()) { 189map [unicode_cpt_to_utf8 (256 + n )]= ch ; 190++ n ; 191 } 192 } 193return map ; 194} 195 196static std::vector < std::string > unicode_byte_encoding_process (const std::vector < std::string > & bpe_words ) { 197 std::vector < std::string > bpe_encoded_words ; 198for (const auto & word :bpe_words ) { 199 std::string text_utf ; 200auto utf_word = unicode_cpts_from_utf8 (word ); 201for (size_t i = 0 ;i < utf_word .size ();++ i ) { 202text_utf += unicode_cpt_to_utf8 (utf_word [i ]); 203 } 204 205 std::string encoded_token ; 206for (char & c :text_utf ) { 207encoded_token += unicode_byte_to_utf8 (c ); 208 } 209bpe_encoded_words .emplace_back (encoded_token ); 210 } 211return bpe_encoded_words ; 212} 213 214// GPT2 system regex: 's|'t|'re|'ve|'m|'ll|'d| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+ 215static std::vector < size_t > unicode_regex_split_custom_gpt2 (const std::string & text ,const std::vector < size_t > & offsets ) { 216 std::vector < size_t > bpe_offsets ;// store the offset of each word 217bpe_offsets .reserve (offsets .size ());// Reserve memory for the approximate size 218 219const auto cpts = unicode_cpts_from_utf8 (text ); 220 221size_t start = 0 ; 222for (auto offset :offsets ) { 223const size_t offset_ini = start ; 224const size_t offset_end = start + offset ; 225assert (offset_end <=cpts .size ()); 226start = offset_end ; 227 228static const uint32_t OUT_OF_RANGE = 0xFFFFFFFF ; 229auto _get_cpt = [& ] (const size_t pos )-> uint32_t { 230return (offset_ini <=pos && pos < offset_end ) ?cpts [pos ] :OUT_OF_RANGE ; 231 }; 232 233auto _get_flags = [& ] (const size_t pos )-> unicode_cpt_flags { 234return (offset_ini <=pos && pos < offset_end ) ?unicode_cpt_flags_from_cpt (cpts [pos ]) :unicode_cpt_flags {}; 235 }; 236 237size_t _prev_end = offset_ini ; 238auto _add_token = [& ] (const size_t end )-> size_t { 239assert (_prev_end <=end && end <=offset_end ); 240size_t len = end - _prev_end ; 241if (len > 0 ) { 242bpe_offsets .push_back (len ); 243 } 244_prev_end = end ; 245//if (len > 0) { 246// std::string s = ""; 247// for(size_t p = end-len; p < end; p++) 248// s += unicode_cpt_to_utf8(cpts[p]); 249// printf(">>> '%s'\n", s.c_str()); 250//} 251return len ; 252 }; 253 254for (size_t pos = offset_ini ;pos < offset_end ;/*pos++*/ ) { 255const uint32_t cpt = _get_cpt (pos ); 256const auto flags = _get_flags (pos ); 257 258// regex: 's|'t|'re|'ve|'m|'ll|'d 259if (cpt == '\'' && pos + 1 < offset_end ) { 260uint32_t cpt_next = _get_cpt (pos + 1 ); 261if (cpt_next == 's' || cpt_next == 't' || cpt_next == 'm' || cpt_next == 'd' ) { 262pos += _add_token (pos + 2 ); 263continue ; 264 } 265if (pos + 2 < offset_end ) { 266uint32_t cpt_next_next = _get_cpt (pos + 2 ); 267if ((cpt_next == 'r' && cpt_next_next == 'e' )|| 268 (cpt_next == 'v' && cpt_next_next == 'e' )|| 269 (cpt_next == 'l' && cpt_next_next == 'l' )) { 270pos += _add_token (pos + 3 ); 271continue ; 272 } 273 } 274 } 275 276auto flags2 = (cpt == ' ' ?_get_flags (pos + 1 ) :flags ); 277// regex: <space>?\p{L}+ 278if (flags2 .is_letter ) { 279pos += (cpt == ' ' ); 280while (flags2 .is_letter ) { 281flags2 = _get_flags (++ pos ); 282 } 283_add_token (pos ); 284continue ; 285 } 286// regex: <space>?\p{N}+ 287if (flags2 .is_number ) { 288pos += (cpt == ' ' ); 289while (flags2 .is_number ) { 290flags2 = _get_flags (++ pos ); 291 } 292_add_token (pos ); 293continue ; 294 } 295// regex: <space>?[^\s\p{L}\p{N}]+ 296if (!(flags2 .is_whitespace |flags2 .is_letter |flags2 .is_number )&& flags2 .as_uint ()) { 297pos += (cpt == ' ' ); 298while (!(flags2 .is_whitespace |flags2 .is_letter |flags2 .is_number )&& flags2 .as_uint ()) { 299flags2 = _get_flags (++ pos ); 300 } 301_add_token (pos ); 302continue ; 303 } 304 305size_t num_whitespaces = 0 ; 306while (_get_flags (pos + num_whitespaces ).is_whitespace ) { 307num_whitespaces ++ ; 308 } 309 310// regex: \s+(?!\S) 311if (num_whitespaces > 1 && _get_cpt (pos + num_whitespaces )!= OUT_OF_RANGE ) { 312pos += num_whitespaces - 1 ; 313_add_token (pos ); 314continue ; 315 } 316 317// regex: \s+ 318if (num_whitespaces > 0 ) { 319pos += num_whitespaces ; 320_add_token (pos ); 321continue ; 322 } 323 324// no matches 325_add_token (++ pos ); 326 } 327 } 328 329return bpe_offsets ; 330} 331 332// LLAMA3 system regex: "(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}{1,3}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+" 333static std::vector < size_t > unicode_regex_split_custom_llama3 (const std::string & text ,const std::vector < size_t > & offsets ) { 334 std::vector < size_t > bpe_offsets ;// store the offset of each word 335bpe_offsets .reserve (offsets .size ());// Reserve memory for the approximate size 336 337const auto cpts = unicode_cpts_from_utf8 (text ); 338 339size_t start = 0 ; 340for (auto offset :offsets ) { 341const size_t offset_ini = start ; 342const size_t offset_end = start + offset ; 343assert (offset_end <=cpts .size ()); 344start = offset_end ; 345 346static const uint32_t OUT_OF_RANGE = 0xFFFFFFFF ; 347auto _get_cpt = [& ] (const size_t pos )-> uint32_t { 348return (offset_ini <=pos && pos < offset_end ) ?cpts [pos ] :OUT_OF_RANGE ; 349 }; 350 351auto _get_flags = [& ] (const size_t pos )-> unicode_cpt_flags { 352return (offset_ini <=pos && pos < offset_end ) ?unicode_cpt_flags_from_cpt (cpts [pos ]) :unicode_cpt_flags {}; 353 }; 354 355size_t _prev_end = offset_ini ; 356auto _add_token = [& ] (const size_t end )-> size_t { 357assert (_prev_end <=end && end <=offset_end ); 358size_t len = end - _prev_end ; 359if (len > 0 ) { 360bpe_offsets .push_back (len ); 361 } 362_prev_end = end ; 363//if (len > 0) { 364// std::string s = ""; 365// for(size_t p = end-len; p < end; p++) 366// s += unicode_cpt_to_utf8(cpts[p]); 367// printf(">>> '%s'\n", s.c_str()); 368//} 369return len ; 370 }; 371 372for (size_t pos = offset_ini ;pos < offset_end ;/*pos++*/ ) { 373const uint32_t cpt = _get_cpt (pos ); 374const auto flags = _get_flags (pos ); 375 376// regex: (?i:'s|'t|'re|'ve|'m|'ll|'d) // case insensitive 377if (cpt == '\'' && pos + 1 < offset_end ) { 378uint32_t cpt_next = unicode_tolower (_get_cpt (pos + 1 )); 379if (cpt_next == 's' || cpt_next == 't' || cpt_next == 'm' || cpt_next == 'd' ) { 380pos += _add_token (pos + 2 ); 381continue ; 382 } 383if (pos + 2 < offset_end ) { 384uint32_t cpt_next_next = unicode_tolower (_get_cpt (pos + 2 )); 385if ((cpt_next == 'r' && cpt_next_next == 'e' )|| 386 (cpt_next == 'v' && cpt_next_next == 'e' )|| 387 (cpt_next == 'l' && cpt_next_next == 'l' )) { 388pos += _add_token (pos + 3 ); 389continue ; 390 } 391 } 392 } 393 394// regex: [^\r\n\p{L}\p{N}]?\p{L}+ 395if (!(cpt == '\r' || cpt == '\n' || flags .is_number )) { 396if (flags .is_letter || _get_flags (pos + 1 ).is_letter ) {// one or more letters 397pos ++ ; 398while (_get_flags (pos ).is_letter ) { 399pos ++ ; 400 } 401_add_token (pos ); 402continue ; 403 } 404 } 405 406// regex: \p{N}{1,3} 407if (flags .is_number ) { 408size_t ini = pos ; 409while (_get_flags (pos ).is_number ) { 410if (++ pos - ini >=3 ) { 411_add_token (pos ); 412ini = pos ; 413 } 414 } 415_add_token (pos ); 416continue ; 417 } 418 419// regex: <space>?[^\s\p{L}\p{N}]+[\r\n]* 420auto flags2 = (cpt == ' ' ?_get_flags (pos + 1 ) :flags ); 421if (!(flags2 .is_whitespace |flags2 .is_letter |flags2 .is_number )&& flags .as_uint ()) { 422pos += (cpt == ' ' ); 423while (!(flags2 .is_whitespace |flags2 .is_letter |flags2 .is_number )&& flags2 .as_uint ()) { 424flags2 = _get_flags (++ pos ); 425 } 426uint32_t cpt2 = _get_cpt (pos ); 427while (cpt2 == '\r' || cpt2 == '\n' ) { 428cpt2 = _get_cpt (++ pos ); 429 } 430_add_token (pos ); 431continue ; 432 } 433 434size_t num_whitespaces = 0 ; 435size_t last_end_r_or_n = 0 ; 436while (_get_flags (pos + num_whitespaces ).is_whitespace ) { 437uint32_t cpt2 = _get_cpt (pos + num_whitespaces ); 438if (cpt2 == '\r' || cpt2 == '\n' ) { 439last_end_r_or_n = pos + num_whitespaces + 1 ; 440 } 441num_whitespaces ++ ; 442 } 443 444// regex: \s*[\r\n]+ 445if (last_end_r_or_n > 0 ) { 446pos = last_end_r_or_n ; 447_add_token (pos ); 448continue ; 449 } 450 451// regex: \s+(?!\S) 452if (num_whitespaces > 1 && _get_cpt (pos + num_whitespaces )!= OUT_OF_RANGE ) { 453pos += num_whitespaces - 1 ; 454_add_token (pos ); 455continue ; 456 } 457 458// regex: \s+ 459if (num_whitespaces > 0 ) { 460pos += num_whitespaces ; 461_add_token (pos ); 462continue ; 463 } 464 465// no matches 466_add_token (++ pos ); 467 } 468 } 469 470return bpe_offsets ; 471} 472 473// Qwen2 system regex: "(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\\r\\n\\p{L}\\p{N}]?\\p{L}+|\\p{N}| ?[^\\s\\p{L}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+" 474static std::vector < size_t > unicode_regex_split_custom_qwen2 (const std::string & text ,const std::vector < size_t > & offsets ) { 475 std::vector < size_t > bpe_offsets ;// store the offset of each word 476bpe_offsets .reserve (offsets .size ());// Reserve memory for the approximate size 477 478const auto cpts = unicode_cpts_from_utf8 (text ); 479 480size_t start = 0 ; 481for (auto offset :offsets ) { 482const size_t offset_ini = start ; 483const size_t offset_end = start + offset ; 484assert (offset_end <=cpts .size ()); 485start = offset_end ; 486 487static const uint32_t OUT_OF_RANGE = 0xFFFFFFFF ; 488auto _get_cpt = [& ] (const size_t pos )-> uint32_t { 489return (offset_ini <=pos && pos < offset_end ) ?cpts [pos ] :OUT_OF_RANGE ; 490 }; 491 492auto _get_flags = [& ] (const size_t pos )-> unicode_cpt_flags { 493return (offset_ini <=pos && pos < offset_end ) ?unicode_cpt_flags_from_cpt (cpts [pos ]) :unicode_cpt_flags {}; 494 }; 495 496size_t _prev_end = offset_ini ; 497auto _add_token = [& ] (const size_t end )-> size_t { 498assert (_prev_end <=end && end <=offset_end ); 499size_t len = end - _prev_end ; 500if (len > 0 ) { 501bpe_offsets .push_back (len ); 502 } 503_prev_end = end ; 504//if (len > 0) { 505// std::string s = ""; 506// for(size_t p = end-len; p < end; p++) 507// s += unicode_cpt_to_utf8(cpts[p]); 508// printf(">>> '%s'\n", s.c_str()); 509//} 510return len ; 511 }; 512 513for (size_t pos = offset_ini ;pos < offset_end ;/*pos++*/ ) { 514const uint32_t cpt = _get_cpt (pos ); 515const auto flags = _get_flags (pos ); 516 517// regex: (?i:'s|'t|'re|'ve|'m|'ll|'d) // case insensitive 518if (cpt == '\'' && pos + 1 < offset_end ) { 519uint32_t cpt_next = unicode_tolower (_get_cpt (pos + 1 )); 520if (cpt_next == 's' || cpt_next == 't' || cpt_next == 'm' || cpt_next == 'd' ) { 521pos += _add_token (pos + 2 ); 522continue ; 523 } 524if (pos + 2 < offset_end ) { 525uint32_t cpt_next_next = unicode_tolower (_get_cpt (pos + 2 )); 526if ((cpt_next == 'r' && cpt_next_next == 'e' )|| 527 (cpt_next == 'v' && cpt_next_next == 'e' )|| 528 (cpt_next == 'l' && cpt_next_next == 'l' )) { 529pos += _add_token (pos + 3 ); 530continue ; 531 } 532 } 533 } 534 535// regex: [^\r\n\p{L}\p{N}]?\p{L}+ 536if (!(cpt == '\r' || cpt == '\n' || flags .is_number )) { 537if (flags .is_letter || _get_flags (pos + 1 ).is_letter ) {// one or more letters 538pos ++ ; 539while (_get_flags (pos ).is_letter ) { 540pos ++ ; 541 } 542_add_token (pos ); 543continue ; 544 } 545 } 546 547// regex: \p{N} 548if (flags .is_number ) { 549pos ++ ; 550_add_token (pos ); 551continue ; 552 } 553 554// regex: <space>?[^\s\p{L}\p{N}]+[\r\n]* 555auto flags2 = (cpt == ' ' ?_get_flags (pos + 1 ) :flags ); 556if (!(flags2 .is_whitespace |flags2 .is_letter |flags2 .is_number )&& flags .as_uint ()) { 557pos += (cpt == ' ' ); 558while (!(flags2 .is_whitespace |flags2 .is_letter |flags2 .is_number )&& flags2 .as_uint ()) { 559flags2 = _get_flags (++ pos ); 560 } 561uint32_t cpt2 = _get_cpt (pos ); 562while (cpt2 == '\r' || cpt2 == '\n' ) { 563cpt2 = _get_cpt (++ pos ); 564 } 565_add_token (pos ); 566continue ; 567 } 568 569size_t num_whitespaces = 0 ; 570size_t last_end_r_or_n = 0 ; 571while (_get_flags (pos + num_whitespaces ).is_whitespace ) { 572uint32_t cpt2 = _get_cpt (pos + num_whitespaces ); 573if (cpt2 == '\r' || cpt2 == '\n' ) { 574last_end_r_or_n = pos + num_whitespaces + 1 ; 575 } 576num_whitespaces ++ ; 577 } 578 579// regex: \s*[\r\n]+ 580if (last_end_r_or_n > 0 ) { 581pos = last_end_r_or_n ; 582_add_token (pos ); 583continue ; 584 } 585 586// regex: \s+(?!\S) 587if (num_whitespaces > 1 && _get_cpt (pos + num_whitespaces )!= OUT_OF_RANGE ) { 588pos += num_whitespaces - 1 ; 589_add_token (pos ); 590continue ; 591 } 592 593// regex: \s+ 594if (num_whitespaces > 0 ) { 595pos += num_whitespaces ; 596_add_token (pos ); 597continue ; 598 } 599 600// no matches 601_add_token (++ pos ); 602 } 603 } 604 605return bpe_offsets ; 606} 607 608// Qwen3.5 system regex: "(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\\r\\n\\p{L}\\p{N}]?[\\p{L}\\p{M}]+|\\p{N}| ?[^\\s\\p{L}\\p{M}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+" 609// Compared to Qwen2, letter-runs also consume Unicode combining marks (\p{M}): [\p{L}\p{M}]+ instead of \p{L}+ 610static std::vector < size_t > unicode_regex_split_custom_qwen35 (const std::string & text ,const std::vector < size_t > & offsets ) { 611 std::vector < size_t > bpe_offsets ;// store the offset of each word 612bpe_offsets .reserve (offsets .size ());// Reserve memory for the approximate size 613 614const auto cpts = unicode_cpts_from_utf8 (text ); 615 616size_t start = 0 ; 617for (auto offset :offsets ) { 618const size_t offset_ini = start ; 619const size_t offset_end = start + offset ; 620assert (offset_end <=cpts .size ()); 621start = offset_end ; 622 623static const uint32_t OUT_OF_RANGE = 0xFFFFFFFF ; 624auto _get_cpt = [& ] (const size_t pos )-> uint32_t { 625return (offset_ini <=pos && pos < offset_end ) ?cpts [pos ] :OUT_OF_RANGE ; 626 }; 627 628auto _get_flags = [& ] (const size_t pos )-> unicode_cpt_flags { 629return (offset_ini <=pos && pos < offset_end ) ?unicode_cpt_flags_from_cpt (cpts [pos ]) :unicode_cpt_flags {}; 630 }; 631 632size_t _prev_end = offset_ini ; 633auto _add_token = [& ] (const size_t end )-> size_t { 634assert (_prev_end <=end && end <=offset_end ); 635size_t len = end - _prev_end ; 636if (len > 0 ) { 637bpe_offsets .push_back (len ); 638 } 639_prev_end = end ; 640return len ; 641 }; 642 643for (size_t pos = offset_ini ;pos < offset_end ;/*pos++*/ ) { 644const uint32_t cpt = _get_cpt (pos ); 645const auto flags = _get_flags (pos ); 646 647// regex: (?i:'s|'t|'re|'ve|'m|'ll|'d) // case insensitive 648if (cpt == '\'' && pos + 1 < offset_end ) { 649uint32_t cpt_next = unicode_tolower (_get_cpt (pos + 1 )); 650if (cpt_next == 's' || cpt_next == 't' || cpt_next == 'm' || cpt_next == 'd' ) { 651pos += _add_token (pos + 2 ); 652continue ; 653 } 654if (pos + 2 < offset_end ) { 655uint32_t cpt_next_next = unicode_tolower (_get_cpt (pos + 2 )); 656if ((cpt_next == 'r' && cpt_next_next == 'e' )|| 657 (cpt_next == 'v' && cpt_next_next == 'e' )|| 658 (cpt_next == 'l' && cpt_next_next == 'l' )) { 659pos += _add_token (pos + 3 ); 660continue ; 661 } 662 } 663 } 664 665// regex: [^\r\n\p{L}\p{N}]?[\p{L}\p{M}]+ 666if (!(cpt == '\r' || cpt == '\n' || flags .is_number )) { 667if (flags .is_letter || flags .is_accent_mark || _get_flags (pos + 1 ).is_accent_mark || _get_flags (pos + 1 ).is_letter ) { 668pos ++ ; 669while (_get_flags (pos ).is_letter || _get_flags (pos ).is_accent_mark ) { 670pos ++ ; 671 } 672_add_token (pos ); 673continue ; 674 } 675 } 676 677// regex: \p{N} 678if (flags .is_number ) { 679pos ++ ; 680_add_token (pos ); 681continue ; 682 } 683 684// regex: <space>?[^\s\p{L}\p{M}\p{N}]+[\r\n]* 685auto flags2 = (cpt == ' ' ?_get_flags (pos + 1 ) :flags ); 686if (!(flags2 .is_whitespace |flags2 .is_letter |flags2 .is_accent_mark |flags2 .is_number )&& flags .as_uint ()) { 687pos += (cpt == ' ' ); 688while (!(flags2 .is_whitespace |flags2 .is_letter |flags2 .is_accent_mark |flags2 .is_number )&& flags2 .as_uint ()) { 689flags2 = _get_flags (++ pos ); 690 } 691uint32_t cpt2 = _get_cpt (pos ); 692while (cpt2 == '\r' || cpt2 == '\n' ) { 693cpt2 = _get_cpt (++ pos ); 694 } 695_add_token (pos ); 696continue ; 697 } 698 699size_t num_whitespaces = 0 ; 700size_t last_end_r_or_n = 0 ; 701while (_get_flags (pos + num_whitespaces ).is_whitespace ) { 702uint32_t cpt2 = _get_cpt (pos + num_whitespaces ); 703if (cpt2 == '\r' || cpt2 == '\n' ) { 704last_end_r_or_n = pos + num_whitespaces + 1 ; 705 } 706num_whitespaces ++ ; 707 } 708 709// regex: \s*[\r\n]+ 710if (last_end_r_or_n > 0 ) { 711pos = last_end_r_or_n ; 712_add_token (pos ); 713continue ; 714 } 715 716// regex: \s+(?!\S) 717if (num_whitespaces > 1 && _get_cpt (pos + num_whitespaces )!= OUT_OF_RANGE ) { 718pos += num_whitespaces - 1 ; 719_add_token (pos ); 720continue ; 721 } 722 723// regex: \s+ 724if (num_whitespaces > 0 ) { 725pos += num_whitespaces ; 726_add_token (pos ); 727continue ; 728 } 729 730// no matches 731_add_token (++ pos ); 732 } 733 } 734 735return bpe_offsets ; 736} 737 738template < typename CharT > 739static std::vector < size_t > unicode_regex_split_stl (const std::basic_string < CharT > & text ,const std::basic_string < CharT > & regex ,const std::vector < size_t > & offsets ) { 740using BidirIt = typename std::basic_string < CharT > ::const_iterator ; 741#ifdef _MSC_VER 742// Bypass bug in MSVC: https://github.com/ggml-org/llama.cpp/issues/17830 743constexpr auto regex_flags = std::regex_constants::ECMAScript ; 744#else 745constexpr auto regex_flags = std::regex_constants::optimize | std::regex_constants::nosubs ; 746#endif 747 std::basic_regex < CharT > expr (regex ,regex_flags ); 748 std::vector < size_t > bpe_offsets ;// store the offset of each word 749bpe_offsets .reserve (offsets .size ());// Reserve memory for the approximate size 750size_t start = 0 ; 751for (auto offset :offsets ) { 752 std::regex_iterator < BidirIt > it (text .begin ()+ start ,text .begin ()+ start + offset ,expr ); 753 std::regex_iterator < BidirIt > end ; 754 755int64_t start_idx = 0 ; 756while (it != end ) { 757 std::match_results < BidirIt > match = * it ; 758if (match .position ()> start_idx ) { 759bpe_offsets .emplace_back (match .position ()- start_idx ); 760 } 761bpe_offsets .emplace_back (match .length ()); 762start_idx = match .position ()+ match .length (); 763++ it ; 764 } 765 766if (start_idx < (int64_t )offset ) { 767bpe_offsets .emplace_back (offset - start_idx ); 768 } 769start += offset ; 770 } 771 772return bpe_offsets ; 773} 774 775// K2 system regex patterns (from tokenization_kimi.py): 776// [\p{Han}]+|[^\r\n\p{L}\p{N}]?[\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{M}&&[^\p{Han}]]*[\p{Ll}\p{Lm}\p{Lo}\p{M}&&[^\p{Han}]]+(?i:'s|'t|'re|'ve|'m|'ll|'d)?|[^\r\n\p{L}\p{N}]?[\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{M}&&[^\p{Han}]]+[\p{Ll}\p{Lm}\p{Lo}\p{M}&&[^\p{Han}]]*(?i:'s|'t|'re|'ve|'m|'ll|'d)?|\p{N}{1,3}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+ 777static std::vector < size_t > unicode_regex_split_custom_kimi_k2 (const std::string & text ,const std::vector < size_t > & offsets ) { 778 std::vector < size_t > bpe_offsets ; 779bpe_offsets .reserve (offsets .size ()); 780 781const auto cpts = unicode_cpts_from_utf8 (text ); 782 783size_t start = 0 ; 784for (auto offset :offsets ) { 785const size_t offset_ini = start ; 786const size_t offset_end = start + offset ; 787assert (offset_end <=cpts .size ()); 788start = offset_end ; 789 790static const uint32_t OUT_OF_RANGE = 0xFFFFFFFF ; 791auto _get_cpt = [& ] (const size_t pos )-> uint32_t { 792return (offset_ini <=pos && pos < offset_end ) ?cpts [pos ] :OUT_OF_RANGE ; 793 }; 794 795auto _get_flags = [& ] (const size_t pos )-> unicode_cpt_flags { 796return (offset_ini <=pos && pos < offset_end ) ?unicode_cpt_flags_from_cpt (cpts [pos ]) :unicode_cpt_flags {}; 797 }; 798 799size_t _prev_end = offset_ini ; 800auto _add_token = [& ] (const size_t end )-> size_t { 801assert (_prev_end <=end && end <=offset_end ); 802size_t len = end - _prev_end ; 803if (len > 0 ) { 804bpe_offsets .push_back (len ); 805 } 806_prev_end = end ; 807return len ; 808 }; 809 810for (size_t pos = offset_ini ;pos < offset_end ;/*pos++*/ ) { 811const uint32_t cpt = _get_cpt (pos ); 812const auto flags = _get_flags (pos ); 813 814// Pattern 1: [\p{Han}]+ (Chinese characters) 815if (unicode_cpt_is_han (cpt )) { 816while (unicode_cpt_is_han (_get_cpt (pos ))) { 817pos ++ ; 818 } 819_add_token (pos ); 820continue ; 821 } 822 823// Pattern 2 & 3: Letter words excluding Han characters with optional contractions 824// [^\r\n\p{L}\p{N}]?[\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{M}&&[^\p{Han}]]*[\p{Ll}\p{Lm}\p{Lo}\p{M}&&[^\p{Han}]]+(?:'s|'t|'re|'ve|'m|'ll|'d)? 825// [^\r\n\p{L}\p{N}]?[\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{M}&&[^\p{Han}]]+[\p{Ll}\p{Lm}\p{Lo}\p{M}&&[^\p{Han}]]*(?:'s|'t|'re|'ve|'m|'ll|'d)? 826// Check if current char is a letter OR if current char could be a leading char and next char is a letter 827bool is_letter_pattern = (flags .is_letter && !unicode_cpt_is_han (cpt ))|| 828 (!(cpt == '\r' || cpt == '\n' || flags .is_letter || flags .is_number )&& 829_get_flags (pos + 1 ).is_letter && !unicode_cpt_is_han (_get_cpt (pos + 1 ))); 830 831if (is_letter_pattern ) { 832// Handle optional leading non-letter/non-number character 833bool has_leading_char = false; 834if (!(cpt == '\r' || cpt == '\n' || flags .is_letter || flags .is_number )) { 835has_leading_char = true; 836pos ++ ; 837 } 838 839// Match letter sequence (excluding Han characters) 840bool has_letters = false; 841while (_get_flags (pos ).is_letter && !unicode_cpt_is_han (_get_cpt (pos ))) { 842has_letters = true; 843pos ++ ; 844 } 845 846// Only proceed if we found letters (after potentially skipping leading char) 847if (has_letters || (!has_leading_char && _get_flags (pos ).is_letter && !unicode_cpt_is_han (_get_cpt (pos )))) { 848if (!has_letters )pos ++ ;// consume the first letter if we didn't already 849 850// Continue consuming letters 851while (_get_flags (pos ).is_letter && !unicode_cpt_is_han (_get_cpt (pos ))) { 852pos ++ ; 853 } 854 855// Check for optional contractions (?:'s|'t|'re|'ve|'m|'ll|'d) 856if (_get_cpt (pos )== '\'' && pos + 1 < offset_end ) { 857uint32_t cpt_next = unicode_tolower (_get_cpt (pos + 1 )); 858if (cpt_next == 's' || cpt_next == 't' || cpt_next == 'm' || cpt_next == 'd' ) { 859pos += 2 ; 860 }else if (pos + 2 < offset_end ) { 861uint32_t cpt_next_next = unicode_tolower (_get_cpt (pos + 2 )); 862if ((cpt_next == 'r' && cpt_next_next == 'e' )|| 863 (cpt_next == 'v' && cpt_next_next == 'e' )|| 864 (cpt_next == 'l' && cpt_next_next == 'l' )) { 865pos += 3 ; 866 } 867 } 868 } 869 870_add_token (pos ); 871continue ; 872 }else if (has_leading_char ) { 873// We consumed a leading char but found no letters, backtrack 874pos -- ; 875 } 876 } 877 878// Pattern 4: \p{N}{1,3} (numbers 1-3 digits) 879if (flags .is_number ) { 880size_t ini = pos ; 881while (_get_flags (pos ).is_number ) { 882if (++ pos - ini >=3 ) { 883_add_token (pos ); 884ini = pos ; 885 } 886 } 887_add_token (pos ); 888continue ; 889 } 890 891// Pattern 5: ?[^\s\p{L}\p{N}]+[\r\n]* (optional space + non-word chars + optional newlines) 892auto flags2 = (cpt == ' ' ?_get_flags (pos + 1 ) :flags ); 893if (!(flags2 .is_whitespace || flags2 .is_letter || flags2 .is_number )&& flags2 .as_uint ()) { 894pos += (cpt == ' ' ); 895while (!(flags2 .is_whitespace || flags2 .is_letter || flags2 .is_number )&& flags2 .as_uint ()) { 896flags2 = _get_flags (++ pos ); 897 } 898// Match optional [\r\n]* 899uint32_t cpt2 = _get_cpt (pos ); 900while (cpt2 == '\r' || cpt2 == '\n' ) { 901cpt2 = _get_cpt (++ pos ); 902 } 903_add_token (pos ); 904continue ; 905 } 906 907// Count whitespace characters 908size_t num_whitespaces = 0 ; 909size_t last_end_r_or_n = 0 ; 910while (_get_flags (pos + num_whitespaces ).is_whitespace ) { 911uint32_t cpt2 = _get_cpt (pos + num_whitespaces ); 912if (cpt2 == '\r' || cpt2 == '\n' ) { 913last_end_r_or_n = pos + num_whitespaces + 1 ; 914 } 915num_whitespaces ++ ; 916 } 917 918// Pattern 6: \s*[\r\n]+ (whitespace with newlines) 919if (last_end_r_or_n > 0 ) { 920pos = last_end_r_or_n ; 921_add_token (pos ); 922continue ; 923 } 924 925// Pattern 7: \s+(?!\S) (trailing whitespace) 926if (num_whitespaces > 1 && _get_cpt (pos + num_whitespaces )!= OUT_OF_RANGE ) { 927pos += num_whitespaces - 1 ; 928_add_token (pos ); 929continue ; 930 } 931 932// Pattern 8: \s+ (general whitespace) 933if (num_whitespaces > 0 ) { 934pos += num_whitespaces ; 935_add_token (pos ); 936continue ; 937 } 938 939// No matches - consume single character 940_add_token (++ pos ); 941 } 942 } 943 944return bpe_offsets ; 945} 946 947// AFMOE digit handling: splits digits with leading 1-2 based on total length modulo 3 948static std::vector < size_t > unicode_regex_split_custom_afmoe (const std::string & text ,const std::vector < size_t > & offsets ) { 949 std::vector < size_t > bpe_offsets ; 950bpe_offsets .reserve (offsets .size ()); 951 952const auto cpts = unicode_cpts_from_utf8 (text ); 953 954size_t start = 0 ; 955for (auto offset :offsets ) { 956const size_t offset_ini = start ; 957const size_t offset_end = start + offset ; 958assert (offset_end <=cpts .size ()); 959start = offset_end ; 960 961auto _get_flags = [& ] (const size_t pos )-> unicode_cpt_flags { 962return (offset_ini <=pos && pos < offset_end ) ?unicode_cpt_flags_from_cpt (cpts [pos ]) :unicode_cpt_flags {}; 963 }; 964 965size_t _prev_end = offset_ini ; 966auto _add_token = [& ] (const size_t end )-> size_t { 967assert (_prev_end <=end && end <=offset_end ); 968size_t len = end - _prev_end ; 969if (len > 0 ) { 970bpe_offsets .push_back (len ); 971 } 972_prev_end = end ; 973return len ; 974 }; 975 976for (size_t pos = offset_ini ;pos < offset_end ; ) { 977const auto flags = _get_flags (pos ); 978 979// Handle digit sequences with special splitting logic 980if (flags .is_number ) { 981size_t digit_start = pos ; 982size_t digit_count = 0 ; 983 984// Count consecutive digits 985while (_get_flags (pos ).is_number && pos < offset_end ) { 986digit_count ++ ; 987pos ++ ; 988 } 989 990// Split based on total length modulo 3 991size_t remainder = digit_count %3 ; 992size_t current = digit_start ; 993 994// Emit leading 1-2 digits if needed 995if (remainder > 0 ) { 996_add_token (current + remainder ); 997current += remainder ; 998 } 999 1000// Emit groups of 3 1001while (current < digit_start + digit_count ) { 1002_add_token (current + 3 ); 1003current += 3 ; 1004 } 1005continue ; 1006 } 1007 1008// For non-digits, just move forward 1009pos ++ ; 1010 } 1011 1012// Add any remaining content 1013if (_prev_end < offset_end ) { 1014_add_token (offset_end ); 1015 } 1016 } 1017 1018return bpe_offsets ; 1019} 1020 1021// regex: [^\n]+|[\n]+ 1022// splits text into runs of non-newline characters and runs of newline characters 1023static std::vector < size_t > unicode_regex_split_custom_newlines (const std::string & text ,const std::vector < size_t > & offsets ) { 1024 std::vector < size_t > bpe_offsets ; 1025bpe_offsets .reserve (offsets .size ()); 1026 1027const auto cpts = unicode_cpts_from_utf8 (text ); 1028 1029size_t start = 0 ; 1030for (auto offset :offsets ) { 1031const size_t offset_ini = start ; 1032const size_t offset_end = start + offset ; 1033assert (offset_end <=cpts .size ()); 1034start = offset_end ; 1035 1036size_t pos = offset_ini ; 1037while (pos < offset_end ) { 1038const bool is_newline = (cpts [pos ]== '\n' ); 1039const size_t run_start = pos ; 1040while (pos < offset_end && (cpts [pos ]== '\n' )== is_newline ) { 1041pos ++ ; 1042 } 1043bpe_offsets .push_back (pos - run_start ); 1044 } 1045 } 1046 1047return bpe_offsets ; 1048} 1049 1050static std::vector < size_t > unicode_regex_split_custom (const std::string & text ,const std::string & regex_expr ,const std::vector < size_t > & offsets ) { 1051 std::vector < size_t > bpe_offsets ; 1052 1053if (regex_expr == "'s|'t|'re|'ve|'m|'ll|'d| ?\\p{L}+| ?\\p{N}+| ?[^\\s\\p{L}\\p{N}]+|\\s+(?!\\S)" ) { 1054bpe_offsets = unicode_regex_split_custom_gpt2 (text ,offsets ); 1055 }else if ( 1056regex_expr == "(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\\r\\n\\p{L}\\p{N}]?\\p{L}+|\\p{N}{1,3}| ?[^\\s\\p{L}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+" || 1057regex_expr == "(?:'[sS]|'[tT]|'[rR][eE]|'[vV][eE]|'[mM]|'[lL][lL]|'[dD])|[^\\r\\n\\p{L}\\p{N}]?\\p{L}+|\\p{N}{1,3}| ?[^\\s\\p{L}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+" ) { 1058bpe_offsets = unicode_regex_split_custom_llama3 (text ,offsets ); 1059 }else if ( 1060regex_expr == "(?:'[sS]|'[tT]|'[rR][eE]|'[vV][eE]|'[mM]|'[lL][lL]|'[dD])|[^\\r\\n\\p{L}\\p{N}]?\\p{L}+|\\p{N}| ?[^\\s\\p{L}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+" ) { 1061bpe_offsets = unicode_regex_split_custom_qwen2 (text ,offsets ); 1062 }else if ( 1063regex_expr == "(?:'[sS]|'[tT]|'[rR][eE]|'[vV][eE]|'[mM]|'[lL][lL]|'[dD])|[^\\r\\n\\p{L}\\p{N}]?[\\p{L}\\p{M}]+|\\p{N}| ?[^\\s\\p{L}\\p{M}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+" ) { 1064bpe_offsets = unicode_regex_split_custom_qwen35 (text ,offsets ); 1065 }else if (regex_expr == "\\p{Han}+" ) { 1066// K2's first pattern - handle all K2 patterns together 1067bpe_offsets = unicode_regex_split_custom_kimi_k2 (text ,offsets ); 1068 }else if (regex_expr == "\\p{AFMoE_digits}" ) { 1069// AFMOE digit pattern - use custom implementation for proper splitting 1070bpe_offsets = unicode_regex_split_custom_afmoe (text ,offsets ); 1071 }else if (regex_expr == "[^\\n]+|[\\n]+" ) { 1072bpe_offsets = unicode_regex_split_custom_newlines (text ,offsets ); 1073 }else if (regex_expr == "\\d{1,3}(?=(?:\\d{3})*\\b)" ) { 1074// tiny_aya digit grouping pattern from tokenizer.json: 1075// {"type": "Split", "pattern": {"Regex": "\\d{1,3}(?=(?:\\d{3})*\\b)"}, "behavior": "Isolated"} 1076// Splits digits into groups of 3 from the right (e.g., 1234567 -> 1, 234, 567) 1077// TODO: Revisit this regex, in case there are any subtle tokenization differences with the original regex. 1078bpe_offsets = unicode_regex_split_custom_afmoe (text ,offsets ); 1079 } 1080 1081return bpe_offsets ; 1082} 1083 1084// 1085// interface 1086// 1087 1088std::string unicode_cpt_to_utf8 (uint32_t cpt ) { 1089 std::string result ; 1090 1091if (/* 0x00 <= cpt && */ cpt <=0x7f ) { 1092result .push_back (cpt ); 1093return result ; 1094 } 1095if (0x80 <=cpt && cpt <=0x7ff ) { 1096result .push_back (0xc0 | ((cpt >>6 )& 0x1f )); 1097result .push_back (0x80 | (cpt & 0x3f )); 1098return result ; 1099 } 1100if (0x800 <=cpt && cpt <=0xffff ) { 1101result .push_back (0xe0 | ((cpt >>12 )& 0x0f )); 1102result .push_back (0x80 | ((cpt >>6 )& 0x3f )); 1103result .push_back (0x80 | (cpt & 0x3f )); 1104return result ; 1105 } 1106if (0x10000 <=cpt && cpt <=0x10ffff ) { 1107result .push_back (0xf0 | ((cpt >>18 )& 0x07 )); 1108result .push_back (0x80 | ((cpt >>12 )& 0x3f )); 1109result .push_back (0x80 | ((cpt >>6 )& 0x3f )); 1110result .push_back (0x80 | (cpt & 0x3f )); 1111return result ; 1112 } 1113 1114throw std::invalid_argument ("invalid codepoint" ); 1115} 1116 1117std::vector < uint32_t > unicode_cpts_normalize_nfd (const std::vector < uint32_t > & cpts ) { 1118auto comp = [] (const uint32_t cpt ,const range_nfd & range ) { 1119return cpt < range .first ; 1120 }; 1121 std::vector < uint32_t > result (cpts .size ()); 1122for (size_t i = 0 ;i < cpts .size ();++ i ) { 1123const uint32_t cpt = cpts [i ]; 1124auto it = std::upper_bound (unicode_ranges_nfd .begin (),unicode_ranges_nfd .end (),cpt ,comp )- 1 ; 1125result [i ]= (it -> first <=cpt && cpt <=it -> last ) ?it -> nfd :cpt ; 1126 } 1127return result ; 1128} 1129 1130std::vector < uint32_t > unicode_cpts_from_utf8 (const std::string & utf8 ) { 1131 std::vector < uint32_t > result ; 1132result .reserve (utf8 .size ()); 1133size_t offset = 0 ; 1134while (offset < utf8 .size ()) { 1135try { 1136result .push_back (unicode_cpt_from_utf8 (utf8 ,offset )); 1137 } 1138catch (const std::invalid_argument & /*ex*/ ) { 1139// Silently ignore invalid UTF-8 input to avoid leaking the exception beyond llama_tokenize 1140++ offset ; 1141result .emplace_back (0xFFFD );// replacement character 1142 } 1143 } 1144return result ; 1145} 1146 1147unicode_cpt_flags unicode_cpt_flags_from_cpt (const uint32_t cpt ) { 1148static const unicode_cpt_flags undef (unicode_cpt_flags::UNDEFINED ); 1149static const auto cpt_flags = unicode_cpt_flags_array (); 1150return cpt < cpt_flags .size () ?cpt_flags [cpt ] :undef ; 1151} 1152 1153unicode_cpt_flags unicode_cpt_flags_from_utf8 (const std::string & utf8 ) { 1154static const unicode_cpt_flags undef (unicode_cpt_flags::UNDEFINED ); 1155if (utf8 .empty ()) { 1156return undef ;// undefined 1157 } 1158size_t offset = 0 ; 1159return unicode_cpt_flags_from_cpt (unicode_cpt_from_utf8 (utf8 ,offset )); 1160} 1161 1162std::string unicode_byte_to_utf8 (uint8_t byte ) { 1163static std::unordered_map < uint8_t , std::string > map = unicode_byte_to_utf8_map (); 1164return map .at (byte ); 1165} 1166 1167uint8_t unicode_utf8_to_byte (const std::string & utf8 ) { 1168static std::unordered_map < std::string ,uint8_t > map = unicode_utf8_to_byte_map (); 1169return map .at (utf8 ); 1170} 1171 1172uint32_t unicode_tolower (uint32_t cpt ) { 1173// binary search 1174auto it = std::lower_bound (unicode_map_lowercase .begin (),unicode_map_lowercase .end (),cpt , 1175 [](const std::pair < uint32_t ,uint32_t > & pair ,uint32_t value ) { 1176return pair .first < value ; 1177 }); 1178if (it != unicode_map_lowercase .end ()&& it -> first == cpt ) { 1179return it -> second ; 1180 } 1181return cpt ;// Return the original code point if no lowercase mapping is found 1182} 1183 1184bool unicode_cpt_is_han (uint32_t cpt ) { 1185// Han character ranges (Chinese/CJK characters) 1186// CJK Unified Ideographs (most common) 1187if (cpt >=0x4E00 && cpt <=0x9FFF )return true; 1188 1189// CJK Extension A 1190if (cpt >=0x3400 && cpt <=0x4DBF )return true; 1191 1192// CJK Extension B 1193if (cpt >=0x20000 && cpt <=0x2A6DF )return true; 1194 1195// CJK Extension C 1196if (cpt >=0x2A700 && cpt <=0x2B73F )return true; 1197 1198// CJK Extension D 1199if (cpt >=0x2B740 && cpt <=0x2B81F )return true; 1200 1201// CJK Extension E 1202if (cpt >=0x2B820 && cpt <=0x2CEAF )return true; 1203 1204// CJK Extension F 1205if (cpt >=0x2CEB0 && cpt <=0x2EBEF )return true; 1206 1207// CJK Compatibility Ideographs 1208if (cpt >=0xF900 && cpt <=0xFAFF )return true; 1209 1210// CJK Compatibility Ideographs Supplement 1211if (cpt >=0x2F800 && cpt <=0x2FA1F )return true; 1212 1213return false; 1214} 1215 1216std::vector < std::string > unicode_regex_split (const std::string & text ,const std::vector < std::string > & regex_exprs ,bool byte_encode ) { 1217// unicode categories 1218static const std::map < std::string ,int > k_ucat_enum = { 1219 {"\\p{N}" , unicode_cpt_flags::NUMBER }, 1220 {"\\p{L}" , unicode_cpt_flags::LETTER }, 1221 {"\\p{P}" , unicode_cpt_flags::PUNCTUATION }, 1222 {"\\p{M}" , unicode_cpt_flags::ACCENT_MARK }, 1223 {"\\p{S}" , unicode_cpt_flags::SYMBOL }, 1224 {"\\p{Lu}" , unicode_cpt_flags::LETTER },// Uppercase letter 1225 {"\\p{Ll}" , unicode_cpt_flags::LETTER },// Lowercase letter 1226 {"\\p{Lt}" , unicode_cpt_flags::LETTER },// Titlecase letter 1227 {"\\p{Lm}" , unicode_cpt_flags::LETTER },// Modifier letter 1228 {"\\p{Lo}" , unicode_cpt_flags::LETTER },// Other letter 1229 }; 1230 1231static const std::map < int ,int > k_ucat_cpt = { 1232 { unicode_cpt_flags::NUMBER ,0xD1 }, 1233 { unicode_cpt_flags::LETTER ,0xD2 }, 1234 { unicode_cpt_flags::PUNCTUATION ,0xD3 }, 1235 { unicode_cpt_flags::ACCENT_MARK ,0xD4 }, 1236 { unicode_cpt_flags::SYMBOL ,0xD5 }, 1237 }; 1238 1239static const std::map < int , std::string > k_ucat_map = { 1240 { unicode_cpt_flags::NUMBER ,"\x30-\x39" },// 0-9 1241 { unicode_cpt_flags::LETTER ,"\x41-\x5A\x61-\x7A" },// A-Za-z 1242 { unicode_cpt_flags::PUNCTUATION ,"\x21-\x23\x25-\x2A\x2C-\x2F\x3A-\x3B\x3F-\x40\\\x5B-\\\x5D\x5F\\\x7B\\\x7D" },// !-#%-*,-/:-;?-@\[-\]_\{\} 1243 { unicode_cpt_flags::ACCENT_MARK ,"" },// no sub-128 codepoints 1244 { unicode_cpt_flags::SYMBOL ,"\\\x24\\\x2B\x3C-\x3E\x5E\x60\\\x7C\\\x7E" },// $+<=>^`|~ 1245 }; 1246 1247// compute collapsed codepoints only if needed by at least one regex 1248bool need_collapse = false; 1249for (const auto & regex_expr :regex_exprs ) { 1250// search for unicode categories 1251for (const auto & ucat :k_ucat_enum ) { 1252if (std::string::npos != regex_expr .find (ucat .first )) { 1253need_collapse = true; 1254break ; 1255 } 1256 } 1257 } 1258 1259const auto cpts = unicode_cpts_from_utf8 (text ); 1260 1261// generate a "collapsed" representation of the text, where all codepoints are replaced by a single byte 1262// ref: https://github.com/ggml-org/llama.cpp/pull/6920#issuecomment-2081479935 1263 std::string text_collapsed ; 1264if (need_collapse ) { 1265// collapse all unicode categories 1266text_collapsed .resize (cpts .size ()); 1267 1268for (size_t i = 0 ;i < cpts .size ();++ i ) { 1269// keep single-byte codepoints as is 1270if (cpts [i ]< 128 ) { 1271text_collapsed [i ]= cpts [i ]; 1272continue ; 1273 } 1274 1275const auto flags = unicode_cpt_flags_from_cpt (cpts [i ]); 1276 1277if (flags .is_whitespace ) { 1278//NOTE: C++ std::regex \s does not mach 0x85, Rust and Python regex does. 1279//text_collapsed[i] = (char) 0x85; // <Next Line> as whitespace fallback 1280text_collapsed [i ]= (char )0x0B ;// <vertical tab> as whitespace fallback 1281 }else if (k_ucat_cpt .find (flags .category_flag ())!= k_ucat_cpt .end ()) { 1282text_collapsed [i ]= k_ucat_cpt .at (flags .category_flag ()); 1283 }else { 1284text_collapsed [i ]= (char )0xD0 ;// fallback 1285 } 1286 } 1287 } 1288 1289 std::vector < size_t > bpe_offsets = {cpts .size () }; 1290 1291for (const auto & regex_expr :regex_exprs ) { 1292// first, see if we have an efficient custom regex implementation 1293auto tmp = unicode_regex_split_custom (text ,regex_expr ,bpe_offsets ); 1294 1295if (!tmp .empty ()) { 1296bpe_offsets = std::move (tmp ); 1297continue ; 1298 } 1299 1300// fallback to general-purpose std::regex / std::wregex 1301try { 1302// if a unicode category is used in the regex, we use the collapsed text and replace the unicode category 1303// with the corresponding collapsed representation 1304bool use_collapsed = false; 1305for (const auto & ucat :k_ucat_enum ) { 1306if (std::string::npos != regex_expr .find (ucat .first )) { 1307use_collapsed = true; 1308break ; 1309 } 1310 } 1311const auto cpts_regex = unicode_cpts_from_utf8 (regex_expr ); 1312 1313if (use_collapsed ) { 1314// sanity-check that the original regex does not contain any non-ASCII characters 1315for (size_t i = 0 ;i < cpts_regex .size ();++ i ) { 1316if (cpts_regex [i ] >=128 ) { 1317throw std::runtime_error ("Regex includes both unicode categories and non-ASCII characters - not supported" ); 1318 } 1319 } 1320 1321// generate a collapsed representation of the regex 1322 std::string regex_expr_collapsed ; 1323 1324// track if we are inside [], because nested [] are not allowed 1325bool inside = false; 1326for (size_t i = 0 ;i < regex_expr .size ();++ i ) { 1327if (regex_expr [i ]== '[' && (i == 0 || regex_expr [i - 1 ]!= '\\' )) { 1328regex_expr_collapsed += '[' ; 1329inside = true; 1330continue ; 1331 } 1332 1333if (inside && regex_expr [i ]== ']' && regex_expr [i - 1 ]!= '\\' ) { 1334regex_expr_collapsed += ']' ; 1335inside = false; 1336continue ; 1337 } 1338 1339// Match \p{...} Unicode properties of varying lengths 1340if (regex_expr [i + 0 ]== '\\' && i + 3 < regex_expr .size ()&& 1341regex_expr [i + 1 ]== 'p' && 1342regex_expr [i + 2 ]== '{' ) { 1343// Find the closing brace 1344size_t closing_brace = regex_expr .find ('}' ,i + 3 ); 1345if (closing_brace != std::string::npos && closing_brace <=i + 10 ) {// reasonable limit 1346const std::string pat = regex_expr .substr (i ,closing_brace - i + 1 ); 1347if (k_ucat_enum .find (pat )!= k_ucat_enum .end ()) { 1348if (!inside ) { 1349regex_expr_collapsed += '[' ; 1350 } 1351regex_expr_collapsed += k_ucat_cpt .at (k_ucat_enum .at (pat )); 1352regex_expr_collapsed += k_ucat_map .at (k_ucat_enum .at (pat )); 1353if (!inside ) { 1354regex_expr_collapsed += ']' ; 1355 } 1356i = closing_brace ; 1357continue ; 1358 } 1359 } 1360 } 1361 1362regex_expr_collapsed += regex_expr [i ]; 1363 } 1364 1365//printf("text_collapsed: %s\n", text_collapsed.c_str()); 1366//printf("regex_expr_collapsed: %s\n", regex_expr_collapsed.c_str()); 1367bpe_offsets = unicode_regex_split_stl (text_collapsed ,regex_expr_collapsed ,bpe_offsets ); 1368 }else { 1369// no unicode category used, we can use std::wregex directly 1370 std::wstring wregex_expr (cpts_regex .begin (),cpts_regex .end ()); 1371 1372// std::wregex \s does not mach non-ASCII whitespaces, using 0x0B as fallback 1373 std::wstring wtext (cpts .begin (),cpts .end ()); 1374for (size_t i = 0 ;i < wtext .size ();++ i ) { 1375if (wtext [i ]> 0x7F && unicode_cpt_flags_from_cpt (wtext [i ]).is_whitespace ) { 1376wtext [i ]= 0x0B ; 1377 } 1378 } 1379 1380//printf("text: %s\n", text.c_str()); 1381//printf("regex_expr: %s\n", regex_expr.c_str()); 1382bpe_offsets = unicode_regex_split_stl (wtext ,wregex_expr ,bpe_offsets ); 1383 } 1384 }catch (std::regex_error & e ) { 1385fprintf (stderr ,"Failed to process regex: '%s'\n" ,regex_expr .c_str ()); 1386fprintf (stderr ,"Regex error: %s\n" ,e .what ()); 1387throw std::runtime_error ("Failed to process regex" ); 1388 } 1389 } 1390 1391 std::vector < std::string > bpe_words ; 1392bpe_words .reserve (bpe_offsets .size ());// reserve memory for the approximate size 1393 1394size_t start = 0 ; 1395for (size_t & offset :bpe_offsets ) { 1396bpe_words .emplace_back (); 1397for (size_t i = start ;i < start + offset ;++ i ) { 1398bpe_words .back ()+= unicode_cpt_to_utf8 (cpts [i ]); 1399 } 1400start += offset ; 1401 } 1402 1403if (byte_encode ) { 1404return unicode_byte_encoding_process (bpe_words ); 1405 } 1406 1407return bpe_words ; 1408}