An educational implementation of a Machine Learning and Automatic Differentiation (Autograd) framework built entirely from scratch in C.
The goal of this project is to gain a deep understanding of how modern frameworks such as PyTorch and TensorFlow work internally by manually implementing their core components: tensors, computational graphs, backpropagation, and optimization algorithms.
Note: Practical applications will be made in future versions.
MLInC is an experimental framework that currently implements:
- Automatic Differentiation (Reverse-Mode Autodiff)
- Computational Graphs
- Backpropagation
- Gradient Descent
- Multidimensional Tensors
- Basic Mathematical Operations
- Matrix Operations
- Error Handling
- Manual Memory Management
The framework provides a generic tensor structure:
typedef struct {
int ndim;
int* shape;
int size;
double* data;
} Tensor;Where:
ndimrepresents the number of dimensionsshaperepresents the tensor shapesizerepresents the total number of elementsdatastores the values in contiguous memory
Examples:
Scalar
ndim = 0
Vector
shape = {4}
ndim = 1
Matrix
shape = {3,4}
ndim = 2
tensor_create()tensor_scalar()tensor_free()
tensor_index()tensor_get()tensor_set()
tensor_add()tensor_sub()tensor_mul()tensor_div()
tensor_transpose()tensor_matmul()
Each value in a computation is represented by a graph node:
typedef struct GraphNode {
Tensor value;
Tensor grad;
Tensor extra;
struct GraphNode* left;
struct GraphNode* right;
void (*backward)(struct GraphNode*);
Operation op;
int ref_count;
} GraphNode;Each node stores:
- The computed value
- Its accumulated gradient
- The operation that generated it
- References to dependency nodes
- A local backpropagation function
add_node()sub_node()mul_node()div_node()pow_node()log_node()exp_node()
Each operation provides its own backward implementation:
Forward Pass
↓
Computational Graph
↓
Backward Pass
↓
Gradients
The framework uses:
topo(...)to generate a valid propagation order.
backward(loss)which:
- Builds a topological ordering of the graph
- Sets:
∂loss/∂loss = 1
- Traverses the graph in reverse order
- Accumulates gradients automatically
Currently the framework implements:
step(params, count, lr)Updating parameters using:
weight = weight - learning_rate × gradient
The project implements reference counting:
retain(node);
release(node);allowing node reuse while preventing memory leaks.
The framework includes a custom error system:
MLInCERRORincluding:
MLINC_NULL_POINTER_ERRORMLINC_OUT_OF_MEMORY_ERRORMLINC_INVALID_DIMENSION_ERRORMLINC_SHAPE_MISMATCH_ERRORMLINC_DIVISION_BY_ZERO_ERRORMLINC_INVALID_OPERATION_ERRORMLINC_OVERFLOW_ERRORMLINC_NAN_ERROR
Global error variable:
extern MLInCERROR mlinc_errno;The project integrates with GraphViz:
graph_export(...)allowing visualization of computational graphs generated during training.
Generated .dot files are exported to:
epochs/
and can be rendered using GraphViz.
Simple Linear Regression:
GraphNode* w = node(-3.0);
GraphNode* b = node(10.0);
GraphNode* x = node(2.0);
GraphNode* target = node(12.0);
for (int epoch = 0; epoch < 500; epoch++) {
GraphNode* wx = mul_node(w, x);
GraphNode* pred = add_node(wx, b);
GraphNode* loss = mse(pred, target);
backward(loss);
GraphNode* params[] = {w, b};
step(params, 2, 0.01);
release(loss);
}MLInC uses CMake as its build system.
- GCC (MinGW/MSYS2 on Windows)
- CMake 3.16+
git clone https://github.com/<your-username>/MLInC.git
cmake -B build
cmake --build build
This generates the executable inside the build directory.
On Windows:
./build/mlinc.exe
On Linux/macOS:
./build/mlinc
To completely rebuild/clean the project:
cmake --build build --clean-first
Use to clean the first build
Or remove the build directory and configure again:
rm -rf build
cmake -B build
cmake --build build
- Broadcasting
- Reduction Operations (
sum,mean) - ReLU
- Sigmoid
- Tanh
- Softmax
- Batch Operations
- Dense Layers (Linear)
- MLP (Multi-Layer Perceptron)
- Dataset API
- DataLoader
- Convolutions
- CNN Support
- GPU Backend
- Model Serialization
- Batch Training