Tokenizer abstraction: char + byte-level BPE - #16
Conversation
New `tokenizers/` package introduces a Tokenizer base class with
encode/decode/vocab_size/state_dict and two implementations:
- CharTokenizer : the original behavior — one token per unique
character in the corpus, ~85-250 vocab.
- BPETokenizer : byte-level BPE in the minbpe style. Greedy
merges of the most-frequent adjacent pair until target vocab
is reached. ~3-4x sequence compression on English text, so
the same block_size covers more actual context.
`gpt.py` now drives encoding/decoding through the tokenizer object
and gets two new CLI flags:
--tokenizer {char,bpe} (default char)
--tokenizer-vocab-size N (default 1024, BPE only)
Naive-Python BPE is slow, so the encoded corpus is cached to disk
under `{dataset_path}.{tokenizer}_v{vocab_size}.cache.pt`, keyed on
the tokenizer state so a stale cache is detected and regenerated.
BPE merge-training itself runs on a 5 MB prefix of the corpus —
sufficient for stable merges, prohibitive otherwise under pure Python.
Checkpoint format gains `tokenizer_type` + `tokenizer_state`. For
char tokenizers we still write the legacy `chars` field so existing
viz_embeddings / export_onnx / web demo paths keep working without
changes. `load_model_from_checkpoint` returns the tokenizer instead
of `chars` and transparently falls back to a CharTokenizer when
loading legacy checkpoints (only `chars` present).
Verified end-to-end:
- Legacy ckpt_default_step_03375.pt loads via the new path
- BPE tokenizer round-trips through state_dict identically
- export_onnx still produces vocab.json with `chars` for char-level
- pyright clean
- viz_embeddings asserts CharTokenizer with a clear error for BPE
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (11)
📝 WalkthroughSummary by CodeRabbit
WalkthroughThis PR replaces fixed character-level vocabulary with a configurable tokenizer system supporting character and byte-pair encoding, updates model training to use parallelized corpus encoding and learning-rate scheduling, and adapts all dependent tools to work with tokenizer-aware checkpoints. ChangesTokenizer System and Model Integration
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
…l-C. Bundled rather than split because they all build on the same BPE plumbing and we're iterating quickly on a draft PR. * gpt.py: encode the corpus in parallel chunks with progress reporting; track last_saved_step and run the HF upload against it on either normal completion or KeyboardInterrupt — killing a training run no longer drops the latest checkpoint on the floor. * tokenizers/bpe.py: JIT-compile the BPE encode loop with numba. ~60x speedup over pure Python on Shakespeare; combined with the parallel chunk dispatcher, full-corpus encoding drops from estimated >24h to roughly a minute. Verified bit-identical output vs the pure-Python reference. * datasets/gutenberg.py: bump default to 10000 books (~7 GB), new cache filename `input_gutenberg_10k.txt` so the old 3K cache doesn't shadow it. Parallel HTTP downloads via 32-thread ThreadPoolExecutor — preserves book order via executor.map. * viz_embeddings.py: new Tokens tab works for both char and BPE checkpoints, shows a sortable table of (id, token, chars, bytes) plus a token-length histogram on BPE. Other tabs degrade gracefully when loaded against a BPE checkpoint instead of killing the page. * pyproject.toml + uv.lock: add numba. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The cosine schedule was the wrong shape for what the user actually wanted: it always decays, regardless of whether training has actually plateaued. Now we use torch's ReduceLROnPlateau, which only halves the lr when val loss hasn't improved by lr_threshold for lr_patience eval intervals. Floors at min_lr; never ratchets back up (standard behavior). Linear warmup over the first warmup_iters is kept — it's about early stability with the bumped peak lr (6e-4), not predetermined decay. The plateau scheduler doesn't see warmup-era val losses so its "best so far" tracking starts clean once warmup finishes. Terminal prints "lr reduced: X → Y (val loss plateau)" on each drop. TensorBoard's `lr` scalar shows the staircase. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Adds a `Tokenizer` base class and two concrete implementations, switchable via CLI.
New `tokenizers/` package
`gpt.py` integration
Checkpoint format
Downstream tool updates
Test plan
🤖 Generated with Claude Code