← Back to C-Kernel-Engine Docs Doxygen Source Documentation
 
Loading...
Searching...
No Matches
tokenizer_spm.c File Reference
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include "tokenizer/tokenizer.h"
#include "tokenizer/hash_table.h"

Go to the source code of this file.

Macros

#define GGUF_TOKEN_BYTE   6
 
#define GGUF_TOKEN_CONTROL   3
 
#define GGUF_TOKEN_NORMAL   1
 
#define GGUF_TOKEN_UNKNOWN   2
 
#define GGUF_TOKEN_UNUSED   5
 
#define GGUF_TOKEN_USER_DEFINED   4
 

Functions

int ck_tokenizer_encode_spm_dispatch (const CKTokenizer *tok, const char *text, int text_len, int32_t *ids, int max_ids)
 
static int ck_tokenizer_encode_spm_impl (const CKTokenizer *tok, const char *text, int text_len, int32_t *ids, int max_ids)
 
static int ck_tokenizer_encode_spm_llama_impl (const CKTokenizer *tok, const char *text, int text_len, int32_t *ids, int max_ids)
 
static int ck_tokenizer_encode_spm_plain_segment (const CKTokenizer *tok, const char *text, int text_len, int32_t *ids, int max_ids)
 
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 int32_t ck_tokenizer_lookup_exact (const CKTokenizer *tok, const char *token)
 
static int32_t ck_tokenizer_lookup_exact_n (const CKTokenizer *tok, const char *text, int text_len)
 
static int preprocess_spm_llama_text (const char *text, int text_len, char *out, int out_max, bool add_space_prefix)
 
static int preprocess_spm_text (const char *text, int text_len, char *out, int out_max, bool add_space_prefix)
 
static void spm_build_byte_lookup (CKTokenizer *tok, const char *strings, const int32_t *offsets, int vocab_size)
 
static int spm_count_unknown_run (const CKTokenizer *tok, const char *text, int text_len, size_t pos)
 
static int spm_encode_byte_fallback (const CKTokenizer *tok, const char *text, int text_len, int32_t *ids, int max_ids)
 
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 spm_find_special_token_at_pos (const CKTokenizer *tok, const char *text, int text_len, size_t pos, size_t *match_len)
 
static int32_t spm_get_byte_token (const CKTokenizer *tok, unsigned char byte_val)
 
static bool spm_is_byte_token (const CKTokenizer *tok, int32_t token_id)
 
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 bool spm_token_allowed_in_dp (const CKTokenizer *tok, int32_t token_id)
 
static bool spm_token_is_byte_format (const char *token)
 
static int utf8_len (unsigned char c)
 

Macro Definition Documentation

◆ GGUF_TOKEN_BYTE

#define GGUF_TOKEN_BYTE   6

Definition at line 113 of file tokenizer_spm.c.

◆ GGUF_TOKEN_CONTROL

#define GGUF_TOKEN_CONTROL   3

Definition at line 110 of file tokenizer_spm.c.

◆ GGUF_TOKEN_NORMAL

#define GGUF_TOKEN_NORMAL   1

Definition at line 108 of file tokenizer_spm.c.

◆ GGUF_TOKEN_UNKNOWN

#define GGUF_TOKEN_UNKNOWN   2

Definition at line 109 of file tokenizer_spm.c.

◆ GGUF_TOKEN_UNUSED

#define GGUF_TOKEN_UNUSED   5

Definition at line 112 of file tokenizer_spm.c.

◆ GGUF_TOKEN_USER_DEFINED

#define GGUF_TOKEN_USER_DEFINED   4

Definition at line 111 of file tokenizer_spm.c.

Function Documentation

◆ ck_tokenizer_encode_spm_dispatch()

int ck_tokenizer_encode_spm_dispatch ( const CKTokenizer tok,
const char *  text,
int  text_len,
int32_t *  ids,
int  max_ids 
)

Definition at line 792 of file tokenizer_spm.c.

796 {
797 if (!tok || !text || !ids || max_ids <= 0) return 0;
798 if (text_len < 0) text_len = (int)strlen(text);
799
800 int out_idx = 0;
801 if (tok->config.add_bos && tok->bos_id >= 0 && out_idx < max_ids) {
802 ids[out_idx++] = tok->bos_id;
803 }
804 if (text_len == 0) {
805 if (tok->config.add_eos && tok->eos_id >= 0 && out_idx < max_ids) {
806 ids[out_idx++] = tok->eos_id;
807 }
808 return out_idx;
809 }
810
811 int segment_start = 0;
812 for (int pos = 0; pos < text_len && out_idx < max_ids;) {
813 size_t special_len = 0;
814 int32_t special_id = spm_find_special_token_at_pos(tok, text, text_len, (size_t)pos, &special_len);
815 if (special_id < 0 || special_len == 0) {
816 pos++;
817 continue;
818 }
819
820 if (segment_start < pos) {
822 tok,
823 text + segment_start,
824 pos - segment_start,
825 ids + out_idx,
826 max_ids - out_idx
827 );
828 if (n <= 0) return n;
829 out_idx += n;
830 if (out_idx >= max_ids) break;
831 }
832
833 ids[out_idx++] = special_id;
834 pos += (int)special_len;
835 segment_start = pos;
836 }
837
838 if (segment_start < text_len && out_idx < max_ids) {
840 tok,
841 text + segment_start,
842 text_len - segment_start,
843 ids + out_idx,
844 max_ids - out_idx
845 );
846 if (n <= 0) return n;
847 out_idx += n;
848 }
849
850 if (tok->config.add_eos && tok->eos_id >= 0 && out_idx < max_ids) {
851 ids[out_idx++] = tok->eos_id;
852 }
853
854 return out_idx;
855}
int32_t bos_id
int32_t eos_id
CKTokenizerConfig config
Definition tokenizer.h:98
const int32_t * ids
Definition tokenizer.h:444
const char * text
Definition tokenizer.h:564
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)
static int ck_tokenizer_encode_spm_plain_segment(const CKTokenizer *tok, const char *text, int text_len, int32_t *ids, int max_ids)
const char int text_len
Definition true_bpe.h:270
const char int int32_t int max_ids
Definition true_bpe.h:272

