24 if (!tok || !
token)
return -1;
26 return info ? info->id : -1;
33 char *tmp = stack_buf;
34 if (
text_len >= (
int)
sizeof(stack_buf)) {
35 tmp = (
char *)malloc((
size_t)
text_len + 1);
41 if (tmp != stack_buf) free(tmp);
51 if (match_len) *match_len = 0;
52 if (!tok || !
text || text_len <= 0 || pos >= (
size_t)
text_len)
return -1;
60 while (cur < (
size_t)
text_len && node) {
61 unsigned char c = (
unsigned char)
text[cur];
72 if (match_len) *match_len = best_len;
82 for (
int len =
max_len; len >= 1; len--) {
83 memcpy(tmp,
text + pos, (
size_t)len);
86 if (info && info->id >= 0 && info->is_special) {
87 if (match_len) *match_len = (size_t)len;
109#define GGUF_TOKEN_NORMAL 1
110#define GGUF_TOKEN_UNKNOWN 2
111#define GGUF_TOKEN_CONTROL 3
112#define GGUF_TOKEN_USER_DEFINED 4
113#define GGUF_TOKEN_UNUSED 5
114#define GGUF_TOKEN_BYTE 6
119 if (!tok->
types || token_id < 0 || token_id >= (int32_t)tok->
vocab_size) {
122 uint8_t t = tok->
types[token_id];
129 if (!tok->
types || token_id < 0 || token_id >= (int32_t)tok->
vocab_size) {
143 int len = snprintf(byte_token,
sizeof(byte_token),
"<0x%02X>", byte_val);
144 if (len <= 0)
return tok->
unk_id;
159 tok->
byte_token_id = (int32_t *)malloc(256 *
sizeof(int32_t));
164 for (
int i = 0; i < 256; i++) {
173 size_t len = strlen(
token);
177 unsigned char byte_val = (
unsigned char)
token[0];
181 unsigned int byte_val;
182 if (sscanf(
token,
"<0x%02X>", &byte_val) == 1 && byte_val < 256) {
191 if ((c & 0x80) == 0x00)
return 1;
192 if ((c & 0xE0) == 0xC0)
return 2;
193 if ((c & 0xF0) == 0xE0)
return 3;
194 if ((c & 0xF8) == 0xF0)
return 4;
208 if (
out_len + 3 > out_max)
return -1;
215 if (
text[i] ==
' ') {
225 if (
out_len + 3 > out_max)
return -1;
230 if (
out_len + run > out_max)
return -1;
231 for (
int k = 0; k < run; k++) {
237 if (
out_len + 1 > out_max)
return -1;
261 const SpmLlamaNode *nodes,
266 if (!tok || !nodes || node_id < 0 || !ids || out_idx >=
max_ids) {
270 const SpmLlamaNode *node = &nodes[node_id];
273 ids[out_idx++] = token_id;
277 if (node->left >= 0 && node->right >= 0) {
283 for (
int i = 0; i < node->n && out_idx <
max_ids; i++) {
285 ids[out_idx++] = (byte_token >= 0) ? byte_token : tok->
unk_id;
303 if (
text_len > (INT_MAX - 4) / 3)
return 0;
305 char *preprocessed = (
char *)malloc((
size_t)pp_capacity);
306 if (!preprocessed)
return 0;
309 if (pp_len < 0) { free(preprocessed);
return 0; }
310 preprocessed[pp_len] =
'\0';
313 for (
int offs = 0; offs < pp_len;) {
314 int char_len =
utf8_len((
unsigned char)preprocessed[offs]);
315 if (char_len <= 0) char_len = 1;
316 if (offs + char_len > pp_len) char_len = pp_len - offs;
320 if (num_symbols <= 0) { free(preprocessed);
return 0; }
322 SpmLlamaSymbol *symbols = (SpmLlamaSymbol *)calloc((
size_t)num_symbols,
sizeof(SpmLlamaSymbol));
323 int node_cap = 2 * num_symbols + 1;
324 SpmLlamaNode *nodes = (SpmLlamaNode *)calloc((
size_t)node_cap,
sizeof(SpmLlamaNode));
325 if (!symbols || !nodes) {
326 if (symbols) free(symbols);
327 if (nodes) free(nodes);
333 for (
int offs = 0; offs < pp_len && index < num_symbols;) {
334 int char_len =
utf8_len((
unsigned char)preprocessed[offs]);
335 if (char_len <= 0) char_len = 1;
336 if (offs + char_len > pp_len) char_len = pp_len - offs;
338 symbols[index].text = preprocessed + offs;
339 symbols[index].n = char_len;
340 symbols[index].prev = index - 1;
341 symbols[index].next = (index + 1 < num_symbols) ? (index + 1) : -1;
342 symbols[index].node_id = index;
344 nodes[index].text = preprocessed + offs;
345 nodes[index].n = char_len;
346 nodes[index].left = -1;
347 nodes[index].right = -1;
353 int node_count = num_symbols;
357 float best_score = -1e30f;
361 if (
right < 0)
continue;
363 int pair_len = symbols[
left].n + symbols[
right].n;
365 if (token_id < 0 || token_id >= (int32_t)tok->
vocab_size)
continue;
372 if (best_left < 0 || score > best_score || (
score == best_score &&
left < best_left)) {
379 if (best_left < 0 || best_right < 0)
break;
380 if (node_count >= node_cap)
break;
382 SpmLlamaSymbol *
left = &symbols[best_left];
383 SpmLlamaSymbol *
right = &symbols[best_right];
385 int new_node_id = node_count++;
386 nodes[new_node_id].text =
left->text;
387 nodes[new_node_id].n =
left->n +
right->n;
388 nodes[new_node_id].left =
left->node_id;
389 nodes[new_node_id].right =
right->node_id;
392 left->node_id = new_node_id;
394 if (
right->next >= 0) {
395 symbols[
right->next].prev = best_left;
404 for (
int i = 0; i != -1 && out_idx <
max_ids; i = symbols[i].next) {
425 while (lead_spaces <
text_len &&
text[lead_spaces] ==
' ') {
430 int trail_spaces = 0;
431 while (trail_spaces <
text_len - lead_spaces &&
437 int content_len =
text_len - lead_spaces - trail_spaces;
438 int starts_with_prefix = (
text_len >= 3 &&
439 (
unsigned char)
text[0] == 0xE2 &&
440 (
unsigned char)
text[1] == 0x96 &&
441 (
unsigned char)
text[2] == 0x81);
442 int inserted_prefix = 0;
444 if (
out_len + 3 > out_max)
return -1;
453 int last_was_space = (starts_with_prefix || inserted_prefix) ? 1 : 0;
454 while (i <
text_len - trail_spaces) {
455 if (
text[i] ==
' ') {
456 if (!last_was_space) {
458 if (
out_len + 3 > out_max)
return -1;
466 if (
out_len + 1 > out_max)
return -1;
478 size_t pos, int32_t *candidates,
int max_candidates);
497 const int dbg = getenv(
"CK_DEBUG_SPM_ENCODE") ? 1 : 0;
499 fprintf(stderr,
"[SPM] encode start: text_len=%d max_ids=%d\n",
text_len,
max_ids);
503 if (
text_len > (INT_MAX - 4) / 3)
return 0;
505 char *preprocessed = (
char *)malloc((
size_t)pp_capacity);
506 if (!preprocessed)
return 0;
509 if (pp_len < 0) { free(preprocessed);
return 0; }
510 preprocessed[pp_len] =
'\0';
512 fprintf(stderr,
"[SPM] preprocessed len=%d: \"%.*s\"\n", pp_len, pp_len, preprocessed);
516 size_t n = (size_t)pp_len + 1;
517 float *best_score = (
float *)malloc(n *
sizeof(
float));
518 int32_t *best_prev = (int32_t *)malloc(n *
sizeof(int32_t));
519 int32_t *best_token = (int32_t *)malloc(n *
sizeof(int32_t));
521 fprintf(stderr,
"[SPM] DP alloc n=%zu\n", n);
524 if (!best_score || !best_prev || !best_token) {
525 if (best_score) free(best_score);
526 if (best_prev) free(best_prev);
527 if (best_token) free(best_token);
533 const float neg_inf = -1e30f;
534 const float unknown_penalty = -10.0f;
535 for (
size_t i = 0; i < n; i++) {
536 best_score[i] = neg_inf;
540 best_score[0] = 0.0f;
543 for (
size_t pos = 0; pos < n; pos++) {
544 if (best_score[pos] == neg_inf)
continue;
547 int32_t candidates[64];
549 if (dbg && pos < 8) {
550 fprintf(stderr,
"[SPM] pos=%zu cand=%d\n", pos, num_cand);
553 for (
int c = 0; c < num_cand; c++) {
554 int32_t token_id = candidates[c];
563 if (!
token)
continue;
566 int token_len = (int)strlen(
token);
569 if (token_id == tok->
unk_id) {
571 if (token_len == 0) token_len = 1;
574 size_t next_pos = pos + token_len;
576 if (next_pos >= n)
continue;
579 float token_score = 0.0f;
581 token_score = tok->
scores[token_id];
585 if (tok->
types && token_id >= 0 && token_id < (int32_t)tok->
types_size) {
591 if (token_id == tok->
unk_id) {
592 token_score += unknown_penalty;
596 float new_score = best_score[pos] + token_score;
598 if (new_score > best_score[next_pos]) {
599 best_score[next_pos] = new_score;
600 best_prev[next_pos] = (int32_t)pos;
601 best_token[next_pos] = token_id;
607 int32_t *reverse_ids = (int32_t *)malloc(
max_ids *
sizeof(int32_t));
617 int32_t curr = (int32_t)(n - 1);
620 while (curr > 0 && best_token[curr] < 0) {
621 curr = best_prev[curr];
627 while (curr > 0 && num_tokens <
max_ids) {
628 int32_t token_id = best_token[curr];
631 int token_start = best_prev[curr];
634 if (token_start != last_start) {
635 reverse_ids[num_tokens++] = token_id;
636 last_start = token_start;
639 curr = best_prev[curr];
642 fprintf(stderr,
"[SPM] backtrack tokens=%d curr=%d\n", num_tokens, curr);
654 for (
int i = 0; i < num_tokens / 2; i++) {
655 int32_t tmp = reverse_ids[i];
656 reverse_ids[i] = reverse_ids[num_tokens - 1 - i];
657 reverse_ids[num_tokens - 1 - i] = tmp;
662 for (
int i = 0; i < num_tokens && out_idx <
max_ids; i++) {
663 int32_t token_id = reverse_ids[i];
666 if (token_id == tok->
unk_id && out_idx > 0 &&
ids[out_idx - 1] == tok->
unk_id) {
669 ids[out_idx++] = token_id;
672 fprintf(stderr,
"[SPM] encode done: out=%d\n", out_idx);
678 if (num_tokens == 0) {
708 unsigned char byte_val = (
unsigned char)
text[i];
712 if (byte_token >= 0 && byte_token != tok->
unk_id) {
723 size_t pos, int32_t *candidates,
int max_candidates) {
732 for (
int len =
max_len; len >= 1 && num_found < max_candidates; len--) {
733 memcpy(tmp,
text + pos, len);
737 if (info && info->id >= 0 && info->id != tok->
unk_id) {
745 for (
int j = 0; j < num_found; j++) {
746 if (candidates[j] == info->id) {
752 candidates[num_found++] = info->id;
761 if (num_found == 0 && tok->
unk_id >= 0 && max_candidates > 0) {
764 candidates[num_found++] = tok->
unk_id;
775 while (pos + run < (
size_t)
text_len) {
777 if (pos + run + 3 <= (
size_t)
text_len &&
778 (
unsigned char)
text[pos + run] == 0xE2 &&
779 (
unsigned char)
text[pos + run + 1] == 0x96 &&
780 (
unsigned char)
text[pos + run + 2] == 0x81) {
790 for (
int len =
max_len; len >= 1; len--) {
792 memcpy(tmp,
text + pos + run, len);
826 int segment_start = 0;
828 size_t special_len = 0;
830 if (special_id < 0 || special_len == 0) {
835 if (segment_start < pos) {
838 text + segment_start,
843 if (n <= 0)
return n;
848 ids[out_idx++] = special_id;
849 pos += (int)special_len;
856 text + segment_start,
861 if (n <= 0)
return n;
878 const uint8_t *
types,
899 if (!tok->
scores)
return -1;
918 float score = scores ? scores[i] : 0.0f;
927 int count_normal = 0, count_unknown = 0, count_control = 0, count_byte = 0, count_other = 0;
930 uint8_t t = tok->
types[i];
931 if (t > max_type) max_type = t;
937 default: count_other++;
break;
940 fprintf(stderr,
"[TOKENIZER] Loaded %d tokens: normal=%d, unknown=%d, control=%d, byte=%d, other=%d\n",
941 vocab_size, count_normal, count_unknown, count_control, count_byte, count_other);
943 fprintf(stderr,
"[TOKENIZER] Warning: Unexpected token type %d\n", max_type);
int32_t ck_tokenizer_lookup(const CKTokenizer *tok, const char *token, int len)
int32_t ck_tokenizer_add_token(CKTokenizer *tok, const char *token, int len)
const char * ck_tokenizer_id_to_token(const CKTokenizer *tok, int32_t id)
void * ck_tokenizer_hash_table_lookup(CKTokenizerHashTable *table, const char *key)
CKTokenizerHashTable * vocab
struct CKTrieNode * children[256]
void ck_tokenizer_reset(CKTokenizer *tok)
#define CK_TOKENIZER_MAX_TOKEN_LEN
const int32_t int int * out_len
static int spm_find_candidates_at_pos(const CKTokenizer *tok, const char *text, int text_len, size_t pos, int32_t *candidates, int max_candidates)
static int32_t ck_tokenizer_lookup_exact(const CKTokenizer *tok, const char *token)
static int preprocess_spm_llama_text(const char *text, int text_len, char *out, int out_max, bool add_space_prefix)
static bool spm_token_is_byte_format(const char *token)
int ck_tokenizer_load_binary_with_scores(CKTokenizer *tok, int vocab_size, const int32_t *offsets, const char *strings, const float *scores, const uint8_t *types, int num_merges, const int32_t *merges)
static int ck_tokenizer_encode_spm_llama_impl(const CKTokenizer *tok, const char *text, int text_len, int32_t *ids, int max_ids)
static bool spm_token_allowed_in_dp(const CKTokenizer *tok, int32_t token_id)
#define GGUF_TOKEN_CONTROL
static void spm_build_byte_lookup(CKTokenizer *tok, const char *strings, const int32_t *offsets, int vocab_size)
static int preprocess_spm_text(const char *text, int text_len, char *out, int out_max, bool add_space_prefix)
static int spm_encode_byte_fallback(const CKTokenizer *tok, const char *text, int text_len, int32_t *ids, int max_ids)
int ck_tokenizer_encode_spm_dispatch(const CKTokenizer *tok, const char *text, int text_len, int32_t *ids, int max_ids)
static int spm_llama_resegment_node(const CKTokenizer *tok, const SpmLlamaNode *nodes, int node_id, int32_t *ids, int max_ids, int out_idx)
static int32_t ck_tokenizer_lookup_exact_n(const CKTokenizer *tok, const char *text, int text_len)
#define GGUF_TOKEN_USER_DEFINED
static int utf8_len(unsigned char c)
static int32_t spm_find_special_token_at_pos(const CKTokenizer *tok, const char *text, int text_len, size_t pos, size_t *match_len)
#define GGUF_TOKEN_UNUSED
static bool spm_is_byte_token(const CKTokenizer *tok, int32_t token_id)
static int32_t spm_get_byte_token(const CKTokenizer *tok, unsigned char byte_val)
static int spm_count_unknown_run(const CKTokenizer *tok, const char *text, int text_len, size_t pos)
#define GGUF_TOKEN_UNKNOWN
static int ck_tokenizer_encode_spm_impl(const CKTokenizer *tok, const char *text, int text_len, int32_t *ids, int max_ids)
#define GGUF_TOKEN_NORMAL
static int ck_tokenizer_encode_spm_plain_segment(const CKTokenizer *tok, const char *text, int text_len, int32_t *ids, int max_ids)
int const int32_t const char int num_merges
int const int32_t const char * strings
int const int32_t const char int const int32_t * merges
const int32_t int char int max_len
int const int32_t * offsets
const uint8_t size_t count
const char int int32_t int max_ids
const char const char * right