\documentclass[11pt,a4paper]{article} % ===== Packages ===== \usepackage[utf8]{inputenc} \usepackage[T1]{fontenc} \usepackage[margin=1in]{geometry} \usepackage{amsmath,amssymb} \usepackage{booktabs} \usepackage{graphicx} \usepackage{hyperref} \usepackage{float} \usepackage{caption} \usepackage{enumitem} \usepackage{url} \DeclareMathOperator{\Var}{Var} \title{End-to-End Training of a 1.2B Transformer with AstrAI \\ \large Data Pipeline, Distributed Training, and a BF16 Numerical Case Study} \author{AstrAI Contributors} \date{June 2026} \begin{document} \maketitle \begin{abstract} We document the end-to-end process of training a 1.2B-parameter autoregressive language model from scratch using the {\sc AstrAI} open-source framework. The pipeline covers data preprocessing (JSONL $\rightarrow$ BBPE tokenization $\rightarrow$ HDF5/mmap storage), a 24-layer GQA-SwiGLU decoder-only architecture, and distributed training with DDP/FSDP and cosine scheduling. During training, we encountered a BF16 numerical pathology: approximately 73,500 weights irreversibly locked at $1.0$ within 100k steps. We analyze this as a three-stage cascade---variance accumulation in unscaled residuals, gradient saturation at deep layers, and BF16 precision loss blocking recovery---and show that residual scaling ($\sigma_o = 0.02 / \sqrt{2L}$) on output projections reduces per-block residual variance by a factor of 48, preventing the lock-in. \end{abstract} % ====================================================================== \section{Introduction} % ====================================================================== Training a billion-parameter language model end-to-end involves far more than model architecture. Data must be preprocessed and stored efficiently, the training loop must handle distributed parallelism, gradient accumulation, checkpointing, and logging---and numerical pitfalls must be diagnosed and fixed. This paper describes the complete workflow using {\sc AstrAI}~\cite{astrai}, an open-source framework for Transformer training and inference, and highlights a BF16 precision issue encountered along the way. % ====================================================================== \section{Data Pipeline} % ====================================================================== \subsection{Preprocessing} Raw data arrives as JSONL files. The preprocessing pipeline is configured via a JSON specification that defines: \begin{itemize}[nosep] \item \textbf{Tokenization}: BBPE tokenizer (100K vocabulary) with standard special tokens. \item \textbf{Masking}: Declarative loss mask assignment per section (e.g.,~mask user input, compute loss on assistant response). \item \textbf{Packing}: Documents concatenated via \texttt{simple} (sequential), \texttt{bfd} (best-fit decreasing), or \texttt{bfd\_split} strategies. \item \textbf{Position IDs}: \texttt{none}, \texttt{doc\_reset} (per-document boundary), or \texttt{continuous}. \item \textbf{Output}: Tokenized sequences written to \texttt{.h5} or \texttt{.bin} shards, auto-split at 100M tokens per shard. \end{itemize} Samples shorter than 50~chars or longer than 2M~chars are filtered out. \subsection{Storage Backends} Two storage backends serve the DataLoader: \begin{itemize}[nosep] \item \textbf{H5Store}: HDF5-based, memory-loaded with shared-memory support for multi-worker access. \item \textbf{MmapStore}: Zero-copy memory-mapped \texttt{.bin} files shared via OS page cache. \end{itemize} A resumable distributed sampler provides seed-based shuffle with epoch/iteration resume. % ====================================================================== \section{Model Architecture} % ====================================================================== The model is a 24-layer decoder-only Transformer with Grouped Query Attention (GQA)~\cite{ainslie2023gqa} and SwiGLU feed-forward blocks~\cite{shazeer2020glu}, with Rotary Position Embedding (RoPE)~\cite{su2024roformer}. \begin{table}[H] \centering \caption{Model configuration. Total: $\sim$1.2B parameters.} \label{tab:model_config} \begin{tabular}{@{}lrlr@{}} \toprule \textbf{Parameter} & \textbf{Value} & \textbf{Parameter} & \textbf{Value} \\ \midrule Vocabulary ($V$) & 100,000 & Hidden dim ($d$) & 1,536 \\ Layers ($L$) & 24 & FFN dim ($d_{\textit{ffn}}$) & 6,912 \\ Query heads & 24 & KV heads & 4 \\ Head dim & 64 & Max length & 2,048 \\ Norm & RMSNorm ($\epsilon=10^{-5}$) & RoPE $\theta$ & 10,000 \\ \bottomrule \end{tabular} \end{table} Each decoder block $\ell$ computes: \begin{equation} \mathbf{h}_\ell = \mathbf{x}_\ell + \operatorname{GQA}\bigl(\operatorname{RMSNorm}(\mathbf{x}_\ell)\bigr), \qquad \mathbf{x}_{\ell+1} = \mathbf{h}_\ell + \operatorname{MLP}\bigl(\operatorname{RMSNorm}(\mathbf{h}_\ell)\bigr), \end{equation} where $\operatorname{MLP}(\mathbf{x}) = \mathbf{W}_{\text{down}}(\mathbf{W}_{\text{up}}\mathbf{x} \odot \operatorname{SiLU}(\mathbf{W}_{\text{gate}}\mathbf{x}))$. \subsection{Initialization} Linear weights follow $\mathcal{N}(0, 0.02)$; embeddings follow $\mathcal{N}(0, 0.02)$. The output projection $\mathbf{W}_o$ and FFN down-projection $\mathbf{W}_{\text{down}}$ use residual-scaled initialization~\cite{radford2019gpt2}: \begin{equation} \sigma_o = \sigma_{\text{down}} = 0.02 / \sqrt{2L}. \end{equation} This scaling is critical for BF16 stability (Section~\ref{sec:bf16}). % ====================================================================== \section{Training Configuration} % ====================================================================== The model is trained on next-token cross-entropy loss: \begin{equation} \mathcal{L} = -\sum_{t=1}^{T} \log P(x_t \mid x_{