Metadata-Version: 2.4
Name: mingrad
Version: 0.1.0
Summary: Motor de autograd minimalista en C, con bindings nativos a Python (sin ctypes/pybind11)
Author: axiol2056
License: MIT
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: C
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Development Status :: 3 - Alpha
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: author
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: license
Dynamic: license-file
Dynamic: requires-python
Dynamic: summary

# 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](#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).

```bash
pip install mingrad
```

O desde el código fuente:

```bash
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

```python
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

```python
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)

```python
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)

```python
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

```python
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](https://pypi.org/project/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:

```bash
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](LICENSE).
