ZeRO-3: don't partition frozen params during an activation-checkpoint recompute#8130
Conversation
There was a problem hiding this comment.
💡 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".
… recompute Signed-off-by: Quentin Gallouédec <gallouedec.quentin@gmail.com>
…dParameterCoordinator Signed-off-by: Quentin Gallouédec <gallouedec.quentin@gmail.com>
9d2cb7f to
d652fd9
Compare
|
Hi @qgallouedec thanks for your fix! Can you also add a UT in this PR to expose the issue? Thanks! |
Signed-off-by: Quentin Gallouédec <gallouedec.quentin@gmail.com>
|
Thanks, done Before the fix: After the fix: |
There was a problem hiding this comment.
Hi @qgallouedec,
Thank you for submitting a PR! The behavior of Z3+AC has always been a bit confusing and often thrown errors that are hard to understand for many users. This PR will significanly improves the issue.
The direction of the fix looks good to me. However, I see there is still a release-lifetime issue when the checkpointed block receives an input that does not require gradients. ZeRO only registers the module post-backward callback when that module input requires gradients, but the new recompute path skips the frozen parameter's forward release unconditionally within the graph task.
In a two-rank ZeRO-3 test with
- stage3_param_persistence_threshold=0
- no-grad input
- frozen layer followed by a trainable layer
both release-eligible frozen parameters remained AVAILABLE after the first backward. Running a second gradient-accumulation microbatch before step() confirmed that they remained gathered across microbatches and caused the coordinator accounting to diverge. They were only partitioned by the optimizer-step safety net.
Can you double check this behavior and add this no-grad-input/delayed-optimizer-step case?
|
Thanks @tohtana, confirmed. With a no-grad block input the frozen params stay Honestly, I don't understand ZeRO-3's release lifecycle deeply enough to land a clean fix for the no-grad case here (I tried, helped by Claude, a deferred-release-at- |
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:
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
CheckpointError(recomputed shape[0])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:
CheckpointErrorwith PEFT + DeepSpeed ZeRO-3 + gradient checkpointing huggingface/transformers#47254 minimal PEFT + ZeRO-3 repro.It's now the default failure in huggingface/transformers:
use_reentrant=False(PyTorch recommended) huggingface/transformers#43203 switched the gradient-checkpointing default touse_reentrant=False(Switch gradient checkpointing default to use_reentrant=False (PyTorch recommended) huggingface/trl#4811 did the same in TRL).Prior mitigation / adjacent fixes:
use_reentrant=Truefor PEFT + ZeRO-3 + gradient checkpointing (downstream guard working around this bug).non_reentrant_checkpointto support inputs require no grad #4118 + usenon_reentrant_checkpointfix requires_grad of input must be true for activation checkpoint layer in pipeline train. #4224 addednon_reentrant_checkpoint(pipeline-only; not used by the HF path); [REQUEST] How to use deepspeed.checkpointing.non_reentrant_checkpoint() properly with Stage3? #4595 shows it still fails under stage 3.use_reentrant=Truestage-3 path.