References CKTokenizerConfig::add_bos, CKTokenizerConfig::add_eos, CKTokenizer::bos_id, ck_tokenizer_encode_spm_plain_segment(), CKTokenizer::config, CKTokenizer::eos_id, ids, max_ids, spm_find_special_token_at_pos(), text, and text_len.

Referenced by ck_tokenizer_encode().

◆ ck_tokenizer_encode_spm_impl()

static int ck_tokenizer_encode_spm_impl ( const CKTokenizer tok,
const char *  text,
int  text_len,
int32_t *  ids,
int  max_ids 
)
static

Definition at line 480 of file tokenizer_spm.c.

484 {
485 if (!tok || !text || !ids || max_ids <= 0) return 0;
486 if (text_len < 0) text_len = (int)strlen(text);
487 if (text_len == 0) return 0;
488 const int dbg = getenv("CK_DEBUG_SPM_ENCODE") ? 1 : 0;
489 if (dbg) {
490 fprintf(stderr, "[SPM] encode start: text_len=%d max_ids=%d\n", text_len, max_ids);
491 }
492
493 /* Preprocess: replace spaces with ▁ */
494 char preprocessed[8192];
495 int pp_len = preprocess_spm_text(text, text_len, preprocessed, sizeof(preprocessed) - 1,
497 if (pp_len < 0) return 0;
498 preprocessed[pp_len] = '\0';
499 if (dbg) {
500 fprintf(stderr, "[SPM] preprocessed len=%d: \"%.*s\"\n", pp_len, pp_len, preprocessed);
501 }
502
503 /* DP arrays - use malloc for large inputs */
504 size_t n = (size_t)pp_len + 1;
505 float *best_score = (float *)malloc(n * sizeof(float));
506 int32_t *best_prev = (int32_t *)malloc(n * sizeof(int32_t));
507 int32_t *best_token = (int32_t *)malloc(n * sizeof(int32_t));
508 if (dbg) {
509 fprintf(stderr, "[SPM] DP alloc n=%zu\n", n);
510 }
511
512 if (!best_score || !best_prev || !best_token) {
513 if (best_score) free(best_score);
514 if (best_prev) free(best_prev);
515 if (best_token) free(best_token);
516 return 0;
517 }
518
519 /* Initialize DP */
520 const float neg_inf = -1e30f;
521 const float unknown_penalty = -10.0f; /* SentencePiece-style UNK penalty */
522 for (size_t i = 0; i < n; i++) {
523 best_score[i] = neg_inf;
524 best_prev[i] = -1;
525 best_token[i] = -1;
526 }
527 best_score[0] = 0.0f;
528
529 /* DP: for each position, find best way to reach it */
530 for (size_t pos = 0; pos < n; pos++) {
531 if (best_score[pos] == neg_inf) continue;
532
533 /* Find all tokens that match at this position */
534 int32_t candidates[64];
535 int num_cand = spm_find_candidates_at_pos(tok, preprocessed, pp_len, pos, candidates, 64);
536 if (dbg && pos < 8) {
537 fprintf(stderr, "[SPM] pos=%zu cand=%d\n", pos, num_cand);
538 }
539
540 for (int c = 0; c < num_cand; c++) {
541 int32_t token_id = candidates[c];
542
543 /* Skip disallowed token types in DP */
544 if (!spm_token_allowed_in_dp(tok, token_id)) {
545 continue;
546 }
547
548 /* Get token string and length */
549 const char *token = ck_tokenizer_id_to_token(tok, token_id);
550 if (!token) continue;
551
552 /* Calculate token length in bytes */
553 int token_len = (int)strlen(token);
554
555 /* For UNK token, use the unknown run length to cover all consecutive unknown bytes */
556 if (token_id == tok->unk_id) {
557 token_len = spm_count_unknown_run(tok, preprocessed, pp_len, pos);
558 if (token_len == 0) token_len = 1; /* At least 1 byte */
559 }
560
561 size_t next_pos = pos + token_len;
562
563 if (next_pos >= n) continue;
564
565 /* Get token score for Viterbi */
566 float token_score = 0.0f;
567 if (tok->scores && token_id >= 0 && token_id < (int32_t)tok->vocab_size) {
568 token_score = tok->scores[token_id];
569 }
570
571 /* USER_DEFINED tokens get score 0 (like llama.cpp) */
572 if (tok->types && token_id >= 0 && token_id < (int32_t)tok->types_size) {
573 if (tok->types[token_id] == GGUF_TOKEN_USER_DEFINED) {
574 token_score = 0.0f;
575 }
576 }
577 /* Apply UNK penalty (SentencePiece behavior) */
578 if (token_id == tok->unk_id) {
579 token_score += unknown_penalty;
580 }
581
582 /* Transition: score = best_score[pos] + token_score */
583 float new_score = best_score[pos] + token_score;
584
585 if (new_score > best_score[next_pos]) {
586 best_score[next_pos] = new_score;
587 best_prev[next_pos] = (int32_t)pos;
588 best_token[next_pos] = token_id;
589 }
590 }
591 }
592
593 /* Backtrack to find best token sequence */
594 int32_t *reverse_ids = (int32_t *)malloc(max_ids * sizeof(int32_t));
595 if (!reverse_ids) {
596 free(best_score);
597 free(best_prev);
598 free(best_token);
599 return 0;
600 }
601
602 int num_tokens = 0;
603 int32_t curr = (int32_t)(n - 1);
604
605 /* Handle trailingUNK by finding valid end */
606 while (curr > 0 && best_token[curr] < 0) {
607 curr = best_prev[curr];
608 }
609
610 /* Backtrack from end to start, collecting tokens.
611 * We track the token's start position to avoid duplicates. */
612 int last_start = -1; /* Track the start position of last added token */
613 while (curr > 0 && num_tokens < max_ids) {
614 int32_t token_id = best_token[curr];
615 if (token_id >= 0) {
616 /* Use the DP backpointer as the true token start */
617 int token_start = best_prev[curr];
618
619 /* Only add if this is a new token (different start position) */
620 if (token_start != last_start) {
621 reverse_ids[num_tokens++] = token_id;
622 last_start = token_start;
623 }
624 }
625 curr = best_prev[curr];
626 }
627 if (dbg) {
628 fprintf(stderr, "[SPM] backtrack tokens=%d curr=%d\n", num_tokens, curr);
629 }
630
631 /* Free DP arrays before using reverse_ids */
632 free(best_score);
633 free(best_prev);
634 free(best_token);
635
636 if (num_tokens > max_ids) num_tokens = max_ids;
637
638 /* Backtracking collected tokens in reverse order, so reverse once */
639 for (int i = 0; i < num_tokens / 2; i++) {
640 int32_t tmp = reverse_ids[i];
641 reverse_ids[i] = reverse_ids[num_tokens - 1 - i];
642 reverse_ids[num_tokens - 1 - i] = tmp;
643 }
644
645 /* Copy to output and merge consecutive UNK tokens (SPM behavior) */
646 int out_idx = 0;
647 for (int i = 0; i < num_tokens && out_idx < max_ids; i++) {
648 int32_t token_id = reverse_ids[i];
649
650 /* Merge consecutive UNK tokens into one */
651 if (token_id == tok->unk_id && out_idx > 0 && ids[out_idx - 1] == tok->unk_id) {
652 continue; /* Skip - already have UNK */
653 }
654 ids[out_idx++] = token_id;
655 }
656 if (dbg) {
657 fprintf(stderr, "[SPM] encode done: out=%d\n", out_idx);
658 }
659
660 free(reverse_ids);
661
662 /* If DP failed to produce valid tokens, use byte-fallback */
663 if (num_tokens == 0) {
665 }
666
667 return out_idx;
668}
const char * ck_tokenizer_id_to_token(const CKTokenizer *tok, int32_t id)
float * scores
Definition tokenizer.h:112
size_t types_size
Definition tokenizer.h:115
int32_t unk_id
uint8_t * types
Definition tokenizer.h:114
const char * token
Definition tokenizer.h:307
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 bool spm_token_allowed_in_dp(const CKTokenizer *tok, int32_t token_id)
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)
#define GGUF_TOKEN_USER_DEFINED
static int spm_count_unknown_run(const CKTokenizer *tok, const char *text, int text_len, size_t pos)

