Memory and speed in training

Hugging Face Transformers · Advanced · 14 min

Out-of-memory errors and slow epochs are the main practical obstacles in fine-tuning.

Training memory has four parts: weights, gradients, optimizer state (AdamW keeps two extra values per parameter) and activations. TrainingArguments exposes the standard levers:

  • Mixed precision: bf16=True (Ampere or newer GPUs) runs most math in bfloat16; faster and lighter. fp16=True for older GPUs uses loss scaling automatically.
  • Gradient accumulation: gradient_accumulation_steps=8 with per_device_train_batch_size=2 gives an effective batch of 16 per device without the activation memory of 16.
  • Gradient checkpointing: gradient_checkpointing=True recomputes activations in the backward pass, cutting activation memory substantially for roughly 20 to 30% more compute.
  • Optimizers: optim="adamw_torch_fused" is faster; "adamw_bnb_8bit" or "paged_adamw_8bit" shrink optimizer state; "adafactor" uses even less.
  • Throughput: train_sampling_strategy="group_by_length", dataloader_num_workers, torch_compile=True, and padding-free packing for LLM training remove wasted compute on pad tokens.

A practical OOM checklist: enable bf16, halve the batch and double accumulation, turn on gradient checkpointing, shorten max_length, switch to an 8-bit optimizer, then move to LoRA or QLoRA.

Use auto_find_batch_size=True to let Trainer retry with smaller batches on OOM. Remember that the effective batch size changes the best learning rate.

Example

from transformers import TrainingArguments

args = TrainingArguments(
    output_dir="out",
    per_device_train_batch_size=4,
    gradient_accumulation_steps=8,      # effective batch 32 per device
    bf16=True,                          # mixed precision (Ampere+ GPUs)
    gradient_checkpointing=True,        # trade compute for activation memory
    optim="adamw_torch_fused",
    learning_rate=2e-5,
    warmup_steps=100,
    lr_scheduler_type="cosine",
    train_sampling_strategy="group_by_length",  # less padding per batch
    dataloader_num_workers=4,
    report_to="none",
)
eff = args.per_device_train_batch_size * args.gradient_accumulation_steps
print(eff)   # 32

Remember

  • Weights, gradients, optimizer state and activations all consume memory
  • Gradient accumulation raises effective batch size without more activation memory
  • Gradient checkpointing and 8-bit optimizers are the next levers after bf16

Watch out for

  • Raising gradient_accumulation_steps without adjusting the learning rate, then wondering why training dynamics changed.

Check yourself

  1. per_device_train_batch_size=2, gradient_accumulation_steps=16, 4 GPUs. What is the effective global batch size?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: huggingface.co/docs/transformers/grad_checkpointing