Loading…
gathering yarn balls…
Cat Game Research
Frontier
Dev-log transmissions from the expedition

GRU-L3 V3a: Gradient Checkpointing, 75M Parameters, and the Path Past TF 0.75

Scaling our native GRU-L3 cat_chat from 3.3M to 75.9M parameters using A18 gradient checkpointing, sqrt-scaled gradient clipping, and a 20-epoch TF decay schedule targeting free-running generation.

The Memory Wall

Our previous GRU-L3 V3 model was a 4.8M parameter character-level RNN trained at D=512, B=64, T=1024 on a single RTX 3080 8GB. It reached 74% top-1 eval accuracy in 6 epochs with teacher forcing decayed from 1.0 to 0.75. But it plateaued hard — eval loss degraded every epoch after TF dropped below 0.95. The model didn't have enough capacity to model the free-running generation distribution.

The obvious fix was more parameters. But scaling from D=512 to D=2048 meant 16x more parameters (75.9M) and a VRAM footprint that blew past 8GB at the batch sizes we needed. The activation buffers alone — h_normed, gate_cache, combined_input — scaled linearly with batch size, sequence length, hidden dim, and layer count. Something had to give.

75.9M
Parameters (16x V3)
44%
VRAM Reduction (A18)
7.7h
Per Epoch (B=128)
1.54
Tokens/Param (Chinchilla)

A18: Segment-Wise Gradient Checkpointing

The solution was gradient checkpointing — trading compute for memory by recomputing forward activations during the backward pass instead of storing them all. We call it A18 in our optimization plan, and it's the hardest low-level GPU work we've done on the cat_chat stack.

The core idea: divide the sequence dimension T into K segments of size S = ceil(T/K). Instead of allocating activation buffers for the full sequence, allocate them for one segment. During backward, recompute the forward pass for each segment before consuming its gradients.

Full sequence T=512 (h_normed — NOT checkpointed)Seg 0Seg 1Seg 2Seg 3t: 0-127t: 128-255t: 256-383t: 384-511Checkpointed buffers (sized to S=128, one segment at a time)gate_cache [S,B,3D,L]h_raw [S,B,D,L]Recomputed per segment during backward (17% overhead at K=4)
Figure 1: A18 checkpoint segmentation. Full-T buffers (h_normed) remain allocated; checkpointed buffers shrink from T to S.

The implementation required careful offset management. Each timestep within a segment uses t_local = t % ckpt_S to index into the smaller buffers. The recompute lambda replicates the full forward GRU pipeline — reading h_normed and combined_input from full-T buffers, writing checkpointed buffers at t_local offsets. The backward pass processes segments in reverse order, recomputing each segment's forward activations before consuming them.

One buffer remains stubbornly full-T: h_normed. The classifier backward and cross-segment hidden carry need all timesteps. Segmenting it would require a backward redesign for the loss path — a future optimization.

The VRAM Math

At B=64, K=4, the savings were dramatic:

ConfigurationVRAMParamsBatch TimeNotes
V3a no checkpoint (B=64)7749 MiB75.9M8.1s/batchBaseline (A12b)
V3a A18 K=4 (B=64)4322 MiB75.9M9.5s/batch17% recompute overhead
V3a A18 K=4 (B=128)7064 MiB75.9M15.6s/batch2x throughput, fits 8GB

The 44% VRAM reduction (7749 → 4322 MiB at B=64) came from shrinking five checkpointed buffers from T to S: gate_cache, h_raw, rmsnorm_in, rmsnorm_sq, and r_h_prev. At K=4, S=128 — a quarter of the original T=512.

With that headroom, we doubled the batch size to B=128. The VRAM climbed to 7064 MiB — still under the 8192 MiB ceiling with 1.1 GB of safety margin. The batch time increased from 9.5s to 15.6s, but we process 2x more sequences per batch. Net throughput: ~4200 tokens/second vs ~3400 tokens/second at B=64 — a 22% throughput improvement with 2x the gradient stability.

Scaling Decisions: Width, Depth, Batch, Context

With freed VRAM, the question was: spend it on width (D), depth (L), batch (B), or sequence length (T)? We researched each option.