References CKTokenizerConfig::add_space_prefix, ck_tokenizer_id_to_token(), CKTokenizer::config, GGUF_TOKEN_USER_DEFINED, ids, max_ids, preprocess_spm_text(), CKTokenizer::scores, spm_count_unknown_run(), spm_encode_byte_fallback(), spm_find_candidates_at_pos(), spm_token_allowed_in_dp(), text, text_len, token, CKTokenizer::types, CKTokenizer::types_size, CKTokenizer::unk_id, and CKTokenizer::vocab_size.

Referenced by ck_tokenizer_encode_spm_plain_segment().

◆ ck_tokenizer_encode_spm_llama_impl()

static int ck_tokenizer_encode_spm_llama_impl ( const CKTokenizer tok,
const char *  text,
int  text_len,
int32_t *  ids,
int  max_ids 
)
static

Definition at line 290 of file tokenizer_spm.c.

294 {
295 if (!tok || !text || !ids || max_ids <= 0) return 0;
296 if (text_len < 0) text_len = (int)strlen(text);
297 if (text_len == 0) return 0;
298
299 char preprocessed[8192];
300 int pp_len = preprocess_spm_llama_text(text, text_len, preprocessed, (int)sizeof(preprocessed) - 1,
302 if (pp_len < 0) return 0;
303 preprocessed[pp_len] = '\0';
304
305 int num_symbols = 0;
306 for (int offs = 0; offs < pp_len;) {
307 int char_len = utf8_len((unsigned char)preprocessed[offs]);
308 if (char_len <= 0) char_len = 1;
309 if (offs + char_len > pp_len) char_len = pp_len - offs;
310 offs += char_len;
311 num_symbols++;
312 }
313 if (num_symbols <= 0) return 0;
314
315 SpmLlamaSymbol *symbols = (SpmLlamaSymbol *)calloc((size_t)num_symbols, sizeof(SpmLlamaSymbol));
316 int node_cap = 2 * num_symbols + 1;
317 SpmLlamaNode *nodes = (SpmLlamaNode *)calloc((size_t)node_cap, sizeof(SpmLlamaNode));
318 if (!symbols || !nodes) {
319 if (symbols) free(symbols);
320 if (nodes) free(nodes);
321 return 0;
322 }
323
324 int index = 0;
325 for (int offs = 0; offs < pp_len && index < num_symbols;) {
326 int char_len = utf8_len((unsigned char)preprocessed[offs]);
327 if (char_len <= 0) char_len = 1;
328 if (offs + char_len > pp_len) char_len = pp_len - offs;
329
330 symbols[index].text = preprocessed + offs;
331 symbols[index].n = char_len;
332 symbols[index].prev = index - 1;
333 symbols[index].next = (index + 1 < num_symbols) ? (index + 1) : -1;
334 symbols[index].node_id = index;
335
336 nodes[index].text = preprocessed + offs;
337 nodes[index].n = char_len;
338 nodes[index].left = -1;
339 nodes[index].right = -1;
340
341 offs += char_len;
342 index++;
343 }
344
345 int node_count = num_symbols;
346 for (;;) {
347 int best_left = -1;
348 int best_right = -1;
349 float best_score = -1e30f;
350
351 for (int left = 0; left != -1; left = symbols[left].next) {
352 int right = symbols[left].next;
353 if (right < 0) continue;
354
355 int pair_len = symbols[left].n + symbols[right].n;
356 int32_t token_id = ck_tokenizer_lookup_exact_n(tok, symbols[left].text, pair_len);
357 if (token_id < 0 || token_id >= (int32_t)tok->vocab_size) continue;
358
359 float score = 0.0f;
360 if (tok->scores && token_id >= 0 && token_id < (int32_t)tok->scores_size) {
361 score = tok->scores[token_id];
362 }
363
364 if (best_left < 0 || score > best_score || (score == best_score && left < best_left)) {
365 best_left = left;
366 best_right = right;
367 best_score = score;
368 }
369 }
370
371 if (best_left < 0 || best_right < 0) break;
372 if (node_count >= node_cap) break;
373
374 SpmLlamaSymbol *left = &symbols[best_left];
375 SpmLlamaSymbol *right = &symbols[best_right];
376
377 int new_node_id = node_count++;
378 nodes[new_node_id].text = left->text;
379 nodes[new_node_id].n = left->n + right->n;
380 nodes[new_node_id].left = left->node_id;
381 nodes[new_node_id].right = right->node_id;
382
383 left->n += right->n;
384 left->node_id = new_node_id;
385 left->next = right->next;
386 if (right->next >= 0) {
387 symbols[right->next].prev = best_left;
388 }
389
390 right->n = 0;
391 right->prev = -1;
392 right->next = -1;
393 }
394
395 int out_idx = 0;
396 for (int i = 0; i != -1 && out_idx < max_ids; i = symbols[i].next) {
397 out_idx = spm_llama_resegment_node(tok, nodes, symbols[i].node_id, ids, max_ids, out_idx);
398 }
399
400 free(symbols);
401 free(nodes);
402 return out_idx;
403}
size_t scores_size
Definition tokenizer.h:113
int32_t float * score
Definition tokenizer.h:328
static int preprocess_spm_llama_text(const char *text, int text_len, char *out, int out_max, bool add_space_prefix)
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)
static int utf8_len(unsigned char c)
const char * left
Definition true_bpe.h:138
const char const char * right
Definition true_bpe.h:139

