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
Copy file name to clipboardExpand all lines: book/quarto/contents/vol1/backmatter/appendix_data.qmd
+54-31Lines changed: 54 additions & 31 deletions
Original file line number
Diff line number
Diff line change
@@ -185,12 +185,12 @@ Even after data arrives at the machine, we face one final hurdle: the serializat
185
185
[^fn-serialization]: **Serialization**: From Latin *serialis* (forming a series). The process of converting in-memory data structures into a byte stream for storage or transmission, and the reverse (*deserialization*). In ML pipelines, the choice of serialization format can dominate end-to-end training time; @sec-data-engineering covers pipeline design strategies that minimize this overhead.
186
186
187
187
|**Format**|**Decoding Speed (MB/s)**|**Relative CPU Decode Cost**|**Suitability**|
|**Parquet/Arrow**|`{python} SerializationCost.parquet_speed_mb_s_str`|`{python} SerializationCost.parquet_cycles_str`| High-Scale Training |
192
192
193
-
: **Serialization Overhead**: Zero-copy formats like Arrow are at least 10$\times$ faster than row-based text formats in this representative comparison because they align directly with internal memory structures. {#tbl-serialization-cost tbl-colwidths="[25,25,25,25]"}
193
+
: **Serialization Overhead**: Zero-copy formats like Arrow are at least 10$\times$ faster than row-based text formats in this representative comparison because they align directly with internal memory structures. {#tbl-serialization-cost tbl-colwidths="[21,27,27,25]"}
194
194
195
195
### Row vs. columnar formats {#sec-appdx-data-foundations-row-vs-columnar-formats-bca2}
**Scenario**: A sentiment classifier was trained on data where `{python} KLDrift.scenario_str`.
589
603
590
604
Training distribution $P_0$: `{python} KLDrift.P0_vec_str`. Serving distribution $P_t$: `{python} KLDrift.Pt_vec_str`.
591
-
592
-
`{python} KLDrift.kl_qp_eq`
593
-
594
-
`{python} KLDrift.kl_qp_calc_eq` nats
595
-
596
-
`{python} KLDrift.kl_pq_eq`
597
-
598
-
`{python} KLDrift.kl_pq_calc_eq` nats
605
+
```{python}
606
+
#| echo: false
607
+
#| output: asis
608
+
print(r"\vspace{-4mm}")
609
+
print(KLDrift.kl_qp_eq)
610
+
print(r"\vspace{-6mm}")
611
+
print(KLDrift.kl_qp_calc_eq)
612
+
print(r"\vspace{-4mm}")
613
+
print(KLDrift.kl_pq_eq)
614
+
print(r"\vspace{-6mm}")
615
+
print(KLDrift.kl_pq_calc_eq)
616
+
```
599
617
600
618
Notice the asymmetry: $\mathcal{D}_{\text{KL}}(P_0 \lVert P_t) \neq \mathcal{D}_{\text{KL}}(P_t \lVert P_0)$. The Population Stability Index (PSI) symmetrizes this:
601
-
602
-
`{python} KLDrift.psi_eq`
603
-
619
+
```{python}
620
+
#| echo: false
621
+
#| output: asis
622
+
print(r"\vspace{-4mm}")
623
+
print(KLDrift.psi_eq)
624
+
```
625
+
604
626
```{python}
605
627
#| echo: false
606
628
#| label: psi-threshold
@@ -662,12 +684,11 @@ class LogitOverflowExample:
662
684
Neural networks output *logits*[^fn-logit] (unnormalized scores), not probabilities. We convert them using Softmax:
663
685
664
686
[^fn-logit]: **Logit**: From *log* + *unit*, coined by Joseph Berkson in 1944. The logit function is the inverse of the logistic (sigmoid) function: $\text{logit}(p) = \log(p/(1-p))$. In deep learning, "logits" refers more loosely to the raw, unnormalized output of the final linear layer before any activation function is applied.
The problem is that if $z_i$ is large (for example, `{python} LogitOverflowExample.logit_val_str`), the exponential $e^{z_i}$ overflows common training and inference formats such as FP32, BF16, and FP16. FP64 can represent this specific value, but production ML kernels rarely use FP64 for softmax. The solution is to compute in log-space: the "Log-Sum-Exp" trick allows us to compute $\log(\sum e^{z_j})$ without ever calculating the massive exponentials directly, preserving numerical precision.[^fn-logsumexp]
689
+
The problem is that if $z_i$ is large (for example, `{python} LogitOverflowExample.logit_val_str`), the exponential $e^{z_i}$ overflows common training and inference formats such as FP32, BF16, and FP16. FP64 can represent this specific value, but production ML kernels rarely use FP64 for softmax. The solution is to compute in log-space: the "Log-Sum-Exp" trick allows us to compute $\log\left(\sum e^{z_j}\right)$ without ever calculating the massive exponentials directly, preserving numerical precision.[^fn-logsumexp]
669
690
670
-
[^fn-logsumexp]: **Log-Sum-Exp**: Implemented as `torch.logsumexp` in PyTorch and `scipy.special.logsumexp` in SciPy. It relies on the identity $\log(\sum e^{x_i}) = a + \log(\sum e^{x_i - a})$, where $a = \max(x_i)$. Shifted values $x_i - a$ are $\le 0$, ensuring exponentials never overflow.
691
+
[^fn-logsumexp]: **Log-Sum-Exp**: Implemented as `torch.logsumexp` in PyTorch and `scipy.special.logsumexp` in SciPy. It relies on the identity $\log\left(\sum e^{x_i}\right) = a + \log\left(\sum e^{x_i - a}\right)$, where $a = \max(x_i)$. Shifted values $x_i - a$ are $\le 0$, ensuring exponentials never overflow.
671
692
672
693
A small softmax calculation shows why this trick matters in practice, even for large but realistic logit values:
These numbers are representable in FP64 but overflow FP32 (max $\approx 3.4 \times 10^{38}$). With FP16 (max $\approx 65{,}504$), even $e^{12}$ overflows. In practice, logits of magnitude 100 are not unusual in deep networks, and FP16/BF16 is the standard training precision—so naive softmax fails routinely.
785
807
786
808
**With the trick**: Subtract $a = \max(z) =$ `{python} LogSumExp.a_str`:
0 commit comments