★ 104,947PythonNOASSERTIONupdated Oct 2026
best for the default pick for research and most new deep learning work today
This is the default for a reason. Eager execution means your stack traces point at your own code, not a wall of graph-compilation internals — when something breaks, you're debugging your model, not the framework. The ecosystem around it (torch.compile, distributed training, ONNX export) is wider than anything else on this list, and new architectures land here first, often months before TensorFlow or Burn get a port.
The catch shows up the moment you need a custom build. Building from source wants a C++20 compiler, 10GB+ of disk, and 30 to 60 minutes, plus a maze of environment variables for CUDA, ROCm, and MKL — this isn't a pip install and move on. Windows support is visibly second-class throughout the docs, and the installed wheels are heavy, which matters if you're deploying somewhere disk-constrained. If you just want the pretrained-model workflow, Transformers below gets you there without touching any of this.
Our full take →GitHub ↗
★ 195,843C++Apache-2.0updated Jun 2026
best for teams needing production deployment to mobile, browser, or embedded via TFLite/TF.js
TensorFlow earns its spot on production deployment, not research. TFLite and TFX are genuinely built for shipping models to mobile, browser, and embedded targets in a way PyTorch still doesn't match, and tf.distribute handles large-scale distributed training without you writing the coordination code yourself.
Everything else about it carries a decade of design debt. The eager/graph duality never fully got resolved, and tf.function tracing bugs cost real debugging time that PyTorch users simply don't pay. Keras 3 splitting into its own project made the migration story worse, not better. Building from source means Bazel, and the prebuilt wheels pin to a narrow CUDA version — update your driver and you may be rebuilding from scratch. Worse, most new model architectures debut in PyTorch first and some never make it here at all, so you're often a generation behind.
Our full take →GitHub ↗
★ 64,375PythonApache-2.0updated Oct 2026
best for a high-level API when you want to write less boilerplate, now backend-agnostic across TensorFlow, PyTorch, and JAX
Keras 3 is the one abstraction layer here that actually delivers what it promises — the same model code runs on TensorFlow, JAX, or PyTorch, so you write once and pick the backend for speed or deployment later. The API surface is kept disciplined on purpose, with a real generation and versioning process behind it, which is rare for something this size.
That abstraction has a ceiling. The moment you need a backend-specific feature — TensorFlow's distribute strategies, PyTorch's autograd tricks — you drop out of Keras and the write-once pitch stops applying. Debugging an ops-level failure often means reading backend-dispatch code instead of your own model. The OpenVINO backend is inference-only and clearly an afterthought next to the other three. If you're not actually planning to switch backends, you're paying the abstraction tax for a feature you'll never use — just write the PyTorch.
Our full take →GitHub ↗
★ 161,547PythonApache-2.0updated Jun 2026
best for fine-tuning or serving pretrained transformer models without writing a training loop from scratch
If you're working with a pretrained model in Python, you end up here regardless of which framework you prefer underneath — vLLM, SGLang, Axolotl, and most of the serving and fine-tuning ecosystem build on top of Transformers' model definitions. The Pipeline API really is three lines to run inference on a Hub checkpoint, and the refusal to abstract across model files means you can read one model's forward pass without tracing through five layers of inheritance.
The Trainer class is where that discipline falls apart. It tries to own distributed training, mixed precision, PEFT, DeepSpeed, and FSDP all at once, and the result is a few thousand lines of flags that interact in ways nobody wrote down. Large parts of the model catalog are effectively unmaintained — you can load an old architecture, but don't expect a tokenizer bug in it to get fixed quickly. Installing the right CUDA and PyTorch combination is still a small ritual, and importing the library costs more memory than it should even when you need one model class. If your work is actually classical ML rather than pretrained transformers, skip straight to scikit-learn — this is overkill.
Our full take →GitHub ↗
★ 67,462PythonBSD-3-Clauseupdated Oct 2026
best for classical ML — regression, trees, clustering — where you don't need a neural net at all
scikit-learn isn't competing with the rest of this list — it's the answer when you don't need a neural net at all. Regression, trees, clustering, dimensionality reduction: the estimator contract of fit, predict, and transform is consistent enough that Pipeline and GridSearchCV compose without glue code, and the user guide actually explains where each algorithm breaks down instead of just listing parameters.
Its limits are honest ones, not bugs. The only neural model is a basic MLP, so anything past tabular-scale work means reaching for PyTorch and wiring the two together yourself. Everything runs in memory on one machine by default — out-of-core or distributed work means bringing in Dask or Spark separately. Parallelism is joblib, and it's easy to oversubscribe cores when BLAS threads stack on top of joblib workers. Minor version upgrades also tend to deprecate parameters you're relying on, so read the changelog before bumping the version in anything pinned.
Our full take →GitHub ↗
★ 15,403RustApache-2.0updated Jun 2026
best for Rust codebases wanting a native tensor and deep learning library instead of binding to Python
Burn is the only serious option if you want to train and deploy without bringing Python into the stack at all. The backend-decorator pattern is the right architectural call — implement the Backend trait once and autodiff plus kernel fusion come free, instead of reimplementing them per backend — and it genuinely runs where PyTorch can't: no_std embedded targets, and WebGPU inference that actually works in a browser rather than in a demo video.
It costs you the ecosystem. ONNX operator coverage is incomplete by the project's own admission, so a production model using anything outside common transformer or CNN ops hits a wall you either work around or fix yourself. The models repo is thin next to Transformers' catalog, so porting weights from PyTorch is a manual exercise per architecture, every time. The API still breaks: a recent internal data-type migration broke binary record formats outright, which is what adopting something pre-1.0 actually costs you. If you don't specifically need Rust, there's no reason to take this on yet.
Our full take →GitHub ↗
★ 11,716PythonApache-2.0updated Jun 2026
best for defining a custom model via a config file instead of hand-writing architecture code
Ludwig pushes the less-boilerplate idea further than Keras does — a YAML file replaces the training loop, the data pipeline, and the eval code entirely. Switching from local training to a Ray cluster is one config line, the LoRA and multi-adapter options track current research closely, and full configs get saved per run so experiments are actually reproducible without a separate tracking server.
The trade-off is the inverse of Burn's: Ludwig gives up low-level control, and it shows the moment something breaks. A failure means debugging through the YAML schema, a type registry, combiner logic, and then PyTorch stack traces with no indication of what in your config caused any of it. The full install drags in Ray, bitsandbytes, and vLLM together, and getting a clean setup on a specific GPU usually means manual version pinning the docs don't mention. Custom encoders are thinly documented, so the promise of expert control when you need it stops being true right when you need it most.
Our full take →GitHub ↗
// questions people ask
Before you choose
Should I start with PyTorch or TensorFlow?
PyTorch, unless you already have a TensorFlow production pipeline or need TFLite for mobile deployment. Most tutorials, papers, and new model releases target PyTorch first, and TensorFlow's eager/graph duality causes more debugging headaches than PyTorch's plain eager execution.
Do I need Keras if I already know PyTorch?
No. Keras exists to hide the differences between backends and cut boilerplate, but if you can already write a PyTorch training loop, Keras mostly adds a layer you have to debug through rather than code you were struggling to write anyway.
Is scikit-learn obsolete now that everyone talks about deep learning?
Not even close. Most real-world tabular data problems still don't need a neural net, and scikit-learn's trees and linear models will beat a hastily-trained deep model on a dataset with a few thousand rows. Reach for PyTorch when you actually need one, not by default.
Are Burn or Ludwig ready to replace PyTorch?
Not for most teams. Burn makes sense specifically if you need Rust and can live with thin ONNX support and a pre-1.0 API that still breaks compatibility. Ludwig makes sense if you want YAML-only model training and can accept that debugging a failure means reading PyTorch stack traces through someone else's abstraction.
If none of these fit — you need something narrower than a general framework, or your stack doesn't match what any of these do well — that's exactly the kind of gap our weekly email is built to find, matched to what you're actually building instead of what's popular this month.