Skip to content

ZeRO-3: don't partition frozen params during an activation-checkpoint recompute#8130

Open
qgallouedec wants to merge 2 commits into
deepspeedai:masterfrom
qgallouedec:fix/zero3-frozen-param-checkpoint-recompute
Open

ZeRO-3: don't partition frozen params during an activation-checkpoint recompute#8130
qgallouedec wants to merge 2 commits into
deepspeedai:masterfrom
qgallouedec:fix/zero3-frozen-param-checkpoint-recompute

Conversation

@qgallouedec

@qgallouedec qgallouedec commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Problem

ZeRO-3 + gradient checkpointing (torch non-reentrant, the PyTorch/HF default) + any frozen params (e.g. LoRA adapters or a quantized base) crashes in backward:

torch.utils.checkpoint.CheckpointError: Recomputed values ... have different metadata ...
tensor at position N: saved {'shape': [1024]} vs recomputed {'shape': [0]}

The recompute re-fires ZeRO-3's forward hooks; the post-hook partitions params in place (param.data = torch.empty(0)). torch's checkpoint keeps a live, undetached reference to non-grad tensors (x = x.detach() if x.requires_grad else x), so freeing a frozen param shrinks the very tensor the recompute is about to validate → shape [0] → error. Trainable params are saved detached and are unaffected, which is why the bug only appears with frozen params.

Fix

In PartitionedParameterCoordinator.release_sub_module, skip partitioning a frozen (non-grad) param when a forward release fires inside a backward (forward and torch._C._current_graph_task_id() != -1), i.e. a checkpoint recompute; it is released normally by the ensuing backward. Trainable params still partition module-by-module, so full finetuning (and its memory profile) is unchanged. Correctness-preserving — the recompute runs on full-shape weights — and a no-op outside recompute.

MRE

# deepspeed --num_gpus 2 mre.py
import torch, torch.nn as nn, deepspeed
from torch.utils.checkpoint import checkpoint

D = 1024

class Block(nn.Module):
    def __init__(self):
        super().__init__()
        self.norm = nn.LayerNorm(D)   # frozen below -> recomputes to shape [0]
        self.lin = nn.Linear(D, D)    # trainable -> keeps the block on the autograd graph
    def forward(self, x):
        return self.lin(self.norm(x))

class Net(nn.Module):
    def __init__(self, n=2):
        super().__init__()
        self.blocks = nn.ModuleList(Block() for _ in range(n))
    def forward(self, x):
        for b in self.blocks:
            x = checkpoint(b, x, use_reentrant=False)   # non-reentrant (PyTorch default)
        return x

deepspeed.init_distributed()
model = Net()
for name, p in model.named_parameters():
    if "norm" in name:                # freeze the norms, as LoRA/quantization freezes the base
        p.requires_grad_(False)

config = {
    "train_micro_batch_size_per_gpu": 1,
    "optimizer": {"type": "AdamW", "params": {"lr": 1e-4}},
    "zero_optimization": {"stage": 3, "stage3_param_persistence_threshold": 0},
}
engine, *_ = deepspeed.initialize(
    model=model, model_parameters=[p for p in model.parameters() if p.requires_grad], config=config)

x = torch.randn(1, 8, D, device=engine.device, requires_grad=True)
engine.backward(engine(x).float().sum())
engine.step()
  • before: CheckpointError (recomputed shape [0])
  • after: ok

Verified on 2×H100 (torch 2.11, deepspeed 0.19.2; identical code path on master). Full finetune (no frozen params) is unaffected.

References

This is not an edge case! This has been reported many times in the wild, and is a known limitation of ZeRO-3 + non-reentrant checkpointing.

Root cause / trackers:

It's now the default failure in huggingface/transformers:

Prior mitigation / adjacent fixes:

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: acd54c5a83

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread deepspeed/runtime/zero/partitioned_param_coordinator.py Outdated
… recompute

Signed-off-by: Quentin Gallouédec <gallouedec.quentin@gmail.com>
…dParameterCoordinator

Signed-off-by: Quentin Gallouédec <gallouedec.quentin@gmail.com>
@qgallouedec qgallouedec force-pushed the fix/zero3-frozen-param-checkpoint-recompute branch from 9d2cb7f to d652fd9 Compare July 11, 2026 01:36
@delock

delock commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

Hi @qgallouedec thanks for your fix! Can you also add a UT in this PR to expose the issue? Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants