← Back to C-Kernel-Engine Docs Doxygen Source Documentation
 
Loading...
Searching...
No Matches
ck_tokenizer.c File Reference
#include "ck_tokenizer.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

Go to the source code of this file.

Functions

void * ck_pool_alloc (CKMemPool *pool, size_t size)
 
void ck_pool_free (CKMemPool *pool)
 
void ck_pool_init (CKMemPool *pool)
 
char * ck_pool_strdup (CKMemPool *pool, const char *s, int len)
 
int ck_tokenizer_add_merge (CKTokenizer *tok, int32_t left, int32_t right, int32_t merged)
 
int32_t ck_tokenizer_add_token (CKTokenizer *tok, const char *token, int len)
 
int ck_tokenizer_decode (const CKTokenizer *tok, const int32_t *ids, int num_ids, char *text, int max_len)
 
int ck_tokenizer_encode (const CKTokenizer *tok, const char *text, int text_len, int32_t *ids, int max_ids)
 
void ck_tokenizer_free (CKTokenizer *tok)
 
const char * ck_tokenizer_id_to_token (const CKTokenizer *tok, int32_t id)
 
int ck_tokenizer_init (CKTokenizer *tok)
 
int ck_tokenizer_load (CKTokenizer *tok, const char *path)
 
int32_t ck_tokenizer_lookup (const CKTokenizer *tok, const char *token, int len)
 
int ck_tokenizer_lookup_merge (const CKTokenizer *tok, int32_t left, int32_t right)
 
static uint32_t hash_pair (int32_t left, int32_t right)
 
static uint32_t hash_string (const char *s, int len)
 
static int json_match_char (JSONParser *p, char c)
 
static int json_parse_int (JSONParser *p, int *out)
 
static int json_parse_string (JSONParser *p, char *buf, int max_len)
 
static void json_skip_value (JSONParser *p)
 
static void json_skip_whitespace (JSONParser *p)
 
static CKPoolBlockpool_new_block (size_t capacity)
 

Function Documentation

◆ ck_pool_alloc()

void * ck_pool_alloc ( CKMemPool pool,
size_t  size 
)

Definition at line 69 of file ck_tokenizer.c.

69 {
70 /* Align to 8 bytes */
71 size = (size + 7) & ~7;
72
73 /* Check if current block has space */
74 if (pool->current && pool->current->used + size <= pool->current->capacity) {
75 void *ptr = pool->current->data + pool->current->used;
76 pool->current->used += size;
77 pool->total_allocated += size;
78 return ptr;
79 }
80
81 /* Need new block */
82 size_t block_size = CK_POOL_BLOCK_SIZE;
83 if (size > block_size) block_size = size;
84
85 CKPoolBlock *block = pool_new_block(block_size);
86 if (!block) return NULL;
87
88 block->next = pool->head;
89 pool->head = block;
90 pool->current = block;
91
92 void *ptr = block->data;
93 block->used = size;
94 pool->total_allocated += size;
95 return ptr;
96}
static CKPoolBlock * pool_new_block(size_t capacity)
#define CK_POOL_BLOCK_SIZE
CKPoolBlock * current
CKPoolBlock * head
size_t total_allocated
uint8_t * data
struct CKPoolBlock * next

References CK_POOL_BLOCK_SIZE, CKMemPool::current, CKPoolBlock::data, CKMemPool::head, CKPoolBlock::next, pool_new_block(), CKMemPool::total_allocated, and CKPoolBlock::used.

Referenced by ck_pool_strdup(), ck_tokenizer_add_token(), ck_tokenizer_load(), and ck_tokenizer_load_binary().

◆ ck_pool_free()

void ck_pool_free ( CKMemPool pool)

Definition at line 107 of file ck_tokenizer.c.

107 {
108 CKPoolBlock *block = pool->head;
109 while (block) {
110 CKPoolBlock *next = block->next;
111 free(block->data);
112 free(block);
113 block = next;
114 }
115 memset(pool, 0, sizeof(*pool));
116}

References CKPoolBlock::data, CKMemPool::head, and CKPoolBlock::next.

Referenced by ck_tokenizer_free().

◆ ck_pool_init()

void ck_pool_init ( CKMemPool pool)

Definition at line 51 of file ck_tokenizer.c.

51 {
52 memset(pool, 0, sizeof(*pool));
53}

Referenced by ck_tokenizer_init().

◆ ck_pool_strdup()

char * ck_pool_strdup ( CKMemPool pool,
const char *  s,
int  len 
)

Definition at line 98 of file ck_tokenizer.c.

