ultragraph-1bit 0.1.0__tar.gz
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.
- ultragraph_1bit-0.1.0/.github/workflows/ci.yml +27 -0
- ultragraph_1bit-0.1.0/.github/workflows/publish.yml +25 -0
- ultragraph_1bit-0.1.0/.gitignore +8 -0
- ultragraph_1bit-0.1.0/CHANGELOG.md +37 -0
- ultragraph_1bit-0.1.0/CONTRIBUTING.md +44 -0
- ultragraph_1bit-0.1.0/LICENSE +21 -0
- ultragraph_1bit-0.1.0/PKG-INFO +135 -0
- ultragraph_1bit-0.1.0/README.md +123 -0
- ultragraph_1bit-0.1.0/assets/architecture.png +0 -0
- ultragraph_1bit-0.1.0/assets/fig_architecture.png +0 -0
- ultragraph_1bit-0.1.0/assets/fig_attention.png +0 -0
- ultragraph_1bit-0.1.0/assets/fig_ternary_weights.png +0 -0
- ultragraph_1bit-0.1.0/assets/make_figures.py +201 -0
- ultragraph_1bit-0.1.0/docs/references.md +34 -0
- ultragraph_1bit-0.1.0/docs/superpowers/specs/2026-07-10-ultragraph-design.md +169 -0
- ultragraph_1bit-0.1.0/examples/char_lm.py +55 -0
- ultragraph_1bit-0.1.0/examples/mini_gpt.py +82 -0
- ultragraph_1bit-0.1.0/examples/render_viz.py +54 -0
- ultragraph_1bit-0.1.0/examples/transformer_lm.py +68 -0
- ultragraph_1bit-0.1.0/justfile +29 -0
- ultragraph_1bit-0.1.0/pyproject.toml +34 -0
- ultragraph_1bit-0.1.0/tests/run_all.py +47 -0
- ultragraph_1bit-0.1.0/tests/test_advanced.py +136 -0
- ultragraph_1bit-0.1.0/tests/test_attention.py +74 -0
- ultragraph_1bit-0.1.0/tests/test_autograd.py +214 -0
- ultragraph_1bit-0.1.0/tests/test_core.py +109 -0
- ultragraph_1bit-0.1.0/tests/test_e2e.py +82 -0
- ultragraph_1bit-0.1.0/tests/test_io.py +70 -0
- ultragraph_1bit-0.1.0/tests/test_quant.py +36 -0
- ultragraph_1bit-0.1.0/tests/test_viz.py +57 -0
- ultragraph_1bit-0.1.0/ultragraph/__init__.py +35 -0
- ultragraph_1bit-0.1.0/ultragraph/autograd.py +282 -0
- ultragraph_1bit-0.1.0/ultragraph/core.py +316 -0
- ultragraph_1bit-0.1.0/ultragraph/io.py +88 -0
- ultragraph_1bit-0.1.0/ultragraph/nn.py +152 -0
- ultragraph_1bit-0.1.0/ultragraph/optim.py +113 -0
- ultragraph_1bit-0.1.0/ultragraph/quant.py +45 -0
- ultragraph_1bit-0.1.0/ultragraph/viz.py +491 -0
- ultragraph_1bit-0.1.0/uv.lock +689 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- name: Install uv
|
|
19
|
+
uses: astral-sh/setup-uv@v5
|
|
20
|
+
with:
|
|
21
|
+
enable-cache: true
|
|
22
|
+
- name: Sync environment (incl. viz extra for matplotlib tests)
|
|
23
|
+
run: uv sync --extra viz --python ${{ matrix.python-version }}
|
|
24
|
+
- name: Lint (ruff)
|
|
25
|
+
run: uv run ruff check ultragraph tests examples assets
|
|
26
|
+
- name: Test (pytest)
|
|
27
|
+
run: uv run pytest
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# Publishes `ultragraph-1bit` via PyPI Trusted Publishing (OIDC) — no API token
|
|
4
|
+
# stored in the repo. Fires when a GitHub Release is published, or manually.
|
|
5
|
+
# One-time setup on PyPI: add a "pending" trusted publisher for project
|
|
6
|
+
# `ultragraph-1bit`, owner `peterlodri-sec`, repo `ultra-graph`, workflow
|
|
7
|
+
# `publish.yml` (no environment).
|
|
8
|
+
|
|
9
|
+
on:
|
|
10
|
+
release:
|
|
11
|
+
types: [published]
|
|
12
|
+
workflow_dispatch:
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
publish:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
permissions:
|
|
18
|
+
id-token: write # required for trusted publishing
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
- uses: astral-sh/setup-uv@v5
|
|
22
|
+
- name: Build sdist + wheel
|
|
23
|
+
run: uv build
|
|
24
|
+
- name: Publish to PyPI (trusted publishing)
|
|
25
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project. Format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/); this project uses
|
|
5
|
+
[Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [0.1.0] — 2026-07-11
|
|
8
|
+
|
|
9
|
+
The byte-graph that is a 1-bit (ternary) LLM.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **Core** (`core.py`) — `Node`/`Edge`/`Tree`/`UltraEdge`/`UltraGraph`, the byte
|
|
13
|
+
buffers, the ad-hoc side store, and the dunder API (`node >> node` micro-edge,
|
|
14
|
+
`tree >> tree` ultra-edge, `len`/`iter`/`in`, `_repr_svg_`).
|
|
15
|
+
- **Quantization** (`quant.py`) — ternary weights (absmean) + int8 activations
|
|
16
|
+
(absmax), with the small-`eps` / finite guards.
|
|
17
|
+
- **Autograd** (`autograd.py`) — n-D reverse-mode tape: `+ - * / @`, `transpose`,
|
|
18
|
+
`swapaxes`, `reshape`, `relu`, `sqrt`, `sum`, `mean(axis)`, `softmax`,
|
|
19
|
+
`cross_entropy`; and `ternary_linear` with a straight-through estimator and
|
|
20
|
+
per-token int8 activation quantization.
|
|
21
|
+
- **nn** (`nn.py`) — `linear_tree`, `mlp`, single-head `Attention`, batched
|
|
22
|
+
`MultiHeadAttention` (causal), `RMSNorm`, `LayerNorm`.
|
|
23
|
+
- **Optimizers** (`optim.py`) — `SGD` and `Adam`, both with global-norm gradient
|
|
24
|
+
clipping; re-quantize the ternary buffers after each step.
|
|
25
|
+
- **Visualization** (`viz.py`) — pure-SVG micro/macro/byte-heatmap views plus an
|
|
26
|
+
optional matplotlib backend (`[viz]` extra).
|
|
27
|
+
- **I/O** (`io.py`) — byte-exact save/load.
|
|
28
|
+
- **Examples** — `char_lm.py`, `transformer_lm.py`, `mini_gpt.py` (batched
|
|
29
|
+
multi-head transformer that memorizes a toy corpus), `render_viz.py`,
|
|
30
|
+
`make_figures.py`.
|
|
31
|
+
- **Tests** — numeric-gradient checks for every op, an attention causality proof,
|
|
32
|
+
save/load byte-exactness, and end-to-end training; a dependency-free runner.
|
|
33
|
+
- **Tooling & docs** — `ruff` (config in `pyproject.toml`), GitHub Actions CI
|
|
34
|
+
(`ruff` + `pytest` on Python 3.11–3.13), `CONTRIBUTING.md`, `CHANGELOG.md`, and
|
|
35
|
+
`docs/references.md` (an Erdős graph-theory reading list).
|
|
36
|
+
|
|
37
|
+
[0.1.0]: https://github.com/peterlodri-sec/ultra-graph/releases/tag/v0.1.0
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Contributing to ultragraph
|
|
2
|
+
|
|
3
|
+
Thanks for poking at a byte-graph. It's a small, pure-Python (+ numpy) library, so
|
|
4
|
+
the loop is short.
|
|
5
|
+
|
|
6
|
+
## Setup
|
|
7
|
+
|
|
8
|
+
Uses [uv](https://github.com/astral-sh/uv) and Python ≥ 3.11.
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
uv sync --extra viz # core + dev (pytest, ruff) + matplotlib for viz tests
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## The loop
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
just test # pytest (or: uv run pytest)
|
|
18
|
+
just test-fast # dependency-free runner (stdlib + numpy only)
|
|
19
|
+
uv run ruff check ultragraph tests examples assets # lint
|
|
20
|
+
just demo # end-to-end ternary mini-GPT
|
|
21
|
+
just viz # render example graphs
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
CI (`.github/workflows/ci.yml`) runs `ruff check` + `pytest` on Python 3.11–3.13.
|
|
25
|
+
Both must be green before merge.
|
|
26
|
+
|
|
27
|
+
## Conventions
|
|
28
|
+
|
|
29
|
+
- **Correctness first.** New autograd ops need a numeric-gradient test
|
|
30
|
+
(`tests/test_autograd.py` has the pattern: analytic backward vs central finite
|
|
31
|
+
differences). Quantized paths are checked against their unquantized surrogate
|
|
32
|
+
(that's what STE claims).
|
|
33
|
+
- **The byte contract holds.** One node = one byte (`int8` activation), one edge =
|
|
34
|
+
one byte (ternary weight). Anything bigger than a byte lives in the ad-hoc side
|
|
35
|
+
store, not the hot buffers.
|
|
36
|
+
- **Keep files focused.** Match the surrounding style: `ruff` config in
|
|
37
|
+
`pyproject.toml` (`select = F, E7, E9, I`); imports sorted.
|
|
38
|
+
- **numpy is the only runtime dependency.** `matplotlib` is optional (`[viz]`
|
|
39
|
+
extra); the SVG backend stays stdlib-only.
|
|
40
|
+
|
|
41
|
+
## Pull requests
|
|
42
|
+
|
|
43
|
+
Small, focused PRs. Include tests. Run `ruff check` + `pytest` locally first. Say
|
|
44
|
+
what you changed and why in the description.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Peter Lodri
|
|
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.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ultragraph-1bit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A pure-Python (+numpy) byte-graph that is a 1-bit (ternary) LLM: dunder API, autograd, ultra-edges, visualization.
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Requires-Dist: numpy>=2.0
|
|
9
|
+
Provides-Extra: viz
|
|
10
|
+
Requires-Dist: matplotlib>=3.8; extra == 'viz'
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# ultragraph
|
|
14
|
+
|
|
15
|
+
[](https://github.com/peterlodri-sec/ultra-graph/actions/workflows/ci.yml)
|
|
16
|
+
[](pyproject.toml)
|
|
17
|
+
[](LICENSE)
|
|
18
|
+
|
|
19
|
+
A pure-Python (+ numpy) **byte-graph that is a 1-bit (ternary) LLM**.
|
|
20
|
+
|
|
21
|
+
> **genesis** `251e6ea` · themed after [pocoo.vaked.dev](https://pocoo.vaked.dev)
|
|
22
|
+
|
|
23
|
+

|
|
24
|
+
|
|
25
|
+
Three levels:
|
|
26
|
+
|
|
27
|
+
| level | unit | storage |
|
|
28
|
+
|-------|------|---------|
|
|
29
|
+
| micro | **node** / **edge** | **1 byte each** — `int8` activation / ternary weight `{-1,0,+1}` |
|
|
30
|
+
| meso | **tree** | a whole graph == one net/module (a Linear/MLP block) |
|
|
31
|
+
| macro | **ultra-edge** (`===`) | typed wiring between trees → the **ultra-graph** = the model |
|
|
32
|
+
|
|
33
|
+
Weights are ternary (BitNet b1.58 style); activations are int8. Full-precision
|
|
34
|
+
"master" weights live in an ad-hoc side store during training; the byte buffers are
|
|
35
|
+
the deployed state. Training uses a straight-through estimator (STE).
|
|
36
|
+
|
|
37
|
+
## Illustrations
|
|
38
|
+
|
|
39
|
+
Real outputs from a trained ternary mini-GPT — regenerate with `uv run python assets/make_figures.py`:
|
|
40
|
+
|
|
41
|
+
| ultra-graph | causal attention | ternary weight bytes |
|
|
42
|
+
|:---:|:---:|:---:|
|
|
43
|
+
|  |  |  |
|
|
44
|
+
|
|
45
|
+
Left: the model as an **ultra-graph** — trees wired by ultra-edges (`===`), with residual skips.
|
|
46
|
+
Middle: **real** causal self-attention weights (lower-triangular → no peeking at the future).
|
|
47
|
+
Right: a trained query projection's weight bytes, each ∈ {−1, 0, +1}.
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
pip install ultragraph-1bit # then: import ultragraph
|
|
53
|
+
# or from source (Python >=3.11):
|
|
54
|
+
uv sync
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Dunder API
|
|
58
|
+
|
|
59
|
+
`>>` is overloaded by operand type:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
import numpy as np
|
|
63
|
+
from ultragraph import Tree, UltraGraph, Tensor, mlp, SGD
|
|
64
|
+
|
|
65
|
+
# micro-edges inside a sparse tree
|
|
66
|
+
g = Tree(4, "g")
|
|
67
|
+
g[0] >> g[1] # node >> node -> micro-edge
|
|
68
|
+
g[2] = 7 # set a node byte
|
|
69
|
+
print(len(g), 2 in g, list(g))
|
|
70
|
+
|
|
71
|
+
# ultra-edges between trees
|
|
72
|
+
ug = UltraGraph()
|
|
73
|
+
a = ug.add(Tree.dense(8, 16, "a"))
|
|
74
|
+
b = ug.add(Tree.dense(16, 4, "b", act="none"))
|
|
75
|
+
a >> b # tree >> tree -> ultra-edge (plain)
|
|
76
|
+
a.wire(b, "residual")
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Train a tiny ternary net
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
ug = mlp([4, 16, 2]) # dense ternary linear trees wired plain
|
|
83
|
+
opt = SGD(ug, lr=0.3, momentum=0.9)
|
|
84
|
+
x = Tensor(np.random.randn(32, 4).astype("float32"))
|
|
85
|
+
for _ in range(300):
|
|
86
|
+
loss = ug.forward(x).cross_entropy(y)
|
|
87
|
+
opt.zero_grad(); loss.backward(); opt.step() # step() re-quantizes weights
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
See `examples/char_lm.py` (MLP LM), `examples/transformer_lm.py` (single-head attention),
|
|
91
|
+
and `examples/mini_gpt.py` (batched **multi-head** attention + **RMSNorm** + **Adam**) for
|
|
92
|
+
end-to-end char-level ternary language models.
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from ultragraph import Embedding, MultiHeadAttention, RMSNorm, linear_tree, Adam
|
|
96
|
+
# pre-norm transformer block over a [B, T, d_model] sequence:
|
|
97
|
+
# x = x + mha(norm1(x)); x = x + ff2(ff1(norm2(x)))
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Tasks
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
just test # pytest
|
|
104
|
+
just test-fast # dependency-free runner (stdlib + numpy)
|
|
105
|
+
just demo # char-LM end-to-end
|
|
106
|
+
just viz # render example SVGs
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Layout
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
ultragraph/quant.py ternary + int8 quantization, STE
|
|
113
|
+
ultragraph/autograd.py numpy autograd tape; ternary_linear (STE)
|
|
114
|
+
ultragraph/core.py Node/Edge/Tree/UltraEdge/UltraGraph + dunder API
|
|
115
|
+
ultragraph/nn.py linear_tree, mlp, Attention, MultiHeadAttention, RMSNorm, LayerNorm
|
|
116
|
+
ultragraph/optim.py SGD + Adam over fp32 masters (grad clip), re-quantize after step
|
|
117
|
+
ultragraph/viz.py pure-SVG + optional matplotlib (micro / macro / byte-heatmap)
|
|
118
|
+
ultragraph/io.py byte-exact save / load
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Design spec: `docs/superpowers/specs/2026-07-10-ultragraph-design.md`.
|
|
122
|
+
Graph-theory reading list (Erdős classics): [`docs/references.md`](docs/references.md).
|
|
123
|
+
|
|
124
|
+
## Install from source
|
|
125
|
+
|
|
126
|
+
```sh
|
|
127
|
+
git clone https://github.com/peterlodri-sec/ultra-graph
|
|
128
|
+
cd ultra-graph
|
|
129
|
+
uv sync
|
|
130
|
+
just test
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# ultragraph
|
|
2
|
+
|
|
3
|
+
[](https://github.com/peterlodri-sec/ultra-graph/actions/workflows/ci.yml)
|
|
4
|
+
[](pyproject.toml)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
A pure-Python (+ numpy) **byte-graph that is a 1-bit (ternary) LLM**.
|
|
8
|
+
|
|
9
|
+
> **genesis** `251e6ea` · themed after [pocoo.vaked.dev](https://pocoo.vaked.dev)
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
Three levels:
|
|
14
|
+
|
|
15
|
+
| level | unit | storage |
|
|
16
|
+
|-------|------|---------|
|
|
17
|
+
| micro | **node** / **edge** | **1 byte each** — `int8` activation / ternary weight `{-1,0,+1}` |
|
|
18
|
+
| meso | **tree** | a whole graph == one net/module (a Linear/MLP block) |
|
|
19
|
+
| macro | **ultra-edge** (`===`) | typed wiring between trees → the **ultra-graph** = the model |
|
|
20
|
+
|
|
21
|
+
Weights are ternary (BitNet b1.58 style); activations are int8. Full-precision
|
|
22
|
+
"master" weights live in an ad-hoc side store during training; the byte buffers are
|
|
23
|
+
the deployed state. Training uses a straight-through estimator (STE).
|
|
24
|
+
|
|
25
|
+
## Illustrations
|
|
26
|
+
|
|
27
|
+
Real outputs from a trained ternary mini-GPT — regenerate with `uv run python assets/make_figures.py`:
|
|
28
|
+
|
|
29
|
+
| ultra-graph | causal attention | ternary weight bytes |
|
|
30
|
+
|:---:|:---:|:---:|
|
|
31
|
+
|  |  |  |
|
|
32
|
+
|
|
33
|
+
Left: the model as an **ultra-graph** — trees wired by ultra-edges (`===`), with residual skips.
|
|
34
|
+
Middle: **real** causal self-attention weights (lower-triangular → no peeking at the future).
|
|
35
|
+
Right: a trained query projection's weight bytes, each ∈ {−1, 0, +1}.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
pip install ultragraph-1bit # then: import ultragraph
|
|
41
|
+
# or from source (Python >=3.11):
|
|
42
|
+
uv sync
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Dunder API
|
|
46
|
+
|
|
47
|
+
`>>` is overloaded by operand type:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import numpy as np
|
|
51
|
+
from ultragraph import Tree, UltraGraph, Tensor, mlp, SGD
|
|
52
|
+
|
|
53
|
+
# micro-edges inside a sparse tree
|
|
54
|
+
g = Tree(4, "g")
|
|
55
|
+
g[0] >> g[1] # node >> node -> micro-edge
|
|
56
|
+
g[2] = 7 # set a node byte
|
|
57
|
+
print(len(g), 2 in g, list(g))
|
|
58
|
+
|
|
59
|
+
# ultra-edges between trees
|
|
60
|
+
ug = UltraGraph()
|
|
61
|
+
a = ug.add(Tree.dense(8, 16, "a"))
|
|
62
|
+
b = ug.add(Tree.dense(16, 4, "b", act="none"))
|
|
63
|
+
a >> b # tree >> tree -> ultra-edge (plain)
|
|
64
|
+
a.wire(b, "residual")
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Train a tiny ternary net
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
ug = mlp([4, 16, 2]) # dense ternary linear trees wired plain
|
|
71
|
+
opt = SGD(ug, lr=0.3, momentum=0.9)
|
|
72
|
+
x = Tensor(np.random.randn(32, 4).astype("float32"))
|
|
73
|
+
for _ in range(300):
|
|
74
|
+
loss = ug.forward(x).cross_entropy(y)
|
|
75
|
+
opt.zero_grad(); loss.backward(); opt.step() # step() re-quantizes weights
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
See `examples/char_lm.py` (MLP LM), `examples/transformer_lm.py` (single-head attention),
|
|
79
|
+
and `examples/mini_gpt.py` (batched **multi-head** attention + **RMSNorm** + **Adam**) for
|
|
80
|
+
end-to-end char-level ternary language models.
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from ultragraph import Embedding, MultiHeadAttention, RMSNorm, linear_tree, Adam
|
|
84
|
+
# pre-norm transformer block over a [B, T, d_model] sequence:
|
|
85
|
+
# x = x + mha(norm1(x)); x = x + ff2(ff1(norm2(x)))
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Tasks
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
just test # pytest
|
|
92
|
+
just test-fast # dependency-free runner (stdlib + numpy)
|
|
93
|
+
just demo # char-LM end-to-end
|
|
94
|
+
just viz # render example SVGs
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Layout
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
ultragraph/quant.py ternary + int8 quantization, STE
|
|
101
|
+
ultragraph/autograd.py numpy autograd tape; ternary_linear (STE)
|
|
102
|
+
ultragraph/core.py Node/Edge/Tree/UltraEdge/UltraGraph + dunder API
|
|
103
|
+
ultragraph/nn.py linear_tree, mlp, Attention, MultiHeadAttention, RMSNorm, LayerNorm
|
|
104
|
+
ultragraph/optim.py SGD + Adam over fp32 masters (grad clip), re-quantize after step
|
|
105
|
+
ultragraph/viz.py pure-SVG + optional matplotlib (micro / macro / byte-heatmap)
|
|
106
|
+
ultragraph/io.py byte-exact save / load
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Design spec: `docs/superpowers/specs/2026-07-10-ultragraph-design.md`.
|
|
110
|
+
Graph-theory reading list (Erdős classics): [`docs/references.md`](docs/references.md).
|
|
111
|
+
|
|
112
|
+
## Install from source
|
|
113
|
+
|
|
114
|
+
```sh
|
|
115
|
+
git clone https://github.com/peterlodri-sec/ultra-graph
|
|
116
|
+
cd ultra-graph
|
|
117
|
+
uv sync
|
|
118
|
+
just test
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## License
|
|
122
|
+
|
|
123
|
+
MIT — see [LICENSE](LICENSE).
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""Generate themed illustrations for the README from a REAL trained model.
|
|
2
|
+
|
|
3
|
+
Palette matches pocoo.vaked.dev: deep navy background with cyan / green / purple
|
|
4
|
+
accents. Figures are produced from an actual mini-GPT forward pass (ternary
|
|
5
|
+
weights, causal attention matrix) plus an architecture diagram.
|
|
6
|
+
|
|
7
|
+
Run: uv run python assets/make_figures.py
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import math
|
|
12
|
+
import os
|
|
13
|
+
|
|
14
|
+
import matplotlib
|
|
15
|
+
import numpy as np
|
|
16
|
+
|
|
17
|
+
matplotlib.use("Agg")
|
|
18
|
+
import matplotlib.pyplot as plt
|
|
19
|
+
from matplotlib.colors import LinearSegmentedColormap
|
|
20
|
+
from matplotlib.patches import FancyArrowPatch, FancyBboxPatch
|
|
21
|
+
|
|
22
|
+
from ultragraph import Adam, Embedding, MultiHeadAttention, RMSNorm, Tensor, UltraGraph, linear_tree
|
|
23
|
+
|
|
24
|
+
BG = "#070b16"
|
|
25
|
+
PANEL = "#0b1120"
|
|
26
|
+
CYAN = "#00d4ff"
|
|
27
|
+
GREEN = "#00e660"
|
|
28
|
+
PURPLE = "#b48bff"
|
|
29
|
+
FG = "#c8d3e6"
|
|
30
|
+
MUTED = "#4a5a80"
|
|
31
|
+
|
|
32
|
+
OUT = os.path.join(os.path.dirname(__file__))
|
|
33
|
+
|
|
34
|
+
# purple -> navy -> cyan, for ternary weights in {-1, 0, +1}
|
|
35
|
+
TERNARY_CMAP = LinearSegmentedColormap.from_list("ug_ternary", [PURPLE, "#0e1730", CYAN])
|
|
36
|
+
# navy -> cyan, sequential, for attention weights in [0, 1]
|
|
37
|
+
ATTN_CMAP = LinearSegmentedColormap.from_list("ug_attn", ["#0a1024", "#0f5c78", CYAN])
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _style(ax):
|
|
41
|
+
ax.set_facecolor(BG)
|
|
42
|
+
for s in ax.spines.values():
|
|
43
|
+
s.set_color(MUTED)
|
|
44
|
+
ax.tick_params(colors=FG, labelsize=8)
|
|
45
|
+
ax.xaxis.label.set_color(FG)
|
|
46
|
+
ax.yaxis.label.set_color(FG)
|
|
47
|
+
ax.title.set_color(FG)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _save(fig, name):
|
|
51
|
+
path = os.path.join(OUT, name)
|
|
52
|
+
fig.savefig(path, dpi=150, bbox_inches="tight", facecolor=BG)
|
|
53
|
+
plt.close(fig)
|
|
54
|
+
print("wrote", path)
|
|
55
|
+
return path
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def train_model():
|
|
59
|
+
np.random.seed(0)
|
|
60
|
+
text = "hello ultra graph world " * 4
|
|
61
|
+
chars = sorted(set(text))
|
|
62
|
+
stoi = {c: i for i, c in enumerate(chars)}
|
|
63
|
+
vocab = len(chars)
|
|
64
|
+
d_model, n_heads, T = 24, 4, 8
|
|
65
|
+
ids = np.array([stoi[c] for c in text], dtype=np.int64)
|
|
66
|
+
seqs = np.stack([ids[i : i + T + 1] for i in range(len(ids) - T)])
|
|
67
|
+
inputs, targets = seqs[:, :-1], seqs[:, 1:]
|
|
68
|
+
B = inputs.shape[0]
|
|
69
|
+
|
|
70
|
+
emb = Embedding(vocab, d_model, "emb")
|
|
71
|
+
n1 = RMSNorm(d_model, name="n1")
|
|
72
|
+
mha = MultiHeadAttention(d_model, n_heads, causal=True)
|
|
73
|
+
n2 = RMSNorm(d_model, name="n2")
|
|
74
|
+
ff1 = linear_tree(d_model, 4 * d_model, "ff1", act="relu")
|
|
75
|
+
ff2 = linear_tree(4 * d_model, d_model, "ff2", act="none")
|
|
76
|
+
unembed = linear_tree(d_model, vocab, "unembed", act="none")
|
|
77
|
+
model = UltraGraph("mini_gpt")
|
|
78
|
+
for m in (emb, n1, mha, n2):
|
|
79
|
+
model.register(m)
|
|
80
|
+
for t in (ff1, ff2, unembed):
|
|
81
|
+
model.add(t)
|
|
82
|
+
opt = Adam(model, lr=0.02, clip=1.0)
|
|
83
|
+
|
|
84
|
+
def forward(idx):
|
|
85
|
+
x = emb(idx.reshape(-1)).reshape(B, idx.shape[1], d_model)
|
|
86
|
+
x = x + mha(n1(x))
|
|
87
|
+
x = x + ff2.forward(ff1.forward(n2(x)))
|
|
88
|
+
return unembed.forward(x)
|
|
89
|
+
|
|
90
|
+
for _ in range(300):
|
|
91
|
+
loss = forward(inputs).cross_entropy(targets)
|
|
92
|
+
opt.zero_grad()
|
|
93
|
+
loss.backward()
|
|
94
|
+
opt.step()
|
|
95
|
+
return dict(mha=mha, n1=n1, emb=emb, inputs=inputs, T=T, d_model=d_model, n_heads=n_heads, B=B)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def fig_ternary_weights(m):
|
|
99
|
+
"""Real ternary weight matrix from the trained attention query projection."""
|
|
100
|
+
wq = m["mha"].wq.wq # int8 {-1,0,1}, shape [d_model, d_model]
|
|
101
|
+
fig, ax = plt.subplots(figsize=(4.6, 4.2))
|
|
102
|
+
fig.patch.set_facecolor(BG)
|
|
103
|
+
im = ax.imshow(wq, cmap=TERNARY_CMAP, vmin=-1, vmax=1, interpolation="nearest")
|
|
104
|
+
_style(ax)
|
|
105
|
+
ax.set_title("ternary weight bytes · W_q ∈ {-1, 0, +1}", fontsize=10)
|
|
106
|
+
ax.set_xlabel("in")
|
|
107
|
+
ax.set_ylabel("out")
|
|
108
|
+
cb = fig.colorbar(im, ax=ax, fraction=0.046, pad=0.04, ticks=[-1, 0, 1])
|
|
109
|
+
cb.ax.yaxis.set_tick_params(color=FG)
|
|
110
|
+
plt.setp(plt.getp(cb.ax.axes, "yticklabels"), color=FG)
|
|
111
|
+
return _save(fig, "fig_ternary_weights.png")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def fig_attention(m):
|
|
115
|
+
"""Real causal attention weights (head 0) from a forward pass."""
|
|
116
|
+
mha, n1, emb = m["mha"], m["n1"], m["emb"]
|
|
117
|
+
T, d, H = m["T"], m["d_model"], m["n_heads"]
|
|
118
|
+
dh = d // H
|
|
119
|
+
x = emb(m["inputs"][0]).reshape(1, T, d)
|
|
120
|
+
xn = n1(x)
|
|
121
|
+
q = mha.wq.forward(xn).reshape(1, T, H, dh).swapaxes(1, 2)
|
|
122
|
+
k = mha.wk.forward(xn).reshape(1, T, H, dh).swapaxes(1, 2)
|
|
123
|
+
scores = (q @ k.swapaxes(-1, -2)) * (1.0 / math.sqrt(dh))
|
|
124
|
+
mask = np.triu(np.full((T, T), -1e9, dtype=np.float32), k=1)
|
|
125
|
+
attn = (scores + Tensor(mask)).softmax(axis=-1).data[0, 0] # [T, T]
|
|
126
|
+
|
|
127
|
+
fig, ax = plt.subplots(figsize=(4.6, 4.2))
|
|
128
|
+
fig.patch.set_facecolor(BG)
|
|
129
|
+
im = ax.imshow(attn, cmap=ATTN_CMAP, vmin=0, vmax=1, interpolation="nearest")
|
|
130
|
+
_style(ax)
|
|
131
|
+
ax.set_title("causal self-attention · softmax(QKᵀ/√d)", fontsize=10)
|
|
132
|
+
ax.set_xlabel("key position")
|
|
133
|
+
ax.set_ylabel("query position")
|
|
134
|
+
cb = fig.colorbar(im, ax=ax, fraction=0.046, pad=0.04)
|
|
135
|
+
cb.ax.yaxis.set_tick_params(color=FG)
|
|
136
|
+
plt.setp(plt.getp(cb.ax.axes, "yticklabels"), color=FG)
|
|
137
|
+
return _save(fig, "fig_attention.png")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def fig_architecture():
|
|
141
|
+
"""Ultra-graph view of the mini-GPT block: trees wired by ultra-edges."""
|
|
142
|
+
fig, ax = plt.subplots(figsize=(5.2, 6.4))
|
|
143
|
+
fig.patch.set_facecolor(BG)
|
|
144
|
+
ax.set_facecolor(BG)
|
|
145
|
+
ax.set_xlim(0, 10)
|
|
146
|
+
ax.set_ylim(0, 13)
|
|
147
|
+
ax.axis("off")
|
|
148
|
+
|
|
149
|
+
def box(y, label, color, sub=""):
|
|
150
|
+
b = FancyBboxPatch((2.6, y), 4.8, 1.0, boxstyle="round,pad=0.08,rounding_size=0.18",
|
|
151
|
+
linewidth=1.6, edgecolor=color, facecolor=PANEL)
|
|
152
|
+
ax.add_patch(b)
|
|
153
|
+
ax.text(5.0, y + 0.62, label, ha="center", va="center", color=FG, fontsize=10, fontweight="bold")
|
|
154
|
+
if sub:
|
|
155
|
+
ax.text(5.0, y + 0.28, sub, ha="center", va="center", color=MUTED, fontsize=7.5)
|
|
156
|
+
return y
|
|
157
|
+
|
|
158
|
+
layers = [
|
|
159
|
+
(11.4, "Embedding", GREEN, "vocab → d_model"),
|
|
160
|
+
(9.6, "RMSNorm", PURPLE, ""),
|
|
161
|
+
(8.2, "MultiHeadAttention", CYAN, "ternary Q·K·V·O, causal"),
|
|
162
|
+
(6.4, "RMSNorm", PURPLE, ""),
|
|
163
|
+
(5.0, "MLP (ternary)", CYAN, "ff1 relu · ff2"),
|
|
164
|
+
(3.0, "Unembed", GREEN, "d_model → vocab"),
|
|
165
|
+
]
|
|
166
|
+
for y, lab, col, sub in layers:
|
|
167
|
+
box(y, lab, col, sub)
|
|
168
|
+
|
|
169
|
+
def arrow(y0, y1, color=FG, curve=0.0, lw=1.4):
|
|
170
|
+
a = FancyArrowPatch((5.0, y0), (5.0, y1), connectionstyle=f"arc3,rad={curve}",
|
|
171
|
+
arrowstyle="-|>", mutation_scale=12, color=color, lw=lw)
|
|
172
|
+
ax.add_patch(a)
|
|
173
|
+
|
|
174
|
+
arrow(11.4, 10.6) # emb -> norm
|
|
175
|
+
arrow(9.6, 9.2) # norm -> attn
|
|
176
|
+
arrow(8.2, 7.4) # attn -> norm2
|
|
177
|
+
arrow(6.4, 6.0) # norm2 -> mlp
|
|
178
|
+
arrow(5.0, 4.0) # mlp -> unembed
|
|
179
|
+
# residual ultra-edges (curved, on the right)
|
|
180
|
+
for y0, y1, c in [(11.4, 8.2, CYAN), (8.2, 5.0, CYAN)]:
|
|
181
|
+
a = FancyArrowPatch((7.4, y0 + 0.5), (7.4, y1 + 0.5), connectionstyle="arc3,rad=-0.5",
|
|
182
|
+
arrowstyle="-|>", mutation_scale=11, color=c, lw=1.2, linestyle=(0, (4, 2)))
|
|
183
|
+
ax.add_patch(a)
|
|
184
|
+
ax.text(9.0, (y0 + y1) / 2 + 0.5, "===", ha="center", va="center", color=c, fontsize=9, rotation=90)
|
|
185
|
+
|
|
186
|
+
ax.text(5.0, 12.5, "ultra-graph · trees wired by ultra-edges (===)", ha="center",
|
|
187
|
+
color=FG, fontsize=11, fontweight="bold")
|
|
188
|
+
ax.text(5.0, 2.2, "every weight = 1 ternary byte · every activation = 1 int8 byte",
|
|
189
|
+
ha="center", color=MUTED, fontsize=8)
|
|
190
|
+
return _save(fig, "fig_architecture.png")
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def main():
|
|
194
|
+
m = train_model()
|
|
195
|
+
fig_ternary_weights(m)
|
|
196
|
+
fig_attention(m)
|
|
197
|
+
fig_architecture()
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
if __name__ == "__main__":
|
|
201
|
+
main()
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# References — a graph theory reading list
|
|
2
|
+
|
|
3
|
+
`ultragraph` is a byte-graph, so it owes a debt to graph theory. This is a short,
|
|
4
|
+
opinionated reading list of Paul Erdős's most important graph-theory (and
|
|
5
|
+
graph-theorist-adjacent combinatorics) publications — the classics every graph
|
|
6
|
+
person eventually meets.
|
|
7
|
+
|
|
8
|
+
Citation details were checked against the Wikipedia articles for the named
|
|
9
|
+
theorems, the journals (Oxford Academic / Cambridge Core / Springer / AMS), and
|
|
10
|
+
the Rényi Institute's Erdős archive. A few conjecture citations are flagged where
|
|
11
|
+
a canonical volume/page could not be confirmed.
|
|
12
|
+
|
|
13
|
+
### Foundational papers
|
|
14
|
+
|
|
15
|
+
- **Erdős, P. & Szekeres, G. (1935).** "A combinatorial problem in geometry." *Compositio Mathematica*, 2, 463–470. [numdam](http://www.numdam.org/item/CM_1935__2__463_0/) — The Erdős–Szekeres theorem: any sequence longer than $(r-1)(s-1)$ has a monotone subsequence of length $r$ or $s$. A founding result of Ramsey theory (the "happy ending problem").
|
|
16
|
+
- **Erdős, P. & Stone, A. H. (1946).** "On the structure of linear graphs." *Bulletin of the AMS*, 52(12), 1087–1091. [doi:10.1090/S0002-9904-1946-08715-7](https://doi.org/10.1090/S0002-9904-1946-08715-7) — The Erdős–Stone theorem fixes the asymptotic maximum edge count of graphs forbidding a fixed subgraph — the "fundamental theorem of extremal graph theory," generalizing Turán.
|
|
17
|
+
- **Erdős, P. (1947).** "Some remarks on the theory of graphs." *Bulletin of the AMS*, 53(4), 292–294. [doi:10.1090/S0002-9904-1947-08785-1](https://doi.org/10.1090/S0002-9904-1947-08785-1) — The probabilistic lower bound $R(k,k) > 2^{k/2}$ for Ramsey numbers; launched the probabilistic method.
|
|
18
|
+
- **Erdős, P. & Rényi, A. (1959).** "On random graphs. I." *Publicationes Mathematicae Debrecen*, 6, 290–297. [doi:10.5486/PMD.1959.6.3-4.12](https://doi.org/10.5486/PMD.1959.6.3-4.12) — Introduced the $G(n,m)$ random-graph model; founded the theory of random graphs.
|
|
19
|
+
- **Erdős, P. & Rényi, A. (1960).** "On the evolution of random graphs." *Publ. Math. Inst. Hungar. Acad. Sci.*, 5, 17–61. [PDF](https://www.renyi.hu/~p_erdos/1960-10.pdf) (no DOI) — The phase-transition / giant-component paper; foundational for modern network science.
|
|
20
|
+
- **Erdős, P. & Gallai, T. (1960).** "Gráfok előírt fokszámú pontokkal" [Graphs with vertices of prescribed degrees]. *Matematikai Lapok*, 11, 264–274. [PDF](https://www.renyi.hu/~p_erdos/1961-05.pdf) (no DOI) — The Erdős–Gallai theorem: the inequalities that decide when an integer sequence is *graphic* (a degree sequence of a simple graph).
|
|
21
|
+
- **Erdős, P. (1959).** "Graph theory and probability." *Canadian Journal of Mathematics*, 11, 34–38. [doi:10.4153/CJM-1959-003-9](https://doi.org/10.4153/CJM-1959-003-9) — Graphs with simultaneously arbitrarily high girth *and* high chromatic number exist — a celebrated nonconstructive result.
|
|
22
|
+
- **Erdős, P., Ko, C. & Rado, R. (1961).** "Intersection theorems for systems of finite sets." *Quarterly J. of Mathematics* (Oxford, 2nd Ser.), 12, 313–320. [doi:10.1093/qmath/12.1.313](https://doi.org/10.1093/qmath/12.1.313) — The Erdős–Ko–Rado theorem bounds the largest intersecting family of $k$-subsets; a cornerstone of extremal set theory.
|
|
23
|
+
- **Erdős, P., Rényi, A. & Sós, V. T. (1966).** "On a problem of graph theory." *Studia Sci. Math. Hungar.*, 1, 215–235. [PDF](https://www.renyi.hu/~p_erdos/1966-06.pdf) (no DOI) — The friendship theorem: if every two vertices share exactly one common neighbour, the graph is a windmill.
|
|
24
|
+
|
|
25
|
+
### Conjectures
|
|
26
|
+
|
|
27
|
+
- **Erdős–Faber–Lovász** (1972; recorded in Erdős, P. (1981), "On the combinatorial problems which I would most like to see solved," *Combinatorica*, 1(1), 25–42, [doi:10.1007/BF02579174](https://doi.org/10.1007/BF02579174)). [wiki](https://en.wikipedia.org/wiki/Erd%C5%91s%E2%80%93Faber%E2%80%93Lov%C3%A1sz_conjecture) — The union of $k$ cliques of size $k$ pairwise sharing ≤1 vertex is $k$-colourable. Proved for all large $k$ in 2023 (Kang–Kelly–Kühn–Methuku–Osthus).
|
|
28
|
+
- **Erdős–Sós** (posed ~1962; stated in Erdős, P. (1964), "Extremal problems in graph theory," in *Theory of Graphs and Its Applications* (Proc. Sympos. Smolenice 1963), Czechoslovak Acad. Sci., pp. 29–36; no DOI). [Erdős list](https://www.emis.de/classics/Erdos/extrram.htm) — Any graph with more than $(k-1)n/2$ edges contains every tree with $k$ edges. *(Exact page range as commonly cited; volume not viewed directly.)*
|
|
29
|
+
- **Erdős–Hajnal** (Erdős, P. & Hajnal, A. (1977), "On spanned subgraphs of graphs," in *Contributions to Graph Theory and Its Applications*, Ilmenau, pp. 80–96; often cited via their (1989) "Ramsey-type theorems," *Discrete Applied Mathematics*, 25(1–2), 37–52). [wiki](https://en.wikipedia.org/wiki/Erd%C5%91s%E2%80%93Hajnal_conjecture) — Graphs omitting a fixed induced subgraph contain a clique or independent set of polynomial size $n^{c}$. Major open problem.
|
|
30
|
+
- **Erdős–Gyárfás** (Erdős & Gyárfás, 1995). [wiki](https://en.wikipedia.org/wiki/Erd%C5%91s%E2%80%93Gy%C3%A1rf%C3%A1s_conjecture) · [West](http://dwest.web.illinois.edu/openp/2powcyc.html) — Every graph with minimum degree ≥ 3 has a cycle whose length is a power of two. *(Posed at a conference; no single origin paper — citation details unverified.)*
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
Compiled 2026-07-11; verify anything you plan to cite. `ultragraph` genesis `251e6ea`.
|