1
Width > depth for char-RNN. W&B's char-RNN study and Cahuantzi et al. (2023) found width has a much larger effect than depth for character-level language modeling. Two layers may be enough with wider networks. We chose D=2048 over L=4.
2
RNNs can't use long context. Khandelwal et al. (2018) showed word-level LSTMs only effectively use ~200 tokens of context. T=512 is already beyond the effective GRU window. Increasing T would double VRAM for negligible quality gain.
3
Large batch needs sqrt LR scaling. You et al. (SC 2019) scaled LSTM batch 64x with Linear-Epoch Gradual Warmup and square-root scaling. For Adam/NAdam, the NeurIPS 2024 Surge Phenomenon paper shows optimal LR first rises then falls as batch increases — linear scaling is wrong. We kept lr=0.002 (Karpathy's canonical char-RNN value) rather than sqrt-scaling up.
4
Chinchilla says we're 13x under-trained. Hoffmann et al. (2022) prescribe 20 tokens/param for optimal compute. At 75.9M params and 117M tokens, our ratio is 1.54 — 13x over-parameterized. We need either 1.5B tokens (13 epochs) or a smaller model. We chose more epochs.

Gradient Clip Scaling

The V3 model used clip_norm = 1.0 — the de-facto standard for Adam-type optimizers, backed by arXiv 2406.04443. But V3 had 4.8M parameters. V3a has 75.9M — 16x more. The global L2 gradient norm scales with sqrt(num_params), so the same clip threshold is effectively 4x more aggressive on the larger model.

At clip_norm = 1.0, V3a's early gradients (gn_pre ~4.5) were being scaled by 0.22 — cutting 78% of the gradient signal. At clip_norm = 2.5, the same gradients scale by 0.56 — still controlled, but with 2.5x more update magnitude. The sqrt-scaled value would be 4.0, so 2.5 is conservative.

"Global L2 norm scales with sqrt(num_params). When you scale model size 16x, you need to scale clip_norm by sqrt(16) = 4x — or you're silently throttling your gradients."
Gradient clip scaling principle

The 20-Epoch TF Decay Schedule

V3 Run 2 peaked at epoch 2 (TF=0.95, eval top1=74.04%) then degraded steadily as TF dropped. By epoch 6 (TF=0.75), eval top1 had fallen to 73.00%. The model couldn't maintain quality when weaned off teacher forcing.

V3a's 16x larger capacity may delay that plateau. More parameters means more capacity to model the free-running distribution, not just the teacher-forced one. The schedule:

# TF decay: -0.05/epoch, 20 epochs, incremental resume E0: TF=1.00 E7: TF=0.65 E14: TF=0.30 E1: TF=0.95 E8: TF=0.60 E15: TF=0.25 E2: TF=0.90 E9: TF=0.55 E16: TF=0.20 E3: TF=0.85 E10: TF=0.50 E17: TF=0.15 E4: TF=0.80 E11: TF=0.45 E18: TF=0.10 E5: TF=0.75 E12: TF=0.40 E19: TF=0.05 E6: TF=0.70 E13: TF=0.35 E20: TF=0.00 # full free-running

Each epoch runs as a separate 1-epoch session with resume = true, loading the previous checkpoint. This lets us monitor eval metrics between epochs and halt if degradation appears earlier than expected. At 7.7 hours per epoch, the full 20-epoch run takes ~6.4 days.

Epoch 0 Telemetry: First Light

The E0 run is in progress as of this post. Early telemetry (first 50 steps, 2.8% of epoch):

StepLossBPBGrad NormLRStatus
14.60486.6434.5/4.50.00000Init (log V)
93.55235.1255.7/4.90.00036Warmup, clipping active
172.94744.2522.3/2.30.00081Below clip threshold
252.67133.8542.6/2.60.00125Stable descent
332.45343.5392.6/2.60.00169Stable descent
412.28033.2901.8/1.80.00200Peak LR, no clipping
492.10263.0331.2/1.20.00200Grad norm settling

Loss is descending from the expected log(100) = 4.605 starting point. BPB dropped from 6.64 to 3.03 in 49 steps. The gradient norm settled below the clip threshold by step 41 — a sign that clip_norm = 2.5 is well-calibrated for this model size. No NaN, no Inf, no anomalies.

For comparison, V3 at the same step count was still at BPB ~4.6. V3a descends faster per optimizer step because B=128 sees 2x more tokens per batch, giving each gradient update more data to work with. The larger model's higher capacity is already showing in the steeper BPB descent.

The Data Question

Open Question
At 75.9M parameters and 117M tokens, we're at 1.54 tokens/param — 13x below the Chinchilla optimal of 20. The model is heavily over-parameterized for the current corpus. This means: (1) the model will likely overfit the training data within a few epochs, (2) eval loss may start diverging from train loss earlier than V3 did, and (3) we need substantially more dialogue data to properly train this model size. The 20-epoch TF decay run will tell us exactly when overfitting begins, which calibrates how much data we need to collect. If V3a hits its best eval at epoch 2-3 (like V3 did), we need ~10x more data before scaling further.

The telemetry from this run serves a dual purpose: training the model and measuring the data gap. If the model peaks early and degrades, that's a data problem, not an architecture problem. The fix is more dialogue data, not more parameters. If it holds steady through the TF decay, the architecture is working and we can focus on data collection for the next scale-up.

What's Next

The immediate roadmap after E0 completes:

  1. E1-E20 incremental resume with TF decay -0.05/epoch, monitoring eval at each boundary
  2. Parity control on the release build to validate A18 checkpointing end-to-end
  3. Data collection — if overfitting appears before E5, we need 500M-1.5B tokens to reach Chinchilla optimal
  4. h_normed segmentation — the last full-T buffer, requiring a classifier backward redesign
  5. FP16 training — halve weights/grads/optimizer VRAM, enabling D=3072 or B=256

The A18 checkpointing implementation, VRAM budgeting, and clip scaling are all implemented and running. The 20-epoch TF decay schedule and data gap analysis are in progress. The h_normed segmentation and FP16 training are roadmap.

← All transmissions