Team Ai
Datasetpublic

echodict/llama.cpp

version https://git-lfs.github.com/spec/v1 oid sha256:cfc44b7ba25614df70e6b65e3341cae0310163bd32fd31a6b928a542df433faf size 30786

sourceHugging Faceupdated 6mo agoView on Hugging Face
0likes479downloads
llama-vocab.cpp4103 linesDownload Raw Back to src
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 <forward_list>18#include <limits>19#include <map>20#include <queue>21#include <set>22#include <unordered_map>23 24//25// helpers26//27 28struct naive_trie {29    naive_trie() : has_value(false), value(0) {30    }31    void insert(const char * key, size_t len, int32_t value = 0) {32        if (len == 0) {33            this->has_value = true;34            this->value = value;35            return;36        }37        char c = key[0];38        auto res = children.find(c);39        if (res != children.end()) {40            res->second.insert(key + 1, len - 1, value);41        } else {42            auto res = children.insert(std::make_pair(c, naive_trie()));43            res.first->second.insert(key + 1, len - 1, value);44        }45    }46    std::pair<const char *, size_t> get_longest_prefix(const char * key, size_t len, size_t offset = 0) const {47        if (len == 0 || offset == len) {48            return std::make_pair(key, offset);49        }50        char c = key[offset];51        auto res = children.find(c);52        if (res != children.end()) {53            return res->second.get_longest_prefix(key, len, offset + 1);54        }55 56        return std::make_pair(key, offset);57    }58    const struct naive_trie * traverse(const char c) const {59        auto res = children.find(c);60        if (res != children.end()) {61            return &res->second;62        }63 64        return NULL;65    }66    std::map<char, struct naive_trie> children;67    bool has_value;68    llama_token value;69};70 71//72// tokenizers73//74 75struct llm_tokenizer {76    llm_tokenizer() {}77    virtual ~llm_tokenizer() = default;78};79 80struct llm_symbol {81    using index = int;82    index prev;83    index next;84    const char * text;85    size_t n;86};87 88static_assert(std::is_trivially_copyable<llm_symbol>::value, "llm_symbol is not trivially copyable");89 90//91// SPM tokenizer92// original implementation:93// https://github.com/ggml-org/llama.cpp/commit/074bea2eb1f1349a0118239c4152914aecaa1be494//95 96struct llm_bigram_spm {97    struct comparator {98        bool operator()(llm_bigram_spm & l, llm_bigram_spm & r) {99            return (l.score < r.score) || (l.score == r.score && l.left > r.left);100        }101    };102    using queue_storage = std::vector<llm_bigram_spm>;103    using queue = std::priority_queue<llm_bigram_spm, queue_storage, comparator>;104    llm_symbol::index left;105    llm_symbol::index right;106    float score;107    size_t size;108};109 110struct llm_tokenizer_spm : llm_tokenizer {111    llm_tokenizer_spm(const llama_vocab & /*vocab*/) {}112};113 114struct llm_tokenizer_spm_session {115    llm_tokenizer_spm_session(const llama_vocab & vocab) : vocab(vocab) {}116 117    void tokenize(const std::string & text, std::vector<llama_token> & output) {118        // split string into utf8 chars119        int index = 0;120        size_t offs = 0;121        while (offs < text.size()) {122            llm_symbol sym;123            size_t len = unicode_len_utf8(text[offs]);124            sym.text = text.c_str() + offs;125            sym.n = std::min(len, text.size() - offs);126            offs += sym.n;127            sym.prev = index - 1;128            sym.next = offs == text.size() ? -1 : index + 1;129            index++;130            symbols.emplace_back(sym);131        }132 133        // seed the work queue with all possible 2-character tokens.134        for (int i = 1; i < (int) symbols.size(); ++i) {135            try_add_bigram(i - 1, i);136        }137 138        // keep substituting the highest frequency pairs for as long as we can.139        while (!work_queue.empty()) {140            auto bigram = work_queue.top();141            work_queue.pop();142 143            auto & left_sym = symbols[bigram.left];144            auto & right_sym = symbols[bigram.right];145 146            // if one of the symbols already got merged, skip it.147            if (left_sym.n == 0 || right_sym.n == 0 ||148                left_sym.n + right_sym.n != bigram.size) {149                continue;150            }151 152            // merge the right sym into the left one153            left_sym.n += right_sym.n;154            right_sym.n = 0;155 156            //LLAMA_LOG_INFO("left = '%*s' size = %zu\n", (int) left_sym.n, left_sym.text, bigram.size);157 158            // remove the right sym from the chain159            left_sym.next = right_sym.next;160            if (right_sym.next >= 0) {161                symbols[right_sym.next].prev = bigram.left;162            }163 164            // find more substitutions165            try_add_bigram(left_sym.prev, bigram.left);166            try_add_bigram(bigram.left, left_sym.next);167        }168 169        for (int i = 0; i != -1; i = symbols[i].next) {170            auto & symbol = symbols[i];171            resegment(symbol, output);172        }173    }174 175private:176    void resegment(llm_symbol & symbol, std::vector<llama_token> & output) {177        auto text = std::string(symbol.text, symbol.n);178        auto token = vocab.text_to_token(text);179 180        // Do we need to support is_unused?181        if (token != LLAMA_TOKEN_NULL) {182            output.push_back(token);183            return;184        }185 186        const auto p = rev_merge.find(text);187 188        if (p == rev_merge.end()) {189            // output any symbols that did not form tokens as bytes.190            output.reserve(output.size() + symbol.n);191            for (int j = 0; j < (int)symbol.n; ++j) {192                llama_token id = vocab.byte_to_token(symbol.text[j]);193                output.push_back(id);194            }195            return;196        }197 198        resegment(symbols[p->second.first], output);199        resegment(symbols[p->second.second], output);200    }201 202    void try_add_bigram(int left, int right) {203        if (left == -1 || right == -1) {204            return;205        }206        const std::string text = std::string(symbols[left].text, symbols[left].n + symbols[right].n);207        auto token = vocab.text_to_token(text);208 209        if (token == LLAMA_TOKEN_NULL) {210            return;211        }212 213        if (static_cast<uint32_t>(token) >= vocab.n_tokens()) {214            return;215        }216 217        const auto & tok_data = vocab.get_token_data(token);218 219        llm_bigram_spm bigram;220        bigram.left  = left;221        bigram.right = right;222        bigram.score = tok_data.score;223        bigram.size  = text.size();224 225        work_queue.push(bigram);226 227        // Do we need to support is_unused?228        rev_merge[text] = std::make_pair(left, right);229    }230 231    const llama_vocab & vocab;232    // currently unused233    // const llm_tokenizer_spm * spm_tokenizer;234 235    std::vector<llm_symbol> symbols;236    llm_bigram_spm::queue work_queue;237    std::map<std::string, std::pair<int, int>> rev_merge;238};239 240//241// BPE tokenizer242// adapted from https://github.com/cmp-nct/ggllm.cpp [MIT License]243// tried to simplify unicode stuff, so most likely does not work 100% correctly!244//245 246// TODO: there are a lot of common parts between spm and bpe tokenizers, should be refactored and reused247 248template<typename T, typename Container = std::vector<T>, typename Compare = std::less<typename Container::value_type>>249class llama_priority_queue : public std::priority_queue<T, Container, Compare> {250public:251    using std::priority_queue<T, Container, Compare>::priority_queue;252 253    T pop_move() {254        T item = std::move(this->c.front());255        std::pop_heap(this->c.begin(), this->c.end(), this->comp);256        this->c.pop_back();257        return item;258    }259 260    void pop() =  delete;261};262 263struct llm_bigram_bpe {264    struct comparator {265        bool operator()(const llm_bigram_bpe & l, const llm_bigram_bpe & r) const {266            return l.rank > r.rank || (l.rank == r.rank && l.left > r.left);267        }268    };269 270    using queue_storage = std::vector<llm_bigram_bpe>;271    using queue = llama_priority_queue<llm_bigram_bpe, queue_storage, comparator>;272    llm_symbol::index left;273    llm_symbol::index right;274    std::string text;275    int rank;276    size_t size;277};278 279struct llm_tokenizer_bpe : llm_tokenizer {280    llm_tokenizer_bpe(const llama_vocab & vocab) {281        GGML_ASSERT(vocab.get_type() == LLAMA_VOCAB_TYPE_BPE);282        switch (vocab.get_pre_type()) {283            case LLAMA_VOCAB_PRE_TYPE_LLAMA3:284                regex_exprs = {285                    // original regex from tokenizer.json286                    //"(?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+",287 288                    // adapted: https://github.com/ggml-org/llama.cpp/pull/6920#issuecomment-2080233989289                    "(?:'[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+",290                };291                break;292            case LLAMA_VOCAB_PRE_TYPE_JAIS2:293                regex_exprs = {294                    // original regex from tokenizer.json295                    //"(?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}",296 297                    // adapted: same as llama3 but with cascading whitespace pattern298                    "(?:'[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}",299                };300                break;301            case LLAMA_VOCAB_PRE_TYPE_DBRX:302            case LLAMA_VOCAB_PRE_TYPE_SMAUG:303                regex_exprs = {304                    // same as llama3305                    "(?:'[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+",306                };307                break;308            case LLAMA_VOCAB_PRE_TYPE_DEEPSEEK_LLM:309                regex_exprs = {310                    "[\r\n]",311                    "\\s?[A-Za-zµÀ-ÖØ-öø-ƺƼ-ƿDŽ-ʓʕ-ʯͰ-ͳͶͷͻ-ͽͿΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԯԱ-ՖႠ-ჅᎠ-Ᏽᏸ-ᏽᲐ-ᲺᲽ-Ჿᴀ-ᴫᵫ-ᵷᵹ-ᶚḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼℂℇℊ-ℓℕℙ-ℝℤΩℨK-ℭℯ-ℴℹℼ-ℿⅅ-ⅉⅎↃↄⰀ-ⱻⱾ-ⳤⳫ-ⳮⳲⳳꙀ-ꙭꚀ-ꚛꜢ-ꝯꝱ-ꞇꞋ-ꞎꭰ-ꮿff-stﬓ-ﬗA-Za-z𐐀-𐑏𐒰-𐓓𐓘-𐓻𐲀-𐲲𐳀-𐳲𑢠-𑣟𞤀-𞥃]+",312                    "\\s?[!-/:-~!-/:-~‘-‟ -。]+",313                    "\\s+$",314                    "[一-龥ࠀ-一가-퟿]+",315                    "\\p{N}+",316                };317                break;318            case LLAMA_VOCAB_PRE_TYPE_DEEPSEEK3_LLM:319            case LLAMA_VOCAB_PRE_TYPE_HUNYUAN_DENSE:320            case LLAMA_VOCAB_PRE_TYPE_JOYAI_LLM:321                regex_exprs = {322                    "\\p{N}{1,3}",323                    "[一-龥぀-ゟ゠-ヿ]+",324                    "[!\"#$%&'()*+,\\-./:;<=>?@\\[\\\\\\]^_`{|}~][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+",325                };326                break;327            case LLAMA_VOCAB_PRE_TYPE_YOUTU:328                regex_exprs = {329                    "[가-힣ㄱ-ㆎ]+|[!…“”‘’—:;,、-〿︰-﹏]+|[ㄅ-ㄯ]+|[一-龥぀-ゟ゠-ヿ]+",330                    "[^\\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+",331                };332                break;333            case LLAMA_VOCAB_PRE_TYPE_DEEPSEEK_CODER:334                regex_exprs = {335                    "[\r\n]",336                    "\\s?\\p{L}+",337                    "\\s?\\p{P}+",338                    "[一-龥ࠀ-一가-퟿]+",339                    "\\p{N}",340                };341                break;342            case LLAMA_VOCAB_PRE_TYPE_FALCON:343                regex_exprs = {344                    "[\\p{P}\\$\\+<=>\\^~\\|`]+",345                    "'s|'t|'re|'ve|'m|'ll|'d| ?\\p{L}+| ?\\p{N}+| ?[^\\s\\p{L}\\p{N}]+|\\s+(?!\\S)",346                    "[0-9][0-9][0-9]",347                };348                break;349            case LLAMA_VOCAB_PRE_TYPE_STARCODER:350            case LLAMA_VOCAB_PRE_TYPE_REFACT:351            case LLAMA_VOCAB_PRE_TYPE_COMMAND_R:352            case LLAMA_VOCAB_PRE_TYPE_SMOLLM:353            case LLAMA_VOCAB_PRE_TYPE_CODESHELL:354            case LLAMA_VOCAB_PRE_TYPE_EXAONE:355            case LLAMA_VOCAB_PRE_TYPE_MINERVA:356                regex_exprs = {357                    "\\p{N}",358                    "'s|'t|'re|'ve|'m|'ll|'d| ?\\p{L}+| ?\\p{N}+| ?[^\\s\\p{L}\\p{N}]+|\\s+(?!\\S)",359                };360                break;361            case LLAMA_VOCAB_PRE_TYPE_GPT2:362            case LLAMA_VOCAB_PRE_TYPE_MPT:363            case LLAMA_VOCAB_PRE_TYPE_OLMO:364            case LLAMA_VOCAB_PRE_TYPE_JAIS:365            case LLAMA_VOCAB_PRE_TYPE_TRILLION:366            case LLAMA_VOCAB_PRE_TYPE_GRANITE_DOCLING:367                regex_exprs = {368                    "'s|'t|'re|'ve|'m|'ll|'d| ?\\p{L}+| ?\\p{N}+| ?[^\\s\\p{L}\\p{N}]+|\\s+(?!\\S)",369                };370                break;371            case LLAMA_VOCAB_PRE_TYPE_STABLELM2:372            case LLAMA_VOCAB_PRE_TYPE_QWEN2:373            case LLAMA_VOCAB_PRE_TYPE_HUNYUAN:374            case LLAMA_VOCAB_PRE_TYPE_SOLAR_OPEN:375                regex_exprs = {376                    // original regex from tokenizer.json377                    // "(?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+"378                    "(?:'[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+",379                };380                break;381            case LLAMA_VOCAB_PRE_TYPE_QWEN35:382                regex_exprs = {383                    // original regex from tokenizer.json384                    // "(?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+"385                    "(?:'[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+",386                };387                break;388            case LLAMA_VOCAB_PRE_TYPE_PORO:389            case LLAMA_VOCAB_PRE_TYPE_BLOOM:390            case LLAMA_VOCAB_PRE_TYPE_GPT3_FINNISH:391                regex_exprs = {392                    " ?[^(\\s|.,!?…。,、।۔،)]+",393                };394                break;395            case LLAMA_VOCAB_PRE_TYPE_CHATGLM4:396                regex_exprs = {397                    "(?:'[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+",398                };399                break;400            case LLAMA_VOCAB_PRE_TYPE_VIKING:401                regex_exprs = {402                    " ?[^(\\s|.,!?…。,、।۔،)]+",403                    "\\p{N}",404                };405                break;406            case LLAMA_VOCAB_PRE_TYPE_TEKKEN:407                // original regex from tokenizer.json408                // "[^\\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+"409                regex_exprs = {410                    "[^\\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+",411                };412                break;413            case LLAMA_VOCAB_PRE_TYPE_CHAMELEON:414                // Note: in theory, the special token (sentinel and image token) regex_exprs below415                // are unnecessary, as they are split in `tokenizer_st_partition` anyway.416                // However, since the upstream pre-tokenizer uses them, they are also417                // included here (see https://huggingface.co/facebook/chameleon-7b).418                regex_exprs = {419                    "<sentinel:[0-9]+>",  // Sentinel tokens420                    "(IMGIMG)((A|B|C|D|E|F|G|H|I){1,4})Z",  // Image tokens421                    "([\\t\\n]|    |  )",  // directly from tokenizer.json422                    "\\p{N}", // Individual digits423                    "[\\p{P}!-/:-@\\[-`{-~]",  // Punctuation, Isolated424                    "'s|'t|'re|'ve|'m|'ll|'d| ?\\p{L}+| ?\\p{N}+| ?[^\\s\\p{L}\\p{N}]+|\\s+(?!\\S)",425                };426                break;427            case LLAMA_VOCAB_PRE_TYPE_GPT4O:428            case LLAMA_VOCAB_PRE_TYPE_MINIMAX_M2:429                regex_exprs = {430                    // original regex from tokenizer.json431                    // "[^\\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+",432                    "[^\\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+",433                };434                break;435            case LLAMA_VOCAB_PRE_TYPE_TINY_AYA:436                regex_exprs = {437                    // original regex from tokenizer.json: "\\d{1,3}(?=(?:\\d{3})*\\b)"438                    "\\d{1,3}(?=(?:\\d{3})*\\b)",439                    // 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+"440                    "[^\\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+",441                };442                break;443            case LLAMA_VOCAB_PRE_TYPE_KIMI_K2:444                regex_exprs = {445                    // K2 trigger pattern - this will activate the custom K2 handler in unicode.cpp446                    // The custom handler implements all K2 patterns with proper Han character exclusion447                    "\\p{Han}+",448                };449                break;450            case LLAMA_VOCAB_PRE_TYPE_SUPERBPE:451                regex_exprs = {452                    "\\p{N}+",453                    "(?=(\\d{3})+(?!\\d))",454                };455                break;456            case LLAMA_VOCAB_PRE_TYPE_BAILINGMOE:457                regex_exprs = {458                    // original regex from tokenizer.json459                    // "'(?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+"460                    // FIXME? Changed possessive quantifiers (?+ and ++) to greedy to avoid errors and imatrix hanging (tried atomic grouping but it's not supported?)461                    "'(?:[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+",462                };463                break;464            case LLAMA_VOCAB_PRE_TYPE_SEED_CODER:465                regex_exprs = {466                    // original regex from tokenizer.json467                    // "(?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+"468                    "(?:'[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+",469                };470                break;471            case LLAMA_VOCAB_PRE_TYPE_GROK_2:472                regex_exprs = {473                    // original regex from tokenizer.json474                    // "(?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+"475                    "(?:'[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+",476                };477                break;478            case LLAMA_VOCAB_PRE_TYPE_AFMOE:479                regex_exprs = {480                    // Digit handling - uses custom implementation in unicode.cpp481                    // Groups digits with leading 1-2 based on total length modulo 3482                    "\\p{AFMoE_digits}",483                    // CJK and Asian scripts (using direct Unicode literals)484                    "[一-鿿㐀-䶿豈-﫿぀-ゟ゠-ヿ・-゚⼀-⿟เ-๿຀-໿ក-៿က-႟ꩠ-ꩿꧠ-꧿가-힯ᄀ-ᇿ]+",485                    // Main BPE pattern486                    "[!\"#$%&'()*+,\\-./:;<=>?@\\[\\\\\\]^_`{|}~][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+",487                };488                break;489            case LLAMA_VOCAB_PRE_TYPE_EXAONE_MOE:490                regex_exprs = {491                    // original regex from tokenizer.json492                    // "(?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+"493                    "(?:'[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+",494                };495                break;496            case LLAMA_VOCAB_PRE_TYPE_GEMMA4:497                // Gemma4 uses SPM-style BPE: spaces are replaced with ▁ by the498                // normalizer, then BPE merges run on the whole text without499                // word-level pre-splitting. We only need to split on newlines500                // since BPE merge lookup asserts no newlines in tokens.501                regex_exprs = {502                    "[^\\n]+|[\\n]+",503                };504                byte_encode = false; // uses raw UTF-8, not GPT-2 byte encoding505                break;506            default:507                // default regex for BPE tokenization pre-processing508                regex_exprs = {509                    "[\\p{P}\\$\\+<=>\\^~\\|]+",510                    "'s|'t|'re|'ve|'m|'ll|'d| ?\\p{L}+| ?\\p{N}+| ?[^\\s\\p{L}\\p{N}]+|\\s+(?!\\S)",511                    "\\p{N}+",512                    "[0-9][0-9][0-9]",513                };514                break;515        }516    }517 518    std::vector<std::string> regex_exprs;519    bool byte_encode = true; // GPT-2 byte encoding; false for SPM-style BPE (raw UTF-8)520};521 522struct llm_tokenizer_bpe_session {523    llm_tokenizer_bpe_session(const llama_vocab & vocab, const llm_tokenizer_bpe & tokenizer) : vocab(vocab), tokenizer(tokenizer) {}524 525    static void append(const llama_token token_id, std::vector<llama_token> & output)  {526        output.push_back(token_id);527    }528 529    bool append_bos(std::vector<llama_token> & output) const {530        if (vocab.get_add_bos()) {531            GGML_ASSERT(vocab.token_bos() != LLAMA_TOKEN_NULL);532            output.push_back(vocab.token_bos());533            return true;534        }535        return false;536    }537 538    bool append_eos(std::vector<llama_token> & output) const {539        if (vocab.get_add_eos()) {540            GGML_ASSERT(vocab.token_eos() != LLAMA_TOKEN_NULL);541            output.push_back(vocab.token_eos());542            return true;543        }544        return false;545    }546 547    void check_double_bos_eos(const std::vector<llama_token> & output) const {548        if (vocab.get_add_bos() && output.size() >= 2 && output[1] == vocab.token_bos()) {549            LLAMA_LOG_WARN(550                "%s: Added a BOS token to the prompt as specified by the model but the prompt "551                "also starts with a BOS token. So now the final prompt starts with 2 BOS tokens. "552                "Are you sure this is what you want?\n", __FUNCTION__);553        }554        if (vocab.get_add_eos() && output.size() >= 2 && *(output.end()-2) == vocab.token_eos()) {555            LLAMA_LOG_WARN(556                "%s: Added a EOS token to the prompt as specified by the model but the prompt "557                "also ends with a EOS token. So now the final prompt ends with 2 EOS tokens. "558                "Are you sure this is what you want?\n", __FUNCTION__);559        }560    }561 562    void tokenize(const std::string & text, std::vector<llama_token> & output) {563        int final_prev_index = -1;564        const auto word_collection = unicode_regex_split(text, tokenizer.regex_exprs, tokenizer.byte_encode);565 566        symbols_final.clear();567        auto tok_pre = vocab.get_pre_type();568 569        for (const auto & word : word_collection) {570            work_queue = llm_bigram_bpe::queue();571            symbols.clear();572 573            int index = 0;574            size_t offset = 0;575 576            //if (vocab.tokenizer_ignore_merges && vocab.token_to_id.find(word) != vocab.token_to_id.end()) {577            if (vocab.get_ignore_merges() && vocab.text_to_token(word) != LLAMA_TOKEN_NULL) {578                symbols.emplace_back(llm_symbol{-1, -1, word.c_str(), word.size()});579                offset = word.size();580            } else if (tok_pre == LLAMA_VOCAB_PRE_TYPE_GEMMA4 && word.find_first_not_of('\n') == std::string::npos) {581                // fix for gemma 4, ref: https://github.com/ggml-org/llama.cpp/pull/21343582                auto tok = vocab.text_to_token(word);583                if (tok != LLAMA_TOKEN_NULL) {584                    symbols.emplace_back(llm_symbol{-1, -1, word.c_str(), word.size()});585                    offset = word.size();586                }587            }588 589            while (offset < word.size()) {590                llm_symbol sym;591                size_t char_len = std::min(word.size() - offset, (size_t) unicode_len_utf8(word[offset]));592                sym.text = word.c_str() + offset;593                sym.n = char_len;594                offset += sym.n;595                sym.prev = index - 1;596                sym.next = offset == word.size() ? -1 : index + 1;597                index++;598                symbols.emplace_back(sym);599            }600            for (int i = 1; i < (int) symbols.size(); ++i) {601                add_new_bigram(i - 1, i);602            }603 604            // build token(s)605            while (!work_queue.empty()) {606                auto bigram = work_queue.pop_move();607 608                auto & left_symbol = symbols[bigram.left];609                auto & right_symbol = symbols[bigram.right];610 611                if (left_symbol.n == 0 || right_symbol.n == 0) {612                    continue;613                }614                std::string left_token = std::string(left_symbol.text, left_symbol.n);615                std::string right_token = std::string(right_symbol.text, right_symbol.n);616                if (left_token + right_token != bigram.text) {617                    continue;  // Skip this bigram if it's outdated618                }619 620                // merge the right sym into the left one621                left_symbol.n += right_symbol.n;622                right_symbol.n = 0;623 624                // remove the right sym from the chain625                left_symbol.next = right_symbol.next;626                if (right_symbol.next >= 0) {627                    symbols[right_symbol.next].prev = bigram.left;628                }629 630                add_new_bigram(left_symbol.prev, bigram.left);  // left side of current symbol631                add_new_bigram(bigram.left, left_symbol.next);  // right side of current symbol632            }633 634            // add the finished tokens to the final list keeping correct order for next and prev635            for (auto & sym : symbols) {636                if (sym.n > 0) {637                    sym.prev = final_prev_index;638                    sym.next = -1;639                    if (final_prev_index != -1) {640                        symbols_final[final_prev_index].next = symbols_final.size();641                    }642                    symbols_final.emplace_back(sym);643                    final_prev_index = symbols_final.size() - 1;644                }645            }646        }647 648        symbols = symbols_final;649 650        if (!symbols.empty()) {651            for (int i = 0; i != -1; i = symbols[i].next) {652                auto & symbol = symbols[i];653                if (symbol.n == 0) {654                    continue;655                }656 657                const std::string str = std::string(symbol.text, symbol.n);658                const auto token = vocab.text_to_token(str);659 660                if (token == LLAMA_TOKEN_NULL) {661                    for (auto j = str.begin(); j != str.end(); ++j) {662                        llama_token token_multibyte = LLAMA_TOKEN_NULL;663                        if (tokenizer.byte_encode) {664                            std::string byte_str(1, *j);665                            token_multibyte = vocab.text_to_token(byte_str);666                        } else {667                            // For non-byte-encoded BPE (e.g. gemma-4), byte tokens use <0xXX> format668                            static const char * hex = "0123456789ABCDEF";669                            const uint8_t ch = (uint8_t)*j;670                            const char buf[7] = { '<', '0', 'x', hex[ch >> 4], hex[ch & 15], '>', 0 };671                            token_multibyte = vocab.text_to_token(buf);672                        }673                        if (token_multibyte != LLAMA_TOKEN_NULL) {674                            output.push_back(token_multibyte);675                        }676                    }677                } else {678                    output.push_back(token);679                }680            }681        }682    }683 684private:685    void add_new_bigram(int left, int right) {686        if (left == -1 || right == -1) {687            return;688        }689        std::string left_token  = std::string(symbols[left].text,  symbols[left].n);690        std::string right_token = std::string(symbols[right].text, symbols[right].n);691 692        int rank_found = -1;693 694        rank_found = vocab.find_bpe_rank(left_token, right_token);695 696        if (rank_found < 0) {697            return;698        }699 700        llm_bigram_bpe bigram;701 702        bigram.left  = left;703        bigram.right = right;704        bigram.text  = left_token + right_token;705        bigram.size  = left_token.size() + right_token.size();706        bigram.rank  = rank_found;707 708        work_queue.push(bigram);709    }710 711    const llama_vocab & vocab;712    const llm_tokenizer_bpe & tokenizer;713 714    std::vector<llm_symbol> symbols;715    std::vector<llm_symbol> symbols_final;716    llm_bigram_bpe::queue work_queue;717};718 719//720// WPM tokenizer721//722 723struct llm_tokenizer_wpm : llm_tokenizer {724    llm_tokenizer_wpm(const llama_vocab & /*vocab*/) {}725};726 727struct llm_tokenizer_wpm_session {728    llm_tokenizer_wpm_session(const llama_vocab & vocab) : vocab(vocab) {}729 730    void tokenize(const std::string & text, std::vector<llama_token> & output) {731        // normalize and split by whitespace732        std::vector<std::string> words = preprocess(text);733        // bos token prepended already734 735        // find the longest tokens that form the words736        for (const std::string & word : words) {737            // skip empty words738            if (word.size() == 0) {739                continue;740            }741 742            // prepend phantom space743            const std::string word1 = "\xe2\x96\x81" + word;744            const int n = word1.size();745 746            const size_t current_tokens = output.size();747 748            // we're at the start of a new word749            // move through character position in word750            for (int i = 0; i < n; ++i) {751                // loop through possible match length752                bool match = false;753                for (int j = std::min(n, i + vocab.max_token_len() + 1); j > i; j--) {754                    auto id = vocab.text_to_token(word1.substr(i, j - i));755                    if (id != LLAMA_TOKEN_NULL) {756                        output.push_back(id);757                        match = true;758                        i = j - 1;759                        break;760                    }761                }762 763                if (!match) { // discard all764                    output.resize(current_tokens);765                    break;  // and discard next tokens766                }767            }768 769            // we didn't find any matches for this word770            if (current_tokens == output.size()) {771                output.push_back(vocab.token_unk());772            }773        }774    }775 776    // TODO: reduce string copies by using cpts_offs array777    static std::vector<std::string> preprocess(const std::string & text)  {778        const std::vector<uint32_t> cpts_nfd = unicode_cpts_normalize_nfd(unicode_cpts_from_utf8(text));779        std::vector<std::string> words(1, "");780 781        for (const uint32_t cpt : cpts_nfd) {782            const auto flags = unicode_cpt_flags_from_cpt(cpt);783 784            if (flags.is_whitespace) {785                if (words.back().size()) {  // finish previous word if any786                    words.emplace_back();787                }788                continue;789            }790 791            assert (!flags.is_separator);792            if (cpt == 0 || cpt == 0xFFFD || flags.is_control) {793                continue;794            }795 796            const std::string s = unicode_cpt_to_utf8(unicode_tolower(cpt));797            if (flags.is_punctuation || ( cpt < 0x7F && flags.is_symbol ) || is_chinese_char(cpt)) {798                if (words.back().size()) {  // finish previous word if any799                    words.emplace_back();800                }801                words.back() = s;       // single char word802                words.emplace_back();   // start a new word803            } else {804                words.back() += s;  // append char to word805            }806        }807 808        if (!words.back().size()) {809            words.pop_back();810        }811 812        return words;813    }814 815    static bool is_chinese_char(uint32_t cpt) {816        return817            (cpt >= 0x04E00 && cpt <= 0x09FFF) ||818            (cpt >= 0x03400 && cpt <= 0x04DBF) ||819            (cpt >= 0x20000 && cpt <= 0x2A6DF) ||820            (cpt >= 0x2A700 && cpt <= 0x2B73F) ||821            (cpt >= 0x2B740 && cpt <= 0x2B81F) ||822            (cpt >= 0x2B920 && cpt <= 0x2CEAF) || // this should be 0x2B820 but in hf rust code it is 0x2B920823            (cpt >= 0x0F900 && cpt <= 0x0FAFF) ||824            (cpt >= 0x2F800 && cpt <= 0x2FA1F);825            //(cpt >= 0x3000  && cpt <= 0x303F)  ||826            //(cpt >= 0xFF00  && cpt <= 0xFFEF);827    }828 829private:830    const llama_vocab & vocab;831    // currently unused832    // const llm_tokenizer_wpm * wpm_tokenizer;833};834 835//836// UGM tokenizer837//838 839struct llm_tokenizer_ugm : llm_tokenizer {840    llm_tokenizer_ugm(const llama_vocab & vocab, const std::vector<char> & precompiled_charsmap) {841        if (precompiled_charsmap.size() > 0) {842            size_t charsmap_offset = 0;843 844            // First four bytes of precompiled_charsmap contains length of binary845            // blob containing XOR-compressed compact double array (XCDA) entries846            uint32_t xcda_blob_size = *(const uint32_t *) &precompiled_charsmap[0];847            charsmap_offset += sizeof(xcda_blob_size);848            if (xcda_blob_size + charsmap_offset >= precompiled_charsmap.size()) {849                throw std::runtime_error("Index out of array bounds in precompiled charsmap!");850            }851 852            // Next xcda_blob_size bytes contain entries of XOR-compressed compact853            // double array (XCDA). Each entry is bit-packed into a 32-bit integer.854            xcda_array = (const uint32_t *) &precompiled_charsmap[charsmap_offset];855            xcda_array_size = xcda_blob_size / sizeof(uint32_t);856            charsmap_offset += xcda_blob_size;857 858            // Remaining bytes of precompiled charsmap contain null-terminated859            // replacement strings for prefixes matched by the XCDA.860            prefix_replacements = &precompiled_charsmap[charsmap_offset];861            prefix_replacements_size = precompiled_charsmap.size() - charsmap_offset;862        }863 864        for (uint32_t id = 0; id < vocab.n_tokens(); ++id) {865            const auto & token_data = vocab.get_token_data(id);866 867            if (vocab.is_normal(id)) {868                min_score = std::min<float>(min_score, token_data.score);869                max_score = std::max<float>(max_score, token_data.score);870            }871 872            if (vocab.is_normal(id) ||873                vocab.is_user_defined(id) ||874                vocab.is_unused(id)) {875                token_matcher.insert(token_data.text.data(), token_data.text.size(), id);876            }877 878            if (vocab.is_user_defined(id)) {879                user_defined_token_matcher.insert(token_data.text.data(), token_data.text.size());880            }881        }882 883        unknown_token_score = min_score - unknown_token_score_penalty;884    }885 886    // escaped space symbol - U+2581 (Lower One Eighth Block)887    const std::string escaped_space = "\xE2\x96\x81";888 889    const char * prefix_replacements = NULL;890    size_t prefix_replacements_size = 0;891 892    const uint32_t * xcda_array = NULL;893    size_t xcda_array_size = 0;894 895    struct naive_trie user_defined_token_matcher;896 897    float min_score = FLT_MAX;898    float max_score = -FLT_MAX;899 900    float unknown_token_score_penalty = 10.0;901    float unknown_token_score;902 903    struct naive_trie token_matcher;904};905 906struct llm_tokenizer_ugm_session {907    llm_tokenizer_ugm_session(const llama_vocab & vocab, const llm_tokenizer_ugm & tokenizer) : vocab(vocab), tokenizer(tokenizer) {}908 909    /* This implementation is based on SentencePiece optimized Viterbi algorithm for910     * unigram language models. The general idea is to:911     * - move along the input sequence in steps of one UTF code point,912     * - at each step find all possible tokenizations of the prefix by913     *   traversing the tokens trie,914     * - for each tokenization store the best one so far (by higher score)915     * - use the position in sequence after given token as an index to store916     *   results917     * - if there was no valid tokenization of the current UTF code point918     *   then use unknown token with additional score penalty919     * After processing the whole sequence we backtrack from the end to get920     * the best tokenization.921    */922    void tokenize(const std::string & text, std::vector<llama_token> & output) {923        // get current size of output (for reversal later)924        size_t output_size = output.size();925 926        // normalize the input first927        std::string normalized;928        normalize(text, &normalized);929        size_t input_len = normalized.size();930        if (input_len == 0) {931            return;932        }933 934        // initialize score_sum to -FLT_MAX so it will be always lower than sums of token scores935        std::vector<struct best_tokenization> tokenization_results(input_len + 1, {vocab.token_unk(), 0, -DBL_MAX});936        // at the beginning tokenization score is zero937        tokenization_results[0] = { vocab.token_unk(), 0, 0 };938 939        for (size_t input_offset = 0; input_offset < input_len;) {940            size_t prefix_offset = input_offset;941            // calculate how many code units are in the currently processed UTF code point942            size_t n_utf8_code_units = std::min<size_t>(unicode_len_utf8(normalized[input_offset]), input_len - input_offset);943 944            // traverse the token matcher trie to find a matching token945            bool single_codepoint_token_found = false;946            const struct best_tokenization & current_best = tokenization_results[input_offset];947            const struct naive_trie * node = tokenizer.token_matcher.traverse(normalized[prefix_offset++]);948 949            while (prefix_offset <= input_len && node != NULL) {950                // check if we found valid token in prefix951                if (node->has_value) {952                    // check if it corresponds to the whole UTF code point953                    if (prefix_offset - input_offset == n_utf8_code_units) {954                        single_codepoint_token_found = true;955                    }956                    llama_token token_id = node->value;957                    const auto & token_data = vocab.get_token_data(token_id);958 959                    // we set the user-defined token scores to 0 to make them more likely to be selected960                    // (normal token scores are log probabilities, so they are negative)961                    // score type is double here to make tokenization results exactly962                    // the same as in the HF tokenizer using SentencePiece963                    const double token_score = vocab.is_user_defined(token_id) ? 0.0 : token_data.score;964                    const double challenger_score = current_best.score_sum + token_score;965                    struct best_tokenization & current_champ = tokenization_results[prefix_offset];966                    if (challenger_score > current_champ.score_sum) {967                        struct best_tokenization challenger = { token_id, input_offset, challenger_score };968                        current_champ = challenger;969                    }970                }971                node = node->traverse(normalized[prefix_offset++]);972            }973 974            // if we didn't find a valid token corresponding to the whole UTF code point975            // then use unknown token as the tokenization of this UTF code point976            if (!single_codepoint_token_found) {977                const double challenger_score = current_best.score_sum + tokenizer.unknown_token_score;978                prefix_offset = input_offset + n_utf8_code_units;979                struct best_tokenization & current_champ = tokenization_results[prefix_offset];980                if (challenger_score > current_champ.score_sum) {981                    struct best_tokenization challenger = { vocab.token_unk(), input_offset, challenger_score };982                    current_champ = challenger;983                }984            }985 986            // move to the next UTF code point987            input_offset += n_utf8_code_units;988        }989 990        // now backtrack from the end to gather token ids of the best tokenization991        // merge sequences of consecutive unknown tokens into single unknown tokens992        bool is_prev_unknown = false;993        for (struct best_tokenization & tokenization = tokenization_results[input_len]; ; tokenization = tokenization_results[tokenization.input_offset]) {994            bool is_unknown = tokenization.token_id == vocab.token_unk();995            if (!(is_prev_unknown && is_unknown)) {996                output.push_back(tokenization.token_id);997            }998            if (tokenization.input_offset == 0) {999                break;1000            }1001            is_prev_unknown = is_unknown;1002        }1003 1004        // reverse the output since we added tokens starting from the end of the input1005        std::reverse(output.begin() + output_size, output.end());1006    }1007 1008private:1009 1010    // helper structure for returning normalization results1011    struct normalization_result {1012        const char * normalized;1013        size_t normalized_len;1014        size_t consumed_input;1015    };1016 1017    void normalize(const std::string& input, std::string * normalized) {1018        normalized->clear();1019        normalized->reserve(input.size() * 3);1020 1021        const std::string space = vocab.get_escape_whitespaces() ? tokenizer.escaped_space : " ";1022 1023        const bool shall_prepend_space = !vocab.get_treat_whitespace_as_suffix() && vocab.get_add_space_prefix();1024        const bool shall_append_space  =  vocab.get_treat_whitespace_as_suffix() && vocab.get_add_space_prefix();1025        const bool shall_merge_spaces  =  vocab.get_remove_extra_whitespaces();1026 1027        bool is_space_prepended = false;1028        bool processing_non_ws = false;1029 1030        size_t input_len = input.size();1031 1032        for (size_t input_offset = 0; input_offset < input_len; ) {1033            auto norm_res = normalize_prefix(input, input_offset);1034            for (size_t i = 0; i < norm_res.normalized_len; i++) {1035                char c = norm_res.normalized[i];1036                if (c != ' ') {1037                    if (!processing_non_ws) {1038                        processing_non_ws = true;1039                        if ((shall_prepend_space && !is_space_prepended) || shall_merge_spaces) {1040                            normalized->append(space);1041                            is_space_prepended = true;1042                        }1043                    }1044                    normalized->push_back(c);1045                } else {1046                    if (processing_non_ws) {1047                        processing_non_ws = false;1048                    }1049                    if (!shall_merge_spaces) {1050                        normalized->append(space);1051                    }1052                }1053            }1054 1055            input_offset += norm_res.consumed_input;1056        }1057 1058        if (shall_append_space) {1059            normalized->append(space);1060        }1061    }1062 1063    /*1064     * This structure is a view wrapper for XOR-compressed double array (XCDA)1065     * See Shunsuke Kanda (2018). Space- and Time-Efficient String Dictionaries.1066     * Each bit-packed entry contains:1067     * - BASE array value in bits 10-301068     * - LCHECK array value in bits 0-71069     * - LEAF array value in bit 91070     * Entries containing indexes of replacement sequences have set bit 311071     */1072    struct xcda_array_view {1073    public:1074        xcda_array_view(const uint32_t * xcda_array, size_t xcda_array_size) : xcda_array(xcda_array), xcda_array_size(xcda_array_size) {1075        }1076        uint32_t get_base(size_t index) {1077            uint32_t packed_node = get_node(index);1078            return (packed_node >> 10) << ((packed_node & (1U << 9)) >> 6);1079        }1080        uint32_t get_lcheck(size_t index) {1081            uint32_t packed_node = get_node(index);1082            return packed_node & ((1U << 31) | 0xff);1083        }1084        bool get_leaf(size_t index) {1085            uint32_t packed_node = get_node(index);1086            return (packed_node >> 8) & 1;1087        }1088        uint32_t get_value(size_t index) {1089            uint32_t packed_node = get_node(index);1090            return packed_node & ((1U << 31) - 1);1091        }1092    private:1093        uint32_t get_node(size_t index) {1094            if (index >= xcda_array_size) {1095                throw std::runtime_error("Index out of array bounds in XCDA array!");1096            }1097            return xcda_array[index];1098        }1099        const uint32_t * xcda_array;1100        size_t xcda_array_size;1101    };1102 1103    // this structure stores the best tokenization so far at input_offset1104    struct best_tokenization {1105        llama_token token_id;1106        size_t input_offset;1107        double score_sum;1108    };1109 1110    struct normalization_result normalize_prefix(const std::string & input, size_t input_offset) {1111        if (input_offset == input.size()) {1112            return { &input[input_offset], 0, 0 };1113        }1114 1115        // if input prefix matches some user-defined token return this token as normalization result1116        auto user_defined_token_match =1117           tokenizer.user_defined_token_matcher.get_longest_prefix(&input[input_offset], input.size() - input_offset);1118        if (user_defined_token_match.second > 0) {1119            return { &input[input_offset], user_defined_token_match.second, user_defined_token_match.second };1120        }1121 1122        size_t longest_prefix_length = 0;1123        size_t longest_prefix_offset = 0;1124 1125        if (tokenizer.xcda_array_size > 0) {1126            struct xcda_array_view xcda_view(tokenizer.xcda_array, tokenizer.xcda_array_size);1127 1128            // Find the longest normalized sequence matching the input prefix by walking1129            // the XOR-compressed compact double array (XCDA) starting from the root node1130            // We find the index of the next node by calculating BASE[s] ^ c where s is1131            // the index of the previous node and c is a numerical character value1132            uint32_t node_index = 0;1133            // get BASE of the root node1134            node_index = xcda_view.get_base(node_index);1135            for (size_t prefix_offset = input_offset; prefix_offset < input.size(); prefix_offset++) {1136                unsigned char c = input[prefix_offset];1137                if (c == 0) {1138                    break;1139                }1140                node_index ^= c;1141                // if value of LCHECK is not c it means that this is not a child of1142                // the previous node, so we stop matching1143                if (xcda_view.get_lcheck(node_index) != c) {1144                    break;1145                }1146                bool is_leaf = xcda_view.get_leaf(node_index);1147                // get BASE of the current node1148                node_index ^= xcda_view.get_base(node_index);1149                // if LEAF of the current node is true, it means that its BASE points to the node1150                // containing index of replacement sequence for currently matched input prefix1151                if (is_leaf)1152                {1153                    longest_prefix_length = prefix_offset - input_offset + 1;1154                    // get index of replacement sequence for currently matched input prefix1155                    longest_prefix_offset = xcda_view.get_value(node_index);1156                }1157            }1158        }1159 1160        if (longest_prefix_length > 0) {1161            // we have a match, so return the replacement sequence1162            if (longest_prefix_offset >= tokenizer.prefix_replacements_size) {1163                throw std::runtime_error("Index out of array bounds in precompiled charsmap!");1164            }1165            const char * prefix_replacement = &(tokenizer.prefix_replacements)[longest_prefix_offset];1166            return { prefix_replacement, strlen(prefix_replacement), longest_prefix_length };1167        }1168 1169        // check if the input prefix contains a valid sequence of UTF-8 code units1170        try {1171            // if yes, return this sequence unmodified1172            size_t prefix_offset = input_offset;1173            unicode_cpt_from_utf8(input, prefix_offset);1174            return { &input[input_offset], prefix_offset - input_offset, prefix_offset - input_offset };1175        } catch (std::invalid_argument & /*ex*/) {1176            // if no, consume 1 byte and return U+FFFD - REPLACEMENT CHARACTER1177            return { "\xEF\xBF\xBD", 3, 1 };1178        }1179    }1180 1181    const llama_vocab & vocab;1182    const llm_tokenizer_ugm & tokenizer;1183};1184 1185//1186// RWKV tokenizer1187//1188 1189static std::vector<uint8_t> llama_unescape_rwkv_token(const std::string & escaped) {1190    std::vector<uint8_t> output;1191    output.reserve(escaped.size());1192 1193    // Parser state1194    bool escaping = false;1195    uint8_t hex_remaining = 0;1196    uint8_t hex_acc = 0;1197 1198    // Step through characters, performing parsing1199    for (const char & c : escaped) {1200        // If we're parsing a hex code, interpret the next character

Showing the first 1,200 of 4103 lines. Download the file for the rest.