98 {
99 if (len < 0) len = (int)strlen(s);
100 char *copy = (char *)ck_pool_alloc(pool, len + 1);
101 if (!copy) return NULL;
102 memcpy(copy, s, len);
103 copy[len] = '\0';
104 return copy;
105}
void * ck_pool_alloc(CKMemPool *pool, size_t size)

References ck_pool_alloc().

Referenced by ck_tokenizer_add_token(), and ck_tokenizer_load().

◆ ck_tokenizer_add_merge()

int ck_tokenizer_add_merge ( CKTokenizer tok,
int32_t  left,
int32_t  right,
int32_t  merged 
)

Definition at line 248 of file ck_tokenizer.c.

248 {
249 int idx = tok->num_merges;
250
251 /* Grow merges array if needed */
252 if (idx % 4096 == 0) {
253 size_t new_cap = (idx + 4096) * sizeof(CKMergeRule);
254 CKMergeRule *new_merges = (CKMergeRule *)realloc(tok->merges, new_cap);
255 if (!new_merges) return -1;
256 tok->merges = new_merges;
257 }
258
259 tok->merges[idx].left = left;
260 tok->merges[idx].right = right;
261 tok->merges[idx].merged = merged;
262 tok->merges[idx].priority = idx; /* Earlier = higher priority */
263
264 /* Add to hash table */
265 uint32_t bucket = hash_pair(left, right) % tok->merge_hash_size;
266 /* Linear probing */
267 while (tok->merge_hash[bucket] >= 0) {
268 bucket = (bucket + 1) % tok->merge_hash_size;
269 }
270 tok->merge_hash[bucket] = idx;
271
272 tok->num_merges++;
273 return 0;
274}
static uint32_t hash_pair(int32_t left, int32_t right)
int32_t left
int32_t right
int32_t merged
int merge_hash_size
CKMergeRule * merges
int * merge_hash
const char * left
Definition true_bpe.h:138
const char const char * right
Definition true_bpe.h:139

References hash_pair(), CKMergeRule::left, left, CKTokenizer::merge_hash, CKTokenizer::merge_hash_size, CKMergeRule::merged, CKTokenizer::merges, CKTokenizer::num_merges, CKMergeRule::priority, CKMergeRule::right, and right.

Referenced by ck_tokenizer_load(), and ck_tokenizer_load_binary().

◆ ck_tokenizer_add_token()

int32_t ck_tokenizer_add_token ( CKTokenizer tok,
const char *  token,
int  len 
)

Definition at line 196 of file ck_tokenizer.c.

196 {
197 if (len < 0) len = (int)strlen(token);
198 if (tok->vocab_size >= CK_MAX_VOCAB_SIZE) return -1;
199
200 /* Check if already exists */
201 int32_t existing = ck_tokenizer_lookup(tok, token, len);
202 if (existing != tok->unk_id || (len == 0)) {
203 return existing;
204 }
205
206 /* Create new entry */
207 CKVocabEntry *entry = (CKVocabEntry *)ck_pool_alloc(&tok->pool, sizeof(CKVocabEntry));
208 if (!entry) return -1;
209
210 entry->token = ck_pool_strdup(&tok->pool, token, len);
211 if (!entry->token) return -1;
212 entry->token_len = len;
213 entry->id = tok->vocab_size;
214
215 /* Add to hash table */
216 uint32_t bucket = hash_string(token, len) % tok->vocab_hash_size;
217 entry->next = tok->vocab_hash[bucket];
218 tok->vocab_hash[bucket] = entry;
219
220 /* Add to reverse lookup */
221 tok->id_to_token[tok->vocab_size] = entry->token;
222
223 tok->vocab_size++;
224 return entry->id;
225}
int32_t ck_tokenizer_lookup(const CKTokenizer *tok, const char *token, int len)
static uint32_t hash_string(const char *s, int len)
char * ck_pool_strdup(CKMemPool *pool, const char *s, int len)
#define CK_MAX_VOCAB_SIZE
CKMemPool pool
int32_t unk_id
CKVocabEntry ** vocab_hash
int vocab_hash_size
char ** id_to_token
struct CKVocabEntry * next
const char * token
Definition tokenizer.h:307

References CK_MAX_VOCAB_SIZE, ck_pool_alloc(), ck_pool_strdup(), ck_tokenizer_lookup(), hash_string(), CKVocabEntry::id, CKTokenizer::id_to_token, CKVocabEntry::next, CKTokenizer::pool, CKVocabEntry::token, token, CKVocabEntry::token_len, CKTokenizer::unk_id, CKTokenizer::vocab_hash, CKTokenizer::vocab_hash_size, and CKTokenizer::vocab_size.

