Poutyne: a Keras-style training loop for plain PyTorch models
A simplified framework and utilities for PyTorch
At a glance
- What is it?
- Poutyne wraps an existing torch.nn.Module in a Model object that runs the training loop, metrics and callbacks for you. It is for PyTorch users who want Keras ergonomics without giving up their own network, optimizers or data pipeline.
- Who is it for?
- Adopt Poutyne if you already have a torch.nn.Module and want fit, evaluate, predict and callbacks without rewriting your data pipeline; it is the wrong tool if you need a custom multi-optimizer training schedule or a framework that owns the whole stack.
- Can I use it commercially?
- Yes, with conditions. LGPL-3.0 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
- Is it still maintained?
- Yes. The repository last received commits 115 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What Poutyne adds to a plain torch.nn.Module
The README describes Poutyne as "a simplified framework for PyTorch" that "handles much of the boilerplating code needed to train neural networks." That is the whole pitch, and it is narrow on purpose. You still write your own network as a torch.nn.Module, you still choose the optimizer and the loss, and you still own the tensors. What you stop writing is the loop around them: the epoch counter, the batch iteration, the zero_grad/backward/step sequence, the running loss, the validation pass, and the printing of metrics.
The audience is anyone who has used Keras and then moved to PyTorch, and misses Model.fit. The README makes that lineage explicit: Poutyne's Model handles "all the steps, stats and callbacks, similar to what Keras does," and the author notes that the API is "really similar" to Keras's model functions. If you never used Keras, the value proposition is thinner but still real: a training loop you do not have to maintain or debug.
The project has been shipping for years. The pyproject.toml classifies it as "Development Status :: 5 - Production/Stable", and the most recent release listed is v1.19.0 on 2026-06-07, with the last push to the repository on the same date. That is roughly three and a half months before this article, so the repository is not stale, but it is also not a project that pushes daily.
How the Model object runs a training step
There is no separate execution engine. Poutyne's Model is a wrapper: you hand it a network plus string names for the optimizer and loss, and it drives the standard PyTorch autograd cycle for each batch. The README example passes 'sgd' and 'cross_entropy' as strings, which means Poutyne resolves those names to PyTorch implementations internally. Metrics work the same way: batch_metrics=['accuracy'] and epoch_metrics=['f1', torchmetrics.AUROC(...)] are accumulated by the wrapper, not by your code.
The distinction between batch_metrics and epoch_metrics is the one design detail worth internalizing. Batch metrics are computed per batch and are useful for watching progress within an epoch. Epoch metrics are computed over the whole epoch and are what you would report in a paper. Poutyne also accepts a torchmetrics object directly in the epoch_metrics list, so third-party metrics that follow the torchmetrics interface drop in without an adapter.
Device placement is explicit. The README builds a torch.device from torch.cuda.is_available() and passes it to Model as device=device. Poutyne does not silently move your network or your tensors for you; if you construct the Model on CPU and your data is on GPU, that is your problem to solve. This is a deliberate difference from frameworks that manage placement themselves, and it means the wrapper stays predictable.
Callbacks are the extension point. The README points to a callbacks page in the documentation and describes them as the way to "save checkpoints, log training statistics and more." Above that sits ModelBundle, which the README says "offers automatic checkpointing, logging and more using callbacks under the hood." So the layering is: Model is the loop, callbacks hook into it, ModelBundle is a preassembled set of callbacks plus a save directory.
Installing Poutyne and training a first network
PyTorch has to be present first. The README states plainly: "Before installing Poutyne, you must have the latest version of PyTorch in your environment." Poutyne itself does not pin a torch version in the dependencies list; it lists torch unpinned alongside numpy, torchmetrics and lightning_utilities. In practice that means pip will not upgrade or downgrade your torch for you, and version mismatches are yours to catch.
The stable release installs from PyPI. The README gives both a pip form and a uv form:
pip install poutyne
# or with uv
uv add poutyneThere is also a development install from the dev branch, and a published container image:
docker pull ghcr.io/freud14/poutyne:latestThe repository Dockerfile shows what that image is built on: it starts from pytorch/pytorch:latest, copies the uv binary in from ghcr.io/astral-sh/uv:latest, and runs uv pip install --system with git+https://github.com/freud14/poutyne.git@stable. Note that the Dockerfile installs from the stable branch, not from a tagged release, while the image tag is latest. If you need reproducibility, pin the image digest rather than trusting the tag.
A first real run follows the README's classification example. You define a network, wrap it, and call fit:
from poutyne import Model
import torch
import torch.nn as nn
network = nn.Sequential(
nn.Linear(20, 100),
nn.ReLU(),
nn.Linear(100, 5)
)
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model = Model(
network,
'sgd',
'cross_entropy',
batch_metrics=['accuracy'],
device=device
)
model.fit(train_x, train_y, validation_data=(valid_x, valid_y), epochs=5, batch_size=32)What you should see is a training progress display with the loss and the accuracy metric per batch, and a validation line per epoch. Afterwards, model.evaluate(test_x, test_y) returns a loss and a tuple of metric values in the order you declared them, and model.predict(test_x) returns predictions. The README's own evaluate call destructures it as loss, (accuracy, f1score), which confirms that metric ordering is positional and worth keeping straight.
If you would rather not wire up checkpointing yourself, ModelBundle takes a directory and does it for you:
from poutyne import ModelBundle
model_bundle = ModelBundle.from_network(
'./saves/my_classification_network', network, optimizer='sgd', task='classif', device=device
)
model_bundle.train_data(train_x, train_y, validation_data=(valid_x, valid_y), epochs=5)
model_bundle.test_data(test_x, test_y)The README says everything is saved under ./saves/my_classification_network. The task='classif' argument is how the bundle picks sensible defaults for classification; a regression variant exists in the examples directory.
Where the abstraction gets in your way
String names for optimizers and losses are convenient until they are not. 'sgd' and 'cross_entropy' cover the common cases, but the moment you need an optimizer configured in a way the string shorthand does not expose, or a loss that takes extra arguments, you are back to constructing the PyTorch object yourself and passing it in. The README does not document the full set of accepted strings, so treat the shorthand as a convenience layer rather than the primary API.
The bigger limitation is architectural. Poutyne owns the training loop. Anything that requires restructuring that loop, such as alternating between two optimizers, doing gradient accumulation across batches, or training two networks adversarially in one step, does not fit the fit() shape. You can reach for callbacks, but callbacks hook into events, they do not replace the step. For research code that needs a bespoke inner loop, bare PyTorch is the honest answer and Poutyne is the wrong tool.
There is also a versioning risk that the README does not address. The stated requirement is "the latest version of PyTorch", which is a moving target. A wrapper that intercepts the autograd cycle depends on PyTorch internals staying put. The project's CI badge and the release cadence suggest this is tracked, but the README does not document a compatibility matrix, so a reader cannot tell from the documentation alone which torch versions are known to work with v1.19.0.
Finally, the README does not document rollback, downgrade paths, or what happens to a ModelBundle directory written by an older version. If you are checkpointing long runs, that is a gap you should test yourself before relying on it.
Poutyne against PyTorch Lightning and against bare PyTorch
The obvious comparison is PyTorch Lightning, and the difference is where the abstraction sits. Lightning asks you to restructure your code into a LightningModule with training_step, validation_step and configure_optimizers. Poutyne asks for almost nothing: you keep your nn.Module exactly as it is and hand it to a wrapper. That is a real trade-off. Lightning's structure is what lets it handle multi-GPU, gradient accumulation and checkpoint resumption in a framework-defined way. Poutyne's lack of structure is what makes it a five-line change to an existing script, and also what limits how much it can automate.
Against bare PyTorch, the comparison is simpler. Poutyne is the training loop you would write yourself, plus metrics, plus a callback system. If your loop is already twenty lines and you never change it, Poutyne buys you the metric accumulation and the progress display and not much else. If you find yourself copying that loop between projects and re-debugging the validation pass each time, the wrapper earns its place.
Against Keras itself, the difference is the backend. Keras owns the model definition and the training loop and runs on top of a backend it controls. Poutyne deliberately does not own the model. The README's framing, that you create your PyTorch module "as usual" and only then feed it into the Model, is the design statement: the network stays yours.
Licence and the cost of staying current
Poutyne is LGPL-3.0-or-later. The pyproject.toml declares license = { text = "LGPL-3.0-or-later" } and the classifier confirms GNU Lesser General Public License v3. LGPL is weaker than GPL in one specific way that matters here: it permits linking proprietary code against the library. For a Python library this generally means importing Poutyne in your application does not force your application under LGPL, but modifications you make to Poutyne itself carry the licence. This is not legal advice; if you are shipping a closed product, have your own counsel read the LGPL text rather than this paragraph.
Upgrade cost is low but not zero. The dependency list is short: lightning_utilities, numpy, torch, torchmetrics. Everything else is optional and opt-in, including colorama, scikit-learn, tensorboard, tensorboardX, torchvision, pandas and mlflow. That optional-dependency design means you only pay for what you import. The maintenance surface is therefore mostly torch and torchmetrics version drift, plus whatever the callbacks touch.
The release history shows the project does not move fast. v1.17.4 landed on 2025-05-05, then v1.18.0 and v1.19.0 both on 2026-06-07, thirteen months later. Two releases in a single day suggests a coordinated change rather than incremental drift. If you pin Poutyne, expect long stretches between versions and plan to test against new PyTorch releases yourself rather than waiting for a Poutyne release to tell you it is fine.
Editorial conclusion
Adopt Poutyne if you already have a torch.nn.Module and want fit, evaluate, predict and callbacks without rewriting your data pipeline; it is the wrong tool if you need a custom multi-optimizer training schedule or a framework that owns the whole stack. Before committing, verify that your PyTorch version matches what the current release expects, that the LGPL-3.0-or-later terms fit how you distribute your code, and that the callbacks you need exist in the documentation rather than assuming a Keras callback will port over.
Frequently asked questions
Does Poutyne replace PyTorch?
No. Poutyne is a wrapper around PyTorch, not a replacement. You still define your network as a torch.nn.Module and still install PyTorch first, as the README requires before installing Poutyne.
What Python version does Poutyne need?
The README states Poutyne is compatible with Python >= 3.10, and pyproject.toml sets requires-python = ">=3.10" with classifiers through Python 3.14.
How do I install Poutyne with uv?
The README gives uv add poutyne for the stable release and uv add git+https://github.com/freud14/poutyne.git@dev for the development version.
Is there a Docker image for Poutyne?
Yes. The README lists docker pull ghcr.io/freud14/poutyne:latest, and the repository Dockerfile builds on pytorch/pytorch:latest and installs Poutyne from the stable branch with uv.
What licence does Poutyne use?
Poutyne is LGPL-3.0-or-later according to pyproject.toml, and the classifier lists GNU Lesser General Public License v3.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/freud14-poutyne)