Skip to main content

A lightweight deep learning library for Python featuring a fast C backend, autograd, neural network layers, optimizers, and tensor operations.

Project description

mingrad

Motor de autograd minimalista escrito en C puro, con bindings nativos a Python vía Python.h (sin ctypes, sin pybind11, sin Cython, sin NumPy como dependencia). Piénsalo como una base tipo PyTorch pero mucho más simple, pensada para servir de punto de partida para entrenar tus propios modelos sin depender de un framework grande.

Incluye tensores N-dimensionales, autograd con grafo dinámico, capas Linear, Conv2D y Conv3D, activaciones, losses (MSE, CrossEntropy), y un optimizador SGD.

Por qué existe

Para tener una base propia, entendible de punta a punta, sobre la que entrenar modelos (incluyendo datos volumétricos/3D) sin la complejidad interna de PyTorch/TensorFlow. En benchmarks propios contra NumPy+BLAS, mingrad resultó consistentemente más rápido (ver Rendimiento) gracias a un diseño de acceso a memoria cache-friendly, sin necesidad de escribir SIMD a mano.

Instalación

Requiere un compilador de C (gcc/clang) y headers de desarrollo de Python (python3-dev / python3-devel según tu distro).

pip install mingrad

O desde el código fuente:

git clone <repo>
cd mingrad
pip install .

No hay dependencias de Python en tiempo de ejecución (ni NumPy, ni nada más).

Uso básico

Tensores y autograd

import mingrad as mg

mg.init(64 * 1024 * 1024)  # reserva 64 MB de pool; llamar una sola vez, al principio

x = mg.Tensor([1.0, 2.0, 3.0, 4.0], shape=(2, 2), requires_grad=True)
y = mg.Tensor([5.0, 6.0, 7.0, 8.0], shape=(2, 2), requires_grad=True)

z = (x + y).sum()
z.backward()

print(x.grad)  # [1.0, 1.0, 1.0, 1.0]

mg.shutdown()

Operaciones disponibles sobre Tensor: +, -, *, @ (matmul), .relu(), .sigmoid(), .tanh(), .softmax(), .sum(), .mean(), .mse_loss(target), .cross_entropy_loss(targets).

Entrenar un MLP

import mingrad as mg

mg.init(64 * 1024 * 1024)

# datos de ejemplo: X (lista plana, row-major), Y (índices de clase)
X, Y, N = load_my_dataset()  # tu propio dataset

model = mg.Model()
model.add_linear(in_features=2, out_features=16).add_relu()
model.add_linear(in_features=16, out_features=3)

opt = mg.SGD(model.parameters(), lr=0.1)

for epoch in range(200):
    x = mg.Tensor(X, shape=(N, 2))
    logits = model(x)
    loss = logits.cross_entropy_loss(Y)

    model.zero_grad()
    loss.backward()
    opt.step()

    if epoch % 20 == 0:
        print(f"epoch {epoch}: loss={loss.data[0]:.4f}")

mg.shutdown()

Conv2D (imágenes)

model = mg.Model()
model.add_conv2d(in_channels=3, out_channels=16, kernel_size=3, stride=1, padding=1)
model.add_relu()

# input: (batch, in_channels, height, width), row-major
x = mg.Tensor(image_data, shape=(batch, 3, 32, 32))
out = model(x)  # (batch, 16, 32, 32)

Conv3D (datos volumétricos)

model = mg.Model()
model.add_conv3d(in_channels=1, out_channels=8, kernel_size=3, stride=1, padding=1)
model.add_relu()

# input: (batch, in_channels, depth, height, width), row-major
x = mg.Tensor(volume_data, shape=(batch, 1, 16, 16, 16))
out = model(x)  # (batch, 8, 16, 16, 16)

kernel_size, stride y padding aceptan un entero (mismo valor en todos los ejes) o una tupla explícita: kernel_size=(3, 3) para Conv2D, kernel_size=(3, 3, 3) para Conv3D.

Leer datos y gradientes

t.shape   # tupla de ints
t.data    # copia de los datos como list[float]
t.grad    # copia del gradiente como list[float] (requiere requires_grad=True y backward() ya llamado)

