char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
98a01afd9
char/aliceai-t5-moe
1#include "llama-vocab.h" 2 3#include "ggml.h" 4#include "gguf.h" 5#include "llama-impl.h" 6#include "llama-model-loader.h" 7 8#include "unicode.h" 9 10#include <algorithm> 11#include <cassert> 12#include <cctype> 13#include <cfloat> 14#include <cmath> 15#include <cstdarg> 16#include <cstring> 17#include <cstdlib> 18#include <forward_list> 19#include <limits> 20#include <map> 21#include <queue> 22#include <set> 23#include <unordered_map> 24 25// 26// helpers 27// 28 29struct naive_trie { 30naive_trie () :has_value (false),value (0 ) { 31 } 32void insert (const char * key ,size_t len ,int32_t value = 0 ) { 33if (len == 0 ) { 34this -> has_value = true; 35this -> value = value ; 36return ; 37 } 38char c = key [0 ]; 39auto res = children .find (c ); 40if (res != children .end ()) { 41res -> second .insert (key + 1 ,len - 1 ,value ); 42 }else { 43auto res = children .insert (std::make_pair (c ,naive_trie ())); 44res .first -> second .insert (key + 1 ,len - 1 ,value ); 45 } 46 } 47 std::pair < const char * ,size_t > get_longest_prefix (const char * key ,size_t len ,size_t offset = 0 )const { 48if (len == 0 || offset == len ) { 49return std::make_pair (key ,offset ); 50 } 51char c = key [offset ]; 52auto res = children .find (c ); 53if (res != children .end ()) { 54return res -> second .get_longest_prefix (key ,len ,offset + 1 ); 55 } 56 57return std::make_pair (key ,offset ); 58 } 59const struct naive_trie * traverse (const char c )const { 60auto res = children .find (c ); 61if (res != children .end ()) { 62return & res -> second ; 63 } 64 65return NULL ; 66 } 67 std::map < char ,struct naive_trie > children ; 68bool has_value ; 69llama_token value ; 70}; 71 72// 73// tokenizers 74// 75 76struct llm_tokenizer { 77llm_tokenizer () {} 78virtual ~llm_tokenizer ()= default ; 79}; 80 81struct llm_symbol { 82using index = int ; 83index prev ; 84index next ; 85const char * text ; 86size_t n ; 87}; 88 89static_assert(std::is_trivially_copyable < llm_symbol > ::value ,"llm_symbol is not trivially copyable" ); 90 91// 92// SPM tokenizer 93// original implementation: 94// https://github.com/ggml-org/llama.cpp/commit/074bea2eb1f1349a0118239c4152914aecaa1be4 95// 96 97struct llm_bigram_spm { 98struct comparator { 99bool operator()(llm_bigram_spm & l ,llm_bigram_spm & r ) { 100return (l .score < r .score )|| (l .score == r .score && l .left > r .left ); 101 } 102 }; 103using queue_storage = std::vector < llm_bigram_spm > ; 104using queue = std::priority_queue < llm_bigram_spm ,queue_storage ,comparator > ; 105 llm_symbol::index left ; 106 llm_symbol::index right ; 107float score ; 108size_t size ; 109}; 110 111struct llm_tokenizer_spm :llm_tokenizer { 112llm_tokenizer_spm (const llama_vocab & /*vocab*/ ) {} 113}; 114 115struct llm_tokenizer_spm_session { 116llm_tokenizer_spm_session (const llama_vocab & vocab ) :vocab (vocab ) {} 117 118void tokenize (const std::string & text , std::vector < llama_token > & output ) { 119// split string into utf8 chars 120int index = 0 ; 121size_t offs = 0 ; 122while (offs < text .size ()) { 123llm_symbol sym ; 124size_t len = unicode_len_utf8 (text [offs ]); 125sym .text = text .c_str ()+ offs ; 126sym .n = std::min (len ,text .size ()- offs ); 127offs += sym .n ; 128sym .prev = index - 1 ; 129sym .next = offs == text .size () ?-1 :index + 1 ; 130index ++ ; 131symbols .emplace_back (sym ); 132 } 133 134// seed the work queue with all possible 2-character tokens. 135for (int i = 1 ;i < (int )symbols .size ();++ i ) { 136try_add_bigram (i - 1 ,i ); 137 } 138 139// keep substituting the highest frequency pairs for as long as we can. 140while (!work_queue .empty ()) { 141auto bigram = work_queue .top (); 142work_queue .pop (); 143 144auto & left_sym = symbols [bigram .left ]; 145auto & right_sym = symbols [bigram .right ]; 146 147// if one of the symbols already got merged, skip it. 148if (left_sym .n == 0 || right_sym .n == 0 || 149left_sym .n + right_sym .n != bigram .size ) { 150continue ; 151 } 152 153// merge the right sym into the left one 154left_sym .n += right_sym .n ; 155right_sym .n = 0 ; 156 157//LLAMA_LOG_INFO("left = '%*s' size = %zu\n", (int) left_sym.n, left_sym.text, bigram.size); 158 159// remove the right sym from the chain 160left_sym .next = right_sym .next ; 161if (right_sym .next >=0 ) { 162symbols [right_sym .next ].prev = bigram .left ; 163 } 164 165// find more substitutions 166try_add_bigram (left_sym .prev ,bigram .left ); 167try_add_bigram (bigram .left ,left_sym .next ); 168 } 169 170for (int i = 0 ;i != -1 ;i = symbols [i ].next ) { 171auto & symbol = symbols [i ]; 172resegment (symbol ,output ); 173 } 174 } 175 176private : 177void resegment (llm_symbol & symbol , std::vector < llama_token > & output ) { 178auto text = std::string (symbol .text ,symbol .n ); 179auto token = vocab .text_to_token (text ); 180 181// Do we need to support is_unused? 182if (token != LLAMA_TOKEN_NULL ) { 183output .push_back (token ); 184return ; 185 } 186 187const auto p = rev_merge .find (text ); 188 189if (p == rev_merge .end ()) { 190// output any symbols that did not form tokens as bytes. 191output .reserve (output .size ()+ symbol .n ); 192for (int j = 0 ;j < (int )symbol .n ;++ j ) { 193llama_token id = vocab .byte_to_token (symbol .text [j ]); 194output .push_back (id ); 195 } 196return ; 197 } 198 199resegment (symbols [p -> second .first ],output ); 200resegment (symbols [p -> second .second ],output ); 201 } 202 203void try_add_bigram (int left ,int right ) { 204if (left == -1 || right == -1 ) { 205return ; 206 } 207const std::string text = std::string (symbols [left ].text ,symbols [left ].n + symbols [right ].n ); 208auto token = vocab .text_to_token (text ); 209 210if (token == LLAMA_TOKEN_NULL ) { 211return ; 212 } 213 214if (static_cast < uint32_t > (token ) >=vocab .n_tokens ()) { 215return ; 216 } 217 218const auto & tok_data = vocab .get_token_data (token ); 219 220llm_bigram_spm bigram ; 221bigram .left = left ; 222bigram .right = right ; 223bigram .score = tok_data .score ; 224bigram .size = text .size (); 225 226work_queue .push (bigram ); 227 228// Do we need to support is_unused? 229rev_merge [text ]= std::make_pair (left ,right ); 230 } 231 232const llama_vocab & vocab ; 233// currently unused 234// const llm_tokenizer_spm * spm_tokenizer; 235 236 std::vector < llm_symbol > symbols ; 237 llm_bigram_spm::queue work_queue ; 238 std::map < std::string , std::pair < int ,int >> rev_merge ; 239}; 240 241// 242// BPE tokenizer 243// adapted from https://github.com/cmp-nct/ggllm.cpp [MIT License] 244// tried to simplify unicode stuff, so most likely does not work 100% correctly! 245// 246 247// TODO: there are a lot of common parts between spm and bpe tokenizers, should be refactored and reused 248 249template < typename T ,typename Container = std::vector < T > ,typename Compare = std::less < typename Container ::value_type >> 250class llama_priority_queue :public std::priority_queue < T ,Container ,Compare > { 251public : 252using std::priority_queue < T ,Container ,Compare > ::priority_queue ; 253 254T pop_move () { 255T item = std::move (this -> c .front ()); 256 std::pop_heap (this -> c .begin (),this -> c .end (),this -> comp ); 257this -> c .pop_back (); 258return item ; 259 } 260 261void pop ()= delete ; 262}; 263 264struct llm_bigram_bpe { 265struct comparator { 266bool operator()(const llm_bigram_bpe & l ,const llm_bigram_bpe & r )const { 267return l .rank > r .rank || (l .rank == r .rank && l .left > r .left ); 268 } 269 }; 270 271using queue_storage = std::vector < llm_bigram_bpe > ; 272using queue = llama_priority_queue < llm_bigram_bpe ,queue_storage ,comparator > ; 273 llm_symbol::index left ; 274 llm_symbol::index right ; 275 std::string text ; 276int rank ; 277size_t size ; 278}; 279 280struct llm_tokenizer_bpe :llm_tokenizer { 281llm_tokenizer_bpe (const llama_vocab & vocab ) { 282GGML_ASSERT (vocab .get_type ()== LLAMA_VOCAB_TYPE_BPE ); 283switch (vocab .get_pre_type ()) { 284case LLAMA_VOCAB_PRE_TYPE_LLAMA3 : 285regex_exprs = { 286// original regex from tokenizer.json 287//"(?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+", 288 289// adapted: https://github.com/ggml-org/llama.cpp/pull/6920#issuecomment-2080233989 290"(?:'[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+" , 291 }; 292break ; 293case LLAMA_VOCAB_PRE_TYPE_JAIS2 : 294regex_exprs = { 295// original regex from tokenizer.json 296//"(?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{512}(?!\\S)|\\s{256}(?!\\S)|\\s{128}(?!\\S)|\\s{64}(?!\\S)|\\s{32}(?!\\S)|\\s{16}(?!\\S)|\\s{8}(?!\\S)|\\s{4}(?!\\S)|\\s{1,2}(?!\\S)|\\s{1}", 297 298// adapted: same as llama3 but with cascading whitespace pattern 299"(?:'[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{512}(?!\\S)|\\s{256}(?!\\S)|\\s{128}(?!\\S)|\\s{64}(?!\\S)|\\s{32}(?!\\S)|\\s{16}(?!\\S)|\\s{8}(?!\\S)|\\s{4}(?!\\S)|\\s{1,2}(?!\\S)|\\s{1}" , 300 }; 301break ; 302case LLAMA_VOCAB_PRE_TYPE_DBRX : 303case LLAMA_VOCAB_PRE_TYPE_SMAUG : 304regex_exprs = { 305// same as llama3 306"(?:'[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+" , 307 }; 308break ; 309case LLAMA_VOCAB_PRE_TYPE_DEEPSEEK_LLM : 310regex_exprs = { 311"[\r\n]" , 312"\\s?[A-Za-zµÀ-ÖØ-öø-ƺƼ-ƿDŽ-ʓʕ-ʯͰ-ͳͶͷͻ-ͽͿΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԯԱ-ՖႠ-ჅᎠ-Ᏽᏸ-ᏽᲐ-ᲺᲽ-Ჿᴀ-ᴫᵫ-ᵷᵹ-ᶚḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼℂℇℊ-ℓℕℙ-ℝℤΩℨK-ℭℯ-ℴℹℼ-ℿⅅ-ⅉⅎↃↄⰀ-ⱻⱾ-ⳤⳫ-ⳮⳲⳳꙀ-ꙭꚀ-ꚛꜢ-ꝯꝱ-ꞇꞋ-ꞎꭰ-ꮿff-stﬓ-ﬗA-Za-z𐐀-𐑏𐒰-𐓓𐓘-𐓻𐲀-𐲲𐳀-𐳲𑢠-𑣟𞤀-𞥃]+" , 313"\\s?[!-/:-~!-/:-~‘-‟ -。]+" , 314"\\s+$" , 315"[一-龥ࠀ-一가-]+" , 316"\\p{N}+" , 317 }; 318break ; 319case LLAMA_VOCAB_PRE_TYPE_DEEPSEEK3_LLM : 320case LLAMA_VOCAB_PRE_TYPE_HUNYUAN_DENSE : 321case LLAMA_VOCAB_PRE_TYPE_JOYAI_LLM : 322case LLAMA_VOCAB_PRE_TYPE_HY_V4 : 323regex_exprs = { 324"\\p{N}{1,3}" , 325"[一-龥-ゟ゠-ヿ]+" , 326"[!\"#$%&'()*+,\\-./:;<=>?@\\[\\\\\\]^_`{|}~][A-Za-z]+|[^\r\n\\p{L}\\p{P}\\p{S}]?[\\p{L}\\p{M}]+| ?[\\p{P}\\p{S}]+[\r\n]*|\\s*[\r\n]+|\\s+(?!\\S)|\\s+" , 327 }; 328break ; 329case LLAMA_VOCAB_PRE_TYPE_SPARK2_5 : 330regex_exprs = { 331"\\p{N}{1,3}" , 332"[一-龥-ゟ゠-ヿ]+" , 333"[!\"#$%&'()*+,\\-./:;<=>?@\\[\\\\\\]^_`{|}~][A-Za-z]+|[^\r\n\\p{L}\\p{P}\\p{S}]?[\\p{L}\\p{M}]+| ?[\\p{P}\\p{S}]+|[\r\n]|\\s+(?!\\S)|\\s+" , 334"\\p{N}" , 335 }; 336break ; 337case LLAMA_VOCAB_PRE_TYPE_YOUTU : 338regex_exprs = { 339"[가-힣ㄱ-ㆎ]+|[!…“”‘’—:;,、-〿︰-﹏]+|[ㄅ-ㄯ]+|[一-龥-ゟ゠-ヿ]+" , 340"[^\\r\\n\\p{L}\\p{N}]?[\\p{Lu}\\p{Lt}\\p{Lm}\\p{Lo}\\p{M}]*[\\p{Ll}\\p{Lm}\\p{Lo}\\p{M}]+(?:'[sS]|'[tT]|'[rR][eE]|'[vV][eE]|'[mM]|'[lL][lL]|'[dD])?|[^\\r\\n\\p{L}\\p{N}]?[\\p{Lu}\\p{Lt}\\p{Lm}\\p{Lo}\\p{M}]+[\\p{Ll}\\p{Lm}\\p{Lo}\\p{M}]*(?:'[sS]|'[tT]|'[rR][eE]|'[vV][eE]|'[mM]|'[lL][lL]|'[dD])?|\\p{N}| ?[^\\s\\p{L}\\p{N}]+[\\r\\n/]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+" , 341 }; 342break ; 343case LLAMA_VOCAB_PRE_TYPE_DEEPSEEK_CODER : 344regex_exprs = { 345"[\r\n]" , 346"\\s?\\p{L}+" , 347"\\s?\\p{P}+" , 348"[一-龥ࠀ-一가-]+" , 349"\\p{N}" , 350 }; 351break ; 352case LLAMA_VOCAB_PRE_TYPE_FALCON : 353regex_exprs = { 354"[\\p{P}\\$\\+<=>\\^~\\|`]+" , 355"'s|'t|'re|'ve|'m|'ll|'d| ?\\p{L}+| ?\\p{N}+| ?[^\\s\\p{L}\\p{N}]+|\\s+(?!\\S)" , 356"[0-9][0-9][0-9]" , 357 }; 358break ; 359case LLAMA_VOCAB_PRE_TYPE_STARCODER : 360case LLAMA_VOCAB_PRE_TYPE_REFACT : 361case LLAMA_VOCAB_PRE_TYPE_COMMAND_R : 362case LLAMA_VOCAB_PRE_TYPE_SMOLLM : 363case LLAMA_VOCAB_PRE_TYPE_CODESHELL : 364case LLAMA_VOCAB_PRE_TYPE_EXAONE : 365case LLAMA_VOCAB_PRE_TYPE_MINERVA : 366case LLAMA_VOCAB_PRE_TYPE_MELLUM2 : 367regex_exprs = { 368"\\p{N}" , 369"'s|'t|'re|'ve|'m|'ll|'d| ?\\p{L}+| ?\\p{N}+| ?[^\\s\\p{L}\\p{N}]+|\\s+(?!\\S)" , 370 }; 371break ; 372case LLAMA_VOCAB_PRE_TYPE_GPT2 : 373case LLAMA_VOCAB_PRE_TYPE_MPT : 374case LLAMA_VOCAB_PRE_TYPE_OLMO : 375case LLAMA_VOCAB_PRE_TYPE_JAIS : 376case LLAMA_VOCAB_PRE_TYPE_TRILLION : 377case LLAMA_VOCAB_PRE_TYPE_GRANITE_DOCLING : 378regex_exprs = { 379"'s|'t|'re|'ve|'m|'ll|'d| ?\\p{L}+| ?\\p{N}+| ?[^\\s\\p{L}\\p{N}]+|\\s+(?!\\S)" , 380 }; 381break ; 382case LLAMA_VOCAB_PRE_TYPE_STABLELM2 : 383case LLAMA_VOCAB_PRE_TYPE_QWEN2 : 384case LLAMA_VOCAB_PRE_TYPE_HUNYUAN : 385case LLAMA_VOCAB_PRE_TYPE_SOLAR_OPEN : 386regex_exprs = { 387// original regex from tokenizer.json 388// "(?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+" 389"(?:'[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+" , 390 }; 391break ; 392case LLAMA_VOCAB_PRE_TYPE_QWEN35 : 393regex_exprs = { 394// original regex from tokenizer.json 395// "(?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+" 396"(?:'[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+" , 397 }; 398break ; 399case LLAMA_VOCAB_PRE_TYPE_PORO : 400case LLAMA_VOCAB_PRE_TYPE_BLOOM : 401case LLAMA_VOCAB_PRE_TYPE_GPT3_FINNISH : 402regex_exprs = { 403" ?[^(\\s|.,!?…。,、।۔،)]+" , 404 }; 405break ; 406case LLAMA_VOCAB_PRE_TYPE_CHATGLM4 : 407regex_exprs = { 408"(?:'[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+" , 409 }; 410break ; 411case LLAMA_VOCAB_PRE_TYPE_VIKING : 412regex_exprs = { 413" ?[^(\\s|.,!?…。,、।۔،)]+" , 414"\\p{N}" , 415 }; 416break ; 417case LLAMA_VOCAB_PRE_TYPE_TEKKEN : 418// original regex from tokenizer.json 419// "[^\\r\\n\\p{L}\\p{N}]?[\\p{Lu}\\p{Lt}\\p{Lm}\\p{Lo}\\p{M}]*[\\p{Ll}\\p{Lm}\\p{Lo}\\p{M}]+|[^\\r\\n\\p{L}\\p{N}]?[\\p{Lu}\\p{Lt}\\p{Lm}\\p{Lo}\\p{M}]+[\\p{Ll}\\p{Lm}\\p{Lo}\\p{M}]*|\\p{N}| ?[^\\s\\p{L}\\p{N}]+[\\r\\n/]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+" 420regex_exprs = { 421"[^\\r\\n\\p{L}\\p{N}]?((?=[\\p{L}])([^a-z]))*((?=[\\p{L}])([^A-Z]))+|[^\\r\\n\\p{L}\\p{N}]?((?=[\\p{L}])([^a-z]))+((?=[\\p{L}])([^A-Z]))*|\\p{N}| ?[^\\s\\p{L}\\p{N}]+[\\r\\n/]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+" , 422 }; 423break ; 424case LLAMA_VOCAB_PRE_TYPE_CHAMELEON : 425// Note: in theory, the special token (sentinel and image token) regex_exprs below 426// are unnecessary, as they are split in `tokenizer_st_partition` anyway. 427// However, since the upstream pre-tokenizer uses them, they are also 428// included here (see https://huggingface.co/facebook/chameleon-7b). 429regex_exprs = { 430"<sentinel:[0-9]+>" ,// Sentinel tokens 431"(IMGIMG)((A|B|C|D|E|F|G|H|I){1,4})Z" ,// Image tokens 432"([\\t\\n]| | )" ,// directly from tokenizer.json 433"\\p{N}" ,// Individual digits 434"[\\p{P}!-/:-@\\[-`{-~]" ,// Punctuation, Isolated 435"'s|'t|'re|'ve|'m|'ll|'d| ?\\p{L}+| ?\\p{N}+| ?[^\\s\\p{L}\\p{N}]+|\\s+(?!\\S)" , 436 }; 437break ; 438case LLAMA_VOCAB_PRE_TYPE_GPT4O : 439case LLAMA_VOCAB_PRE_TYPE_MINIMAX_M2 : 440regex_exprs = { 441// original regex from tokenizer.json 442// "[^\\r\\n\\p{L}\\p{N}]?[\\p{Lu}\\p{Lt}\\p{Lm}\\p{Lo}\\p{M}]*[\\p{Ll}\\p{Lm}\\p{Lo}\\p{M}]+(?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{Ll}\\p{Lm}\\p{Lo}\\p{M}]*(?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+", 443"[^\\r\\n\\p{L}\\p{N}]?((?=[\\p{L}])([^a-z]))*((?=[\\p{L}])([^A-Z]))+(?:'[sS]|'[tT]|'[rR][eE]|'[vV][eE]|'[mM]|'[lL][lL]|'[dD])?|[^\\r\\n\\p{L}\\p{N}]?((?=[\\p{L}])([^a-z]))+((?=[\\p{L}])([^A-Z]))*(?:'[sS]|'[tT]|'[rR][eE]|'[vV][eE]|'[mM]|'[lL][lL]|'[dD])?|\\p{N}{1,3}| ?[^\\s\\p{L}\\p{N}]+[\\r\\n/]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+" , 444 }; 445break ; 446case LLAMA_VOCAB_PRE_TYPE_GRANITE_EMB_MULTI : 447// Same lookaheads as GPT4O but with \p{M} added so combining marks 448// (diacritics) attach to their base letters. Avoids excessive 449// backtracking on scripts that use them heavily (Bengali, Hindi, 450// Telugu, Thai, ...). See PR #22716 for benchmarks. 451regex_exprs = { 452"[^\\r\\n\\p{L}\\p{N}]?((?=[\\p{L}\\p{M}])([^a-z]))*((?=[\\p{L}\\p{M}])([^A-Z]))+(?:'[sS]|'[tT]|'[rR][eE]|'[vV][eE]|'[mM]|'[lL][lL]|'[dD])?|[^\\r\\n\\p{L}\\p{N}]?((?=[\\p{L}\\p{M}])([^a-z]))+((?=[\\p{L}\\p{M}])([^A-Z]))*(?:'[sS]|'[tT]|'[rR][eE]|'[vV][eE]|'[mM]|'[lL][lL]|'[dD])?|\\p{N}{1,3}| ?[^\\s\\p{L}\\p{N}]+[\\r\\n/]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+" , 453 }; 454break ; 455case LLAMA_VOCAB_PRE_TYPE_TINY_AYA : 456regex_exprs = { 457// original regex from tokenizer.json: "\\d{1,3}(?=(?:\\d{3})*\\b)" 458"\\d{1,3}(?=(?:\\d{3})*\\b)" , 459// original regex from tokenizer.json: "[^\\r\\n\\p{L}\\p{N}]?[\\p{Lu}\\p{Lt}\\p{Lm}\\p{Lo}\\p{M}]*[\\p{Ll}\\p{Lm}\\p{Lo}\\p{M}]+(?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{Ll}\\p{Lm}\\p{Lo}\\p{M}]*(?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+" 460"[^\\r\\n\\p{L}\\p{N}]?[\\p{Lu}\\p{Lt}\\p{Lm}\\p{Lo}\\p{M}]*[\\p{Ll}\\p{Lm}\\p{Lo}\\p{M}]+(?:'[sS]|'[tT]|'[rR][eE]|'[vV][eE]|'[mM]|'[lL][lL]|'[dD])?|[^\\r\\n\\p{L}\\p{N}]?[\\p{Lu}\\p{Lt}\\p{Lm}\\p{Lo}\\p{M}]+[\\p{Ll}\\p{Lm}\\p{Lo}\\p{M}]*(?:'[sS]|'[tT]|'[rR][eE]|'[vV][eE]|'[mM]|'[lL][lL]|'[dD])?|\\p{N}{1,3}| ?[^\\s\\p{L}\\p{N}]+[\\r\\n/]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+" , 461 }; 462break ; 463case LLAMA_VOCAB_PRE_TYPE_KIMI_K2 : 464regex_exprs = { 465// K2 trigger pattern - this will activate the custom K2 handler in unicode.cpp 466// The custom handler implements all K2 patterns with proper Han character exclusion 467"\\p{Han}+" , 468 }; 469break ; 470case LLAMA_VOCAB_PRE_TYPE_SUPERBPE : 471regex_exprs = { 472"\\p{N}+" , 473"(?=(\\d{3})+(?!\\d))" , 474 }; 475break ; 476case LLAMA_VOCAB_PRE_TYPE_BAILINGMOE : 477regex_exprs = { 478// original regex from tokenizer.json 479// "'(?i:[sdmt]|ll|ve|re)|[^\\r\\n\\p{L}\\p{N}]?+\\p{L}+|\\p{N}| ?[^\\s\\p{L}\\p{N}]++[\\r\\n]*|\\s*[\\r\\n]|\\s+(?!\\S)|\\s+" 480// FIXME? Changed possessive quantifiers (?+ and ++) to greedy to avoid errors and imatrix hanging (tried atomic grouping but it's not supported?) 481"'(?:[sSdDmMtT]|[lL][lL]|[vV][eE]|[rR][eE])|[^\\r\\n\\p{L}\\p{N}]?\\p{L}+|\\p{N}| ?[^\\s\\p{L}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]|\\s+(?!\\S)|\\s+" , 482 }; 483break ; 484case LLAMA_VOCAB_PRE_TYPE_SEED_CODER : 485regex_exprs = { 486// original regex from tokenizer.json 487// "(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\\p{L}\\p{N}]?\\p{L}+|\\p{N}{1}| ?[^\\s\\p{L}\\p{N}\r\n]+|\\s*[\r\n]+|\\s+(?!\\S)|\\s+" 488"(?:'[sS]|'[tT]|'[rR][eE]|'[vV][eE]|'[mM]|'[lL][lL]|'[dD])|[^\\r\\n\\p{L}\\p{N}]?\\p{L}+|\\p{N}{1}| ?[^\\s\\p{L}\\p{N}\\r\\n]+|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+" , 489 }; 490break ; 491case LLAMA_VOCAB_PRE_TYPE_GROK_2 : 492regex_exprs = { 493// original regex from tokenizer.json 494// "(?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+" 495"(?:'[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+" , 496 }; 497break ; 498case LLAMA_VOCAB_PRE_TYPE_AFMOE : 499regex_exprs = { 500// Digit handling - uses custom implementation in unicode.cpp 501// Groups digits with leading 1-2 based on total length modulo 3 502"\\p{AFMoE_digits}" , 503// CJK and Asian scripts (using direct Unicode literals) 504"[一-鿿㐀-䶿豈--ゟ゠-ヿ・-゚⼀-เ--ក-က-႟ꩠ-ꩿꧠ-가-ᄀ-ᇿ]+" , 505// Main BPE pattern 506"[!\"#$%&'()*+,\\-./:;<=>?@\\[\\\\\\]^_`{|}~][A-Za-z]+|[^\\r\\n\\p{L}\\p{P}\\p{S}]?[\\p{L}\\p{M}]+| ?[\\p{P}\\p{S}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+" , 507 }; 508break ; 509case LLAMA_VOCAB_PRE_TYPE_LAGUNA : 510regex_exprs = { 511"[^\\n]+|[\\n]+" , 512"(?:'[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+" , 513 }; 514break ; 515case LLAMA_VOCAB_PRE_TYPE_EXAONE_MOE : 516regex_exprs = { 517// original regex from tokenizer.json 518// "(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\\r\\n\\p{L}\\p{N}]?(?:\\p{L}\\p{M}*(?: \\p{L}\\p{M}*)*)+|\\p{N}| ?[^\\s\\p{L}\\p{N}]+[\\r\\n/]?|\\s*[\\r\\n]|\\s+(?!\\S)|\\s+" 519"(?:'[sS]|'[tT]|'[rR][eE]|'[vV][eE]|'[mM]|'[lL][lL]|'[dD])|[^\\r\\n\\p{L}\\p{N}]?(?:\\p{L}\\p{M}*(?: \\p{L}\\p{M}*)*)+|\\p{N}| ?[^\\s\\p{L}\\p{N}]+[\\r\\n/]?|\\s*[\\r\\n]|\\s+(?!\\S)|\\s+" , 520 }; 521break ; 522case LLAMA_VOCAB_PRE_TYPE_GEMMA4 : 523// Gemma4 uses SPM-style BPE: spaces are replaced with ▁ by the 524// normalizer, then BPE merges run on the whole text without 525// word-level pre-splitting. We only need to split on newlines 526// since BPE merge lookup asserts no newlines in tokens. 527regex_exprs = { 528"[^\\n]+|[\\n]+" , 529 }; 530byte_encode = false;// uses raw UTF-8, not GPT-2 byte encoding 531break ; 532case LLAMA_VOCAB_PRE_TYPE_SARVAM_MOE : 533// Sarvam uses SPM-style BPE (same shape as Gemma4): spaces replaced with U+2581 534// by the normalizer, BPE merges over the whole text on raw UTF-8. 535regex_exprs = { 536"[^\\n]+|[\\n]+" , 537 }; 538byte_encode = false; 539break ; 540case LLAMA_VOCAB_PRE_TYPE_MINICPM5 : 541regex_exprs = { 542// original regex from tokenizer.json (openbmb/MiniCPM5-1B) 543"\\p{N}{1,3}" , 544// "(?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+" 545"(?:'[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+" , 546 }; 547break ; 548case LLAMA_VOCAB_PRE_TYPE_WHITESPACE : 549// whitespace pre-tokenizer (jinaai/jina-embeddings-v2-base-zh) 550regex_exprs = { 551"\\S+" , 552 }; 553byte_encode = false; 554break ; 555default : 556// default regex for BPE tokenization pre-processing 557regex_exprs = { 558"[\\p{P}\\$\\+<=>\\^~\\|]+" , 559"'s|'t|'re|'ve|'m|'ll|'d| ?\\p{L}+| ?\\p{N}+| ?[^\\s\\p{L}\\p{N}]+|\\s+(?!\\S)" , 560"\\p{N}+" , 561"[0-9][0-9][0-9]" , 562 }; 563break ; 564 } 565 } 566 567 std::vector < std::string > regex_exprs ; 568bool byte_encode = true;// GPT-2 byte encoding; false for SPM-style BPE (raw UTF-8) 569}; 570 571struct llm_tokenizer_bpe_session { 572llm_tokenizer_bpe_session (const llama_vocab & vocab ,const llm_tokenizer_bpe & tokenizer ) :vocab (vocab ),tokenizer (tokenizer ) {} 573 574virtual ~llm_tokenizer_bpe_session ()= default ; 575 576static void append (const llama_token token_id , std::vector < llama_token > & output ) { 577output .push_back (token_id ); 578 } 579 580bool append_bos (std::vector < llama_token > & output )const { 581if (vocab .get_add_bos ()) { 582GGML_ASSERT (vocab .token_bos ()!= LLAMA_TOKEN_NULL ); 583output .push_back (vocab .token_bos ()); 584return true; 585 } 586return false; 587 } 588 589bool append_eos (std::vector < llama_token > & output )const { 590if (vocab .get_add_eos ()) { 591GGML_ASSERT (vocab .token_eos ()!= LLAMA_TOKEN_NULL ); 592output .push_back (vocab .token_eos ()); 593return true; 594 } 595return false; 596 } 597 598void check_double_bos_eos (const std::vector < llama_token > & output )const { 599if (vocab .get_add_bos ()&& output .size () >=2 && output [1 ]== vocab .token_bos ()) { 600LLAMA_LOG_WARN ( 601"%s: Added a BOS token to the prompt as specified by the model but the prompt " 602"also starts with a BOS token. So now the final prompt starts with 2 BOS tokens. " 603"Are you sure this is what you want?\n" ,__FUNCTION__ ); 604 } 605if (vocab .get_add_eos ()&& output .size () >=2 && * (output .end ()- 2 )== vocab .token_eos ()) { 606LLAMA_LOG_WARN ( 607"%s: Added a EOS token to the prompt as specified by the model but the prompt " 608"also ends with a EOS token. So now the final prompt ends with 2 EOS tokens. " 609"Are you sure this is what you want?\n" ,__FUNCTION__ ); 610 } 611 } 612 613virtual void tokenize (const std::string & text , std::vector < llama_token > & output ) { 614int final_prev_index = -1 ; 615const auto word_collection = unicode_regex_split (text ,tokenizer .regex_exprs ,tokenizer .byte_encode ); 616 617symbols_final .clear (); 618auto tok_pre = vocab .get_pre_type (); 619 620for (const auto & word :word_collection ) { 621work_queue = llm_bigram_bpe::queue (); 622symbols .clear (); 623 624int index = 0 ; 625size_t offset = 0 ; 626 627//if (vocab.tokenizer_ignore_merges && vocab.token_to_id.find(word) != vocab.token_to_id.end()) { 628if (vocab .get_ignore_merges ()&& vocab .text_to_token (word )!= LLAMA_TOKEN_NULL ) { 629symbols .emplace_back (llm_symbol {-1 ,-1 ,word .c_str (),word .size ()}); 630offset = word .size (); 631 }else if (tok_pre == LLAMA_VOCAB_PRE_TYPE_GEMMA4 && word .find_first_not_of ('\n' )== std::string::npos ) { 632// fix for gemma 4, ref: https://github.com/ggml-org/llama.cpp/pull/21343 633auto tok = vocab .text_to_token (word ); 634if (tok != LLAMA_TOKEN_NULL ) { 635symbols .emplace_back (llm_symbol {-1 ,-1 ,word .c_str (),word .size ()}); 636offset = word .size (); 637 } 638 } 639 640while (offset < word .size ()) { 641llm_symbol sym ; 642size_t char_len = std::min (word .size ()- offset , (size_t )unicode_len_utf8 (word [offset ])); 643sym .text = word .c_str ()+ offset ; 644sym .n = char_len ; 645offset += sym .n ; 646sym .prev = index - 1 ; 647sym .next = offset == word .size () ?-1 :index + 1 ; 648index ++ ; 649symbols .emplace_back (sym ); 650 } 651for (int i = 1 ;i < (int )symbols .size ();++ i ) { 652add_new_bigram (i - 1 ,i ); 653 } 654 655// build token(s) 656while (!work_queue .empty ()) { 657auto bigram = work_queue .pop_move (); 658 659auto & left_symbol = symbols [bigram .left ]; 660auto & right_symbol = symbols [bigram .right ]; 661 662if (left_symbol .n == 0 || right_symbol .n == 0 ) { 663continue ; 664 } 665 std::string left_token = std::string (left_symbol .text ,left_symbol .n ); 666 std::string right_token = std::string (right_symbol .text ,right_symbol .n ); 667if (left_token + right_token != bigram .text ) { 668continue ;// Skip this bigram if it's outdated 669 } 670 671// merge the right sym into the left one 672left_symbol .n += right_symbol .n ; 673right_symbol .n = 0 ; 674 675// remove the right sym from the chain 676left_symbol .next = right_symbol .next ; 677if (right_symbol .next >=0 ) { 678symbols [right_symbol .next ].prev = bigram .left ; 679 } 680 681add_new_bigram (left_symbol .prev ,bigram .left );// left side of current symbol 682add_new_bigram (bigram .left ,left_symbol .next );// right side of current symbol 683 } 684 685// add the finished tokens to the final list keeping correct order for next and prev 686for (auto & sym :symbols ) { 687if (sym .n > 0 ) { 688sym .prev = final_prev_index ; 689sym .next = -1 ; 690if (final_prev_index != -1 ) { 691symbols_final [final_prev_index ].next = symbols_final .size (); 692 } 693symbols_final .emplace_back (sym ); 694final_prev_index = symbols_final .size ()- 1 ; 695 } 696 } 697 } 698 699symbols = symbols_final ; 700 701if (!symbols .empty ()) { 702for (int i = 0 ;i != -1 ;i = symbols [i ].next ) { 703auto & symbol = symbols [i ]; 704if (symbol .n == 0 ) { 705continue ; 706 } 707 708const std::string str = std::string (symbol .text ,symbol .n ); 709const auto token = vocab .text_to_token (str ); 710 711if (token == LLAMA_TOKEN_NULL ) { 712for (auto j = str .begin ();j != str .end ();++ j ) { 713llama_token token_multibyte = LLAMA_TOKEN_NULL ; 714if (tokenizer .byte_encode ) { 715 std::string byte_str (1 ,* j ); 716token_multibyte = vocab .text_to_token (byte_str ); 717 }else { 718// For non-byte-encoded BPE (e.g. gemma-4), byte tokens use <0xXX> format 719static const char * hex = "0123456789ABCDEF" ; 720const uint8_t ch = (uint8_t )* j ; 721const char buf [7 ]= {'<' ,'0' ,'x' ,hex [ch >>4 ],hex [ch & 15 ],'>' ,0 }; 722token_multibyte = vocab .text_to_token (buf ); 723 } 724if (token_multibyte != LLAMA_TOKEN_NULL ) { 725output .push_back (token_multibyte ); 726 } 727 } 728 }else { 729output .push_back (token ); 730 } 731 } 732 } 733 } 734 735private : 736void add_new_bigram (int left ,int right ) { 737if (left == -1 || right == -1 ) { 738return ; 739 } 740 std::string left_token = std::string (symbols [left ].text ,symbols [left ].n ); 741 std::string right_token = std::string (symbols [right ].text ,symbols [right ].n ); 742 743int rank_found = -1 ; 744 745rank_found = vocab .find_bpe_rank (left_token ,right_token ); 746 747if (rank_found < 0 ) { 748return ; 749 } 750 751llm_bigram_bpe bigram ; 752 753bigram .left = left ; 754bigram .right = right ; 755bigram .text = left_token + right_token ; 756bigram .size = left_token .size ()+ right_token .size (); 757bigram .rank = rank_found ; 758 759work_queue .push (bigram ); 760 } 761 762const llama_vocab & vocab ; 763const llm_tokenizer_bpe & tokenizer ; 764 765 std::vector < llm_symbol > symbols ; 766 std::vector < llm_symbol > symbols_final ; 767 llm_bigram_bpe::queue work_queue ; 768}; 769 770// 771// WPM tokenizer 772// 773 774struct llm_tokenizer_wpm :llm_tokenizer { 775llm_tokenizer_wpm (const llama_vocab & /*vocab*/ ) {} 776}; 777 778struct llm_tokenizer_wpm_session { 779llm_tokenizer_wpm_session (const llama_vocab & vocab ) :vocab (vocab ) {} 780 781void tokenize (const std::string & text , std::vector < llama_token > & output ) { 782// normalize and split by whitespace 783 std::vector < std::string > words = preprocess (text ,vocab .get_normalizer_opts ()); 784// bos token prepended already 785 786// find the longest tokens that form the words 787for (const std::string & word :words ) { 788// skip empty words 789if (word .size ()== 0 ) { 790continue ; 791 } 792 793// prepend phantom space 794const std::string word1 = "\xe2\x96\x81" + word ; 795const int n = word1 .size (); 796 797const size_t current_tokens = output .size (); 798 799// we're at the start of a new word 800// move through character position in word 801for (int i = 0 ;i < n ;++ i ) { 802// loop through possible match length 803bool match = false; 804for (int j = std::min (n ,i + vocab .max_token_len ()+ 1 );j > i ;j -- ) { 805auto id = vocab .text_to_token (word1 .substr (i ,j - i )); 806if (id != LLAMA_TOKEN_NULL ) { 807output .push_back (id ); 808match = true; 809i = j - 1 ; 810break ; 811 } 812 } 813 814if (!match ) {// discard all 815output .resize (current_tokens ); 816break ;// and discard next tokens 817 } 818 } 819 820// we didn't find any matches for this word 821if (current_tokens == output .size ()) { 822output .push_back (vocab .token_unk ()); 823 } 824 } 825 } 826 827// TODO: reduce string copies by using cpts_offs array 828static std::vector < std::string > preprocess (const std::string & text ,const llama_vocab::normalizer_options & normalizer_opts ) { 829 std::vector < uint32_t > cpts = unicode_cpts_from_utf8 (text ); 830if (normalizer_opts .strip_accents ) { 831cpts = unicode_cpts_normalize_nfd (cpts ); 832 } 833 std::vector < std::string > words (1 ,"" ); 834 835for (const uint32_t cpt :cpts ) { 836const auto flags = unicode_cpt_flags_from_cpt (cpt ); 837 838if (flags .is_whitespace ) { 839if (words .back ().size ()) {// finish previous word if any 840words .emplace_back (); 841 } 842continue ; 843 } 844 845assert (!flags .is_separator ); 846if (cpt == 0 || cpt == 0xFFFD || flags .is_control ) { 847continue ; 848 } 849 850if (normalizer_opts .strip_accents && flags .is_accent_mark ) { 851continue ; 852 } 853 854const std::string s = unicode_cpt_to_utf8 (normalizer_opts .lowercase ?unicode_tolower (cpt ) :cpt ); 855if (flags .is_punctuation || (cpt < 0x7F && flags .is_symbol )|| is_chinese_char (cpt )) { 856if (words .back ().size ()) {// finish previous word if any 857words .emplace_back (); 858 } 859words .back ()= s ;// single char word 860words .emplace_back ();// start a new word 861 }else { 862words .back ()+= s ;// append char to word 863 } 864 } 865 866if (!words .back ().size ()) { 867words .pop_back (); 868 } 869 870return words ; 871 } 872 873static bool is_chinese_char (uint32_t cpt ) { 874return 875 (cpt >=0x04E00 && cpt <=0x09FFF )|| 876 (cpt >=0x03400 && cpt <=0x04DBF )|| 877 (cpt >=0x20000 && cpt <=0x2A6DF )|| 878 (cpt >=0x2A700 && cpt <=0x2B73F )|| 879 (cpt >=0x2B740 && cpt <=0x2B81F )|| 880 (cpt >=0x2B920 && cpt <=0x2CEAF )|| // this should be 0x2B820 but in hf rust code it is 0x2B920 881 (cpt >=0x0F900 && cpt <=0x0FAFF )|| 882 (cpt >=0x2F800 && cpt <=0x2FA1F ); 883//(cpt >= 0x3000 && cpt <= 0x303F) || 884//(cpt >= 0xFF00 && cpt <= 0xFFEF); 885 } 886 887private : 888const llama_vocab & vocab ; 889// currently unused 890// const llm_tokenizer_wpm * wpm_tokenizer; 891}; 892 893// 894// UGM tokenizer 895// 896 897struct llm_tokenizer_ugm :llm_tokenizer { 898llm_tokenizer_ugm (const llama_vocab & vocab ,const std::vector < char > & precompiled_charsmap ) { 899if (precompiled_charsmap .size ()> 0 ) { 900size_t charsmap_offset = 0 ; 901 902// First four bytes of precompiled_charsmap contains length of binary 903// blob containing XOR-compressed compact double array (XCDA) entries 904uint32_t xcda_blob_size = * (const uint32_t * )& precompiled_charsmap [0 ]; 905charsmap_offset += sizeof (xcda_blob_size ); 906 907// Next xcda_blob_size bytes contain entries of XOR-compressed compact 908// double array (XCDA). Each entry is bit-packed into a 32-bit integer. 909xcda_array = (const uint32_t * )& precompiled_charsmap [charsmap_offset ]; 910xcda_array_size = xcda_blob_size /sizeof (uint32_t ); 911charsmap_offset += xcda_blob_size ; 912 913// Remaining bytes of precompiled charsmap contain null-terminated 914// replacement strings for prefixes matched by the XCDA. 915prefix_replacements = & precompiled_charsmap [charsmap_offset ]; 916prefix_replacements_size = precompiled_charsmap .size ()- charsmap_offset ; 917 } 918 919for (uint32_t id = 0 ;id < vocab .n_tokens ();++ id ) { 920const auto & token_data = vocab .get_token_data (id ); 921 922if (vocab .is_normal (id )) { 923min_score = std::min < float > (min_score ,token_data .score ); 924max_score = std::max < float > (max_score ,token_data .score ); 925 } 926 927if (vocab .is_normal (id )|| 928vocab .is_user_defined (id )|| 929vocab .is_unused (id )) { 930token_matcher .insert (token_data .text .data (),token_data .text .size (),id ); 931 } 932 933if (vocab .is_user_defined (id )) { 934user_defined_token_matcher .insert (token_data .text .data (),token_data .text .size ()); 935 } 936 } 937 938unknown_token_score = min_score - unknown_token_score_penalty ; 939 } 940 941// escaped space symbol - U+2581 (Lower One Eighth Block) 942const std::string escaped_space = "\xE2\x96\x81" ; 943 944const char * prefix_replacements = NULL ; 945size_t prefix_replacements_size = 0 ; 946 947const uint32_t * xcda_array = NULL ; 948size_t xcda_array_size = 0 ; 949 950struct naive_trie user_defined_token_matcher ; 951 952float min_score = FLT_MAX ; 953float max_score = - FLT_MAX ; 954 955float unknown_token_score_penalty = 10.0 ; 956float unknown_token_score ; 957 958struct naive_trie token_matcher ; 959}; 960 961struct llm_tokenizer_ugm_session { 962llm_tokenizer_ugm_session (const llama_vocab & vocab ,const llm_tokenizer_ugm & tokenizer ) :vocab (vocab ),tokenizer (tokenizer ) {} 963 964/* This implementation is based on SentencePiece optimized Viterbi algorithm for 965* unigram language models. The general idea is to: 966* - move along the input sequence in steps of one UTF code point, 967* - at each step find all possible tokenizations of the prefix by 968* traversing the tokens trie, 969* - for each tokenization store the best one so far (by higher score) 970* - use the position in sequence after given token as an index to store 971* results 972* - if there was no valid tokenization of the current UTF code point 973* then use unknown token with additional score penalty 974* After processing the whole sequence we backtrack from the end to get 975* the best tokenization. 976*/ 977void tokenize (const std::string & text , std::vector < llama_token > & output ) { 978// get current size of output (for reversal later) 979size_t output_size = output .size (); 980 981// normalize the input first 982 std::string normalized ; 983normalize (text ,& normalized ); 984size_t input_len = normalized .size (); 985if (input_len == 0 ) { 986return ; 987 } 988 989// initialize score_sum to -FLT_MAX so it will be always lower than sums of token scores 990 std::vector < struct best_tokenization > tokenization_results (input_len + 1 , {vocab .token_unk (),0 ,- DBL_MAX }); 991// at the beginning tokenization score is zero 992tokenization_results [0 ]= {vocab .token_unk (),0 ,0 }; 993 994for (size_t input_offset = 0 ;input_offset < input_len ;) { 995size_t prefix_offset = input_offset ; 996// calculate how many code units are in the currently processed UTF code point 997size_t n_utf8_code_units = std::min < size_t > (unicode_len_utf8 (normalized [input_offset ]),input_len - input_offset ); 998 999// traverse the token matcher trie to find a matching token 1000bool single_codepoint_token_found = false; 1001const struct best_tokenization & current_best = tokenization_results [input_offset ]; 1002const struct naive_trie * node = tokenizer .token_matcher .traverse (normalized [prefix_offset ++ ]); 1003 1004while (prefix_offset <=input_len && node != NULL ) { 1005// check if we found valid token in prefix 1006if (node -> has_value ) { 1007// check if it corresponds to the whole UTF code point 1008if (prefix_offset - input_offset == n_utf8_code_units ) { 1009single_codepoint_token_found = true; 1010 } 1011llama_token token_id = node -> value ; 1012const auto & token_data = vocab .get_token_data (token_id ); 1013 1014// we set the user-defined token scores to 0 to make them more likely to be selected 1015// (normal token scores are log probabilities, so they are negative) 1016// score type is double here to make tokenization results exactly 1017// the same as in the HF tokenizer using SentencePiece 1018const double token_score = vocab .is_user_defined (token_id ) ?0.0 :token_data .score ; 1019const double challenger_score = current_best .score_sum + token_score ; 1020struct best_tokenization & current_champ = tokenization_results [prefix_offset ]; 1021if (challenger_score > current_champ .score_sum ) { 1022struct best_tokenization challenger = {token_id ,input_offset ,challenger_score }; 1023current_champ = challenger ; 1024 } 1025 } 1026node = node -> traverse (normalized [prefix_offset ++ ]); 1027 } 1028 1029// if we didn't find a valid token corresponding to the whole UTF code point 1030// then use unknown token as the tokenization of this UTF code point 1031if (!single_codepoint_token_found ) { 1032const double challenger_score = current_best .score_sum + tokenizer .unknown_token_score ; 1033prefix_offset = input_offset + n_utf8_code_units ; 1034struct best_tokenization & current_champ = tokenization_results [prefix_offset ]; 1035if (challenger_score > current_champ .score_sum ) { 1036struct best_tokenization challenger = {vocab .token_unk (),input_offset ,challenger_score }; 1037current_champ = challenger ; 1038 } 1039 } 1040 1041// move to the next UTF code point 1042input_offset += n_utf8_code_units ; 1043 } 1044 1045// now backtrack from the end to gather token ids of the best tokenization 1046// merge sequences of consecutive unknown tokens into single unknown tokens 1047bool is_prev_unknown = false; 1048for (struct best_tokenization & tokenization = tokenization_results [input_len ]; ;tokenization = tokenization_results [tokenization .input_offset ]) { 1049bool is_unknown = tokenization .token_id == vocab .token_unk (); 1050if (!(is_prev_unknown && is_unknown )) { 1051output .push_back (tokenization .token_id ); 1052 } 1053if (tokenization .input_offset == 0 ) { 1054break ; 1055 } 1056is_prev_unknown = is_unknown ; 1057 } 1058 1059// reverse the output since we added tokens starting from the end of the input 1060 std::reverse (output .begin ()+ output_size ,output .end ()); 1061 } 1062 1063private : 1064 1065// helper structure for returning normalization results 1066struct normalization_result { 1067const char * normalized ; 1068size_t normalized_len ; 1069size_t consumed_input ; 1070 }; 1071 1072void normalize (const std::string & input , std::string * normalized ) { 1073normalized -> clear (); 1074normalized -> reserve (input .size ()* 3 ); 1075 1076const std::string space = vocab .get_escape_whitespaces () ?tokenizer .escaped_space :" " ; 1077 1078const bool shall_prepend_space = !vocab .get_treat_whitespace_as_suffix ()&& vocab .get_add_space_prefix (); 1079const bool shall_append_space = vocab .get_treat_whitespace_as_suffix ()&& vocab .get_add_space_prefix (); 1080const bool shall_merge_spaces = vocab .get_remove_extra_whitespaces (); 1081 1082bool is_space_prepended = false; 1083bool processing_non_ws = false; 1084 1085size_t input_len = input .size (); 1086 1087for (size_t input_offset = 0 ;input_offset < input_len ; ) { 1088auto norm_res = normalize_prefix (input ,input_offset ); 1089for (size_t i = 0 ;i < norm_res .normalized_len ;i ++ ) { 1090char c = norm_res .normalized [i ]; 1091if (c != ' ' ) { 1092if (!processing_non_ws ) { 1093processing_non_ws = true; 1094if ((shall_prepend_space && !is_space_prepended )|| shall_merge_spaces ) { 1095normalized -> append (space ); 1096is_space_prepended = true; 1097 } 1098 } 1099normalized -> push_back (c ); 1100 }else { 1101if (processing_non_ws ) { 1102processing_non_ws = false; 1103 } 1104if (!shall_merge_spaces ) { 1105normalized -> append (space ); 1106 } 1107 } 1108 } 1109 1110input_offset += norm_res .consumed_input ; 1111 } 1112 1113if (shall_append_space ) { 1114normalized -> append (space ); 1115 } 1116 } 1117 1118/* 1119* This structure is a view wrapper for XOR-compressed double array (XCDA) 1120* See Shunsuke Kanda (2018). Space- and Time-Efficient String Dictionaries. 1121* Each bit-packed entry contains: 1122* - BASE array value in bits 10-30 1123* - LCHECK array value in bits 0-7 1124* - LEAF array value in bit 9 1125* Entries containing indexes of replacement sequences have set bit 31 1126*/ 1127struct xcda_array_view { 1128public : 1129xcda_array_view (const uint32_t * xcda_array ,size_t xcda_array_size ) :xcda_array (xcda_array ),xcda_array_size (xcda_array_size ) { 1130 } 1131uint32_t get_base (size_t index ) { 1132uint32_t packed_node = get_node (index ); 1133return (packed_node >>10 ) << ((packed_node & (1U <<9 )) >>6 ); 1134 } 1135uint32_t get_lcheck (size_t index ) { 1136uint32_t packed_node = get_node (index ); 1137return packed_node & ((1U <<31 ) |0xff ); 1138 } 1139bool get_leaf (size_t index ) { 1140uint32_t packed_node = get_node (index ); 1141return (packed_node >>8 )& 1 ; 1142 } 1143uint32_t get_value (size_t index ) { 1144uint32_t packed_node = get_node (index ); 1145return packed_node & ((1U <<31 )- 1 ); 1146 } 1147private : 1148uint32_t get_node (size_t index ) { 1149if (index >=xcda_array_size ) { 1150throw std::runtime_error ("Index out of array bounds in XCDA array!" ); 1151 } 1152return xcda_array [index ]; 1153 } 1154const uint32_t * xcda_array ; 1155size_t xcda_array_size ; 1156 }; 1157 1158// this structure stores the best tokenization so far at input_offset 1159struct best_tokenization { 1160llama_token token_id ; 1161size_t input_offset ; 1162double score_sum ; 1163 }; 1164 1165struct normalization_result normalize_prefix (const std::string & input ,size_t input_offset ) { 1166if (input_offset == input .size ()) { 1167return {& input [input_offset ],0 ,0 }; 1168 } 1169 1170// if input prefix matches some user-defined token return this token as normalization result 1171auto user_defined_token_match = 1172tokenizer .user_defined_token_matcher .get_longest_prefix (& input [input_offset ],input .size ()- input_offset ); 1173if (user_defined_token_match .second > 0 ) { 1174return {& input [input_offset ],user_defined_token_match .second ,user_defined_token_match .second }; 1175 } 1176 1177size_t longest_prefix_length = 0 ; 1178size_t longest_prefix_offset = 0 ; 1179 1180if (tokenizer .xcda_array_size > 0 ) { 1181struct xcda_array_view xcda_view (tokenizer .xcda_array ,tokenizer .xcda_array_size ); 1182 1183// Find the longest normalized sequence matching the input prefix by walking 1184// the XOR-compressed compact double array (XCDA) starting from the root node 1185// We find the index of the next node by calculating BASE[s] ^ c where s is 1186// the index of the previous node and c is a numerical character value 1187uint32_t node_index = 0 ; 1188// get BASE of the root node 1189node_index = xcda_view .get_base (node_index ); 1190for (size_t prefix_offset = input_offset ;prefix_offset < input .size ();prefix_offset ++ ) { 1191unsigned char c = input [prefix_offset ]; 1192if (c == 0 ) { 1193break ; 1194 } 1195node_index ^=c ; 1196// if value of LCHECK is not c it means that this is not a child of 1197// the previous node, so we stop matching 1198if (xcda_view .get_lcheck (node_index )!= c ) { 1199break ; 1200 } 1201bool is_leaf = xcda_view .get_leaf (node_index ); 1202// get BASE of the current node 1203node_index ^=xcda_view .get_base (node_index ); 1204// if LEAF of the current node is true, it means that its BASE points to the node 1205// containing index of replacement sequence for currently matched input prefix 1206if (is_leaf ) 1207 { 1208longest_prefix_length = prefix_offset - input_offset + 1 ; 1209// get index of replacement sequence for currently matched input prefix 1210longest_prefix_offset = xcda_view .get_value (node_index ); 1211 } 1212 } 1213 } 1214 1215if (longest_prefix_length > 0 ) { 1216// we have a match, so return the replacement sequence 1217if (longest_prefix_offset >=tokenizer .prefix_replacements_size ) { 1218throw std::runtime_error ("Index out of array bounds in precompiled charsmap!" ); 1219 } 1220const char * prefix_replacement = & (tokenizer .prefix_replacements )[longest_prefix_offset ]; 1221size_t max_len = tokenizer .prefix_replacements_size - longest_prefix_offset ; 1222size_t repl_len = 0 ; 1223while (repl_len < max_len && prefix_replacement [repl_len ]!= '\0' ) { 1224repl_len ++ ; 1225 } 1226if (repl_len == max_len ) { 1227throw std::runtime_error ("Unterminated string in precompiled charsmap!" ); 1228 } 1229return {prefix_replacement ,repl_len ,longest_prefix_length }; 1230 } 1231 1232// check if the input prefix contains a valid sequence of UTF-8 code units 1233try { 1234// if yes, return this sequence unmodified 1235size_t prefix_offset = input_offset ; 1236unicode_cpt_from_utf8 (input ,prefix_offset ); 1237return {& input [input_offset ],prefix_offset - input_offset ,prefix_offset - input_offset }; 1238 }catch (std::invalid_argument & /*ex*/ ) { 1239// if no, consume 1 byte and return U+FFFD - REPLACEMENT CHARACTER 1240return {"\xEF\xBF\xBD" ,3 ,1 }; 1241 } 1242 } 1243 1244const llama_vocab & vocab ; 1245const llm_tokenizer_ugm & tokenizer ; 1246}; 1247 1248// 1249// RWKV tokenizer 1250// 1251 1252static std::vector < uint8_t > llama_unescape_rwkv_token (const std::string & escaped ) { 1253 std::vector < uint8_t > output ; 1254output .reserve (escaped .size ()); 1255 1256// Parser state 1257bool escaping = false; 1258uint8_t hex_remaining = 0 ; 1259uint8_t hex_acc = 0 ; 1260 1261// Step through characters, performing parsing 1262for (const char & c :escaped ) { 1263// If we're parsing a hex code, interpret the next character 1264if (hex_remaining != 0 ) { 1265uint8_t value = (c >='a' ) ? (c - 'a' + 10 ) : (c - '0' ); 1266hex_acc = (hex_acc <<4 )+ value ; 1267 1268hex_remaining -= 1 ; 1269if (hex_remaining == 0 ) { 1270output .push_back (hex_acc ); 1271hex_acc = 0 ; 1272 } 1273 1274continue ; 1275 } 1276 1277// If we got an escape character, interpret it 1278if (escaping ) { 1279if (c == 't' ) { 1280output .push_back ('\t' ); 1281 }else if (c == 'n' ) { 1282output .push_back ('\n' ); 1283 }else if (c == 'r' ) { 1284output .push_back ('\r' ); 1285 }else if (c == 'x' ) { 1286hex_remaining = 2 ; 1287 }else { 1288output .push_back (c ); 1289 } 1290 1291escaping = false; 1292continue ; 1293 } 1294 1295if (c == '\\' ) { 1296escaping = true; 1297continue ; 1298 } 1299 1300output .push_back (c ); 1301 } 1302 1303return output ; 1304} 1305 1306struct llm_tokenizer_rwkv :llm_tokenizer { 1307llm_tokenizer_rwkv (const llama_vocab & vocab ) { 1308// RWKV supports arbitrary byte tokens, but the vocab struct only supports string tokens. 1309// For now, we decode the vocab here into the lookup we'll use for tokenization. 1310 1311// build trie 1312for (uint32_t id = 0 ;id < vocab .n_tokens ();++ id ) { 1313const auto & data = vocab .get_token_data (id ); 1314const auto text = llama_unescape_rwkv_token (data .text ); 1315token_matcher .insert ((const char * )text .data (),text .size (),id ); 1316 } 1317 } 1318 1319struct naive_trie token_matcher ; 1320}; 1321 1322struct llm_tokenizer_rwkv_session { 1323llm_tokenizer_rwkv_session (const llama_vocab & vocab ,const llm_tokenizer_rwkv & tokenizer ) :vocab (vocab ),tokenizer (tokenizer ) {} 1324 1325void tokenize (const std::string & text , std::vector < llama_token > & output ) { 1326uint32_t position = 0 ; 1327while (position < text .size ()) { 1328const struct naive_trie * node = tokenizer .token_matcher .traverse (text [position ]); 1329if (node == NULL ) { 1330// no matching token found, add unknown token 1331output .push_back (vocab .token_unk ()); 1332position += 1 ; 1333continue ; 1334 } 1335 1336// traverse the trie to find the longest matching token 1337uint32_t token_id = 0 ; 1338uint32_t token_length = 0 ; 1339while (node != NULL ) { 1340if (node -> has_value ) { 1341token_id = node -> value ; 1342token_length = position + 1 ; 1343 } 1344if (position + 1 >=text .size ()) { 1345break ; 1346 } 1347node = node -> traverse (text [++ position ]); 1348 } 1349 1350// add the longest matching token 1351output .push_back (token_id ); 1352position = token_length ; 1353 } 1354 } 1355 1356private : 1357const llama_vocab & vocab ; 1358const llm_tokenizer_rwkv & tokenizer ; 1359}; 1360 1361struct llm_tokenizer_plamo2 :llm_tokenizer { 1362llm_tokenizer_plamo2 (const llama_vocab & vocab ) { 1363build (vocab ); 1364 } 1365 1366void build (const llama_vocab & vocab ) { 1367// Reset internal structures 1368tokens_ .clear (); 1369bytes_ .assign (256 ,0 ); 1370to_suffix_id_ .clear (); 1371table_ .clear (); 1372 1373// Build token list and byte mapping 1374 std::unordered_map < std::string ,float > suffix_to_score ; 1375 std::unordered_map < std::string ,llama_token > token_to_id ; 1376 1377for (size_t token_id = 0 ;token_id < vocab .n_tokens ();++ token_id ) { 1378const auto & entry = vocab .get_token_data (token_id ); 1379tokens_ .push_back (entry .text ); 1380token_to_id [entry .text ]= static_cast < llama_token > (token_id ); 1381 1382// Handle byte tokens 1383if (vocab .is_byte (token_id )) { 1384if (entry .text .length ()== 6 && entry .text .substr (0 ,3 )== "<0x" && entry .text .back ()== '>' ) { 1385 std::string hex_str = entry .text .substr (3 ,2 ); 1386if (std::isxdigit (static_cast < unsigned char > (hex_str [0 ]))&& std::isxdigit (static_cast < unsigned char > (hex_str [1 ]))) { 1387int byte_val = std::stoi (hex_str ,nullptr ,16 ); 1388bytes_ [byte_val ]= static_cast < llama_token > (token_id ); 1389 } 1390 } 1391continue ; 1392 } 1393 1394// Add token and all its suffixes to suffix_to_score 1395suffix_to_score [entry .text ]= entry .score ; 1396 1397// Extract suffixes character by character (UTF-8 aware) 1398 std::vector < uint32_t > cpts = unicode_cpts_from_utf8 (entry .text ); 1399for (size_t i = 1 ;i < cpts .size ();++ i ) { 1400 std::string suffix ; 1401for (size_t j = i ;j < cpts .size ();++ j ) { 1402suffix += unicode_cpt_to_utf8 (cpts [j ]); 1403 } 1404if (suffix_to_score .find (suffix )== suffix_to_score .end ()) { 1405suffix_to_score [suffix ]= std::numeric_limits < float > ::quiet_NaN (); 1406 } 1407 } 1408 } 1409 1410// Check that all byte tokens are set 1411for (int i = 0 ;i < 256 ;++ i ) { 1412if (bytes_ [i ]== 0 ) { 1413throw std::runtime_error ("Byte token for <0x" + std::to_string (i )+ "> is not set" ); 1414 } 1415 } 1416 1417// Build suffix list in lexicographical order of reversed strings 1418 std::vector < std::string > suffixes ; 1419suffixes .reserve (suffix_to_score .size ()+ 1 ); 1420for (const auto & pair :suffix_to_score ) { 1421suffixes .push_back (pair .first ); 1422 } 1423suffixes .push_back ("" );// Empty suffix 1424 1425 std::sort (suffixes .begin (),suffixes .end (), [](const std::string & a ,const std::string & b ) { 1426 std::string rev_a (a .rbegin (),a .rend ()); 1427 std::string rev_b (b .rbegin (),b .rend ()); 1428return rev_a < rev_b ; 1429 }); 1430 1431// Build suffix_to_id and to_suffix_id_ 1432 std::unordered_map < std::string ,int32_t > suffix_to_id ; 1433int32_t num_pieces = 0 ; 1434 1435for (const auto & suffix :suffixes ) { 1436suffix_to_id [suffix ]= num_pieces ; 1437if (!suffix .empty ()) { 1438 std::vector < uint32_t > cpts = unicode_cpts_from_utf8 (suffix ); 1439 1440 std::string remaining ; 1441for (size_t i = 1 ;i < cpts .size ();++ i ) { 1442remaining += unicode_cpt_to_utf8 (cpts [i ]); 1443 } 1444 1445int64_t piece_code = (static_cast < int64_t > (cpts [0 ]) <<32 ) |suffix_to_id [remaining ]; 1446to_suffix_id_ [piece_code ]= num_pieces ; 1447 1448// Count number of pieces for this suffix 1449int32_t pieces_for_suffix = 1 ;// sentinel row 1450for (int32_t piece_length = static_cast < int32_t > (cpts .size ());piece_length > 0 ;-- piece_length ) { 1451 std::string piece ; 1452for (int32_t i = 0 ;i < piece_length ;++ i ) { 1453piece += unicode_cpt_to_utf8 (cpts [i ]); 1454 } 1455if (suffix_to_score .find (piece )!= suffix_to_score .end ()) { 1456pieces_for_suffix ++ ; 1457 } 1458 } 1459num_pieces += pieces_for_suffix ; 1460 }else { 1461num_pieces ++ ;// Empty suffix contributes one piece (sentinel row) 1462 } 1463 } 1464 1465// Build flattened table 1466table_ .resize (num_pieces , std::vector < int32_t > (4 ,0 )); 1467int32_t table_idx = 0 ; 1468 1469for (const auto & suffix :suffixes ) { 1470// Add all prefixes of the suffix to the table (in decreasing order of length) 1471 std::vector < uint32_t > cpts = unicode_cpts_from_utf8 (suffix ); 1472for (int32_t piece_length = static_cast < int32_t > (cpts .size ());piece_length > 0 ;-- piece_length ) { 1473 std::string piece ; 1474for (int32_t i = 0 ;i < piece_length ;++ i ) { 1475piece += unicode_cpt_to_utf8 (cpts [i ]); 1476 } 1477 1478auto score_it = suffix_to_score .find (piece ); 1479if (score_it == suffix_to_score .end ()) { 1480continue ; 1481 } 1482 1483table_ [table_idx ][TABLE_PIECE_LENGTH ]= piece_length ; 1484auto token_it = token_to_id .find (piece ); 1485table_ [table_idx ][TABLE_TOKEN_ID ]= (token_it != token_to_id .end ()) ?token_it -> second :-1 ; 1486 1487float score = score_it -> second ; 1488table_ [table_idx ][TABLE_SCORE ]= std::isfinite (score ) ? 1489static_cast < int32_t > (std::round (score * 1e4 )) :INVALID_SCORE ; 1490table_ [table_idx ][TABLE_PIECE_ID ]= suffix_to_id [piece ]; 1491 1492table_idx ++ ; 1493 } 1494 1495// Add sentinel row 1496table_ [table_idx ][TABLE_PIECE_LENGTH ]= 1 ; 1497table_ [table_idx ][TABLE_TOKEN_ID ]= -1 ; 1498table_ [table_idx ][TABLE_SCORE ]= UNKNOWN_SCORE ; 1499table_idx ++ ; 1500 } 1501 } 1502 1503 std::vector < llama_token > encode (const std::string & text )const { 1504 std::vector < uint32_t > unicode_data = unicode_cpts_from_utf8 (text ); 1505// Skip the first code point if it is a BOM (Byte Order Mark) 1506if (!unicode_data .empty ()&& unicode_data [0 ]== 0xFEFF ) { 1507unicode_data .erase (unicode_data .begin ()); 1508 } 1509 1510if (unicode_data .empty ()) { 1511return {}; 1512 } 1513 1514const size_t data_len = unicode_data .size (); 1515 1516// Initialize scores array (dynamic programming) 1517 std::vector < int64_t > scores (data_len + 1 ,static_cast < int64_t > (1 ) <<60 ); 1518scores [data_len ]= 0 ; 1519 1520// Path array to track best tokenization 1521 std::vector < std::vector < int32_t >> path (data_len + 1 , std::vector < int32_t > (3 ,0 )); 1522 1523int32_t suffix_id = 0 ; 1524 1525// Process from end to beginning 1526for (int i = static_cast < int > (data_len )- 1 ;i >=0 ;-- i ) { 1527uint32_t c = unicode_data [i ]; 1528 1529// Find next suffix ID 1530for (size_t p = suffix_id ;p < table_ .size ();++ p ) { 1531int64_t piece_code = (static_cast < int64_t > (c ) <<32 ) |table_ [p ][TABLE_PIECE_ID ]; 1532auto it = to_suffix_id_ .find (piece_code ); 1533suffix_id = (it != to_suffix_id_ .end ()) ?it -> second :0 ; 1534 1535if (suffix_id > 0 || table_ [p ][TABLE_SCORE ]== UNKNOWN_SCORE ) { 1536break ; 1537 } 1538 } 1539 1540// Update best path 1541for (size_t p = suffix_id ;p < table_ .size ();++ p ) { 1542int32_t score = table_ [p ][TABLE_SCORE ]; 1543if (score > INVALID_SCORE ) { 1544int32_t piece_length = table_ [p ][TABLE_PIECE_LENGTH ]; 1545int64_t s = scores [i + piece_length ]- score ; 1546 1547if (s < scores [i ]) { 1548scores [i ]= s ; 1549path [i ][PATH_TOKEN_LENGTH ]= piece_length ; 1550path [i ][PATH_TOKEN_ID ]= table_ [p ][TABLE_TOKEN_ID ]; 1551path [i ][PATH_NUM_TOKENS ]= path [i + piece_length ][PATH_NUM_TOKENS ]+ 1 ; 1552 1553if (score == UNKNOWN_SCORE ) { 1554// Add UTF-8 byte count 1555path [i ][PATH_NUM_TOKENS ]+= (c >=0x80 )+ (c >=0x800 )+ (c >=0x10000 ); 1556 } 1557 } 1558 } 1559 1560if (score == UNKNOWN_SCORE ) { 1561break ; 1562 } 1563 } 1564 } 1565 1566// Decode the best path 1567 std::vector < llama_token > token_ids ; 1568token_ids .reserve (path [0 ][PATH_NUM_TOKENS ]); 1569 1570int pos = 0 ; 1571while (pos < static_cast < int > (data_len )) { 1572if (path [pos ][PATH_TOKEN_ID ] >=0 ) { 1573token_ids .push_back (path [pos ][PATH_TOKEN_ID ]); 1574 }else { 1575// Fall back to byte tokens 1576uint32_t c = unicode_data [pos ]; 1577int s = 1 + (c >=0x80 )+ (c >=0x800 )+ (c >=0x10000 ); 1578 1579for (int i = 0 ;i < s ;++ i ) { 1580uint8_t b ; 1581if (s == 1 ) { 1582b = c ; 1583 }else { 1584if (i == 0 ) { 1585b = (0xF00 >>s )& 0xFF ; 1586 }else { 1587b = 0x80 ; 1588 } 1589 } 1590token_ids .push_back (bytes_ [b | ((c >> ((s - i - 1 )* 6 ))& 0x3F )]); 1591 } 1592 } 1593 1594assert (path [pos ][PATH_TOKEN_LENGTH ]> 0 ); 1595pos += path [pos ][PATH_TOKEN_LENGTH ]; 1596 } 1597 1598return token_ids ; 1599 } 1600private : 1601// Constants for table structure 1602static constexpr int32_t TABLE_PIECE_LENGTH = 0 ; 1603static constexpr int32_t TABLE_TOKEN_ID = 1 ; 1604static constexpr int32_t TABLE_SCORE = 2 ; 1605static constexpr int32_t TABLE_PIECE_ID = 3 ; 1606 1607// Constants for path array 1608static constexpr int32_t PATH_TOKEN_LENGTH = 0 ; 1609static constexpr int32_t PATH_TOKEN_ID = 1 ; 1610static constexpr int32_t PATH_NUM_TOKENS = 2 ; 1611 1612// Score constants 1613static constexpr int32_t INVALID_SCORE = -20000000 ; 1614static constexpr int32_t UNKNOWN_SCORE = -10000000 ; 1615 1616// List of tokens in the vocabulary 1617 std::vector < std::string > tokens_ ; 1618 1619// Mapping from byte code point to token ID (for byte fallback) 1620 std::vector < llama_token > bytes_ ; 1621 1622// Mapping from piece code to suffix ID 1623 std::unordered_map < int64_t ,int32_t > to_suffix_id_ ; 1624 1625// Flattened table representing the Trie structure 1626// Each row contains: [piece_length, token_id, score, piece_id] 1627 std::vector < std::vector < int32_t >> table_ ; 1628}; 1629 1630struct llm_tokenizer_plamo2_session { 1631llm_tokenizer_plamo2_session (const llm_tokenizer_plamo2 & tokenizer ) :tokenizer (tokenizer ) {} 1632 1633void tokenize (const std::string & text , std::vector < llama_token > & output ) { 1634 std::vector < llama_token > tokens = tokenizer .encode (text ); 1635output .insert (output .end (),tokens .begin (),tokens .end ()); 1636 } 1637 1638private : 1639const llm_tokenizer_plamo2 & tokenizer ; 1640}; 1641 1642// reserved suffix (U+E000) that keeps DNA k-mers distinct from identical 1643// base-vocab BPE tokens (e.g. CCCCCC) in token_to_id; erased from id_to_token 1644// text at load 1645static const std::string dna_kmer_marker = "\xee\x80\x80" ; 1646 1647struct llm_tokenizer_hybriddna_session :llm_tokenizer_bpe_session { 1648llm_tokenizer_hybriddna_session (const llama_vocab & vocab ,const llm_tokenizer_bpe & tokenizer ) :llm_tokenizer_bpe_session {vocab ,tokenizer },vocab {vocab } {} 1649 1650void tokenize (const std::string & text , std::vector < llama_token > & output )override { 1651static const std::string open_tag = "<dna>" ; 1652static const std::string close_tag = "</dna>" ; 1653 1654const auto dna_begin_id = vocab .text_to_token (open_tag ); 1655const auto dna_end_id = vocab .text_to_token (close_tag ); 1656const auto dna_oov_id = vocab .text_to_token ("<oov>" ); 1657 1658// Fall back to plain BPE if the DNA pieces aren't in the vocab. 1659if (dna_begin_id == LLAMA_TOKEN_NULL || dna_end_id == LLAMA_TOKEN_NULL || dna_oov_id == LLAMA_TOKEN_NULL ) { 1660 llm_tokenizer_bpe_session::tokenize (text ,output ); 1661return ; 1662 } 1663 1664const size_t k = 6 ; 1665size_t pos = 0 ; 1666 1667while (pos < text .size ()) { 1668const size_t start = text .find (open_tag ,pos ); 1669if (start == std::string::npos ) { 1670if (pos < text .size ()) { 1671 llm_tokenizer_bpe_session::tokenize (text .substr (pos ),output ); 1672 } 1673break ; 1674 } 1675if (start > pos ) { 1676 llm_tokenizer_bpe_session::tokenize (text .substr (pos ,start - pos ),output ); 1677 } 1678output .push_back (dna_begin_id ); 1679 1680const size_t content_start = start + open_tag .size (); 1681const size_t end = text .find (close_tag ,content_start ); 1682const size_t content_end = (end == std::string::npos ) ?text .size () :end ; 1683 1684emit_dna_kmers (text .substr (content_start ,content_end - content_start ),k ,dna_oov_id ,output ); 1685 1686if (end == std::string::npos ) { 1687break ; 1688 } 1689output .push_back (dna_end_id ); 1690pos = end + close_tag .size (); 1691 } 1692 } 1693 1694private : 1695void emit_dna_kmers (const std::string & raw ,size_t k ,llama_token oov_id , std::vector < llama_token > & output ) { 1696 std::string seq = raw ; 1697for (char & c :seq ) { 1698if (c >='a' && c <='z' ) { 1699c = char (c - 32 ); 1700 } 1701 } 1702 1703// k-mers carry the reserved marker suffix; a non-ACGT k-mer simply 1704// isn't in the vocab and falls back to <oov> 1705auto kmer_token = [& ](const std::string & kmer ) { 1706const auto tok = vocab .text_to_token (kmer + dna_kmer_marker ); 1707return tok != LLAMA_TOKEN_NULL ?tok :oov_id ; 1708 }; 1709 1710size_t i = 0 ; 1711for (;i + k <=seq .size ();i += k ) { 1712output .push_back (kmer_token (seq .substr (i ,k ))); 1713 } 1714if (i < seq .size ()) { 1715 std::string kmer = seq .substr (i ); 1716kmer .append (k - kmer .size (),'A' ); 1717output .push_back (kmer_token (kmer )); 1718 } 1719 } 1720 1721const llama_vocab & vocab ; 1722}; 1723 1724struct llm_tokenizer_whitespace_session :llm_tokenizer_bpe_session { 1725llm_tokenizer_whitespace_session (const llama_vocab & vocab ,const llm_tokenizer_bpe & tokenizer ) :llm_tokenizer_bpe_session {vocab ,tokenizer },vocab {vocab } {} 1726 1727void tokenize (const std::string & text , std::vector < llama_token > & output )override { 1728const bool lowercase = vocab .get_normalizer_opts ().lowercase ; 1729 1730 std::string segment ; 1731auto flush = [& ]() { 1732if (!segment .empty ()) { 1733 llm_tokenizer_bpe_session::tokenize (segment ,output ); 1734segment .clear (); 1735 } 1736 }; 1737 1738for (uint32_t cpt :unicode_cpts_from_utf8 (text )) { 1739// drop whitespace 1740if (unicode_cpt_flags_from_cpt (cpt ).is_whitespace ) { 1741flush (); 1742 }else { 1743segment += unicode_cpt_to_utf8 (lowercase ?unicode_tolower (cpt ) :cpt ); 1744 } 1745 } 1746flush (); 1747 } 1748 1749private : 1750const llama_vocab & vocab ; 1751}; 1752 1753// 1754// impl 1755// 1756 1757typedef enum FRAGMENT_BUFFER_VARIANT_TYPE { 1758FRAGMENT_BUFFER_VARIANT_TYPE_TOKEN , 1759FRAGMENT_BUFFER_VARIANT_TYPE_RAW_TEXT 1760}FRAGMENT_BUFFER_VARIANT_TYPE ; 1761 1762struct fragment_buffer_variant { 1763fragment_buffer_variant (llama_token _token ) 1764 : 1765type (FRAGMENT_BUFFER_VARIANT_TYPE_TOKEN ), 1766token (_token ), 1767raw_text (_dummy ), 1768offset (0 ), 1769length (0 ) {} 1770 1771fragment_buffer_variant (const std::string & _raw_text ,int64_t _offset ,int64_t _length ) 1772 : 1773type (FRAGMENT_BUFFER_VARIANT_TYPE_RAW_TEXT ), 1774token ((llama_token )- 1 ), 1775raw_text (_raw_text ), 1776offset (_offset ), 1777length (_length ){ 1778GGML_ASSERT (_offset >=0 ); 1779GGML_ASSERT (_length >=1 ); 1780GGML_ASSERT (offset + length <=raw_text .length ()); 1781 } 1782 1783const FRAGMENT_BUFFER_VARIANT_TYPE type ; 1784const llama_token token ; 1785const std::string _dummy ; 1786const std::string & raw_text ; 1787const uint64_t offset ; 1788const uint64_t length ; 1789}; 1790 1791struct llama_vocab::impl { 1792uint32_t n_token_types = 0 ;// for BERT-style token types 1793 1794 std::string tokenizer_model ; 1795 std::string tokenizer_pre ; 1796 1797enum llama_vocab_type type = LLAMA_VOCAB_TYPE_SPM ; 1798enum llama_vocab_pre_type pre_type = LLAMA_VOCAB_PRE_TYPE_DEFAULT ; 1799 1800int max_token_len = 0 ;// used for optimizing longest token search 1801 1802// default LLaMA special tokens 1803// TODO: should we set all of these to LLAMA_TOKEN_NULL? 1804llama_token special_bos_id = 1 ; 1805llama_token special_eos_id = 2 ; 1806llama_token special_eot_id = LLAMA_TOKEN_NULL ; 1807llama_token special_eom_id = LLAMA_TOKEN_NULL ; 1808llama_token special_unk_id = 0 ; 1809llama_token special_sep_id = LLAMA_TOKEN_NULL ; 1810llama_token special_pad_id = LLAMA_TOKEN_NULL ; 1811llama_token special_mask_id = LLAMA_TOKEN_NULL ; 1812 1813llama_token linefeed_id = 13 ; 1814 1815// fim tokens 1816llama_token special_fim_pre_id = LLAMA_TOKEN_NULL ; 1817llama_token special_fim_suf_id = LLAMA_TOKEN_NULL ; 1818llama_token special_fim_mid_id = LLAMA_TOKEN_NULL ; 1819llama_token special_fim_pad_id = LLAMA_TOKEN_NULL ; 1820llama_token special_fim_rep_id = LLAMA_TOKEN_NULL ;// repo 1821llama_token special_fim_sep_id = LLAMA_TOKEN_NULL ;// file separator 1822 1823// tokenizer flags 1824bool add_space_prefix = false; 1825bool add_bos = false; 1826bool add_eos = false; 1827bool add_sep = false; 1828bool ignore_merges = false; 1829bool clean_spaces = false;// clean_up_tokenization_spaces 1830bool remove_extra_whitespaces = false; 1831bool escape_whitespaces = true; 1832bool treat_whitespace_as_suffix = false; 1833 1834// BertNormalizer options 1835 llama_vocab::normalizer_options normalizer_opts ; 1836 1837 std::unordered_map < std::string ,llama_token > token_to_id ; 1838 std::vector < token_data > id_to_token ; 1839 1840 std::vector < llama_token > cache_special_tokens ; 1841 std::vector < std::string > cache_token_to_piece ;// llama_token_to_piece(special = true); 1842struct pair_hash { 1843size_t operator()(const std::pair < std::string , std::string > & p )const { 1844return std::hash < std::string > {}(p .first ) ^//create some hash for pair 1845 (std::hash < std::string > {}(p .second ) <<1 ); 1846 } 1847 }; 1848 std::unordered_map < std::pair < std::string , std::string > ,int ,pair_hash > bpe_ranks ; 1849 1850// set of all tokens that cause "end of generation" 1851 std::set < llama_token > special_eog_ids ; 1852 1853 std::vector < llama_token > suppress_tokens ; 1854 1855 std::unique_ptr < llm_tokenizer > tokenizer ; 1856 1857 std::vector < char > precompiled_charsmap ; 1858 1859impl (const llama_vocab & vocab ) :vocab (vocab ) { 1860 } 1861 1862 ~impl ()= default ; 1863 1864void load (llama_model_loader & ml ,const LLM_KV & kv ); 1865 1866enum llama_vocab_type get_type ()const ; 1867 1868 std::string type_name ()const ; 1869 1870bool is_normal (llama_token id )const ; 1871bool is_unknown (llama_token id )const ; 1872bool is_control (llama_token id )const ; 1873bool is_byte (llama_token id )const ; 1874bool is_user_defined (llama_token id )const ; 1875bool is_unused (llama_token id )const ; 1876bool is_eog (llama_token id )const ; 1877 1878uint8_t token_to_byte (llama_token id )const ; 1879 1880llama_token_attr token_get_attr (llama_token id )const ; 1881 1882void init_tokenizer (enum llama_vocab_type type ); 1883 1884void tokenizer_st_partition (std::forward_list < fragment_buffer_variant > & buffer ,bool parse_special )const ; 1885 1886 std::string token_to_piece_for_cache ( 1887llama_token token , 1888bool special )const ; 1889 1890 1891 std::vector < llama_token > tokenize ( 1892const std::string & raw_text , 1893bool add_special , 1894bool parse_special = false)const ; 1895 1896int32_t tokenize ( 1897const char * text , 1898int32_t text_len , 1899llama_token * tokens , 1900int32_t n_tokens_max , 1901bool add_special , 1902bool parse_special )const ; 1903 1904// does not write null-terminator to buf 1905int32_t token_to_piece ( 1906llama_token token , 1907char * buf , 1908int32_t length , 1909int32_t lstrip , 1910bool special )const ; 1911 1912// use cached data 1913const std::string & token_to_piece (llama_token token )const ; 1914 1915int32_t detokenize ( 1916const llama_token * tokens , 1917int32_t n_tokens , 1918char * text , 1919int32_t text_len_max , 1920bool remove_special , 1921bool unparse_special )const ; 1922 1923 std::string detokenize ( 1924const std::vector < llama_token > & tokens , 1925bool special )const ; 1926 1927void print_info ()const ; 1928 1929private : 1930const llama_vocab & vocab ; 1931}; 1932 1933void llama_vocab::impl::load (llama_model_loader & ml ,const LLM_KV & kv ) { 1934struct gguf_context * ctx = ml .metadata ; 1935 1936// determine vocab type 1937 { 1938ml .get_key (LLM_KV_TOKENIZER_MODEL ,tokenizer_model ); 1939ml .get_key (LLM_KV_TOKENIZER_PRE ,tokenizer_pre , false); 1940 1941ml .get_key (LLM_KV_TOKENIZER_TOKEN_TYPE_COUNT ,n_token_types , false); 1942 1943if (tokenizer_model == "no_vocab" || tokenizer_model == "none" ) { 1944type = LLAMA_VOCAB_TYPE_NONE ; 1945 1946// default special tokens 1947special_bos_id = LLAMA_TOKEN_NULL ; 1948special_eos_id = LLAMA_TOKEN_NULL ; 1949special_unk_id = LLAMA_TOKEN_NULL ; 1950special_sep_id = LLAMA_TOKEN_NULL ; 1951special_pad_id = LLAMA_TOKEN_NULL ; 1952special_mask_id = LLAMA_TOKEN_NULL ; 1953linefeed_id = LLAMA_TOKEN_NULL ; 1954 1955// read vocab size from metadata 1956uint32_t n_tokens = 0 ; 1957if (ml .get_key (LLM_KV_VOCAB_SIZE ,n_tokens , false)) { 1958LLAMA_LOG_WARN ("%s: adding %u dummy tokens\n" ,__func__ ,n_tokens ); 1959id_to_token .resize (n_tokens ); 1960 } 1961 1962return ; 1963 } 1964 1965if (tokenizer_model == "llama" ) { 1966type = LLAMA_VOCAB_TYPE_SPM ; 1967 1968// default special tokens 1969special_bos_id = 1 ; 1970special_eos_id = 2 ; 1971special_unk_id = 0 ; 1972special_sep_id = LLAMA_TOKEN_NULL ; 1973special_pad_id = LLAMA_TOKEN_NULL ; 1974special_mask_id = LLAMA_TOKEN_NULL ; 1975 }else if (tokenizer_model == "bert" ) { 1976type = LLAMA_VOCAB_TYPE_WPM ; 1977 1978// default special tokens 1979special_bos_id = 101 ; 1980special_eos_id = LLAMA_TOKEN_NULL ; 1981special_unk_id = 100 ; 1982special_sep_id = 102 ; 1983special_pad_id = 0 ; 1984special_mask_id = 103 ; 1985 1986add_sep = true; 1987 }else if (tokenizer_model == "gpt2" || tokenizer_model == "hybriddna" || tokenizer_model == "whitespace" ) { 1988type = LLAMA_VOCAB_TYPE_BPE ; 1989 1990// read bpe merges and populate bpe ranks 1991const int merges_keyidx = gguf_find_key (ctx ,kv (LLM_KV_TOKENIZER_MERGES ).c_str ()); 1992// Kimi-K2 uses custom tokenization without traditional BPE merges 1993const bool is_kimi_k2 = (tokenizer_pre == "kimi-k2" ); 1994 1995if (merges_keyidx == -1 ) { 1996if (!is_kimi_k2 ) { 1997throw std::runtime_error ("cannot find tokenizer merges in model file\n" ); 1998 } 1999// Kimi-K2 doesn't need merges, skip 2000LLAMA_LOG_INFO ("%s: Kimi-K2 tokenizer detected, skipping BPE merges\n" ,__func__ ); 2001 }else { 2002if (gguf_get_kv_type (ctx ,merges_keyidx )!= GGUF_TYPE_ARRAY || 2003gguf_get_arr_type (ctx ,merges_keyidx )!= GGUF_TYPE_STRING ) { 2004throw std::runtime_error (format ("invalid gguf type for %s" ,kv (LLM_KV_TOKENIZER_MERGES ).c_str ())); 2005 } 2006const int n_merges = gguf_get_arr_n (ctx ,merges_keyidx ); 2007for (int i = 0 ;i < n_merges ;i ++ ) { 2008const std::string word = gguf_get_arr_str (ctx ,merges_keyidx ,i ); 2009//GGML_ASSERT(unicode_cpts_from_utf8(word).size() > 0); 2010 2011 std::string first ; 2012 std::string second ; 2013 2014const size_t pos = word .find (' ' ,1 ); 2015 2016if (pos != std::string::npos ) { 2017first = word .substr (0 ,pos ); 2018second = word .substr (pos + 1 ); 2019 } 2020 2021bpe_ranks .emplace (std::make_pair (first ,second ),i ); 2022 } 2023 } 2024 2025// default special tokens 2026special_bos_id = 11 ; 2027special_eos_id = 11 ; 2028special_unk_id = LLAMA_TOKEN_NULL ; 2029special_sep_id = LLAMA_TOKEN_NULL ; 2030special_pad_id = LLAMA_TOKEN_NULL ; 2031special_mask_id = LLAMA_TOKEN_NULL ; 2032 }else if (tokenizer_model == "t5" ) { 2033type = LLAMA_VOCAB_TYPE_UGM ; 2034 2035// default special tokens 2036special_bos_id = LLAMA_TOKEN_NULL ; 2037special_eos_id = 1 ; 2038special_unk_id = 2 ; 2039special_sep_id = LLAMA_TOKEN_NULL ; 2040special_pad_id = 0 ; 2041special_mask_id = LLAMA_TOKEN_NULL ; 2042 2043const int precompiled_charsmap_keyidx = gguf_find_key (ctx ,kv (LLM_KV_TOKENIZER_PRECOMPILED_CHARSMAP ).c_str ()); 2044if (precompiled_charsmap_keyidx != -1 ) { 2045if (gguf_get_kv_type (ctx ,precompiled_charsmap_keyidx )!= GGUF_TYPE_ARRAY ) { 2046throw std::runtime_error (format ("invalid gguf type for %s" ,kv (LLM_KV_TOKENIZER_PRECOMPILED_CHARSMAP ).c_str ())); 2047 } 2048const gguf_type pc_type = gguf_get_arr_type (ctx ,precompiled_charsmap_keyidx ); 2049if (pc_type != GGUF_TYPE_INT8 && pc_type != GGUF_TYPE_UINT8 ) { 2050throw std::runtime_error (format ("invalid gguf type for %s" ,kv (LLM_KV_TOKENIZER_PRECOMPILED_CHARSMAP ).c_str ())); 2051 } 2052 2053const size_t n_precompiled_charsmap = gguf_get_arr_n (ctx ,precompiled_charsmap_keyidx ); 2054const char * pc = (const char * )gguf_get_arr_data (ctx ,precompiled_charsmap_keyidx ); 2055precompiled_charsmap .assign (pc ,pc + n_precompiled_charsmap ); 2056if (precompiled_charsmap .size ()< sizeof (uint32_t )) { 2057throw std::runtime_error ("precompiled_charsmap too small for xcda_blob_size header!" ); 2058 } 2059uint32_t * xcda_blob_size = (uint32_t * )& precompiled_charsmap [0 ]; 2060#if defined(__BYTE_ORDER__ )&& defined(__ORDER_BIG_ENDIAN__ )&& __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ 2061* xcda_blob_size = __builtin_bswap32 (* xcda_blob_size ); 2062#endif 2063if (* xcda_blob_size + sizeof (uint32_t ) >=precompiled_charsmap .size ()) { 2064throw std::runtime_error ("Index out of array bounds in precompiled charsmap!" ); 2065 } 2066#if defined(__BYTE_ORDER__ )&& defined(__ORDER_BIG_ENDIAN__ )&& __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ 2067// correct endianness of data in precompiled_charsmap binary blob 2068size_t xcda_array_size = * xcda_blob_size /sizeof (uint32_t ); 2069uint32_t * xcda_array = (uint32_t * )& precompiled_charsmap [sizeof (uint32_t )]; 2070for (size_t i = 0 ;i < xcda_array_size ;++ i ) { 2071xcda_array [i ]= __builtin_bswap32 (xcda_array [i ]); 2072 } 2073#endif 2074 } 2075 }else if (tokenizer_model == "rwkv" ) { 2076type = LLAMA_VOCAB_TYPE_RWKV ; 2077 2078// default special tokens 2079special_bos_id = LLAMA_TOKEN_NULL ; 2080special_eos_id = LLAMA_TOKEN_NULL ; 2081special_unk_id = LLAMA_TOKEN_NULL ; 2082special_sep_id = LLAMA_TOKEN_NULL ; 2083special_pad_id = LLAMA_TOKEN_NULL ; 2084 }else if (tokenizer_model == "plamo2" ) { 2085type = LLAMA_VOCAB_TYPE_PLAMO2 ; 2086 2087// PLaMo-2 default special tokens (these will be overridden by model config) 2088special_bos_id = 1 ;// <|plamo:bos|> 2089special_eos_id = 2 ;// <|plamo:eos|> 2090special_unk_id = 0 ;// <|plamo:unk|> 2091special_sep_id = LLAMA_TOKEN_NULL ; 2092special_pad_id = 3 ;// <|plamo:pad|> 2093special_mask_id = LLAMA_TOKEN_NULL ; 2094 }else if (tokenizer_model == "gemma4" ) { 2095type = LLAMA_VOCAB_TYPE_BPE ; 2096 2097// read bpe merges and populate bpe ranks 2098const int merges_keyidx = gguf_find_key (ctx ,kv (LLM_KV_TOKENIZER_MERGES ).c_str ()); 2099if (merges_keyidx == -1 ) { 2100throw std::runtime_error ("cannot find tokenizer merges in model file\n" ); 2101 } 2102 { 2103if (gguf_get_kv_type (ctx ,merges_keyidx )!= GGUF_TYPE_ARRAY || 2104gguf_get_arr_type (ctx ,merges_keyidx )!= GGUF_TYPE_STRING ) { 2105throw std::runtime_error (format ("invalid gguf type for %s" ,kv (LLM_KV_TOKENIZER_MERGES ).c_str ())); 2106 } 2107const int n_merges = gguf_get_arr_n (ctx ,merges_keyidx ); 2108for (int i = 0 ;i < n_merges ;i ++ ) { 2109const std::string word = gguf_get_arr_str (ctx ,merges_keyidx ,i ); 2110 2111 std::string first ; 2112 std::string second ; 2113 2114const size_t pos = word .find (' ' ,1 ); 2115 2116if (pos != std::string::npos ) { 2117first = word .substr (0 ,pos ); 2118second = word .substr (pos + 1 ); 2119 } 2120 2121bpe_ranks .emplace (std::make_pair (first ,second ),i ); 2122 } 2123 } 2124 2125// default special tokens (to be read from GGUF) 2126special_bos_id = LLAMA_TOKEN_NULL ; 2127special_eos_id = LLAMA_TOKEN_NULL ; 2128special_unk_id = LLAMA_TOKEN_NULL ; 2129special_sep_id = LLAMA_TOKEN_NULL ; 2130special_pad_id = LLAMA_TOKEN_NULL ; 2131special_mask_id = LLAMA_TOKEN_NULL ; 2132 2133tokenizer_pre = "gemma4" ; 2134 }else { 2135throw std::runtime_error (format ("unknown tokenizer: '%s'" ,tokenizer_model .c_str ())); 2136 } 2137 2138// for now, only BPE models have pre-tokenizers 2139if (type == LLAMA_VOCAB_TYPE_BPE ) { 2140add_space_prefix = false; 2141escape_whitespaces = false; 2142clean_spaces = true; 2143if (tokenizer_pre .empty ()) { 2144LLAMA_LOG_WARN ("%s: missing pre-tokenizer type, using: 'default'\n" ,__func__ ); 2145LLAMA_LOG_WARN ("%s: \n" ,__func__ ); 2146LLAMA_LOG_WARN ("%s: ************************************ \n" ,__func__ ); 2147LLAMA_LOG_WARN ("%s: GENERATION QUALITY WILL BE DEGRADED! \n" ,__func__ ); 2148LLAMA_LOG_WARN ("%s: CONSIDER REGENERATING THE MODEL \n" ,__func__ ); 2149LLAMA_LOG_WARN ("%s: ************************************ \n" ,__func__ ); 2150LLAMA_LOG_WARN ("%s: \n" ,__func__ ); 2151pre_type = LLAMA_VOCAB_PRE_TYPE_DEFAULT ; 2152 }else if (tokenizer_pre == "default" ) { 2153pre_type = LLAMA_VOCAB_PRE_TYPE_DEFAULT ; 2154 }else if (tokenizer_pre == "minicpm5" ) { 2155pre_type = LLAMA_VOCAB_PRE_TYPE_MINICPM5 ; 2156ignore_merges = true; 2157 }else if ( 2158tokenizer_pre == "llama3" || 2159tokenizer_pre == "llama-v3" || 2160tokenizer_pre == "llama-bpe" || 2161tokenizer_pre == "falcon3" || 2162tokenizer_pre == "falcon-h1" || 2163tokenizer_pre == "pixtral" || 2164tokenizer_pre == "midm-2.0" || 2165tokenizer_pre == "lfm2" || 2166tokenizer_pre == "jina-v5-nano" ) { 2167pre_type = LLAMA_VOCAB_PRE_TYPE_LLAMA3 ; 2168ignore_merges = true; 2169add_bos = true; 2170 }else if ( 2171tokenizer_pre == "deepseek-llm" ) { 2172pre_type = LLAMA_VOCAB_PRE_TYPE_DEEPSEEK_LLM ; 2173clean_spaces = false; 2174 }else if ( 2175tokenizer_pre == "deepseek-coder" ) { 2176pre_type = LLAMA_VOCAB_PRE_TYPE_DEEPSEEK_CODER ; 2177clean_spaces = false; 2178 }else if ( 2179tokenizer_pre == "deepseek-v3" ) { 2180pre_type = LLAMA_VOCAB_PRE_TYPE_DEEPSEEK3_LLM ; 2181clean_spaces = false; 2182 }else if ( 2183tokenizer_pre == "spark2_5" ) { 2184pre_type = LLAMA_VOCAB_PRE_TYPE_SPARK2_5 ; 2185clean_spaces = false; 2186 }else if ( 2187tokenizer_pre == "youtu" ) { 2188pre_type = LLAMA_VOCAB_PRE_TYPE_YOUTU ; 2189clean_spaces = false; 2190ignore_merges = true; 2191 }else if ( 2192tokenizer_pre == "falcon" ) { 2193pre_type = LLAMA_VOCAB_PRE_TYPE_FALCON ; 2194 }else if ( 2195tokenizer_pre == "mpt" ) { 2196pre_type = LLAMA_VOCAB_PRE_TYPE_MPT ; 2197 }else if ( 2198tokenizer_pre == "starcoder" ) { 2199pre_type = LLAMA_VOCAB_PRE_TYPE_STARCODER ; 2200 }else if ( 2201tokenizer_pre == "gpt-2" || 2202tokenizer_pre == "phi-2" || 2203tokenizer_pre == "jina-es" || 2204tokenizer_pre == "jina-de" || 2205tokenizer_pre == "gigachat" || 2206tokenizer_pre == "jina-v2-es" || 2207tokenizer_pre == "jina-v2-de" || 2208tokenizer_pre == "a.x-4.0" || 2209tokenizer_pre == "mellum" || 2210tokenizer_pre == "modern-bert" ) { 2211pre_type = LLAMA_VOCAB_PRE_TYPE_GPT2 ; 2212 }else if ( 2213tokenizer_pre == "jais-2" ) { 2214pre_type = LLAMA_VOCAB_PRE_TYPE_JAIS2 ; 2215 }else if ( 2216tokenizer_pre == "gemma4" || 2217tokenizer_pre == "granite-embed-multi-311m" ) { 2218pre_type = LLAMA_VOCAB_PRE_TYPE_GEMMA4 ; 2219escape_whitespaces = true; 2220if (ml .get_arch ()== LLM_ARCH_ALICEAI_T5_MOE ) { 2221clean_spaces = false; 2222 } 2223 }else if ( 2224tokenizer_pre == "sarvam-moe" ) { 2225pre_type = LLAMA_VOCAB_PRE_TYPE_SARVAM_MOE ; 2226escape_whitespaces = true; 2227clean_spaces = false; 2228 }else if ( 2229tokenizer_pre == "jina-v1-en" || 2230tokenizer_pre == "jina-v2-code" || 2231tokenizer_pre == "roberta-bpe" ) { 2232pre_type = LLAMA_VOCAB_PRE_TYPE_GPT2 ; 2233add_sep = true; 2234 }else if ( 2235tokenizer_pre == "whitespace" ) { 2236pre_type = LLAMA_VOCAB_PRE_TYPE_WHITESPACE ; 2237normalizer_opts .lowercase = false; 2238 }else if ( 2239tokenizer_pre == "refact" ) { 2240pre_type = LLAMA_VOCAB_PRE_TYPE_REFACT ; 2241 }else if ( 2242tokenizer_pre == "command-r" ) { 2243pre_type = LLAMA_VOCAB_PRE_TYPE_COMMAND_R ; 2244clean_spaces = false; 2245 }else if ( 2246tokenizer_pre == "qwen2" || 2247tokenizer_pre == "deepseek-r1-qwen" || 2248tokenizer_pre == "kormo" || 2249tokenizer_pre == "f2llmv2" ) { 2250pre_type = LLAMA_VOCAB_PRE_TYPE_QWEN2 ; 2251clean_spaces = false; 2252 }else if ( 2253tokenizer_pre == "qwen35" ) { 2254pre_type = LLAMA_VOCAB_PRE_TYPE_QWEN35 ; 2255clean_spaces = false; 2256 }else if ( 2257tokenizer_pre == "stablelm2" ) { 2258pre_type = LLAMA_VOCAB_PRE_TYPE_STABLELM2 ; 2259 }else if ( 2260tokenizer_pre == "olmo" ) { 2261pre_type = LLAMA_VOCAB_PRE_TYPE_OLMO ; 2262 }else if ( 2263tokenizer_pre == "dbrx" ) { 2264pre_type = LLAMA_VOCAB_PRE_TYPE_DBRX ; 2265 }else if ( 2266tokenizer_pre == "smaug-bpe" ) { 2267pre_type = LLAMA_VOCAB_PRE_TYPE_SMAUG ; 2268 }else if ( 2269tokenizer_pre == "poro-chat" ) { 2270pre_type = LLAMA_VOCAB_PRE_TYPE_PORO ; 2271clean_spaces = false; 2272 }else if ( 2273tokenizer_pre == "glm4" || 2274tokenizer_pre == "chatglm-bpe" ) { 2275pre_type = LLAMA_VOCAB_PRE_TYPE_CHATGLM4 ; 2276special_bos_id = LLAMA_TOKEN_NULL ; 2277 }else if ( 2278tokenizer_pre == "viking" ) { 2279pre_type = LLAMA_VOCAB_PRE_TYPE_VIKING ; 2280clean_spaces = false; 2281 }else if ( 2282tokenizer_pre == "jais" ) { 2283pre_type = LLAMA_VOCAB_PRE_TYPE_JAIS ; 2284 }else if ( 2285tokenizer_pre == "tekken" ) { 2286pre_type = LLAMA_VOCAB_PRE_TYPE_TEKKEN ; 2287clean_spaces = false; 2288ignore_merges = true; 2289add_bos = true; 2290 }else if ( 2291tokenizer_pre == "smollm" ) { 2292pre_type = LLAMA_VOCAB_PRE_TYPE_SMOLLM ; 2293clean_spaces = false; 2294 }else if ( 2295tokenizer_pre == "codeshell" ) { 2296pre_type = LLAMA_VOCAB_PRE_TYPE_CODESHELL ; 2297 }else if ( 2298tokenizer_pre == "bloom" ) { 2299pre_type = LLAMA_VOCAB_PRE_TYPE_BLOOM ; 2300 }else if ( 2301tokenizer_pre == "gpt3-finnish" ) { 2302pre_type = LLAMA_VOCAB_PRE_TYPE_GPT3_FINNISH ; 2303 }else if ( 2304tokenizer_pre == "exaone" ) { 2305pre_type = LLAMA_VOCAB_PRE_TYPE_EXAONE ; 2306 }else if ( 2307tokenizer_pre == "exaone4" ) { 2308pre_type = LLAMA_VOCAB_PRE_TYPE_GPT2 ; 2309 }else if ( 2310tokenizer_pre == "exaone-moe" ) { 2311pre_type = LLAMA_VOCAB_PRE_TYPE_EXAONE_MOE ; 2312 }else if ( 2313tokenizer_pre == "chameleon" ) { 2314pre_type = LLAMA_VOCAB_PRE_TYPE_CHAMELEON ; 2315add_bos = true; 2316clean_spaces = false; 2317 }else if ( 2318tokenizer_pre == "minerva-7b" ) { 2319pre_type = LLAMA_VOCAB_PRE_TYPE_MINERVA ; 2320 }else if ( 2321tokenizer_pre == "megrez" ) { 2322pre_type = LLAMA_VOCAB_PRE_TYPE_QWEN2 ; 2323 }else if ( 2324tokenizer_pre == "gpt-4o" || 2325tokenizer_pre == "llama4" || 2326tokenizer_pre == "kanana2" || 2327tokenizer_pre == "talkie" ) { 2328pre_type = LLAMA_VOCAB_PRE_TYPE_GPT4O ; 2329clean_spaces = false; 2330 }else if ( 2331tokenizer_pre == "granite-embed-multi-97m" ) { 2332pre_type = LLAMA_VOCAB_PRE_TYPE_GRANITE_EMB_MULTI ; 2333clean_spaces = false; 2334ignore_merges = true; 2335 }else if ( 2336tokenizer_pre == "tiny_aya" || 2337tokenizer_pre == "cohere2moe" ) { 2338pre_type = LLAMA_VOCAB_PRE_TYPE_TINY_AYA ; 2339clean_spaces = false; 2340 }else if ( 2341tokenizer_pre == "superbpe" ) { 2342pre_type = LLAMA_VOCAB_PRE_TYPE_SUPERBPE ; 2343clean_spaces = false; 2344 }else if ( 2345tokenizer_pre == "trillion" ) { 2346pre_type = LLAMA_VOCAB_PRE_TYPE_TRILLION ; 2347clean_spaces = false; 2348 }else if ( 2349tokenizer_pre == "granite-docling" ) { 2350pre_type = LLAMA_VOCAB_PRE_TYPE_GRANITE_DOCLING ; 2351clean_spaces = false; 2352 }else if ( 2353tokenizer_pre == "bailingmoe" || 2354tokenizer_pre == "bailingmoe2" || 2355tokenizer_pre == "llada-moe" ) { 2356pre_type = LLAMA_VOCAB_PRE_TYPE_BAILINGMOE ; 2357clean_spaces = false; 2358 }else if ( 2359tokenizer_pre == "seed-coder" ) { 2360pre_type = LLAMA_VOCAB_PRE_TYPE_SEED_CODER ; 2361clean_spaces = false; 2362 }else if ( 2363tokenizer_pre == "hunyuan" ) { 2364pre_type = LLAMA_VOCAB_PRE_TYPE_HUNYUAN ; 2365clean_spaces = false; 2366 }else if ( 2367tokenizer_pre == "hunyuan-dense" ) { 2368pre_type = LLAMA_VOCAB_PRE_TYPE_HUNYUAN_DENSE ; 2369clean_spaces = false; 2370 }else if ( 2371tokenizer_pre == "hy_v4" ) { 2372pre_type = LLAMA_VOCAB_PRE_TYPE_HY_V4 ; 2373clean_spaces = false; 2374 }else if ( 2375tokenizer_pre == "joyai-llm" ) { 2376pre_type = LLAMA_VOCAB_PRE_TYPE_JOYAI_LLM ; 2377clean_spaces = false; 2378 }else if ( 2379tokenizer_pre == "kimi-k2" ) { 2380pre_type = LLAMA_VOCAB_PRE_TYPE_KIMI_K2 ; 2381clean_spaces = false; 2382 }else if ( 2383tokenizer_pre == "grok-2" ) { 2384pre_type = LLAMA_VOCAB_PRE_TYPE_GROK_2 ; 2385clean_spaces = false; 2386 }else if ( 2387tokenizer_pre == "afmoe" ) { 2388pre_type = LLAMA_VOCAB_PRE_TYPE_AFMOE ; 2389clean_spaces = false; 2390 }else if ( 2391tokenizer_pre == "laguna" ) { 2392pre_type = LLAMA_VOCAB_PRE_TYPE_LAGUNA ; 2393clean_spaces = false; 2394 }else if ( 2395tokenizer_pre == "minimax-m2" ) { 2396pre_type = LLAMA_VOCAB_PRE_TYPE_MINIMAX_M2 ; 2397clean_spaces = false; 2398 }else if ( 2399tokenizer_pre == "solar-open" ) { 2400pre_type = LLAMA_VOCAB_PRE_TYPE_SOLAR_OPEN ; 2401clean_spaces = false; 2402 }else if ( 2403tokenizer_pre == "mellum2" ) { 2404pre_type = LLAMA_VOCAB_PRE_TYPE_MELLUM2 ; 2405 }else { 2406throw std::runtime_error (format ("unknown pre-tokenizer type: '%s'" ,tokenizer_pre .c_str ())); 2407 } 2408 }else if (type == LLAMA_VOCAB_TYPE_SPM ) { 2409pre_type = LLAMA_VOCAB_PRE_TYPE_DEFAULT ; 2410add_space_prefix = true; 2411clean_spaces = false; 2412add_bos = true; 2413add_eos = false; 2414 }else if (type == LLAMA_VOCAB_TYPE_WPM ) { 2415pre_type = LLAMA_VOCAB_PRE_TYPE_DEFAULT ; 2416add_space_prefix = false; 2417clean_spaces = true; 2418add_bos = true; 2419add_eos = false; 2420add_sep = true; 2421 }else if (type == LLAMA_VOCAB_TYPE_UGM ) { 2422pre_type = LLAMA_VOCAB_PRE_TYPE_DEFAULT ; 2423add_bos = false; 2424add_eos = true; 2425 }else if (type == LLAMA_VOCAB_TYPE_RWKV ) { 2426pre_type = LLAMA_VOCAB_PRE_TYPE_DEFAULT ; 2427add_space_prefix = false; 2428clean_spaces = false; 2429add_bos = false; 2430add_eos = false; 2431 }else { 2432pre_type = LLAMA_VOCAB_PRE_TYPE_DEFAULT ; 2433 } 2434 2435ml .get_key (LLM_KV_TOKENIZER_ADD_PREFIX ,add_space_prefix , false); 2436ml .get_key (LLM_KV_TOKENIZER_REMOVE_EXTRA_WS ,remove_extra_whitespaces , false); 2437 } 2438 2439const int token_idx = gguf_find_key (ctx ,kv (LLM_KV_TOKENIZER_LIST ).c_str ()); 2440if (token_idx == -1 ) { 2441throw std::runtime_error ("cannot find tokenizer vocab in model file\n" ); 2442 } 2443 2444if (gguf_get_kv_type (ctx ,token_idx )!= GGUF_TYPE_ARRAY || 2445gguf_get_arr_type (ctx ,token_idx )!= GGUF_TYPE_STRING ) { 2446throw std::runtime_error (format ("invalid gguf type for %s" ,kv (LLM_KV_TOKENIZER_LIST ).c_str ())); 2447 } 2448 2449const uint32_t n_tokens = gguf_get_arr_n (ctx ,token_idx ); 2450 2451const float * scores = nullptr ; 2452const int * iscores = nullptr ; 2453const int score_idx = gguf_find_key (ctx ,kv (LLM_KV_TOKENIZER_SCORES ).c_str ()); 2454if (score_idx != -1 ) { 2455const gguf_type kv_type = gguf_get_kv_type (ctx ,score_idx ); 2456const gguf_type arr_type = kv_type == GGUF_TYPE_ARRAY ?gguf_get_arr_type (ctx ,score_idx ) :GGUF_TYPE_COUNT ; 2457if (arr_type != GGUF_TYPE_INT32 && 2458arr_type != GGUF_TYPE_FLOAT32 ) { 2459throw std::runtime_error (format ("invalid gguf type for %s" ,kv (LLM_KV_TOKENIZER_SCORES ).c_str ())); 2460 } 2461const uint32_t n_scores = gguf_get_arr_n (ctx ,score_idx ); 2462if (n_scores < n_tokens ) { 2463throw std::runtime_error ("Index out of array bounds for scores (" + std::to_string (n_scores )+ " < " + std::to_string (n_tokens )+ ")\n" ); 2464 } 2465if (arr_type == GGUF_TYPE_INT32 ) { 2466iscores = (const int * )gguf_get_arr_data (ctx ,score_idx ); 2467 }else { 2468scores = (const float * )gguf_get_arr_data (ctx ,score_idx ); 2469 } 2470 } 2471 2472const int * toktypes = nullptr ; 2473const int toktype_idx = gguf_find_key (ctx ,kv (LLM_KV_TOKENIZER_TOKEN_TYPE ).c_str ()); 2474if (toktype_idx != -1 ) { 2475if (gguf_get_kv_type (ctx ,toktype_idx )!= GGUF_TYPE_ARRAY || 2476gguf_get_arr_type (ctx ,toktype_idx )!= GGUF_TYPE_INT32 ) { 2477throw std::runtime_error (format ("invalid gguf type for %s" ,kv (LLM_KV_TOKENIZER_TOKEN_TYPE ).c_str ())); 2478 } 2479const uint32_t n_toktypes = gguf_get_arr_n (ctx ,toktype_idx ); 2480if (n_toktypes < n_tokens ) { 2481throw std::runtime_error ("Index out of array bounds for toktypes (" + std::to_string (n_toktypes )+ " < " + std::to_string (n_tokens )+ ")\n" ); 2482 } 2483toktypes = (const int * )gguf_get_arr_data (ctx ,toktype_idx ); 2484 } 2485 2486id_to_token .resize (n_tokens ); 2487 2488for (uint32_t i = 0 ;i < n_tokens ;i ++ ) { 2489 std::string word = gguf_get_arr_str (ctx ,token_idx ,i ); 2490if (word .empty ()) { 2491LLAMA_LOG_WARN ("%s: empty token at index %u\n" ,__func__ ,i ); 2492word = "[EMPTY_" + std::to_string (i )+ "]" ; 2493 } 2494 2495token_to_id [word ]= i ; 2496max_token_len = std::max (max_token_len , (int )word .size ()); 2497 2498auto & token_data = id_to_token [i ]; 2499token_data .text = std::move (word ); 2500if (scores ) { 2501token_data .score = scores [i ]; 2502 }else if (iscores ) { 2503token_data .score = static_cast < float > (iscores [i ]); 2504 }else { 2505token_data .score = 0.0f ; 2506 } 2507token_data .attr = LLAMA_TOKEN_ATTR_NORMAL ; 2508 2509if (toktypes ) {//TODO: remove, required until per token attributes are available from GGUF file 2510switch (toktypes [i ]) { 2511case LLAMA_TOKEN_TYPE_UNKNOWN :token_data .attr = LLAMA_TOKEN_ATTR_UNKNOWN ;break ; 2512case LLAMA_TOKEN_TYPE_UNUSED :token_data .attr = LLAMA_TOKEN_ATTR_UNUSED ;break ; 2513case LLAMA_TOKEN_TYPE_NORMAL :token_data .attr = LLAMA_TOKEN_ATTR_NORMAL ;break ; 2514case LLAMA_TOKEN_TYPE_CONTROL :token_data .attr = LLAMA_TOKEN_ATTR_CONTROL ;break ; 2515case LLAMA_TOKEN_TYPE_USER_DEFINED :token_data .attr = LLAMA_TOKEN_ATTR_USER_DEFINED ;break ; 2516case LLAMA_TOKEN_TYPE_BYTE :token_data .attr = LLAMA_TOKEN_ATTR_BYTE ;break ; 2517case LLAMA_TOKEN_TYPE_UNDEFINED :token_data .attr = LLAMA_TOKEN_ATTR_UNDEFINED ;break ; 2518default :token_data .attr = LLAMA_TOKEN_ATTR_UNDEFINED ;break ; 2519 } 2520 } 2521 } 2522GGML_ASSERT (id_to_token .size ()== token_to_id .size ()); 2523 2524if (ml .get_arch ()== LLM_ARCH_ALICEAI_T5_MOE ) { 2525// Published GGUFs label byte fallback tokens as normal text. 2526for (uint32_t byte = 0 ;byte < 256 ;++ byte ) { 2527id_to_token [token_to_id .at (format ("<0x%02X>" ,byte ))].attr = LLAMA_TOKEN_ATTR_BYTE ; 2528 } 2529 } 2530 2531// hybriddna: the marker suffix kept k-mer ids distinct in token_to_id; erase 2532// it from id_to_token so the k-mers detokenize to the bare DNA sequence. The 2533// k-mers are the block right after <oov>, so only scan from there. 2534if (tokenizer_model == "hybriddna" ) { 2535const auto idx = token_to_id .find ("<oov>" ); 2536if (idx != token_to_id .end ()) { 2537auto it = id_to_token .begin ()+ idx -> second + 1 ; 2538for (;it != id_to_token .end ();++ it ) { 2539 std::string & text = it -> text ; 2540if (text .size ()> dna_kmer_marker .size () 2541&& text .compare (text .size ()- dna_kmer_marker .size (),dna_kmer_marker .size (),dna_kmer_marker )== 0 ) { 2542text .erase (text .size ()- dna_kmer_marker .size ()); 2543 } 2544 } 2545 } 2546 } 2547 2548init_tokenizer (type ); 2549 2550// determine the newline token: LLaMA "<0x0A>" == 10 == '\n', Falcon 193 == '\n' 2551if (ml .get_arch ()== LLM_ARCH_ALICEAI_T5_MOE ) { 2552linefeed_id = vocab .text_to_token ("\n" ); 2553 }else if (type == LLAMA_VOCAB_TYPE_SPM ) { 2554try { 2555linefeed_id = vocab .byte_to_token ('\n' ); 2556 }catch (const std::exception & e ) { 2557LLAMA_LOG_WARN ("%s: SPM vocabulary, but newline token not found: %s! Using special_pad_id instead." ,__func__ ,e .what ()); 2558linefeed_id = special_pad_id ; 2559 } 2560 }else if (type == LLAMA_VOCAB_TYPE_WPM ) { 2561linefeed_id = special_pad_id ; 2562 }else if (type == LLAMA_VOCAB_TYPE_RWKV ) { 2563const std::vector < int > ids = tokenize ("\n" , false); 2564GGML_ASSERT (!ids .empty ()&& "model vocab missing newline token" ); 2565linefeed_id = ids [0 ]; 2566 }else { 2567const std::vector < int > ids = tokenize ("\n" , false); 2568 2569//GGML_ASSERT(!ids.empty() && "model vocab missing newline token"); 2570if (ids .empty ()) { 2571LLAMA_LOG_WARN ("%s: model vocab missing newline token, using special_pad_id instead\n" ,__func__ ); 2572linefeed_id = special_pad_id ; 2573 }else { 2574linefeed_id = ids [0 ]; 2575 } 2576 } 2577 2578// special tokens 2579 { 2580const std::vector < std::pair < enum llm_kv ,int32_t &>> special_token_types = { 2581 {LLM_KV_TOKENIZER_BOS_ID ,special_bos_id }, 2582 {LLM_KV_TOKENIZER_EOS_ID ,special_eos_id }, 2583 {LLM_KV_TOKENIZER_EOT_ID ,special_eot_id }, 2584 {LLM_KV_TOKENIZER_EOM_ID ,special_eom_id }, 2585 {LLM_KV_TOKENIZER_UNK_ID ,special_unk_id }, 2586 {LLM_KV_TOKENIZER_SEP_ID ,special_sep_id }, 2587 {LLM_KV_TOKENIZER_PAD_ID ,special_pad_id }, 2588 {LLM_KV_TOKENIZER_MASK_ID ,special_mask_id }, 2589 {LLM_KV_TOKENIZER_FIM_PRE_ID ,special_fim_pre_id }, 2590 {LLM_KV_TOKENIZER_FIM_SUF_ID ,special_fim_suf_id }, 2591 {LLM_KV_TOKENIZER_FIM_MID_ID ,special_fim_mid_id }, 2592 {LLM_KV_TOKENIZER_FIM_PAD_ID ,special_fim_pad_id }, 2593 {LLM_KV_TOKENIZER_FIM_REP_ID ,special_fim_rep_id }, 2594 {LLM_KV_TOKENIZER_FIM_SEP_ID ,special_fim_sep_id }, 2595 2596// deprecated 2597 {LLM_KV_TOKENIZER_PREFIX_ID ,special_fim_pre_id }, 2598 {LLM_KV_TOKENIZER_SUFFIX_ID ,special_fim_suf_id }, 2599 {LLM_KV_TOKENIZER_MIDDLE_ID ,special_fim_mid_id }, 2600 }; 2601 2602for (const auto & it :special_token_types ) { 2603const std::string & key = kv (std::get < 0 > (it )); 2604int32_t & id = std::get < 1 > (it ); 2605 2606if (id >=0 && static_cast < size_t > (id ) >=id_to_token .size ()) { 2607LLAMA_LOG_WARN ("%s: default special token '%s' = %d out of vocab range, disabling\n" , 2608__func__ ,key .c_str (),id ); 2609id = LLAMA_TOKEN_NULL ; 2610 } 2611 2612uint32_t new_id ; 2613if (!ml .get_key (std::get < 0 > (it ),new_id , false)) { 2614continue ; 2615 } 2616if (new_id >=id_to_token .size ()) { 2617LLAMA_LOG_WARN ("%s: bad special token: '%s' = %u, using default id %d\n" , 2618__func__ ,key .c_str (),new_id ,id ); 2619 }else { 2620id = new_id ; 2621 } 2622 } 2623 2624// Handle add_bos, add_eos and add_sep 2625 { 2626bool temp = true; 2627 2628if (ml .get_key (LLM_KV_TOKENIZER_ADD_BOS ,temp , false)) { 2629add_bos = temp ; 2630 } 2631if (ml .get_key (LLM_KV_TOKENIZER_ADD_EOS ,temp , false)) { 2632add_eos = temp ; 2633 } 2634if (ml .get_key (LLM_KV_TOKENIZER_ADD_SEP ,temp , false)) { 2635add_sep = temp ; 2636 } 2637 2638// workaround for Gemma 4 2639// ref: https://github.com/ggml-org/llama.cpp/pull/21500 2640if (pre_type == LLAMA_VOCAB_PRE_TYPE_GEMMA4 && ml .get_arch ()!= LLM_ARCH_ALICEAI_T5_MOE && !add_bos ) { 2641add_bos = true; 2642 2643LLAMA_LOG_WARN ("%s: override '%s' to 'true' for Gemma4\n" ,__func__ ,kv (LLM_KV_TOKENIZER_ADD_BOS ).c_str ()); 2644 } 2645 } 2646 2647// BertNormalizer options 2648ml .get_key (LLM_KV_TOKENIZER_NORMALIZER_LOWERCASE ,normalizer_opts .lowercase , false); 2649normalizer_opts .strip_accents = normalizer_opts .lowercase ; 2650ml .get_key (LLM_KV_TOKENIZER_NORMALIZER_STRIP_ACCENTS ,normalizer_opts .strip_accents , false); 2651 2652// suppress tokens 2653 { 2654const int suppress_idx = gguf_find_key (ctx ,kv (LLM_KV_TOKENIZER_SUPPRESS_TOKENS ).c_str ()); 2655if (suppress_idx != -1 ) { 2656if (gguf_get_kv_type (ctx ,suppress_idx )!= GGUF_TYPE_ARRAY || 2657gguf_get_arr_type (ctx ,suppress_idx )!= GGUF_TYPE_INT32 ) { 2658throw std::runtime_error (format ("invalid gguf type for %s" ,kv (LLM_KV_TOKENIZER_SUPPRESS_TOKENS ).c_str ())); 2659 } 2660const int n = gguf_get_arr_n (ctx ,suppress_idx ); 2661const int32_t * data = (const int32_t * )gguf_get_arr_data (ctx ,suppress_idx ); 2662// drop out-of-range ids 2663suppress_tokens .reserve (n ); 2664for (int i = 0 ;i < n ;++ i ) { 2665const int32_t id = data [i ]; 2666if (id >=0 && id < (int )id_to_token .size ()) { 2667suppress_tokens .push_back (id ); 2668 } 2669 } 2670 } 2671 } 2672 2673// auto-detect special tokens by text 2674// TODO: convert scripts should provide these tokens through the KV metadata LLM_KV_TOKENIZER_... 2675// for now, we apply this workaround to find the tokens based on their text 2676 2677for (const auto & t :token_to_id ) { 2678auto & attr = id_to_token [t .second ].attr ; 2679 2680// find EOT token: "<|eot_id|>", "<|im_end|>", "<end_of_turn>", etc. 2681if (special_eot_id == LLAMA_TOKEN_NULL ) { 2682if (false 2683|| t .first == "<|eot_id|>" 2684|| t .first == "<|im_end|>" 2685|| t .first == "<|end|>" 2686|| t .first == "<end_of_turn>" 2687|| t .first == "<|endoftext|>" 2688|| t .first == "<|end_of_text|>" // granite 2689|| t .first == "<EOT>" 2690|| t .first == "_<EOT>" 2691|| t .first == "[EOT]" // Kimi-K2 2692|| t .first == "<|end▁of▁sentence|>" // DeepSeek 2693|| t .first == "<end_of_utterance>" // smoldocling 2694 ) { 2695special_eot_id = t .second ; 2696if ((attr & LLAMA_TOKEN_ATTR_CONTROL )== 0 ) { 2697LLAMA_LOG_WARN ("%s: control-looking token: %6d '%s' was not control-type; this is probably a bug in the model. its type will be overridden\n" , 2698__func__ ,t .second ,t .first .c_str ()); 2699attr = (llama_token_attr ) (attr |LLAMA_TOKEN_ATTR_CONTROL ); 2700 } 2701 } 2702 } 2703 2704// find EOM token: "<|eom_id|>" 2705if (special_eom_id == LLAMA_TOKEN_NULL ) { 2706if (false 2707|| t .first == "<|eom_id|>" 2708 ) { 2709special_eom_id = t .second ; 2710if ((attr & LLAMA_TOKEN_ATTR_CONTROL )== 0 ) { 2711LLAMA_LOG_WARN ("%s: control-looking token: %6d '%s' was not control-type; this is probably a bug in the model. its type will be overridden\n" , 2712__func__ ,t .second ,t .first .c_str ()); 2713attr = (llama_token_attr ) (attr |LLAMA_TOKEN_ATTR_CONTROL ); 2714 } 2715 } 2716 } 2717 2718// find FIM_PRE token: "<|fim_prefix|>", "<fim-prefix>", "<PRE>", etc. 2719if (special_fim_pre_id == LLAMA_TOKEN_NULL ) { 2720if (false 2721|| t .first == "<|fim_prefix|>" // Qwen 2722|| t .first == "<fim-prefix>" 2723|| t .first == "<fim_prefix>" // Granite 2724|| t .first == "<|fim▁begin|>" // DeepSeek 2725|| t .first == "<PRE>" 2726|| t .first == "▁<PRE>" // CodeLlama 2727|| t .first == "<|code_prefix|>" // GLM-4.5 2728|| t .first == "<|prefix|>" // Falcon-H1-Tiny-Coder 2729 ) { 2730special_fim_pre_id = t .second ; 2731if ((attr & LLAMA_TOKEN_ATTR_CONTROL )== 0 ) { 2732LLAMA_LOG_WARN ("%s: control-looking token: %6d '%s' was not control-type; this is probably a bug in the model. its type will be overridden\n" , 2733__func__ ,t .second ,t .first .c_str ()); 2734attr = (llama_token_attr ) (attr |LLAMA_TOKEN_ATTR_CONTROL ); 2735 } 2736 } 2737 } 2738 2739// find FIM_SUF token: "<|fim_suffix|>", "<fim-suffix>", "<SUF>", etc. 2740if (special_fim_suf_id == LLAMA_TOKEN_NULL ) { 2741if (false 2742|| t .first == "<|fim_suffix|>" // Qwen 2743|| t .first == "<fim-suffix>" 2744|| t .first == "<fim_suffix>" // Granite 2745|| t .first == "<|fim▁hole|>" // DeepSeek 2746|| t .first == "<SUF>" 2747|| t .first == "▁<SUF>" // CodeLlama 2748|| t .first == "<|code_suffix|>" // GLM-4.5 2749|| t .first == "<|suffix|>" // Falcon-H1-Tiny-Coder 2750 ) { 2751special_fim_suf_id = t .second ; 2752if ((attr & LLAMA_TOKEN_ATTR_CONTROL )== 0 ) { 2753LLAMA_LOG_WARN ("%s: control-looking token: %6d '%s' was not control-type; this is probably a bug in the model. its type will be overridden\n" , 2754__func__ ,t .second ,t .first .c_str ()); 2755attr = (llama_token_attr ) (attr |LLAMA_TOKEN_ATTR_CONTROL ); 2756 } 2757 } 2758 } 2759 2760// find FIM_MID token: "<|fim_middle|>", "<fim-middle>", "<MID>", etc. 2761if (special_fim_mid_id == LLAMA_TOKEN_NULL ) { 2762if (false 2763|| t .first == "<|fim_middle|>" // Qwen 2764|| t .first == "<fim-middle>" 2765|| t .first == "<fim_middle>" // Granite 2766|| t .first == "<|fim▁end|>" // DeepSeek 2767|| t .first == "<MID>" 2768|| t .first == "▁<MID>" // CodeLlama 2769|| t .first == "<|code_middle|>" // GLM-4.5 2770|| t .first == "<|middle|>" // Falcon-H1-Tiny-Coder 2771 ) { 2772special_fim_mid_id = t .second ; 2773if ((attr & LLAMA_TOKEN_ATTR_CONTROL )== 0 ) { 2774LLAMA_LOG_WARN ("%s: control-looking token: %6d '%s' was not control-type; this is probably a bug in the model. its type will be overridden\n" , 2775__func__ ,t .second ,t .first .c_str ()); 2776attr = (llama_token_attr ) (attr |LLAMA_TOKEN_ATTR_CONTROL ); 2777 } 2778 } 2779 } 2780 2781// find FIM_PAD token: "<|fim_pad|>", "<fim-pad>", "<PAD>", etc. 2782if (special_fim_pad_id == LLAMA_TOKEN_NULL ) { 2783if (false 2784|| t .first == "<|fim_pad|>" // Qwen 2785|| t .first == "<fim-pad>" 2786|| t .first == "<fim_pad>" // Granite 2787|| t .first == "<PAD>" 2788|| t .first == "[PAD]" // Kimi-K2 2789 ) { 2790special_fim_pad_id = t .second ; 2791if ((attr & LLAMA_TOKEN_ATTR_CONTROL )== 0 ) { 2792LLAMA_LOG_WARN ("%s: control-looking token: %6d '%s' was not control-type; this is probably a bug in the model. its type will be overridden\n" , 2793__func__ ,t .second ,t .first .c_str ()); 2794attr = (llama_token_attr ) (attr |LLAMA_TOKEN_ATTR_CONTROL ); 2795 } 2796 } 2797 } 2798 2799// find FIM_REP token: "<|fim_repo|>", "<fim-repo>", "<REP>", etc. 2800if (special_fim_rep_id == LLAMA_TOKEN_NULL ) { 2801if (false 2802|| t .first == "<|fim_repo|>" // Qwen 2803|| t .first == "<|repo_name|>" 2804|| t .first == "<fim-repo>" 2805|| t .first == "<REPO>" 2806|| t .first == "<reponame>" // Granite 2807 ) { 2808special_fim_rep_id = t .second ; 2809if ((attr & LLAMA_TOKEN_ATTR_CONTROL )== 0 ) { 2810LLAMA_LOG_WARN ("%s: control-looking token: %6d '%s' was not control-type; this is probably a bug in the model. its type will be overridden\n" , 2811__func__ ,t .second ,t .first .c_str ()); 2812attr = (llama_token_attr ) (attr |LLAMA_TOKEN_ATTR_CONTROL ); 2813 } 2814 } 2815 } 2816 2817// find FIM_SEP token: "<|file_sep|>" 2818if (special_fim_sep_id == LLAMA_TOKEN_NULL ) { 2819if (false 2820|| t .first == "<|file_sep|>" // Qwen 2821 ) { 2822special_fim_sep_id = t .second ; 2823if ((attr & LLAMA_TOKEN_ATTR_CONTROL )== 0 ) { 2824LLAMA_LOG_WARN ("%s: control-looking token: %6d '%s' was not control-type; this is probably a bug in the model. its type will be overridden\n" , 2825__func__ ,t .second ,t .first .c_str ()); 2826attr = (llama_token_attr ) (attr |LLAMA_TOKEN_ATTR_CONTROL ); 2827 } 2828 } 2829 } 2830 } 2831 2832// auto-detect unused tokens: e.g. control tokens with the word "unused" 2833// ideally, these tokens should be marked as unused during conversion 2834 { 2835uint32_t n_unused = 0 ; 2836 2837for (const auto & t :token_to_id ) { 2838auto & attr = id_to_token [t .second ].attr ; 2839 2840if ((attr & LLAMA_TOKEN_ATTR_CONTROL )== 0 ) { 2841continue ; 2842 } 2843 2844if ((attr & LLAMA_TOKEN_ATTR_UNUSED )== 0 ) { 2845if (strstr (t .first .c_str (),"unused" )!= NULL ) { 2846attr = (llama_token_attr ) (attr |LLAMA_TOKEN_ATTR_UNUSED ); 2847 } 2848 } 2849 2850if (attr & LLAMA_TOKEN_ATTR_UNUSED ) { 2851n_unused ++ ; 2852 } 2853 } 2854 2855LLAMA_LOG_INFO ("%s: %u unused tokens\n" ,__func__ ,n_unused ); 2856 } 2857 2858// maintain a list of tokens that cause end-of-generation 2859// this is currently determined based on the token text, which is obviously not ideal 2860// ref: https://github.com/ggml-org/llama.cpp/issues/9606 2861special_eog_ids .clear (); 2862 2863if (special_fim_pad_id != LLAMA_TOKEN_NULL && special_eog_ids .count (special_fim_pad_id )== 0 ) { 2864special_eog_ids .insert (special_fim_pad_id ); 2865 } 2866 2867if (special_fim_rep_id != LLAMA_TOKEN_NULL && special_eog_ids .count (special_fim_rep_id )== 0 ) { 2868special_eog_ids .insert (special_fim_rep_id ); 2869 } 2870 2871if (special_fim_sep_id != LLAMA_TOKEN_NULL && special_eog_ids .count (special_fim_sep_id )== 0 ) { 2872special_eog_ids .insert (special_fim_sep_id ); 2873 } 2874 2875for (const auto & t :token_to_id ) { 2876auto & attr = id_to_token [t .second ].attr ; 2877 2878if (false 2879|| t .first == "<|eot_id|>" 2880|| t .first == "<|im_end|>" 2881|| t .first == "<|end|>" 2882|| t .first == "<|return|>" // o200k_harmony 2883|| t .first == "<|call|>" // o200k_harmony 2884|| t .first == "<|flush|>" // solar-open 2885|| t .first == "<|calls|>" // solar-open 2886|| t .first == "<end_of_turn>" 2887|| t .first == "<|endoftext|>" 2888|| t .first == "</s>" // paddleocr 2889|| t .first == "<|eom_id|>" 2890|| t .first == "<EOT>" 2891|| t .first == "_<EOT>" 2892|| t .first == "[EOT]" // Kimi-K2 2893|| t .first == "[EOS]" // Kimi-K2 2894|| t .first == "<|end_of_text|>" 2895|| t .first == "<end_of_utterance>" // smoldocling 2896|| t .first == "<eos>" // gemma4 2897|| t .first == "<turn|>" // gemma4 2898|| t .first == "<|tool_response>" // gemma4 2899|| t .first == "<|end▁of▁sentence|>" // deepseek-ocr 2900|| t .first == "[e~[" // minimax-m2/m3 2901 ) { 2902special_eog_ids .insert (t .second ); 2903if ((attr & LLAMA_TOKEN_ATTR_CONTROL )== 0 ) { 2904LLAMA_LOG_WARN ("%s: control-looking token: %6d '%s' was not control-type; this is probably a bug in the model. its type will be overridden\n" , 2905__func__ ,t .second ,t .first .c_str ()); 2906attr = (llama_token_attr ) (attr |LLAMA_TOKEN_ATTR_CONTROL ); 2907 } 2908 }else { 2909if (attr & LLAMA_TOKEN_ATTR_CONTROL && !(attr & LLAMA_TOKEN_ATTR_UNUSED )) { 2910// token is control, but not marked as EOG -> print a debug log 2911if (special_eog_ids .count (t .second )== 0 ) { 2912LLAMA_LOG_DEBUG ("%s: control token: %6d '%s' is not marked as EOG\n" , 2913__func__ ,t .second ,t .first .c_str ()); 2914 } 2915 } 2916 } 2917 } 2918 2919// @ngxson : quick hack for gpt-oss, always render these tokens 2920for (const auto & t :token_to_id ) { 2921auto & attr = id_to_token [t .second ].attr ; 2922 2923if (t .first == "<|channel|>" || t .first == "<|message|>" || t .first == "<|start|>" || t .first == "<|constrain|>" ) { 2924LLAMA_LOG_WARN ("%s: setting token '%s' (%d) attribute to USER_DEFINED (%u), old attributes: %u\n" , 2925__func__ ,t .first .c_str (),t .second ,LLAMA_TOKEN_ATTR_USER_DEFINED ,attr ); 2926 2927attr = LLAMA_TOKEN_ATTR_USER_DEFINED ; 2928 } 2929 } 2930 2931// sanity checks 2932if (special_eos_id != LLAMA_TOKEN_NULL && special_eog_ids .count (special_eos_id )== 0 ) { 2933special_eog_ids .insert (special_eos_id ); 2934LLAMA_LOG_WARN ("%s: special_eos_id is not in special_eog_ids - the tokenizer config may be incorrect\n" ,__func__ ); 2935 } 2936 2937if (special_eot_id != LLAMA_TOKEN_NULL && special_eog_ids .count (special_eot_id )== 0 ) { 2938special_eog_ids .insert (special_eot_id ); 2939LLAMA_LOG_WARN ("%s: special_eot_id is not in special_eog_ids - the tokenizer config may be incorrect\n" ,__func__ ); 2940 } 2941 2942if (special_eom_id != LLAMA_TOKEN_NULL && special_eog_ids .count (special_eom_id )== 0 ) { 2943special_eog_ids .insert (special_eom_id ); 2944LLAMA_LOG_WARN ("%s: special_eom_id is not in special_eog_ids - the tokenizer config may be incorrect\n" ,__func__ ); 2945 } 2946 2947// TODO: workaround for o200k_harmony and solar-open tokenizer: the "<|end|>" token should not be EOG 2948// we don't have a good way to detect this, so for now, if we have "<|return|>" and "<|call|>" tokens ("<|calls|>" and "<|flush|>" for solar-open), 2949// we remove the "<|end|>" token from the EOG list 2950 { 2951bool has_return = false; 2952bool has_call = false; 2953bool has_end = false; 2954bool has_flush = false; 2955 2956llama_token end_id = LLAMA_TOKEN_NULL ; 2957 2958LLAMA_LOG_INFO ("%s: printing all EOG tokens:\n" ,__func__ ); 2959for (auto tid :special_eog_ids ) { 2960if (tid < 0 || tid >= (llama_token )id_to_token .size ()) { 2961LLAMA_LOG_WARN ("%s: EOG token id %d is out of range (vocab size %zu), skipping\n" , 2962__func__ ,tid ,id_to_token .size ()); 2963continue ; 2964 } 2965auto & text = id_to_token [tid ].text ; 2966 2967LLAMA_LOG_INFO ("%s: - %d ('%s')\n" ,__func__ ,tid ,text .c_str ()); 2968 2969if (text == "<|return|>" ) { 2970has_return = true; 2971 }else if (text == "<|call|>" || text == "<|calls|>" ) { 2972has_call = true; 2973 }else if (text == "<|flush|>" ) { 2974has_flush = true; 2975 }else if (text == "<|end|>" ) { 2976has_end = true; 2977end_id = tid ; 2978 } 2979 } 2980 2981if ((has_return && has_call && has_end )|| (has_call && has_flush && has_end )) { 2982special_eog_ids .erase (end_id ); 2983 2984auto & attr = id_to_token [end_id ].attr ; 2985attr = LLAMA_TOKEN_ATTR_USER_DEFINED ; 2986 2987LLAMA_LOG_WARN ("%s: special_eog_ids contains both '<|return|>' and '<|call|>', or '<|calls|>' and '<|flush|>' tokens, removing '<|end|>' token from EOG list\n" ,__func__ ); 2988 } 2989 } 2990 2991// workaround for gemma4 and paddleocr: do not include </s> as an eog token 2992 { 2993bool has_tool_response = false; 2994bool has_s = false; 2995 2996llama_token s_id = LLAMA_TOKEN_NULL ; 2997 2998for (auto tid :special_eog_ids ) { 2999if (tid < 0 || tid >= (llama_token )id_to_token .size ()) { 3000continue ; 3001 } 3002const auto & text = id_to_token [tid ].text ; 3003if (text == "<|tool_response>" ) { 3004has_tool_response = true; 3005 }else if (text == "</s>" ) { 3006has_s = true; 3007s_id = tid ; 3008 } 3009 } 3010 3011if (has_tool_response && has_s ) { 3012special_eog_ids .erase (s_id ); 3013 3014auto & attr = id_to_token [s_id ].attr ; 3015attr = LLAMA_TOKEN_ATTR_NORMAL ; 3016 3017LLAMA_LOG_WARN ("%s: special_eog_ids contains '<|tool_response>', removing '</s>' token from EOG list\n" ,__func__ ); 3018 } 3019 } 3020 } 3021 3022// build special tokens cache 3023 { 3024for (llama_token id = 0 ;id < (llama_token )n_tokens ;++ id ) { 3025if (id_to_token [id ].attr & (LLAMA_TOKEN_ATTR_CONTROL |LLAMA_TOKEN_ATTR_USER_DEFINED |LLAMA_TOKEN_ATTR_UNKNOWN )) { 3026cache_special_tokens .push_back (id ); 3027 } 3028 } 3029 3030 std::sort (cache_special_tokens .begin (),cache_special_tokens .end (), 3031 [& ] (const llama_token a ,const llama_token b ) { 3032return id_to_token [a ].text .size ()> id_to_token [b ].text .size (); 3033 } 3034 ); 3035 3036LLAMA_LOG_INFO ("%s: special tokens cache size = %u\n" ,__func__ , (uint32_t )cache_special_tokens .size ()); 3037 } 3038 3039// build token to piece cache 3040 { 3041size_t size_cache = 0 ; 3042 3043 std::vector < std::string > cache (n_tokens ); 3044 3045for (uint32_t id = 0 ;id < n_tokens ;++ id ) { 3046cache [id ]= token_to_piece_for_cache (id , true); 3047 3048size_cache += cache [id ].size (); 3049 } 3050 3051 std::swap (cache_token_to_piece ,cache ); 3052 3053LLAMA_LOG_INFO ("%s: token to piece cache size = %.4f MB\n" ,__func__ ,size_cache /1024.0 /1024.0 ); 3054 } 3055 3056// Handle per token attributes 3057//NOTE: Each model customizes per token attributes. 3058//NOTE: Per token attributes are missing from the GGUF file. 3059//TODO: Extract attributes from GGUF file. 3060 { 3061auto _contains_any = [] (const std::string & str ,const std::vector < std::string_view > & substrs )-> bool { 3062for (const auto & substr :substrs ) { 3063if (str .find (substr )!= std::string::npos ) { 3064return true; 3065 } 3066 } 3067return false; 3068 }; 3069 3070auto _set_tokenid_attr = [& ] (const llama_token id ,llama_token_attr attr ,bool value ) { 3071uint32_t current = id_to_token .at (id ).attr ; 3072current = value ? (current |attr ) : (current & ~attr ); 3073id_to_token [id ].attr = (llama_token_attr )current ; 3074 }; 3075 3076auto _set_token_attr = [& ] (const std::string & token ,llama_token_attr attr ,bool value ) { 3077_set_tokenid_attr (token_to_id .at (token ),attr ,value ); 3078 }; 3079 3080 std::string model_name ; 3081 std::string tokenizer_pre ; 3082 std::string general_arch ; 3083 3084ml .get_key (LLM_KV_GENERAL_NAME ,model_name , false); 3085ml .get_key (LLM_KV_TOKENIZER_PRE ,tokenizer_pre , false); 3086ml .get_key (LLM_KV_GENERAL_ARCHITECTURE ,general_arch , false); 3087 3088// model name to lowercase 3089 std::transform (model_name .begin (),model_name .end (),model_name .begin (), 3090 [] (const std::string::value_type x ) { 3091return std::tolower (x ); 3092 } 3093 ); 3094 3095// set attributes by model/tokenizer/architecture name 3096if (false 3097|| _contains_any (tokenizer_pre , {"jina-v2-de" ,"jina-v2-es" ,"jina-v2-code" }) 3098|| _contains_any (general_arch , {"nomic-bert-moe" ,"jina-bert-v3" }) 3099 ) { 3100if (token_to_id .count ("<mask>" )== 0 ) { 3101LLAMA_LOG_WARN ("%s: Mask token is missing in vocab, please reconvert model!\n" ,__func__ ); 3102 }else { 3103_set_token_attr ("<mask>" ,LLAMA_TOKEN_ATTR_LSTRIP , true); 3104 } 3105 }else if (_contains_any (model_name , {"phi-3" ,"phi3" })) { 3106for (auto id :cache_special_tokens ) { 3107_set_tokenid_attr (id ,LLAMA_TOKEN_ATTR_RSTRIP , true); 3108 } 3109for (const auto * token : {"</s>" }) { 3110_set_token_attr (token ,LLAMA_TOKEN_ATTR_RSTRIP , true); 3111 } 3112for (const auto * token : {"<unk>" ,"<s>" ,"<|endoftext|>" }) { 3113_set_token_attr (token ,LLAMA_TOKEN_ATTR_RSTRIP , false); 3114 } 3115 }else if (_contains_any (model_name , {"modern-bert" })) { 3116if (token_to_id .count ("[MASK]" )== 0 ) { 3117LLAMA_LOG_WARN ("%s: Mask token missing in vocab!\n" ,__func__ ); 3118 } 3119else { 3120_set_token_attr ("[MASK]" ,LLAMA_TOKEN_ATTR_LSTRIP , true); 3121 } 3122 } 3123 } 3124} 3125 3126enum llama_vocab_type llama_vocab::impl::get_type ()const { 3127return type ; 3128} 3129 3130std::string llama_vocab::impl::type_name ()const { 3131switch (type ) { 3132case LLAMA_VOCAB_TYPE_NONE :return "no vocab" ; 3133case LLAMA_VOCAB_TYPE_SPM :return "SPM" ; 3134case LLAMA_VOCAB_TYPE_BPE :return "BPE" ; 3135case LLAMA_VOCAB_TYPE_WPM :return "WPM" ; 3136case LLAMA_VOCAB_TYPE_UGM :return "UGM" ; 3137case LLAMA_VOCAB_TYPE_RWKV :return "RWKV" ; 3138case LLAMA_VOCAB_TYPE_PLAMO2 :return "PLaMo2" ; 3139default :return "unknown" ; 3140 } 3141} 3142 3143bool llama_vocab::impl::is_normal (llama_token id )const { 3144GGML_ASSERT (type != LLAMA_VOCAB_TYPE_NONE ); 3145return id_to_token [id ].attr & LLAMA_TOKEN_ATTR_NORMAL ; 3146} 3147 3148bool llama_vocab::impl::is_unknown (llama_token id )const { 3149GGML_ASSERT (type != LLAMA_VOCAB_TYPE_NONE ); 3150return id_to_token [id ].attr & LLAMA_TOKEN_ATTR_UNKNOWN ; 3151} 3152 3153bool llama_vocab::impl::is_control (llama_token id )const { 3154GGML_ASSERT (type != LLAMA_VOCAB_TYPE_NONE ); 3155return id_to_token [id ].attr & LLAMA_TOKEN_ATTR_CONTROL ; 3156} 3157 3158bool llama_vocab::impl::is_byte (llama_token id )const { 3159GGML_ASSERT (type != LLAMA_VOCAB_TYPE_NONE ); 3160return id_to_token [id ].attr & LLAMA_TOKEN_ATTR_BYTE ; 3161} 3162 3163bool llama_vocab::impl::is_user_defined (llama_token id )const { 3164GGML_ASSERT (type != LLAMA_VOCAB_TYPE_NONE ); 3165return id_to_token [id ].attr & LLAMA_TOKEN_ATTR_USER_DEFINED ; 3166} 3167 3168bool llama_vocab::impl::is_unused (llama_token id )const { 3169GGML_ASSERT (type != LLAMA_VOCAB_TYPE_NONE ); 3170return id_to_token [id ].attr & LLAMA_TOKEN_ATTR_UNUSED ; 3171} 3172 3173bool llama_vocab::impl::is_eog (llama_token id )const { 3174return id != LLAMA_TOKEN_NULL && special_eog_ids .count (id )> 0 ; 3175} 3176 3177uint8_t llama_vocab::impl::token_to_byte (llama_token id )const { 3178GGML_ASSERT (get_type ()!= LLAMA_VOCAB_TYPE_NONE ); 3179GGML_ASSERT (is_byte (id )); 3180const auto & token_data = id_to_token .at (id ); 3181switch (get_type ()) { 3182case LLAMA_VOCAB_TYPE_SPM : 3183case LLAMA_VOCAB_TYPE_UGM : { 3184auto buf = token_data .text .substr (3 ,2 ); 3185return strtol (buf .c_str (),NULL ,16 ); 3186 } 3187case LLAMA_VOCAB_TYPE_BPE : { 3188// Gemma4 uses BPE with SPM-style byte fallback tokens (<0xXX>) 3189auto buf = token_data .text .substr (3 ,2 ); 3190return strtol (buf .c_str (),NULL ,16 ); 3191 } 3192case LLAMA_VOCAB_TYPE_WPM : { 3193GGML_ABORT ("fatal error" ); 3194 } 3195default : 3196GGML_ABORT ("fatal error" ); 3197 } 3198} 3199 3200llama_token_attr llama_vocab::impl::token_get_attr (llama_token id )const { 3201GGML_ASSERT (type != LLAMA_VOCAB_TYPE_NONE ); 3202return id_to_token .at (id ).attr ; 3203} 3204 3205void llama_vocab::impl::init_tokenizer (enum llama_vocab_type type ) { 3206LLAMA_LOG_DEBUG ("%s: initializing tokenizer for type %d\n" ,__func__ ,type ); 3207 3208switch (type ) { 3209case LLAMA_VOCAB_TYPE_SPM : 3210tokenizer = std::make_unique < llm_tokenizer_spm > (vocab ); 3211break ; 3212case LLAMA_VOCAB_TYPE_BPE : 3213tokenizer = std::make_unique < llm_tokenizer_bpe > (vocab ); 3214break ; 3215case LLAMA_VOCAB_TYPE_WPM : 3216tokenizer = std::make_unique < llm_tokenizer_wpm > (vocab ); 3217break ; 3218case LLAMA_VOCAB_TYPE_UGM : 3219tokenizer = std::make_unique < llm_tokenizer_ugm > (vocab ,precompiled_charsmap ); 3220break ; 3221case LLAMA_VOCAB_TYPE_RWKV : 3222tokenizer = std::make_unique < llm_tokenizer_rwkv > (vocab ); 3223break ; 3224case LLAMA_VOCAB_TYPE_PLAMO2 : 3225tokenizer = std::make_unique < llm_tokenizer_plamo2 > (vocab ); 3226break ; 3227default : 3228GGML_ABORT ("unsupported vocab type" ); 3229 } 3230} 3231 3232// 3233// (de-) tokenize 3234// 3235 3236// #define PRETOKENIZERDEBUG 3237 3238void llama_vocab::impl::tokenizer_st_partition (std::forward_list < fragment_buffer_variant > & buffer ,bool parse_special )const { 3239// for each special token 3240for (const llama_token special_id :cache_special_tokens ) { 3241const auto & data = vocab .get_token_data (special_id ); 3242const auto & text = data .text ; 3243 3244if (!parse_special && (data .attr & (LLAMA_TOKEN_ATTR_CONTROL |LLAMA_TOKEN_ATTR_UNKNOWN ))) { 3245// Ignore control and unknown tokens when parse_special == false 3246continue ; 3247// User-defined tokens are still pre-tokenized before everything else 3248// ref: https://github.com/huggingface/tokenizers/blob/fdd26ba9a3f0c133427aab0423888cbde91362d7/tokenizers/src/tokenizer/mod.rs#L726 3249// This is mostly relevant for neox-style tokenizers (mpt, olmo, stablelm, etc.) 3250 } 3251 3252// for each text fragment 3253 std::forward_list < fragment_buffer_variant > ::iterator it = buffer .begin (); 3254while (it != buffer .end ()) { 3255auto & fragment = (* it ); 3256 3257// if a fragment is text ( not yet processed ) 3258if (fragment .type == FRAGMENT_BUFFER_VARIANT_TYPE_RAW_TEXT ) { 3259const auto & raw_text = fragment .raw_text ; 3260 3261auto raw_text_base_offset = fragment .offset ; 3262auto raw_text_base_length = fragment .length ; 3263 3264// loop over the text 3265while (true) { 3266// find the first occurrence of a given special token in this fragment 3267// passing offset argument only limit the "search area" but match coordinates 3268// are still relative to the source full raw_text 3269// string_view begins at pos 0 for the same reason 3270auto match = std::string_view (raw_text .data (),raw_text_base_offset + raw_text_base_length ).find (text ,raw_text_base_offset ); 3271 3272// no occurrences found, stop processing this fragment for a given special token 3273if (match == std::string::npos )break ; 3274 3275#ifdef PRETOKENIZERDEBUG 3276LLAMA_LOG_WARN ("FF: (%ld %ld %ld) '%s'\n" ,raw_text -> length (),raw_text_base_offset ,raw_text_base_length ,raw_text -> substr (raw_text_base_offset ,raw_text_base_length ).c_str ()); 3277#endif 3278auto source = std::distance (buffer .begin (),it ); 3279 3280// if match is further than base offset 3281// then we have some text to the left of it 3282if (match > raw_text_base_offset ) { 3283// left 3284const int64_t left_reminder_offset = raw_text_base_offset + 0 ; 3285int64_t left_reminder_length = match - raw_text_base_offset ; 3286 3287if (data .attr & LLAMA_TOKEN_ATTR_LSTRIP ) { 3288while (left_reminder_length > 0 && isspace (raw_text [left_reminder_offset + left_reminder_length - 1 ])) { 3289left_reminder_length -- ; 3290 } 3291 } 3292 3293if (left_reminder_length > 0 ) { 3294buffer .emplace_after (it ,raw_text ,left_reminder_offset ,left_reminder_length ); 3295it ++ ; 3296 } 3297 3298#ifdef PRETOKENIZERDEBUG 3299LLAMA_LOG_WARN ("FL: (%ld %ld) '%s'\n" ,left_reminder_offset ,left_reminder_length ,raw_text -> substr (left_reminder_offset ,left_reminder_length ).c_str ()); 3300#endif 3301 } 3302 3303// special token 3304buffer .emplace_after (it ,special_id ); 3305it ++ ; 3306 3307// right 3308if (match + text .length ()< raw_text_base_offset + raw_text_base_length ) { 3309int64_t right_reminder_offset = match + text .length (); 3310int64_t right_reminder_length = raw_text_base_length - ((match - raw_text_base_offset )+ text .length ()); 3311 3312if (data .attr & LLAMA_TOKEN_ATTR_RSTRIP ) { 3313while (right_reminder_length > 0 && isspace (raw_text [right_reminder_offset ])) { 3314right_reminder_offset ++ ; 3315right_reminder_length -- ; 3316 } 3317 } 3318 3319if (right_reminder_length > 0 ) { 3320buffer .emplace_after (it ,raw_text ,right_reminder_offset ,right_reminder_length ); 3321it ++ ; 3322 } 3323 3324#ifdef PRETOKENIZERDEBUG 3325LLAMA_LOG_WARN ("FR: (%ld %ld) '%s'\n" ,right_reminder_offset ,right_reminder_length ,raw_text -> substr (right_reminder_offset ,right_reminder_length ).c_str ()); 3326#endif 3327 3328if (source == 0 ) { 3329buffer .erase_after (buffer .before_begin ()); 3330 }else { 3331buffer .erase_after (std::next (buffer .begin (), (source - 1 ))); 3332 } 3333 3334// repeat for the right side 3335raw_text_base_offset = right_reminder_offset ; 3336raw_text_base_length = right_reminder_length ; 3337 3338#ifdef PRETOKENIZERDEBUG 3339LLAMA_LOG_WARN ("RR: (%ld %ld) '%s'\n" ,raw_text_base_offset ,raw_text_base_length ,raw_text -> substr (raw_text_base_offset ,raw_text_base_length ).c_str ()); 3340#endif 3341 }else { 3342if (source == 0 ) { 3343buffer .erase_after (buffer .before_begin ()); 3344 }else { 3345buffer .erase_after (std::next (buffer .begin (), (source - 1 ))); 3346 } 3347break ; 3348 } 3349 } 3350 } 3351it ++ ; 3352 } 3353 } 3354} 3355 3356// NOTE: avoid ever using this except for building the token_to_piece caches 3357std::string llama_vocab::impl::token_to_piece_for_cache (llama_token token ,bool special )const { 3358 std::string piece ; 3359piece .resize (piece .capacity ());// using string internal cache 3360const int n_chars = vocab .token_to_piece (token ,& piece [0 ],piece .size (),0 ,special ); 3361if (n_chars < 0 ) { 3362piece .resize (- n_chars ); 3363int check = vocab .token_to_piece (token ,& piece [0 ],piece .size (),0 ,special ); 3364GGML_ASSERT (check == - n_chars ); 3365 } 3366else { 3367piece .resize (n_chars ); 3368 } 3369 3370return piece ; 3371} 3372 3373static void llama_escape_whitespace (std::string & text ) { 3374replace_all (text ," " ,"\xe2\x96\x81" ); 3375} 3376 3377static void llama_unescape_whitespace (std::string & word ) { 3378replace_all (word ,"\xe2\x96\x81" ," " ); 3379} 3380 3381static std::string llama_decode_text (const std::string & text ) { 3382 std::string decoded_text ; 3383 3384const auto cpts = unicode_cpts_from_utf8 (text ); 3385for (const auto cpt :cpts ) { 3386const auto utf8 = unicode_cpt_to_utf8 (cpt ); 3387try { 3388decoded_text += unicode_utf8_to_byte (utf8 ); 3389 }catch (const std::out_of_range & /*e*/ ) { 3390decoded_text += "[UNK_BYTE_0x" ; 3391for (const auto c :utf8 ) { 3392decoded_text += format ("%02x" , (uint8_t )c ); 3393 } 3394decoded_text += text + "]" ; 3395 } 3396 } 3397 3398return decoded_text ; 3399} 3400 3401std::vector < llama_token > llama_vocab::impl::tokenize ( 3402const std::string & raw_text , 3403bool add_special , 3404bool parse_special )const { 3405GGML_ASSERT (tokenizer && "Tokenizer not initialized. Call llama_vocab::init_tokenizer() first." ); 3406 3407 std::vector < llama_token > output ; 3408 std::forward_list < fragment_buffer_variant > fragment_buffer ; 3409 3410if (!raw_text .empty ()) { 3411fragment_buffer .emplace_front (raw_text ,0 ,raw_text .length ()); 3412tokenizer_st_partition (fragment_buffer ,parse_special ); 3413 } 3414 3415switch (get_type ()) { 3416case LLAMA_VOCAB_TYPE_SPM : 3417 { 3418// OG tokenizer behavior: 3419// 3420// tokenizer.encode('', add_special_tokens=True) returns [1] 3421// tokenizer.encode('', add_special_tokens=False) returns [] 3422 3423bool is_prev_special = true;// prefix with space if first token 3424 3425if (add_special && add_bos ) { 3426GGML_ASSERT (special_bos_id != LLAMA_TOKEN_NULL ); 3427output .push_back (special_bos_id ); 3428is_prev_special = true; 3429 } 3430 3431for (const auto & fragment :fragment_buffer ) { 3432if (fragment .type == FRAGMENT_BUFFER_VARIANT_TYPE_RAW_TEXT ) { 3433 std::string text ; 3434 3435// prefix with space if previous is special 3436if (add_space_prefix && is_prev_special ) { 3437text = ' ' ; 3438 } 3439 3440text += fragment .raw_text .substr (fragment .offset ,fragment .length ); 3441 3442#ifdef PRETOKENIZERDEBUG 3443LLAMA_LOG_WARN ("TT: (%ld %ld %ld) '%s'\n" ,text .length (),fragment .offset ,fragment .length ,text .c_str ()); 3444#endif 3445llama_escape_whitespace (text ); 3446llm_tokenizer_spm_session session (vocab ); 3447session .tokenize (text ,output ); 3448is_prev_special = false; 3449 }else {// if (fragment.type == FRAGMENT_BUFFER_VARIANT_TYPE_TOKEN) 3450output .push_back (fragment .token ); 3451is_prev_special = true; 3452 } 3453 } 3454 3455if (add_special && add_bos && output .size () >=2 && output [1 ]== special_bos_id ) { 3456LLAMA_LOG_WARN ( 3457"%s: Added a BOS token to the prompt as specified by the model but the prompt " 3458"also starts with a BOS token. So now the final prompt starts with 2 BOS tokens. " 3459"Are you sure this is what you want?\n" ,__FUNCTION__ ); 3460 } 3461 3462if (add_special && add_eos ) { 3463GGML_ASSERT (special_eos_id != LLAMA_TOKEN_NULL ); 3464output .push_back (special_eos_id ); 3465 } 3466 }break ; 3467case LLAMA_VOCAB_TYPE_BPE : 3468 { 3469// it calls some other methods that are not exist in llm_tokenizer, 3470// here just cast it to bpe tokenizer object 3471const llm_tokenizer_bpe * tok_bpe = static_cast < const llm_tokenizer_bpe *> (tokenizer .get ()); 3472 3473 std::unique_ptr < llm_tokenizer_bpe_session > session ; 3474if (vocab .get_tokenizer_model ()== "hybriddna" ) { 3475session = std::make_unique < llm_tokenizer_hybriddna_session > (vocab ,* tok_bpe ); 3476 }else if (vocab .get_tokenizer_model ()== "whitespace" ) { 3477session = std::make_unique < llm_tokenizer_whitespace_session > (vocab ,* tok_bpe ); 3478 }else { 3479session = std::make_unique < llm_tokenizer_bpe_session > (vocab ,* tok_bpe ); 3480 } 3481 3482if (add_special ) { 3483session -> append_bos (output ); 3484 } 3485for (const auto & fragment :fragment_buffer ) { 3486if (fragment .type == FRAGMENT_BUFFER_VARIANT_TYPE_RAW_TEXT ) { 3487 std::string text = fragment .raw_text .substr (fragment .offset ,fragment .length ); 3488 3489if (escape_whitespaces ) { 3490if (add_space_prefix && !text .empty ()&& text [0 ]!= ' ' && text .compare (0 ,3 ,"\xe2\x96\x81" )!= 0 ) { 3491text .insert (0 ," " ); 3492 } 3493llama_escape_whitespace (text ); 3494 } 3495 3496#ifdef PRETOKENIZERDEBUG 3497LLAMA_LOG_WARN ("TT: (%ld %ld %ld) '%s'\n" ,text .length (),fragment .offset ,fragment .length ,text .c_str ()); 3498#endif 3499session -> tokenize (text ,output ); 3500 }else {// if (fragment.type == FRAGMENT_BUFFER_VARIANT_TYPE_TOKEN) 3501session -> append (fragment .token ,output ); 3502 } 3503 } 3504 3505if (add_special ) { 3506session -> append_eos (output ); 3507session -> check_double_bos_eos (output ); 3508 } 3509 }break ; 3510case LLAMA_VOCAB_TYPE_WPM : 3511 { 3512if (add_special ) { 3513GGML_ASSERT (special_bos_id != LLAMA_TOKEN_NULL ); 3514output .push_back (special_bos_id ); 3515 } 3516 3517llm_tokenizer_wpm_session session (vocab ); 3518 3519for (const auto & fragment :fragment_buffer ) { 3520if (fragment .type == FRAGMENT_BUFFER_VARIANT_TYPE_RAW_TEXT ) { 3521 std::string text = fragment .raw_text .substr (fragment .offset ,fragment .length ); 3522 3523#ifdef PRETOKENIZERDEBUG 3524LLAMA_LOG_WARN ("TT: (%ld %ld %ld) '%s'\n" ,text .length (),fragment .offset ,fragment .length ,text .c_str ()); 3525#endif 3526session .tokenize (text ,output ); 3527 }else {// if (fragment.type == FRAGMENT_BUFFER_VARIANT_TYPE_TOKEN) 3528output .push_back (fragment .token ); 3529 } 3530 } 3531 3532if (add_special ) { 3533GGML_ASSERT (special_sep_id != LLAMA_TOKEN_NULL ); 3534output .push_back (special_sep_id ); 3535 } 3536 }break ; 3537case LLAMA_VOCAB_TYPE_UGM : 3538 { 3539if (add_special && add_bos ) { 3540GGML_ASSERT (special_bos_id != LLAMA_TOKEN_NULL ); 3541output .push_back (special_bos_id ); 3542 } 3543llm_tokenizer_ugm_session session (vocab ,* static_cast < const llm_tokenizer_ugm *> (tokenizer .get ())); 3544 3545for (const auto & fragment :fragment_buffer ) { 3546if (fragment .type == FRAGMENT_BUFFER_VARIANT_TYPE_RAW_TEXT ) { 3547 std::string text = fragment .raw_text .substr (fragment .offset ,fragment .length ); 3548#ifdef PRETOKENIZERDEBUG 3549LLAMA_LOG_WARN ("TT: (%ld %ld %ld) '%s'\n" ,text .length (),fragment .offset ,fragment .length ,text .c_str ()); 3550#endif 3551session .tokenize (text ,output ); 3552 }else {// if (fragment.type == FRAGMENT_BUFFER_VARIANT_TYPE_TOKEN) 3553output .push_back (fragment .token ); 3554 } 3555 } 3556 3557if (add_special && add_bos && output .size () >=2 && output [1 ]== special_bos_id ) { 3558LLAMA_LOG_WARN ( 3559"%s: Added a BOS token to the prompt as specified by the model but the prompt " 3560"also starts with a BOS token. So now the final prompt starts with 2 BOS tokens. " 3561"Are you sure this is what you want?\n" ,__FUNCTION__ ); 3562 } 3563 3564if (add_special && add_eos ) { 3565GGML_ASSERT (special_eos_id != LLAMA_TOKEN_NULL ); 3566output .push_back (special_eos_id ); 3567 } 3568 }break ; 3569case LLAMA_VOCAB_TYPE_RWKV : 3570 { 3571llm_tokenizer_rwkv_session session (vocab ,* static_cast < const llm_tokenizer_rwkv *> (tokenizer .get ())); 3572for (const auto & fragment :fragment_buffer ) { 3573if (fragment .type == FRAGMENT_BUFFER_VARIANT_TYPE_RAW_TEXT ) { 3574 std::string text = fragment .raw_text .substr (fragment .offset ,fragment .length ); 3575 3576#ifdef PRETOKENIZERDEBUG 3577LLAMA_LOG_WARN ("TT: (%ld %ld %ld) '%s'\n" ,text .length (),fragment .offset ,fragment .length ,text .c_str ()); 3578#endif 3579 3580session .tokenize (text ,output ); 3581 }else {// if (fragment.type == FRAGMENT_BUFFER_VARIANT_TYPE_TOKEN) 3582output .push_back (fragment .token ); 3583 } 3584 } 3585 }break ; 3586case LLAMA_VOCAB_TYPE_PLAMO2 : 3587 { 3588llm_tokenizer_plamo2_session session (* static_cast < const llm_tokenizer_plamo2 *> (tokenizer .get ())); 3589for (const auto & fragment :fragment_buffer ) { 3590if (fragment .type == FRAGMENT_BUFFER_VARIANT_TYPE_RAW_TEXT ) { 3591 std::string text = fragment .raw_text .substr (fragment .offset ,fragment .length ); 3592 3593#ifdef PRETOKENIZERDEBUG 3594LLAMA_LOG_WARN ("TT: (%ld %ld %ld) '%s'\n" ,text .length (),fragment .offset ,fragment .length ,text .c_str ()); 3595#endif 3596 3597session .tokenize (text ,output ); 3598 }else {// if (fragment.type == FRAGMENT_BUFFER_VARIANT_TYPE_TOKEN) 3599output .push_back (fragment .token ); 3600 } 3601 } 3602 }break ; 3603case LLAMA_VOCAB_TYPE_NONE : 3604GGML_ABORT ("fatal error" ); 3605 } 3606 3607return output ; 3608} 3609 3610int32_t llama_vocab::impl::token_to_piece (llama_token token ,char * buf ,int32_t length ,int32_t lstrip ,bool special )const { 3611// ref: https://github.com/ggml-org/llama.cpp/pull/7587#discussion_r1620983843 3612static const int attr_special = LLAMA_TOKEN_ATTR_UNKNOWN |LLAMA_TOKEN_ATTR_CONTROL ; 3613const llama_token_attr attr = token_get_attr (token ); 3614if (!special && (attr & attr_special )) { 3615return 0 ; 3616 } 3617 3618// copy piece chars to output text buffer 3619// skip up to 'lstrip' leading spaces before copying 3620auto _try_copy = [= ] (const char * token ,size_t size )-> int32_t { 3621if (size >=static_cast < size_t > (std::numeric_limits < int32_t > ::max ())) { 3622GGML_ABORT ("invalid token size: %zu exceeds int32_t limit" ,size ); 3623 } 3624 3625for (int32_t i = 0 ;i < lstrip && size && * token == ' ' ;++ i ) { 3626token ++ ; 3627size -- ; 3628 } 3629if (length < (int32_t )size ) { 3630return - (int32_t )size ; 3631 } 3632memcpy (buf ,token ,size ); 3633return (int32_t )size ; 3634 }; 3635 3636// if we have a cache - use it 3637 { 3638const auto & cache = cache_token_to_piece ; 3639 3640if (!cache .empty ()) { 3641const auto & result = cache .at (token ); 3642return _try_copy (result .data (),result .size ()); 3643 } 3644 } 3645 3646if (0 <=token && token < (int32_t )id_to_token .size ()) { 3647const std::string & token_text = id_to_token [token ].text ; 3648switch (get_type ()) { 3649case LLAMA_VOCAB_TYPE_WPM : 3650case LLAMA_VOCAB_TYPE_SPM : 3651case LLAMA_VOCAB_TYPE_UGM : { 3652// NOTE: we accept all unsupported token types, 3653// suppressing them like CONTROL tokens. 3654if (attr & (attr_special |LLAMA_TOKEN_ATTR_USER_DEFINED )) { 3655return _try_copy (token_text .data (),token_text .size ()); 3656 } 3657if (attr & LLAMA_TOKEN_ATTR_NORMAL ) { 3658 std::string result = token_text ; 3659llama_unescape_whitespace (result ); 3660return _try_copy (result .data (),result .size ()); 3661 } 3662if (attr & LLAMA_TOKEN_ATTR_BYTE ) { 3663char byte = (char )token_to_byte (token ); 3664return _try_copy ((char * )& byte ,1 ); 3665 } 3666break ; 3667 } 3668case LLAMA_VOCAB_TYPE_BPE : { 3669// NOTE: we accept all unsupported token types, 3670// suppressing them like CONTROL tokens. 3671if (attr & (attr_special |LLAMA_TOKEN_ATTR_USER_DEFINED )) { 3672return _try_copy (token_text .data (),token_text .size ()); 3673 } 3674if (attr & LLAMA_TOKEN_ATTR_NORMAL ) { 3675if (escape_whitespaces ) { 3676// SPM-style BPE: tokens contain ▁ for spaces 3677 std::string result = token_text ; 3678llama_unescape_whitespace (result ); 3679return _try_copy (result .data (),result .size ()); 3680 } 3681 std::string result = llama_decode_text (token_text ); 3682return _try_copy (result .data (),result .size ()); 3683 } 3684if (attr & LLAMA_TOKEN_ATTR_BYTE ) { 3685char byte = (char )token_to_byte (token ); 3686return _try_copy ((char * )& byte ,1 ); 3687 } 3688break ; 3689 } 3690case LLAMA_VOCAB_TYPE_RWKV : { 3691 std::vector < uint8_t > result = llama_unescape_rwkv_token (token_text ); 3692 3693// If we don't have enough space, return an error 3694if (result .size ()> (size_t )length ) { 3695return - (int )result .size (); 3696 } 3697 3698memcpy (buf ,result .data (),result .size ()); 3699return (int )result .size (); 3700 } 3701case LLAMA_VOCAB_TYPE_PLAMO2 : { 3702// PLaMo-2 uses similar token handling as BPE/SPM 3703if (vocab .is_byte (token )) { 3704// Handle byte tokens like <0xXX> 3705if (token_text .length ()== 6 && token_text .substr (0 ,3 )== "<0x" && token_text .back ()== '>' ) { 3706 std::string hex_str = token_text .substr (3 ,2 ); 3707if (std::isxdigit (static_cast < unsigned char > (hex_str [0 ]))&& std::isxdigit (static_cast < unsigned char > (hex_str [1 ]))) { 3708int hex_val = std::stoi (hex_str ,nullptr ,16 ); 3709if (length < 1 ) { 3710return -1 ; 3711 } 3712buf [0 ]= static_cast < char > (hex_val ); 3713return 1 ; 3714 } 3715 } 3716 } 3717 3718// Normal token - just copy the text 3719 std::string result = token_text ; 3720return _try_copy (result .data (),result .size ()); 3721 } 3722default : 3723GGML_ABORT ("fatal error" ); 3724 } 3725 } 3726 3727return 0 ; 3728} 3729 3730const std::string & llama_vocab::impl::token_to_piece (llama_token token )const { 3731return cache_token_to_piece .at (token ); 3732} 3733 3734int32_t llama_vocab::impl::detokenize ( 3735const llama_token * tokens , 3736int32_t n_tokens , 3737char * text , 3738int32_t text_len_max , 3739bool remove_special , 3740bool unparse_special )const { 3741if (type == LLAMA_VOCAB_TYPE_NONE ) { 3742return 0 ; 3743 } 3744 3745GGML_ASSERT (tokenizer && "Tokenizer not initialized. Call llama_vocab::init_tokenizer() first." ); 3746 3747int32_t avail = text_len_max ; 3748int32_t total = 0 ; 3749 3750// remove the leading space 3751bool remove_space = add_space_prefix ; 3752 3753if (remove_special && add_bos ) { 3754if (n_tokens > 0 && tokens [0 ]== special_bos_id ) { 3755remove_space = false; 3756n_tokens -- ; 3757tokens ++ ; 3758 } 3759 } 3760 3761if (remove_special && add_eos ) { 3762if (n_tokens > 0 && tokens [n_tokens - 1 ]== special_eos_id ) { 3763n_tokens -- ; 3764 } 3765 } 3766 3767for (int32_t i = 0 ;i < n_tokens ;++ i ) { 3768GGML_ASSERT (avail >=0 ); 3769int32_t n_chars = token_to_piece (tokens [i ],text ,avail ,remove_space ,unparse_special ); 3770remove_space = false; 3771if (n_chars < 0 ) { 3772avail = 0 ; 3773total -= n_chars ; 3774 }else if (n_chars > 0 ) { 3775avail -= n_chars ; 3776text += n_chars ; 3777total += n_chars ; 3778 } 3779 } 3780 3781if (total > text_len_max ) { 3782return - total ; 3783 } 3784 3785if (clean_spaces ) { 3786text -= total ;// restart text 3787 3788// first pass: characters ?!., //TODO: where do these characters come from? 3789const int32_t total1 = total ; 3790total = total ?1 :0 ; 3791for (int32_t i = 1 ;i < total1 ;++ i ) { 3792const char x = text [i ]; 3793if (text [i - 1 ]== ' ' ) { 3794if (x == '?' || x == '!' || x == '.' || x == ',' ) {// " ?", " !", " .", " ," 3795total -- ;// remove space 3796 } 3797 } 3798text [total ++ ]= x ; 3799 } 3800 3801// second pass: strip single apostrophe between spaces 3802const int32_t total2 = total ; 3803total = total ?1 :0 ; 3804for (int32_t i = 1 ;i < total2 ;++ i ) { 3805const char x = text [i ]; 3806if (x == '\'' && i + 1 < total2 && text [i - 1 ]== ' ' && text [i + 1 ]== ' ' ) {// " ' " 3807total -- ;// remove prev space 3808text [++ i ]= '\0' ;// remove next space 3809 } 3810text [total ++ ]= x ; 3811 } 3812 3813// third pass: apostrophe contractions //NOTE: this makes sense? 3814const int32_t total3 = total ; 3815total = total ?1 :0 ; 3816for (int32_t i = 1 ;i < total3 ;++ i ) { 3817const char x = text [i ]; 3818if (text [i - 1 ]== ' ' ) { 3819if (x == '\'' && i + 1 < total3 ) { 3820const char x1 = text [i + 1 ]; 3821if (x1 == 't' || x1 == 'd' ) {// " 't", " 'd" 3822//total--; // remove space 3823 }else if (x1 == 's' || x1 == 'm' ) {// " 's", " 'm" 3824total -- ;// remove space 3825 }else if (i + 2 < total3 ) { 3826const char x2 = text [i + 2 ]; 3827if ((x1 == 'l' && x2 == 'l' )) {// " 'll" 3828//total--; // remove space 3829 }else if ((x1 == 'r' && x2 == 'e' )|| (x1 == 'v' && x2 == 'e' )) {// " 're", " 've" 3830total -- ;// remove space 3831 }else { 3832//total--; // remove space 3833 } 3834 }else { 3835//total--; // remove space 3836 } 3837 } 3838 } 3839text [total ++ ]= x ; 3840 } 3841 } 3842 3843return total <=text_len_max ?total :- total ; 3844} 3845 3846void llama_vocab::impl::print_info ()const { 3847LLAMA_LOG_INFO ("%s: vocab type = %s\n" ,__func__ ,type_name ().c_str ()); 3848LLAMA_LOG_INFO ("%s: n_vocab = %u\n" ,__func__ ,vocab .n_tokens ()); 3849LLAMA_LOG_INFO ("%s: n_merges = %u\n" ,__func__ , (uint32_t )bpe_ranks .size ()); 3850 3851// special tokens 3852if (special_bos_id != LLAMA_TOKEN_NULL ) {LLAMA_LOG_INFO ("%s: BOS token = %d '%s'\n" ,__func__ ,special_bos_id ,id_to_token .at (special_bos_id ).text .c_str () ); } 3853if (special_eos_id != LLAMA_TOKEN_NULL ) {LLAMA_LOG_INFO ("%s: EOS token = %d '%s'\n" ,__func__ ,special_eos_id ,id_to_token .at (special_eos_id ).text .c_str () ); } 3854if (special_eot_id != LLAMA_TOKEN_NULL ) {LLAMA_LOG_INFO ("%s: EOT token = %d '%s'\n" ,__func__ ,special_eot_id ,id_to_token .at (special_eot_id ).text .c_str () ); } 3855if (special_eom_id != LLAMA_TOKEN_NULL ) {LLAMA_LOG_INFO ("%s: EOM token = %d '%s'\n" ,__func__ ,special_eom_id ,id_to_token .at (special_eom_id ).text .c_str () ); } 3856if (special_unk_id != LLAMA_TOKEN_NULL ) {LLAMA_LOG_INFO ("%s: UNK token = %d '%s'\n" ,__func__ ,special_unk_id ,id_to_token .at (special_unk_id ).text .c_str () ); } 3857if (special_sep_id != LLAMA_TOKEN_NULL ) {LLAMA_LOG_INFO ("%s: SEP token = %d '%s'\n" ,__func__ ,special_sep_id ,id_to_token .at (special_sep_id ).text .c_str () ); } 3858if (special_pad_id != LLAMA_TOKEN_NULL ) {LLAMA_LOG_INFO ("%s: PAD token = %d '%s'\n" ,__func__ ,special_pad_id ,id_to_token .at (special_pad_id ).text .c_str () ); } 3859if (special_mask_id != LLAMA_TOKEN_NULL ) {LLAMA_LOG_INFO ("%s: MASK token = %d '%s'\n" ,__func__ ,special_mask_id ,id_to_token .at (special_mask_id ).text .c_str () ); } 3860 3861if (linefeed_id != LLAMA_TOKEN_NULL ) {LLAMA_LOG_INFO ("%s: LF token = %d '%s'\n" ,__func__ ,linefeed_id ,id_to_token .at (linefeed_id ).text .c_str () ); } 3862 3863if (special_fim_pre_id != LLAMA_TOKEN_NULL ) {LLAMA_LOG_INFO ("%s: FIM PRE token = %d '%s'\n" ,__func__ ,special_fim_pre_id ,id_to_token .at (special_fim_pre_id ).text .c_str () ); } 3864if (special_fim_suf_id != LLAMA_TOKEN_NULL ) {LLAMA_LOG_INFO ("%s: FIM SUF token = %d '%s'\n" ,__func__ ,special_fim_suf_id ,id_to_token .at (special_fim_suf_id ).text .c_str () ); } 3865if (special_fim_mid_id != LLAMA_TOKEN_NULL ) {LLAMA_LOG_INFO ("%s: FIM MID token = %d '%s'\n" ,__func__ ,special_fim_mid_id ,id_to_token .at (special_fim_mid_id ).text .c_str () ); } 3866if (special_fim_pad_id != LLAMA_TOKEN_NULL ) {LLAMA_LOG_INFO ("%s: FIM PAD token = %d '%s'\n" ,__func__ ,special_fim_pad_id ,id_to_token .at (special_fim_pad_id ).text .c_str () ); } 3867if (special_fim_rep_id != LLAMA_TOKEN_NULL ) {LLAMA_LOG_INFO ("%s: FIM REP token = %d '%s'\n" ,__func__ ,special_fim_rep_id ,id_to_token .at (special_fim_rep_id ).text .c_str () ); } 3868if (special_fim_sep_id != LLAMA_TOKEN_NULL ) {LLAMA_LOG_INFO ("%s: FIM SEP token = %d '%s'\n" ,__func__ ,special_fim_sep_id ,id_to_token .at (special_fim_sep_id ).text .c_str () ); } 3869 3870for (const auto & id :special_eog_ids ) { 3871LLAMA_LOG_INFO ("%s: EOG token = %d '%s'\n" ,__func__ ,id ,id_to_token .at (id ).text .c_str () ); 3872 } 3873 3874LLAMA_LOG_INFO ("%s: max token length = %d\n" ,__func__ ,max_token_len ); 3875} 3876 3877llama_vocab::llama_vocab () :pimpl (new impl (* this )) { 3878} 3879 3880llama_vocab::~llama_vocab ()= default ; 3881 3882void llama_vocab::load (llama_model_loader & ml ,const LLM_KV & kv ) { 3883pimpl -> load (ml ,kv ); 3884} 3885 3886std::string llama_vocab::get_tokenizer_model ()const { 3887return pimpl -> tokenizer_model ; 3888} 3889 3890std::string llama_vocab::get_tokenizer_pre ()const { 3891return pimpl -> tokenizer_pre ; 3892} 3893 3894enum llama_vocab_type llama_vocab::get_type ()const { 3895return pimpl -> type ; 3896} 3897 3898enum llama_vocab_pre_type llama_vocab::get_pre_type ()const { 3899return pimpl -> pre_type ; 3900} 3901 3902uint32_t llama_vocab::n_tokens ()const { 3903return (uint32_t )pimpl -> id_to_token .size (); 3904} 3905 3906uint32_t llama_vocab::n_token_types ()const { 3907return (uint32_t )pimpl -> n_token_types ; 3908} 3909 3910std::string llama_vocab::type_name ()const { 3911return pimpl -> type_name (); 3912} 3913 3914bool llama_vocab::is_normal (llama_token id )const { 3915return pimpl -> is_normal (id ); 3916} 3917 3918bool llama_vocab::is_unknown (llama_token id )const { 3919return pimpl -> is_unknown (id ); 3920} 3921 3922bool llama_vocab::is_control (llama_token id )const { 3923return pimpl -> is_control (id ); 3924} 3925 3926bool llama_vocab::is_byte (llama_token id )const { 3927return pimpl -> is_byte (id ); 3928} 3929 3930bool llama_vocab::is_user_defined (llama_token id )const { 3931return pimpl -> is_user_defined (id ); 3932} 3933 3934bool llama_vocab::is_unused (llama_token id )const { 3935return pimpl -> is_unused (id ); 3936} 3937 3938bool llama_vocab::is_eog (llama_token id )const { 3939return pimpl -> is_eog (id ); 3940} 3941 3942uint8_t llama_vocab::token_to_byte (llama_token id )const { 3943return pimpl -> token_to_byte (id ); 3944} 3945 3946llama_token llama_vocab::byte_to_token (uint8_t ch )const { 3947GGML_ASSERT (get_type ()!= LLAMA_VOCAB_TYPE_NONE ); 3948static const char * hex = "0123456789ABCDEF" ; 3949switch (get_type ()) { 3950case LLAMA_VOCAB_TYPE_SPM : 3951case LLAMA_VOCAB_TYPE_UGM : { 3952const char buf [7 ]= {'<' ,'0' ,'x' ,hex [ch >>4 ],hex [ch & 15 ],'>' ,0 }; 3953auto token = pimpl -> token_to_id .find (buf ); 3954if (token != pimpl -> token_to_id .end ()) { 3955return (* token ).second ; 3956 } 3957// Try to fall back to just the byte as a string 3958const char buf2 [2 ]= { (char )ch ,0 }; 3959return pimpl -> token_to_id .at (buf2 ); 3960 } 3961case LLAMA_VOCAB_TYPE_WPM : 3962case LLAMA_VOCAB_TYPE_BPE : { 3963return pimpl -> token_to_id .at (unicode_byte_to_utf8 (ch )); 3964 } 3965case LLAMA_VOCAB_TYPE_PLAMO2 : { 3966// PLaMo-2 uses byte tokens in format <0xXX> 3967char hex_str [8 ]; 3968snprintf (hex_str ,sizeof (hex_str ),"<0x%02X>" ,ch ); 3969return pimpl -> token_to_id .at (hex_str ); 3970 } 3971default : 3972GGML_ABORT ("fatal error" ); 3973 } 3974} 3975 3976llama_token llama_vocab::text_to_token (const std::string & text )const { 3977GGML_ASSERT (pimpl -> type != LLAMA_VOCAB_TYPE_NONE ); 3978auto it = pimpl -> token_to_id .find (text ); 3979if (it != pimpl -> token_to_id .end ()) { 3980return (* it ).second ; 3981 } 3982return LLAMA_TOKEN_NULL ; 3983} 3984 3985const llama_vocab::token_data & llama_vocab::get_token_data (llama_token id )const { 3986GGML_ASSERT (pimpl -> type != LLAMA_VOCAB_TYPE_NONE ); 3987return pimpl -> id_to_token .at (id ); 3988} 3989 3990const char * llama_vocab::token_get_text (llama_token id )const { 3991GGML_ASSERT (pimpl -> type != LLAMA_VOCAB_TYPE_NONE ); 3992return pimpl -> id_to_token .at (id ).text .c_str (); 3993} 3994 3995float llama_vocab::token_get_score (llama_token id )const { 3996GGML_ASSERT (pimpl -> type != LLAMA_VOCAB_TYPE_NONE ); 3997return pimpl -> id_to_token .at (id ).score ; 3998} 3999 4000llama_token_attr llama_vocab::token_get_attr (llama_token id )const { 4001return pimpl -> token_get_attr (id ); 4002} 4003 4004llama_token llama_vocab::token_bos ()const { 4005return pimpl -> special_bos_id ; 4006} 4007 4008llama_token llama_vocab::token_eos ()const { 4009return pimpl -> special_eos_id ; 4010} 4011 4012llama_token llama_vocab::token_eot ()const { 4013return pimpl -> special_eot_id ; 4014} 4015 4016llama_token llama_vocab::token_eom ()const { 4017return pimpl -> special_eom_id ; 4018} 4019 4020llama_token llama_vocab::token_unk ()const { 4021return pimpl -> special_unk_id ; 4022} 4023 4024llama_token llama_vocab::token_sep ()const { 4025return pimpl -> special_sep_id ; 4026} 4027 4028llama_token llama_vocab::token_nl ()const { 4029return pimpl -> linefeed_id ; 4030} 4031 4032llama_token llama_vocab::token_pad ()const { 4033return pimpl -> special_pad_id ; 4034} 4035 4036llama_token llama_vocab::token_prefix ()const { 4037return pimpl -> special_fim_pre_id ; 4038} 4039 4040llama_token llama_vocab::token_middle ()const { 4041return pimpl -> special_fim_mid_id ; 4042} 4043 4044llama_token llama_vocab::token_suffix ()const { 4045return pimpl -> special_fim_suf_id ; 4046} 4047 4048llama_token llama_vocab::token_fim_pre ()const { 4049return pimpl -> special_fim_pre_id ; 4050} 4051 4052llama_token llama_vocab::token_fim_suf ()const { 4053return pimpl -> special_fim_suf_id ; 4054} 4055 4056llama_token llama_vocab::token_fim_mid ()const { 4057return pimpl -> special_fim_mid_id ; 4058} 4059 4060llama_token llama_vocab::token_fim_pad ()const { 4061return pimpl -> special_fim_pad_id ; 4062} 4063 4064llama_token llama_vocab::token_fim_rep ()const { 4065return pimpl -> special_fim_rep_id ; 4066} 4067 4068llama_token llama_vocab::token_fim_sep ()const { 4069return pimpl -> special_fim_sep_id ; 4070} 4071 4072llama_token llama_vocab::token_mask ()const { 4073return pimpl -> special_mask_id ; 4074} 4075 4076bool llama_vocab::get_add_space_prefix ()const { 4077return pimpl -> add_space_prefix ; 4078} 4079 4080bool llama_vocab::get_add_bos ()const { 4081return pimpl -> add_bos ; 4082} 4083 4084bool llama_vocab::get_add_eos ()const { 4085return pimpl -> add_eos ; 4086} 4087 4088bool llama_vocab::get_add_sep ()const { 4089return pimpl -> add_sep ; 4090} 4091 4092bool llama_vocab::get_ignore_merges ()const { 4093return pimpl -> ignore_merges ; 4094} 4095 4096bool llama_vocab::get_clean_spaces ()const { 4097return pimpl -> clean_spaces ; 4098} 4099 4100bool llama_vocab::get_remove_extra_whitespaces ()const { 4101return pimpl -> remove_extra_whitespaces ; 4102} 4103 4104bool llama_vocab::get_escape_whitespaces ()const { 4105return pimpl -> escape_whitespaces ; 4106} 4107 4108bool llama_vocab::get_treat_whitespace_as_suffix ()const { 4109return pimpl -> treat_whitespace_as_suffix ; 4110} 4111 4112const llama_vocab::normalizer_options & llama_vocab::get_normalizer_opts ()const { 4113return pimpl -> normalizer_opts ; 4114} 4115 4116const std::vector < llama_token > & llama_vocab::get_suppress_tokens ()const { 4117return pimpl -> suppress_tokens ; 4118} 4119 4120int llama_vocab::max_token_len ()const { 4121return pimpl -> max_token_len ; 4122} 4123 4124int llama_vocab::find_bpe_rank (const std::string & token_left ,const std::string & token_right )const { 4125GGML_ASSERT (token_left .find (' ' )== std::string::npos ); 4126GGML_ASSERT (token_right .find (' ' )== std::string::npos ); 4127 4128auto it = pimpl -> bpe_ranks .find (std::make_pair (token_left ,token_right )); 4129if (it == pimpl -> bpe_ranks .end ()) { 4130return -1 ; 4131 } 4132 4133return it -> second ; 4134} 4135 4136std::vector < std::string > llama_vocab::get_bpe_merges ()const { 4137int max_rank = -1 ; 4138for (const auto & pair :pimpl -> bpe_ranks ) { 4139max_rank = std::max (max_rank ,pair .second ); 4140 } 4141 std::vector < std::string > result (max_rank + 1 ); 4142 4143for (const auto & pair :pimpl -> bpe_ranks ) { 4144result [pair .second ]= pair .first .first + " " + pair .first .second ; 4145 } 4146 4147return result ; 4148} 4149 4150std::vector < char > llama_vocab::get_precompiled_charsmap ()const { 4151return pimpl -> precompiled_charsmap ; 4152} 4153 4154int32_t llama_vocab::tokenize ( 4155const char * text , 4156int32_t text_len , 4157llama_token * tokens , 4158int32_t n_tokens_max , 4159bool add_special , 4160bool parse_special )const { 4161auto res = tokenize (std::string (text ,text_len ),add_special ,parse_special ); 4162if (res .size () >=static_cast < size_t > (std::numeric_limits < int32_t > ::max ())) { 4163LLAMA_LOG_ERROR ("%s: tokenization result size %zu exceeds int32_t limit\n" ,__func__ ,res .size ()); 4164return std::numeric_limits < int32_t > ::min (); 4165 } 4166 4167if (n_tokens_max < (int )res .size ()) { 4168// LLAMA_LOG_ERROR("%s: too many tokens\n", __func__); 4169return - ((int )res .size ()); 4170 } 4171 4172for (size_t i = 0 ;i < res .size ();i ++ ) { 4173tokens [i ]= res [i ]; 4174 } 4175 4176return res .size (); 4177} 4178 4179std::vector < llama_token > llama_vocab::tokenize ( 4180const std::string & raw_text , 4181bool add_special , 4182bool parse_special )const { 4183return pimpl -> tokenize (raw_text ,add_special ,parse_special ); 4184} 4185 4186const std::string & llama_vocab::token_to_piece (llama_token token )const { 4187return pimpl -> token_to_piece (token ); 4188} 4189 4190int32_t llama_vocab::token_to_piece (llama_token token ,char * buf ,int32_t length ,int32_t lstrip ,bool special )const { 4191return pimpl -> token_to_piece (token ,buf ,length ,lstrip ,special ); 4192} 4193 4194int32_t llama_vocab::detokenize ( 4195const llama_token * tokens , 4196int32_t n_tokens , 4197char * text , 4198int32_t text_len_max , 4199bool remove_special , 4200bool unparse_special )const { 4201return pimpl -> detokenize (tokens ,n_tokens ,text ,text_len_max ,remove_special ,unparse_special ); 4202} 4203 4204std::string llama_vocab::detokenize (const std::vector < llama_token > & tokens ,bool special )const { 4205 std::string text ; 4206text .resize (std::max (text .capacity (),tokens .size ())); 4207int32_t n_chars = detokenize (tokens .data (), (int32_t )tokens .size (),& text [0 ], (int32_t )text .size (), false,special ); 4208if (n_chars < 0 ) { 4209text .resize (- n_chars ); 4210n_chars = detokenize (tokens .data (), (int32_t )tokens .size (),& text [0 ], (int32_t )text .size (), false,special ); 4211GGML_ASSERT (n_chars <= (int32_t )text .size ());// whitespace trimming is performed after per-token detokenization 4212 } 4213 4214text .resize (n_chars ); 4215 4216// NOTE: the original tokenizer decodes bytes after collecting the pieces. 4217return text ; 4218} 4219 4220void llama_vocab::print_info ()const { 4221pimpl -> print_info (); 4222} 4223 4224// 4225// interface implementation 4226// 4227 4228int32_t llama_vocab_n_tokens (const struct llama_vocab * vocab ) { 4229return vocab -> n_tokens (); 4230} 4231 4232// deprecated 4233int32_t llama_n_vocab (const struct llama_vocab * vocab ) { 4234return llama_vocab_n_tokens (vocab ); 4235} 4236 4237enum llama_vocab_type llama_vocab_type (const struct llama_vocab * vocab ) { 4238return vocab -> get_type (); 4239} 4240 4241const char * llama_vocab_get_text (const struct llama_vocab * vocab ,llama_token token ) { 4242return vocab -> token_get_text (token ); 4243} 4244 4245float llama_vocab_get_score (const struct llama_vocab * vocab ,llama_token token ) { 4246return vocab -> token_get_score (token ); 4247} 4248 4249enum llama_token_attr llama_vocab_get_attr (const struct llama_vocab * vocab ,llama_token token ) { 4250return vocab -> token_get_attr (token ); 4251} 4252 4253bool llama_vocab_is_eog (const struct llama_vocab * vocab ,llama_token token ) { 4254return vocab -> is_eog (token ); 4255} 4256 4257bool llama_vocab_is_control (const struct llama_vocab * vocab ,llama_token token ) { 4258return vocab -> is_control (token ); 4259} 4260 4261llama_token llama_vocab_bos (const struct llama_vocab * vocab ) { 4262return vocab -> token_bos (); 4263} 4264 4265llama_token llama_vocab_eos (const struct llama_vocab * vocab ) { 4266return vocab -> token_eos (); 4267} 4268 4269llama_token llama_vocab_eot (const struct llama_vocab * vocab ) { 4270return vocab -> token_eot (); 4271} 4272 4273// deprecated 4274llama_token llama_vocab_cls (const struct llama_vocab * vocab ) { 4275return vocab -> token_bos (); 4276} 4277 4278llama_token llama_vocab_sep (const struct llama_vocab * vocab ) { 4279return vocab -> token_sep (); 4280} 4281 4282llama_token llama_vocab_nl (const struct llama_vocab * vocab ) { 4283return vocab -> token_nl (); 4284} 4285 4286llama_token llama_vocab_pad (const struct llama_vocab * vocab ) { 4287return vocab -> token_pad (); 4288} 4289 4290bool llama_vocab_get_add_bos (const struct llama_vocab * vocab ) { 4291return vocab -> get_add_bos (); 4292} 4293 4294bool llama_vocab_get_add_eos (const struct llama_vocab * vocab ) { 4295return vocab -> get_add_eos (); 4296} 4297 4298bool llama_vocab_get_add_sep (const struct llama_vocab * vocab ) { 4299return vocab -> get_add_sep (); 4300} 4301 4302const llama_token * llama_vocab_get_suppress_tokens (const struct llama_vocab * vocab ,int32_t * n_suppress_tokens ) { 4303const std::vector < llama_token > & tokens = vocab -> get_suppress_tokens (); 4304if (n_suppress_tokens ) { 4305* n_suppress_tokens = (int32_t )tokens .size (); 4306 } 4307return tokens .data (); 4308} 4309 4310llama_token llama_vocab_fim_pre (const struct llama_vocab * vocab ) { 4311return vocab -> token_fim_pre (); 4312} 4313 4314llama_token llama_vocab_fim_suf (const struct llama_vocab * vocab ) { 4315return vocab -> token_fim_suf (); 4316} 4317 4318llama_token llama_vocab_fim_mid (const struct llama_vocab * vocab ) { 4319return vocab -> token_fim_mid (); 4320} 4321 4322llama_token llama_vocab_fim_pad (const struct llama_vocab * vocab ) { 4323return vocab -> token_fim_pad (); 4324} 4325 4326llama_token llama_vocab_fim_rep (const struct llama_vocab * vocab ) { 4327return vocab -> token_fim_rep (); 4328} 4329 4330llama_token llama_vocab_fim_sep (const struct llama_vocab * vocab ) { 4331return vocab -> token_fim_sep (); 4332} 4333 4334llama_token llama_vocab_mask (const struct llama_vocab * vocab ) { 4335return vocab -> token_mask (); 4336} 4337 4338// deprecated 4339const char * llama_token_get_text (const struct llama_vocab * vocab ,llama_token token ) { 4340return llama_vocab_get_text (vocab ,token ); 4341} 4342 4343// deprecated 4344float llama_token_get_score (const struct llama_vocab * vocab ,llama_token token ) { 4345return llama_vocab_get_score (vocab ,token ); 4346} 4347 4348// deprecated 4349enum llama_token_attr llama_token_get_attr (const struct llama_vocab * vocab ,llama_token token ) { 4350return llama_vocab_get_attr (vocab ,token ); 4351} 4352 4353// deprecated 4354bool llama_token_is_eog (const struct llama_vocab * vocab ,llama_token token ) { 4355return llama_vocab_is_eog (vocab ,token ); 4356} 4357 4358// deprecated 4359bool llama_token_is_control (const struct llama_vocab * vocab ,llama_token token ) { 4360return llama_vocab_is_control (vocab ,token ); 4361} 4362 4363// deprecated 4364llama_token llama_token_bos (const struct llama_vocab * vocab ) { 4365return llama_vocab_bos (vocab ); 4366} 4367 4368// deprecated 4369llama_token llama_token_eos (const struct llama_vocab * vocab ) { 4370return llama_vocab_eos (vocab ); 4371} 4372 4373// deprecated 4374llama_token llama_token_eot (const struct llama_vocab * vocab ) { 4375return llama_vocab_eot (vocab ); 4376} 4377 4378// deprecated 4379llama_token llama_token_cls (const struct llama_vocab * vocab ) { 4380//return llama_vocab_cls(vocab); 4381return llama_vocab_bos (vocab );// avoid deprecation warning 4382} 4383 4384// deprecated 4385llama_token llama_token_sep (const struct llama_vocab * vocab ) { 4386return llama_vocab_sep (vocab ); 4387} 4388 4389// deprecated 4390llama_token llama_token_nl (const struct llama_vocab * vocab ) { 4391return llama_vocab_nl (vocab ); 4392} 4393 4394// deprecated 4395llama_token llama_token_pad (const struct llama_vocab * vocab ) { 4396return llama_vocab_pad (vocab ); 4397} 4398 4399// deprecated 4400bool llama_add_bos_token (const struct llama_vocab * vocab ) { 4401return llama_vocab_get_add_bos (vocab ); 4402} 4403 4404// deprecated 4405bool llama_add_eos_token (const struct llama_vocab * vocab ) { 4406return llama_vocab_get_add_eos (vocab ); 4407} 4408 4409// deprecated 4410llama_token llama_token_fim_pre (const struct llama_vocab * vocab ) { 4411return llama_vocab_fim_pre (vocab ); 4412} 4413 4414// deprecated 4415llama_token llama_token_fim_suf (const struct llama_vocab * vocab ) { 4416return llama_vocab_fim_suf (vocab ); 4417} 4418 4419// deprecated 4420llama_token llama_token_fim_mid (const struct llama_vocab * vocab ) { 4421return llama_vocab_fim_mid (vocab ); 4422} 4423 4424// deprecated 4425llama_token llama_token_fim_pad (const struct llama_vocab * vocab ) { 4426return llama_vocab_fim_pad (vocab ); 4427} 4428 4429// deprecated 4430llama_token llama_token_fim_rep (const struct llama_vocab * vocab ) { 4431return llama_vocab_fim_rep (vocab ); 4432} 4433 4434// deprecated 4435llama_token llama_token_fim_sep (const struct llama_vocab * vocab ) { 4436return llama_vocab_fim_sep (vocab ); 4437} 4438 4439// 4440// tokenization 4441// 4442 4443int32_t llama_tokenize ( 4444const struct llama_vocab * vocab , 4445const char * text , 4446int32_t text_len , 4447llama_token * tokens , 4448int32_t n_tokens_max , 4449bool add_special , 4450bool parse_special ) { 4451return vocab -> tokenize (text ,text_len ,tokens ,n_tokens_max ,add_special ,parse_special ); 4452} 4453 4454int32_t llama_token_to_piece ( 4455const struct llama_vocab * vocab , 4456llama_token token , 4457char * buf , 4458int32_t length , 4459int32_t lstrip , 4460bool special ) { 4461return vocab -> token_to_piece (token ,buf ,length ,lstrip ,special ); 4462} 4463 4464int32_t llama_detokenize ( 4465const struct llama_vocab * vocab , 4466const llama_token * tokens , 4467int32_t n_tokens , 4468char * text , 4469int32_t text_len_max , 4470bool remove_special , 4471bool unparse_special ) { 4472return vocab -> detokenize (tokens ,n_tokens ,text ,text_len_max ,remove_special ,unparse_special ); 4473}