Upload Slayer GPT tokenizer model archive
Browse files- .gitattributes +3 -33
- .gitignore +6 -0
- README.md +101 -0
- docs/REPLICATION_GUIDE.md +246 -0
- docs/TOKENIZER_NOTES.md +75 -0
- examples/prepare_corpus.py +115 -0
- logs/run_f143baf2.log +0 -0
- logs/train.out +65 -0
- metadata/artifact_manifest.json +44 -0
- metadata/loss_train.csv +161 -0
- metadata/traj.csv +8 -0
- model/ckpt.pt +3 -0
- requirements.txt +4 -0
- scripts/knowbench_mac.py +86 -0
- scripts/model.py +330 -0
- scripts/sample_mac.py +56 -0
- scripts/syntaxbench_mac.py +70 -0
- scripts/upload_to_hf.py +49 -0
- tokenizers/polish_bpe_32k.json +0 -0
- tokenizers/rxlm_polish_bpe_65k.json +0 -0
- training/polish_nanogpt.patch +90 -0
- training/requirements.txt +9 -0
- training/run_polish.sh +8 -0
- training/train_gpt.py +0 -0
- training/triton_kernels.py +1007 -0
.gitattributes
CHANGED
|
@@ -1,35 +1,5 @@
|
|
| 1 |
-
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
-
|
| 27 |
-
*.
|
| 28 |
-
*.
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
| 2 |
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
tokenizers/*.json filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
metadata/*.csv filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitignore
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
.DS_Store
|
| 3 |
+
.venv/
|
| 4 |
+
venv/
|
| 5 |
+
*.pyc
|
| 6 |
+
|
README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: other
|
| 3 |
+
library_name: pytorch
|
| 4 |
+
tags:
|
| 5 |
+
- pytorch
|
| 6 |
+
- gpt
|
| 7 |
+
- gpt2-style
|
| 8 |
+
- polish
|
| 9 |
+
- tokenizer
|
| 10 |
+
- byte-level-bpe
|
| 11 |
+
- causal-lm
|
| 12 |
+
language:
|
| 13 |
+
- pl
|
| 14 |
+
pipeline_tag: text-generation
|
| 15 |
+
---
|
| 16 |
+
|
| 17 |
+
# Slayer GPT Tokenizer Model
|
| 18 |
+
|
| 19 |
+
Teaching archive for the Slayer GPT-style Polish language-model experiment.
|
| 20 |
+
|
| 21 |
+
This repo is meant to help people replicate the workflow, not just store artifacts. It includes a runnable local GPT checkpoint, the custom tokenizer it was trained with, a later tokenizer variant, the Slayer H100 training scripts, run logs, and replication docs.
|
| 22 |
+
|
| 23 |
+
This is a raw PyTorch/custom-code checkpoint, not a Transformers-native `AutoModelForCausalLM` repository.
|
| 24 |
+
|
| 25 |
+
## Start Here
|
| 26 |
+
|
| 27 |
+
Read:
|
| 28 |
+
|
| 29 |
+
- `docs/REPLICATION_GUIDE.md` - full step-by-step replication lesson.
|
| 30 |
+
- `docs/TOKENIZER_NOTES.md` - tokenizer-specific teaching notes.
|
| 31 |
+
- `metadata/artifact_manifest.json` - exact artifact provenance and model/tokenizer metadata.
|
| 32 |
+
|
| 33 |
+
Run the saved model:
|
| 34 |
+
|
| 35 |
+
```bash
|
| 36 |
+
python3 -m venv .venv
|
| 37 |
+
source .venv/bin/activate
|
| 38 |
+
pip install -r requirements.txt
|
| 39 |
+
python scripts/sample_mac.py "Polska jest" 80
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
## What Is Included
|
| 43 |
+
|
| 44 |
+
- `model/ckpt.pt` - runnable nanoGPT-style checkpoint from `/Users/kacper/Local/Ventures/Slayer/gpt2-pl-mac/ckpt.pt`.
|
| 45 |
+
- `tokenizers/polish_bpe_32k.json` - custom byte-level BPE tokenizer paired with `model/ckpt.pt`.
|
| 46 |
+
- `tokenizers/rxlm_polish_bpe_65k.json` - separate later 65k custom tokenizer from RXLM/Slayer work.
|
| 47 |
+
- `scripts/model.py` - GPT model definition for the checkpoint.
|
| 48 |
+
- `scripts/sample_mac.py` - local sampler.
|
| 49 |
+
- `scripts/knowbench_mac.py`, `scripts/syntaxbench_mac.py` - simple probes.
|
| 50 |
+
- `examples/prepare_corpus.py` - reference corpus/tokenizer/shard preparation script.
|
| 51 |
+
- `training/` - Slayer remote nanoGPT training code and launch script.
|
| 52 |
+
- `logs/` and `metadata/` - run evidence.
|
| 53 |
+
|
| 54 |
+
## Key Compatibility Rule
|
| 55 |
+
|
| 56 |
+
Use this pairing:
|
| 57 |
+
|
| 58 |
+
```text
|
| 59 |
+
model/ckpt.pt -> tokenizers/polish_bpe_32k.json
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
Do not sample `model/ckpt.pt` with `tokenizers/rxlm_polish_bpe_65k.json`. That tokenizer is a separate later artifact.
|
| 63 |
+
|
| 64 |
+
## Checkpoint Summary
|
| 65 |
+
|
| 66 |
+
`model/ckpt.pt`:
|
| 67 |
+
|
| 68 |
+
- 12 layers
|
| 69 |
+
- 12 attention heads
|
| 70 |
+
- 768 embedding dimension
|
| 71 |
+
- 1024 token context
|
| 72 |
+
- 32768 vocabulary size
|
| 73 |
+
- about 136M state-dict parameters
|
| 74 |
+
- checkpoint step stored as `iter=500`
|
| 75 |
+
|
| 76 |
+
## Remote Slayer Training States
|
| 77 |
+
|
| 78 |
+
The full remote training states are not committed because each is about 3.6 GB and includes optimizer/runtime state.
|
| 79 |
+
|
| 80 |
+
Known local copies:
|
| 81 |
+
|
| 82 |
+
```text
|
| 83 |
+
/Users/kacper/Local/Ventures/Slayer/slayer-nanogpt/ckpt/state_step000500.pt
|
| 84 |
+
/Users/kacper/Local/Ventures/Slayer/slayer-nanogpt/ckpt/state_step001000.pt
|
| 85 |
+
/Users/kacper/Local/Ventures/Slayer/slayer-nanogpt/ckpt/state_step001500.pt
|
| 86 |
+
```
|
| 87 |
+
|
| 88 |
+
Known remote copies:
|
| 89 |
+
|
| 90 |
+
```text
|
| 91 |
+
ssh slayer:/home/ubuntu/modded-nanogpt/logs/f143baf2-dbfc-406e-b13b-a9fcc166b31b/state_step000500.pt
|
| 92 |
+
ssh slayer:/home/ubuntu/modded-nanogpt/logs/f143baf2-dbfc-406e-b13b-a9fcc166b31b/state_step001000.pt
|
| 93 |
+
ssh slayer:/home/ubuntu/modded-nanogpt/logs/f143baf2-dbfc-406e-b13b-a9fcc166b31b/state_step001500.pt
|
| 94 |
+
```
|
| 95 |
+
|
| 96 |
+
Fetch explicitly when needed:
|
| 97 |
+
|
| 98 |
+
```bash
|
| 99 |
+
mkdir -p training-states
|
| 100 |
+
rsync -avP slayer:/home/ubuntu/modded-nanogpt/logs/f143baf2-dbfc-406e-b13b-a9fcc166b31b/state_step001500.pt training-states/
|
| 101 |
+
```
|
docs/REPLICATION_GUIDE.md
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Replication Guide
|
| 2 |
+
|
| 3 |
+
This guide explains how to replicate the Slayer GPT-style Polish language-model experiment from raw text to a runnable checkpoint.
|
| 4 |
+
|
| 5 |
+
The repo contains two related tracks:
|
| 6 |
+
|
| 7 |
+
1. `model/ckpt.pt` is a small, runnable GPT checkpoint paired with `tokenizers/polish_bpe_32k.json`.
|
| 8 |
+
2. `training/` contains the larger Slayer H100 training code derived from modded-nanoGPT. Its full optimizer checkpoints are documented but not committed.
|
| 9 |
+
|
| 10 |
+
Use the small track for teaching and local demos. Use the H100 track to explain how the larger remote run was structured.
|
| 11 |
+
|
| 12 |
+
## 1. What Was Built
|
| 13 |
+
|
| 14 |
+
The local model is a GPT-2-style decoder-only Transformer:
|
| 15 |
+
|
| 16 |
+
- 12 layers
|
| 17 |
+
- 12 attention heads
|
| 18 |
+
- 768 embedding dimension
|
| 19 |
+
- 1024 token context
|
| 20 |
+
- 32768-token custom Polish byte-level BPE vocabulary
|
| 21 |
+
- bias-free linear/layernorm setup
|
| 22 |
+
- about 136M parameters in the checkpoint state dict
|
| 23 |
+
|
| 24 |
+
It is not a fine-tune of OpenAI GPT-2 weights. The important idea is the recipe:
|
| 25 |
+
|
| 26 |
+
1. collect Polish text,
|
| 27 |
+
2. train a custom byte-level BPE tokenizer,
|
| 28 |
+
3. tokenize the corpus into token-id shards,
|
| 29 |
+
4. train a causal language model on next-token prediction,
|
| 30 |
+
5. sample and evaluate with the exact same tokenizer.
|
| 31 |
+
|
| 32 |
+
## 2. Repository Map
|
| 33 |
+
|
| 34 |
+
- `model/ckpt.pt` - runnable model checkpoint.
|
| 35 |
+
- `tokenizers/polish_bpe_32k.json` - tokenizer paired with `model/ckpt.pt`.
|
| 36 |
+
- `tokenizers/rxlm_polish_bpe_65k.json` - separate later 65k custom tokenizer. Do not use it with `model/ckpt.pt`.
|
| 37 |
+
- `scripts/model.py` - GPT implementation for the local checkpoint.
|
| 38 |
+
- `scripts/sample_mac.py` - generation script.
|
| 39 |
+
- `scripts/knowbench_mac.py` and `scripts/syntaxbench_mac.py` - simple evaluation probes.
|
| 40 |
+
- `examples/prepare_corpus.py` - reference script for tokenizer training and `.bin` shard creation.
|
| 41 |
+
- `training/train_gpt.py` - H100 Slayer training script.
|
| 42 |
+
- `training/run_polish.sh` - remote launch script used on `ssh slayer`.
|
| 43 |
+
- `logs/` and `metadata/` - evidence from the saved local and remote runs.
|
| 44 |
+
|
| 45 |
+
## 3. Local Demo
|
| 46 |
+
|
| 47 |
+
```bash
|
| 48 |
+
python3 -m venv .venv
|
| 49 |
+
source .venv/bin/activate
|
| 50 |
+
pip install -r requirements.txt
|
| 51 |
+
python scripts/sample_mac.py "Polska jest" 80
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
Expected behavior:
|
| 55 |
+
|
| 56 |
+
- On Apple Silicon, the sampler uses MPS.
|
| 57 |
+
- On other machines, it falls back to CPU.
|
| 58 |
+
- The model should load `model/ckpt.pt` and `tokenizers/polish_bpe_32k.json` without path changes.
|
| 59 |
+
|
| 60 |
+
Run the lightweight probes:
|
| 61 |
+
|
| 62 |
+
```bash
|
| 63 |
+
python scripts/syntaxbench_mac.py
|
| 64 |
+
python scripts/knowbench_mac.py
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
## 4. Corpus Preparation
|
| 68 |
+
|
| 69 |
+
For teaching, start with a plain UTF-8 text corpus:
|
| 70 |
+
|
| 71 |
+
```text
|
| 72 |
+
data/raw/doc_0001.txt
|
| 73 |
+
data/raw/doc_0002.txt
|
| 74 |
+
...
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
One document per file is easiest to reason about. Clean enough to remove boilerplate and encoding damage, but do not over-normalize language. The tokenizer here is byte-level BPE, so it can represent arbitrary text.
|
| 78 |
+
|
| 79 |
+
Recommended minimum for a useful class demo:
|
| 80 |
+
|
| 81 |
+
- tokenizer demo: 10 MB to 100 MB of text,
|
| 82 |
+
- tiny GPT training demo: 100 MB to 1 GB,
|
| 83 |
+
- meaningful GPT run: many GB.
|
| 84 |
+
|
| 85 |
+
Keep validation data separate before tokenization.
|
| 86 |
+
|
| 87 |
+
## 5. Tokenizer Training
|
| 88 |
+
|
| 89 |
+
The model checkpoint in this repo uses:
|
| 90 |
+
|
| 91 |
+
- byte-level BPE,
|
| 92 |
+
- vocab size 32768,
|
| 93 |
+
- no Unicode normalizer,
|
| 94 |
+
- `add_prefix_space=False`,
|
| 95 |
+
- `<|endoftext|>` as the document separator / BOS-style token.
|
| 96 |
+
|
| 97 |
+
Train a compatible tokenizer and create token-id shards with:
|
| 98 |
+
|
| 99 |
+
```bash
|
| 100 |
+
python examples/prepare_corpus.py \
|
| 101 |
+
--raw-dir data/raw \
|
| 102 |
+
--out-dir data/processed \
|
| 103 |
+
--vocab-size 32768 \
|
| 104 |
+
--train-tokenizer
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
This writes:
|
| 108 |
+
|
| 109 |
+
- `data/processed/tokenizer.json`
|
| 110 |
+
- `data/processed/shards/polish_train_000000.bin`
|
| 111 |
+
- `data/processed/shards/polish_val_000000.bin`
|
| 112 |
+
|
| 113 |
+
The `.bin` shards are raw `uint16` token ids. This matters because `training/train_gpt.py` expects token ids to fit under 65536 and loads shards as `torch.uint16`.
|
| 114 |
+
|
| 115 |
+
For a fresh 65k experiment you may use a 65536-token tokenizer, but then the model config and training code must match that vocabulary size. Do not use the 65k tokenizer with the checked-in 32768-vocab checkpoint.
|
| 116 |
+
|
| 117 |
+
## 6. Training A Small Local GPT
|
| 118 |
+
|
| 119 |
+
This repo includes inference code for the saved checkpoint, not a full clean-room tiny trainer. For teaching, the simplest path is:
|
| 120 |
+
|
| 121 |
+
1. Use `examples/prepare_corpus.py` to create tokenizer and shards.
|
| 122 |
+
2. Use an existing nanoGPT trainer or your own minimal causal LM trainer.
|
| 123 |
+
3. Match these model settings for compatibility with `scripts/model.py`:
|
| 124 |
+
|
| 125 |
+
```python
|
| 126 |
+
GPTConfig(
|
| 127 |
+
n_layer=12,
|
| 128 |
+
n_head=12,
|
| 129 |
+
n_embd=768,
|
| 130 |
+
block_size=1024,
|
| 131 |
+
vocab_size=32768,
|
| 132 |
+
bias=False,
|
| 133 |
+
dropout=0.0,
|
| 134 |
+
)
|
| 135 |
+
```
|
| 136 |
+
|
| 137 |
+
4. Save checkpoints in this format:
|
| 138 |
+
|
| 139 |
+
```python
|
| 140 |
+
torch.save(
|
| 141 |
+
{
|
| 142 |
+
"model": model.state_dict(),
|
| 143 |
+
"model_args": config_dict,
|
| 144 |
+
"iter": step,
|
| 145 |
+
},
|
| 146 |
+
"ckpt.pt",
|
| 147 |
+
)
|
| 148 |
+
```
|
| 149 |
+
|
| 150 |
+
Then put the checkpoint at `model/ckpt.pt`, the tokenizer at `tokenizers/polish_bpe_32k.json`, and run:
|
| 151 |
+
|
| 152 |
+
```bash
|
| 153 |
+
python scripts/sample_mac.py "Dawno temu w Polsce" 100
|
| 154 |
+
```
|
| 155 |
+
|
| 156 |
+
## 7. Replicating The Slayer H100 Run
|
| 157 |
+
|
| 158 |
+
The remote run used a modified fast GPT trainer under:
|
| 159 |
+
|
| 160 |
+
```text
|
| 161 |
+
ssh slayer:/home/ubuntu/modded-nanogpt
|
| 162 |
+
```
|
| 163 |
+
|
| 164 |
+
The local copy of the relevant training files is in `training/`.
|
| 165 |
+
|
| 166 |
+
Important paths from the run:
|
| 167 |
+
|
| 168 |
+
```text
|
| 169 |
+
~/dynaword/shards/polish_train_*.bin
|
| 170 |
+
~/dynaword/shards/polish_val_*.bin
|
| 171 |
+
~/modded-nanogpt/logs/f143baf2-dbfc-406e-b13b-a9fcc166b31b/state_step001500.pt
|
| 172 |
+
```
|
| 173 |
+
|
| 174 |
+
The H100 trainer expects:
|
| 175 |
+
|
| 176 |
+
- CUDA-capable Linux host,
|
| 177 |
+
- PyTorch 2.10-compatible environment,
|
| 178 |
+
- Triton,
|
| 179 |
+
- `kernels`,
|
| 180 |
+
- token-id shards as raw `uint16`,
|
| 181 |
+
- train files matching `~/dynaword/shards/polish_train_*.bin`,
|
| 182 |
+
- validation files matching `~/dynaword/shards/polish_val_*.bin`.
|
| 183 |
+
|
| 184 |
+
The launch script:
|
| 185 |
+
|
| 186 |
+
```bash
|
| 187 |
+
cd training
|
| 188 |
+
sed -n '1,120p' run_polish.sh
|
| 189 |
+
```
|
| 190 |
+
|
| 191 |
+
The core launch pattern is:
|
| 192 |
+
|
| 193 |
+
```bash
|
| 194 |
+
export TORCHINDUCTOR_CACHE_DIR="$HOME/.cache/torchinductor_polish"
|
| 195 |
+
export TORCHINDUCTOR_FX_GRAPH_CACHE=1
|
| 196 |
+
export TORCHINDUCTOR_AUTOGRAD_CACHE=1
|
| 197 |
+
cd "$HOME/modded-nanogpt"
|
| 198 |
+
.venv/bin/torchrun --standalone --nproc_per_node=1 train_gpt.py
|
| 199 |
+
```
|
| 200 |
+
|
| 201 |
+
The saved full training states include model and optimizer state:
|
| 202 |
+
|
| 203 |
+
```text
|
| 204 |
+
state_step000500.pt
|
| 205 |
+
state_step001000.pt
|
| 206 |
+
state_step001500.pt
|
| 207 |
+
```
|
| 208 |
+
|
| 209 |
+
They are about 3.6 GB each and are not committed. Fetch them only when needed:
|
| 210 |
+
|
| 211 |
+
```bash
|
| 212 |
+
mkdir -p training-states
|
| 213 |
+
rsync -avP slayer:/home/ubuntu/modded-nanogpt/logs/f143baf2-dbfc-406e-b13b-a9fcc166b31b/state_step001500.pt training-states/
|
| 214 |
+
```
|
| 215 |
+
|
| 216 |
+
## 8. Evaluation And Sanity Checks
|
| 217 |
+
|
| 218 |
+
Always validate these before trusting a run:
|
| 219 |
+
|
| 220 |
+
- Tokenizer round trip: encode and decode sample Polish text.
|
| 221 |
+
- Vocabulary compatibility: checkpoint `vocab_size` equals tokenizer vocab size.
|
| 222 |
+
- Loss curve: training loss should decrease smoothly, not only memorize a tiny sample.
|
| 223 |
+
- Sample quality: inspect repeated n-grams and broken Unicode.
|
| 224 |
+
- Validation loss: keep validation shards separate from training shards.
|
| 225 |
+
|
| 226 |
+
The included metadata shows the local training loss dropping from about `10.54` at step 0 to around `4.63` at step 500, with later probe rows in `metadata/traj.csv`.
|
| 227 |
+
|
| 228 |
+
## 9. Common Mistakes
|
| 229 |
+
|
| 230 |
+
- Mixing tokenizer files. A model trained with `polish_bpe_32k.json` must be sampled with that tokenizer.
|
| 231 |
+
- Saving only weights but losing `model_args`. The loader needs architecture parameters.
|
| 232 |
+
- Tokenizing train and validation together. Split first, tokenize second.
|
| 233 |
+
- Using `int32` shards with the Slayer trainer. Its loader is built around raw `uint16` token ids.
|
| 234 |
+
- Treating full optimizer checkpoints as deployable model artifacts. For inference, export a model-only checkpoint when possible.
|
| 235 |
+
- Teaching from the H100 script first. Start with the local checkpoint and tokenizer, then show the larger training script as the scaled version.
|
| 236 |
+
|
| 237 |
+
## 10. Suggested Lesson Flow
|
| 238 |
+
|
| 239 |
+
1. Show `scripts/sample_mac.py` generating from the saved model.
|
| 240 |
+
2. Open `metadata/artifact_manifest.json` and explain the model/tokenizer pairing.
|
| 241 |
+
3. Train a toy tokenizer on a small Polish corpus with `examples/prepare_corpus.py`.
|
| 242 |
+
4. Inspect tokenization of Polish words, punctuation, and diacritics.
|
| 243 |
+
5. Explain next-token prediction and the checkpoint format.
|
| 244 |
+
6. Show how `training/train_gpt.py` scales the same idea to H100 training.
|
| 245 |
+
7. End with failure modes: wrong tokenizer, data leakage, repeated text, and no validation split.
|
| 246 |
+
|
docs/TOKENIZER_NOTES.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Tokenizer Notes
|
| 2 |
+
|
| 3 |
+
The key teaching point is that the tokenizer is part of the model. It is not an interchangeable preprocessing detail.
|
| 4 |
+
|
| 5 |
+
## Tokenizers In This Repo
|
| 6 |
+
|
| 7 |
+
| File | Vocab | Use |
|
| 8 |
+
| --- | ---: | --- |
|
| 9 |
+
| `tokenizers/polish_bpe_32k.json` | 32768 | Paired with `model/ckpt.pt` |
|
| 10 |
+
| `tokenizers/rxlm_polish_bpe_65k.json` | 65536 | Separate later custom tokenizer artifact |
|
| 11 |
+
|
| 12 |
+
## 32k Polish BPE
|
| 13 |
+
|
| 14 |
+
Properties:
|
| 15 |
+
|
| 16 |
+
- byte-level BPE,
|
| 17 |
+
- 32768 vocabulary entries,
|
| 18 |
+
- 32511 merges,
|
| 19 |
+
- no normalizer,
|
| 20 |
+
- `add_prefix_space=False`,
|
| 21 |
+
- `<|endoftext|>` is the special document separator token.
|
| 22 |
+
|
| 23 |
+
This tokenizer is small enough that token ids fit safely in `uint16`, which is why the training shards can be compact raw binary files.
|
| 24 |
+
|
| 25 |
+
## 65k RXLM BPE
|
| 26 |
+
|
| 27 |
+
Properties:
|
| 28 |
+
|
| 29 |
+
- byte-level BPE,
|
| 30 |
+
- 65536 vocabulary entries,
|
| 31 |
+
- 65283 merges,
|
| 32 |
+
- NFKC normalization,
|
| 33 |
+
- `add_prefix_space=True`,
|
| 34 |
+
- 12 added tokens.
|
| 35 |
+
|
| 36 |
+
This is a different tokenizer. It should be taught as a later design variant, not as the tokenizer for `model/ckpt.pt`.
|
| 37 |
+
|
| 38 |
+
## Why Custom BPE
|
| 39 |
+
|
| 40 |
+
For Polish, a custom tokenizer can reduce awkward fragmentation of common morphemes, diacritics, inflected forms, and domain-specific text. The lesson is not that 32k is always best; the lesson is that tokenizer choice changes:
|
| 41 |
+
|
| 42 |
+
- effective context length,
|
| 43 |
+
- training cost,
|
| 44 |
+
- model embedding size,
|
| 45 |
+
- output fluency,
|
| 46 |
+
- evaluation comparability.
|
| 47 |
+
|
| 48 |
+
## Quick Inspection Snippet
|
| 49 |
+
|
| 50 |
+
```python
|
| 51 |
+
from tokenizers import Tokenizer
|
| 52 |
+
|
| 53 |
+
tok = Tokenizer.from_file("tokenizers/polish_bpe_32k.json")
|
| 54 |
+
text = "Zażółć gęślą jaźń. Polska jest częścią Europy."
|
| 55 |
+
enc = tok.encode(text)
|
| 56 |
+
|
| 57 |
+
print(enc.ids)
|
| 58 |
+
print(enc.tokens)
|
| 59 |
+
print(tok.decode(enc.ids))
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
## Compatibility Rule
|
| 63 |
+
|
| 64 |
+
For inference:
|
| 65 |
+
|
| 66 |
+
```text
|
| 67 |
+
checkpoint vocab_size == tokenizer vocab size
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
For this repo:
|
| 71 |
+
|
| 72 |
+
```text
|
| 73 |
+
model/ckpt.pt -> tokenizers/polish_bpe_32k.json
|
| 74 |
+
```
|
| 75 |
+
|
examples/prepare_corpus.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Train a byte-level BPE tokenizer and write uint16 token-id shards.
|
| 3 |
+
|
| 4 |
+
This is a teaching/reference script for reproducing the data shape expected by
|
| 5 |
+
the Slayer GPT-style training code. Input is a directory of UTF-8 .txt files.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import argparse
|
| 11 |
+
import random
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
|
| 14 |
+
import numpy as np
|
| 15 |
+
from tokenizers import Tokenizer
|
| 16 |
+
from tokenizers.decoders import ByteLevel as ByteLevelDecoder
|
| 17 |
+
from tokenizers.models import BPE
|
| 18 |
+
from tokenizers.pre_tokenizers import ByteLevel
|
| 19 |
+
from tokenizers.trainers import BpeTrainer
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
SPECIAL_TOKEN = "<|endoftext|>"
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def iter_text_files(raw_dir: Path) -> list[Path]:
|
| 26 |
+
files = sorted(raw_dir.rglob("*.txt"))
|
| 27 |
+
if not files:
|
| 28 |
+
raise SystemExit(f"No .txt files found under {raw_dir}")
|
| 29 |
+
return files
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def train_tokenizer(files: list[Path], out_path: Path, vocab_size: int) -> Tokenizer:
|
| 33 |
+
tokenizer = Tokenizer(BPE(unk_token=None))
|
| 34 |
+
tokenizer.pre_tokenizer = ByteLevel(add_prefix_space=False)
|
| 35 |
+
tokenizer.decoder = ByteLevelDecoder()
|
| 36 |
+
|
| 37 |
+
trainer = BpeTrainer(
|
| 38 |
+
vocab_size=vocab_size,
|
| 39 |
+
min_frequency=2,
|
| 40 |
+
special_tokens=[SPECIAL_TOKEN],
|
| 41 |
+
show_progress=True,
|
| 42 |
+
)
|
| 43 |
+
tokenizer.train([str(path) for path in files], trainer)
|
| 44 |
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
| 45 |
+
tokenizer.save(str(out_path))
|
| 46 |
+
return tokenizer
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def load_tokenizer(path: Path) -> Tokenizer:
|
| 50 |
+
return Tokenizer.from_file(str(path))
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def write_shard(tokenizer: Tokenizer, files: list[Path], out_path: Path) -> int:
|
| 54 |
+
ids: list[int] = []
|
| 55 |
+
eot = tokenizer.token_to_id(SPECIAL_TOKEN)
|
| 56 |
+
if eot is None:
|
| 57 |
+
raise SystemExit(f"Tokenizer is missing {SPECIAL_TOKEN!r}")
|
| 58 |
+
|
| 59 |
+
for path in files:
|
| 60 |
+
text = path.read_text(encoding="utf-8", errors="replace").strip()
|
| 61 |
+
if not text:
|
| 62 |
+
continue
|
| 63 |
+
ids.append(eot)
|
| 64 |
+
ids.extend(tokenizer.encode(text).ids)
|
| 65 |
+
|
| 66 |
+
arr = np.asarray(ids, dtype=np.uint16)
|
| 67 |
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
| 68 |
+
arr.tofile(out_path)
|
| 69 |
+
return int(arr.size)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def main() -> None:
|
| 73 |
+
parser = argparse.ArgumentParser()
|
| 74 |
+
parser.add_argument("--raw-dir", type=Path, required=True)
|
| 75 |
+
parser.add_argument("--out-dir", type=Path, required=True)
|
| 76 |
+
parser.add_argument("--vocab-size", type=int, default=32768)
|
| 77 |
+
parser.add_argument("--val-frac", type=float, default=0.01)
|
| 78 |
+
parser.add_argument("--seed", type=int, default=42)
|
| 79 |
+
parser.add_argument("--train-tokenizer", action="store_true")
|
| 80 |
+
args = parser.parse_args()
|
| 81 |
+
|
| 82 |
+
if args.vocab_size > 65536:
|
| 83 |
+
raise SystemExit("This shard writer uses uint16; vocab size must be <= 65536")
|
| 84 |
+
|
| 85 |
+
files = iter_text_files(args.raw_dir)
|
| 86 |
+
random.Random(args.seed).shuffle(files)
|
| 87 |
+
val_count = max(1, int(len(files) * args.val_frac))
|
| 88 |
+
val_files = sorted(files[:val_count])
|
| 89 |
+
train_files = sorted(files[val_count:])
|
| 90 |
+
|
| 91 |
+
tokenizer_path = args.out_dir / "tokenizer.json"
|
| 92 |
+
if args.train_tokenizer or not tokenizer_path.exists():
|
| 93 |
+
tokenizer = train_tokenizer(train_files, tokenizer_path, args.vocab_size)
|
| 94 |
+
else:
|
| 95 |
+
tokenizer = load_tokenizer(tokenizer_path)
|
| 96 |
+
|
| 97 |
+
train_tokens = write_shard(
|
| 98 |
+
tokenizer,
|
| 99 |
+
train_files,
|
| 100 |
+
args.out_dir / "shards" / "polish_train_000000.bin",
|
| 101 |
+
)
|
| 102 |
+
val_tokens = write_shard(
|
| 103 |
+
tokenizer,
|
| 104 |
+
val_files,
|
| 105 |
+
args.out_dir / "shards" / "polish_val_000000.bin",
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
print(f"tokenizer={tokenizer_path}")
|
| 109 |
+
print(f"train_files={len(train_files)} train_tokens={train_tokens}")
|
| 110 |
+
print(f"val_files={len(val_files)} val_tokens={val_tokens}")
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
if __name__ == "__main__":
|
| 114 |
+
main()
|
| 115 |
+
|
logs/run_f143baf2.log
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
logs/train.out
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[Gloo] Rank 0 is connected to 0 peer ranks. Expected number of connected peer ranks is : 0
|
| 2 |
+
|
| 3 |
+
logs/b9ed9818-4ffe-4f5a-aff8-0df5b4a150d1.txt
|
| 4 |
+
Compiling model and warming up kernels (~7 minutes on first execution)
|
| 5 |
+
Sampling steps [0, 1, 4398, 4399, 4400, 4401, 8798, 8799, 8800, 8801, 13198, 13199, 13200, 13201] for warmup
|
| 6 |
+
W0615 01:45:46.376000 198186 torch/distributed/elastic/agent/server/api.py:739] Received 15 death signal, shutting down workers
|
| 7 |
+
W0615 01:45:46.376000 198186 torch/distributed/elastic/multiprocessing/api.py:1010] Sending process 198206 closing signal SIGTERM
|
| 8 |
+
W0615 01:45:46.377000 198186 torch/distributed/elastic/multiprocessing/api.py:1010] Sending process 198206 closing signal SIGTERM
|
| 9 |
+
Traceback (most recent call last):
|
| 10 |
+
File "/home/ubuntu/modded-nanogpt/.venv/lib/python3.14/site-packages/torch/distributed/elastic/agent/server/api.py", line 731, in run
|
| 11 |
+
result = self._invoke_run(role)
|
| 12 |
+
File "/home/ubuntu/modded-nanogpt/.venv/lib/python3.14/site-packages/torch/distributed/elastic/agent/server/api.py", line 908, in _invoke_run
|
| 13 |
+
time.sleep(monitor_interval)
|
| 14 |
+
~~~~~~~~~~^^^^^^^^^^^^^^^^^^
|
| 15 |
+
File "/home/ubuntu/modded-nanogpt/.venv/lib/python3.14/site-packages/torch/distributed/elastic/multiprocessing/api.py", line 86, in _terminate_process_handler
|
| 16 |
+
raise SignalException(f"Process {os.getpid()} got signal: {sigval}", sigval=sigval)
|
| 17 |
+
torch.distributed.elastic.multiprocessing.api.SignalException: Process 198186 got signal: 15
|
| 18 |
+
|
| 19 |
+
During handling of the above exception, another exception occurred:
|
| 20 |
+
|
| 21 |
+
Traceback (most recent call last):
|
| 22 |
+
File "/home/ubuntu/modded-nanogpt/.venv/bin/torchrun", line 6, in <module>
|
| 23 |
+
sys.exit(main())
|
| 24 |
+
~~~~^^
|
| 25 |
+
File "/home/ubuntu/modded-nanogpt/.venv/lib/python3.14/site-packages/torch/distributed/elastic/multiprocessing/errors/__init__.py", line 362, in wrapper
|
| 26 |
+
return f(*args, **kwargs)
|
| 27 |
+
File "/home/ubuntu/modded-nanogpt/.venv/lib/python3.14/site-packages/torch/distributed/run.py", line 991, in main
|
| 28 |
+
run(args)
|
| 29 |
+
~~~^^^^^^
|
| 30 |
+
File "/home/ubuntu/modded-nanogpt/.venv/lib/python3.14/site-packages/torch/distributed/run.py", line 982, in run
|
| 31 |
+
elastic_launch(
|
| 32 |
+
~~~~~~~~~~~~~~~
|
| 33 |
+
config=config,
|
| 34 |
+
~~~~~~~~~~~~~~
|
| 35 |
+
entrypoint=cmd,
|
| 36 |
+
~~~~~~~~~~~~~~~
|
| 37 |
+
)(*cmd_args)
|
| 38 |
+
~^^^^^^^^^^^
|
| 39 |
+
File "/home/ubuntu/modded-nanogpt/.venv/lib/python3.14/site-packages/torch/distributed/launcher/api.py", line 170, in __call__
|
| 40 |
+
return launch_agent(self._config, self._entrypoint, list(args))
|
| 41 |
+
File "/home/ubuntu/modded-nanogpt/.venv/lib/python3.14/site-packages/torch/distributed/launcher/api.py", line 308, in launch_agent
|
| 42 |
+
result = agent.run()
|
| 43 |
+
File "/home/ubuntu/modded-nanogpt/.venv/lib/python3.14/site-packages/torch/distributed/elastic/metrics/api.py", line 134, in wrapper
|
| 44 |
+
result = f(*args, **kwargs)
|
| 45 |
+
File "/home/ubuntu/modded-nanogpt/.venv/lib/python3.14/site-packages/torch/distributed/elastic/agent/server/api.py", line 740, in run
|
| 46 |
+
self._shutdown(e.sigval)
|
| 47 |
+
~~~~~~~~~~~~~~^^^^^^^^^^
|
| 48 |
+
File "/home/ubuntu/modded-nanogpt/.venv/lib/python3.14/site-packages/torch/distributed/elastic/agent/server/local_elastic_agent.py", line 413, in _shutdown
|
| 49 |
+
self._pcontext.close(death_sig)
|
| 50 |
+
~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
|
| 51 |
+
File "/home/ubuntu/modded-nanogpt/.venv/lib/python3.14/site-packages/torch/distributed/elastic/multiprocessing/api.py", line 659, in close
|
| 52 |
+
self._close(death_sig=death_sig, timeout=timeout)
|
| 53 |
+
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 54 |
+
File "/home/ubuntu/modded-nanogpt/.venv/lib/python3.14/site-packages/torch/distributed/elastic/multiprocessing/api.py", line 1022, in _close
|
| 55 |
+
handler.proc.wait(time_to_wait)
|
| 56 |
+
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^
|
| 57 |
+
File "/usr/lib/python3.14/subprocess.py", line 1279, in wait
|
| 58 |
+
return self._wait(timeout=timeout)
|
| 59 |
+
~~~~~~~~~~^^^^^^^^^^^^^^^^^
|
| 60 |
+
File "/usr/lib/python3.14/subprocess.py", line 2078, in _wait
|
| 61 |
+
time.sleep(delay)
|
| 62 |
+
~~~~~~~~~~^^^^^^^
|
| 63 |
+
File "/home/ubuntu/modded-nanogpt/.venv/lib/python3.14/site-packages/torch/distributed/elastic/multiprocessing/api.py", line 86, in _terminate_process_handler
|
| 64 |
+
raise SignalException(f"Process {os.getpid()} got signal: {sigval}", sigval=sigval)
|
| 65 |
+
torch.distributed.elastic.multiprocessing.api.SignalException: Process 198186 got signal: 1
|
metadata/artifact_manifest.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"created_from": {
|
| 3 |
+
"local_gpt_checkpoint": "/Users/kacper/Local/Ventures/Slayer/gpt2-pl-mac/ckpt.pt",
|
| 4 |
+
"local_gpt_tokenizer": "/Users/kacper/Local/Ventures/Slayer/gpt2-pl-mac/polish_bpe_32k.json",
|
| 5 |
+
"local_rxlm_tokenizer": "/Users/kacper/Local/DueDil/rxlm/beta-training/tokenizer.json",
|
| 6 |
+
"remote_rxlm_tokenizer": "slayer:/home/ubuntu/rxlm_dd/beta-training/tokenizer.json",
|
| 7 |
+
"remote_nanogpt_tree": "slayer:/home/ubuntu/modded-nanogpt"
|
| 8 |
+
},
|
| 9 |
+
"model_ckpt": {
|
| 10 |
+
"path": "model/ckpt.pt",
|
| 11 |
+
"format": "torch checkpoint",
|
| 12 |
+
"checkpoint_keys": ["model", "model_args", "iter"],
|
| 13 |
+
"model_args": {
|
| 14 |
+
"n_layer": 12,
|
| 15 |
+
"n_head": 12,
|
| 16 |
+
"n_embd": 768,
|
| 17 |
+
"block_size": 1024,
|
| 18 |
+
"bias": false,
|
| 19 |
+
"vocab_size": 32768,
|
| 20 |
+
"dropout": 0.0
|
| 21 |
+
},
|
| 22 |
+
"iter": 500,
|
| 23 |
+
"state_dict_parameters": 136071936,
|
| 24 |
+
"tokenizer": "tokenizers/polish_bpe_32k.json"
|
| 25 |
+
},
|
| 26 |
+
"tokenizers": {
|
| 27 |
+
"polish_bpe_32k": {
|
| 28 |
+
"path": "tokenizers/polish_bpe_32k.json",
|
| 29 |
+
"type": "ByteLevel BPE",
|
| 30 |
+
"vocab_size": 32768,
|
| 31 |
+
"merges": 32511,
|
| 32 |
+
"add_prefix_space": false
|
| 33 |
+
},
|
| 34 |
+
"rxlm_polish_bpe_65k": {
|
| 35 |
+
"path": "tokenizers/rxlm_polish_bpe_65k.json",
|
| 36 |
+
"type": "ByteLevel BPE",
|
| 37 |
+
"vocab_size": 65536,
|
| 38 |
+
"merges": 65283,
|
| 39 |
+
"normalizer": "NFKC",
|
| 40 |
+
"add_prefix_space": true
|
| 41 |
+
}
|
| 42 |
+
}
|
| 43 |
+
}
|
| 44 |
+
|
metadata/loss_train.csv
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
0,10.5432
|
| 2 |
+
10,8.9096
|
| 3 |
+
20,8.3818
|
| 4 |
+
30,7.7284
|
| 5 |
+
40,7.3585
|
| 6 |
+
50,7.2123
|
| 7 |
+
60,6.8500
|
| 8 |
+
70,6.7956
|
| 9 |
+
80,6.4644
|
| 10 |
+
90,6.4187
|
| 11 |
+
100,6.4978
|
| 12 |
+
110,6.2566
|
| 13 |
+
120,6.3528
|
| 14 |
+
130,6.0993
|
| 15 |
+
140,6.0455
|
| 16 |
+
150,6.0754
|
| 17 |
+
160,5.8164
|
| 18 |
+
170,5.8069
|
| 19 |
+
180,5.7759
|
| 20 |
+
190,5.6976
|
| 21 |
+
200,5.6238
|
| 22 |
+
210,5.6289
|
| 23 |
+
220,5.5407
|
| 24 |
+
230,5.4001
|
| 25 |
+
240,5.4523
|
| 26 |
+
250,5.4548
|
| 27 |
+
260,5.2646
|
| 28 |
+
270,5.2490
|
| 29 |
+
280,5.2464
|
| 30 |
+
290,5.2203
|
| 31 |
+
300,5.2502
|
| 32 |
+
310,5.1590
|
| 33 |
+
320,5.0595
|
| 34 |
+
330,5.1221
|
| 35 |
+
340,5.0980
|
| 36 |
+
350,4.9837
|
| 37 |
+
360,4.9870
|
| 38 |
+
370,4.8676
|
| 39 |
+
380,5.0423
|
| 40 |
+
390,4.8983
|
| 41 |
+
400,4.8116
|
| 42 |
+
410,4.7852
|
| 43 |
+
420,4.7880
|
| 44 |
+
430,4.7554
|
| 45 |
+
440,4.7762
|
| 46 |
+
450,4.7746
|
| 47 |
+
460,4.8073
|
| 48 |
+
470,4.5162
|
| 49 |
+
480,4.5992
|
| 50 |
+
490,4.6830
|
| 51 |
+
500,4.6345
|
| 52 |
+
510,4.3883
|
| 53 |
+
520,4.6188
|
| 54 |
+
530,4.4315
|
| 55 |
+
540,4.4713
|
| 56 |
+
550,4.4083
|
| 57 |
+
560,4.3543
|
| 58 |
+
570,4.3069
|
| 59 |
+
580,4.2223
|
| 60 |
+
590,4.3264
|
| 61 |
+
600,4.3473
|
| 62 |
+
610,4.1376
|
| 63 |
+
620,4.2780
|
| 64 |
+
630,4.2489
|
| 65 |
+
640,4.1217
|
| 66 |
+
650,4.1767
|
| 67 |
+
660,4.0496
|
| 68 |
+
670,4.0011
|
| 69 |
+
680,4.0010
|
| 70 |
+
690,4.0702
|
| 71 |
+
700,4.0163
|
| 72 |
+
710,4.0544
|
| 73 |
+
720,4.1402
|
| 74 |
+
730,4.0240
|
| 75 |
+
740,4.1338
|
| 76 |
+
750,4.0968
|
| 77 |
+
760,3.9717
|
| 78 |
+
770,3.8710
|
| 79 |
+
780,3.9123
|
| 80 |
+
790,3.9936
|
| 81 |
+
800,3.9854
|
| 82 |
+
810,3.9391
|
| 83 |
+
820,3.8748
|
| 84 |
+
830,3.9396
|
| 85 |
+
840,4.0900
|
| 86 |
+
850,3.9185
|
| 87 |
+
860,3.9237
|
| 88 |
+
870,3.9972
|
| 89 |
+
880,3.8443
|
| 90 |
+
890,3.8706
|
| 91 |
+
900,3.9335
|
| 92 |
+
910,3.8034
|
| 93 |
+
920,3.8431
|
| 94 |
+
930,3.8501
|
| 95 |
+
940,3.9286
|
| 96 |
+
950,3.8670
|
| 97 |
+
960,3.8986
|
| 98 |
+
970,3.6916
|
| 99 |
+
980,3.7584
|
| 100 |
+
990,3.7107
|
| 101 |
+
1000,3.5749
|
| 102 |
+
1010,3.7844
|
| 103 |
+
1020,3.8467
|
| 104 |
+
1030,3.6829
|
| 105 |
+
1040,3.7354
|
| 106 |
+
1050,3.9265
|
| 107 |
+
1060,3.7477
|
| 108 |
+
1070,3.6859
|
| 109 |
+
1080,3.7451
|
| 110 |
+
1090,3.8840
|
| 111 |
+
1100,3.7716
|
| 112 |
+
1110,3.6441
|
| 113 |
+
1120,3.7806
|
| 114 |
+
1130,3.6817
|
| 115 |
+
1140,3.7985
|
| 116 |
+
1150,3.7247
|
| 117 |
+
1160,3.7286
|
| 118 |
+
1170,3.7495
|
| 119 |
+
1180,3.7451
|
| 120 |
+
1190,3.7496
|
| 121 |
+
1200,3.7041
|
| 122 |
+
1210,3.7436
|
| 123 |
+
1220,3.5851
|
| 124 |
+
1230,3.6694
|
| 125 |
+
1240,3.5732
|
| 126 |
+
1250,3.7169
|
| 127 |
+
1260,3.7615
|
| 128 |
+
1270,3.7332
|
| 129 |
+
1280,3.6454
|
| 130 |
+
1290,3.7745
|
| 131 |
+
1300,3.5835
|
| 132 |
+
1310,3.6660
|
| 133 |
+
1320,3.7584
|
| 134 |
+
1330,3.6219
|
| 135 |
+
1340,3.6977
|
| 136 |
+
1350,3.5445
|
| 137 |
+
1360,3.6224
|
| 138 |
+
1370,3.6865
|
| 139 |
+
1380,3.6163
|
| 140 |
+
1390,3.8143
|
| 141 |
+
1400,3.6447
|
| 142 |
+
1410,3.6732
|
| 143 |
+
1420,3.5276
|
| 144 |
+
1430,3.6848
|
| 145 |
+
1440,3.7317
|
| 146 |
+
1450,3.7915
|
| 147 |
+
1460,3.6741
|
| 148 |
+
1470,3.6490
|
| 149 |
+
1480,3.6448
|
| 150 |
+
1490,3.5571
|
| 151 |
+
1500,3.6427
|
| 152 |
+
1510,3.7507
|
| 153 |
+
1520,3.6749
|
| 154 |
+
1530,3.7123
|
| 155 |
+
1540,3.7059
|
| 156 |
+
1550,3.5544
|
| 157 |
+
1560,3.6306
|
| 158 |
+
1570,3.7105
|
| 159 |
+
1580,3.7773
|
| 160 |
+
1590,3.7557
|
| 161 |
+
1600,3.6184
|
metadata/traj.csv
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
iter,gram,know
|
| 2 |
+
1000,90.9,28.4
|
| 3 |
+
1100,90.9,32.6
|
| 4 |
+
1200,90.9,25.3
|
| 5 |
+
1300,90.9,26.3
|
| 6 |
+
1400,90.9,30.5
|
| 7 |
+
1500,90.9,28.4
|
| 8 |
+
1600,90.9,27.4
|
model/ckpt.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:157c49b887ab119a697b14db3b01ad724b8b2110d49fa5f33c79e38716473b1e
|
| 3 |
+
size 272168309
|
requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch
|
| 2 |
+
tokenizers
|
| 3 |
+
numpy
|
| 4 |
+
|
scripts/knowbench_mac.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Mini knowledge bench (likelihood MCQ) for GPT-2-PL. Run: python knowbench_mac.py [-v]
|
| 2 |
+
Scores each item by length-normalized log-prob of each answer; picks argmax. No generation."""
|
| 3 |
+
import os, sys, math, torch, torch.nn.functional as F
|
| 4 |
+
from model import GPTConfig, GPT
|
| 5 |
+
from tokenizers import Tokenizer
|
| 6 |
+
|
| 7 |
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 8 |
+
ROOT = os.path.dirname(HERE)
|
| 9 |
+
VERBOSE = "-v" in sys.argv
|
| 10 |
+
dev = "mps" if torch.backends.mps.is_available() else "cpu"
|
| 11 |
+
|
| 12 |
+
# (prompt, correct, [distractors]) — correct is index 0; choices shuffled deterministically by pos
|
| 13 |
+
ITEMS = [
|
| 14 |
+
("Stolicą Polski jest", "Warszawa", ["Kraków", "Gdańsk", "Poznań"]),
|
| 15 |
+
("Najdłuższą rzeką w Polsce jest", "Wisła", ["Odra", "Warta", "Bug"]),
|
| 16 |
+
("Autorem „Pana Tadeusza” jest", "Adam Mickiewicz", ["Juliusz Słowacki", "Henryk Sienkiewicz", "Bolesław Prus"]),
|
| 17 |
+
("Pierwszym koronowanym królem Polski był", "Bolesław Chrobry", ["Kazimierz Wielki", "Władysław Łokietek", "Jan Sobieski"]),
|
| 18 |
+
("Polska leży w", "Europie", ["Azji", "Afryce", "Ameryce"]),
|
| 19 |
+
("Walutą Polski jest", "złoty", ["euro", "dolar", "frank"]),
|
| 20 |
+
("Tatry to", "góry", ["rzeka", "jezioro", "miasto"]),
|
| 21 |
+
("Mikołaj Kopernik był", "astronomem", ["malarzem", "kompozytorem", "pisarzem"]),
|
| 22 |
+
("Fryderyk Chopin komponował", "muzykę", ["obrazy", "powieści", "rzeźby"]),
|
| 23 |
+
("Druga wojna światowa wybuchła w roku", "1939", ["1914", "1945", "1918"]),
|
| 24 |
+
("Polska wstąpiła do Unii Europejskiej w roku", "2004", ["1999", "2010", "1989"]),
|
| 25 |
+
("Słońce jest", "gwiazdą", ["planetą", "księżycem", "kometą"]),
|
| 26 |
+
("Woda składa się z wodoru i", "tlenu", ["azotu", "węgla", "żelaza"]),
|
| 27 |
+
("Największym oceanem jest Ocean", "Spokojny", ["Atlantycki", "Indyjski", "Arktyczny"]),
|
| 28 |
+
("Pszczoły produkują", "miód", ["mleko", "jedwab", "wełnę"]),
|
| 29 |
+
("Stolicą Francji jest", "Paryż", ["Londyn", "Berlin", "Madryt"]),
|
| 30 |
+
("Wisła wpada do Morza", "Bałtyckiego", ["Czarnego", "Śródziemnego", "Czerwonego"]),
|
| 31 |
+
("Lech Wałęsa był przywódcą", "Solidarności", ["PZPR", "Sejmu", "wojska"]),
|
| 32 |
+
("Kraków leży nad", "Wisłą", ["Odrą", "Wartą", "Bugiem"]),
|
| 33 |
+
("Zakopane leży w", "Tatrach", ["Bieszczadach", "Sudetach", "Karkonoszach"]),
|
| 34 |
+
("Bursztyn powstaje z", "żywicy", ["kamienia", "metalu", "piasku"]),
|
| 35 |
+
("Człowiek do oddychania potrzebuje", "tlenu", ["azotu", "wodoru", "helu"]),
|
| 36 |
+
("Księżyc krąży wokół", "Ziemi", ["Słońca", "Marsa", "Wenus"]),
|
| 37 |
+
("Orzeł biały znajduje się w godle", "Polski", ["Niemiec", "Francji", "Czech"]),
|
| 38 |
+
]
|
| 39 |
+
|
| 40 |
+
ck = torch.load(os.path.join(ROOT, "model", "ckpt.pt"), map_location="cpu")
|
| 41 |
+
m = GPT(GPTConfig(**ck["model_args"]))
|
| 42 |
+
sd = ck["model"]
|
| 43 |
+
for k in list(sd):
|
| 44 |
+
if k.startswith("_orig_mod."): sd[k[len("_orig_mod."):]] = sd.pop(k)
|
| 45 |
+
m.load_state_dict(sd); m.eval().to(dev)
|
| 46 |
+
block = ck["model_args"]["block_size"]
|
| 47 |
+
tok = Tokenizer.from_file(os.path.join(ROOT, "tokenizers", "polish_bpe_32k.json"))
|
| 48 |
+
|
| 49 |
+
@torch.no_grad()
|
| 50 |
+
def score(prompt, answer):
|
| 51 |
+
"""length-normalized log-prob of answer tokens given prompt."""
|
| 52 |
+
pids = tok.encode(prompt).ids
|
| 53 |
+
fids = tok.encode(prompt + " " + answer).ids
|
| 54 |
+
# common prefix length (BPE boundary safety)
|
| 55 |
+
plen = 0
|
| 56 |
+
for a, b in zip(pids, fids):
|
| 57 |
+
if a != b: break
|
| 58 |
+
plen += 1
|
| 59 |
+
ans = fids[plen:]
|
| 60 |
+
if not ans: return -1e9
|
| 61 |
+
x = torch.tensor(fids, dtype=torch.long, device=dev)[None]
|
| 62 |
+
logits, _ = m(x, x) # targets triggers full-position logits
|
| 63 |
+
logp = F.log_softmax(logits[0].float(), dim=-1)
|
| 64 |
+
total = sum(logp[plen + i - 1, ans[i]].item() for i in range(len(ans)))
|
| 65 |
+
return total / len(ans)
|
| 66 |
+
|
| 67 |
+
correct = 0
|
| 68 |
+
rows = []
|
| 69 |
+
for prompt, ans, dist in ITEMS:
|
| 70 |
+
cands = [ans] + dist
|
| 71 |
+
scores = [(c, score(prompt, c)) for c in cands]
|
| 72 |
+
pred = max(scores, key=lambda s: s[1])[0]
|
| 73 |
+
ok = pred == ans
|
| 74 |
+
correct += ok
|
| 75 |
+
rows.append((prompt, ans, pred, ok))
|
| 76 |
+
|
| 77 |
+
n = len(ITEMS)
|
| 78 |
+
base = sum(1/(1+len(d)) for _,_,d in ITEMS) / n
|
| 79 |
+
print(f"\nMini-bench wiedzy PL (likelihood MCQ, {n} pytań, 4 opcje)")
|
| 80 |
+
print(f"checkpoint iter {ck.get('iter')} · device {dev}")
|
| 81 |
+
print("=" * 50)
|
| 82 |
+
print(f"ACCURACY: {correct}/{n} = {100*correct/n:.1f}% (losowy baseline {100*base:.1f}%)")
|
| 83 |
+
if VERBOSE:
|
| 84 |
+
print("-" * 50)
|
| 85 |
+
for p, a, pred, ok in rows:
|
| 86 |
+
print(f"{'✓' if ok else '✗'} {p} → {pred}" + ("" if ok else f" [poprawnie: {a}]"))
|
scripts/model.py
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Full definition of a GPT Language Model, all of it in this single file.
|
| 3 |
+
References:
|
| 4 |
+
1) the official GPT-2 TensorFlow implementation released by OpenAI:
|
| 5 |
+
https://github.com/openai/gpt-2/blob/master/src/model.py
|
| 6 |
+
2) huggingface/transformers PyTorch implementation:
|
| 7 |
+
https://github.com/huggingface/transformers/blob/main/src/transformers/models/gpt2/modeling_gpt2.py
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import math
|
| 11 |
+
import inspect
|
| 12 |
+
from dataclasses import dataclass
|
| 13 |
+
|
| 14 |
+
import torch
|
| 15 |
+
import torch.nn as nn
|
| 16 |
+
from torch.nn import functional as F
|
| 17 |
+
|
| 18 |
+
class LayerNorm(nn.Module):
|
| 19 |
+
""" LayerNorm but with an optional bias. PyTorch doesn't support simply bias=False """
|
| 20 |
+
|
| 21 |
+
def __init__(self, ndim, bias):
|
| 22 |
+
super().__init__()
|
| 23 |
+
self.weight = nn.Parameter(torch.ones(ndim))
|
| 24 |
+
self.bias = nn.Parameter(torch.zeros(ndim)) if bias else None
|
| 25 |
+
|
| 26 |
+
def forward(self, input):
|
| 27 |
+
return F.layer_norm(input, self.weight.shape, self.weight, self.bias, 1e-5)
|
| 28 |
+
|
| 29 |
+
class CausalSelfAttention(nn.Module):
|
| 30 |
+
|
| 31 |
+
def __init__(self, config):
|
| 32 |
+
super().__init__()
|
| 33 |
+
assert config.n_embd % config.n_head == 0
|
| 34 |
+
# key, query, value projections for all heads, but in a batch
|
| 35 |
+
self.c_attn = nn.Linear(config.n_embd, 3 * config.n_embd, bias=config.bias)
|
| 36 |
+
# output projection
|
| 37 |
+
self.c_proj = nn.Linear(config.n_embd, config.n_embd, bias=config.bias)
|
| 38 |
+
# regularization
|
| 39 |
+
self.attn_dropout = nn.Dropout(config.dropout)
|
| 40 |
+
self.resid_dropout = nn.Dropout(config.dropout)
|
| 41 |
+
self.n_head = config.n_head
|
| 42 |
+
self.n_embd = config.n_embd
|
| 43 |
+
self.dropout = config.dropout
|
| 44 |
+
# flash attention make GPU go brrrrr but support is only in PyTorch >= 2.0
|
| 45 |
+
self.flash = hasattr(torch.nn.functional, 'scaled_dot_product_attention')
|
| 46 |
+
if not self.flash:
|
| 47 |
+
print("WARNING: using slow attention. Flash Attention requires PyTorch >= 2.0")
|
| 48 |
+
# causal mask to ensure that attention is only applied to the left in the input sequence
|
| 49 |
+
self.register_buffer("bias", torch.tril(torch.ones(config.block_size, config.block_size))
|
| 50 |
+
.view(1, 1, config.block_size, config.block_size))
|
| 51 |
+
|
| 52 |
+
def forward(self, x):
|
| 53 |
+
B, T, C = x.size() # batch size, sequence length, embedding dimensionality (n_embd)
|
| 54 |
+
|
| 55 |
+
# calculate query, key, values for all heads in batch and move head forward to be the batch dim
|
| 56 |
+
q, k, v = self.c_attn(x).split(self.n_embd, dim=2)
|
| 57 |
+
k = k.view(B, T, self.n_head, C // self.n_head).transpose(1, 2) # (B, nh, T, hs)
|
| 58 |
+
q = q.view(B, T, self.n_head, C // self.n_head).transpose(1, 2) # (B, nh, T, hs)
|
| 59 |
+
v = v.view(B, T, self.n_head, C // self.n_head).transpose(1, 2) # (B, nh, T, hs)
|
| 60 |
+
|
| 61 |
+
# causal self-attention; Self-attend: (B, nh, T, hs) x (B, nh, hs, T) -> (B, nh, T, T)
|
| 62 |
+
if self.flash:
|
| 63 |
+
# efficient attention using Flash Attention CUDA kernels
|
| 64 |
+
y = torch.nn.functional.scaled_dot_product_attention(q, k, v, attn_mask=None, dropout_p=self.dropout if self.training else 0, is_causal=True)
|
| 65 |
+
else:
|
| 66 |
+
# manual implementation of attention
|
| 67 |
+
att = (q @ k.transpose(-2, -1)) * (1.0 / math.sqrt(k.size(-1)))
|
| 68 |
+
att = att.masked_fill(self.bias[:,:,:T,:T] == 0, float('-inf'))
|
| 69 |
+
att = F.softmax(att, dim=-1)
|
| 70 |
+
att = self.attn_dropout(att)
|
| 71 |
+
y = att @ v # (B, nh, T, T) x (B, nh, T, hs) -> (B, nh, T, hs)
|
| 72 |
+
y = y.transpose(1, 2).contiguous().view(B, T, C) # re-assemble all head outputs side by side
|
| 73 |
+
|
| 74 |
+
# output projection
|
| 75 |
+
y = self.resid_dropout(self.c_proj(y))
|
| 76 |
+
return y
|
| 77 |
+
|
| 78 |
+
class MLP(nn.Module):
|
| 79 |
+
|
| 80 |
+
def __init__(self, config):
|
| 81 |
+
super().__init__()
|
| 82 |
+
self.c_fc = nn.Linear(config.n_embd, 4 * config.n_embd, bias=config.bias)
|
| 83 |
+
self.gelu = nn.GELU()
|
| 84 |
+
self.c_proj = nn.Linear(4 * config.n_embd, config.n_embd, bias=config.bias)
|
| 85 |
+
self.dropout = nn.Dropout(config.dropout)
|
| 86 |
+
|
| 87 |
+
def forward(self, x):
|
| 88 |
+
x = self.c_fc(x)
|
| 89 |
+
x = self.gelu(x)
|
| 90 |
+
x = self.c_proj(x)
|
| 91 |
+
x = self.dropout(x)
|
| 92 |
+
return x
|
| 93 |
+
|
| 94 |
+
class Block(nn.Module):
|
| 95 |
+
|
| 96 |
+
def __init__(self, config):
|
| 97 |
+
super().__init__()
|
| 98 |
+
self.ln_1 = LayerNorm(config.n_embd, bias=config.bias)
|
| 99 |
+
self.attn = CausalSelfAttention(config)
|
| 100 |
+
self.ln_2 = LayerNorm(config.n_embd, bias=config.bias)
|
| 101 |
+
self.mlp = MLP(config)
|
| 102 |
+
|
| 103 |
+
def forward(self, x):
|
| 104 |
+
x = x + self.attn(self.ln_1(x))
|
| 105 |
+
x = x + self.mlp(self.ln_2(x))
|
| 106 |
+
return x
|
| 107 |
+
|
| 108 |
+
@dataclass
|
| 109 |
+
class GPTConfig:
|
| 110 |
+
block_size: int = 1024
|
| 111 |
+
vocab_size: int = 50304 # GPT-2 vocab_size of 50257, padded up to nearest multiple of 64 for efficiency
|
| 112 |
+
n_layer: int = 12
|
| 113 |
+
n_head: int = 12
|
| 114 |
+
n_embd: int = 768
|
| 115 |
+
dropout: float = 0.0
|
| 116 |
+
bias: bool = True # True: bias in Linears and LayerNorms, like GPT-2. False: a bit better and faster
|
| 117 |
+
|
| 118 |
+
class GPT(nn.Module):
|
| 119 |
+
|
| 120 |
+
def __init__(self, config):
|
| 121 |
+
super().__init__()
|
| 122 |
+
assert config.vocab_size is not None
|
| 123 |
+
assert config.block_size is not None
|
| 124 |
+
self.config = config
|
| 125 |
+
|
| 126 |
+
self.transformer = nn.ModuleDict(dict(
|
| 127 |
+
wte = nn.Embedding(config.vocab_size, config.n_embd),
|
| 128 |
+
wpe = nn.Embedding(config.block_size, config.n_embd),
|
| 129 |
+
drop = nn.Dropout(config.dropout),
|
| 130 |
+
h = nn.ModuleList([Block(config) for _ in range(config.n_layer)]),
|
| 131 |
+
ln_f = LayerNorm(config.n_embd, bias=config.bias),
|
| 132 |
+
))
|
| 133 |
+
self.lm_head = nn.Linear(config.n_embd, config.vocab_size, bias=False)
|
| 134 |
+
# with weight tying when using torch.compile() some warnings get generated:
|
| 135 |
+
# "UserWarning: functional_call was passed multiple values for tied weights.
|
| 136 |
+
# This behavior is deprecated and will be an error in future versions"
|
| 137 |
+
# not 100% sure what this is, so far seems to be harmless. TODO investigate
|
| 138 |
+
self.transformer.wte.weight = self.lm_head.weight # https://paperswithcode.com/method/weight-tying
|
| 139 |
+
|
| 140 |
+
# init all weights
|
| 141 |
+
self.apply(self._init_weights)
|
| 142 |
+
# apply special scaled init to the residual projections, per GPT-2 paper
|
| 143 |
+
for pn, p in self.named_parameters():
|
| 144 |
+
if pn.endswith('c_proj.weight'):
|
| 145 |
+
torch.nn.init.normal_(p, mean=0.0, std=0.02/math.sqrt(2 * config.n_layer))
|
| 146 |
+
|
| 147 |
+
# report number of parameters
|
| 148 |
+
print("number of parameters: %.2fM" % (self.get_num_params()/1e6,))
|
| 149 |
+
|
| 150 |
+
def get_num_params(self, non_embedding=True):
|
| 151 |
+
"""
|
| 152 |
+
Return the number of parameters in the model.
|
| 153 |
+
For non-embedding count (default), the position embeddings get subtracted.
|
| 154 |
+
The token embeddings would too, except due to the parameter sharing these
|
| 155 |
+
params are actually used as weights in the final layer, so we include them.
|
| 156 |
+
"""
|
| 157 |
+
n_params = sum(p.numel() for p in self.parameters())
|
| 158 |
+
if non_embedding:
|
| 159 |
+
n_params -= self.transformer.wpe.weight.numel()
|
| 160 |
+
return n_params
|
| 161 |
+
|
| 162 |
+
def _init_weights(self, module):
|
| 163 |
+
if isinstance(module, nn.Linear):
|
| 164 |
+
torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
|
| 165 |
+
if module.bias is not None:
|
| 166 |
+
torch.nn.init.zeros_(module.bias)
|
| 167 |
+
elif isinstance(module, nn.Embedding):
|
| 168 |
+
torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
|
| 169 |
+
|
| 170 |
+
def forward(self, idx, targets=None):
|
| 171 |
+
device = idx.device
|
| 172 |
+
b, t = idx.size()
|
| 173 |
+
assert t <= self.config.block_size, f"Cannot forward sequence of length {t}, block size is only {self.config.block_size}"
|
| 174 |
+
pos = torch.arange(0, t, dtype=torch.long, device=device) # shape (t)
|
| 175 |
+
|
| 176 |
+
# forward the GPT model itself
|
| 177 |
+
tok_emb = self.transformer.wte(idx) # token embeddings of shape (b, t, n_embd)
|
| 178 |
+
pos_emb = self.transformer.wpe(pos) # position embeddings of shape (t, n_embd)
|
| 179 |
+
x = self.transformer.drop(tok_emb + pos_emb)
|
| 180 |
+
for block in self.transformer.h:
|
| 181 |
+
x = block(x)
|
| 182 |
+
x = self.transformer.ln_f(x)
|
| 183 |
+
|
| 184 |
+
if targets is not None:
|
| 185 |
+
# if we are given some desired targets also calculate the loss
|
| 186 |
+
logits = self.lm_head(x)
|
| 187 |
+
loss = F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1), ignore_index=-1)
|
| 188 |
+
else:
|
| 189 |
+
# inference-time mini-optimization: only forward the lm_head on the very last position
|
| 190 |
+
logits = self.lm_head(x[:, [-1], :]) # note: using list [-1] to preserve the time dim
|
| 191 |
+
loss = None
|
| 192 |
+
|
| 193 |
+
return logits, loss
|
| 194 |
+
|
| 195 |
+
def crop_block_size(self, block_size):
|
| 196 |
+
# model surgery to decrease the block size if necessary
|
| 197 |
+
# e.g. we may load the GPT2 pretrained model checkpoint (block size 1024)
|
| 198 |
+
# but want to use a smaller block size for some smaller, simpler model
|
| 199 |
+
assert block_size <= self.config.block_size
|
| 200 |
+
self.config.block_size = block_size
|
| 201 |
+
self.transformer.wpe.weight = nn.Parameter(self.transformer.wpe.weight[:block_size])
|
| 202 |
+
for block in self.transformer.h:
|
| 203 |
+
if hasattr(block.attn, 'bias'):
|
| 204 |
+
block.attn.bias = block.attn.bias[:,:,:block_size,:block_size]
|
| 205 |
+
|
| 206 |
+
@classmethod
|
| 207 |
+
def from_pretrained(cls, model_type, override_args=None):
|
| 208 |
+
assert model_type in {'gpt2', 'gpt2-medium', 'gpt2-large', 'gpt2-xl'}
|
| 209 |
+
override_args = override_args or {} # default to empty dict
|
| 210 |
+
# only dropout can be overridden see more notes below
|
| 211 |
+
assert all(k == 'dropout' for k in override_args)
|
| 212 |
+
from transformers import GPT2LMHeadModel
|
| 213 |
+
print("loading weights from pretrained gpt: %s" % model_type)
|
| 214 |
+
|
| 215 |
+
# n_layer, n_head and n_embd are determined from model_type
|
| 216 |
+
config_args = {
|
| 217 |
+
'gpt2': dict(n_layer=12, n_head=12, n_embd=768), # 124M params
|
| 218 |
+
'gpt2-medium': dict(n_layer=24, n_head=16, n_embd=1024), # 350M params
|
| 219 |
+
'gpt2-large': dict(n_layer=36, n_head=20, n_embd=1280), # 774M params
|
| 220 |
+
'gpt2-xl': dict(n_layer=48, n_head=25, n_embd=1600), # 1558M params
|
| 221 |
+
}[model_type]
|
| 222 |
+
print("forcing vocab_size=50257, block_size=1024, bias=True")
|
| 223 |
+
config_args['vocab_size'] = 50257 # always 50257 for GPT model checkpoints
|
| 224 |
+
config_args['block_size'] = 1024 # always 1024 for GPT model checkpoints
|
| 225 |
+
config_args['bias'] = True # always True for GPT model checkpoints
|
| 226 |
+
# we can override the dropout rate, if desired
|
| 227 |
+
if 'dropout' in override_args:
|
| 228 |
+
print(f"overriding dropout rate to {override_args['dropout']}")
|
| 229 |
+
config_args['dropout'] = override_args['dropout']
|
| 230 |
+
# create a from-scratch initialized minGPT model
|
| 231 |
+
config = GPTConfig(**config_args)
|
| 232 |
+
model = GPT(config)
|
| 233 |
+
sd = model.state_dict()
|
| 234 |
+
sd_keys = sd.keys()
|
| 235 |
+
sd_keys = [k for k in sd_keys if not k.endswith('.attn.bias')] # discard this mask / buffer, not a param
|
| 236 |
+
|
| 237 |
+
# init a huggingface/transformers model
|
| 238 |
+
model_hf = GPT2LMHeadModel.from_pretrained(model_type)
|
| 239 |
+
sd_hf = model_hf.state_dict()
|
| 240 |
+
|
| 241 |
+
# copy while ensuring all of the parameters are aligned and match in names and shapes
|
| 242 |
+
sd_keys_hf = sd_hf.keys()
|
| 243 |
+
sd_keys_hf = [k for k in sd_keys_hf if not k.endswith('.attn.masked_bias')] # ignore these, just a buffer
|
| 244 |
+
sd_keys_hf = [k for k in sd_keys_hf if not k.endswith('.attn.bias')] # same, just the mask (buffer)
|
| 245 |
+
transposed = ['attn.c_attn.weight', 'attn.c_proj.weight', 'mlp.c_fc.weight', 'mlp.c_proj.weight']
|
| 246 |
+
# basically the openai checkpoints use a "Conv1D" module, but we only want to use a vanilla Linear
|
| 247 |
+
# this means that we have to transpose these weights when we import them
|
| 248 |
+
assert len(sd_keys_hf) == len(sd_keys), f"mismatched keys: {len(sd_keys_hf)} != {len(sd_keys)}"
|
| 249 |
+
for k in sd_keys_hf:
|
| 250 |
+
if any(k.endswith(w) for w in transposed):
|
| 251 |
+
# special treatment for the Conv1D weights we need to transpose
|
| 252 |
+
assert sd_hf[k].shape[::-1] == sd[k].shape
|
| 253 |
+
with torch.no_grad():
|
| 254 |
+
sd[k].copy_(sd_hf[k].t())
|
| 255 |
+
else:
|
| 256 |
+
# vanilla copy over the other parameters
|
| 257 |
+
assert sd_hf[k].shape == sd[k].shape
|
| 258 |
+
with torch.no_grad():
|
| 259 |
+
sd[k].copy_(sd_hf[k])
|
| 260 |
+
|
| 261 |
+
return model
|
| 262 |
+
|
| 263 |
+
def configure_optimizers(self, weight_decay, learning_rate, betas, device_type):
|
| 264 |
+
# start with all of the candidate parameters
|
| 265 |
+
param_dict = {pn: p for pn, p in self.named_parameters()}
|
| 266 |
+
# filter out those that do not require grad
|
| 267 |
+
param_dict = {pn: p for pn, p in param_dict.items() if p.requires_grad}
|
| 268 |
+
# create optim groups. Any parameters that is 2D will be weight decayed, otherwise no.
|
| 269 |
+
# i.e. all weight tensors in matmuls + embeddings decay, all biases and layernorms don't.
|
| 270 |
+
decay_params = [p for n, p in param_dict.items() if p.dim() >= 2]
|
| 271 |
+
nodecay_params = [p for n, p in param_dict.items() if p.dim() < 2]
|
| 272 |
+
optim_groups = [
|
| 273 |
+
{'params': decay_params, 'weight_decay': weight_decay},
|
| 274 |
+
{'params': nodecay_params, 'weight_decay': 0.0}
|
| 275 |
+
]
|
| 276 |
+
num_decay_params = sum(p.numel() for p in decay_params)
|
| 277 |
+
num_nodecay_params = sum(p.numel() for p in nodecay_params)
|
| 278 |
+
print(f"num decayed parameter tensors: {len(decay_params)}, with {num_decay_params:,} parameters")
|
| 279 |
+
print(f"num non-decayed parameter tensors: {len(nodecay_params)}, with {num_nodecay_params:,} parameters")
|
| 280 |
+
# Create AdamW optimizer and use the fused version if it is available
|
| 281 |
+
fused_available = 'fused' in inspect.signature(torch.optim.AdamW).parameters
|
| 282 |
+
use_fused = fused_available and device_type == 'cuda'
|
| 283 |
+
extra_args = dict(fused=True) if use_fused else dict()
|
| 284 |
+
optimizer = torch.optim.AdamW(optim_groups, lr=learning_rate, betas=betas, **extra_args)
|
| 285 |
+
print(f"using fused AdamW: {use_fused}")
|
| 286 |
+
|
| 287 |
+
return optimizer
|
| 288 |
+
|
| 289 |
+
def estimate_mfu(self, fwdbwd_per_iter, dt):
|
| 290 |
+
""" estimate model flops utilization (MFU) in units of A100 bfloat16 peak FLOPS """
|
| 291 |
+
# first estimate the number of flops we do per iteration.
|
| 292 |
+
# see PaLM paper Appendix B as ref: https://arxiv.org/abs/2204.02311
|
| 293 |
+
N = self.get_num_params()
|
| 294 |
+
cfg = self.config
|
| 295 |
+
L, H, Q, T = cfg.n_layer, cfg.n_head, cfg.n_embd//cfg.n_head, cfg.block_size
|
| 296 |
+
flops_per_token = 6*N + 12*L*H*Q*T
|
| 297 |
+
flops_per_fwdbwd = flops_per_token * T
|
| 298 |
+
flops_per_iter = flops_per_fwdbwd * fwdbwd_per_iter
|
| 299 |
+
# express our flops throughput as ratio of A100 bfloat16 peak flops
|
| 300 |
+
flops_achieved = flops_per_iter * (1.0/dt) # per second
|
| 301 |
+
flops_promised = 312e12 # A100 GPU bfloat16 peak flops is 312 TFLOPS
|
| 302 |
+
mfu = flops_achieved / flops_promised
|
| 303 |
+
return mfu
|
| 304 |
+
|
| 305 |
+
@torch.no_grad()
|
| 306 |
+
def generate(self, idx, max_new_tokens, temperature=1.0, top_k=None):
|
| 307 |
+
"""
|
| 308 |
+
Take a conditioning sequence of indices idx (LongTensor of shape (b,t)) and complete
|
| 309 |
+
the sequence max_new_tokens times, feeding the predictions back into the model each time.
|
| 310 |
+
Most likely you'll want to make sure to be in model.eval() mode of operation for this.
|
| 311 |
+
"""
|
| 312 |
+
for _ in range(max_new_tokens):
|
| 313 |
+
# if the sequence context is growing too long we must crop it at block_size
|
| 314 |
+
idx_cond = idx if idx.size(1) <= self.config.block_size else idx[:, -self.config.block_size:]
|
| 315 |
+
# forward the model to get the logits for the index in the sequence
|
| 316 |
+
logits, _ = self(idx_cond)
|
| 317 |
+
# pluck the logits at the final step and scale by desired temperature
|
| 318 |
+
logits = logits[:, -1, :] / temperature
|
| 319 |
+
# optionally crop the logits to only the top k options
|
| 320 |
+
if top_k is not None:
|
| 321 |
+
v, _ = torch.topk(logits, min(top_k, logits.size(-1)))
|
| 322 |
+
logits[logits < v[:, [-1]]] = -float('Inf')
|
| 323 |
+
# apply softmax to convert logits to (normalized) probabilities
|
| 324 |
+
probs = F.softmax(logits, dim=-1)
|
| 325 |
+
# sample from the distribution
|
| 326 |
+
idx_next = torch.multinomial(probs, num_samples=1)
|
| 327 |
+
# append sampled index to the running sequence and continue
|
| 328 |
+
idx = torch.cat((idx, idx_next), dim=1)
|
| 329 |
+
|
| 330 |
+
return idx
|
scripts/sample_mac.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""GPT-2-PL probe on Apple Silicon (MPS). Usage: python sample_mac.py "Prompt" [n_tok]"""
|
| 2 |
+
import os, sys, time, torch, torch.nn.functional as F
|
| 3 |
+
from model import GPTConfig, GPT
|
| 4 |
+
from tokenizers import Tokenizer
|
| 5 |
+
|
| 6 |
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 7 |
+
ROOT = os.path.dirname(HERE)
|
| 8 |
+
TEMP, TOP_K, TOP_P, REP_PEN, NGRAM = 0.7, 40, 0.92, 1.15, 3
|
| 9 |
+
EOT = 0
|
| 10 |
+
dev = "mps" if torch.backends.mps.is_available() else "cpu"
|
| 11 |
+
|
| 12 |
+
prompt = sys.argv[1] if len(sys.argv) > 1 else "Polska jest"
|
| 13 |
+
maxtok = int(sys.argv[2]) if len(sys.argv) > 2 else 90
|
| 14 |
+
|
| 15 |
+
ck = torch.load(os.path.join(ROOT, "model", "ckpt.pt"), map_location="cpu")
|
| 16 |
+
m = GPT(GPTConfig(**ck["model_args"]))
|
| 17 |
+
sd = ck["model"]
|
| 18 |
+
for k in list(sd):
|
| 19 |
+
if k.startswith("_orig_mod."):
|
| 20 |
+
sd[k[len("_orig_mod."):]] = sd.pop(k)
|
| 21 |
+
m.load_state_dict(sd); m.eval().to(dev)
|
| 22 |
+
block = ck["model_args"]["block_size"]
|
| 23 |
+
tok = Tokenizer.from_file(os.path.join(ROOT, "tokenizers", "polish_bpe_32k.json"))
|
| 24 |
+
|
| 25 |
+
def banned(seq, n):
|
| 26 |
+
if len(seq) < n - 1: return set()
|
| 27 |
+
pre = tuple(seq[-(n-1):]); bad = set()
|
| 28 |
+
for i in range(len(seq)-n+1):
|
| 29 |
+
if tuple(seq[i:i+n-1]) == pre: bad.add(seq[i+n-1])
|
| 30 |
+
return bad
|
| 31 |
+
|
| 32 |
+
@torch.no_grad()
|
| 33 |
+
def gen(prompt, maxtok):
|
| 34 |
+
idx = torch.tensor(tok.encode(prompt).ids, dtype=torch.long, device=dev)[None]
|
| 35 |
+
t0 = time.time(); n = 0
|
| 36 |
+
for _ in range(maxtok):
|
| 37 |
+
logits, _ = m(idx[:, -block:]); logits = logits[:, -1, :].float()
|
| 38 |
+
for t in set(idx[0].tolist()):
|
| 39 |
+
logits[0, t] /= REP_PEN if logits[0, t] > 0 else 1/REP_PEN
|
| 40 |
+
for t in banned(idx[0].tolist(), NGRAM):
|
| 41 |
+
logits[0, t] = -float("inf")
|
| 42 |
+
logits /= TEMP
|
| 43 |
+
kth = torch.topk(logits, TOP_K)[0][..., -1, None]; logits[logits < kth] = -float("inf")
|
| 44 |
+
sl, si = torch.sort(logits, descending=True)
|
| 45 |
+
cum = torch.cumsum(F.softmax(sl, dim=-1), dim=-1)
|
| 46 |
+
rm = cum > TOP_P; rm[..., 1:] = rm[..., :-1].clone(); rm[..., 0] = False
|
| 47 |
+
logits[0, si[0][rm[0]]] = -float("inf")
|
| 48 |
+
nxt = torch.multinomial(F.softmax(logits, dim=-1), 1); n += 1
|
| 49 |
+
if nxt.item() == EOT: break
|
| 50 |
+
idx = torch.cat([idx, nxt], dim=1)
|
| 51 |
+
dt = time.time() - t0
|
| 52 |
+
return tok.decode(idx[0].tolist()), n/dt
|
| 53 |
+
|
| 54 |
+
txt, tps = gen(prompt, maxtok)
|
| 55 |
+
print(f"[device={dev} {tps:.1f} tok/s]\n")
|
| 56 |
+
print(txt)
|
scripts/syntaxbench_mac.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Mini SYNTAX bench (grammatical minimal pairs, BLiMP-style) for GPT-2-PL.
|
| 2 |
+
For each pair, model should give higher total log-prob to the grammatical sentence.
|
| 3 |
+
Run: python syntaxbench_mac.py [-v]"""
|
| 4 |
+
import os, sys, torch, torch.nn.functional as F
|
| 5 |
+
from model import GPTConfig, GPT
|
| 6 |
+
from tokenizers import Tokenizer
|
| 7 |
+
|
| 8 |
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 9 |
+
ROOT = os.path.dirname(HERE)
|
| 10 |
+
VERBOSE = "-v" in sys.argv
|
| 11 |
+
dev = "mps" if torch.backends.mps.is_available() else "cpu"
|
| 12 |
+
EOT = 0
|
| 13 |
+
|
| 14 |
+
# (grammatical, ungrammatical) — differ by one agreement/case/conjugation error
|
| 15 |
+
PAIRS = [
|
| 16 |
+
("Czerwony samochód stoi przed domem.", "Czerwona samochód stoi przed domem."),
|
| 17 |
+
("Mały pies głośno szczeka.", "Mała pies głośno szczeka."),
|
| 18 |
+
("Idę do szkoły.", "Idę do szkoła."),
|
| 19 |
+
("Mieszkam w Warszawie.", "Mieszkam w Warszawa."),
|
| 20 |
+
("Dzieci bawią się w parku.", "Dzieci bawi się w parku."),
|
| 21 |
+
("Kot śpi na kanapie.", "Kot śpią na kanapie."),
|
| 22 |
+
("Ona poszła do domu.", "Ona poszedł do domu."),
|
| 23 |
+
("On czytał książkę.", "On czytała książkę."),
|
| 24 |
+
("Widzę dużego psa.", "Widzę dużego pies."),
|
| 25 |
+
("Lubię mocną kawę.", "Lubię mocną kawa."),
|
| 26 |
+
("Duże domy stoją w mieście.", "Duży domy stoją w mieście."),
|
| 27 |
+
("Piszę długopisem.", "Piszę długopis."),
|
| 28 |
+
("Nie mam czasu.", "Nie mam czas."),
|
| 29 |
+
("Nie lubię gorzkiej herbaty.", "Nie lubię gorzkiej herbata."),
|
| 30 |
+
("Trzy koty śpią na dworze.", "Trzy kot śpią na dworze."),
|
| 31 |
+
("Rozmawiam z bratem.", "Rozmawiam z brat."),
|
| 32 |
+
("To jest moja siostra.", "To jest mój siostra."),
|
| 33 |
+
("To jest mój brat.", "To jest moja brat."),
|
| 34 |
+
("Ja idę do domu.", "Ja idzie do domu."),
|
| 35 |
+
("Ty masz rację.", "Ty ma rację."),
|
| 36 |
+
("Wczoraj byłem w kinie.", "Wczoraj byłem w kino."),
|
| 37 |
+
("Ten wysoki mężczyzna śpiewa.", "Ten wysoka mężczyzna śpiewa."),
|
| 38 |
+
]
|
| 39 |
+
|
| 40 |
+
ck = torch.load(os.path.join(ROOT, "model", "ckpt.pt"), map_location="cpu")
|
| 41 |
+
m = GPT(GPTConfig(**ck["model_args"]))
|
| 42 |
+
sd = ck["model"]
|
| 43 |
+
for k in list(sd):
|
| 44 |
+
if k.startswith("_orig_mod."): sd[k[len("_orig_mod."):]] = sd.pop(k)
|
| 45 |
+
m.load_state_dict(sd); m.eval().to(dev)
|
| 46 |
+
tok = Tokenizer.from_file(os.path.join(ROOT, "tokenizers", "polish_bpe_32k.json"))
|
| 47 |
+
|
| 48 |
+
@torch.no_grad()
|
| 49 |
+
def logprob(sentence):
|
| 50 |
+
ids = [EOT] + tok.encode(sentence).ids
|
| 51 |
+
x = torch.tensor(ids, dtype=torch.long, device=dev)[None]
|
| 52 |
+
logits, _ = m(x, x)
|
| 53 |
+
lp = F.log_softmax(logits[0].float(), dim=-1)
|
| 54 |
+
return sum(lp[i, ids[i+1]].item() for i in range(len(ids)-1))
|
| 55 |
+
|
| 56 |
+
correct = 0; rows = []
|
| 57 |
+
for good, bad in PAIRS:
|
| 58 |
+
lg, lb = logprob(good), logprob(bad)
|
| 59 |
+
ok = lg > lb; correct += ok
|
| 60 |
+
rows.append((good, bad, ok))
|
| 61 |
+
|
| 62 |
+
n = len(PAIRS)
|
| 63 |
+
print(f"\nMini-bench SKŁADNI PL (pary minimalne, {n} par)")
|
| 64 |
+
print(f"checkpoint iter {ck.get('iter')} · device {dev}")
|
| 65 |
+
print("=" * 50)
|
| 66 |
+
print(f"ACCURACY: {correct}/{n} = {100*correct/n:.1f}% (losowy baseline 50.0%)")
|
| 67 |
+
if VERBOSE:
|
| 68 |
+
print("-" * 50)
|
| 69 |
+
for good, bad, ok in rows:
|
| 70 |
+
print(f"{'✓' if ok else '✗'} {good} vs {bad}")
|
scripts/upload_to_hf.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Upload this archive to Hugging Face Hub.
|
| 3 |
+
|
| 4 |
+
Requires an HF token with write access to the target namespace.
|
| 5 |
+
Default target: SlayerLab/slayer-gpt-tokenizer-model
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import argparse
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
from huggingface_hub import HfApi, create_repo
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def main() -> None:
|
| 17 |
+
parser = argparse.ArgumentParser()
|
| 18 |
+
parser.add_argument("--repo-id", default="SlayerLab/slayer-gpt-tokenizer-model")
|
| 19 |
+
parser.add_argument("--private", action="store_true")
|
| 20 |
+
parser.add_argument("--repo-type", default="model", choices=["model", "dataset", "space"])
|
| 21 |
+
args = parser.parse_args()
|
| 22 |
+
|
| 23 |
+
root = Path(__file__).resolve().parents[1]
|
| 24 |
+
create_repo(
|
| 25 |
+
args.repo_id,
|
| 26 |
+
repo_type=args.repo_type,
|
| 27 |
+
private=args.private,
|
| 28 |
+
exist_ok=True,
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
commit = HfApi().upload_folder(
|
| 32 |
+
repo_id=args.repo_id,
|
| 33 |
+
repo_type=args.repo_type,
|
| 34 |
+
folder_path=str(root),
|
| 35 |
+
commit_message="Upload Slayer GPT tokenizer model archive",
|
| 36 |
+
ignore_patterns=[
|
| 37 |
+
".git/*",
|
| 38 |
+
".venv/*",
|
| 39 |
+
"__pycache__/*",
|
| 40 |
+
"**/__pycache__/*",
|
| 41 |
+
".DS_Store",
|
| 42 |
+
],
|
| 43 |
+
)
|
| 44 |
+
print(commit)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
if __name__ == "__main__":
|
| 48 |
+
main()
|
| 49 |
+
|
tokenizers/polish_bpe_32k.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
tokenizers/rxlm_polish_bpe_65k.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
training/polish_nanogpt.patch
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
diff --git a/train_gpt.py b/train_gpt.py
|
| 2 |
+
index a9b3f62..9ae8e7a 100644
|
| 3 |
+
--- a/train_gpt.py
|
| 4 |
+
+++ b/train_gpt.py
|
| 5 |
+
@@ -1494,8 +1494,8 @@ def _load_data_shard(file: Path):
|
| 6 |
+
assert nbytes == 2 * num_tokens, "number of tokens read does not match header"
|
| 7 |
+
return tokens
|
| 8 |
+
|
| 9 |
+
-BOS_ID = 50256
|
| 10 |
+
-TRAIN_MAX_NUM_DOCS = {16384: 64, 32768: 96, 49152: 128}
|
| 11 |
+
+BOS_ID = 0 # Polish BPE <|endoftext|>
|
| 12 |
+
+TRAIN_MAX_NUM_DOCS = {16384: 384, 32768: 768, 49152: 1152} # bumped: dense short Polish docs
|
| 13 |
+
|
| 14 |
+
class Shard:
|
| 15 |
+
def __init__(self, tokens: Tensor, world_size: int = 1):
|
| 16 |
+
@@ -1602,7 +1602,7 @@ def distributed_data_generator(filename_pattern: str, num_tokens: int, max_seq_l
|
| 17 |
+
|
| 18 |
+
while True:
|
| 19 |
+
num_tokens_local = num_tokens // world_size
|
| 20 |
+
- max_num_docs = TRAIN_MAX_NUM_DOCS.get(num_tokens_local, next_multiple_of_n(num_tokens_local // 300, n=128))
|
| 21 |
+
+ max_num_docs = TRAIN_MAX_NUM_DOCS.get(num_tokens_local, next_multiple_of_n(num_tokens_local // 48, n=128))
|
| 22 |
+
|
| 23 |
+
if align_to_bos:
|
| 24 |
+
try:
|
| 25 |
+
@@ -1669,13 +1669,13 @@ def distributed_data_generator(filename_pattern: str, num_tokens: int, max_seq_l
|
| 26 |
+
class Hyperparameters:
|
| 27 |
+
# data
|
| 28 |
+
data_path = os.environ.get("DATA_PATH", ".")
|
| 29 |
+
- train_files: str = os.path.join(data_path, "data/fineweb10B/fineweb_train_*.bin") # input .bin to train on
|
| 30 |
+
- val_files: str = os.path.join(data_path, "data/fineweb10B/fineweb_val_*.bin") # input .bin to eval validation loss on
|
| 31 |
+
+ train_files: str = os.path.expanduser("~/dynaword/shards/polish_train_*.bin") # input .bin to train on
|
| 32 |
+
+ val_files: str = os.path.expanduser("~/dynaword/shards/polish_val_*.bin") # input .bin to eval validation loss on
|
| 33 |
+
val_tokens: int = 10485760 # how many tokens of validation data? it's important to keep this fixed for consistent comparisons
|
| 34 |
+
# batch sizes
|
| 35 |
+
val_batch_size: int = 4 * 64 * 1024 * 8
|
| 36 |
+
# schedule
|
| 37 |
+
- num_scheduled_iterations: int = 1375 # number of steps to complete lr and ws schedule
|
| 38 |
+
+ num_scheduled_iterations: int = 13200 # ~1 epoch of 3.47B Polish tokens # number of steps to complete lr and ws schedule
|
| 39 |
+
num_extension_iterations: int = 10 # number of steps to continue training at final lr and ws
|
| 40 |
+
# evaluation and logging
|
| 41 |
+
run_id: str = f"{uuid.uuid4()}"
|
| 42 |
+
@@ -1684,7 +1684,8 @@ class Hyperparameters:
|
| 43 |
+
# - (1 + m_r9) * x self-reference fuse on layer 9
|
| 44 |
+
# - backout_lambda fully removed (slot dropped from self.scalars; absorbed into MUDD bias init)
|
| 45 |
+
val_loss_every: int = 250 # every how many steps to evaluate val loss? 0 for only at the end
|
| 46 |
+
- save_checkpoint: bool = False
|
| 47 |
+
+ save_checkpoint: bool = True
|
| 48 |
+
+ checkpoint_every: int = 500 # save every N steps for crash-resume
|
| 49 |
+
run_evals: bool = False # run additional evaluations after training is completed
|
| 50 |
+
# bigram hash embedding
|
| 51 |
+
bigram_vocab_size: int = 50304 * 15
|
| 52 |
+
@@ -2014,7 +2015,7 @@ print0(nvidia_smi())
|
| 53 |
+
print0("="*100)
|
| 54 |
+
|
| 55 |
+
model: nn.Module = GPT(
|
| 56 |
+
- vocab_size=50257,
|
| 57 |
+
+ vocab_size=32896, # mult of 128, not power-of-2 (Karpathy); tokenizer stays 32768
|
| 58 |
+
num_layers=11,
|
| 59 |
+
num_heads=6,
|
| 60 |
+
head_dim=128,
|
| 61 |
+
@@ -2118,12 +2119,11 @@ for step in range(train_steps + 1):
|
| 62 |
+
torch.cuda.synchronize()
|
| 63 |
+
t0 = time.perf_counter()
|
| 64 |
+
|
| 65 |
+
+ if master_process and args.save_checkpoint and (last_step or (step > 0 and step % args.checkpoint_every == 0)):
|
| 66 |
+
+ log = dict(step=step, code=code, model=model.state_dict(), optimizer=training_manager.get_state())
|
| 67 |
+
+ os.makedirs(f"logs/{run_id}", exist_ok=True)
|
| 68 |
+
+ torch.save(log, f"logs/{run_id}/state_step{step:06d}.pt")
|
| 69 |
+
if last_step:
|
| 70 |
+
- if master_process and args.save_checkpoint:
|
| 71 |
+
- log = dict(step=step, code=code, model=model.state_dict(), optimizer=training_manager.get_state())
|
| 72 |
+
- os.makedirs(f"logs/{run_id}", exist_ok=True)
|
| 73 |
+
- torch.save(log, f"logs/{run_id}/state_step{step:06d}.pt")
|
| 74 |
+
- # the last step only has the validation loop, so break to avoid training
|
| 75 |
+
break
|
| 76 |
+
|
| 77 |
+
# --------------- TRAINING SECTION -----------------
|
| 78 |
+
diff --git a/triton_kernels.py b/triton_kernels.py
|
| 79 |
+
index 4f377ce..6b40884 100644
|
| 80 |
+
--- a/triton_kernels.py
|
| 81 |
+
+++ b/triton_kernels.py
|
| 82 |
+
@@ -898,7 +898,7 @@ ce_fwd_bwd_kernel = torch.cuda._compile_kernel(
|
| 83 |
+
CE_KERNEL_DECLS + CE_KERNEL_SOURCE,
|
| 84 |
+
"ce_fwd_bwd_kernel",
|
| 85 |
+
compute_capability="90",
|
| 86 |
+
- cuda_include_dirs=["/usr/local/cuda/include/"],
|
| 87 |
+
+ cuda_include_dirs=['/home/ubuntu/modded-nanogpt/.venv/lib/python3.14/site-packages/triton/backends/nvidia/include', '/home/ubuntu/modded-nanogpt/.venv/lib/python3.14/site-packages/nvidia/cuda_runtime/include'],
|
| 88 |
+
nvcc_options=["-lineinfo", "--use_fast_math"],
|
| 89 |
+
)
|
| 90 |
+
ce_fwd_bwd_kernel.set_shared_memory_config(CE_KERNEL_VOCAB_SIZE * 2)
|
training/requirements.txt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
numpy
|
| 2 |
+
tqdm
|
| 3 |
+
torch==2.10
|
| 4 |
+
huggingface-hub
|
| 5 |
+
kernels
|
| 6 |
+
setuptools
|
| 7 |
+
typing-extensions==4.15.0
|
| 8 |
+
datasets
|
| 9 |
+
tiktoken
|
training/run_polish.sh
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# Single clean training launch with persistent compile cache.
|
| 3 |
+
export TORCHINDUCTOR_CACHE_DIR="$HOME/.cache/torchinductor_polish"
|
| 4 |
+
export TORCHINDUCTOR_FX_GRAPH_CACHE=1
|
| 5 |
+
export TORCHINDUCTOR_AUTOGRAD_CACHE=1
|
| 6 |
+
mkdir -p "$TORCHINDUCTOR_CACHE_DIR"
|
| 7 |
+
cd "$HOME/modded-nanogpt"
|
| 8 |
+
exec .venv/bin/torchrun --standalone --nproc_per_node=1 train_gpt.py
|
training/train_gpt.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
training/triton_kernels.py
ADDED
|
@@ -0,0 +1,1007 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import triton
|
| 3 |
+
import triton.language as tl
|
| 4 |
+
from triton.tools.tensor_descriptor import TensorDescriptor
|
| 5 |
+
|
| 6 |
+
# -----------------------------------------------------------------------------
|
| 7 |
+
# Triton kernel for symmetric matrix multiplication by @byronxu99
|
| 8 |
+
|
| 9 |
+
@triton.jit
|
| 10 |
+
def _pid_to_block(
|
| 11 |
+
pid,
|
| 12 |
+
M,
|
| 13 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 14 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 15 |
+
GROUP_SIZE_M: tl.constexpr,
|
| 16 |
+
):
|
| 17 |
+
# Split output matrix into blocks of size (BLOCK_SIZE_M, BLOCK_SIZE_N)
|
| 18 |
+
num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
|
| 19 |
+
num_pid_n = tl.cdiv(M, BLOCK_SIZE_N)
|
| 20 |
+
|
| 21 |
+
# Map PID to a single matrix in batch
|
| 22 |
+
batch_idx = pid // (num_pid_m * num_pid_n)
|
| 23 |
+
pid = pid % (num_pid_m * num_pid_n)
|
| 24 |
+
|
| 25 |
+
# Map PID to 2D grid of blocks
|
| 26 |
+
pid_m = pid // num_pid_n
|
| 27 |
+
pid_n = pid % num_pid_n
|
| 28 |
+
pid_m, pid_n = tl.swizzle2d(pid_m, pid_n, num_pid_m, num_pid_n, GROUP_SIZE_M)
|
| 29 |
+
|
| 30 |
+
m_idx = pid_m * BLOCK_SIZE_M
|
| 31 |
+
n_idx = pid_n * BLOCK_SIZE_N
|
| 32 |
+
return batch_idx, m_idx, n_idx
|
| 33 |
+
|
| 34 |
+
@triton.jit
|
| 35 |
+
def XXT_kernel(
|
| 36 |
+
A_ptr, C_ptr,
|
| 37 |
+
M, K,
|
| 38 |
+
a_stride_b, a_stride_r, a_stride_c,
|
| 39 |
+
c_stride_b, c_stride_r, c_stride_c,
|
| 40 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 41 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 42 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 43 |
+
GROUP_SIZE_M: tl.constexpr,
|
| 44 |
+
LOWER_UPPER: tl.constexpr,
|
| 45 |
+
):
|
| 46 |
+
pid = tl.program_id(axis=0)
|
| 47 |
+
batch_idx, m_idx, n_idx = _pid_to_block(
|
| 48 |
+
pid, M, BLOCK_SIZE_M, BLOCK_SIZE_N, GROUP_SIZE_M
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
# Skip blocks that don't need to be computed
|
| 52 |
+
skip_block_below_diag = (LOWER_UPPER == 0) and (n_idx + BLOCK_SIZE_N <= m_idx)
|
| 53 |
+
skip_block_above_diag = (LOWER_UPPER != 0) and (m_idx + BLOCK_SIZE_M <= n_idx)
|
| 54 |
+
if skip_block_below_diag or skip_block_above_diag:
|
| 55 |
+
return
|
| 56 |
+
|
| 57 |
+
# Index into one matrix of batch
|
| 58 |
+
A_ptr += batch_idx * a_stride_b
|
| 59 |
+
C_ptr += batch_idx * c_stride_b
|
| 60 |
+
|
| 61 |
+
# Create pointer arrays for A and A.T
|
| 62 |
+
offs_m = (m_idx + tl.arange(0, BLOCK_SIZE_M)) % M
|
| 63 |
+
offs_n = (n_idx + tl.arange(0, BLOCK_SIZE_N)) % M
|
| 64 |
+
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 65 |
+
|
| 66 |
+
# Load A blocks for C[m,n] = A[m,:] @ A[n,:].T
|
| 67 |
+
# Load A[m, k] -> shape (BM, BK)
|
| 68 |
+
a_ptrs = A_ptr + (offs_m[:, None] * a_stride_r + offs_k[None, :] * a_stride_c)
|
| 69 |
+
# Load A[n, k] -> shape (BN, BK). Transpose to get (BK, BN) for accumulation.
|
| 70 |
+
# Loading (BN, BK) is coalesced because stride_c is 1 (contiguous dim is k).
|
| 71 |
+
at_ptrs = A_ptr + (offs_n[:, None] * a_stride_r + offs_k[None, :] * a_stride_c)
|
| 72 |
+
|
| 73 |
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 74 |
+
|
| 75 |
+
# Accumulate over blocks of K
|
| 76 |
+
for k in tl.range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
| 77 |
+
k_remaining = K - k * BLOCK_SIZE_K
|
| 78 |
+
a = tl.load(a_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0)
|
| 79 |
+
at_temp = tl.load(at_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0)
|
| 80 |
+
at = tl.trans(at_temp)
|
| 81 |
+
accumulator = tl.dot(a, at, accumulator)
|
| 82 |
+
a_ptrs += BLOCK_SIZE_K * a_stride_c
|
| 83 |
+
at_ptrs += BLOCK_SIZE_K * a_stride_c
|
| 84 |
+
|
| 85 |
+
out_dtype = C_ptr.dtype.element_ty
|
| 86 |
+
output = accumulator.to(out_dtype)
|
| 87 |
+
|
| 88 |
+
# Store block of C
|
| 89 |
+
offs_cm = m_idx + tl.arange(0, BLOCK_SIZE_M)
|
| 90 |
+
offs_cn = n_idx + tl.arange(0, BLOCK_SIZE_N)
|
| 91 |
+
c_ptrs = C_ptr + (offs_cm[:, None] * c_stride_r + offs_cn[None, :] * c_stride_c)
|
| 92 |
+
c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < M)
|
| 93 |
+
tl.store(c_ptrs, output, mask=c_mask)
|
| 94 |
+
|
| 95 |
+
# Store block of C mirrored across the diagonal
|
| 96 |
+
c_ptrs_t = C_ptr + (offs_cn[:, None] * c_stride_r + offs_cm[None, :] * c_stride_c)
|
| 97 |
+
c_mask_t = (offs_cn[:, None] < M) & (offs_cm[None, :] < M)
|
| 98 |
+
tl.store(c_ptrs_t, output.T, mask=c_mask_t)
|
| 99 |
+
|
| 100 |
+
def XXT(A: torch.Tensor, out: torch.Tensor):
|
| 101 |
+
"""
|
| 102 |
+
Launch Triton kernel to compute C = A @ A.T
|
| 103 |
+
"""
|
| 104 |
+
assert A.ndim == 2 or A.ndim == 3
|
| 105 |
+
M, K = A.shape[-2:]
|
| 106 |
+
assert out.size(-2) == M, "Output matrix has incorrect shape"
|
| 107 |
+
assert out.size(-1) == M, "Output matrix has incorrect shape"
|
| 108 |
+
|
| 109 |
+
batch_size = A.size(0) if A.ndim == 3 else 1
|
| 110 |
+
input_batch_stride = A.stride(0) if A.ndim == 3 else 0
|
| 111 |
+
output_batch_stride = out.stride(0) if out.ndim == 3 else 0
|
| 112 |
+
|
| 113 |
+
# Hardcoded configs based on H100 autotuning
|
| 114 |
+
if K == 768:
|
| 115 |
+
BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K = 128, 128, 64
|
| 116 |
+
num_stages, num_warps = 4, 8
|
| 117 |
+
else:
|
| 118 |
+
BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K = 64, 128, 128
|
| 119 |
+
num_stages, num_warps = 4, 8
|
| 120 |
+
|
| 121 |
+
grid = (batch_size * triton.cdiv(M, BLOCK_SIZE_M) * triton.cdiv(M, BLOCK_SIZE_N),)
|
| 122 |
+
XXT_kernel[grid](
|
| 123 |
+
A_ptr=A,
|
| 124 |
+
C_ptr=out,
|
| 125 |
+
M=M,
|
| 126 |
+
K=K,
|
| 127 |
+
a_stride_b=input_batch_stride,
|
| 128 |
+
a_stride_r=A.stride(-2),
|
| 129 |
+
a_stride_c=A.stride(-1),
|
| 130 |
+
c_stride_b=output_batch_stride,
|
| 131 |
+
c_stride_r=out.stride(-2),
|
| 132 |
+
c_stride_c=out.stride(-1),
|
| 133 |
+
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 134 |
+
BLOCK_SIZE_N=BLOCK_SIZE_N,
|
| 135 |
+
BLOCK_SIZE_K=BLOCK_SIZE_K,
|
| 136 |
+
GROUP_SIZE_M=8,
|
| 137 |
+
LOWER_UPPER=1,
|
| 138 |
+
num_stages=num_stages,
|
| 139 |
+
num_warps=num_warps,
|
| 140 |
+
)
|
| 141 |
+
return out
|
| 142 |
+
|
| 143 |
+
# -----------------------------------------------------------------------------
|
| 144 |
+
# Triton kernel for X.T @ X (tall matrices)
|
| 145 |
+
# Computes C = A.T @ A where A is (M, K) and output C is (K, K)
|
| 146 |
+
|
| 147 |
+
@triton.jit
|
| 148 |
+
def XTX_kernel(
|
| 149 |
+
A_ptr, C_ptr,
|
| 150 |
+
M, K,
|
| 151 |
+
a_stride_b, a_stride_r, a_stride_c,
|
| 152 |
+
c_stride_b, c_stride_r, c_stride_c,
|
| 153 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 154 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 155 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 156 |
+
GROUP_SIZE_M: tl.constexpr,
|
| 157 |
+
LOWER_UPPER: tl.constexpr,
|
| 158 |
+
):
|
| 159 |
+
"""
|
| 160 |
+
Compute C = A.T @ A where A is (M, K) and C is (K, K).
|
| 161 |
+
This is the transpose variant of XXT for tall matrices.
|
| 162 |
+
|
| 163 |
+
The output matrix C is symmetric, so we compute upper triangle and mirror.
|
| 164 |
+
We iterate over blocks of M (the reduction dimension after transpose).
|
| 165 |
+
"""
|
| 166 |
+
pid = tl.program_id(axis=0)
|
| 167 |
+
# Note: Output is (K, K), so we use K for the output grid
|
| 168 |
+
batch_idx, k_idx, n_idx = _pid_to_block(
|
| 169 |
+
pid, K, BLOCK_SIZE_M, BLOCK_SIZE_N, GROUP_SIZE_M
|
| 170 |
+
)
|
| 171 |
+
|
| 172 |
+
# Skip blocks that don't need to be computed (symmetry optimization)
|
| 173 |
+
skip_block_below_diag = (LOWER_UPPER == 0) and (n_idx + BLOCK_SIZE_N <= k_idx)
|
| 174 |
+
skip_block_above_diag = (LOWER_UPPER != 0) and (k_idx + BLOCK_SIZE_M <= n_idx)
|
| 175 |
+
if skip_block_below_diag or skip_block_above_diag:
|
| 176 |
+
return
|
| 177 |
+
|
| 178 |
+
# Index into one matrix of batch
|
| 179 |
+
A_ptr += batch_idx * a_stride_b
|
| 180 |
+
C_ptr += batch_idx * c_stride_b
|
| 181 |
+
|
| 182 |
+
# For A.T @ A:
|
| 183 |
+
# - A.T has shape (K, M), so A.T[k, m] = A[m, k]
|
| 184 |
+
# - We load blocks from columns k_idx and n_idx of A (which are rows of A.T)
|
| 185 |
+
# - We reduce over M (the shared dimension)
|
| 186 |
+
offs_k = (k_idx + tl.arange(0, BLOCK_SIZE_M)) % K # Output row indices (columns of A)
|
| 187 |
+
offs_n = (n_idx + tl.arange(0, BLOCK_SIZE_N)) % K # Output col indices (columns of A)
|
| 188 |
+
offs_m = tl.arange(0, BLOCK_SIZE_K) # Reduction dimension (rows of A)
|
| 189 |
+
|
| 190 |
+
# Pointers for loading A[:, k_idx:k_idx+BLOCK] (transposed view is A.T[k_idx:, :])
|
| 191 |
+
# at_ptrs loads A.T block: A.T[offs_k, offs_m] = A[offs_m, offs_k]
|
| 192 |
+
at_ptrs = A_ptr + (offs_m[:, None] * a_stride_r + offs_k[None, :] * a_stride_c)
|
| 193 |
+
# a_ptrs loads A block for the other factor: A.T[offs_m, offs_n].T = A[offs_m, offs_n]
|
| 194 |
+
a_ptrs = A_ptr + (offs_m[:, None] * a_stride_r + offs_n[None, :] * a_stride_c)
|
| 195 |
+
|
| 196 |
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 197 |
+
|
| 198 |
+
# Accumulate over blocks of M (the reduction dimension)
|
| 199 |
+
for m in tl.range(0, tl.cdiv(M, BLOCK_SIZE_K)):
|
| 200 |
+
m_remaining = M - m * BLOCK_SIZE_K
|
| 201 |
+
# Load A.T[offs_k, offs_m] = A[offs_m, offs_k] -> shape (BLOCK_K, BLOCK_M)
|
| 202 |
+
at = tl.load(at_ptrs, mask=offs_m[:, None] < m_remaining, other=0.0)
|
| 203 |
+
# Load A[offs_m, offs_n] -> shape (BLOCK_K, BLOCK_N)
|
| 204 |
+
a = tl.load(a_ptrs, mask=offs_m[:, None] < m_remaining, other=0.0)
|
| 205 |
+
# C[k, n] = sum_m A.T[k, m] * A[m, n] = sum_m A[m, k] * A[m, n]
|
| 206 |
+
# at.T @ a: (BLOCK_M, BLOCK_K) @ (BLOCK_K, BLOCK_N) = (BLOCK_M, BLOCK_N)
|
| 207 |
+
accumulator = tl.dot(at.T, a, accumulator)
|
| 208 |
+
at_ptrs += BLOCK_SIZE_K * a_stride_r
|
| 209 |
+
a_ptrs += BLOCK_SIZE_K * a_stride_r
|
| 210 |
+
|
| 211 |
+
out_dtype = C_ptr.dtype.element_ty
|
| 212 |
+
output = accumulator.to(out_dtype)
|
| 213 |
+
|
| 214 |
+
# Store block of C
|
| 215 |
+
offs_ck = k_idx + tl.arange(0, BLOCK_SIZE_M)
|
| 216 |
+
offs_cn = n_idx + tl.arange(0, BLOCK_SIZE_N)
|
| 217 |
+
c_ptrs = C_ptr + (offs_ck[:, None] * c_stride_r + offs_cn[None, :] * c_stride_c)
|
| 218 |
+
c_mask = (offs_ck[:, None] < K) & (offs_cn[None, :] < K)
|
| 219 |
+
tl.store(c_ptrs, output, mask=c_mask)
|
| 220 |
+
|
| 221 |
+
# Store block of C mirrored across the diagonal (symmetry)
|
| 222 |
+
c_ptrs_t = C_ptr + (offs_cn[:, None] * c_stride_r + offs_ck[None, :] * c_stride_c)
|
| 223 |
+
c_mask_t = (offs_cn[:, None] < K) & (offs_ck[None, :] < K)
|
| 224 |
+
tl.store(c_ptrs_t, output.T, mask=c_mask_t)
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
def XTX(A: torch.Tensor, out: torch.Tensor):
|
| 228 |
+
"""
|
| 229 |
+
Launch Triton kernel to compute C = A.T @ A
|
| 230 |
+
|
| 231 |
+
For tall matrices (M > K), this is more efficient than transposing
|
| 232 |
+
and using XXT because the intermediate products are smaller (K x K vs M x M).
|
| 233 |
+
|
| 234 |
+
Args:
|
| 235 |
+
A: Input tensor of shape (M, K) or (batch, M, K)
|
| 236 |
+
out: Output tensor of shape (K, K) or (batch, K, K)
|
| 237 |
+
|
| 238 |
+
Returns:
|
| 239 |
+
out: The same output tensor, filled with A.T @ A
|
| 240 |
+
"""
|
| 241 |
+
assert A.ndim == 2 or A.ndim == 3
|
| 242 |
+
M, K = A.shape[-2:]
|
| 243 |
+
assert out.size(-2) == K, f"Output matrix has incorrect shape: expected ({K}, {K}), got {tuple(out.shape[-2:])}"
|
| 244 |
+
assert out.size(-1) == K, f"Output matrix has incorrect shape: expected ({K}, {K}), got {tuple(out.shape[-2:])}"
|
| 245 |
+
|
| 246 |
+
batch_size = A.size(0) if A.ndim == 3 else 1
|
| 247 |
+
input_batch_stride = A.stride(0) if A.ndim == 3 else 0
|
| 248 |
+
output_batch_stride = out.stride(0) if out.ndim == 3 else 0
|
| 249 |
+
|
| 250 |
+
# Hardcoded configs based on H100 autotuning
|
| 251 |
+
if K == 768:
|
| 252 |
+
BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K = 128, 128, 64
|
| 253 |
+
num_stages, num_warps = 4, 8
|
| 254 |
+
else:
|
| 255 |
+
BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K = 64, 128, 128
|
| 256 |
+
num_stages, num_warps = 4, 8
|
| 257 |
+
|
| 258 |
+
grid = (batch_size * triton.cdiv(K, BLOCK_SIZE_M) * triton.cdiv(K, BLOCK_SIZE_N),)
|
| 259 |
+
XTX_kernel[grid](
|
| 260 |
+
A_ptr=A,
|
| 261 |
+
C_ptr=out,
|
| 262 |
+
M=M,
|
| 263 |
+
K=K,
|
| 264 |
+
a_stride_b=input_batch_stride,
|
| 265 |
+
a_stride_r=A.stride(-2),
|
| 266 |
+
a_stride_c=A.stride(-1),
|
| 267 |
+
c_stride_b=output_batch_stride,
|
| 268 |
+
c_stride_r=out.stride(-2),
|
| 269 |
+
c_stride_c=out.stride(-1),
|
| 270 |
+
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 271 |
+
BLOCK_SIZE_N=BLOCK_SIZE_N,
|
| 272 |
+
BLOCK_SIZE_K=BLOCK_SIZE_K,
|
| 273 |
+
GROUP_SIZE_M=8,
|
| 274 |
+
LOWER_UPPER=1,
|
| 275 |
+
num_stages=num_stages,
|
| 276 |
+
num_warps=num_warps,
|
| 277 |
+
)
|
| 278 |
+
return out
|
| 279 |
+
|
| 280 |
+
|
| 281 |
+
@triton.jit
|
| 282 |
+
def ba_plus_cAA_kernel(
|
| 283 |
+
A_ptr, C_ptr,
|
| 284 |
+
M,
|
| 285 |
+
a_stride_b, a_stride_r, a_stride_c,
|
| 286 |
+
c_stride_b, c_stride_r, c_stride_c,
|
| 287 |
+
alpha, beta,
|
| 288 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 289 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 290 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 291 |
+
GROUP_SIZE_M: tl.constexpr,
|
| 292 |
+
LOWER_UPPER: tl.constexpr,
|
| 293 |
+
):
|
| 294 |
+
# This is mostly duplicated from XXT_kernel, but also loads and adds a block of A
|
| 295 |
+
# Performance is slightly slower than XXT_kernel, so we use two separate kernels
|
| 296 |
+
pid = tl.program_id(axis=0)
|
| 297 |
+
batch_idx, m_idx, n_idx = _pid_to_block(
|
| 298 |
+
pid, M, BLOCK_SIZE_M, BLOCK_SIZE_N, GROUP_SIZE_M
|
| 299 |
+
)
|
| 300 |
+
|
| 301 |
+
# Skip blocks that don't need to be computed
|
| 302 |
+
skip_block_below_diag = (LOWER_UPPER == 0) and (n_idx + BLOCK_SIZE_N <= m_idx)
|
| 303 |
+
skip_block_above_diag = (LOWER_UPPER != 0) and (m_idx + BLOCK_SIZE_M <= n_idx)
|
| 304 |
+
if skip_block_below_diag or skip_block_above_diag:
|
| 305 |
+
return
|
| 306 |
+
|
| 307 |
+
# Index into one matrix of batch
|
| 308 |
+
A_ptr += batch_idx * a_stride_b
|
| 309 |
+
C_ptr += batch_idx * c_stride_b
|
| 310 |
+
|
| 311 |
+
# Create pointer arrays for A and A.T
|
| 312 |
+
offs_m = (m_idx + tl.arange(0, BLOCK_SIZE_M)) % M
|
| 313 |
+
offs_n = (n_idx + tl.arange(0, BLOCK_SIZE_N)) % M
|
| 314 |
+
offs_k = tl.arange(0, BLOCK_SIZE_K)
|
| 315 |
+
|
| 316 |
+
# Coalesced loads similar to XXT_kernel
|
| 317 |
+
a_ptrs = A_ptr + (offs_m[:, None] * a_stride_r + offs_k[None, :] * a_stride_c)
|
| 318 |
+
at_ptrs = A_ptr + (offs_n[:, None] * a_stride_r + offs_k[None, :] * a_stride_c)
|
| 319 |
+
|
| 320 |
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 321 |
+
|
| 322 |
+
# Accumulate over blocks of K
|
| 323 |
+
for k in tl.range(0, tl.cdiv(M, BLOCK_SIZE_K)):
|
| 324 |
+
k_remaining = M - k * BLOCK_SIZE_K
|
| 325 |
+
a = tl.load(a_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0)
|
| 326 |
+
at_temp = tl.load(at_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0)
|
| 327 |
+
at = tl.trans(at_temp)
|
| 328 |
+
accumulator = tl.dot(a, at, accumulator)
|
| 329 |
+
a_ptrs += BLOCK_SIZE_K * a_stride_c
|
| 330 |
+
at_ptrs += BLOCK_SIZE_K * a_stride_c
|
| 331 |
+
|
| 332 |
+
# Load block of A to add (corresponds to the current block of C)
|
| 333 |
+
offs_am = m_idx + tl.arange(0, BLOCK_SIZE_M)
|
| 334 |
+
offs_an = n_idx + tl.arange(0, BLOCK_SIZE_N)
|
| 335 |
+
a_add_ptrs = A_ptr + (offs_am[:, None] * a_stride_r + offs_an[None, :] * a_stride_c)
|
| 336 |
+
a_add_mask = (offs_am[:, None] < M) & (offs_an[None, :] < M)
|
| 337 |
+
a_add = tl.load(a_add_ptrs, mask=a_add_mask, other=0.0).to(tl.float32)
|
| 338 |
+
|
| 339 |
+
# Apply alpha and beta
|
| 340 |
+
accumulator *= alpha
|
| 341 |
+
accumulator += a_add * beta
|
| 342 |
+
|
| 343 |
+
out_dtype = C_ptr.dtype.element_ty
|
| 344 |
+
output = accumulator.to(out_dtype)
|
| 345 |
+
|
| 346 |
+
# Store block of C
|
| 347 |
+
offs_cm = m_idx + tl.arange(0, BLOCK_SIZE_M)
|
| 348 |
+
offs_cn = n_idx + tl.arange(0, BLOCK_SIZE_N)
|
| 349 |
+
c_ptrs = C_ptr + (offs_cm[:, None] * c_stride_r + offs_cn[None, :] * c_stride_c)
|
| 350 |
+
c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < M)
|
| 351 |
+
tl.store(c_ptrs, output, mask=c_mask)
|
| 352 |
+
|
| 353 |
+
# Store block of C mirrored across the diagonal
|
| 354 |
+
c_ptrs_t = C_ptr + (offs_cn[:, None] * c_stride_r + offs_cm[None, :] * c_stride_c)
|
| 355 |
+
c_mask_t = (offs_cn[:, None] < M) & (offs_cm[None, :] < M)
|
| 356 |
+
tl.store(c_ptrs_t, output.T, mask=c_mask_t)
|
| 357 |
+
|
| 358 |
+
def ba_plus_cAA(A: torch.Tensor, alpha: float, beta: float, out: torch.Tensor):
|
| 359 |
+
"""
|
| 360 |
+
Launch Triton kernel to compute C = alpha * A @ A.T + beta * A
|
| 361 |
+
"""
|
| 362 |
+
assert A.ndim == 2 or A.ndim == 3
|
| 363 |
+
M, K = A.shape[-2:]
|
| 364 |
+
assert M == K, "Input matrix must be square"
|
| 365 |
+
assert out.size(-2) == M
|
| 366 |
+
assert out.size(-1) == M
|
| 367 |
+
|
| 368 |
+
batch_size = A.size(0) if A.ndim == 3 else 1
|
| 369 |
+
input_batch_stride = A.stride(0) if A.ndim == 3 else 0
|
| 370 |
+
output_batch_stride = out.stride(0) if out.ndim == 3 else 0
|
| 371 |
+
|
| 372 |
+
# Hardcoded config based on H100 autotuning (M=768)
|
| 373 |
+
BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K = 128, 128, 64
|
| 374 |
+
num_stages, num_warps = 4, 8
|
| 375 |
+
|
| 376 |
+
grid = (batch_size * triton.cdiv(M, BLOCK_SIZE_M) * triton.cdiv(M, BLOCK_SIZE_N),)
|
| 377 |
+
ba_plus_cAA_kernel[grid](
|
| 378 |
+
A_ptr=A,
|
| 379 |
+
C_ptr=out,
|
| 380 |
+
M=M,
|
| 381 |
+
a_stride_b=input_batch_stride,
|
| 382 |
+
a_stride_r=A.stride(-2),
|
| 383 |
+
a_stride_c=A.stride(-1),
|
| 384 |
+
c_stride_b=output_batch_stride,
|
| 385 |
+
c_stride_r=out.stride(-2),
|
| 386 |
+
c_stride_c=out.stride(-1),
|
| 387 |
+
alpha=alpha,
|
| 388 |
+
beta=beta,
|
| 389 |
+
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 390 |
+
BLOCK_SIZE_N=BLOCK_SIZE_N,
|
| 391 |
+
BLOCK_SIZE_K=BLOCK_SIZE_K,
|
| 392 |
+
GROUP_SIZE_M=8,
|
| 393 |
+
LOWER_UPPER=1,
|
| 394 |
+
num_stages=num_stages,
|
| 395 |
+
num_warps=num_warps,
|
| 396 |
+
)
|
| 397 |
+
return out
|
| 398 |
+
|
| 399 |
+
# -----------------------------------------------------------------------------
|
| 400 |
+
# Triton kernel for MLP: relu(x @ W1.T)^2, by @andrewbriand, @jrauvola
|
| 401 |
+
|
| 402 |
+
@triton.jit
|
| 403 |
+
def linear_relu_square_kernel(a_desc, b_desc, c_desc, aux_desc,
|
| 404 |
+
M, N, K,
|
| 405 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 406 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 407 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 408 |
+
GROUP_SIZE_M: tl.constexpr,
|
| 409 |
+
NUM_SMS: tl.constexpr,
|
| 410 |
+
FORWARD: tl.constexpr,
|
| 411 |
+
):
|
| 412 |
+
dtype = tl.bfloat16
|
| 413 |
+
start_pid = tl.program_id(axis=0)
|
| 414 |
+
num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
|
| 415 |
+
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
|
| 416 |
+
k_tiles = tl.cdiv(K, BLOCK_SIZE_K)
|
| 417 |
+
num_tiles = num_pid_m * num_pid_n
|
| 418 |
+
|
| 419 |
+
tile_id_c = start_pid - NUM_SMS
|
| 420 |
+
num_pid_in_group = GROUP_SIZE_M * num_pid_n
|
| 421 |
+
|
| 422 |
+
for tile_id in tl.range(start_pid, num_tiles, NUM_SMS, flatten=True):
|
| 423 |
+
pid_m = tile_id // num_pid_n
|
| 424 |
+
pid_n = tile_id % num_pid_n
|
| 425 |
+
offs_am = pid_m * BLOCK_SIZE_M
|
| 426 |
+
offs_bn = pid_n * BLOCK_SIZE_N
|
| 427 |
+
|
| 428 |
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 429 |
+
for ki in range(k_tiles):
|
| 430 |
+
offs_k = ki * BLOCK_SIZE_K
|
| 431 |
+
a = a_desc.load([offs_am, offs_k])
|
| 432 |
+
b = b_desc.load([offs_bn, offs_k])
|
| 433 |
+
accumulator = tl.dot(a, b.T, accumulator)
|
| 434 |
+
|
| 435 |
+
tile_id_c += NUM_SMS
|
| 436 |
+
pid_m = tile_id // num_pid_n
|
| 437 |
+
pid_n = tile_id % num_pid_n
|
| 438 |
+
offs_am_c = pid_m * BLOCK_SIZE_M
|
| 439 |
+
offs_bn_c = pid_n * BLOCK_SIZE_N
|
| 440 |
+
|
| 441 |
+
acc = tl.reshape(accumulator, (BLOCK_SIZE_M, 2, BLOCK_SIZE_N // 2))
|
| 442 |
+
acc = tl.permute(acc, (0, 2, 1))
|
| 443 |
+
acc0, acc1 = tl.split(acc)
|
| 444 |
+
|
| 445 |
+
c0 = acc0.to(dtype)
|
| 446 |
+
if not FORWARD:
|
| 447 |
+
c0_pre = aux_desc.load([offs_am_c, offs_bn_c])
|
| 448 |
+
c0 = 2 * c0 * tl.where(c0_pre > 0, c0_pre, 0)
|
| 449 |
+
|
| 450 |
+
c_desc.store([offs_am_c, offs_bn_c], c0)
|
| 451 |
+
|
| 452 |
+
if FORWARD:
|
| 453 |
+
c0_post = tl.maximum(c0, 0)
|
| 454 |
+
c0_post = c0_post * c0_post
|
| 455 |
+
aux_desc.store([offs_am_c, offs_bn_c], c0_post)
|
| 456 |
+
|
| 457 |
+
c1 = acc1.to(dtype)
|
| 458 |
+
if not FORWARD:
|
| 459 |
+
c1_pre = aux_desc.load([offs_am_c, offs_bn_c + BLOCK_SIZE_N // 2])
|
| 460 |
+
c1 = 2 * c1 * tl.where(c1_pre > 0, c1_pre, 0)
|
| 461 |
+
|
| 462 |
+
c_desc.store([offs_am_c, offs_bn_c + BLOCK_SIZE_N // 2], c1)
|
| 463 |
+
|
| 464 |
+
if FORWARD:
|
| 465 |
+
c1_post = tl.maximum(c1, 0)
|
| 466 |
+
c1_post = c1_post * c1_post
|
| 467 |
+
aux_desc.store([offs_am_c, offs_bn_c + BLOCK_SIZE_N // 2], c1_post)
|
| 468 |
+
|
| 469 |
+
|
| 470 |
+
def linear_relu_square(a, b, aux=None):
|
| 471 |
+
M, K = a.shape
|
| 472 |
+
N, K = b.shape
|
| 473 |
+
dtype = a.dtype
|
| 474 |
+
|
| 475 |
+
c = torch.empty((M, N), device=a.device, dtype=dtype)
|
| 476 |
+
|
| 477 |
+
FORWARD = False
|
| 478 |
+
if aux is None:
|
| 479 |
+
FORWARD = True
|
| 480 |
+
aux = torch.empty((M, N), device=a.device, dtype=dtype)
|
| 481 |
+
|
| 482 |
+
NUM_SMS = torch.cuda.get_device_properties("cuda").multi_processor_count
|
| 483 |
+
|
| 484 |
+
BLOCK_SIZE_M = 128
|
| 485 |
+
BLOCK_SIZE_N = 256
|
| 486 |
+
BLOCK_SIZE_K = 64
|
| 487 |
+
num_stages = 4 if FORWARD else 3
|
| 488 |
+
num_warps = 8
|
| 489 |
+
|
| 490 |
+
a_desc = TensorDescriptor.from_tensor(a, [BLOCK_SIZE_M, BLOCK_SIZE_K])
|
| 491 |
+
b_desc = TensorDescriptor.from_tensor(b, [BLOCK_SIZE_N, BLOCK_SIZE_K])
|
| 492 |
+
c_desc = TensorDescriptor.from_tensor(c, [BLOCK_SIZE_M, BLOCK_SIZE_N // 2])
|
| 493 |
+
aux_desc = TensorDescriptor.from_tensor(aux, [BLOCK_SIZE_M, BLOCK_SIZE_N // 2])
|
| 494 |
+
|
| 495 |
+
def grid(META):
|
| 496 |
+
return (min(
|
| 497 |
+
NUM_SMS,
|
| 498 |
+
triton.cdiv(M, BLOCK_SIZE_M) * triton.cdiv(N, BLOCK_SIZE_N),
|
| 499 |
+
), )
|
| 500 |
+
|
| 501 |
+
linear_relu_square_kernel[grid](
|
| 502 |
+
a_desc, b_desc, c_desc, aux_desc,
|
| 503 |
+
M, N, K,
|
| 504 |
+
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 505 |
+
BLOCK_SIZE_N=BLOCK_SIZE_N,
|
| 506 |
+
BLOCK_SIZE_K=BLOCK_SIZE_K,
|
| 507 |
+
GROUP_SIZE_M=1,
|
| 508 |
+
NUM_SMS=NUM_SMS,
|
| 509 |
+
FORWARD=FORWARD,
|
| 510 |
+
num_stages=num_stages,
|
| 511 |
+
num_warps=num_warps
|
| 512 |
+
)
|
| 513 |
+
|
| 514 |
+
if FORWARD:
|
| 515 |
+
return c, aux
|
| 516 |
+
else:
|
| 517 |
+
return c
|
| 518 |
+
|
| 519 |
+
class FusedLinearReLUSquareFunction(torch.autograd.Function):
|
| 520 |
+
@staticmethod
|
| 521 |
+
def forward(ctx, x, W1, W2):
|
| 522 |
+
pre, post = linear_relu_square(x.view((-1, x.shape[-1])), W1)
|
| 523 |
+
x3 = post @ W2
|
| 524 |
+
ctx.save_for_backward(x, W1, W2, pre, post)
|
| 525 |
+
return x3.view(x.shape)
|
| 526 |
+
|
| 527 |
+
@staticmethod
|
| 528 |
+
def backward(ctx, grad_output):
|
| 529 |
+
x, W1, W2, pre, post = ctx.saved_tensors
|
| 530 |
+
dW2 = post.T @ grad_output
|
| 531 |
+
dpre = linear_relu_square(grad_output.view((-1, grad_output.shape[-1])), W2, aux=pre)
|
| 532 |
+
dW1 = dpre.T @ x
|
| 533 |
+
dx = dpre @ W1
|
| 534 |
+
return dx.view(x.shape), dW1, dW2
|
| 535 |
+
|
| 536 |
+
|
| 537 |
+
# -----------------------------------------------------------------------------
|
| 538 |
+
# Tiled transpose copy kernel: dst (N, M) = src (M, N).T
|
| 539 |
+
# Uses coalesced reads from src and coalesced writes to dst via tl.trans().
|
| 540 |
+
# Replaces PyTorch's elementwise copy_ which uses a naive 75k-block kernel
|
| 541 |
+
# with non-coalesced writes, saturating all SMs and blocking NCCL.
|
| 542 |
+
|
| 543 |
+
@triton.jit
|
| 544 |
+
def _transpose_copy_kernel(
|
| 545 |
+
src_ptr, dst_ptr,
|
| 546 |
+
M, N,
|
| 547 |
+
src_stride_m, src_stride_n,
|
| 548 |
+
dst_stride_0, dst_stride_1,
|
| 549 |
+
BLOCK_M: tl.constexpr,
|
| 550 |
+
BLOCK_N: tl.constexpr,
|
| 551 |
+
):
|
| 552 |
+
pid_m = tl.program_id(0)
|
| 553 |
+
pid_n = tl.program_id(1)
|
| 554 |
+
|
| 555 |
+
offs_m = (pid_m * BLOCK_M + tl.arange(0, BLOCK_M)).to(tl.int64)
|
| 556 |
+
offs_n = (pid_n * BLOCK_N + tl.arange(0, BLOCK_N)).to(tl.int64)
|
| 557 |
+
|
| 558 |
+
mask = (offs_m[:, None] < M) & (offs_n[None, :] < N)
|
| 559 |
+
|
| 560 |
+
# Coalesced read from src (M, N)
|
| 561 |
+
tile = tl.load(
|
| 562 |
+
src_ptr + offs_m[:, None] * src_stride_m + offs_n[None, :] * src_stride_n,
|
| 563 |
+
mask=mask, other=0.0,
|
| 564 |
+
)
|
| 565 |
+
|
| 566 |
+
# Coalesced write to dst (N, M): dst[n, m] = src[m, n]
|
| 567 |
+
mask_T = (offs_n[:, None] < N) & (offs_m[None, :] < M)
|
| 568 |
+
tl.store(
|
| 569 |
+
dst_ptr + offs_n[:, None] * dst_stride_0 + offs_m[None, :] * dst_stride_1,
|
| 570 |
+
tl.trans(tile), mask=mask_T,
|
| 571 |
+
)
|
| 572 |
+
|
| 573 |
+
|
| 574 |
+
def transpose_copy(src: torch.Tensor, dst: torch.Tensor):
|
| 575 |
+
"""Tiled transpose copy: dst = src.T where src is (M, N) and dst is (N, M).
|
| 576 |
+
|
| 577 |
+
Uses a 64x128 tiled Triton kernel with coalesced reads AND writes,
|
| 578 |
+
achieving near memory-bandwidth-limited performance.
|
| 579 |
+
"""
|
| 580 |
+
assert src.ndim == 2 and dst.ndim == 2
|
| 581 |
+
M, N = src.shape
|
| 582 |
+
assert dst.shape == (N, M), f"Expected dst shape ({N}, {M}), got {dst.shape}"
|
| 583 |
+
|
| 584 |
+
BLOCK_M, BLOCK_N = 64, 128
|
| 585 |
+
grid = (triton.cdiv(M, BLOCK_M), triton.cdiv(N, BLOCK_N))
|
| 586 |
+
|
| 587 |
+
_transpose_copy_kernel[grid](
|
| 588 |
+
src, dst,
|
| 589 |
+
M, N,
|
| 590 |
+
src.stride(0), src.stride(1),
|
| 591 |
+
dst.stride(0), dst.stride(1),
|
| 592 |
+
BLOCK_M=BLOCK_M,
|
| 593 |
+
BLOCK_N=BLOCK_N,
|
| 594 |
+
num_warps=8,
|
| 595 |
+
num_stages=2,
|
| 596 |
+
)
|
| 597 |
+
|
| 598 |
+
|
| 599 |
+
# -----------------------------------------------------------------------------
|
| 600 |
+
# Tiled transpose-add kernel: dst (M, N) += src (N, M).T
|
| 601 |
+
# Same tiling strategy as transpose_copy but with a fused read-add-write.
|
| 602 |
+
# Replaces PyTorch's .add_(src.T) which uses the same 75k-block elementwise
|
| 603 |
+
# kernel with non-coalesced reads from the transposed operand.
|
| 604 |
+
|
| 605 |
+
@triton.jit
|
| 606 |
+
def _transpose_add_kernel(
|
| 607 |
+
src_ptr, dst_ptr,
|
| 608 |
+
M, N,
|
| 609 |
+
src_stride_m, src_stride_n,
|
| 610 |
+
dst_stride_0, dst_stride_1,
|
| 611 |
+
BLOCK_M: tl.constexpr,
|
| 612 |
+
BLOCK_N: tl.constexpr,
|
| 613 |
+
):
|
| 614 |
+
pid_m = tl.program_id(0)
|
| 615 |
+
pid_n = tl.program_id(1)
|
| 616 |
+
|
| 617 |
+
offs_m = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
|
| 618 |
+
offs_n = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
|
| 619 |
+
|
| 620 |
+
mask = (offs_m[:, None] < M) & (offs_n[None, :] < N)
|
| 621 |
+
|
| 622 |
+
# Coalesced read from src (M, N)
|
| 623 |
+
src_tile = tl.load(
|
| 624 |
+
src_ptr + offs_m[:, None] * src_stride_m + offs_n[None, :] * src_stride_n,
|
| 625 |
+
mask=mask, other=0.0,
|
| 626 |
+
)
|
| 627 |
+
|
| 628 |
+
# Coalesced read-add-write on dst (N, M): dst[n, m] += src[m, n]
|
| 629 |
+
mask_T = (offs_n[:, None] < N) & (offs_m[None, :] < M)
|
| 630 |
+
dst_ptrs = dst_ptr + offs_n[:, None] * dst_stride_0 + offs_m[None, :] * dst_stride_1
|
| 631 |
+
dst_tile = tl.load(dst_ptrs, mask=mask_T, other=0.0)
|
| 632 |
+
tl.store(dst_ptrs, dst_tile + tl.trans(src_tile), mask=mask_T)
|
| 633 |
+
|
| 634 |
+
|
| 635 |
+
def transpose_add(src: torch.Tensor, dst: torch.Tensor):
|
| 636 |
+
"""Tiled transpose-add: dst += src.T where src is (M, N) and dst is (N, M).
|
| 637 |
+
|
| 638 |
+
Uses a 32x32 tiled Triton kernel with coalesced access on both src and dst,
|
| 639 |
+
replacing PyTorch's .add_(src.T) which has non-coalesced reads from the
|
| 640 |
+
transposed operand.
|
| 641 |
+
"""
|
| 642 |
+
assert src.ndim == 2 and dst.ndim == 2
|
| 643 |
+
M, N = src.shape
|
| 644 |
+
assert dst.shape == (N, M), f"Expected dst shape ({N}, {M}), got {dst.shape}"
|
| 645 |
+
|
| 646 |
+
BLOCK_M, BLOCK_N = 32, 32
|
| 647 |
+
grid = (triton.cdiv(M, BLOCK_M), triton.cdiv(N, BLOCK_N))
|
| 648 |
+
|
| 649 |
+
_transpose_add_kernel[grid](
|
| 650 |
+
src, dst,
|
| 651 |
+
M, N,
|
| 652 |
+
src.stride(0), src.stride(1),
|
| 653 |
+
dst.stride(0), dst.stride(1),
|
| 654 |
+
BLOCK_M=BLOCK_M,
|
| 655 |
+
BLOCK_N=BLOCK_N,
|
| 656 |
+
num_warps=4,
|
| 657 |
+
num_stages=2,
|
| 658 |
+
)
|
| 659 |
+
|
| 660 |
+
|
| 661 |
+
CE_KERNEL_BLOCK_SIZE = 256
|
| 662 |
+
CE_KERNEL_VOCAB_SIZE = 50304
|
| 663 |
+
|
| 664 |
+
CE_KERNEL_DECLS = f"""
|
| 665 |
+
constexpr int VOCAB_SIZE = {CE_KERNEL_VOCAB_SIZE};
|
| 666 |
+
constexpr int BLOCK_SIZE = {CE_KERNEL_BLOCK_SIZE};
|
| 667 |
+
"""
|
| 668 |
+
|
| 669 |
+
CE_KERNEL_SOURCE = """
|
| 670 |
+
#include <cuda_bf16.h>
|
| 671 |
+
#include <math_constants.h>
|
| 672 |
+
|
| 673 |
+
#define __nv_fp8_e5m2 char
|
| 674 |
+
#define uint16_t unsigned short
|
| 675 |
+
#define uint8_t unsigned char
|
| 676 |
+
#define int64_t long long
|
| 677 |
+
|
| 678 |
+
__device__ __forceinline__ __nv_fp8_e5m2 f32_to_fp8_e5m2(float x) {
|
| 679 |
+
uint16_t packed;
|
| 680 |
+
asm volatile(
|
| 681 |
+
"cvt.rn.satfinite.e5m2x2.f32 %0, %1, %2;"
|
| 682 |
+
: "=h"(packed)
|
| 683 |
+
: "f"(x), "f"(0.0f)
|
| 684 |
+
);
|
| 685 |
+
__nv_fp8_e5m2 result;
|
| 686 |
+
*reinterpret_cast<uint8_t*>(&result) = (packed & (0xFF << 8)) >> 8;
|
| 687 |
+
return result;
|
| 688 |
+
}
|
| 689 |
+
|
| 690 |
+
struct __align__(16) __nv_bfloat168 {
|
| 691 |
+
__nv_bfloat16 data[8];
|
| 692 |
+
__device__ __nv_bfloat16& operator[](int i) { return data[i]; }
|
| 693 |
+
__device__ const __nv_bfloat16& operator[](int i) const { return data[i]; }
|
| 694 |
+
};
|
| 695 |
+
|
| 696 |
+
struct __align__(8) __nv_fp8_e5m28 {
|
| 697 |
+
__nv_fp8_e5m2 data[8];
|
| 698 |
+
__device__ __nv_fp8_e5m2& operator[](int i) { return data[i]; }
|
| 699 |
+
__device__ const __nv_fp8_e5m2& operator[](int i) const { return data[i]; }
|
| 700 |
+
};
|
| 701 |
+
|
| 702 |
+
template<typename T> __device__ constexpr T CEIL_DIV(T a, T b) { return (a + b - 1) / b; }
|
| 703 |
+
|
| 704 |
+
//__device__ float sigmoid(float x) {
|
| 705 |
+
// return 1.0f / (1.0f + __expf(-x));
|
| 706 |
+
//}
|
| 707 |
+
__device__ float sigmoid(float x) {
|
| 708 |
+
return 0.5f + __tanhf(x * 0.5f) * 0.5f;
|
| 709 |
+
}
|
| 710 |
+
|
| 711 |
+
extern "C"
|
| 712 |
+
__launch_bounds__(BLOCK_SIZE, 2)
|
| 713 |
+
__global__ void ce_fwd_bwd_kernel(
|
| 714 |
+
const __nv_bfloat16* __restrict__ logits,
|
| 715 |
+
const int64_t* __restrict__ targets,
|
| 716 |
+
const float* __restrict__ mtp_weights,
|
| 717 |
+
float* __restrict__ losses,
|
| 718 |
+
__nv_fp8_e5m2* grad_input,
|
| 719 |
+
int batch_size,
|
| 720 |
+
int n_predict,
|
| 721 |
+
double A_param,
|
| 722 |
+
double B_param,
|
| 723 |
+
double C_param,
|
| 724 |
+
double grad_s_param,
|
| 725 |
+
double grad_scale_param)
|
| 726 |
+
{
|
| 727 |
+
constexpr int VEC_WIDTH = 8;
|
| 728 |
+
constexpr int NUM_FULL_LOADS = VOCAB_SIZE / (BLOCK_SIZE * VEC_WIDTH);
|
| 729 |
+
constexpr int NUM_LOADS = CEIL_DIV(VOCAB_SIZE, BLOCK_SIZE * VEC_WIDTH);
|
| 730 |
+
|
| 731 |
+
float A = (float)A_param;
|
| 732 |
+
float B = (float)B_param;
|
| 733 |
+
float C = (float)C_param;
|
| 734 |
+
float grad_s = (float)grad_s_param;
|
| 735 |
+
float grad_scale = (float)grad_scale_param;
|
| 736 |
+
|
| 737 |
+
extern __shared__ __nv_bfloat16 smem[];
|
| 738 |
+
|
| 739 |
+
static_assert(VEC_WIDTH == 8);
|
| 740 |
+
|
| 741 |
+
const __nv_bfloat16 *block_logit_ptr = logits + VOCAB_SIZE * blockIdx.x;
|
| 742 |
+
|
| 743 |
+
float inv_C = 1 / C;
|
| 744 |
+
float B_div_C = B * inv_C;
|
| 745 |
+
float thread_max = -CUDART_INF_F;
|
| 746 |
+
|
| 747 |
+
#pragma unroll 25
|
| 748 |
+
for (int i = 0; i < NUM_LOADS; i++) {
|
| 749 |
+
int idx = i * BLOCK_SIZE * VEC_WIDTH + threadIdx.x * VEC_WIDTH;
|
| 750 |
+
if (i < NUM_FULL_LOADS || idx < VOCAB_SIZE) {
|
| 751 |
+
__nv_bfloat168 result = *(__nv_bfloat168*)(&block_logit_ptr[idx]);
|
| 752 |
+
__nv_bfloat168 result_sigmoid;
|
| 753 |
+
#pragma unroll
|
| 754 |
+
for (int k = 0; k < VEC_WIDTH; k++) {
|
| 755 |
+
float tmp = __bfloat162float(result[k]);
|
| 756 |
+
tmp = sigmoid(tmp * inv_C + B_div_C);
|
| 757 |
+
result_sigmoid[k] = __float2bfloat16(tmp);
|
| 758 |
+
tmp = A * tmp;
|
| 759 |
+
thread_max = max(tmp, thread_max);
|
| 760 |
+
}
|
| 761 |
+
*(__nv_bfloat168*)(&smem[idx]) = result_sigmoid;
|
| 762 |
+
}
|
| 763 |
+
}
|
| 764 |
+
|
| 765 |
+
constexpr int NUM_WARPS = BLOCK_SIZE / 32;
|
| 766 |
+
int warp_id = threadIdx.x / 32;
|
| 767 |
+
__shared__ float block_maxs[NUM_WARPS];
|
| 768 |
+
__shared__ float block_sums[NUM_WARPS];
|
| 769 |
+
|
| 770 |
+
for (int offset = 16; offset > 0; offset >>= 1)
|
| 771 |
+
thread_max = fmaxf(thread_max, __shfl_down_sync(0xFFFFFFFF, thread_max, offset));
|
| 772 |
+
|
| 773 |
+
if (threadIdx.x % 32 == 0) {
|
| 774 |
+
block_maxs[warp_id] = thread_max;
|
| 775 |
+
}
|
| 776 |
+
|
| 777 |
+
__syncthreads();
|
| 778 |
+
|
| 779 |
+
float block_max = -CUDART_INF_F;
|
| 780 |
+
for (int i = 0; i < NUM_WARPS; i++) {
|
| 781 |
+
block_max = fmaxf(block_max, block_maxs[i]);
|
| 782 |
+
}
|
| 783 |
+
|
| 784 |
+
float thread_sum = 0.0f;
|
| 785 |
+
#pragma unroll 2
|
| 786 |
+
for (int i = 0; i < NUM_LOADS; i++) {
|
| 787 |
+
int idx = i * BLOCK_SIZE * VEC_WIDTH + threadIdx.x * VEC_WIDTH;
|
| 788 |
+
__nv_bfloat168 l;
|
| 789 |
+
if (i < NUM_FULL_LOADS || idx < VOCAB_SIZE) {
|
| 790 |
+
l = *(__nv_bfloat168*)(&smem[idx]);
|
| 791 |
+
}
|
| 792 |
+
#pragma unroll
|
| 793 |
+
for (int k = 0; k < VEC_WIDTH; k++) {
|
| 794 |
+
float tmp = A * __bfloat162float(l[k]);
|
| 795 |
+
tmp = __expf(tmp - block_max);
|
| 796 |
+
if (i < NUM_FULL_LOADS || idx < VOCAB_SIZE) {
|
| 797 |
+
thread_sum += tmp;
|
| 798 |
+
}
|
| 799 |
+
}
|
| 800 |
+
}
|
| 801 |
+
|
| 802 |
+
for (int offset = 16; offset > 0; offset >>= 1)
|
| 803 |
+
thread_sum += __shfl_down_sync(0xFFFFFFFF, thread_sum, offset);
|
| 804 |
+
|
| 805 |
+
if (threadIdx.x % 32 == 0) {
|
| 806 |
+
block_sums[warp_id] = thread_sum;
|
| 807 |
+
}
|
| 808 |
+
|
| 809 |
+
__syncthreads();
|
| 810 |
+
|
| 811 |
+
float block_sum = 0.0f;
|
| 812 |
+
for (int i = 0; i < NUM_WARPS; i++) {
|
| 813 |
+
block_sum += block_sums[i];
|
| 814 |
+
}
|
| 815 |
+
|
| 816 |
+
float lse = block_max + __logf(block_sum);
|
| 817 |
+
|
| 818 |
+
if (threadIdx.x == 0) {
|
| 819 |
+
float total_loss = 0.0f;
|
| 820 |
+
for (int k = 0; k < n_predict; k++) {
|
| 821 |
+
int64_t target_idx = blockIdx.x + k;
|
| 822 |
+
if (target_idx < batch_size) {
|
| 823 |
+
float weight = mtp_weights[k];
|
| 824 |
+
int64_t target = targets[target_idx];
|
| 825 |
+
if (target >= 0 && target < VOCAB_SIZE) {
|
| 826 |
+
float z_target = A * __bfloat162float(smem[target]);
|
| 827 |
+
total_loss += weight * (lse - z_target);
|
| 828 |
+
}
|
| 829 |
+
}
|
| 830 |
+
}
|
| 831 |
+
losses[blockIdx.x] = total_loss;
|
| 832 |
+
}
|
| 833 |
+
|
| 834 |
+
float S_w = 0.0f;
|
| 835 |
+
|
| 836 |
+
for (int i = 0; i < n_predict; i++) {
|
| 837 |
+
S_w += mtp_weights[i];
|
| 838 |
+
}
|
| 839 |
+
|
| 840 |
+
#pragma unroll 4
|
| 841 |
+
for (int i = 0; i < NUM_LOADS; i++) {
|
| 842 |
+
int idx = i * BLOCK_SIZE * VEC_WIDTH + threadIdx.x * VEC_WIDTH;
|
| 843 |
+
__nv_fp8_e5m28 result;
|
| 844 |
+
|
| 845 |
+
if (i < NUM_FULL_LOADS || idx < VOCAB_SIZE) {
|
| 846 |
+
__nv_bfloat168 sigmoid_us = *(__nv_bfloat168*)(&smem[idx]);
|
| 847 |
+
#pragma unroll
|
| 848 |
+
for (int j = 0; j < VEC_WIDTH; j++) {
|
| 849 |
+
float sigmoid_u = __bfloat162float(sigmoid_us[j]);
|
| 850 |
+
float z = A * sigmoid_u;
|
| 851 |
+
float p = __expf(z - lse);
|
| 852 |
+
|
| 853 |
+
float term1 = S_w * p;
|
| 854 |
+
float term2 = 0.0f;
|
| 855 |
+
|
| 856 |
+
float grad_z = term1 - term2;
|
| 857 |
+
float grad_x = grad_scale * (1.0f / C * A) * (1.0f / grad_s) * grad_z * sigmoid_u * (1.0f - sigmoid_u);
|
| 858 |
+
auto result_tmp = f32_to_fp8_e5m2(grad_x);
|
| 859 |
+
result[j] = *reinterpret_cast<__nv_fp8_e5m2*>(&result_tmp);
|
| 860 |
+
}
|
| 861 |
+
*(__nv_fp8_e5m28*)(&grad_input[blockIdx.x * VOCAB_SIZE + idx]) = result;
|
| 862 |
+
}
|
| 863 |
+
}
|
| 864 |
+
|
| 865 |
+
__syncthreads();
|
| 866 |
+
|
| 867 |
+
if (threadIdx.x < n_predict && blockIdx.x + threadIdx.x < batch_size) {
|
| 868 |
+
int i = threadIdx.x;
|
| 869 |
+
int64_t target = targets[blockIdx.x + i];
|
| 870 |
+
|
| 871 |
+
float sigmoid_u = __bfloat162float(smem[target]);
|
| 872 |
+
float z = A * sigmoid_u;
|
| 873 |
+
float p = __expf(z - lse);
|
| 874 |
+
|
| 875 |
+
float term1 = S_w * p;
|
| 876 |
+
float term2 = 0.0f;
|
| 877 |
+
|
| 878 |
+
#pragma unroll
|
| 879 |
+
for (int k = 0; k < 3; k++) {
|
| 880 |
+
int64_t target_idx = blockIdx.x + k;
|
| 881 |
+
if (target_idx < batch_size && k < n_predict) {
|
| 882 |
+
if (targets[target_idx] == target) {
|
| 883 |
+
term2 += mtp_weights[k];
|
| 884 |
+
}
|
| 885 |
+
}
|
| 886 |
+
}
|
| 887 |
+
|
| 888 |
+
float grad_z = term1 - term2;
|
| 889 |
+
float grad_x = grad_scale * (1.0f / C * A) * (1.0f / grad_s) * grad_z * sigmoid_u * (1.0f - sigmoid_u);
|
| 890 |
+
auto result_tmp = f32_to_fp8_e5m2(grad_x);
|
| 891 |
+
auto result = *reinterpret_cast<__nv_fp8_e5m2*>(&result_tmp);
|
| 892 |
+
grad_input[blockIdx.x * VOCAB_SIZE + target] = result;
|
| 893 |
+
}
|
| 894 |
+
}
|
| 895 |
+
"""
|
| 896 |
+
|
| 897 |
+
ce_fwd_bwd_kernel = torch.cuda._compile_kernel(
|
| 898 |
+
CE_KERNEL_DECLS + CE_KERNEL_SOURCE,
|
| 899 |
+
"ce_fwd_bwd_kernel",
|
| 900 |
+
compute_capability="90",
|
| 901 |
+
cuda_include_dirs=['/home/ubuntu/modded-nanogpt/.venv/lib/python3.14/site-packages/triton/backends/nvidia/include', '/home/ubuntu/modded-nanogpt/.venv/lib/python3.14/site-packages/nvidia/cuda_runtime/include'],
|
| 902 |
+
nvcc_options=["-lineinfo", "--use_fast_math"],
|
| 903 |
+
)
|
| 904 |
+
ce_fwd_bwd_kernel.set_shared_memory_config(CE_KERNEL_VOCAB_SIZE * 2)
|
| 905 |
+
|
| 906 |
+
@torch.library.custom_op("nanogpt::ce_fwd_bwd", mutates_args={"losses", "grad_input"})
|
| 907 |
+
def ce_fwd_bwd(
|
| 908 |
+
logits: torch.Tensor,
|
| 909 |
+
targets: torch.Tensor,
|
| 910 |
+
mtp_weights: torch.Tensor,
|
| 911 |
+
losses: torch.Tensor,
|
| 912 |
+
grad_input: torch.Tensor,
|
| 913 |
+
n_rows: int,
|
| 914 |
+
n_predict: int,
|
| 915 |
+
A: float,
|
| 916 |
+
B: float,
|
| 917 |
+
C: float,
|
| 918 |
+
grad_s: float,
|
| 919 |
+
grad_scale: float,
|
| 920 |
+
) -> None:
|
| 921 |
+
grid = (n_rows, 1, 1)
|
| 922 |
+
ce_fwd_bwd_kernel(
|
| 923 |
+
grid,
|
| 924 |
+
(CE_KERNEL_BLOCK_SIZE, 1, 1),
|
| 925 |
+
(logits, targets, mtp_weights, losses, grad_input,
|
| 926 |
+
n_rows, n_predict, A, B, C, grad_s, grad_scale),
|
| 927 |
+
shared_mem=CE_KERNEL_VOCAB_SIZE * 2,
|
| 928 |
+
)
|
| 929 |
+
|
| 930 |
+
class FusedSoftcappedCrossEntropy(torch.autograd.Function):
|
| 931 |
+
@staticmethod
|
| 932 |
+
def forward(ctx, x, targets, mtp_weights, lm_head_weight, x_s, w_s, grad_s, grad_scale, A=23.0, B=5.0, C=7.5):
|
| 933 |
+
|
| 934 |
+
x_f8 = x.div(x_s).to(torch.float8_e4m3fn)
|
| 935 |
+
w_f8 = lm_head_weight.div(w_s).to(torch.float8_e4m3fn)
|
| 936 |
+
|
| 937 |
+
w_f8_col_major = w_f8.T.contiguous().T
|
| 938 |
+
|
| 939 |
+
logits = torch._scaled_mm(
|
| 940 |
+
x_f8,
|
| 941 |
+
w_f8_col_major,
|
| 942 |
+
out_dtype=torch.bfloat16,
|
| 943 |
+
scale_a=x.new_tensor(x_s, dtype=torch.float32),
|
| 944 |
+
scale_b=x.new_tensor(w_s, dtype=torch.float32),
|
| 945 |
+
use_fast_accum=True,
|
| 946 |
+
)
|
| 947 |
+
|
| 948 |
+
n_rows, n_cols = logits.shape
|
| 949 |
+
if mtp_weights is None:
|
| 950 |
+
mtp_weights = torch.tensor([1.0], device=logits.device, dtype=torch.float32)
|
| 951 |
+
n_predict = mtp_weights.shape[0]
|
| 952 |
+
|
| 953 |
+
losses = torch.empty(n_rows, dtype=torch.float32, device=logits.device)
|
| 954 |
+
lse = torch.empty(n_rows, dtype=torch.float32, device=logits.device)
|
| 955 |
+
|
| 956 |
+
logits = logits.contiguous()
|
| 957 |
+
targets = targets.contiguous()
|
| 958 |
+
mtp_weights = mtp_weights.contiguous()
|
| 959 |
+
|
| 960 |
+
grad_input = torch.empty((n_rows, n_cols), dtype=torch.float8_e5m2, device=logits.device)
|
| 961 |
+
|
| 962 |
+
ce_fwd_bwd(logits, targets, mtp_weights, losses, grad_input,
|
| 963 |
+
n_rows, n_predict, A, B, C, grad_s, grad_scale)
|
| 964 |
+
|
| 965 |
+
ctx.save_for_backward(logits, targets, mtp_weights, lse, x, lm_head_weight, x_f8, w_f8, grad_input)
|
| 966 |
+
ctx.params = (A, B, C, x_s, w_s, grad_s)
|
| 967 |
+
return losses
|
| 968 |
+
|
| 969 |
+
@staticmethod
|
| 970 |
+
def backward(ctx, grad_output):
|
| 971 |
+
logits, targets, mtp_weights, lse, x, lm_head_weight, x_f8, w_f8, grad_input = ctx.saved_tensors
|
| 972 |
+
A, B, C, x_s, w_s, grad_s = ctx.params
|
| 973 |
+
n_rows, n_cols = logits.shape
|
| 974 |
+
n_predict = mtp_weights.shape[0]
|
| 975 |
+
|
| 976 |
+
grad_output = grad_output.contiguous()
|
| 977 |
+
|
| 978 |
+
x_scale = grad_input.new_tensor(x_s, dtype=torch.float32)
|
| 979 |
+
w_scale = grad_input.new_tensor(w_s, dtype=torch.float32)
|
| 980 |
+
grad_scale = grad_input.new_tensor(grad_s, dtype=torch.float32)
|
| 981 |
+
|
| 982 |
+
grad_x = torch._scaled_mm(
|
| 983 |
+
grad_input,
|
| 984 |
+
w_f8.T,
|
| 985 |
+
out_dtype=torch.bfloat16,
|
| 986 |
+
scale_a=grad_scale,
|
| 987 |
+
scale_b=w_scale,
|
| 988 |
+
use_fast_accum=False,
|
| 989 |
+
)
|
| 990 |
+
|
| 991 |
+
x_f8_T = torch.empty((x_f8.shape[1], x_f8.shape[0]), dtype=x_f8.dtype, device=x_f8.device)
|
| 992 |
+
transpose_copy(x_f8, x_f8_T) # (768, n_rows) row-major
|
| 993 |
+
|
| 994 |
+
grad_input_T = torch.empty((n_cols, n_rows), dtype=grad_input.dtype, device=grad_input.device)
|
| 995 |
+
transpose_copy(grad_input, grad_input_T) # (50304, n_rows) row-major
|
| 996 |
+
|
| 997 |
+
grad_w = torch._scaled_mm(
|
| 998 |
+
x_f8_T, # (768, n_rows) row-major
|
| 999 |
+
grad_input_T.T, # (n_rows, 50304) column-major view
|
| 1000 |
+
out_dtype=torch.float32,
|
| 1001 |
+
scale_a=x_scale,
|
| 1002 |
+
scale_b=grad_scale,
|
| 1003 |
+
use_fast_accum=False,
|
| 1004 |
+
)
|
| 1005 |
+
|
| 1006 |
+
return grad_x, None, None, grad_w, None, None, None
|
| 1007 |
+
|