.data y .grad devuelven copias en cada acceso, no vistas — no hay interoperabilidad directa con NumPy/arrays en esta versión (ver limitaciones).

Rendimiento

mingrad usa un pool de memoria global (bump allocator, sin malloc/free por operación) y patrones de acceso a memoria cache-friendly (acceso contiguo, evitando iterar por columnas), auto-vectorizado por el compilador con -O3. No usa SIMD manual ni multithreading.

Benchmarks propios (forward + backward, batch representativo), comparado contra NumPy/BLAS puro y contra nnetflow (motor de autograd en Python+NumPy):

Escenario mingrad vs referencia Python/NumPy
MLP denso (64→128→10), entrenamiento completo ~7.8× más rápido
Conv2D (batch=8, 3×32×32→16 canales) ~6.4× más rápido
Conv3D (batch=4, 2×16³→8 canales) ~1.8× más rápido

Los números exactos dependen de tu CPU y del tamaño de tus tensores; estos son solo un punto de referencia obtenido durante el desarrollo, no una garantía.

Sobre -march=native: por defecto, este paquete se compila con -O3 sin -march=native. Es una decisión deliberada, no un descuido: durante el desarrollo confirmamos que -march=native (con AVX-512) causaba una regresión de rendimiento de más de 4× específicamente en Conv3D en el hardware de prueba, además del riesgo general de que un binario compilado con -march=native puede crashear (SIGILL) si se ejecuta en una CPU distinta a la que compiló. Si sabés que tu caso de uso se beneficia y tu hardware de build/deploy es el mismo, podés recompilar vos mismo con esa flag — pero medí antes y después, no asumas que es una mejora.

Limitaciones

Léelas antes de usar esto en algo importante.

  • El pool de memoria no libera tensores individuales. No hay recolector de basura ni referencia-conteo por tensor: cada tensor que creás (incluyendo resultados intermedios de operaciones) ocupa espacio en el pool hasta que llamás mg.reset_pool() o mg.shutdown(). Un loop de entrenamiento largo sin resetear el pool eventualmente lo agota.

  • Si el pool se queda sin memoria, el proceso aborta (SIGABRT), no lanza una excepción Python. Esto es intencional en el motor en C (preferimos fallar ruidosamente a corromper memoria), pero significa que no podés capturarlo con try/except — tu programa Python entero termina. Dimensioná mg.init(pool_bytes) con margen, y en producción considerá correr el entrenamiento en un subproceso si necesitás aislar este tipo de fallo.

  • mg.reset_pool() invalida todos los handles existentes sin avisar, y usar un handle viejo después de un reset puede devolver datos de OTRO tensor en silencio, sin ningún error. Esto es una consecuencia de cómo se reciclan los índices de handles al hacer reset — no es un caso raro, lo reproducimos fácilmente durante el desarrollo. Nunca conserves una referencia a un Tensor/Model creado antes de un reset_pool(). Si necesitás resetear memoria entre epochs, hacelo solo cuando no haya ningún tensor vivo que te importe (por ejemplo, recreando el modelo también), o simplemente usá un pool lo bastante grande y no resetees.

  • No es thread-safe. El estado interno (tabla de handles, último error, el pool mismo) es completamente global sin ningún lock. Usar mingrad desde más de un thread de Python al mismo tiempo es una condición de carrera, aunque el GIL de Python evite corrupción de memoria a nivel de bytecode individual.

  • .data y .grad devuelven copias en listas Python planas, no arrays de NumPy ni vistas de memoria. Para tensores grandes esto tiene un costo notable de conversión en cada lectura. No hay soporte de __array_interface__ ni buffer protocol en esta versión.

  • Sin GPU. Todo corre en CPU.

  • Sin serialización de modelos. No hay save()/load(); si necesitás persistir un modelo entrenado, tenés que extraer model.parameters() (cada uno con .data) y guardarlos vos mismo (por ejemplo a JSON), y reconstruir el modelo + cargar los datos a mano al volver a levantarlo.

  • Sin batching automático, sin DataLoader, sin manejo de datasets. Vos armás los batches y se los pasás como listas planas.

  • Model solo cubre arquitecturas secuenciales (una capa tras otra). No hay soporte para ramas, skip-connections (tipo ResNet), o cualquier grafo que no sea una cadena lineal. Para eso hay que operar directamente sobre Tensor y construir el grafo a mano con las operaciones sueltas (+, @, etc.).

  • Compilación probada en Linux con gcc. setup.py intenta detectar Windows y usar flags de MSVC, pero no fue probado ahí. macOS con clang debería funcionar (misma familia de flags que gcc) pero tampoco fue verificado extensivamente.

  • Sin validación exhaustiva de shapes en todos los casos límite. El motor de autograd fue validado contra gradiente numérico (diferencias finitas) para todas las operaciones sobre los casos de test incluidos, pero eso no cubre cada combinación posible de shapes, dtypes, o tamaños extremos.