Referenced by ck_tokenizer_load(), ck_tokenizer_load_binary_with_scores(), and main().

◆ ck_tokenizer_decode()

int ck_tokenizer_decode ( const CKTokenizer tok,
const int32_t *  ids,
int  num_ids,
char *  text,
int  max_len 
)

Decode token IDs to text.

Parameters
tokTokenizer
idsInput token IDs
num_idsNumber of IDs
textOutput text buffer
max_lenMaximum text length
Returns
Number of bytes written

Definition at line 734 of file ck_tokenizer.c.

738 {
739 int len = 0;
740
741 for (int i = 0; i < num_ids; i++) {
742 /* Skip special tokens */
743 if (ids[i] == tok->bos_id || ids[i] == tok->eos_id || ids[i] == tok->pad_id) {
744 continue;
745 }
746
747 const char *token = ck_tokenizer_id_to_token(tok, ids[i]);
748 if (!token) continue;
749
750 int token_len = (int)strlen(token);
751
752 /* Handle byte tokens <0xXX> */
753 if (token_len == 6 && token[0] == '<' && token[1] == '0' && token[2] == 'x') {
754 char hex[3] = {token[3], token[4], 0};
755 unsigned int byte = (unsigned int)strtol(hex, NULL, 16);
756 if (len < max_len - 1) {
757 text[len++] = (char)byte;
758 }
759 continue;
760 }
761
762 /* Handle tokenizer space prefixes:
763 * GPT/byte-level BPE: Ġ (0xC4 0xA0)
764 * SentencePiece BPE: ▁ (0xE2 0x96 0x81)
765 */
766 const char *src = token;
767 if ((unsigned char)token[0] == 0xC4 && (unsigned char)token[1] == 0xA0) {
768 if (len < max_len - 1) {
769 text[len++] = ' ';
770 }
771 src = token + 2;
772 token_len -= 2;
773 } else if ((unsigned char)token[0] == 0xE2 &&
774 (unsigned char)token[1] == 0x96 &&
775 (unsigned char)token[2] == 0x81) {
776 if (len < max_len - 1) {
777 text[len++] = ' ';
778 }
779 src = token + 3;
780 token_len -= 3;
781 }
782
783 /* Copy token, normalizing any embedded SentencePiece markers too.
784 * Some Gemma tokenizer pieces contain repeated ▁ markers after
785 * indentation or punctuation; those are word-boundary spaces, not
786 * literal output characters.
787 */
788 for (int j = 0; j < token_len && len < max_len - 1; ) {
789 if (j + 2 < token_len &&
790 (unsigned char)src[j] == 0xE2 &&
791 (unsigned char)src[j + 1] == 0x96 &&
792 (unsigned char)src[j + 2] == 0x81) {
793 text[len++] = ' ';
794 j += 3;
795 continue;
796 }
797 text[len++] = src[j++];
798 }
799 }
800
801 text[len] = '\0';
802 return len;
803}
const char * ck_tokenizer_id_to_token(const CKTokenizer *tok, int32_t id)
int32_t bos_id
int32_t eos_id
int32_t pad_id
const int32_t * ids
Definition tokenizer.h:444
const int32_t int num_ids
Definition tokenizer.h:445
const char * text
Definition tokenizer.h:564
const int32_t int char int max_len
Definition true_bpe.h:288

◆ ck_tokenizer_encode()

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

Encode text to token IDs using greedy longest-match.

For BPE: applies merge rules iteratively. For WordPiece/SPM: greedy longest-match from vocabulary.

Parameters
tokTokenizer
textInput text
text_lenText length, or -1 for null-terminated
idsOutput token IDs
max_idsMaximum IDs to write
Returns
Number of tokens written

Definition at line 635 of file ck_tokenizer.c.