References CKTokenizerConfig::add_space_prefix, ck_tokenizer_lookup_exact_n(), CKTokenizer::config, ids, left, max_ids, preprocess_spm_llama_text(), right, score, CKTokenizer::scores, CKTokenizer::scores_size, spm_llama_resegment_node(), text, text_len, utf8_len(), and CKTokenizer::vocab_size.

Referenced by ck_tokenizer_encode_spm_plain_segment().

◆ ck_tokenizer_encode_spm_plain_segment()

static int ck_tokenizer_encode_spm_plain_segment ( const CKTokenizer tok,
const char *  text,
int  text_len,
int32_t *  ids,
int  max_ids 
)
static

Definition at line 670 of file tokenizer_spm.c.

674 {
675 if (!tok || !text || text_len <= 0 || !ids || max_ids <= 0) return 0;
676 if (tok->config.spm_mode == CK_SPM_MODE_LLAMA) {
678 }
680}
CKSpmMode spm_mode
Definition tokenizer.h:85
@ CK_SPM_MODE_LLAMA
Definition tokenizer.h:70
static int ck_tokenizer_encode_spm_llama_impl(const CKTokenizer *tok, const char *text, int text_len, int32_t *ids, int max_ids)
static int ck_tokenizer_encode_spm_impl(const CKTokenizer *tok, const char *text, int text_len, int32_t *ids, int max_ids)

References CK_SPM_MODE_LLAMA, ck_tokenizer_encode_spm_impl(), ck_tokenizer_encode_spm_llama_impl(), CKTokenizer::config, ids, max_ids, CKTokenizerConfig::spm_mode, text, and text_len.

Referenced by ck_tokenizer_encode_spm_dispatch().

◆ ck_tokenizer_load_binary_with_scores()

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 
)

Load vocabulary from memory-mapped binary data with scores and types.

This extended version supports SPM (SentencePiece) tokenizers which require token scores for Viterbi/DP encoding.

Parameters
tokTokenizer
vocab_sizeNumber of tokens
offsetsArray of offsets into strings pool
stringsString pool containing null-terminated tokens
scoresArray of token scores (float32), can be NULL
typesArray of token types (uint8), can be NULL
num_mergesNumber of BPE merges
mergesMerge rules as (left, right, merged) triplets
Returns
0 on success, -1 on error

Definition at line 858 of file tokenizer_spm.c.

