fromscratch-pyllm 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. fromscratch_pyllm-0.5.0.dist-info/METADATA +231 -0
  2. fromscratch_pyllm-0.5.0.dist-info/RECORD +51 -0
  3. fromscratch_pyllm-0.5.0.dist-info/WHEEL +4 -0
  4. fromscratch_pyllm-0.5.0.dist-info/entry_points.txt +2 -0
  5. fromscratch_pyllm-0.5.0.dist-info/licenses/LICENSE +21 -0
  6. pyllm/__init__.py +5 -0
  7. pyllm/_types.py +10 -0
  8. pyllm/_version.py +24 -0
  9. pyllm/api.py +72 -0
  10. pyllm/autograd/__init__.py +5 -0
  11. pyllm/autograd/gradcheck.py +34 -0
  12. pyllm/autograd/tensor.py +378 -0
  13. pyllm/checkpoint.py +125 -0
  14. pyllm/cli.py +202 -0
  15. pyllm/data/__init__.py +18 -0
  16. pyllm/data/pebble.npz +0 -0
  17. pyllm/data/pebble_corpus.txt +19652 -0
  18. pyllm/data/pokemon.npz +0 -0
  19. pyllm/data/pokemon_corpus.txt +149 -0
  20. pyllm/generate.py +213 -0
  21. pyllm/models/__init__.py +44 -0
  22. pyllm/models/base.py +27 -0
  23. pyllm/models/bigram.py +41 -0
  24. pyllm/models/gpt.py +73 -0
  25. pyllm/models/mlp.py +56 -0
  26. pyllm/nn/__init__.py +34 -0
  27. pyllm/nn/attention.py +79 -0
  28. pyllm/nn/dropout.py +30 -0
  29. pyllm/nn/embedding.py +30 -0
  30. pyllm/nn/functional.py +119 -0
  31. pyllm/nn/linear.py +37 -0
  32. pyllm/nn/module.py +84 -0
  33. pyllm/nn/normalization.py +31 -0
  34. pyllm/nn/transformer.py +65 -0
  35. pyllm/pebble/__init__.py +25 -0
  36. pyllm/pebble/ast.py +141 -0
  37. pyllm/pebble/corpus.py +70 -0
  38. pyllm/pebble/generator.py +145 -0
  39. pyllm/pebble/harvest.py +35 -0
  40. pyllm/pebble/referee.py +440 -0
  41. pyllm/pebble/render.py +109 -0
  42. pyllm/pebble/score.py +41 -0
  43. pyllm/py.typed +0 -0
  44. pyllm/tokenizer/__init__.py +7 -0
  45. pyllm/tokenizer/base.py +24 -0
  46. pyllm/tokenizer/bpe.py +117 -0
  47. pyllm/tokenizer/char.py +33 -0
  48. pyllm/training/__init__.py +7 -0
  49. pyllm/training/data.py +44 -0
  50. pyllm/training/loop.py +51 -0
  51. pyllm/training/optim.py +76 -0