639 {
640 if (text_len < 0) text_len = (int)strlen(text);
641
642 /* Pre-tokenize: split on whitespace, keep spaces as tokens */
643 /* For simplicity, treat each byte as initial token, then apply BPE */
644
645 /* Initial tokens: one per byte */
646 int32_t *tokens = (int32_t *)malloc(text_len * sizeof(int32_t));
647 int num_tokens = 0;
648
649 for (int i = 0; i < text_len; i++) {
650 /* Look up single-character token */
651 char c[2] = {text[i], '\0'};
652 int32_t id = ck_tokenizer_lookup(tok, c, 1);
653
654 /* Handle special byte tokens like <0xXX> */
655 if (id == tok->unk_id) {
656 char byte_token[8];
657 snprintf(byte_token, sizeof(byte_token), "<0x%02X>", (unsigned char)text[i]);
658 id = ck_tokenizer_lookup(tok, byte_token, -1);
659 }
660
661 /* Try UTF-8 multi-byte sequences */
662 if (id == tok->unk_id && (unsigned char)text[i] >= 0x80) {
663 int utf8_len = 1;
664 if ((text[i] & 0xE0) == 0xC0) utf8_len = 2;
665 else if ((text[i] & 0xF0) == 0xE0) utf8_len = 3;
666 else if ((text[i] & 0xF8) == 0xF0) utf8_len = 4;
667
668 if (i + utf8_len <= text_len) {
669 id = ck_tokenizer_lookup(tok, text + i, utf8_len);
670 if (id != tok->unk_id) {
671 tokens[num_tokens++] = id;
672 i += utf8_len - 1;
673 continue;
674 }
675 }
676 }
677
678 tokens[num_tokens++] = id;
679 }
680
681 /* Apply BPE merges iteratively */
682 bool changed = true;
683 while (changed && num_tokens > 1) {
684 changed = false;
685
686 /* Find best merge (lowest priority = earliest in merge list) */
687 int best_pos = -1;
688 int best_priority = tok->num_merges;
689
690 for (int i = 0; i < num_tokens - 1; i++) {
691 int merge_idx = ck_tokenizer_lookup_merge(tok, tokens[i], tokens[i + 1]);
692 if (merge_idx >= 0 && tok->merges[merge_idx].priority < best_priority) {
693 best_pos = i;
694 best_priority = tok->merges[merge_idx].priority;
695 }
696 }
697
698 if (best_pos >= 0) {
699 int merge_idx = ck_tokenizer_lookup_merge(tok, tokens[best_pos], tokens[best_pos + 1]);
700 tokens[best_pos] = tok->merges[merge_idx].merged;
701
702 /* Shift remaining tokens */
703 for (int i = best_pos + 1; i < num_tokens - 1; i++) {
704 tokens[i] = tokens[i + 1];
705 }
706 num_tokens--;
707 changed = true;
708 }
709 }
710
711 /* Copy to output */
712 int out_len = 0;
713
714 if (tok->add_bos && out_len < max_ids) {
715 ids[out_len++] = tok->bos_id;
716 }
717
718 for (int i = 0; i < num_tokens && out_len < max_ids; i++) {
719 ids[out_len++] = tokens[i];
720 }
721
722 if (tok->add_eos && out_len < max_ids) {
723 ids[out_len++] = tok->eos_id;
724 }
725
726 free(tokens);
727 return out_len;
728}
int ck_tokenizer_lookup_merge(const CKTokenizer *tok, int32_t left, int32_t right)
int32_t id
Definition tokenizer.h:316
const int32_t int int * out_len
Definition tokenizer.h:446
static int utf8_len(unsigned char c)
const char int text_len
Definition true_bpe.h:270
const char int int32_t int max_ids
Definition true_bpe.h:272

◆ ck_tokenizer_free()

void ck_tokenizer_free ( CKTokenizer tok)

Definition at line 183 of file ck_tokenizer.c.

183 {
184 ck_pool_free(&tok->pool);
185 free(tok->vocab_hash);
186 free(tok->id_to_token);
187 free(tok->merges);
188 free(tok->merge_hash);
189 memset(tok, 0, sizeof(*tok));
190}
void ck_pool_free(CKMemPool *pool)

◆ ck_tokenizer_id_to_token()

const char * ck_tokenizer_id_to_token ( const CKTokenizer tok,
int32_t  id 
)

Definition at line 239 of file ck_tokenizer.c.

239 {
240 if (id < 0 || id >= tok->vocab_size) return NULL;
241 return tok->id_to_token[id];
242}

Referenced by ck_tokenizer_decode().

◆ ck_tokenizer_init()

int ck_tokenizer_init ( CKTokenizer tok)

Definition at line 148 of file ck_tokenizer.c.

148 {
149 memset(tok, 0, sizeof(*tok));
150 ck_pool_init(&tok->pool);
151
152 /* Default special tokens */
153 tok->unk_id = 0;
154 tok->bos_id = 1;
155 tok->eos_id = 2;
156 tok->pad_id = 3;
157
158 /* Allocate vocab hash table */
159 tok->vocab_hash_size = 65536; /* 64K buckets */
160 tok->vocab_hash = (CKVocabEntry **)calloc(tok->vocab_hash_size, sizeof(CKVocabEntry *));
161 if (!tok->vocab_hash) return -1;
162
163 /* Allocate reverse vocab */
164 tok->id_to_token = (char **)calloc(CK_MAX_VOCAB_SIZE, sizeof(char *));
165 if (!tok->id_to_token) {
166 free(tok->vocab_hash);
167 return -1;
168 }
169
170 /* Allocate merge hash table */
171 tok->merge_hash_size = 262144; /* 256K buckets */
172 tok->merge_hash = (int *)malloc(tok->merge_hash_size * sizeof(int));
173 if (!tok->merge_hash) {
174 free(tok->vocab_hash);
175 free(tok->id_to_token);
176 return -1;
177 }
178 memset(tok->merge_hash, -1, tok->merge_hash_size * sizeof(int));
179
180 return 0;
181}
void ck_pool_init(CKMemPool *pool)

