spb/forge Public MIT
Forge — LLM training from scratch in pure C++20 + Metal on Apple Silicon.
C++ 61.2%
C 23%
Python 7.6%
TeX 7.2%
CMake 1.1%
1// Author: Simon-Pierre Boucher — contact@spboucher.ai2#include "tokenizer/bpe.h"34#include <cstdio>5#include <cstdlib>6#include <fstream>7#include <limits>8#include <sstream>910namespace forge::tok {1112namespace {13[[noreturn]] void die(const std::string& msg) {14 std::fprintf(stderr, "forge/tokenizer: %s\n", msg.c_str());15 std::abort();16}17} // namespace1819void BPETokenizer::load(const std::string& model_path) {20 std::ifstream in(model_path);21 if (!in) die("cannot open " + model_path);22 std::string header;23 std::getline(in, header);24 if (header != "forgebpe v1") die("bad model header in " + model_path);25 int64_t vocab_size = 0;26 in >> vocab_size;2728 vocab_.resize(size_t(vocab_size));29 for (int i = 0; i < 256 && i < vocab_size; ++i) vocab_[size_t(i)] = std::string(1, char(i));3031 int32_t id, left, right;32 while (in >> id >> left >> right) {33 merges_[{left, right}] = id;34 vocab_[size_t(id)] = vocab_[size_t(left)] + vocab_[size_t(right)];35 }36 if (int64_t(merges_.size()) != vocab_size - 256)37 die("merge count does not match vocab size in " + model_path);38}3940std::vector<int32_t> BPETokenizer::encode(const std::string& text) const {41 std::vector<int32_t> ids;42 ids.reserve(text.size());43 for (unsigned char c : text) ids.push_back(int32_t(c));4445 // greedy: repeatedly apply the LOWEST-id (earliest-learned) merge present46 while (ids.size() >= 2) {47 int32_t best_id = std::numeric_limits<int32_t>::max();48 std::pair<int32_t, int32_t> best_pair{-1, -1};49 for (size_t i = 0; i + 1 < ids.size(); ++i) {50 auto it = merges_.find({ids[i], ids[i + 1]});51 if (it != merges_.end() && it->second < best_id) {52 best_id = it->second;53 best_pair = it->first;54 }55 }56 if (best_pair.first < 0) break;57 std::vector<int32_t> next;58 next.reserve(ids.size());59 for (size_t i = 0; i < ids.size();) {60 if (i + 1 < ids.size() && ids[i] == best_pair.first &&61 ids[i + 1] == best_pair.second) {62 next.push_back(best_id);63 i += 2;64 } else {65 next.push_back(ids[i]);66 i += 1;67 }68 }69 ids = std::move(next);70 }71 return ids;72}7374std::string BPETokenizer::decode(const std::vector<int32_t>& ids) const {75 std::string out;76 for (int32_t id : ids) {77 if (id >= 0 && size_t(id) < vocab_.size()) out += vocab_[size_t(id)];78 }79 return out;80}8182} // namespace forge::tok83