Contents
At the core, a neural net is matrices, activations, error, gradients, and weight updates. You can write that in plain Python, but it hurts: every layer, every device, every checkpoint you save. An ML framework takes on tensors, automatic gradients, CPU/GPU placement, the training loop, and serialization. Below: how that stack works inside, how PyTorch, TensorFlow/Keras, JAX, tinygrad, and micrograd differ, where training ends and inference begins (including ONNX and browser WebGPU), and which path makes sense for a beginner.
Key takeaways
A framework is infrastructure, not “the neural net”. The model is math and weights; the stack gives you tensors, a graph, gradients, and devices.
micrograd demystifies autograd. tinygrad shows how the stack is built with minimal abstraction. PyTorch is the practical default for deep learning.
Training ≠ inference. Training needs backward and an optimizer; inference is load weights and get an answer. Often training stays in PyTorch, inference moves to ONNX Runtime or the browser.
Choice depends on task and hardware. CV and OCR — usually PyTorch; TPU — JAX/TensorFlow; Apple Silicon — also MLX; learning internals — tinygrad/micrograd.
Ecosystems sit on top of the core. Ultralytics, Hugging Face Transformers, timm — not replacements for the framework, but layers of models and utilities.
Why you need an ML framework
Without a stack you hand-roll matrix multiplies, storing activations for the backward pass, weight updates, batch loading, and checkpointing. With a framework it looks like a library with a clear API: tensor on device, layer, optimizer, dataloader.
Analogy: you can build an engine from bolts and steel, but for a trip you want a car with a gearbox and a dashboard. The framework is the car for the cycle “data → prediction → error → gradient → new weights”.
Inside the stack: tensor, graph, autograd
Typical architecture:
ML Framework
│
┌─────────────┼─────────────┐
↓ ↓ ↓
Tensor Autograd Models
│ │ │
↓ ↓ ↓
CPU / GPU Gradients Layers
│ │ │
└─────────────┼─────────────┘
↓
Optimizer → Training
Tensor — a multidimensional array with dtype, shape, and device. An image is often [C, H, W]; a batch is [B, C, H, W]. Neural nets live on tensors because almost every operation is bulk linear algebra.
Computational graph — a record of which operations produced a tensor (for the backward pass).
Automatic differentiation (autograd) — automatic gradient computation along that graph.
On top: loss function, optimizer (SGD, Adam, …), layers (Linear, Conv2d), a model as a composition of layers, Dataset/DataLoader, device abstraction (cpu / cuda / …), weight serialization.
Training loop:
data → model → prediction → loss → backpropagation → gradients → optimizer → new weights
micrograd and tinygrad: understand, not only call the API
micrograd (Andrej Karpathy’s idea) — a tiny scalar autograd implementation. The goal is clarity, not speed: each node holds a value and a local gradient; backward walks edges in reverse. After that section you see autograd is not magic — it is a careful graph traversal.
tinygrad — a small framework with its own minimalist, transparent-stack philosophy (not “PyTorch lite”). Useful when you want to see how an op reaches the accelerator without miles of industrial abstraction. You rarely pick it first for production CV; for learning how the ML stack is built — yes.
On fingers. A node stores a value and a local contribution to the derivative. If c = a * b, then on the backward pass the contribution to a is proportional to b, and to b proportional to a. The chain a → b → c is successive applications of the chain rule. That is what loss.backward() does in a large stack — on tensors, with memory optimizations.
PyTorch: the practical default
PyTorch became the de facto standard for deep learning in research and much of applied CV/LLM work. Key pieces: torch.Tensor, torch.nn, torch.optim, Dataset/DataLoader, autograd, CUDA, saving (state_dict, .pt).
Minimal loop:
for x, y in dataloader:
optimizer.zero_grad()
prediction = model(x)
loss = criterion(prediction, y)
loss.backward()
optimizer.step()
zero_grad clears old gradients; forward computes the prediction; criterion is the error; backward fills .grad; step updates weights. One line loss.backward() on the outside — inside: graph traversal, derivatives, memory, and kernel launches on the chosen device.
For OCR, CRNN, the YOLO ecosystem, and most vision pilots, PyTorch is a sensible start. Weights often move to ONNX for server or browser deployment next.
Saving a model is a separate skill. Usually you save a state_dict (weights), not the whole Python object: easier to version model code and load a checkpoint on another machine. .pt / .pth is a torch ecosystem convention, not a universal “any neural net” format.
TensorFlow, Keras, and JAX
TensorFlow was long the main industrial stack. Today it matters together with Keras, tf.data, model serving, and places where TF infrastructure is already in place. TensorFlow 2.x emphasized eager execution — closer to familiar imperative style.
Keras — high-level model API. Historically it merged with TF as tf.keras; Keras also evolves as a convenient layer over different backends. Simplified:
Keras → high-level model API
TensorFlow → compute and ecosystem (often as backend)
JAX — a different angle: “NumPy + differentiation + JIT + accelerators” in a functional style. jax.numpy, grad, jit, vmap — transformations of functions, not a stateful nn.Module object like classic PyTorch API. Strong in research and on TPU; steeper entry if you come from object-oriented nn.Module.
A simple JAX mental model: write the loss as a pure function of parameters and a batch, then grad(loss_fn) yields the gradient, jit speeds things via compilation, vmap vectorizes over the batch axis. Model state is usually explicit arrays/trees, not hidden buffers inside a layer object. Great for research and unusual transforms; it asks for discipline if you are used to “a layer with .parameters() inside”.
Other names and an important distinction
Brief periphery: MXNet — historically important, less common now; PaddlePaddle — its own ecosystem; OneFlow — niche scenarios; MLX — interesting on Apple Silicon (unified memory, Apple GPU).
ONNX Runtime — primarily an inference engine, not a full training framework in the PyTorch sense:
Training (PyTorch / TF / …) → ONNX → ONNX Runtime → Inference
Separately: training framework ≠ library of ready-made models.
PyTorch → tool to build and train
Ultralytics YOLO → ready detection pipeline on top of the ecosystem
HF Transformers → models and pipelines on top of frameworks
timm / Diffusers → model zoos and utilities
Confusing “we’ll install YOLO” with “we picked a framework” is a common beginner mistake.
What to pick for the task
| Task | What to consider |
|---|---|
| Understand backpropagation | micrograd |
| See how a framework is built | tinygrad |
| Computer Vision, CRNN, OCR | PyTorch |
| YOLO | ecosystem around PyTorch |
| LLM | PyTorch + Transformers |
| Research / nonstandard transforms | PyTorch / JAX |
| TPU | JAX / TensorFlow |
| Apple Silicon | PyTorch / MLX |
| Fast UI model prototype | PyTorch / Keras |
| Production inference | ONNX Runtime and other inference runtimes |
| Browser inference | ONNX → Web (Transformers.js / ORT Web + WebGPU) |
This is not a “who is best” ranking — it is matching the tool to the context.
CPU, GPU, and TPU: devices and the hardware contract
Stack placement:
Framework → CPU | GPU (CUDA / Apple / …) | TPU
- CPU — always available; often enough for learning models and debugging.
- NVIDIA CUDA — the main accelerator for
PyTorch/TensorFlowin production and research. - Apple GPU — Metal /
MPSinPyTorch, plus interest inMLXon unified memory. - AMD — depends on the stack (ROCm and builds); check support for your framework version.
- TPU — a strength of
JAX/TensorFlowin Google cloud scenarios.
Accelerator availability depends not only on the framework name but on drivers, builds, and backend. device = "cuda" is a contract with your environment, not a spell. Moving tensors and the model to a device (to(device)) must stay consistent — otherwise you get an error or a quiet speed loss.
Training vs inference and the path to hardware
Training: forward, loss, backward, gradients, optimizer.
Inference: load weights, feed data, get output — no training.
TRAINING: data → model → loss → backprop → weights
INFERENCE: data → model → result
From Python to GPU:
Python → Framework API → tensor ops → backend → CUDA/Metal/TPU runtime → hardware
A line like x @ weights can unfold into a huge amount of work on the GPU — so a “simple API” does not mean a “simple implementation”.
Browser link:
PyTorch (training) → ONNX → Transformers.js / ORT Web + WebGPU (inference in the tab)
Client-side details — WebGPU + Transformers.js; export — ONNX: from PyTorch to Runtime.
Comparison and a beginner roadmap
| Criterion | PyTorch | TF / Keras | JAX | tinygrad |
|---|---|---|---|---|
| Getting started | Very friendly | Keras easier “from zero” | Steeper curve | For the curious |
| Deep learning / CV | Excellent ecosystem | Strong where TF already lives | Research / TPU | Not the main pick |
| Research | Standard | Present | Strong | Educational |
| GPU | Mature CUDA path | Mature | Mature | Present — check status |
| TPU | Weaker focus | Yes | Yes | Not the focus |
| Learning internals | Complex codebase | Complex codebase | Its own style | Strong suit |
| Prod inference | Often via export | Own serving ecosystem | Own pipelines | Rarely the only stack |
Learning route:
Python → NumPy → network basics → micrograd → PyTorch → CNN → CRNN → Transformers → LLM
Vision branch: PyTorch → YOLO / detection → OCR.
A practical mini-project
Take handwritten digits (MNIST or your own set) and walk the loop once by hand:
- Load data and build a
DataLoaderwith batches. - Define a small CNN (
Conv→ activation → pooling → linear layer). - Write the training loop:
zero_grad→ forward → loss →backward→step. - Measure accuracy on a held-out set.
- Save a
state_dictto.pt. - In a separate script, load weights and run inference with no training.
- Mentally map the same steps to
micrograd/tinygrad: where the graph is, where the gradient is, where API “magic” hides.
Hands-on digits on the site: CNN → TrOCR.
Common mistakes
- Treating
PyTorchas “the neural net”, not the framework. - Confusing the framework with a ready model zoo (YOLO/Transformers).
- Not understanding tensor shape and batch dimension.
- Copying
backward()without a mental picture of the graph. - Assuming GPU is mandatory for every learning exercise.
- Mixing up training and inference; expecting
.ptto be universal for any runtime. - Picking
deviceat random without checking drivers.
FAQ
Where to start if the goal is document OCR?
Python + PyTorch, a compact model on crops, then export to ONNX if needed. See also compact CRNN in practice.
Do I need TensorFlow if I already have PyTorch?
Not “for the checklist”. You need it if the team/infrastructure is already on TF or you have TF services in place. Otherwise one primary training stack is easier to maintain.
Is JAX better than PyTorch?
Not “better” — different style: pure functions, jit/vmap/grad, convenience on TPU. For a classic CV pipeline PyTorch’s ecosystem usually wins.
Can you train seriously in the browser on WebGPU?
Usually no for serious training; the browser stack is tuned for inference. Training belongs on a dev machine or server.
Where does a trained model go?
Save framework weights (.pt, etc.); for portable inference — ONNX; for the tab — ORT Web / Transformers.js.
Further reading
Conclusion
A neural net is a mathematical model. A framework is infrastructure around tensors, gradients, and devices. micrograd and tinygrad teach how it works; PyTorch covers most applied training; TensorFlow/Keras and JAX remain important branches for ecosystem and accelerators; inference often lives separately — in ONNX Runtime or even in the browser. Picking a tool answers “what task, what hardware, who maintains it”, not a hunt for a fashionable logo.
Concepts (tensor, grad) → Frameworks → Models (YOLO, CRNN, LLM)
↓
Training → GPU/TPU/CPU
↓
Inference → Server | Browser (WebGPU)



Comments