References CKTokenizer::bos_id, CK_MAX_VOCAB_SIZE, ck_pool_init(), CKTokenizer::eos_id, CKTokenizer::id_to_token, CKTokenizer::merge_hash, CKTokenizer::merge_hash_size, CKTokenizer::pad_id, CKTokenizer::pool, CKTokenizer::unk_id, CKTokenizer::vocab_hash, and CKTokenizer::vocab_hash_size.

Referenced by run_inference().

◆ ck_tokenizer_load()

int ck_tokenizer_load ( CKTokenizer tok,
const char *  path 
)

Definition at line 432 of file ck_tokenizer.c.

432 {
433 FILE *f = fopen(path, "rb");
434 if (!f) {
435 fprintf(stderr, "Failed to open tokenizer: %s\n", path);
436 return -1;
437 }
438
439 fseek(f, 0, SEEK_END);
440 long size = ftell(f);
441 fseek(f, 0, SEEK_SET);
442
443 char *data = (char *)malloc(size + 1);
444 if (!data) {
445 fclose(f);
446 return -1;
447 }
448 fread(data, 1, size, f);
449 data[size] = '\0';
450 fclose(f);
451
452 JSONParser parser = {data, data, data + size};
453 JSONParser *p = &parser;
454
455 /* Parse top-level object */
456 if (!json_match_char(p, '{')) {
457 free(data);
458 return -1;
459 }
460
461 char key[256];
462 while (p->pos < p->end && *p->pos != '}') {
463 if (json_parse_string(p, key, sizeof(key)) < 0) break;
464 if (!json_match_char(p, ':')) break;
465
466 if (strcmp(key, "model") == 0) {
467 /* Parse model object */
468 if (!json_match_char(p, '{')) {
470 json_match_char(p, ',');
471 continue;
472 }
473
474 while (p->pos < p->end && *p->pos != '}') {
475 if (json_parse_string(p, key, sizeof(key)) < 0) break;
476 if (!json_match_char(p, ':')) break;
477
478 if (strcmp(key, "vocab") == 0) {
479 /* Parse vocab object: {"token": id, ...} */
480 if (!json_match_char(p, '{')) {
482 json_match_char(p, ',');
483 continue;
484 }
485
487 while (p->pos < p->end && *p->pos != '}') {
488 int token_len = json_parse_string(p, token, sizeof(token));
489 if (token_len < 0) break;
490 if (!json_match_char(p, ':')) break;
491
492 int id;
493 if (json_parse_int(p, &id) < 0) break;
494
495 /* Ensure we have space up to this ID */
496 while (tok->vocab_size <= id) {
497 ck_tokenizer_add_token(tok, "", 0);
498 }
499
500 /* Add/update token */
501 uint32_t bucket = hash_string(token, token_len) % tok->vocab_hash_size;
502 CKVocabEntry *entry = (CKVocabEntry *)ck_pool_alloc(&tok->pool, sizeof(CKVocabEntry));
503 entry->token = ck_pool_strdup(&tok->pool, token, token_len);
504 entry->token_len = token_len;
505 entry->id = id;
506 entry->next = tok->vocab_hash[bucket];
507 tok->vocab_hash[bucket] = entry;
508 tok->id_to_token[id] = entry->token;
509 if (id >= tok->vocab_size) tok->vocab_size = id + 1;
510
511 json_match_char(p, ',');
512 }
513 json_match_char(p, '}');
514
515 } else if (strcmp(key, "merges") == 0) {
516 /* Parse merges array: ["tok1 tok2", ...] */
517 if (!json_match_char(p, '[')) {
519 json_match_char(p, ',');
520 continue;
521 }
522
523 char merge_str[512];
524 while (p->pos < p->end && *p->pos != ']') {
525 int merge_len = json_parse_string(p, merge_str, sizeof(merge_str));
526 if (merge_len < 0) break;
527
528 /* Parse "token1 token2" */
529 char *space = strchr(merge_str, ' ');
530 if (space) {
531 *space = '\0';
532 char *tok1 = merge_str;
533 char *tok2 = space + 1;
534
535 int32_t id1 = ck_tokenizer_lookup(tok, tok1, -1);
536 int32_t id2 = ck_tokenizer_lookup(tok, tok2, -1);
537
538 /* Create merged token */
539 char merged[512];
540 snprintf(merged, sizeof(merged), "%s%s", tok1, tok2);
541 int32_t merged_id = ck_tokenizer_lookup(tok, merged, -1);
542
543 if (merged_id == tok->unk_id) {
544 merged_id = ck_tokenizer_add_token(tok, merged, -1);
545 }
546
547 ck_tokenizer_add_merge(tok, id1, id2, merged_id);
548 }
549
550 json_match_char(p, ',');
551 }
552 json_match_char(p, ']');
553
554 } else {
556 }
557
558 json_match_char(p, ',');
559 }
560 json_match_char(p, '}');
561
562 } else if (strcmp(key, "added_tokens") == 0) {
563 /* Parse added_tokens array for special tokens */
564 if (!json_match_char(p, '[')) {
566 json_match_char(p, ',');
567 continue;
568 }
569
570 while (p->pos < p->end && *p->pos != ']') {
571 if (!json_match_char(p, '{')) {
573 json_match_char(p, ',');
574 continue;
575 }
576
577 char content[256] = "";
578 int id = -1;
579 while (p->pos < p->end && *p->pos != '}') {
580 if (json_parse_string(p, key, sizeof(key)) < 0) break;
581 if (!json_match_char(p, ':')) break;
582
583 if (strcmp(key, "content") == 0) {
584 json_parse_string(p, content, sizeof(content));
585 } else if (strcmp(key, "id") == 0) {
586 json_parse_int(p, &id);
587 } else if (strcmp(key, "special") == 0) {
590 } else {
592 }
593 json_match_char(p, ',');
594 }
595 json_match_char(p, '}');
596
597 if (id >= 0 && content[0]) {
598 /* Identify special tokens */
599 if (strcmp(content, "<unk>") == 0 || strcmp(content, "[UNK]") == 0) {
600 tok->unk_id = id;
601 } else if (strcmp(content, "<s>") == 0 || strcmp(content, "<bos>") == 0 ||
602 strcmp(content, "[BOS]") == 0) {
603 tok->bos_id = id;
604 } else if (strcmp(content, "</s>") == 0 || strcmp(content, "<eos>") == 0 ||
605 strcmp(content, "[EOS]") == 0 || strcmp(content, "<|endoftext|>") == 0) {
606 tok->eos_id = id;
607 } else if (strcmp(content, "<pad>") == 0 || strcmp(content, "[PAD]") == 0) {
608 tok->pad_id = id;
609 }
610 }
611
612 json_match_char(p, ',');
613 }
614 json_match_char(p, ']');
615
616 } else {
618 }
619
620 json_match_char(p, ',');
621 }
622
623 free(data);
624
625 printf("Loaded tokenizer: %d tokens, %d merges\n", tok->vocab_size, tok->num_merges);
626 printf(" UNK=%d BOS=%d EOS=%d PAD=%d\n", tok->unk_id, tok->bos_id, tok->eos_id, tok->pad_id);
627
628 return 0;
629}
int ck_tokenizer_add_merge(CKTokenizer *tok, int32_t left, int32_t right, int32_t merged)
static void json_skip_whitespace(JSONParser *p)
static int json_match_char(JSONParser *p, char c)
int32_t ck_tokenizer_add_token(CKTokenizer *tok, const char *token, int len)
static int json_parse_string(JSONParser *p, char *buf, int max_len)
static void json_skip_value(JSONParser *p)
static int json_parse_int(JSONParser *p, int *out)
#define CK_MAX_TOKEN_LEN
int32_t int32_t int32_t merged_id
Definition true_bpe.h:122