865 {
866 if (!tok || !offsets || !strings) return -1;
868
869 /* Free any existing scores/types arrays before reallocating */
870 if (tok->scores) {
871 free(tok->scores);
872 tok->scores = NULL;
873 tok->scores_size = 0;
874 }
875 if (tok->types) {
876 free(tok->types);
877 tok->types = NULL;
878 tok->types_size = 0;
879 }
880
881 /* Allocate scores and types arrays if provided */
882 if (scores && vocab_size > 0) {
883 tok->scores = (float *)malloc(vocab_size * sizeof(float));
884 if (!tok->scores) return -1;
885 memcpy(tok->scores, scores, vocab_size * sizeof(float));
886 tok->scores_size = (size_t)vocab_size;
887 }
888 if (types && vocab_size > 0) {
889 tok->types = (uint8_t *)malloc(vocab_size * sizeof(uint8_t));
890 if (!tok->types) {
891 if (tok->scores) {
892 free(tok->scores);
893 tok->scores = NULL;
894 }
895 return -1;
896 }
897 memcpy(tok->types, types, vocab_size * sizeof(uint8_t));
898 tok->types_size = (size_t)vocab_size;
899 }
900
901 for (int i = 0; i < vocab_size; i++) {
902 const char *token = strings + offsets[i];
903 float score = scores ? scores[i] : 0.0f;
905 }
906
907 /* Build byte token lookup table if types are available */
908 if (types && vocab_size > 0) {
910
911 /* Log token type statistics */
912 int count_normal = 0, count_unknown = 0, count_control = 0, count_byte = 0, count_other = 0;
913 int max_type = 0;
914 for (int i = 0; i < vocab_size; i++) {
915 uint8_t t = tok->types[i];
916 if (t > max_type) max_type = t;
917 switch (t) {
918 case GGUF_TOKEN_NORMAL: count_normal++; break;
919 case GGUF_TOKEN_UNKNOWN: count_unknown++; break;
920 case GGUF_TOKEN_CONTROL: count_control++; break;
921 case GGUF_TOKEN_BYTE: count_byte++; break;
922 default: count_other++; break;
923 }
924 }
925 fprintf(stderr, "[TOKENIZER] Loaded %d tokens: normal=%d, unknown=%d, control=%d, byte=%d, other=%d\n",
926 vocab_size, count_normal, count_unknown, count_control, count_byte, count_other);
927 if (max_type > GGUF_TOKEN_BYTE) {
928 fprintf(stderr, "[TOKENIZER] Warning: Unexpected token type %d\n", max_type);
929 }
930 }
931
932 /* TODO: Merges */
933 (void)num_merges; (void)merges;
934 return 0;
935}
int32_t ck_tokenizer_add_token(CKTokenizer *tok, const char *token, int len)
void ck_tokenizer_reset(CKTokenizer *tok)
Definition tokenizer.c:130
#define GGUF_TOKEN_CONTROL
static void spm_build_byte_lookup(CKTokenizer *tok, const char *strings, const int32_t *offsets, int vocab_size)
#define GGUF_TOKEN_BYTE
#define GGUF_TOKEN_UNKNOWN
#define GGUF_TOKEN_NORMAL
int const int32_t const char int num_merges
Definition true_bpe.h:196
int const int32_t const char * strings
Definition true_bpe.h:195
int const int32_t const char int const int32_t * merges
Definition true_bpe.h:197
int vocab_size
Definition true_bpe.h:193
int const int32_t * offsets
Definition true_bpe.h:194

References ck_tokenizer_add_token(), ck_tokenizer_reset(), GGUF_TOKEN_BYTE, GGUF_TOKEN_CONTROL, GGUF_TOKEN_NORMAL, GGUF_TOKEN_UNKNOWN, merges, num_merges, offsets, score, CKTokenizer::scores, CKTokenizer::scores_size, spm_build_byte_lookup(), strings, token, CKTokenizer::types, CKTokenizer::types_size, and vocab_size.

Referenced by ck_tokenizer_load_binary().

◆ ck_tokenizer_lookup_exact()

static int32_t ck_tokenizer_lookup_exact ( const CKTokenizer tok,
const char *  token 
)
static

Definition at line 22 of file tokenizer_spm.c.

22 {
23 if (!tok || !token) return -1;
24 TokenInfo *info = (TokenInfo *)ck_tokenizer_hash_table_lookup(tok->vocab, token);
25 return info ? info->id : -1;
26}
void * ck_tokenizer_hash_table_lookup(CKTokenizerHashTable *table, const char *key)
Definition hash_table.c:202
CKTokenizerHashTable * vocab
Definition tokenizer.h:101

References ck_tokenizer_hash_table_lookup(), token, and CKTokenizer::vocab.

Referenced by ck_tokenizer_lookup_exact_n().

◆ ck_tokenizer_lookup_exact_n()

static int32_t ck_tokenizer_lookup_exact_n ( const CKTokenizer tok,
const char *  text,
int  text_len 
)
static

Definition at line 29 of file tokenizer_spm.c.

29 {
30 if (!tok || !text || text_len <= 0) return -1;
31 char stack_buf[512];
32 char *tmp = stack_buf;
33 if (text_len >= (int)sizeof(stack_buf)) {
34 tmp = (char *)malloc((size_t)text_len + 1);
35 if (!tmp) return -1;
36 }
37 memcpy(tmp, text, (size_t)text_len);
38 tmp[text_len] = '\0';
39 int32_t id = ck_tokenizer_lookup_exact(tok, tmp);
40 if (tmp != stack_buf) free(tmp);
41 return id;
42}
int32_t id
Definition tokenizer.h:316
static int32_t ck_tokenizer_lookup_exact(const CKTokenizer *tok, const char *token)

References ck_tokenizer_lookup_exact(), id, text, and text_len.

Referenced by ck_tokenizer_encode_spm_llama_impl(), and spm_llama_resegment_node().

◆ preprocess_spm_llama_text()

static int preprocess_spm_llama_text ( const char *  text,
int  text_len,
char *  out,
int  out_max,
bool  add_space_prefix 
)
static

Definition at line 202 of file tokenizer_spm.c.