Estructura del paquete

mingrad/
├── __init__.py       # API Python ergonómica (Tensor, Model, SGD)
├── _mingrad           # módulo de extensión compilado (.so)
└── csrc/              # código fuente en C (motor + bindings)
    ├── mingrad.h       # API del motor en C puro
    ├── mingrad_api.h/c # capa de handles, para exponer a otros lenguajes
    ├── _mingrad.c      # módulo de extensión Python (Python.h)
    └── *.c             # tensor, ops, losses, capas, conv2d/3d, optimizador

Si preferís usar el motor directamente desde C (sin Python), mingrad.h es la API completa; mingrad_api.h es una capa simplificada basada en handles enteros, pensada originalmente para bindings pero utilizable también desde C directamente.

Tests

El motor de autograd fue validado con tests de gradiente numérico (diferencias finitas contra el gradiente analítico) para cada operación, incluyendo Conv2D y Conv3D con distintos stride/padding. Estos tests están en tests/ y se compilan directo con gcc contra mingrad/csrc/, sin depender de Python. Los fuentes del motor no necesitan Python.h — solo _mingrad.c (el binding) lo requiere, así que se excluye de este build:

gcc -std=c11 -O2 -Imingrad/csrc \
  mingrad/csrc/arena.c mingrad/csrc/shape.c mingrad/csrc/tensor.c mingrad/csrc/ops.c \
  mingrad/csrc/losses.c mingrad/csrc/layers.c mingrad/csrc/optim.c \
  mingrad/csrc/conv2d.c mingrad/csrc/conv3d.c \
  tests/test_grad_check.c -o test_grad_check -lm
./test_grad_check

Licencia

Ver LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mingrad-0.1.0.tar.gz (45.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mingrad-0.1.0-cp314-cp314-android_24_arm64_v8a.whl (109.7 kB view details)

Uploaded Android API level 24+ ARM64 v8aCPython 3.14

File details

Details for the file mingrad-0.1.0.tar.gz.

File metadata

  • Download URL: mingrad-0.1.0.tar.gz
  • Upload date:
  • Size: 45.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: python-requests/2.34.2

File hashes

Hashes for mingrad-0.1.0.tar.gz
Algorithm Hash digest
SHA256 87aad46a3804faa369cdf806dd9a1466745486113398a3b04fca1c3b6ca973d7
MD5 cbf72ee892fea669b91147b06ae61f83
BLAKE2b-256 254fd3e02310cd308a9d9f3722dd335d982adf635dffc7cf230d21f618920281

See more details on using hashes here.

File details

Details for the file mingrad-0.1.0-cp314-cp314-android_24_arm64_v8a.whl.

File metadata

File hashes

Hashes for mingrad-0.1.0-cp314-cp314-android_24_arm64_v8a.whl
Algorithm Hash digest
SHA256 1d203442b340e7f66a2a22381d74f53dc20ecdaca382c641bf1e0a3ca33f86d4
MD5 bf73ca257841268f46cdff81566c9b43
BLAKE2b-256 fc5d3eed4a42466dd4455f9a76291f5f3ae90f4ded6b34c113324c9f1c66dbca

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page