References CKTokenizer::bos_id, CK_MAX_TOKEN_LEN, ck_pool_alloc(), ck_pool_strdup(), ck_tokenizer_add_merge(), ck_tokenizer_add_token(), ck_tokenizer_lookup(), CKTokenizer::eos_id, hash_string(), CKVocabEntry::id, id, CKTokenizer::id_to_token, json_match_char(), json_parse_int(), json_parse_string(), json_skip_value(), json_skip_whitespace(), merged_id, CKVocabEntry::next, CKTokenizer::num_merges, CKTokenizer::pad_id, CKTokenizer::pool, CKVocabEntry::token, token, CKVocabEntry::token_len, CKTokenizer::unk_id, CKTokenizer::vocab_hash, CKTokenizer::vocab_hash_size, and CKTokenizer::vocab_size.

Referenced by run_inference().

◆ ck_tokenizer_lookup()

int32_t ck_tokenizer_lookup ( const CKTokenizer tok,
const char *  token,
int  len 
)

Definition at line 227 of file ck_tokenizer.c.

227 {
228 if (len < 0) len = (int)strlen(token);
229 uint32_t bucket = hash_string(token, len) % tok->vocab_hash_size;
230
231 for (CKVocabEntry *e = tok->vocab_hash[bucket]; e; e = e->next) {
232 if (e->token_len == len && memcmp(e->token, token, len) == 0) {
233 return e->id;
234 }
235 }
236 return tok->unk_id;
237}