202 {
203 int out_len = 0;
204 if (text_len < 0) text_len = (int)strlen(text);
205
206 if (add_space_prefix && text_len > 0) {
207 if (out_len + 3 > out_max) return -1;
208 out[out_len++] = (char)0xE2;
209 out[out_len++] = (char)0x96;
210 out[out_len++] = (char)0x81;
211 }
212
213 for (int i = 0; i < text_len;) {
214 if (text[i] == ' ') {
215 int j = i;
216 while (j < text_len && text[j] == ' ') {
217 j++;
218 }
219 int run = j - i;
220
221 /* Match llama.cpp behavior for this GGUF family:
222 * single separators map to ▁, but multi-space runs remain literal. */
223 if (run == 1) {
224 if (out_len + 3 > out_max) return -1;
225 out[out_len++] = (char)0xE2;
226 out[out_len++] = (char)0x96;
227 out[out_len++] = (char)0x81;
228 } else {
229 if (out_len + run > out_max) return -1;
230 for (int k = 0; k < run; k++) {
231 out[out_len++] = ' ';
232 }
233 }
234 i = j;
235 } else {
236 if (out_len + 1 > out_max) return -1;
237 out[out_len++] = text[i++];
238 }
239 }
240
241 return out_len;
242}
bool add_space_prefix
Definition tokenizer.h:253
const int32_t int int * out_len
Definition tokenizer.h:446

References add_space_prefix, out_len, text, and text_len.

Referenced by ck_tokenizer_encode_spm_llama_impl().

◆ preprocess_spm_text()

static int preprocess_spm_text ( const char *  text,
int  text_len,
char *  out,
int  out_max,
bool  add_space_prefix 
)
static

Definition at line 411 of file tokenizer_spm.c.

411 {
412 int out_len = 0;
413
414 /* Count leading spaces */
415 int lead_spaces = 0;
416 while (lead_spaces < text_len && text[lead_spaces] == ' ') {
417 lead_spaces++;
418 }
419
420 /* Count trailing spaces */
421 int trail_spaces = 0;
422 while (trail_spaces < text_len - lead_spaces &&
423 text[text_len - 1 - trail_spaces] == ' ') {
424 trail_spaces++;
425 }
426
427 /* Add ▁ at start if there's any non-space content AND text doesn't already start with ▁ */
428 int content_len = text_len - lead_spaces - trail_spaces;
429 int starts_with_prefix = (text_len >= 3 &&
430 (unsigned char)text[0] == 0xE2 &&
431 (unsigned char)text[1] == 0x96 &&
432 (unsigned char)text[2] == 0x81);
433 int inserted_prefix = 0;
434 if (content_len > 0 && !starts_with_prefix && add_space_prefix) {
435 if (out_len + 3 > out_max) return -1;
436 out[out_len++] = (char)0xE2;
437 out[out_len++] = (char)0x96;
438 out[out_len++] = (char)0x81;
439 inserted_prefix = 1;
440 }
441
442 /* Process middle content: collapse multiple spaces to single ▁ */
443 int i = lead_spaces;
444 int last_was_space = (starts_with_prefix || inserted_prefix) ? 1 : 0;
445 while (i < text_len - trail_spaces) {
446 if (text[i] == ' ') {
447 if (!last_was_space) {
448 /* First space after content - add ▁ */
449 if (out_len + 3 > out_max) return -1;
450 out[out_len++] = (char)0xE2;
451 out[out_len++] = (char)0x96;
452 out[out_len++] = (char)0x81;
453 last_was_space = 1;
454 }
455 /* Skip additional consecutive spaces */
456 } else {
457 if (out_len + 1 > out_max) return -1;
458 out[out_len++] = text[i];
459 last_was_space = 0;
460 }
461 i++;
462 }
463
464 return out_len;
465}

References add_space_prefix, out_len, text, and text_len.

Referenced by ck_tokenizer_encode_spm_impl().

◆ spm_build_byte_lookup()

static void spm_build_byte_lookup ( CKTokenizer tok,
const char *  strings,
const int32_t *  offsets,
int  vocab_size 
)
static

Definition at line 155 of file tokenizer_spm.c.

155 {
156 /* Reuse existing array or allocate new one */
157 if (!tok->byte_token_id) {
158 tok->byte_token_id = (int32_t *)malloc(256 * sizeof(int32_t));
159 if (!tok->byte_token_id) return;
160 }
161
162 /* Initialize all entries to -1 */
163 for (int i = 0; i < 256; i++) {
164 tok->byte_token_id[i] = -1;
165 }
166
167 /* Scan vocab for byte tokens */
168 for (int i = 0; i < vocab_size; i++) {
169 if (!tok->types || tok->types[i] != GGUF_TOKEN_BYTE) continue;
170
171 const char *token = strings + offsets[i];
172 size_t len = strlen(token);
173
174 if (len == 1) {
175 /* Raw byte token (single byte) */
176 unsigned char byte_val = (unsigned char)token[0];
177 tok->byte_token_id[byte_val] = i;
178 } else if (spm_token_is_byte_format(token)) {
179 /* <0xXX> format - parse the hex value */
180 unsigned int byte_val;
181 if (sscanf(token, "<0x%02X>", &byte_val) == 1 && byte_val < 256) {
182 tok->byte_token_id[byte_val] = i;
183 }
184 }
185 }
186}
int32_t * byte_token_id
Definition tokenizer.h:118
static bool spm_token_is_byte_format(const char *token)

References CKTokenizer::byte_token_id, GGUF_TOKEN_BYTE, offsets, spm_token_is_byte_format(), strings, token, CKTokenizer::types, and vocab_size.

Referenced by ck_tokenizer_load_binary_with_scores().

◆ spm_count_unknown_run()

static int spm_count_unknown_run ( const CKTokenizer tok,
const char *  text,
int  text_len,
size_t  pos 
)
static

Definition at line 758 of file tokenizer_spm.c.

