In partnership with

Keep up with marketing in 5 minutes

TLDR Marketing is the free daily email with summaries of the most interesting stories in growth, martech, and digital marketing. The tactics worth stealing, minus the digging through LinkedIn.

Every issue is curated by subject-matter experts and lands in your inbox before your morning coffee. A 5-minute read, and you walk into the day already knowing what your competitors are still figuring out.

We cover the channels that move your numbers: paid, SEO, email, social, and ecommerce. Whether you work in B2B or B2C, in-house or agency-side, pick the stories that match your work.

Free, daily, and read by 330K+ marketers. Subscribe for free and let someone else do the digging.

Machine learning frameworks are remarkable pieces of software.

TensorFlow, PyTorch, JAX, and other projects have made capabilities that once required highly specialized infrastructure accessible through relatively approachable programming interfaces. They handle tensor computation, automatic differentiation, neural networks, hardware acceleration, distributed training, compilation, serialization, and increasingly complex workloads.

That capability has enabled an extraordinary amount of progress.

But mature systems also accumulate history.

Features are added. Hardware changes. New execution models emerge. Compatibility requirements grow. APIs evolve. Deployment environments multiply. Architectural decisions that made sense several years ago have to coexist with entirely different requirements today.

Eventually, a framework is no longer solving one problem. It is balancing hundreds of them.

That led me to a question:

What would a machine learning framework look like if we started from scratch today and deliberately made simplicity an architectural constraint?

That question became Nexora.

Starting With the Core

Nexora is an open source machine learning framework I have started developing around a deliberately small computational foundation.

The basic architecture is straightforward:

                Python API
                    │
                    ▼
                  Tensor
                    │
             ┌──────┴──────┐
             ▼             ▼
           Ops          Autograd
             │
             ▼
          Backend
             │
             ▼
            CPU

There is nothing particularly revolutionary about the individual pieces. Tensors, operations, automatic differentiation, and computational backends are well-established ideas.

The experiment is in how much architecture we can avoid.

Rather than beginning with every capability expected of a mature machine learning framework, Nexora begins with the smallest useful computational system and establishes clear boundaries around it.

Version 0.1 focuses on tensor computation, eager execution, automatic differentiation, basic neural-network components, optimizers, data loading, serialization, testing and a CPU backend.

That is intentionally limited.

Complexity Is Easy to Add

One lesson I keep returning to in software is that complexity usually arrives with reasonable justification.

Very few systems become complicated because somebody deliberately decides to make them complicated.

Instead, there is always another useful capability.

Add GPU support, another accelerator, distributed training, graph compilation, quantization, model serving, mobile deployment, another serialization format, and compatibility with an older API.

Individually, each decision can make sense. Collectively, they change the nature of the system.

This is not a criticism of mature frameworks. Their complexity often exists precisely because they solve difficult problems for enormous communities.

But a new project has an advantage they do not have:

It does not have to inherit their history.

Nexora can ask whether an abstraction is necessary before introducing it.

Eager Execution as the Ground Truth

One of the principles behind Nexora is that executing code and optimizing code should not require two different mental models.

A simple computation should look like this:

import nexora as nx

x = nx.tensor(3.0, requires_grad=True)

y = x ** 2
y.backward()

print(x.grad.item())
# 6.0

The operation happens immediately.

The computation graph required for automatic differentiation is created dynamically. Calling backward() traverses that graph and calculates the gradient.

There is no separate graph-building programming model for the developer to learn.

That principle becomes more important when compilation eventually enters the project.

My current view is that compilation should optimize the semantics of the eager program rather than introduce an alternative interpretation of it.

In other words:

The compiler should adapt to the programming model, not force the programming model to adapt to the compiler.

Nexora does not have that compiler today, and that is intentional.

First, the semantics need to be right.

Making Automatic Differentiation Understandable

Automatic differentiation can feel almost magical when using a modern machine learning framework.

Nexora deliberately tries to make it less magical.

Consider:

x = nx.tensor(3.0, requires_grad=True)
y = x * 2
z = y ** 2

z.backward()

Conceptually, Nexora sees something close to:

x
│
Multiply
│
y
│
Power
│
z

Each differentiable operation knows how to compute its contribution during the backward pass.

The graph is traversed in reverse, gradients are propagated to the operation's inputs and gradients reaching leaf tensors are accumulated.

This is standard reverse-mode automatic differentiation.

The important design goal is that a developer interested in Nexora's internals should be able to follow that process through the codebase without first understanding an enormous runtime.

Inspectability is part of the architecture rather than an afterthought.

Small Does Not Mean Toy

There is an important distinction between keeping something small and deliberately making it incapable.

Nexora already establishes the familiar building blocks needed to express basic neural networks:

import nexora as nx
from nexora import nn, optim

model = nn.Sequential(
    nn.Linear(784, 256),
    nn.ReLU(),
    nn.Linear(256, 10),
)

optimizer = optim.AdamW(
    model.parameters(),
    lr=1e-3,
)

The framework includes modules, parameters, layers, activation functions, losses, optimizers, data loading and model-state serialization.

But each capability has to justify its place in the core.

That distinction matters.

A framework does not become serious because its repository contains more directories. It becomes serious when the functionality it claims to provide is correct, understandable, tested, and maintainable.