References hash_string(), token, CKTokenizer::unk_id, CKTokenizer::vocab_hash, and CKTokenizer::vocab_hash_size.

Referenced by ck_tokenizer_add_token(), ck_tokenizer_encode(), ck_tokenizer_load(), main(), and spm_get_byte_token().

◆ ck_tokenizer_lookup_merge()

int ck_tokenizer_lookup_merge ( const CKTokenizer tok,
int32_t  left,
int32_t  right 
)

Definition at line 276 of file ck_tokenizer.c.

276 {
277 uint32_t bucket = hash_pair(left, right) % tok->merge_hash_size;
278
279 /* Linear probing */
280 int probes = 0;
281 while (tok->merge_hash[bucket] >= 0 && probes < tok->merge_hash_size) {
282 int idx = tok->merge_hash[bucket];
283 if (tok->merges[idx].left == left && tok->merges[idx].right == right) {
284 return idx;
285 }
286 bucket = (bucket + 1) % tok->merge_hash_size;
287 probes++;
288 }
289 return -1;
290}

References hash_pair(), CKMergeRule::left, left, CKTokenizer::merge_hash, CKTokenizer::merge_hash_size, CKTokenizer::merges, CKMergeRule::right, and right.

Referenced by ck_tokenizer_encode().

◆ hash_pair()

static uint32_t hash_pair ( int32_t  left,
int32_t  right 
)
static

Definition at line 133 of file ck_tokenizer.c.

133 {
134 uint64_t combined = ((uint64_t)left << 32) | (uint32_t)right;
135 /* MurmurHash3 finalizer */
136 combined ^= combined >> 33;
137 combined *= 0xff51afd7ed558ccdULL;
138 combined ^= combined >> 33;
139 combined *= 0xc4ceb9fe1a85ec53ULL;
140 combined ^= combined >> 33;
141 return (uint32_t)combined;
142}

References left, and right.

Referenced by ck_tokenizer_add_merge(), and ck_tokenizer_lookup_merge().

◆ hash_string()

static uint32_t hash_string ( const char *  s,
int  len 
)
static

Definition at line 123 of file ck_tokenizer.c.

123 {
124 uint32_t hash = 2166136261u;
125 for (int i = 0; i < len; i++) {
126 hash ^= (uint8_t)s[i];
127 hash *= 16777619u;
128 }
129 return hash;
130}

Referenced by ck_tokenizer_add_token(), ck_tokenizer_load(), and ck_tokenizer_lookup().

◆ json_match_char()

static int json_match_char ( JSONParser *  p,
char  c 
)
static

Definition at line 302 of file ck_tokenizer.c.

302 {
304 if (p->pos < p->end && *p->pos == c) {
305 p->pos++;
306 return 1;
307 }
308 return 0;
309}

References json_skip_whitespace().

Referenced by ck_tokenizer_load().

◆ json_parse_int()

static int json_parse_int ( JSONParser *  p,
int *  out 
)
static

Definition at line 363 of file ck_tokenizer.c.

363 {
365 if (p->pos >= p->end) return -1;
366
367 int neg = 0;
368 if (*p->pos == '-') {
369 neg = 1;
370 p->pos++;
371 }
372
373 if (p->pos >= p->end || !isdigit((unsigned char)*p->pos)) return -1;
374
375 int val = 0;
376 while (p->pos < p->end && isdigit((unsigned char)*p->pos)) {
377 val = val * 10 + (*p->pos - '0');
378 p->pos++;
379 }
380
381 *out = neg ? -val : val;
382 return 0;
383}

References json_skip_whitespace().

Referenced by ck_tokenizer_load().

◆ json_parse_string()

static int json_parse_string ( JSONParser *  p,
char *  buf,
int  max_len 
)
static

Definition at line 311 of file ck_tokenizer.c.

