HelloAI
L7 Chapter 6 🐥 🕒 12 min

Advanced Training Techniques: Gradient Checkpointing, Mixed Precision, ZeRO Offload

Engineering tricks that make 70B model fine-tuning possible on consumer cards. The toolkit that makes "big training" affordable.

A
Alai
8/25/2026

L7-02 covered the high-level parallelism. This piece: the workhorse techniques that make training memory-feasible.

Without these, training a 70B model would need a $1M+ GPU cluster. With them, a single RTX 4090 can fine-tune one.

Memory Budget Recap

Training memory = weights + gradients + optimizer state + activations.

For a 7B model in FP16:

  • Weights: 14GB
  • Gradients: 14GB
  • Adam (m, v): 28GB
  • Activations: ~30GB (depends on batch/seq)
  • Total: ~86GB

H100 has 80GB. Doesn’t quite fit. Need tricks.

Technique 1: Gradient Checkpointing

The biggest memory saver in deep nets.

Normal forward pass

Save activations at every layer (for backward):

forward: x → h1 → h2 → ... → h_L → loss
                  ↑   ↑         ↑
          all activations stored

Memory: O(L) — grows linearly with depth.

Gradient checkpointing

Save only every K-th activation. Recompute the rest during backward:

forward: save h_1, h_5, h_10, ...

backward: when computing gradient for h_3:
          recompute from h_1 → h_2 → h_3
          → use h_3 for gradient
          → discard

Memory: O(L / K) Compute: 1.3× more (the recomputation)

For a 7B model — activations drop from 30GB to ~5GB. Huge win.

In PyTorch:

from torch.utils.checkpoint import checkpoint

class CheckpointedBlock(nn.Module):
    def forward(self, x):
        return checkpoint(self._forward, x)

    def _forward(self, x):
        # Actual block computation
        return ...

Technique 2: Mixed Precision

Compute in FP16 / BF16, master weights in FP32.

from torch.cuda.amp import autocast, GradScaler

scaler = GradScaler()

for x, y in loader:
    with autocast(dtype=torch.bfloat16):
        loss = model(x, y)

    scaler.scale(loss).backward()
    scaler.step(optimizer)
    scaler.update()

Effect:

  • 2× faster (Tensor Cores love FP16)
  • ~50% memory savings on activations
  • Same final quality

BF16 vs FP16:

  • FP16: better precision, smaller range — risk of overflow
  • BF16: same range as FP32, less precision — more stable for training

Modern frontier training (LLaMA, DeepSeek) all use BF16.

Technique 3: ZeRO Offload

ZeRO Stage 3 (FSDP) shards weights / gradients / optimizer across GPUs. ZeRO Offload goes further: move some to CPU RAM.

from deepspeed import DeepSpeedConfig

config = DeepSpeedConfig({
    "zero_optimization": {
        "stage": 3,
        "offload_optimizer": {"device": "cpu"},  # Adam state on CPU
        "offload_param": {"device": "cpu"},      # Some weights on CPU
    }
})

Effect: train 7B model on 24GB GPU (4090) by offloading Adam state to CPU RAM.

Cost: ~3-5× slower (PCIe bandwidth bottleneck). Worth it: if you don’t have datacenter GPUs.

Technique 4: 8-bit Adam

Adam state is 8× larger than weights (m + v + FP32 master).

8-bit Adam (Tim Dettmers 2022) quantizes optimizer state to INT8:

import bitsandbytes as bnb

optimizer = bnb.optim.AdamW8bit(model.parameters(), lr=1e-4)

Effect: optimizer memory 4× smaller.

Used in many “fit big model on small GPU” setups.

Technique 5: PEFT (Parameter-Efficient Fine-Tuning)

The biggest trick: only train a tiny fraction of weights.

LoRA

Already covered in L4-05. Train low-rank deltas:

ΔW = A @ B  where A, B are tiny

Effect:

  • Train 0.01-0.5% of parameters
  • Optimizer state: 100-1000× smaller
  • Full fine-tune quality on most tasks

Prefix tuning

Prepend learned vectors to each layer’s input:

# Standard input: [token_1, token_2, ...]
# Prefix-tuned:    [learned_1, learned_2, ..., token_1, token_2, ...]

Even less params than LoRA. Slightly worse quality.

IA³

Element-wise scaling per layer:

output = layer(x) * learnable_vector  (per layer)

Tiny param count. Used for very-low-data scenarios.