Designing for Hardware Without Owning Hardware

Hardware is another area where I want Nexora to take a deliberately different architectural approach.

Version 0.1 is CPU-only.

That could look like a limitation but it is also an opportunity to establish the correct boundary before introducing accelerators.

Today the architecture is essentially:

Tensor
  │
  ▼
Operations
  │
  ▼
Backend
  │
  ▼
CPU

The backend boundary exists because it solves a real problem today: something needs to execute tensor operations.

What does not exist is a collection of empty CUDA, ROCm, Metal, WebGPU, or accelerator packages anticipating functionality that has not been implemented.

Eventually, another backend could sit beside CPU:

                 Backend
                    │
        ┌───────────┼───────────┐
        ▼           ▼           ▼
       CPU         GPU       Accelerator

But I want Nexora to learn from implementing real hardware support before declaring what a permanent hardware plugin interface should look like.

That is a broader principle behind the project:

Design for extension but do not implement speculation.

A Small Intermediate Representation

One of the longer-term ideas I find particularly interesting is an intermediate representation for Nexora.

For now, I am calling the concept Nexora IR.

A Python function such as:

def model(x, w):
    return nx.relu(x @ w)

could eventually be represented internally as something conceptually similar to:

%0 = input tensor<f32>[?,784]
%1 = parameter tensor<f32>[784,256]

%2 = matmul %0, %1
%3 = relu %2

return %3

That representation could become the boundary between Nexora's tensor semantics and future compilers or hardware backends.

But Nexora IR is not part of version 0.1, and this is another deliberate decision.

It would be easy to design an elaborate compiler architecture on paper. It is harder, and more valuable, to first establish what information the tensor runtime actually needs to represent.

The intermediate representation should emerge from those requirements.

Models Should Eventually Outlive Frameworks

Serialization raises another interesting architectural question.

A model's parameters are data. They should not require arbitrary Python object execution simply to be inspected or restored.

Nexora therefore starts with state-oriented serialization rather than treating arbitrary Python objects as the model format.

Conceptually:

model
├── metadata
└── tensors

This is deliberately modest in version 0.1.

Longer term, however, I want to explore whether Nexora can have a portable model representation containing enough information to describe computation, parameters, metadata, and runtime requirements independently of the Python framework itself.

That could eventually look something like:

Nexora Model
├── graph / IR
├── weights
├── metadata
└── runtime requirements

The interesting goal would be for another runtime to execute a Nexora model without needing the original Python training environment.

Portability would then become part of the model architecture rather than merely an export feature.

Errors Are Part of Developer Experience

Another area I want to explore is something much less glamorous than compilers or accelerators: error messages.

Machine learning involves shapes, dimensions, dtypes, devices, gradients, parameters, and increasingly complicated transformations between them.

When something goes wrong, the framework often has enough information to explain considerably more than simply reporting that an operation failed.

A matrix multiplication error, for example, should be able to explain:

ShapeMismatchError

Matrix multiplication failed.

left:  [32, 512]
right: [768, 256]

Expected:
left.shape[-1] == right.shape[-2]

Received:
512 != 768

That is not merely nicer wording.

Good errors reduce the amount of framework internals developers need to understand just to diagnose ordinary mistakes.

I increasingly think error design should be considered part of API design.

What Nexora Is Not

Nexora is not an attempt to declare that TensorFlow, PyTorch, JAX or another established framework should be replaced.

That would be an unrealistic goal for a new project, and frankly, not a particularly interesting one.

Those frameworks have enormous ecosystems, years of optimization work, production experience, hardware integrations and communities that Nexora does not have.

The more interesting experiment is architectural.

  • What happens if a machine learning framework starts with different constraints?

  • What if the goal is not to maximize the number of features?

  • What if eager execution is always the semantic ground truth?

  • What if hardware support is treated as an extension boundary?

  • What if the intermediate representation is public and deliberately small?

  • What if serialized models are designed for portability?

  • What if debugging and inspectability are treated as core capabilities?

  • And perhaps most importantly: What if every new abstraction has to earn its place?

Where Nexora Goes From Here

The immediate work is much less exciting than announcing a grand roadmap.

That is probably a good thing.

Nexora 0.1 needs to become boringly reliable.

That means expanding tests, pressure-testing automatic differentiation, checking gradients numerically, tightening dtype and broadcasting semantics, validating serialization behavior, improving documentation, measuring performance honestly and finding the places where the architecture does not survive contact with real programs.

Only after that foundation is credible does it make sense to move outward.

The longer-term direction could eventually include accelerator backends, an intermediate representation, compilation, portable models, distributed execution, transformer-oriented primitives, quantization and a hardware extension system.

But those are directions, not promises.

The architecture should evolve because real requirements demand it.

Building From the Inside Out

Nexora connects with an idea I have been thinking about more broadly: small systems are often better systems.

Not because small software automatically performs better or because large systems are inherently poorly designed.

Rather, small systems give us something increasingly valuable: the ability to reason about them.

We can understand their boundaries.

We can trace their behavior.

We can identify why an abstraction exists.

And when the system grows, we can ask whether that growth actually makes the system better.

Nexora is my attempt to apply that thinking to machine learning infrastructure.

It begins with a tensor.

Then an operation.

Then a gradient.

Everything after that has to earn its way in.