kacperwikiel commited on
Commit
78c54ec
·
verified ·
1 Parent(s): c789827

Upload Slayer GPT tokenizer model archive

Browse files
.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
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
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
+