Technique 6: Gradient Accumulation

If you want batch size 32 but only fit batch size 4:

for batch in loader:
    loss = model(batch)
    loss = loss / 8  # Scale to compensate
    loss.backward()

    if (step + 1) % 8 == 0:
        optimizer.step()
        optimizer.zero_grad()

8 mini-batches accumulate → optimizer sees gradient of effective batch size 32.

No memory increase, just slower per-step.

Technique 7: Sequence Packing

Variable-length sequences = wasted compute (padding).

Pack multiple short sequences into one long one:

Naive:
  [Hello world      <pad><pad><pad>]
  [The quick brown fox jumps over   ]
  [A    <pad><pad><pad><pad><pad><pad>]

Packed:
  [Hello world </s> The quick brown fox jumps over </s> A </s> <pad>]

With attention masking to prevent cross-sequence interference, you train on 100% useful tokens.

~2× throughput improvement when input lengths vary.

Technique 8: FlashAttention (in training too)

Same trick as inference (L7-01) but for training:

  • O(N²) → O(N) memory
  • Faster long-context training

Default in modern PyTorch via F.scaled_dot_product_attention.

Technique 9: Activation Recomputation

A variant of gradient checkpointing — for specific layers.

Common: don’t store attention map (large), recompute during backward.

# In FlashAttention's training mode
# Forward: don't materialize attention matrix
# Backward: recompute it as needed

Saves significant memory on long-context training.

Technique 10: CPU / Disk Offload

Last resort: move things to slow storage.

# DeepSpeed NVMe offload
config["zero_optimization"]["offload_optimizer"]["device"] = "nvme"
config["zero_optimization"]["offload_optimizer"]["nvme_path"] = "/mnt/nvme"

Effect: optimizer state on SSD instead of GPU/CPU. Cost: 10-30× slower than GPU-only. Worth it: research / experimentation on small budgets.

A Practical Training Recipe

Fine-tune Llama 3 8B on a single 24GB GPU:

# 1. Load 4-bit model
from transformers import BitsAndBytesConfig
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
)
model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-3-8B",
    quantization_config=bnb_config,
)

# 2. Add LoRA adapter
from peft import LoraConfig, get_peft_model
peft_config = LoraConfig(
    r=16, lora_alpha=32,
    target_modules=["q_proj", "v_proj"],
)
model = get_peft_model(model, peft_config)

# 3. Use gradient checkpointing
model.gradient_checkpointing_enable()

# 4. 8-bit Adam
import bitsandbytes as bnb
optimizer = bnb.optim.AdamW8bit(model.parameters(), lr=1e-4)

# 5. BF16 mixed precision
trainer_args = TrainingArguments(
    bf16=True,
    gradient_checkpointing=True,
    per_device_train_batch_size=1,
    gradient_accumulation_steps=16,
    ...
)

Memory used: ~20GB out of 24GB. Speed: ~1000 tokens/sec. Quality: 95%+ of full fine-tuning.

That’s QLoRA — the modern standard for low-resource fine-tuning.

When These Techniques Hurt

Each technique has costs:

TechniqueCost
Gradient checkpointing1.3× slower
Mixed precisionSome loss / stability issues if not careful
ZeRO Offload3-5× slower
8-bit AdamSmall quality risk
PEFTLimited capacity for hard tasks
Gradient accumulationSlower convergence
CPU offload10-30× slower

Combine techniques wisely — don’t apply all at once if you don’t need to.

Training Curve

What real frontier training looks like:

Step 0: model is random
Step 100k: model speaks coherent English
Step 1M: model writes good code, does math
Step 10M: model approaches frontier quality

Each "step" includes:
- forward pass
- backward pass
- optimizer step
- (with our techniques) recomputation, offloading, sync

For a 70B model, 1 step ≈ 1-5 seconds on a good cluster. 1M steps = 2-5 weeks straight.

This is why training is expensive — not just compute, but time.

💡 A truth

Training large models is the engineering Olympics of ML.

A model trained naively on H100s might cost 10M.Thesamemodelwithallthesetricksappliedwellcancost10M. The same model with **all these tricks applied well** can cost 1M.

The tricks compound: LoRA × Gradient Checkpointing × Mixed Precision × Adam 8-bit = the difference between “research toy” and “shipped product”.

If you do ML systems work — these techniques are your bread and butter. Master them and you’re indispensable.

Next recommended: L7-07 Monitoring or L7-02 Distributed Training.