Skip to content

Native training API

The native path supports a static Sequential(Linear, ReLU, ..., Linear) classifier with contiguous float32 features and parameters, integer labels, softmax cross-entropy, and Adam.

train

train(model: Module, x: ndarray, labels: ndarray, *, epochs: int = 1, batch_size: int = 128, lr: float = 0.001, shuffle: bool = True, seed: int = 42) -> NativeTrainingHistory

Train a static Linear/ReLU classifier in a compiled C control loop.

Matrix operations remain NumPy operations, preserving its optimized BLAS. The compiled path owns its Adam state and updates model parameters in place.

Source code in MiniTorch/native/trainer.py
def train(
    model: Module,
    x: np.ndarray,
    labels: np.ndarray,
    *,
    epochs: int = 1,
    batch_size: int = 128,
    lr: float = 1e-3,
    shuffle: bool = True,
    seed: int = 42,
) -> NativeTrainingHistory:
    """Train a static Linear/ReLU classifier in a compiled C control loop.

    Matrix operations remain NumPy operations, preserving its optimized BLAS.
    The compiled path owns its Adam state and updates model parameters in place.
    """
    if train_mlp is None:
        raise RuntimeError(
            "MiniTorch native training is not built. Reinstall the package from "
            "source so its Cython extension can be compiled."
        ) from _IMPORT_ERROR
    if epochs < 1 or batch_size < 1:
        raise ValueError("epochs and batch_size must be positive")

    layers = _linear_layers(model)
    features = np.ascontiguousarray(x, dtype=np.float32)
    targets = np.ascontiguousarray(labels, dtype=np.int64)
    if features.ndim != 2 or targets.ndim != 1:
        raise ValueError("x must be 2-D and labels must be 1-D")
    if len(features) != len(targets):
        raise ValueError("x and labels must contain the same number of samples")
    if features.shape[1] != layers[0].W.shape[0]:
        raise ValueError("input feature count does not match the first Linear layer")

    weights = []
    biases = []
    for layer in layers:
        if layer.W.data is None or layer.b is None or layer.b.data is None:
            raise RuntimeError("model parameter data is unavailable")
        if layer.W.data.dtype != np.float32 or layer.b.data.dtype != np.float32:
            raise TypeError("native training requires float32 model parameters")
        if not layer.W.data.flags.c_contiguous or not layer.b.data.flags.c_contiguous:
            raise ValueError("native training requires contiguous parameters")
        weights.append(layer.W.data)
        biases.append(layer.b.data)

    result = train_mlp(
        weights,
        biases,
        features,
        targets,
        epochs,
        batch_size,
        lr,
        shuffle,
        seed,
    )
    for layer, weight_grad, bias_grad in zip(
        layers,
        result["weight_gradients"],
        result["bias_gradients"],
    ):
        layer.W.grad = Variable(np.asarray(weight_grad))
        if layer.b is not None:
            layer.b.grad = Variable(np.asarray(bias_grad))
    return NativeTrainingHistory(
        losses=[float(loss) for loss in result["losses"]],
        steps=int(result["steps"]),
        samples=int(result["samples"]),
    )

NativeTrainingHistory dataclass

NativeTrainingHistory(losses: list[float], steps: int, samples: int)