311 {
313 if (p->pos >= p->end || *p->pos != '"') return -1;
314 p->pos++;
315
316 int len = 0;
317 while (p->pos < p->end && *p->pos != '"') {
318 char c = *p->pos++;
319 if (c == '\\' && p->pos < p->end) {
320 c = *p->pos++;
321 switch (c) {
322 case 'n': c = '\n'; break;
323 case 'r': c = '\r'; break;
324 case 't': c = '\t'; break;
325 case '\\': c = '\\'; break;
326 case '"': c = '"'; break;
327 case 'u': {
328 /* Unicode escape \uXXXX */
329 if (p->pos + 4 <= p->end) {
330 char hex[5] = {p->pos[0], p->pos[1], p->pos[2], p->pos[3], 0};
331 unsigned int codepoint = (unsigned int)strtol(hex, NULL, 16);
332 p->pos += 4;
333 /* Convert to UTF-8 */
334 if (codepoint < 0x80) {
335 if (len < max_len - 1) buf[len++] = (char)codepoint;
336 } else if (codepoint < 0x800) {
337 if (len < max_len - 2) {
338 buf[len++] = (char)(0xC0 | (codepoint >> 6));
339 buf[len++] = (char)(0x80 | (codepoint & 0x3F));
340 }
341 } else {
342 if (len < max_len - 3) {
343 buf[len++] = (char)(0xE0 | (codepoint >> 12));
344 buf[len++] = (char)(0x80 | ((codepoint >> 6) & 0x3F));
345 buf[len++] = (char)(0x80 | (codepoint & 0x3F));
346 }
347 }
348 continue;
349 }
350 break;
351 }
352 default: break;
353 }
354 }
355 if (len < max_len - 1) buf[len++] = c;
356 }
357 buf[len] = '\0';
358
359 if (p->pos < p->end && *p->pos == '"') p->pos++;
360 return len;
361}

References json_skip_whitespace(), and max_len.

Referenced by ck_tokenizer_load(), and json_skip_value().

◆ json_skip_value()

static void json_skip_value ( JSONParser *  p)
static

Definition at line 385 of file ck_tokenizer.c.

385 {
387 if (p->pos >= p->end) return;
388
389 char c = *p->pos;
390 if (c == '"') {
391 char buf[1024];
392 json_parse_string(p, buf, sizeof(buf));
393 } else if (c == '{') {
394 int depth = 1;
395 p->pos++;
396 while (p->pos < p->end && depth > 0) {
397 if (*p->pos == '{') depth++;
398 else if (*p->pos == '}') depth--;
399 else if (*p->pos == '"') {
400 char buf[1024];
401 json_parse_string(p, buf, sizeof(buf));
402 continue;
403 }
404 p->pos++;
405 }
406 } else if (c == '[') {
407 int depth = 1;
408 p->pos++;
409 while (p->pos < p->end && depth > 0) {
410 if (*p->pos == '[') depth++;
411 else if (*p->pos == ']') depth--;
412 else if (*p->pos == '"') {
413 char buf[1024];
414 json_parse_string(p, buf, sizeof(buf));
415 continue;
416 }
417 p->pos++;
418 }
419 } else {
420 /* Number, bool, null */
421 while (p->pos < p->end && !isspace((unsigned char)*p->pos) &&
422 *p->pos != ',' && *p->pos != '}' && *p->pos != ']') {
423 p->pos++;
424 }
425 }
426}

References json_parse_string(), and json_skip_whitespace().

Referenced by ck_tokenizer_load().

◆ json_skip_whitespace()

static void json_skip_whitespace ( JSONParser *  p)
static

Definition at line 296 of file ck_tokenizer.c.

296 {
297 while (p->pos < p->end && isspace((unsigned char)*p->pos)) {
298 p->pos++;
299 }
300}

Referenced by ck_tokenizer_load(), json_match_char(), json_parse_int(), json_parse_string(), and json_skip_value().

◆ pool_new_block()

static CKPoolBlock * pool_new_block ( size_t  capacity)
static

Definition at line 55 of file ck_tokenizer.c.

55 {
56 CKPoolBlock *block = (CKPoolBlock *)malloc(sizeof(CKPoolBlock));
57 if (!block) return NULL;
58 block->data = (uint8_t *)malloc(capacity);
59 if (!block->data) {
60 free(block);
61 return NULL;
62 }
63 block->used = 0;
64 block->capacity = capacity;
65 block->next = NULL;
66 return block;
67}
size_t capacity

References CKPoolBlock::capacity, CKPoolBlock::data, CKPoolBlock::next, and CKPoolBlock::used.

Referenced by ck_pool_alloc().