@@ -0,0 +1,231 @@
1
+ Metadata-Version: 2.5
2
+ Name: fromscratch-pyllm
3
+ Version: 0.5.0
4
+ Summary: An educational large language model built from scratch in Python
5
+ Project-URL: Homepage, https://github.com/robcox2910/pyllm
6
+ Project-URL: Repository, https://github.com/robcox2910/pyllm
7
+ Project-URL: Issues, https://github.com/robcox2910/pyllm/issues
8
+ Project-URL: Documentation, https://robcox2910.github.io/pyllm/
9
+ Author: Rob Cox
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: autograd,education,gpt,learning,llm,transformer
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Education
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Topic :: Education
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.14
22
+ Requires-Dist: numpy>=2.1
23
+ Provides-Extra: dev
24
+ Requires-Dist: commitizen>=4.4.0; extra == 'dev'
25
+ Requires-Dist: hypothesis>=6.170; extra == 'dev'
26
+ Requires-Dist: pip-audit>=2.10.0; extra == 'dev'
27
+ Requires-Dist: pre-commit>=4.0.1; extra == 'dev'
28
+ Requires-Dist: pyright>=1.1.400; extra == 'dev'
29
+ Requires-Dist: pytest-cov>=6.0.0; extra == 'dev'
30
+ Requires-Dist: pytest>=8.3.4; extra == 'dev'
31
+ Requires-Dist: ruff>=0.9.3; extra == 'dev'
32
+ Provides-Extra: docs
33
+ Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
34
+ Provides-Extra: pebble
35
+ Requires-Dist: fromscratch-pebble>=0.3.0; extra == 'pebble'
36
+ Description-Content-Type: text/markdown
37
+
38
+ # PyLLM
39
+
40
+ An educational large language model built from scratch in Python.
41
+
42
+ Part of the "from scratch in Python" series. Built incrementally with TDD,
43
+ every concept explained with analogies a 12-year-old can follow. The only
44
+ dependency is numpy (for fast array math) — the autograd engine, neural-net
45
+ layers, attention, the Transformer, the tokenizer, the optimizer, the training
46
+ loop, and sampling are all ours.
47
+
48
+ ## Quick Start
49
+
50
+ ```bash
51
+ uv sync --all-extras
52
+ uv run pytest
53
+ ```
54
+
55
+ ## Dream up new Pokémon (generates instantly)
56
+
57
+ PyLLM ships with a tiny pre-trained GPT, so it invents Pokémon-ish names the
58
+ moment you launch it — no training wait:
59
+
60
+ ```console
61
+ $ uv run pyllm --seed 0 --max-new-tokens 40
62
+ machop
63
+ machoke
64
+ machamp
65
+ bellsprout
66
+ weepin
67
+ ```
68
+
69
+ Turn the boldness dial with `--temperature` (0 = safe/greedy, higher = wilder)
70
+ and add a `--top-k` guardrail. Start from a prompt with `--prompt "pi"`.
71
+
72
+ ## Train your own model
73
+
74
+ Pick a rung on the teaching ladder (`bigram`, `mlp`, or `gpt`) and point it at
75
+ any text file (defaults to the bundled Pokémon corpus):
76
+
77
+ ```console
78
+ $ uv run pyllm train --model gpt --steps 2000 --out mine.npz
79
+ step 0: loss 3.31
80
+ ...
81
+ final loss 0.42; saved checkpoint to mine.npz
82
+
83
+ $ uv run pyllm --checkpoint mine.npz --max-new-tokens 60
84
+ ```
85
+
86
+ ## The flagship: teach PyLLM to write Pebble
87
+
88
+ There's no corpus of [Pebble](https://github.com/robcox2910/pebble-lang) code in
89
+ the wild — so we *grow our own*. A generator emits **random-but-provably-valid**
90
+ Pebble ASTs (100% parse under Pebble's own parser), a harvester scrapes real
91
+ snippets from Pebble's docs, and — because we own the language toolchain — we
92
+ **grade** the trained model by running its output back through Pebble's real
93
+ parser and reporting what percentage parses:
94
+
95
+ ```console
96
+ $ uv run pyllm pebble --seed 7 --temperature 0.5 --score
97
+ let result = 62
98
+ }
99
+ if count < 47 {
100
+ let c = 51 + (83 + 32)
101
+ ...
102
+ parse rate: 0.0% of 1 programs
103
+ ```
104
+
105
+ The bundled checkpoint is *tiny* (a few-minute char-level GPT), so it writes
106
+ Pebble-*flavoured* code and only some of it parses — and that's the whole point:
107
+ the parser is an honest, objective judge, so you can watch the score go up as you
108
+ train longer or bigger. Almost no LLM project can grade its own output like this.
109
+
110
+ ### Call in the referee: always-valid Pebble
111
+
112
+ Add `--constrained` and a from-scratch grammar **referee** vetoes every character
113
+ that would break Pebble's rules, so the model rolls again until it picks a legal
114
+ one. Same tiny brain — but now every program it returns parses:
115
+
116
+ ```console
117
+ $ uv run pyllm pebble --seed 7 --constrained --score
118
+ let total = n * (63 - 99)
119
+
120
+ let result = c
121
+ fount = 55 + (n - b) % (6 - b)
122
+
123
+ parse rate: 100.0% of 2 programs
124
+ ```
125
+
126
+ From Python: `generate_pebble(constrained=True)`. See
127
+ [`docs/concepts/the-referee.md`](docs/concepts/the-referee.md).
128
+
129
+ Grow a fresh synthetic corpus with `uv run pyllm gen-corpus`. Live scoring needs
130
+ the optional `fromscratch-pebble` dependency (`uv sync --all-extras`); generation and
131
+ training work without it. See [`docs/concepts/grow-your-own-data.md`](docs/concepts/grow-your-own-data.md).
132
+
133
+ ## See how text becomes tokens
134
+
135
+ ```console
136
+ $ uv run pyllm tokenize --text "pika"
137
+ 'p' -> 16
138
+ 'i' -> 9
139
+ 'k' -> 11
140
+ 'a' -> 1
141
+ ```
142
+
143
+ ## The teaching ladder
144
+
145
+ Each rung is a real, trainable language model that fixes a weakness of the one
146
+ below it, so you feel *why* each idea exists:
147
+
148
+ 1. **Bigram** — "what character usually comes next?" A lookup table.
149
+ 2. **MLP** — a small neural net over the last few characters (embeddings + a
150
+ hidden layer).
151
+ 3. **GPT** — stacked Transformer blocks (multi-head attention + feed-forward +
152
+ LayerNorm + residuals) with positional information. The real thing.
153
+
154
+ ## Learn the ideas
155
+
156
+ Kid-friendly explanations live in [`docs/concepts/`](docs/concepts/README.md):
157
+ the breadcrumb trail (autograd), tokens, embeddings, attention, how a Transformer
158
+ is built, how a model learns, sampling, and — the flagship lesson — growing your
159
+ own data.
160
+
161
+ ## Developing alongside a sibling
162
+
163
+ PyLLM uses a released version of [Pebble](https://github.com/robcox2910/pebble-lang)
164
+ from PyPI (`fromscratch-pebble`, the optional `pebble` extra). To try an
165
+ unreleased Pebble change, install your local checkout over the released one:
166
+
167
+ ```bash
168
+ uv pip install -e ../pebble-lang
169
+ uv run --no-sync pytest
170
+ ```
171
+
172
+ Use `uv run --no-sync` while you do this: a plain `uv run` (like `uv sync`)
173
+ puts the pinned release back, which is how you undo the override.
174
+
175
+ ## Releasing
176
+
177
+ 1. Merge the change to `main`.
178
+ 2. Tag it: `git tag -a vX.Y.Z -m vX.Y.Z && git push origin vX.Y.Z`.
179
+ A change to the public API bumps the minor version (`v0.1.0` → `v0.2.0`);
180
+ anything else bumps the patch (`v0.1.0` → `v0.1.1`).
181
+ 3. Projects that use PyLLM (like [PyStack](https://github.com/robcox2910/pystack))
182
+ upgrade by editing the tag in their `[tool.uv.sources]` and running
183
+ `uv lock --upgrade-package pyllm`, in a pull request so CI checks the pair.
184
+
185
+ ## PyLLM inside PyStack
186
+
187
+ PyLLM is **the brain** of a family of educational projects, each building a real
188
+ system from scratch to teach how it works. They're tied together by
189
+ **[PyStack](https://github.com/robcox2910/pystack)**, where a Pebble program can
190
+ even `import "llm"` and ask PyLLM to write more code.
191
+
192
+ Once PyLLM is installed as a PyStack plugin, any Pebble program can call it:
193
+
194
+ ```pebble
195
+ import "llm"
196
+ let code = llm_generate("a function that adds two numbers")
197
+ print(code)
198
+ ```
199
+
200
+ <!-- series:start -->
201
+ ## Related Projects
202
+
203
+ This project is one of a series that builds real systems "from scratch in Python" to teach how they work. See the [series home](https://robcox2910.github.io/) for the full story.
204
+
205
+ | Project | What it teaches | Repository | Docs |
206
+ | --- | --- | --- | --- |
207
+ | PyOS | Operating systems | [py-os](https://github.com/robcox2910/py-os) | [Docs](https://robcox2910.github.io/py-os/) |
208
+ | Pebble | Compilers and programming languages | [pebble-lang](https://github.com/robcox2910/pebble-lang) | [Docs](https://robcox2910.github.io/pebble-lang/) |
209
+ | PyDB | Relational databases | [pydb](https://github.com/robcox2910/pydb) | [Docs](https://robcox2910.github.io/pydb/) |
210
+ | PyKV | Key-value stores | [pykv](https://github.com/robcox2910/pykv) | [Docs](https://robcox2910.github.io/pykv/) |
211
+ | PyDocDB | Document databases | [pydoc-db](https://github.com/robcox2910/pydoc-db) | [Docs](https://robcox2910.github.io/pydoc-db/) |
212
+ | PyGraphDB | Graph databases | [pygraphdb](https://github.com/robcox2910/pygraphdb) | [Docs](https://robcox2910.github.io/pygraphdb/) |
213
+ | PyTSDB | Time-series databases | [pytsdb](https://github.com/robcox2910/pytsdb) | [Docs](https://robcox2910.github.io/pytsdb/) |
214
+ | PyVecDB | Vector databases | [pyvecdb](https://github.com/robcox2910/pyvecdb) | [Docs](https://robcox2910.github.io/pyvecdb/) |
215
+ | PyColDB | Column-family databases | [pycoldb](https://github.com/robcox2910/pycoldb) | [Docs](https://robcox2910.github.io/pycoldb/) |
216
+ | PyCrypt | Cryptography | [pycrypt](https://github.com/robcox2910/pycrypt) | [Docs](https://robcox2910.github.io/pycrypt/) |
217
+ | PyNet | Computer networking | [pynet](https://github.com/robcox2910/pynet) | [Docs](https://robcox2910.github.io/pynet/) |
218
+ | PyWeb | HTTP web servers | [pyweb](https://github.com/robcox2910/pyweb) | [Docs](https://robcox2910.github.io/pyweb/) |
219
+ | PyGit | Version control | [pygit](https://github.com/robcox2910/pygit) | [Docs](https://robcox2910.github.io/pygit/) |
220
+ | PySearch | Search engines | [pysearch](https://github.com/robcox2910/pysearch) | [Docs](https://robcox2910.github.io/pysearch/) |
221
+ | PyMQ | Message queues | [pymq](https://github.com/robcox2910/pymq) | [Docs](https://robcox2910.github.io/pymq/) |
222
+ | **PyLLM** | **Language models (the brain)** | [pyllm](https://github.com/robcox2910/pyllm) | [Docs](https://robcox2910.github.io/pyllm/) |
223
+ | PyScheduler | Job scheduling | [pyscheduler](https://github.com/robcox2910/pyscheduler) | [Docs](https://robcox2910.github.io/pyscheduler/) |
224
+ | PyStack | Full-stack integration | [pystack](https://github.com/robcox2910/pystack) | [Docs](https://robcox2910.github.io/pystack/) |
225
+ <!-- series:end -->
226
+
227
+ <!-- docs:start -->
228
+ ## Documentation
229
+
230
+ Full docs (with kid-friendly explanations of every idea): https://robcox2910.github.io/pyllm/
231
+ <!-- docs:end -->
@@ -0,0 +1,51 @@
1
+ pyllm/__init__.py,sha256=Mu-ztRj5Wfvx4QT2tMtn17mQw9ph4hFTR_WWhv8jgIc,177
2
+ pyllm/_types.py,sha256=8puuPpD2lBzEyLdVZTOwVD82T8dT2Y7TzV_3_Fyj_LA,332
3
+ pyllm/_version.py,sha256=APp2gXXpoz-tyKGj5fWwVTf7yK8O9LHP-MP0aQ_uNxs,520
4
+ pyllm/api.py,sha256=w170O3RWKpzEOVWonNKnY35OdSLZ5JbL9EZ8mr7Afjs,2239
5
+ pyllm/checkpoint.py,sha256=hIG6wySmA-btpq7bNlF14RA1fShJ2Ikk_s2HAdYFT_Q,5545
6
+ pyllm/cli.py,sha256=EGS5qyYDxooRS1OdTh6KBVucCCpWTaTCCrnleTi8AYk,7692
7
+ pyllm/generate.py,sha256=oRcofKH4k__wKx2kK8JZY6HPbSJeWNFsyb0q4ptpOT8,8447
8
+ pyllm/autograd/__init__.py,sha256=OKp6PrqPn0UpsXYB2pHZrsmy0LbNy6yHzPGV5bqn5bo,145
9
+ pyllm/autograd/gradcheck.py,sha256=dJWpSv-O7pCLTsps8VgrZaQ8Y4eFtfnydMsnW52ttAE,1192
10
+ pyllm/autograd/tensor.py,sha256=5eUX-dSgdHfWUuiZDRga1o4yJP819ByrucK3UsR9Mjk,16096
11
+ pyllm/data/__init__.py,sha256=0hGQ10Ss-CUujtjhKwj5_xyY8Yr_UuyetgC0rOGEpa4,701
12
+ pyllm/models/__init__.py,sha256=6Bj3QjuBzAtQ6w-Pq3Glzh6-jcxLXgDLydSnOPnlZ-8,1672
13
+ pyllm/models/base.py,sha256=s8hijQgjn6OXBtqtavTpRbk5XvoCDkEaKe4YuWJVtVA,899
14
+ pyllm/models/bigram.py,sha256=zML42WIwx2z7LtZkkp72VdTmhGC5-67PV5WgjhQZ5yE,1647
15
+ pyllm/models/gpt.py,sha256=fSSjt_H1EN6LeMuWwUrGyAkA0kIoDjkfrNGZSmszFwQ,3062
16
+ pyllm/models/mlp.py,sha256=DTUAZdpbrrUyW-3O39uZN0YGyk-mUVvrS-te5XxSDCQ,2208
17
+ pyllm/nn/__init__.py,sha256=f4MjGdlHeZjMknePDUJgSlegaHcqly-IpcjkNhdeesU,1048
18
+ pyllm/nn/attention.py,sha256=OdToJPCW78D6qCzLbItyGYPipy5XaGaAt8QANRMK2xM,3301
19
+ pyllm/nn/dropout.py,sha256=TDoz-7mPPnODU5f38FdmIbJIklbBd900F96oiDlohWA,1203
20
+ pyllm/nn/embedding.py,sha256=DEOy6Cnvfe4oVUoCILPaTqSZp5_nyLbqhC45_gDlOzs,1119
21
+ pyllm/nn/functional.py,sha256=g9s0fih9jXkrelSmI0ajrR1rwCRiaRMRxOcW4hgP1C8,4656
22
+ pyllm/nn/linear.py,sha256=ewivFsQqSPOAwKOZb5PFnmWJ3Fm9ODJGJ06SwnYMwZM,1225
23
+ pyllm/nn/module.py,sha256=CLkEe60uXUDU35e_6WR93mUa23dYhrPyG-GV3tVliZs,3309
24
+ pyllm/nn/normalization.py,sha256=doKJTN30hu823hYTu6cxwviRFySoeUh6pYmPnED7ZnA,1221
25
+ pyllm/nn/transformer.py,sha256=7td2g5jDUCRj4iNB6DTkPCsQoYZJ2JleRTNc42bofxE,2654
26
+ pyllm/pebble/__init__.py,sha256=olqN5bt7YzpAjMsvUo0gLxae5J_X4SUhZtuZUju-aHM,794
27
+ pyllm/pebble/ast.py,sha256=uwLtVLW_wu6wkSxUixZ31pA6z9FZmHCRPj_MhHzSyxc,2732
28
+ pyllm/pebble/corpus.py,sha256=X0AjARrmfuQa-CqoD3iHUpZINN0GsNCS02O1WyQQ5g4,2828
29
+ pyllm/pebble/generator.py,sha256=9XXLTvK8t17UQPIKeoCFF3ezUKgeUeUD3KM7lPMt6r0,5894
30
+ pyllm/pebble/harvest.py,sha256=EBoMpQWbY1lDWbrILvh4VkMpOqn9xoMZQoznA8f7QH0,1370
31
+ pyllm/pebble/referee.py,sha256=z0EcQPl6S7XvEhB11RrIBevgbC9SjRvNmhBCaI_Hjqc,15247
32
+ pyllm/pebble/render.py,sha256=JfF6V26sfEneORo1AUQXaM-8bIW3UD_Ukx6z3-LSYFw,3602
33
+ pyllm/pebble/score.py,sha256=aQEpU8cOLoqVOgOq-X1NA2IN9E3N1SvX9aROQfJ5IrA,1522
34
+ pyllm/tokenizer/__init__.py,sha256=1x2yr-c0V4CgLQZ4UHDN7v3TY362faL0bUU0SkmXuFk,271
35
+ pyllm/tokenizer/base.py,sha256=RzB22pUwm63Kty57llBolB9BzAbe36PWjXNmRHgR4MI,631
36
+ pyllm/tokenizer/bpe.py,sha256=H20Oy7Q04AMmty4UMylB9aCcrXPenpmhN4QXEWOtmAo,4782
37
+ pyllm/tokenizer/char.py,sha256=bJhbiY1vmcP8bdfOrgJnsWrlITYWCMqMSgUlc-lx7-o,1419
38
+ pyllm/training/__init__.py,sha256=N3CH1IrHd-3qvnl66Q2o03YAxRbolEUBMmfyxDC5hcM,237
39
+ pyllm/training/data.py,sha256=KJwSOUlw-pvgjK1jmIHeLD-FbyvxPHjSrt8Z3O0z_k8,1723
40
+ pyllm/training/loop.py,sha256=s6N4zEw0P4wZheiPAL1jPLQLr7mMOPEF4sqBhYlcT0g,1819
41
+ pyllm/training/optim.py,sha256=Qxw2SeRz6xqEtKD4V8velwzUygEPsY9B4M3_rkKWQWA,2981
42
+ pyllm/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
+ pyllm/data/pebble.npz,sha256=wHcN3JJkMt09PSvpvXbQLCGjWpyLBtNeCiV-zVVgL6s,921586
44
+ pyllm/data/pebble_corpus.txt,sha256=_ie2Nzl04rET5UAJKOZG5wyBVjncNH9Uq-QGZX9kZHQ,354412
45
+ pyllm/data/pokemon.npz,sha256=gwZb4x8lYiL8cVdU_xuV3bXLeyc1CCez8EkQi5qRqxg,492126
46
+ pyllm/data/pokemon_corpus.txt,sha256=VahZ4Dacr-l0iFjmEbhUgZjxYbkdKAFbzbeQCyDwKXE,1234
47
+ fromscratch_pyllm-0.5.0.dist-info/METADATA,sha256=AjGWGtJjkB8kpC7DzRpM2XsU0zKH-5FnnjMkie4ueDM,9775
48
+ fromscratch_pyllm-0.5.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
49
+ fromscratch_pyllm-0.5.0.dist-info/entry_points.txt,sha256=h7nObqiD8Dca70XQtmOf1hUE3UuDoE08jLXXiby6kbc,41
50
+ fromscratch_pyllm-0.5.0.dist-info/licenses/LICENSE,sha256=NtHM2ak--oE71VCcY50kcCoC1cKS0LRUsxq_xA89RNc,1064
51
+ fromscratch_pyllm-0.5.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pyllm = pyllm.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rob Cox
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
pyllm/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """PyLLM: an educational large language model built from scratch."""
2
+
3
+ from pyllm.api import generate_pebble, generate_pokemon
4
+
5
+ __all__ = ["generate_pebble", "generate_pokemon"]
pyllm/_types.py ADDED
@@ -0,0 +1,10 @@
1
+ """Shared type aliases, so every module spells "a grid of numbers" the same way."""
2
+
3
+ import numpy as np
4
+ from numpy.typing import NDArray
5
+
6
+ type Array = NDArray[np.float64]
7
+ """A grid of 64-bit decimal numbers (the only kind of array pyllm uses)."""
8
+
9
+ type IntArray = NDArray[np.int64]
10
+ """A grid of whole numbers, such as token ids."""
pyllm/_version.py ADDED
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.5.0'
22
+ __version_tuple__ = version_tuple = (0, 5, 0)
23
+
24
+ __commit_id__ = commit_id = None
pyllm/api.py ADDED
@@ -0,0 +1,72 @@
1
+ """A friendly one-call way to make PyLLM write something.
2
+
3
+ Loading a model from disk and wiring up sampling every time would be a chore, so
4
+ this is the "just give me some text" front door: call `generate_pokemon()` to
5
+ dream up creatures, or `generate_pebble()` to write Pebble code. The trained
6
+ brain is loaded once and kept warm (cached) so the second call is instant. This
7
+ is also the door PyStack knocks on to let Pebble programs use PyLLM.
8
+ """
9
+
10
+ from pathlib import Path
11
+
12
+ import numpy as np
13
+
14
+ from pyllm.checkpoint import load
15
+ from pyllm.data import PEBBLE_CHECKPOINT, POKEMON_CHECKPOINT
16
+ from pyllm.generate import generate
17
+ from pyllm.models import LanguageModel
18
+ from pyllm.pebble import referee as pebble_referee
19
+ from pyllm.tokenizer import Tokenizer
20
+
21
+ _CACHE: dict[str, tuple[LanguageModel, Tokenizer]] = {}
22
+
23
+
24
+ def _load_cached(path: Path) -> tuple[LanguageModel, Tokenizer]:
25
+ """Load a checkpoint once and remember it, so later calls are instant."""
26
+ key = str(path)
27
+ if key not in _CACHE:
28
+ _CACHE[key] = load(path)
29
+ return _CACHE[key]
30
+
31
+
32
+ def generate_pokemon(
33
+ prompt: str = "",
34
+ max_new_tokens: int = 60,
35
+ temperature: float = 0.8,
36
+ seed: int | None = None,
37
+ ) -> str:
38
+ """Dream up new Pokémon-ish names using the bundled Pokémon brain."""
39
+ model, tokenizer = _load_cached(POKEMON_CHECKPOINT)
40
+ return generate(
41
+ model,
42
+ tokenizer,
43
+ prompt=prompt,
44
+ max_new_tokens=max_new_tokens,
45
+ temperature=temperature,
46
+ rng=np.random.default_rng(seed),
47
+ )
48
+
49
+
50
+ def generate_pebble(
51
+ prompt: str = "let ",
52
+ max_new_tokens: int = 120,
53
+ temperature: float = 0.7,
54
+ seed: int | None = None,
55
+ *,
56
+ constrained: bool = False,
57
+ ) -> str:
58
+ """Write Pebble-flavoured code using the bundled Pebble brain.
59
+
60
+ With `constrained=True` a grammar referee vetoes every illegal character, so
61
+ what comes back is always real, parseable Pebble.
62
+ """
63
+ model, tokenizer = _load_cached(PEBBLE_CHECKPOINT)
64
+ return generate(
65
+ model,
66
+ tokenizer,
67
+ prompt=prompt,
68
+ max_new_tokens=max_new_tokens,
69
+ temperature=temperature,
70
+ rng=np.random.default_rng(seed),
71
+ referee=pebble_referee if constrained else None,
72
+ )
@@ -0,0 +1,5 @@
1
+ """The autograd engine: tensors that remember how to compute their gradients."""
2
+
3
+ from pyllm.autograd.tensor import Tensor
4
+
5
+ __all__ = ["Tensor"]
@@ -0,0 +1,34 @@
1
+ """Finite-difference gradient checking: an independent test that backward() is right."""
2
+
3
+ from collections.abc import Callable
4
+
5
+ import numpy as np
6
+
7
+ from pyllm._types import Array
8
+ from pyllm.autograd.tensor import Tensor
9
+
10
+
11
+ def numerical_grad(make_output: Callable[[], Tensor], wrt: Tensor, eps: float = 1e-6) -> Array:
12
+ """Finite-difference gradient of sum(make_output().data) w.r.t. `wrt`.
13
+
14
+ `make_output` is a function taking no arguments that builds and returns an
15
+ output Tensor using `wrt`. We nudge each element of `wrt.data` up and down by
16
+ `eps` and measure how the summed output changes -- the classic definition of
17
+ a derivative. This is our independent check that backward() is correct.
18
+ """
19
+ grad = np.zeros_like(wrt.data)
20
+ it = np.nditer(wrt.data, flags=["multi_index"])
21
+ while not it.finished:
22
+ idx = it.multi_index
23
+ original = wrt.data[idx]
24
+
25
+ wrt.data[idx] = original + eps
26
+ plus = float(make_output().data.sum())
27
+
28
+ wrt.data[idx] = original - eps
29
+ minus = float(make_output().data.sum())
30
+
31
+ wrt.data[idx] = original
32
+ grad[idx] = (plus - minus) / (2.0 * eps)
33
+ it.iternext()
34
+ return grad