9#include <nlohmann/json.hpp>
11#include <boost/regex.hpp>
12#include <boost/xpressive/xpressive.hpp>
15#include <unordered_set>
28using json = nlohmann::json;
41 if (!std::isdigit(
static_cast<unsigned char>(
c))) {
60 const std::string&
text)
const {
67 R
"([\r\n]+|[[:space:]]+|[^\r\n[:space:][:alnum:]]+|[[:alnum:]]+)");
73 if (!match.str(0).empty()) {
93 if (!std::isspace(
static_cast<unsigned char>(
c))) {
102 Logger::debug(
"[BPE_SCORES] Unit '" +
unit_raw +
"' is whitespace. Setting prefix flag for next word.");
117 Logger::debug(
"[BPE_SCORES] Raw unit is newline. It will be split into chars. Current unit_to_bpe: '" +
unit_to_bpe +
"'");
121 std::vector<std::string>
chars;
145 int best_rank = std::numeric_limits<int>::max();
148 for (
size_t i = 0;
i <
chars.size() - 1; ++
i) {
172 const std::vector<std::string>& tokens)
const {
173 std::vector<int> ids;
174 ids.reserve(tokens.size());
176 for (
const auto&
token : tokens) {
179 Logger::debug(
"[TOK_TO_ID_NL_DEBUG] Processing token: '\n' (actual newline char). Length: " + std::to_string(
token.length()));
182 if (
pair.first ==
"\n") {
183 Logger::debug(
"[TOK_TO_ID_NL_DEBUG] Found '\n' key in added_tokens_ map. ID: " + std::to_string(
pair.second));
189 Logger::debug(
"[TOK_TO_ID_NL_DEBUG] '\n' key NOT found in added_tokens_ map by direct string compare.");
191 std::string
keys_in_map =
"Keys in added_tokens_: ";
199 else { std::stringstream
ss_hex;
ss_hex <<
"<0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(
static_cast<unsigned char>(
c_key)) <<
">";
key_escaped +=
ss_hex.str(); }
212 "' -> ID: " + std::to_string(
added_it->second));
216 ids.push_back(
base_it->second);
218 "' -> ID: " + std::to_string(
base_it->second));
226 "[TOK_TO_ID] FALLBACK: Found capitalized base token: '" +
234 if (
token.length() == 1) {
238 ids.push_back(
byte_it->second);
239 Logger::debug(
"[TOK_TO_ID] FALLBACK: Mapped single-byte token '" +
240 std::string(1,
c) +
"' to byte token ID " +
241 std::to_string(
byte_it->second));
248 "' not found in added, base, capitalized fallback, or "
249 "byte tokens. Using UNK ID: " +
259 const std::vector<int>& ids)
const {
260 std::vector<std::string> tokens;
261 tokens.reserve(ids.size());
267 }
else if (
id >= 0 &&
static_cast<size_t>(
id) <
id_to_token_.size()) {
273 "ID " + std::to_string(
id) +
274 " found in base vocab range but has empty string. Using UNK token string: '" +
unk_token_ +
"'.");
288 : tokenizer_family_(
config.tokenizer_family),
292 pad_token_(
"<pad>") {
297 throw std::runtime_error(
"Tokenizer vocab_path (tokenizer.json) does not exist: " +
vocab_json_path_abs.string());
329 std::string
init_log_message =
"Tokenizer successfully initialized from JSON/Config. Detected type based on config: ";
345 " - falling back to space tokenization");
349 "No model path provided - falling back to space tokenization");
351 }
catch (
const std::exception&
e) {
352 std::cerr <<
"Failed to load tokenizer or vocab from " <<
vocab_path <<
": "
353 <<
e.what() << std::endl;
354 Logger::error(std::string(
"Failed to load tokenizer or vocab from \"") +
360 throw std::runtime_error(
361 "Failed to initialize tokenizer vocabulary from: " +
vocab_path);
365 " tokens from vocabulary file: " +
vocab_path);
369 for (
size_t i = 0;
i < std::min((
size_t)10,
id_to_token_.size()); ++
i) {
375 }
else if (
c_tok ==
'\'') {
377 }
else if (std::isprint(
static_cast<unsigned char>(
c_tok))) {
381 ss_hex <<
"<0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(
static_cast<unsigned char>(
c_tok)) <<
">";
391 {
"<|system|>", 32000}, {
"<|user|>", 32001}, {
"<|assistant|>", 32002}};
395 const std::string&
tok =
pair.first;
396 int id =
pair.second;
404 "' with assumed ID: " + std::to_string(
id));
407 "' already loaded from JSON. Skipping injection.");
410 "', assumed ID " + std::to_string(
id) +
411 " clashes with loaded vocab size (" +
418 " missing chat tokens.");
429 Logger::warning(
"Cannot generate BPE merges: empty scores or vocabulary");
433 Logger::info(
"Generating BPE merges from vocabulary and scores for older Llama models...");
441 if (
token.length() > 1 &&
442 token.find(
"<") == std::string::npos &&
443 token.find(
">") == std::string::npos &&
452 [](
const auto&
a,
const auto&
b) { return a.first > b.first; });
507 Logger::info(
"Reached maximum merge limit (50000), stopping generation");
517 : tokenizer_family_(
config.tokenizer_family),
518 initialized_from_gguf_(
true) {
519 Logger::info(
"Initializing Tokenizer from GGUFData...");
527 auto it =
gguf_data.metadata.find(
"tokenizer.chat_template");
529 if (std::holds_alternative<std::string>(
it->second)) {
532 Logger::info(
"[Tokenizer GGUF Init] Found and loaded 'tokenizer.chat_template' from GGUF metadata.");
548 Logger::info(
"[Tokenizer GGUF Init] 'tokenizer.chat_template' found in GGUF metadata but is empty.");
551 Logger::warning(
"[Tokenizer GGUF Init] 'tokenizer.chat_template' found in GGUF metadata but is not a string type.");
554 Logger::info(
"[Tokenizer GGUF Init] 'tokenizer.chat_template' not found in GGUF metadata.");
556 }
catch (
const std::exception&
e) {
557 Logger::error(
"[Tokenizer GGUF Init] Exception while trying to access 'tokenizer.chat_template': " + std::string(
e.what()));
560 if (
gguf_data.tokenizer_tokens.empty()) {
561 throw std::runtime_error(
562 "GGUF data does not contain 'tokenizer.ggml.tokens'");
572 if (
static_cast<int>(
i) == 1734) {
583 ss_hex <<
"<0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(
static_cast<unsigned char>(
c)) <<
">";
592 " tokens from GGUF tokenizer_tokens.");
597 for (
size_t i = 0;
i < std::min((
size_t)10,
id_to_token_.size()); ++
i) {
601 if (std::isprint(
static_cast<unsigned char>(
c_tok))) {
605 ss_hex <<
"<0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(
static_cast<unsigned char>(
c_tok)) <<
">";
617 Logger::info(
"Configuring for LLAMA3_TIKTOKEN (gpt2-style BPE).");
619 if (
gguf_data.tokenizer_merges.empty()) {
620 Logger::warning(
"Llama 3 Tiktoken family specified, but GGUF data does not contain 'tokenizer.ggml.merges'. Tiktoken BPE may not function correctly without explicit merges.");
639 " Tiktoken merges from GGUF tokenizer_merges into bpe_merges_ map with ranks.");
642 if (!
gguf_data.tokenizer_scores.empty()) {
643 Logger::info(
"Llama 3 GGUF contains " + std::to_string(
gguf_data.tokenizer_scores.size()) +
" scores. Loaded.");
666 Logger::debug(
"[DEBUG_VOCAB] Found merge for '" +
p1 +
"' + '" +
p2 +
"' ('" + (
p1+
p2) +
"') with rank: " + std::to_string(
merge_it->second));
675 if (!
gguf_data.tokenizer_scores.empty()) {
679 Logger::warning(
"GGUF (SentencePiece path) token and score array sizes mismatch: tokens=" +
683 Logger::warning(
"SentencePiece family: No scores found. BPE merging will likely not work if no other SP model data is available.");
687 if (!
gguf_data.tokenizer_merges.empty()) {
688 Logger::info(
"SentencePiece family path: Found 'tokenizer.ggml.merges' in GGUF. Loading them into bpe_merges_ map.");
703 " merges from GGUF tokenizer_merges into bpe_merges_ map (SentencePiece path).");
705 Logger::warning(
"SentencePiece family path: No 'tokenizer.ggml.merges' found in GGUF. Attempting to generate merges from vocabulary and scores...");
711 Logger::info(
"Successfully generated " + std::to_string(
bpe_merges_.size()) +
" BPE merges from vocabulary and scores for SentencePiece tokenizer");
713 Logger::warning(
"Failed to generate BPE merges. Tokenization may be suboptimal for this model.");
720 Logger::warning(
"Tokenizer family is UNKNOWN. Tokenizer may not function as expected. Will attempt to load basic vocab and scores if present.");
721 if (!
gguf_data.tokenizer_scores.empty()) {
723 Logger::info(
"Loaded " + std::to_string(
token_scores_.size()) +
" token scores from GGUF for UNKNOWN family as a fallback.");
729 std::transform(
gguf_data.tokenizer_token_types.begin(),
731 [](
unsigned int u) { return static_cast<int32_t>(u); });
744 int token_id =
static_cast<int>(
i);
757 }
catch (
const std::exception&
e) {
762 Logger::warning(
"Token type is BYTE (6) but does not match single char or <0xNN> format: '" +
token_str +
"' ID: " + std::to_string(token_id));
785 Logger::warning(
"No byte tokens identified via token_types metadata for Tiktoken. Attempting fallback scan of vocabulary.");
789 for (
int i = 0;
i < 256; ++
i) {
791 ss_hex_repr <<
"<0x" << std::hex << std::setw(2) << std::setfill(
'0') <<
i <<
">";
798 Logger::debug(
"[BYTE_FALLBACK_DEBUG] Checking for SPACE (byte 32). Looking for '<0x20>' and ' '.");
806 Logger::debug(
"[BYTE_FALLBACK_DEBUG] Found '<0x20>' token with ID: " + std::to_string(
it->second) +
". Adding to map.");
813 if (std::isprint(
static_cast<unsigned char>(
i))) {
818 Logger::debug(
"[BYTE_FALLBACK_DEBUG] Did not find '<0x20>', but found literal ' ' token with ID: " + std::to_string(
lit_it->second));
828 Logger::debug(
"[BYTE_FALLBACK_DEBUG] ID " + std::to_string(
lit_it->second) +
" for ' ' not already mapped. Adding to map.");
837 Logger::debug(
"[BYTE_FALLBACK_DEBUG] ID " + std::to_string(
lit_it->second) +
" for ' ' was already mapped (likely by <0x20>). Skipping literal add.");
844 Logger::debug(
"[BYTE_FALLBACK_DEBUG] Did not find '<0x20>' OR literal ' ' token in vocab.");
851 Logger::debug(
"[BYTE_FALLBACK_DEBUG] Did not find '<0x20>' token, and space is not printable, so didn't check for literal ' '.");
858 " byte values in GGUF vocab (using <0xNN> or literal). Intended for Tiktoken BPE.");
864 Logger::warning(
"GGUF tokenizer_token_types array missing or size mismatch. Byte token and special token identification will be limited.");
868 for (
int i = 0;
i < 256; ++
i) {
870 ss_hex_repr <<
"<0x" << std::hex << std::setw(2) << std::setfill(
'0') <<
i <<
">";
877 Logger::debug(
"[BYTE_FALLBACK_DEBUG] Checking for SPACE (byte 32). Looking for '<0x20>' and ' '.");
885 Logger::debug(
"[BYTE_FALLBACK_DEBUG] Found '<0x20>' token with ID: " + std::to_string(
it->second) +
". Adding to map.");
892 if (std::isprint(
static_cast<unsigned char>(
i))) {
897 Logger::debug(
"[BYTE_FALLBACK_DEBUG] Did not find '<0x20>', but found literal ' ' token with ID: " + std::to_string(
lit_it->second));
907 Logger::debug(
"[BYTE_FALLBACK_DEBUG] ID " + std::to_string(
lit_it->second) +
" for ' ' not already mapped. Adding to map.");
916 Logger::debug(
"[BYTE_FALLBACK_DEBUG] ID " + std::to_string(
lit_it->second) +
" for ' ' was already mapped (likely by <0x20>). Skipping literal add.");
923 Logger::debug(
"[BYTE_FALLBACK_DEBUG] Did not find '<0x20>' OR literal ' ' token in vocab.");
930 Logger::debug(
"[BYTE_FALLBACK_DEBUG] Did not find '<0x20>' token, and space is not printable, so didn't check for literal ' '.");
937 " byte values in GGUF vocab (using <0xNN> or literal). Intended for Tiktoken BPE.");
943 Logger::info(
"[GENERAL_BYTE_FALLBACK] Space ' ' not found in byte_char_to_id_. Attempting to populate from vocab.");
945 for (
int i = 0;
i < 256; ++
i) {
953 ss_hex_repr <<
"<0x" << std::hex << std::setw(2) << std::setfill(
'0') <<
i <<
">";
972 " new entries to byte_char_to_id_ map. Final size: " + std::to_string(
byte_char_to_id_.size()));
974 Logger::warning(
"[GENERAL_BYTE_FALLBACK] Space ' ' still not found in byte_char_to_id_ after fallback scan!");
984 "' (ID: " + std::to_string(
it_sp_space->second) +
"). Mapped standard space ' ' to this ID.");
987 Logger::warning(
"[GENERAL_BYTE_FALLBACK] Space ' ' still not found in byte_char_to_id_ after fallback scan AND specific SP space check!");
1013 " from config is out of vocab bounds or invalid. Using default string: '" +
default_str_val +
"'.");
1033 if (
config.pad_token_id == -1) {
1045 Logger::info(std::string(
"Tokenizer successfully initialized from GGUFData. Final type: ") +
1051 const std::vector<std::string>& tokens)
const {
1058 for (
const auto&
token : tokens) {
1059 if (!
token.empty() &&
1067 for (
size_t i = 0;
i < tokens.size(); ++
i) {
1068 std::string
token = tokens[
i];
1071 if (!
token.empty()) {
1073 if (
token.size() > 2) {
1080 else if (
token.size() >= 3 &&
1082 if (
token.size() > 3) {
1134 const size_t lookup[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 3, 4 };
1167 Logger::debug(
"[ENCODE] Using WORD_LEVEL (whole-word lookup) path.");
1172 [](
unsigned char c) { return static_cast<char>(std::tolower(c)); });
1181 Logger::debug(
"[ENCODE] Using LLAMA3_TIKTOKEN (bpe_tokenize_to_ids) path.");
1185 if (this->
bos_token_.empty() || text.rfind(
this->bos_token_, 0) != 0) {
1188 " (text did not already start with it).");
1190 Logger::debug(
"[ENCODE Llama 3 Path] BOS token flag was true, but text already started with BOS string. Skipping explicit BOS ID addition.");
1204 Logger::debug(
"[ENCODE] Using LLAMA_SENTENCEPIECE (old SentencePiece/BPE logic) path.");
1208 "[ENCODE SPM Path] Using simplified merge-based tokenizer path (calling "
1209 "bpe_tokenize directly).");
1213 std::to_string(
bpe_pieces.size()) +
" pieces.");
1227 Logger::debug(
"[ENCODE SPM Path] Final IDs (Simplified Merge Path): " +
1228 std::to_string(
final_ids.size()) +
" tokens.");
1230 Logger::debug(
"[ENCODE SPM Path] Using GGUF score-based tokenizer path.");
1238 std::vector<std::pair<std::string, bool>>
segments;
1245 Logger::debug(
"[ENCODE SPM GGUF Path] Using DEFAULT pre-tokenization (split by special, BPE for non-specials).");
1248 Logger::debug(
"[ENCODE SPM GGUF Path] Using LLAMA_REGEX pre-tokenization.");
1250 Logger::warning(
"[ENCODE SPM GGUF Path] pre_tok_type_ is '" + this->
pre_tok_type_ +
"' or unset. Defaulting to WHITESPACE pre-tokenization for GGUF/SPM path.");
1259 else method_str_log =
"DEFAULT (Special Token Split or WHITESPACE Fallback)";
1265 if (!
pair.first.empty()) all_special_tokens_set.insert(
pair.first);
1267 if (!this->
bos_token_.empty()) all_special_tokens_set.insert(
this->bos_token_);
1268 if (!this->
eos_token_.empty()) all_special_tokens_set.insert(
this->eos_token_);
1269 if (!this->
unk_token_.empty()) all_special_tokens_set.insert(
this->unk_token_);
1286 Logger::debug(
"[ENCODE SPM GGUF Path] No special tokens defined for DEFAULT pre-tok. Treating whole text as one segment.");
1293 boost::sregex_iterator
end;
1296 boost::smatch match = *
it;
1300 segments.push_back({match.str(),
true});
1301 last_pos = match.position() + match.length();
1307 }
catch (
const boost::regex_error&
e) {
1308 Logger::error(
"[ENCODE SPM GGUF Path] Regex error splitting by special tokens: " + std::string(
e.what()) +
". Treating as single segment.");
1314 boost::regex
llama_segment_regex( R
"('s|'t|'re|'ve|'m|'ll|'d| ?[[:alpha:]]+| ?[[:digit:]]+| ?[^\\s[:alpha:][:digit:]]+|\\s+(?!\\S)|\\s+)");
1315 Logger::debug("[ENCODE SPM GGUF Path] Using LLAMA_REGEX for pre-tokenization.");
1318 boost::sregex_iterator
end;
1321 boost::smatch match = *
it;
1325 segments.push_back({match.str(),
false});
1326 last_pos = match.position() + match.length();
1332 }
catch (
const boost::regex_error&
e) {
1333 Logger::error(
"[ENCODE SPM GGUF Path] Regex error during LLAMA_REGEX splitting: " + std::string(
e.what()) +
". Treating as single segment.");
1338 Logger::debug(
"[ENCODE SPM GGUF Path] Using WHITESPACE pre-tokenization (or fallback).");
1341 if (std::isspace(
static_cast<unsigned char>(
c))) {
1356 Logger::debug(
"[ENCODE SPM GGUF Path] Pre-tokenization resulted in " + std::to_string(
segments.size()) +
" segments.");
1368 segment_ids.push_back(
it->second);
1372 "' not in vocab. Using UNK ID: " + std::to_string(this->
unk_token_id_));
1386 Logger::debug(
"[ENCODE SPM GGUF Path] Appended EOS token: " +
1389 Logger::debug(
"[ENCODE SPM GGUF Path] Final IDs (GGUF Score Path): " + std::to_string(
final_ids.size()) +
" tokens.");
1406 Logger::warning(
"[ENCODE] Tokenization resulted in empty ID list for non-empty text: '" +
text +
"'");
1418 std::stringstream
wss;
1420 for (
int id : ids) {
1421 if (
id < 0 ||
static_cast<size_t>(
id) >=
id_to_token_.size())
continue;
1438 Logger::debug(
"[decode] Decoding using Llama 3 / Tiktoken logic.");
1439 std::stringstream
ss;
1440 bool first_token =
true;
1442 for (
int id : ids) {
1444 if (
id < 0 ||
static_cast<size_t>(
id) >=
id_to_token_.size()) {
1446 ss <<
"[INVALID_ID:" <<
id <<
"]";
1447 Logger::debug(
"[decode] Invalid token ID: " + std::to_string(
id));
1448 first_token =
false;
1456 Logger::debug(
"[decode] Skipping special token ID: " + std::to_string(
id) +
1457 " (BOS/EOS/PAD/UNK)");
1461 Logger::debug(
"[decode] Skipping added token ID: " + std::to_string(
id));
1470 if (!std::isprint(
static_cast<unsigned char>(
token_debug[
i]))) {
1477 Logger::debug(
"[decode] Processing token ID " + std::to_string(
id) +
1480 if (
token.empty()) {
1483 Logger::debug(
"[decode] Empty token replaced with UNK token");
1494 Logger::debug(
"[decode] Added space before token with BPE_SPACE_CHAR prefix");
1497 Logger::debug(
"[decode] Added token content after BPE_SPACE_CHAR: '" +
1499 first_token =
false;
1503 first_token =
false;
1513 Logger::debug(
"[decode_sentencepiece] Decoding using SentencePiece logic.");
1515 std::stringstream
ss;
1516 bool first_token =
true;
1520 for (
int id : ids) {
1537 if (
id >= 0 &&
static_cast<size_t>(
id) <
id_to_token_.size()) {
1546 token_str =
"[INVALID_ID:" + std::to_string(
id) +
"]";
1561 }
catch (
const std::exception&
e) {
1579 token_str =
"[EMPTY_TOKEN_FOR_ID:" + std::to_string(
id) +
"]";
1586 Logger::warning(
"[decode_sentencepiece] Encountered empty token string for valid ID " + std::to_string(
id) +
1596 Logger::debug(
"[decode_sentencepiece] Ignored leading standalone space prefix token.");
1603 Logger::debug(
"[decode_sentencepiece] Added space for standalone prefix token mid-sequence.");
1605 first_token =
false;
1627 first_token =
false;
1641 first_token =
false;
1645 first_token =
false;
1675 Logger::info(
"[apply_chat_template] GGUF chat template detected as Jinja2-like.");
1677 Logger::info(
"[apply_chat_template] GGUF chat template detected as simple placeholder template.");
1682 Logger::info(
"[apply_chat_template] Using simple GGUF chat template (non-Jinja).");
1702 Logger::warning(
"[apply_chat_template] GGUF chat template appears to be Jinja2, which is not fully supported by this C++ implementation. Falling back to hardcoded Llama 3 Instruct template. The model's intended GGUF chat template will be ignored.");
1704 Logger::info(
"[apply_chat_template] GGUF chat template not found or empty. Falling back to hardcoded Llama 3 Instruct template.");
1712 if ((!this->
added_tokens_.empty() || !
this->token_to_id_.empty()) && content.rfind(
"<",0) == 0 && content.rfind(
"|",0) != std::string::npos && content.rfind(
">",0) == content.length()-1) {
1713 Logger::warning(
"[apply_chat_template_fallback] Could not find special token string '" + content +
1714 "' in added_tokens_ or vocab. Using default/fallback string: '" +
fallback_value +
"'");
1730 std::stringstream
ss;
1738 Logger::info(
"[apply_chat_template] Applied hardcoded Llama 3 Instruct-like chat template as fallback. Prompt snippet: " +
ss.str().substr(0,100));
1752 if (!
file.is_open()) {
1753 throw std::runtime_error(
"Failed to open vocabulary file: " +
vocab_path);
1762 Logger::info(
"load_vocab_from_json: Detected HuggingFace tokenizer.json format.");
1763 const auto& vocab =
vocab_json[
"model"][
"vocab"];
1767 for (
auto it = vocab.begin();
it != vocab.end(); ++
it) {
1768 int id =
it.value().get<
int>();
1770 Logger::warning(
"load_vocab_from_json: Skipping token with negative ID: " +
it.key());
1773 if (
static_cast<size_t>(
id) >
max_id) {
1780 for (
auto it = vocab.begin();
it != vocab.end(); ++
it) {
1782 int id =
it.value().get<
int>();
1783 if (
id < 0)
continue;
1790 Logger::warning(
"load_vocab_from_json: ID out of bounds during vocab population: " + std::to_string(
id));
1825 std::to_string(token_id));
1831 Logger::info(
"load_vocab_from_json: Detected plain vocabulary format (direct map).");
1834 int id =
it.value().get<
int>();
1835 if (
id < 0)
continue;
1836 if (
static_cast<size_t>(
id) >
max_id) {
1844 int id =
it.value().get<
int>();
1860 throw std::runtime_error(
"load_vocab_from_json: Vocabulary JSON has an unsupported format.");
1874 Logger::info(
"load_vocab_from_json: Loaded vocabulary with " +
1875 std::to_string(
token_to_id_map.size()) +
" unique token strings and " +
1883 }
catch (
const json::exception&
e) {
1884 throw std::runtime_error(
"Error parsing vocabulary JSON from " +
vocab_path +
": " +
e.what());
1885 }
catch (
const std::exception&
e) {
1886 throw std::runtime_error(
"Error loading vocabulary from " +
vocab_path +
": " + std::string(
e.what()));
1891 Logger::warning(
"load_sentencepiece_model: Loading from SentencePiece model file ('" +
model_path +
"') is currently not implemented.");
1898 if (!
file.is_open()) {
1899 throw std::runtime_error(
"load_bpe_merges_from_json: Failed to open BPE merges file: " +
tokenizer_json_path);
1962 throw std::runtime_error(
1963 "load_bpe_merges_from_json: Unsupported BPE model format: no 'model.merges' or top-level 'merges' array found in '" +
tokenizer_json_path +
"'");
1973 }
catch (
const json::exception&
e) {
1974 throw std::runtime_error(
"Error parsing BPE merges JSON from " +
tokenizer_json_path +
": " +
e.what());
1975 }
catch (
const std::exception&
e) {
1976 throw std::runtime_error(
"An unexpected error occurred while loading BPE merges from " +
tokenizer_json_path +
": " + std::string(
e.what()));
1981 if (
s.empty())
return s;
1985 const std::string
sp_space =
"\xE2\x96\x81";
2011 Logger::debug(
"[Original bpe_tokenize for SentencePiece] Entered. bpe_merges_ size: " + std::to_string(
bpe_merges_.size()));
2015 std::vector<std::string>
pieces;
2019 for (
char c :
text) {
2020 if (std::isspace(
static_cast<unsigned char>(
c))) {
2025 pieces.push_back(std::string(1,
c));
2036 Logger::debug(
"[Original bpe_tokenize for SentencePiece] Split text into " + std::to_string(
pieces.size()) +
" pieces (words/spaces).");
2041 if (
piece.empty())
continue;
2044 [](
char c) { return std::isspace(static_cast<unsigned char>(c)); });
2048 Logger::debug(
"[Original bpe_tokenize for SentencePiece] Piece '" +
piece +
"' is whitespace. Setting prefix flag.");
2061 std::vector<std::string>
chars;
2074 if (
chars.empty()) {
2082 int best_rank = std::numeric_limits<int>::max();
2085 for (
size_t i = 0;
i <
chars.size() - 1; ++
i) {
2099 Logger::debug(
"[Original bpe_tokenize for SentencePiece] Applied merge: '" +
merged +
"' with rank " +
2121 [](
const auto&
a,
const auto&
b) {
2122 return a.first.length() > b.first.length();
2133 Logger::debug(std::string(
"[bpe_tokenize_to_ids] Starting Tiktoken BPE tokenization for text length: ") + std::to_string(
text.length()) +
2142 Logger::warning(
"[bpe_tokenize_to_ids] BOS token requested but bos_token_id_ is -1.");
2153 R
"(<\|[^|]+\||[[:alnum:]]+|\.(?![<|])|[^\s<|]+|\s+)";
2156 const boost::xpressive::sregex
tiktoken_pattern_ = boost::xpressive::sregex::compile(
2158 boost::xpressive::regex_constants::icase
2201 Logger::warning(std::string(
"[bpe_tokenize_to_ids] Unrecognized byte '") +
byte_str + std::string(
"' replaced with UNK."));
2203 Logger::error(std::string(
"[bpe_tokenize_to_ids] Unrecognized byte '") +
byte_str + std::string(
"' and no UNK token defined. Skipping."));
2215 Logger::warning(
"[bpe_tokenize_to_ids] Regex search succeeded but matched an empty string. Advancing one char from pos " + std::to_string(
current_idx));
2264 Logger::debug(std::string(
"[bpe_tokenize_to_ids] ignore_merges=true, but word \'") +
word_to_process +
"\' not in vocab directly. Proceeding with BPE char split (unusual for tiktoken special words).");
2268 std::vector<llm_symbol>
symbols;
2297 for (
size_t i = 0;
i <
symbols.size(); ++
i) {
2303 std::priority_queue<std::pair<int, int>,
2304 std::vector<std::pair<int, int>>,
2307 for (
size_t i = 0;
i + 1 <
symbols.size(); ++
i) {
2316 int rank =
top.first;
2345 if (
symbol.n == 0)
continue;
2355 Logger::warning(std::string(
"[bpe_tokenize_to_ids] Symbol not found in vocab: '") +
lookup_s +
"'. Attempting byte-level tokenization.");
2364 Logger::error(std::string(
"[bpe_tokenize_to_ids] Unhandled char '") + std::string(1,
c_char) +
"' and no UNK token ID.");
2386 Logger::warning(
"[bpe_tokenize_to_ids] EOS token requested but eos_token_id_ is -1.");
2392 Logger::debug(
"[bpe_tokenize_to_ids] Finished Tiktoken BPE tokenization. Total IDs: " + std::to_string(
output_ids.size()));
2399 const std::vector<llm_symbol>&
symbols,
2401 std::priority_queue<std::pair<int, int>,
2402 std::vector<std::pair<int, int>>,
2403 std::greater<std::pair<int, int>>>&
work_queue)
const {
2417 if (
s1.n == 0 ||
s2.n == 0) {
2449 int best_rank = std::numeric_limits<int>::max();
static void debug(const std::string &message)
static void warning(const std::string &message)
static void info(const std::string &message)
static void error(const std::string &message)
PreTokenizeMethod
Enumeration of available pre-tokenization methods.
std::unordered_map< std::string, int > token_to_id_
std::unordered_map< int, std::string > id_to_added_token_
std::string capitalize_first_letter(std::string s) const
void load_bpe_merges_from_json(const std::string &model_path)
Loads BPE merge rules from JSON file.
void add_bigram_to_queue_refactored(const char *text_data_base, const std::vector< llm_symbol > &symbols, llm_symbol::index first_symbol_idx, std::priority_queue< std::pair< int, int >, std::vector< std::pair< int, int > >, std::greater< std::pair< int, int > > > &work_queue) const
ModelConfig::TokenizerFamily tokenizer_family_
std::vector< int > bpe_tokenize_to_ids(const std::string &text, bool add_bos_token_param, bool add_eos_token_param, bool ignore_merges_param) const
bool sentencepiece_model_loaded_
std::string decode_sentencepiece(const std::vector< int > &ids, bool skip_special_tokens) const
std::vector< std::string > ids_to_tokens(const std::vector< int > &ids) const
Converts token IDs back to token strings.
std::vector< int > tokens_to_ids(const std::vector< std::string > &tokens) const
std::vector< float > token_scores_
void load_sentencepiece_model(const std::string &model_path)
Loads a SentencePiece model.
const std::string & get_gguf_chat_template() const
std::vector< std::string > bpe_tokenize(const std::string &text) const
int find_bpe_rank(const std::string &token_left, const std::string &token_right) const
std::vector< std::string > bpe_tokenize_from_scores(const std::string &text) const
std::vector< int > encode(const std::string &text, bool add_bos=true, bool add_eos=false, PreTokenizeMethod pre_tok_override=PreTokenizeMethod::DEFAULT) const
Encodes text into token IDs with optional special tokens.
bool initialized_from_gguf_
std::unordered_map< char, int > byte_char_to_id_
int vocab_size() const
Returns the size of the vocabulary.
void load_vocab_from_json(const std::string &vocab_path, std::unordered_map< std::string, int > &token_to_id, std::vector< std::string > &id_to_token)
Loads vocabulary from JSON file.
std::vector< int32_t > token_types_
std::string detokenize(const std::vector< std::string > &tokens) const
Combines tokens back into text.
std::unordered_map< std::string, int > bpe_merges_
std::string pre_tok_type_
std::unordered_map< std::string, int > added_tokens_
std::vector< std::string > id_to_token_
std::string gguf_chat_template_
std::string apply_chat_template(const std::string &user_prompt, const std::string &system_message, const ModelConfig &config) const
Applies chat template formatting to the input prompt.
std::string decode(const std::vector< int > &ids, bool skip_special_tokens=true) const
Decodes token IDs back to text.
Tokenizer(const std::string &vocab_path, const std::string &model_path, const ModelConfig &config)
Constructs a tokenizer from vocabulary and model files (for Llama 2 style JSON)
Logging utilities for the TinyLlama implementation.
Complete representation of a GGUF file's contents.
Model configuration structure holding architecture and hyperparameters.
bool is_numeric(const std::string &s)
const std::string BPE_SPACE_CHAR
static std::string replace_all(std::string str, const std::string &from, const std::string &to)
static std::vector< std::pair< std::string, int > > sort_tokens_by_length_desc(const std::unordered_map< std::string, int > &tokens_map)
static std::unordered_map< std::string, int > generate_bpe_merges_from_vocab_scores(const std::vector< std::string > &id_to_token, const std::vector< float > &token_scores)