Documentation
Versions
Every release keeps its own copy of the documentation, so a link to a particular version does not change meaning when a later one is published. This page is the index, and the record of what changed between them.
v0.0.3 →
float arithmetic, OpenMP simd pragmas, mini-batch gradient accumulation. Engine builds; tests and bindings do not.
v0.0.2 →
The flat-memory rewrite. Fast, much smaller in scope, and the only release with working Python bindings.
v0.0.1 →
One C++17 translation unit. Mini-batching, dropout, L2, softmax, model persistence — and 112 passing tests.
These pages previously documented the flat-memory rewrite under the label v0.0.1. That was wrong: the rewrite is v0.0.2. The directory names in the repository are authoritative, and docs/vX.Y.Z now matches X.Y.Z/ for all three.
Releases
| Version | Status | Engine | Tests | Python | Documentation |
|---|---|---|---|---|---|
| v0.0.3 | Current | Builds | 91 checks, passing | Builds | overview · c++ · python · perf |
| v0.0.2 | Superseded | Builds | Build; several fail by design | Builds | overview · c++ · python |
| v0.0.1 | Superseded | Builds | 112 / 112 pass | None | overview · c++ · |
Verified on this machine with Apple clang: v0.0.1 under -std=c++17 -Wall -Wextra, v0.0.2 and v0.0.3 under -std=c++20.
Which One To Use
- Training a real model in C++ → v0.0.1
- It is the only release with mini-batching and dropout, L2, a validation split, seeded reproducibility, multi-class softmax and model save/load — and the only one whose test suite passes. Its limitation is speed, not capability.
- Calling it from Python → v0.0.2
- The only release whose pybind11 module builds. Per-sample SGD only, and none of v0.0.1’s training features.
- Working on the engine → v0.0.3
- The current line of development: flat memory,
float, SIMD pragmas, batching. It compiles and trains, but has no working tests, so treat it as unverified.
What Changed, Release by Release
Every row is a property of the engine; every cell is what that release does with it. Cells in bold mark where a release changed the previous answer.
Architecture and memory
| v0.0.1 | v0.0.2 | v0.0.3 | |
|---|---|---|---|
| Standard | C++17 | C++20 | C++20 |
| Layout | one translation unit, main.cpp | headers under core/ | unchanged |
| Object depth | NeuralNetwork → Layer → Neuron | Network → Layer → flat vectors | unchanged |
| Weight storage | one vector<double> per neuron | one flat vector per layer, neuron-major | unchanged |
| Scalar type | double | double | float |
| Dispatch | enum class + switch | raw function pointers | unchanged |
| Layer ownership | by value in a vector<Layer> | raw owning pointers (unsafe to copy) | unchanged |
| Heap traffic per pass | several allocations per layer per sample | none | none |
| Translation units | one (library and demo share it) | any | one only — two duplicate-symbol |
The hot path
| v0.0.1 | v0.0.2 | v0.0.3 | |
|---|---|---|---|
| Inner-loop hint | none | #pragma GCC unroll 4 | #pragma omp simd, with reduction clauses |
| Needs a build flag? | no | no | yes — -fopenmp, or the pragmas are silently ignored |
| FP reassociation | not permitted | not permitted | permitted by the reduction clauses; results no longer reproducible |
| Restrict qualifiers | none | throughout | throughout, plus locals hoisted before each loop |
Training
| v0.0.1 | v0.0.2 | v0.0.3 | |
|---|---|---|---|
| Update granularity | mini-batch, TrainOptions::batch_size | per sample only | mini-batch, Network(..., int batches) |
| Gradients | accumulated, scaled by 1/batch | assigned, applied per sample | accumulated, scaled, then applied |
| Shuffling | yes, per epoch, on by default | no | no |
| L2 weight decay | yes (never on biases) | no | no |
| Dropout | yes, inverted, hidden layers only | no | no |
| Train / validation split | yes, taken after a shuffle | no | no |
| Loss history | TrainHistory, train and validation per epoch | a float* buffer, train only | unchanged |
| Early stopping | no (history is recorded, nothing acts on it) | no | no |
| Loss reporting | the configured loss | always squared error, whatever was configured | the configured loss |
| Epoch logging | verbose, on by default | unconditional | verbose, off by default |
| Prediction API | predict() | read neuronOutputsActivated | same — predict() is commented out |
Model capability
| v0.0.1 | v0.0.2 | v0.0.3 | |
|---|---|---|---|
| Multi-class output | yes | no | no |
| Trainable softmax | yes — fused with categorical cross entropy | no — no derivative implemented | no — the fused loss exists but nothing routes to it |
| Model save / load | yes, bit-exact, versioned plain text | no | no |
| Seed control | yes — init, shuffling and dropout masks | no | no |
| Per-layer widths | no — one width for all hidden layers | yes | yes |
| Activations | sigmoid, tanh, ReLU, linear, leaky_ReLU, softmax | + swish, gelu; linear dropped | + identity (linear, renamed) |
| Losses | MSE, BCE, CCE | + MAE, Huber, fused softmax+CCE | unchanged |
| Initializers | He (fixed) | zeros, xavier, he | unchanged |
Safety and verification
| v0.0.1 | v0.0.2 | v0.0.3 | |
|---|---|---|---|
| Test suite | 112 checks, 17 groups, all passing | builds; several fail by design | 91 checks, all passing |
| Gradient check | central-difference, ten configurations, ~1e-11 | present | not ported — gradients are pinned against hand-derived values instead |
| CI | GitHub Actions — GCC + clang, Linux + macOS, ASan/UBSan | none in-tree | none in-tree; clean under --asan locally |
| Shape checking | throws on every mismatch | none | in the Python layer; none in the C++ API |
| Layer index bounds | n/a — layers are appended | unchecked | checked — throws std::out_of_range |
| Bad configuration | rejected in the constructor (softmax without CCE, and the converse) | not rejected | rejected by the Python layer, not by the C++ API |
| Activation / derivative pairing | n/a — one enum selects both | two independent arguments, unchecked | derived from the activation — 7 of 8; softmax excluded by design |
| Copyable network | yes — layers held by value | no — double-frees | no |
Defects fixed, and carried forward
v0.0.3 fixes three of the twelve defects v0.0.2 documents, and introduces five of its own.
| Defect | v0.0.2 | v0.0.3 |
|---|---|---|
computeLoss ignores the configured loss | present (bug 1) | fixed |
Scratch pads sized inputSize × width | present (bug 7) | fixed |
| Gradients assigned, so no mini-batching | present (bug 12) | fixed — they accumulate |
| Mismatched activation / derivative compiles | present (bug 10) | partly fixed — 5 of 8 activations |
Only outputLayers[0] back-propagates | present (bug 3) | still present |
| Layer index not bounds-checked | present (bug 4) | still present |
| Unfilled slots dereferenced at init | present (bug 5) | still present |
Default Layer leaves pointers indeterminate | present (bug 6) | still present |
| No seed control | present (bug 8) | still present |
Network unsafe to copy | present (bug 11) | still present |
Non-inline lookups — two TUs will not link | — | new |
Null derivative for identity, gelu, softmax | — | new |
Cross-entropy eps = 1e-12 inert in float | — | found and fixed — now 1e-7f |
std::expf is not a standard name | — | new |
| Array-section reduction on a non-reduction loop | — | new |
The Shape of the Three Releases
- v0.0.1 — the complete one
- One C++17 translation unit, built to understand backpropagation by implementing every piece of it. It is also the most capable release: mini-batching with shuffling, L2, inverted dropout, a validation split, seeded reproducibility, multi-class softmax fused with cross entropy, and bit-exact model persistence. 112 checks pass, CI runs them on three compiler/OS combinations, and the backward pass is verified against central differences to roughly 10-11. What it is not is fast: every neuron owns its own weight vector, so the hot path chases a pointer per neuron and allocates inside the training loop.
- v0.0.2 — the rewrite
- The
Neuronclass is deleted and its state folded into flat, layer-contiguous vectors;enumdispatch becomes raw function pointers; every buffer is sized once in the constructor. Forward, backward and the update allocate nothing. Python bindings arrive with it. The trade is scope, and it is a steep one: mini-batching, dropout, L2, the validation split, save/load, multi-class and trainable softmax were all left behind. That is why the version number went down — it marks an architecture reset, not an increment. - v0.0.3 — the narrowing
- Same layout, different arithmetic over it. Every scalar halves in width so twice as much fits in cache; the unroll hints become OpenMP
simddirectives; gradients accumulate, so mini-batching returns. Two v0.0.2 defects are fixed and a third is partly addressed. The engine compiles and trains — butpython/andtests/were never touched, so the release has no automated verification at all.
Two threads run through the sequence. Each release removes an indirection the previous one paid for on every neuron on every pass: v0.0.2 removes the object and the switch, v0.0.3 removes half of every scalar and asks the compiler for vector instructions. On verification the sequence dipped and recovered: v0.0.1 ships a passing suite and CI, v0.0.2 ships a suite with known failures, and v0.0.3’s suite was unported until it was brought back to 91 passing checks — clean under AddressSanitizer and UBSan — which closed eight of the twelve defects its reference page had listed. In-tree CI is now the highest-value work outstanding.
Where Each Version Lives
| Docs | Source | Contents |
|---|---|---|
| docs/v0.0.3 | 0.0.3/ | The float engine, fully ported: core/, tests/ and a python/ package with a plotting layer. 1.2–3.2× faster than v0.0.2. |
| docs/v0.0.2 | 0.0.2/ | The flat-memory engine, with bindings and tests. |
| docs/v0.0.1 | 0.0.1/ | main.cpp (the whole library), tests/test_gradiex.cpp, a GitHub Actions workflow, and a React landing page under frontend/. |
Each release directory reports its own version inconsistently. 0.0.1/’s main.cpp header and CHANGELOG.md both say 0.1.0; 0.0.2/ and 0.0.3/ share a CHANGELOG.md whose newest entry is 0.0.2, and both report version = "0.0.1" in pyproject.toml and in python/gradiex_module.cpp. The directory names are authoritative throughout these docs. Reconciling the in-tree metadata is worth doing on the next release cut.
Version Numbering
- Patch
- Bug fixes and documentation corrections. No API change.
- Minor
- New features added in a backward-compatible way — a new activation, a new loss, a new keyword argument.
- Major
- Changes that break existing callers. While the major version is 0, the minor version may also break callers.
These releases do not follow that scheme, and the departure is deliberate. v0.0.2 removed features v0.0.1 had — a smaller API than its predecessor, not a larger one — so the number marks an architecture reset rather than an increment. v0.0.3 restores one of those features and adds none. The intent recorded in CHANGELOG.md is that the first feature-complete release after the rewrite will be numbered 0.2.0.
Publishing a New Version
Run the helper from the repository root. It copies the newest version's pages into a new directory, restamps the version throughout, marks older versions as superseded, and regenerates the release table and the version selectors on every page.
python docs/newversion.py 0.0.4
Then stamp the version in the places the script deliberately does not touch, and rebuild:
0.0.4/pyproject.toml version = "0.0.4"
0.0.4/python/gradiex_module.cpp m.attr("__version__") = py::str("0.0.4")
0.0.4/CHANGELOG.md a new entry
cd 0.0.4 && pip install -e .