Attention backends and device placement

Hugging Face Transformers · Expert · 14 min

Attention kernels and placement decide whether a long-context model is fast, slow or fits at all.

Attention is the most expensive operation in a transformer. Transformers decouples the model code from the attention implementation, selected with attn_implementation at load time:

  • "sdpa" (default when supported): PyTorch's scaled_dot_product_attention, which dispatches to fused FlashAttention-style or memory-efficient kernels automatically.
  • "flash_attention_2" / "flash_attention_3": the FlashAttention packages; very fast on supported NVIDIA GPUs, require fp16/bf16, and enable padding-free training where sequences are packed without pad tokens.
  • "flex_attention": PyTorch's programmable attention for custom masks.
  • "eager": the reference Python implementation, slow but needed to return attention weights (output_attentions=True) or for debugging.

Through the attention interface you can register your own function (AttentionInterface.register) or load kernels from the Hub, and switch an existing model with model.set_attn_implementation(...).

Placement: device_map="auto" uses Accelerate to fill GPUs in order, then CPU RAM, then disk; max_memory={0: "20GiB", "cpu": "60GiB"} sets budgets. Offloaded layers work but are slow, because weights stream over PCIe every forward pass. model.hf_device_map shows the result. device_map="auto" splits layers across GPUs sequentially (pipeline-style, one GPU busy at a time); real tensor parallelism uses tp_plan="auto" under torchrun.

Benchmark before switching: gains depend on sequence length, batch size, dtype and GPU generation.

Example

import torch
from transformers import AutoModelForCausalLM

repo = "Qwen/Qwen2.5-1.5B-Instruct"

model = AutoModelForCausalLM.from_pretrained(
    repo, dtype=torch.bfloat16, attn_implementation="sdpa", device_map="auto",
    max_memory={0: "6GiB", "cpu": "24GiB"},   # spill to CPU beyond 6 GiB of GPU 0
)
print(model.config._attn_implementation)   # sdpa
print(set(model.hf_device_map.values()))   # e.g. {0} or {0, 'cpu'}

# eager attention is required to inspect attention weights
eager = AutoModelForCausalLM.from_pretrained(repo, attn_implementation="eager")
ids = torch.tensor([[1, 2, 3]])
att = eager(ids, output_attentions=True).attentions
print(len(att), att[0].shape)   # num_layers, (batch, heads, seq, seq)

# FlashAttention (needs the flash-attn package, fp16/bf16, supported GPU):
# AutoModelForCausalLM.from_pretrained(repo, dtype=torch.bfloat16,
#                                      attn_implementation="flash_attention_2")

Remember

  • attn_implementation selects sdpa, flash_attention_2/3, flex_attention or eager
  • Only eager returns attention weights; fused kernels never materialize them
  • device_map="auto" with max_memory places layers across GPUs, CPU and disk

Watch out for

  • Assuming device_map='auto' across two GPUs doubles speed; it splits layers sequentially, so only one GPU works at a time.

Check yourself

  1. You need output_attentions=True for an interpretability study. Which implementation should you load?

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

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

Builds on

Unlocks

Nothing builds on this yet.