Source: EveryO 6c05a06. This page preserves the reviewed upstream guide and adds learning context. Version-specific headings and historical wording remain visible; boundary notes explain newer APIs separately.
BEFORE YOU BEGIN
Follow the whole stack.
Read the architecture from NumPy upward. Each layer gives the next one a smaller, clearer contract: storage, differentiable operations, modules, optimization, and training.
Data loading feeds the training loop. Serialization saves model configuration and parameters.
A training step across the layers
- Load a batch. DataLoader yields features and targets.
- Run the model. Module calls compose differentiable tensor operations.
- Measure the loss. The loss reduces prediction errors to a scalar.
- Propagate gradients. backward() traverses the recorded graph.
- Update parameters. SGD or Adam applies the gradients; History records the epoch results.
The standard Tensor architecture described below keeps NumPy buffers in host memory. The separate eo.cuda.to_device() resident-buffer API is inference-only and does not attach autograd nodes. See the device guide for that distinction.
One update without Trainer
This complete example makes the training contract explicit. The optimizer receives model parameters, the forward pass creates a scalar loss, backward() fills the gradients, and step() applies one update.
import everyo as eo
features = eo.tensor([[1.0, 2.0]])
targets = eo.tensor([[1.0]])
model = eo.Sequential(eo.Linear(2, 1, seed=0))
optimizer = eo.SGD(model.parameters(), lr=0.01)
criterion = eo.MSELoss()
model.train()
optimizer.zero_grad()
predictions = model(features)
loss = criterion(predictions, targets)
loss.backward()
optimizer.step()
print(loss.item()) # loss from the forward pass, before the update
Call zero_grad() before each fresh update. Use model.eval() and eo.no_grad() when you want evaluation behavior without recording a new gradient graph. Trainer manages the standard epoch loop for you.
EveryO is layered so that each level depends only on the ones below it. You can read it bottom-up in an afternoon.
everyo.cli everyo.visualization
| |
+------------+--------------------------+
|
everyo.training (Trainer, callbacks, history, metrics)
|
everyo.optim (SGD, Adam) everyo.serialization (.evo archives)
| |
everyo.nn (Module, layers, activations, losses)
|
everyo.core (Tensor, operations, autograd, device, dtype)
|
everyo.backends (numpy_backend, tensorflow_backend) everyo.cuda
| |
NumPy optional native extension
Layers
everyo.core
The foundation.
dtype.py— the small set of supported NumPy dtypes, withfloat32as the default floating point type.device.py— theDeviceabstraction. Buffers always live in host memory; the device selects where operations execute. Requesting CUDA without a working extension degrades to CPU with a warning rather than failing.tensor.py— theTensortype: data, device, gradient state, operators.operations.py— every differentiable operation. Each one validates its inputs, computes the forward result (dispatching to CUDA when the tensor is on a CUDA device and a kernel exists) and records a graph node.autograd.py— the reverse-mode engine: graph nodes, an iterative topological sort, gradient accumulation and theno_gradmode flag.convolution.py— convolution and pooling. These live apart fromoperations.pybecause they need windowing helpers nothing else uses, and because the file would otherwise dwarf every other module. The forward pass is im2col, which turns a convolution into a single matrix multiplication; the backward pass is two more multiplications plus a scatter-add back into the padded image.
everyo.backends
operator_backend.py defines the common OperatorBackend contract and owns
all device dispatch and fallback decisions. numpy_backend.py is the reference
implementation of every kernel EveryO needs. tensorflow_backend.py is
optional and used for cross-checking numerics, for a Keras reference model and
for benchmarks. Importing the package never imports TensorFlow.
everyo.cuda
availability.py answers "can we run CUDA kernels right now?" without ever
raising. interface.py mirrors the NumPy kernel signatures and exposes
supports() plus unsupported_reason() so dispatch misses are observable and
testable. Direct calls fall back to NumPy unless strict=True is passed. The
kernels themselves live in the top-level cuda/ directory and are built
separately.
everyo.nn
Module owns parameters and sub-modules, and can describe itself as a plain
dictionary via get_config(). That description is what makes pickle-free
serialisation possible. Layers, activations and losses all build on it.
everyo.optim
Optimizer holds the parameter list and per-parameter state keyed by object
identity. SGD and Adam implement step().
everyo.data
Dataset is a two-method contract (__len__, __getitem__). DataLoader
batches and shuffles it, with a fast path that slices whole arrays when the
dataset is an ArrayDataset. Preprocessing follows a fit/transform split so
that scaling statistics come from training data only.
everyo.training
Trainer runs the loop; Callback observers can stop training, checkpoint,
log or reschedule the learning rate. History records one row per epoch and
exports to JSON or CSV.
everyo.serialization
An .evo file is a ZIP archive holding manifest.json (metadata plus the
architecture as JSON) and parameters.npz (saved and loaded with
allow_pickle=False). Loading rebuilds the model by looking class names up in
a registry, so opening an untrusted file cannot execute code.
Design decisions
Buffers stay in host memory. A CUDA device tag changes where kernels run, not where data lives. Transfers happen inside each kernel call. This costs bandwidth, and the benchmarks measure that cost honestly; in exchange the data model stays simple enough to read.
Images are NHWC. (batch, height, width, channels) is TensorFlow's
native layout, and EveryO uses TensorFlow as its correctness reference — so
convolution results can be compared against tf.nn.conv2d without transposing
anything, which is exactly what the test suite does.
float32 by default. Python lists and scalars become float32; a NumPy
array keeps its own precision, so float64 work stays float64. Gradient
checks in the test suite use float64 for accuracy.
Gradients are recorded only when needed. A node is created only when gradient mode is on and at least one input requires gradients, so inference costs nothing extra.
Errors name the shapes. EveryOShapeError messages state what was
attempted, which shapes were involved and what would have been valid.
Optional means optional. TensorFlow and CUDA are detected safely, report why they are unavailable, and never prevent the core from working.
Extending EveryO
To add a differentiable operation:
- Implement the forward pass in
everyo/core/operations.py. - Return the gradients for each input from the
backward_fnclosure. - Add a finite-difference check to
tests/test_autograd.py.
To add a layer:
- Subclass
Module, decorate it with@register_moduleso it can be serialised, and implementforward. - Implement
get_config()if the constructor takes arguments. - Export it from
everyo/nn/__init__.pyand the top-leveleveryo/__init__.py. - Add tests, including a save/load round trip.
Original guide reproduced under the project’s MIT license. Upstream source remains authoritative.
View the reviewed source on GitHub ↗