758 {
759 int run = 0;
760 while (pos + run < (size_t)text_len) {
761 /* Stop at '▁' (U+2581 = 0xE2 0x96 0x81) since that's a known token */
762 if (pos + run + 3 <= (size_t)text_len &&
763 (unsigned char)text[pos + run] == 0xE2 &&
764 (unsigned char)text[pos + run + 1] == 0x96 &&
765 (unsigned char)text[pos + run + 2] == 0x81) {
766 break;
767 }
768
769 /* Check if any vocab token matches at this position */
770 int max_len = 64;
771 if (pos + run + max_len > (size_t)text_len) {
772 max_len = (int)(text_len - pos - run);
773 }
774 int found = 0;
775 for (int len = max_len; len >= 1; len--) {
776 char tmp[65];
777 memcpy(tmp, text + pos + run, len);
778 tmp[len] = '\0';
779 TokenInfo *info = (TokenInfo *)ck_tokenizer_hash_table_lookup(tok->vocab, tmp);
780 if (info && info->id >= 0 && info->id != tok->unk_id && spm_token_allowed_in_dp(tok, info->id)) {
781 found = 1;
782 break;
783 }
784 }
785 if (found) break;
786 run++;
787 }
788 return run;
789}
const int32_t int char int max_len
Definition true_bpe.h:288

References ck_tokenizer_hash_table_lookup(), max_len, spm_token_allowed_in_dp(), text, text_len, CKTokenizer::unk_id, and CKTokenizer::vocab.

Referenced by ck_tokenizer_encode_spm_impl().

◆ spm_encode_byte_fallback()

static int spm_encode_byte_fallback ( const CKTokenizer tok,
const char *  text,
int  text_len,
int32_t *  ids,
int  max_ids 
)
static

Definition at line 684 of file tokenizer_spm.c.

686 {
687 if (!tok || !text || !ids || max_ids <= 0) return 0;
688 if (text_len < 0) text_len = (int)strlen(text);
689 if (text_len == 0) return 0;
690
691 int count = 0;
692 for (int i = 0; i < text_len && count < max_ids; i++) {
693 unsigned char byte_val = (unsigned char)text[i];
694 int32_t byte_token = spm_get_byte_token(tok, byte_val);
695
696 /* If we have a byte token, use it; otherwise use UNK */
697 if (byte_token >= 0 && byte_token != tok->unk_id) {
698 ids[count++] = byte_token;
699 } else {
700 ids[count++] = tok->unk_id;
701 }
702 }
703 return count;
704}
static int32_t spm_get_byte_token(const CKTokenizer *tok, unsigned char byte_val)

References ids, max_ids, spm_get_byte_token(), text, text_len, and CKTokenizer::unk_id.

Referenced by ck_tokenizer_encode_spm_impl().

◆ spm_find_candidates_at_pos()

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

Definition at line 707 of file tokenizer_spm.c.

708 {
709 if (!tok || !text || pos >= (size_t)text_len) return 0;
710
711 int num_found = 0;
712 int max_len = 64;
713 if (pos + max_len > (size_t)text_len) max_len = (int)(text_len - pos);
714
715 /* Iterate from longest to shortest to find all matches */
716 char tmp[65];
717 for (int len = max_len; len >= 1 && num_found < max_candidates; len--) {
718 memcpy(tmp, text + pos, len);
719 tmp[len] = '\0';
720
721 TokenInfo *info = (TokenInfo *)ck_tokenizer_hash_table_lookup(tok->vocab, tmp);
722 if (info && info->id >= 0 && info->id != tok->unk_id) {
723 /* Skip disallowed token types */
724 if (!spm_token_allowed_in_dp(tok, info->id)) {
725 continue;
726 }
727
728 /* Check if already added */
729 int dup = 0;
730 for (int j = 0; j < num_found; j++) {
731 if (candidates[j] == info->id) {
732 dup = 1;
733 break;
734 }
735 }
736 if (!dup) {
737 candidates[num_found++] = info->id;
738 }
739 }
740 }
741
742 /* If no candidates found, add UNK token as fallback.
743 * For SPM, UNK should cover all consecutive unknown bytes until a known token or end.
744 * We handle this by adding UNK with a special marker - we'll check at runtime
745 * how far we can extend it. */
746 if (num_found == 0 && tok->unk_id >= 0 && max_candidates > 0) {
747 /* Only add UNK if it's allowed in DP */
748 if (spm_token_allowed_in_dp(tok, tok->unk_id)) {
749 candidates[num_found++] = tok->unk_id;
750 }
751 }
752
753 return num_found;
754}

References ck_tokenizer_hash_table_lookup(), max_len, spm_token_allowed_in_dp(), text, text_len, CKTokenizer::unk_id, and CKTokenizer::vocab.

Referenced by ck_tokenizer_encode_spm_impl().

◆ spm_find_special_token_at_pos()

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 
)
static

Definition at line 45 of file tokenizer_spm.c.

49 {
50 if (match_len) *match_len = 0;
51 if (!tok || !text || text_len <= 0 || pos >= (size_t)text_len) return -1;
52
53 if (tok->vocab_trie && tok->vocab_trie->root) {
54 CKTrieNode *node = tok->vocab_trie->root;
55 CKTrieNode *best_node = NULL;
56 size_t best_len = 0;
57 size_t cur = pos;
58
59 while (cur < (size_t)text_len && node) {
60 unsigned char c = (unsigned char)text[cur];
61 if (!node->children[c]) break;
62 node = node->children[c];
63 cur++;
64 if (node->token_id >= 0 && node->is_special) {
65 best_node = node;
66 best_len = cur - pos;
67 }
68 }
69
70 if (best_node) {
71 if (match_len) *match_len = best_len;
72 return best_node->token_id;
73 }
74 }
75
77 if (pos + (size_t)max_len > (size_t)text_len) {
78 max_len = (int)((size_t)text_len - pos);
79 }
80 char tmp[CK_TOKENIZER_MAX_TOKEN_LEN + 1];
81 for (int len = max_len; len >= 1; len--) {
82 memcpy(tmp, text + pos, (size_t)len);
83 tmp[len] = '\0';
84 TokenInfo *info = (TokenInfo *)ck_tokenizer_hash_table_lookup(tok->vocab, tmp);
85 if (info && info->id >= 0 && info->is_special) {
86 if (match_len) *match_len = (size_t)len;
87 return info->id;
88 }
89 }
90 return -1;
91}
CKTrie * vocab_trie
Definition tokenizer.h:104
struct CKTrieNode * children[256]
#define CK_TOKENIZER_MAX_TOKEN_LEN
Definition tokenizer.h:44

