#include "tokenizer.h" #include #include #include #include static unsigned int hash_str(const char* s) { unsigned int h = 5381; while (*s) h = ((h << 5) + h) + (unsigned char)(*s++); return h; } static void hash_insert(Tokenizer* tok, const char* token, int id) { unsigned int h = hash_str(token) % (unsigned int)tok->hash_size; while (tok->hash_keys[h] != -1) h = (h + 1) % (unsigned int)tok->hash_size; tok->hash_keys[h] = 1; tok->hash_vals[h] = id; strncpy(tok->vocab[id].token, token, TOK_MAX_WORD_LEN - 1); } static int hash_lookup(const Tokenizer* tok, const char* token) { unsigned int h = hash_str(token) % (unsigned int)tok->hash_size; int probes = 0; while (tok->hash_keys[h] != -1 && probes < tok->hash_size) { int id = tok->hash_vals[h]; if (strncmp(tok->vocab[id].token, token, TOK_MAX_WORD_LEN) == 0) return id; h = (h + 1) % (unsigned int)tok->hash_size; probes++; } return TOK_UNK; } static void add_special_tokens(Tokenizer* tok) { const char* specials[] = {"","","","",""}; for (int i = 0; i < 5; i++) { strncpy(tok->vocab[i].token, specials[i], TOK_MAX_WORD_LEN - 1); tok->vocab[i].freq = 999999; hash_insert(tok, specials[i], i); } tok->vocab_size = 5; } static int extract_field(const char* line, const char* key, char* out, int out_len) { char search[64]; snprintf(search, sizeof(search), "\"%s\"", key); const char* p = strstr(line, search); if (!p) return 0; p += strlen(search); while (*p && (*p == ':' || *p == ' ')) p++; if (*p != '"') return 0; p++; int i = 0; while (*p && *p != '"' && i < out_len - 1) { if (*p == '\\') { p++; if (*p) { out[i++] = *p++; } } else out[i++] = *p++; } out[i] = '\0'; return i > 0; } static void count_tokens(Tokenizer* tok, const char* text) { char word[TOK_MAX_WORD_LEN]; int wi = 0; const char* p = text; while (*p) { if (isalnum((unsigned char)*p) || *p == '\'') { if (wi < TOK_MAX_WORD_LEN - 1) word[wi++] = tolower((unsigned char)*p); p++; } else { if (wi > 0) { word[wi] = '\0'; int found = 0; for (int i = 5; i < tok->vocab_size; i++) { if (strncmp(tok->vocab[i].token, word, TOK_MAX_WORD_LEN) == 0) { tok->vocab[i].freq++; found = 1; break; } } if (!found && tok->vocab_size < TOK_VOCAB_SIZE) { strncpy(tok->vocab[tok->vocab_size].token, word, TOK_MAX_WORD_LEN - 1); tok->vocab[tok->vocab_size].freq = 1; tok->vocab_size++; } wi = 0; } if (!isspace((unsigned char)*p)) { char punct[3] = {*p, '\0', '\0'}; int found = 0; for (int i = 5; i < tok->vocab_size; i++) { if (strncmp(tok->vocab[i].token, punct, TOK_MAX_WORD_LEN) == 0) { tok->vocab[i].freq++; found = 1; break; } } if (!found && tok->vocab_size < TOK_VOCAB_SIZE) { strncpy(tok->vocab[tok->vocab_size].token, punct, TOK_MAX_WORD_LEN - 1); tok->vocab[tok->vocab_size].freq = 1; tok->vocab_size++; } } p++; } } if (wi > 0) { word[wi] = '\0'; int found = 0; for (int i = 5; i < tok->vocab_size; i++) { if (strncmp(tok->vocab[i].token, word, TOK_MAX_WORD_LEN) == 0) { tok->vocab[i].freq++; found = 1; break; } } if (!found && tok->vocab_size < TOK_VOCAB_SIZE) { strncpy(tok->vocab[tok->vocab_size].token, word, TOK_MAX_WORD_LEN - 1); tok->vocab[tok->vocab_size].freq = 1; tok->vocab_size++; } } } static int cmp_freq(const void* a, const void* b) { return ((TokenEntry*)b)->freq - ((TokenEntry*)a)->freq; } int tokenizer_build(Tokenizer* tok, const char* jsonl_path, int max_lines) { tok->hash_size = TOK_VOCAB_SIZE * 4; memset(tok->hash_keys, -1, sizeof(tok->hash_keys)); memset(tok->hash_vals, -1, sizeof(tok->hash_vals)); tok->vocab_size = 0; add_special_tokens(tok); FILE* f = fopen(jsonl_path, "r"); if (!f) { fprintf(stderr, "[TOK] cannot open %s\n", jsonl_path); return 0; } char line[4096], field[2048]; int lines = 0; printf("[TOK] Building vocab from %s ...\n", jsonl_path); while (fgets(line, sizeof(line), f) && (max_lines <= 0 || lines < max_lines)) { if (extract_field(line, "input", field, sizeof(field))) count_tokens(tok, field); if (extract_field(line, "output", field, sizeof(field))) count_tokens(tok, field); lines++; if (lines % 50000 == 0) printf("[TOK] Processed %d lines, vocab=%d\n", lines, tok->vocab_size); } fclose(f); qsort(tok->vocab + 5, tok->vocab_size - 5, sizeof(TokenEntry), cmp_freq); if (tok->vocab_size > TOK_VOCAB_SIZE) tok->vocab_size = TOK_VOCAB_SIZE; memset(tok->hash_keys, -1, sizeof(tok->hash_keys)); for (int i = 0; i < tok->vocab_size; i++) hash_insert(tok, tok->vocab[i].token, i); printf("[TOK] Final vocab size: %d (from %d lines)\n", tok->vocab_size, lines); return tok->vocab_size; } int tokenizer_save(const Tokenizer* tok, const char* path) { FILE* f = fopen(path, "w"); if (!f) return 0; fprintf(f, "%d\n", tok->vocab_size); for (int i = 0; i < tok->vocab_size; i++) fprintf(f, "%s\t%d\n", tok->vocab[i].token, tok->vocab[i].freq); fclose(f); printf("[TOK] Saved vocab to %s\n", path); return 1; } int tokenizer_load(Tokenizer* tok, const char* path) { FILE* f = fopen(path, "r"); if (!f) { fprintf(stderr, "[TOK] cannot open %s\n", path); return 0; } tok->hash_size = TOK_VOCAB_SIZE * 4; memset(tok->hash_keys, -1, sizeof(tok->hash_keys)); memset(tok->hash_vals, -1, sizeof(tok->hash_vals)); fscanf(f, "%d\n", &tok->vocab_size); for (int i = 0; i < tok->vocab_size; i++) { fscanf(f, "%31s\t%d\n", tok->vocab[i].token, &tok->vocab[i].freq); hash_insert(tok, tok->vocab[i].token, i); } fclose(f); printf("[TOK] Loaded vocab: %d tokens from %s\n", tok->vocab_size, path); return 1; } int tokenizer_encode(const Tokenizer* tok, const char* text, int* buf, int buf_len) { int n = 0; char word[TOK_MAX_WORD_LEN]; int wi = 0; const char* p = text; while (*p && n < buf_len) { if (isalnum((unsigned char)*p) || *p == '\'') { if (wi < TOK_MAX_WORD_LEN - 1) word[wi++] = tolower((unsigned char)*p); p++; } else { if (wi > 0) { word[wi] = '\0'; buf[n++] = hash_lookup(tok, word); wi = 0; } if (!isspace((unsigned char)*p) && n < buf_len) { char punct[3] = {*p, '\0', '\0'}; buf[n++] = hash_lookup(tok, punct); } p++; } } if (wi > 0 && n < buf_len) { word[wi] = '\0'; buf[n++] = hash_lookup(tok, word); } return n; } void tokenizer_decode(const Tokenizer* tok, const int* ids, int n, char* out, int out_len) { int pos = 0; for (int i = 0; i < n && pos < out_len - 2; i++) { if (ids[i] == TOK_EOS || ids[i] == TOK_PAD) break; if (ids[i] == TOK_BOS || ids[i] == TOK_SEP) continue; const char* tok_str = tokenizer_id_to_token(tok, ids[i]); int len = strlen(tok_str); if (i > 0 && isalpha((unsigned char)tok_str[0]) && pos < out_len - 2) out[pos++] = ' '; if (pos + len < out_len - 1) { memcpy(out + pos, tok_str, len); pos += len; } } out[pos] = '\0'; } int tokenizer_token_to_id(const Tokenizer* tok, const char* token) { return hash_lookup(tok, token); } const char* tokenizer_id_to_token(const Tokenizer* tok, int id) { if (id < 0 || id >= tok->vocab_size) return ""; return tok->vocab[id].token; }