You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Because matrix multiplication is associative and distributive, the product $\mathbf{W}_1 \mathbf{W}_2$ can be precomputed as a single matrix $\mathbf{W}_{\text{comb}} \in \mathbb{R}^{d_{\text{in}} \times d_{\text{out}}}$, and the combined bias vector $\mathbf{b}_1 \mathbf{W}_2 + \mathbf{b}_2$ as $\mathbf{b}_{\text{comb}} \in \mathbb{R}^{d_{\text{out}}}$.
34
+
Because matrix multiplication is associative and distributive, the product $\mathbf{W}_1 \mathbf{W}_2$ can be precomputed as a single matrix $\mathbf{W}_{\text{comb}} \in \mathbb{R}^{d_{\text{in}} \times d_{\text{out}}}$, and the combined bias vector $\mathbf{b}_1 \mathbf{W}_2 + \mathbf{b}_2$ as $\mathbf{b}_{\text{comb}} \in \mathbb{R}^{d_{\text{out}}}$.
In standard IEEE 754 single-precision arithmetic (`float32`), numbers are encoded with 1 sign bit, 8 exponent bits (with bias 127), and 23 mantissa bits.[^ieee754-spec] This yields a dynamic range with maximum finite float $\approx (2 - 2^{-23}) \times 2^{127} \approx 3.4028 \times 10^{38}$.
98
98
99
99
Evaluating $e^{-x}$ on hardware triggers numerical hazards at extreme boundaries:
100
+
100
101
- If $x < -88.72$, $-x > 88.72$, causing $e^{-x} > 3.4028 \times 10^{38}$, which overflows to `+inf`. Evaluating $1 / (1 + \text{inf})$ can cause floating-point exception traps.
101
102
- If $x > 88.72$, $e^{-x}$ drops below the minimum positive normal float ($2^{-126} \approx 1.1755 \times 10^{-38}$), underflowing to subnormal numbers or `0.0`.
102
103
103
-
[^ieee754-spec]: IEEE 754 `float32` allocates 8 bits to the exponent ($E \in [0, 255]$). An exponent of $E=255$ is reserved for $\pm \infty$ (when mantissa is 0) and `NaN` (when mantissa is non-zero). An exponent of $E=0$ represents subnormal numbers or zero. When subnormal numbers occur, CPU pipelines may switch to microcode exception handling, slowing execution by up to 100× unless Flush-To-Zero (FTZ) and Denormals-Are-Zero (DAZ) hardware control flags are enabled.
104
+
[^ieee754-spec]: IEEE 754 `float32` allocates 8 bits to the exponent ($E \in [0, 255]$). An exponent of $E=255$ is reserved for $\pm \infty$ (when mantissa is 0) and `NaN` (when mantissa is nonzero). An exponent of $E=0$ represents subnormal numbers or zero. When subnormal numbers occur, CPU pipelines may switch to microcode exception handling, slowing execution by up to 100× unless Flush-To-Zero (FTZ) and Denormals-Are-Zero (DAZ) hardware control flags are enabled.
104
105
105
106
To guarantee complete numerical stability across the entire real number line without register overflow, TinyTorch uses a branch-stable formulation:
@@ -280,6 +281,7 @@ Because activation layers contain no internal state or learnable weights, they c
280
281
While matrix multiplications ($\mathbf{x} \mathbf{W}$) have high arithmetic intensity ($O(N^3)$ operations on $O(N^2)$ data), activation functions are **strictly memory-bandwidth bound**.
281
282
282
283
For a tensor of $N$ elements executing ReLU in 32-bit float:
284
+
283
285
- Read 4 bytes per element from DRAM into CPU/GPU registers.
284
286
- Perform 1 comparison operation in the ALU.
285
287
- Write 4 bytes per element back to the DRAM memory buffer.
Copy file name to clipboardExpand all lines: books/tinytorch/03_layers.qmd
+6-4Lines changed: 6 additions & 4 deletions
Original file line number
Diff line number
Diff line change
@@ -96,6 +96,7 @@ class Layer:
96
96
```
97
97
98
98
The `Linear` layer (also termed a *Dense* or *Fully Connected* layer) encapsulates two trainable parameter tensors:
99
+
99
100
1.**Weight Matrix ($\mathbf{W}$)** of shape $(d_{\text{in}}, d_{\text{out}})$, initialized with LeCun scaled normal noise.
100
101
2.**Bias Vector ($\mathbf{b}$)** of shape $(d_{\text{out}},)$, initialized to zeros.
101
102
@@ -105,7 +106,7 @@ import numpy as np
105
106
classLinear(Layer):
106
107
"""
107
108
Fully connected linear transformation: y = x @ W + b.
108
-
109
+
109
110
Encapsulates trainable weight and bias parameter Tensors.
110
111
"""
111
112
@@ -134,7 +135,7 @@ class Linear(Layer):
134
135
defforward(self, x: Tensor) -> Tensor:
135
136
"""
136
137
Compute affine projection: y = x @ W + b.
137
-
138
+
138
139
Args:
139
140
x: Input Tensor of shape (batch_size, in_features)
140
141
Returns:
@@ -176,6 +177,7 @@ Standard vs. Inverted Dropout Execution. During training, a random binary mask z
176
177
In classical dropout (Hinton et al., 2012), activations were multiplied by a Bernoulli mask during training, which reduced the expected activation sum by a factor of $(1 - p)$. To compensate, test-time inference required multiplying all model weights or activations by $(1 - p)$.
177
178
178
179
In production ML systems, modifying weights or executing scaling operations during inference creates severe systems overhead:
Compute mean squared error between predictions and targets.
69
-
70
+
70
71
Args:
71
72
predictions: Model output Tensor of shape (B, ...)
72
73
targets: Ground-truth Tensor of identical shape (B, ...)
@@ -82,6 +83,7 @@ class MSELoss:
82
83
```
83
84
84
85
**Properties of MSE**:
86
+
85
87
1.**Non-negativity**: $\mathcal{L}_{\text{MSE}} \ge 0$, with equality if and only if $\hat{\mathbf{y}} = \mathbf{y}$.
86
88
2.**Outlier Sensitivity**: Because errors are squared, a prediction error of $10.0$ generates a penalty 100× greater than an error of $1.0$, heavily discouraging large deviations.
87
89
@@ -157,6 +159,7 @@ Substituting back into the log-softmax formula gives the exact **Log-Sum-Exp Inv
157
159
$$\log(\text{Softmax}(\mathbf{z})_i) = (z_i - M) - \log\left(\sum_{j=1}^C e^{z_j - M}\right)$$
158
160
159
161
This formula possesses two vital numerical stability guarantees:
162
+
160
163
1.**No Overflow**: Because $z_j - M \le 0$ for all $j$, the exponential terms $e^{z_j - M} \in (0, 1]$ **never exceed 1.0**.
161
164
2.**No Underflow to Zero**: Because the maximum element satisfies $z_{\text{argmax}} - M = 0$, the sum always contains $e^0 = 1.0$, guaranteeing $\sum_{j=1}^C e^{z_j - M} \ge 1.0$. The logarithm argument is strictly bounded away from zero ($\log(\ge 1.0) \ge 0.0$), making $\log(0) \to -\infty$ mathematically impossible.
162
165
:::
@@ -165,7 +168,7 @@ This formula possesses two vital numerical stability guarantees:
165
168
deflog_softmax(x: Tensor, dim: int=-1) -> Tensor:
166
169
"""
167
170
Compute log-softmax using the Log-Sum-Exp numerical stability trick.
168
-
171
+
169
172
Args:
170
173
x: Input Tensor of unnormalized logits
171
174
dim: Dimension along which to compute log-softmax (default: -1)
@@ -201,7 +204,6 @@ Consider a large language model with vocabulary size $C = 128,000$ processing a
201
204
- Softmax reads logits $(4096 \times 128,000)$ from DRAM, computes probabilities, and writes probabilities $(4096 \times 128,000 \times 4\text{ B} \approx 2.1\text{ GB})$ back to DRAM.
202
205
- Cross-entropy reads $2.1\text{ GB}$ of probabilities from DRAM, computes logs, indexes target tokens, and writes loss.
203
206
-**Total DRAM Traffic**: Over $4.2\text{ GB}$ of high-latency memory bus traffic.
204
-
205
207
2.**Fused Log-Sum-Exp Loss Kernel**:
206
208
- The GPU kernel loads logit tiles directly into on-chip SRAM registers.
207
209
- Threads compute the maximum logit $M$ and sum-of-exponentials $\sum e^{z_j - M}$ using fast in-register warp shuffle reductions.[^warp-shuffle]
@@ -220,14 +222,14 @@ In TinyTorch (as in PyTorch), `CrossEntropyLoss` takes **raw unnormalized logits
220
222
classCrossEntropyLoss:
221
223
"""
222
224
Cross-Entropy loss combining log_softmax and Negative Log-Likelihood (NLL).
223
-
225
+
224
226
Expects unnormalized logits directly from the model.
Copy file name to clipboardExpand all lines: books/tinytorch/07_optimizers.qmd
+16-14Lines changed: 16 additions & 14 deletions
Original file line number
Diff line number
Diff line change
@@ -47,6 +47,7 @@ where $\eta > 0$ is the learning rate and $\mathbf{u}(\cdot)$ is the transformed
47
47
In high-dimensional deep learning loss surfaces, the local curvature is characterized by the Hessian matrix $\mathbf{H} = \nabla^2 \mathcal{L}(\mathbf{\theta})$. The condition number $\kappa = \frac{\lambda_{\max}}{\lambda_{\min}}$ measures the ratio of maximum to minimum curvature eigenvalues.
48
48
49
49
When $\kappa \gg 1$ (an ill-conditioned ravine):
50
+
50
51
- The gradient along the steep perpendicular direction ($\mathbf{g}_\perp$) is massive, causing vanilla SGD to oscillate violently between ravine walls.
51
52
- The gradient along the flat parallel valley floor ($\mathbf{g}_\parallel$) is tiny, causing progress toward the minimum to stall.
where $\beta \in [0, 1)$ is the momentum decay factor (typically $\beta = 0.9$).
70
71
71
72
**Two Critical Physical Properties**:
73
+
72
74
1.**High-Frequency Damping**: Successive gradient vectors along oscillating directions alternate signs ($\mathbf{g}_{\perp, t} \approx -\mathbf{g}_{\perp, t-1}$), canceling out inside the accumulator ($\sum \mathbf{g}_\perp \to \mathbf{0}$).
73
75
2.**Saddle Point Escape**: At strict saddle points or flat plateaus where local gradient $\nabla \mathcal{L} \approx \mathbf{0}$, standard SGD becomes trapped. Momentum retains stored kinetic velocity ($\mathbf{v}_t = \beta \mathbf{v}_{t-1} \ne \mathbf{0}$), rolling the parameter trajectory through flat regions without stopping.
74
76
@@ -207,24 +209,24 @@ In large-scale training systems, optimizer state buffers are often the single la
207
209
208
210
For a model with $N$ parameters, let us calculate the exact memory required to store parameters, gradients, and optimizer states:
209
211
210
-
| Precision Regime | Component | Precision & Type |Bytes / Param |7B Model Footprint |
A common systems misconception is that switching from FP32 to 16-bit Mixed Precision (FP16/BF16) cuts total training memory in half.
229
+
A common systems misconception is that switching from FP32 to 16-bit Mixed Precision (FP16/BF16) cuts total training memory in half.
228
230
229
231
In reality, because multiplying 16-bit gradients by small learning rates ($\eta = 10^{-4}$) causes underflow below the dynamic range of FP16 ($2^{-14} \approx 6 \times 10^{-5}$), adaptive optimizers **must maintain master weights and moment accumulators in full 32-bit float**. Consequently, both FP32 and AMP AdamW training require exactly **$16\text{ bytes per parameter}$** of static VRAM—excluding all activation memory.[^zero-mem][^eight-bit-adam]
Saving only model weights ($\mathbf{\theta}$) is insufficient to resume training faithfully. If optimizer momentum buffers ($\mathbf{m}, \mathbf{v}$) are discarded, the resumed optimizer starts with zero momentum, triggering massive gradient shocks and loss spikes. A production checkpoint must serialize:
248
+
248
249
1.**Model parameters** (`state_dict`).
249
250
2.**Optimizer state buffers** (momentum, variance, step counters).
250
251
3.**Learning rate scheduler state** (current decay factor and step index).
@@ -263,6 +264,7 @@ In modern AI clusters, the single-GPU `Trainer` scales horizontally across hundr
Runs in a single Python process across multiple threads. It is heavily bottlenecked by Python's Global Interpreter Lock (GIL). On every batch, GPU 0 must broadcast the model weights to all other GPUs, gather all predictions to GPU 0 to compute the loss, and scatter gradients. This causes severe **GPU 0 VRAM memory imbalance** and stalls compute.
Spawns an independent OS process per GPU (`torchrun`), completely eliminating GIL contention. Each process maintains its own independent model replica and optimizer in local GPU VRAM.
268
270
@@ -283,6 +285,7 @@ In modern AI clusters, the single-GPU `Trainer` scales horizontally across hundr
283
285
## PyTorch Systems Bridge: DDP Gradient Bucketing in C++
284
286
285
287
In PyTorch, DDP communication is handled in native C++ by `torch/csrc/distributed/c10d/reducer.cpp`:
- As backpropagation evaluates parameter gradients in reverse DAG order, the autograd engine writes gradients directly into the active bucket.
288
291
- As soon as all parameters assigned to a bucket finish computing gradients, the `Reducer` dispatches an asynchronous, non-blocking **NCCL Ring-AllReduce** kernel over NVLink or InfiniBand on a dedicated CUDA communication stream.
0 commit comments