References CKTrieNode::children, ck_tokenizer_hash_table_lookup(), CK_TOKENIZER_MAX_TOKEN_LEN, CKTrieNode::is_special, max_len, CKTrie::root, text, text_len, CKTrieNode::token_id, CKTokenizer::vocab, and CKTokenizer::vocab_trie.

Referenced by ck_tokenizer_encode_spm_dispatch().

◆ spm_get_byte_token()

static int32_t spm_get_byte_token ( const CKTokenizer tok,
unsigned char  byte_val 
)
inlinestatic

Definition at line 135 of file tokenizer_spm.c.

135 {
136 /* Try fast lookup table first */
137 if (tok->byte_token_id && tok->byte_token_id[byte_val] >= 0) {
138 return tok->byte_token_id[byte_val];
139 }
140 /* Fallback to <0xXX> format */
141 char byte_token[16];
142 int len = snprintf(byte_token, sizeof(byte_token), "<0x%02X>", byte_val);
143 if (len <= 0) return tok->unk_id;
144 return ck_tokenizer_lookup(tok, byte_token);
145}
int32_t ck_tokenizer_lookup(const CKTokenizer *tok, const char *token, int len)

References CKTokenizer::byte_token_id, ck_tokenizer_lookup(), and CKTokenizer::unk_id.

Referenced by spm_encode_byte_fallback(), and spm_llama_resegment_node().

◆ spm_is_byte_token()

static bool spm_is_byte_token ( const CKTokenizer tok,
int32_t  token_id 
)
inlinestatic

Definition at line 127 of file tokenizer_spm.c.

127 {
128 if (!tok->types || token_id < 0 || token_id >= (int32_t)tok->vocab_size) {
129 return false;
130 }
131 return tok->types[token_id] == GGUF_TOKEN_BYTE;
132}

References GGUF_TOKEN_BYTE, CKTokenizer::types, and CKTokenizer::vocab_size.

◆ spm_llama_resegment_node()

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

Definition at line 259 of file tokenizer_spm.c.

264 {
265 if (!tok || !nodes || node_id < 0 || !ids || out_idx >= max_ids) {
266 return out_idx;
267 }
268
269 const SpmLlamaNode *node = &nodes[node_id];
270 int32_t token_id = ck_tokenizer_lookup_exact_n(tok, node->text, node->n);
271 if (token_id >= 0) {
272 ids[out_idx++] = token_id;
273 return out_idx;
274 }
275
276 if (node->left >= 0 && node->right >= 0) {
277 out_idx = spm_llama_resegment_node(tok, nodes, node->left, ids, max_ids, out_idx);
278 out_idx = spm_llama_resegment_node(tok, nodes, node->right, ids, max_ids, out_idx);
279 return out_idx;
280 }
281
282 for (int i = 0; i < node->n && out_idx < max_ids; i++) {
283 int32_t byte_token = spm_get_byte_token(tok, (unsigned char)node->text[i]);
284 ids[out_idx++] = (byte_token >= 0) ? byte_token : tok->unk_id;
285 }
286 return out_idx;
287}

References ck_tokenizer_lookup_exact_n(), ids, max_ids, spm_get_byte_token(), spm_llama_resegment_node(), and CKTokenizer::unk_id.

Referenced by ck_tokenizer_encode_spm_llama_impl(), and spm_llama_resegment_node().

◆ spm_token_allowed_in_dp()

static bool spm_token_allowed_in_dp ( const CKTokenizer tok,
int32_t  token_id 
)
inlinestatic

Definition at line 117 of file tokenizer_spm.c.

117 {
118 if (!tok->types || token_id < 0 || token_id >= (int32_t)tok->vocab_size) {
119 return true; /* No type info, allow all */
120 }
121 uint8_t t = tok->types[token_id];
122 /* Reject CONTROL, UNUSED, and BYTE tokens (but allow UNKNOWN for fallback) */
123 return t != GGUF_TOKEN_CONTROL && t != GGUF_TOKEN_UNUSED && t != GGUF_TOKEN_BYTE;
124}
#define GGUF_TOKEN_UNUSED

References GGUF_TOKEN_BYTE, GGUF_TOKEN_CONTROL, GGUF_TOKEN_UNUSED, CKTokenizer::types, and CKTokenizer::vocab_size.

Referenced by ck_tokenizer_encode_spm_impl(), spm_count_unknown_run(), and spm_find_candidates_at_pos().

◆ spm_token_is_byte_format()

static bool spm_token_is_byte_format ( const char *  token)
inlinestatic

Definition at line 148 of file tokenizer_spm.c.

148 {
149 return token && token[0] == '<' && token[1] == '0' &&
150 token[2] == 'x' && token[3] >= '0' && token[3] <= 'F' &&
151 token[4] >= '0' && token[4] <= 'F' && token[5] == '>';
152}

References token.

Referenced by spm_build_byte_lookup().

◆ utf8_len()

static int utf8_len ( unsigned char  c)
inlinestatic

Definition at line 189 of file tokenizer_spm.c.

189 {
190 if ((c & 0x80) == 0x00) return 1; /* ASCII */
191 if ((c & 0xE0) == 0xC0) return 2; /* 2-byte sequence */
192 if ((c & 0xF0) == 0xE0) return 3; /* 3-byte sequence */
193 if ((c & 0xF8) == 0xF0) return 4; /* 4-byte sequence */
194 return 1; /* Invalid, treat as 1 byte */
195}

Referenced by ck_tokenizer_encode(), and ck_tokenizer_encode_spm_llama_impl().