fix(parametrize): cache-gate hooks leak the global parametrize cache under activation checkpointing (all dequantized params retained)#1999
Open
pjordanandrsn wants to merge 1 commit into
Conversation
…tion checkpointing use_reentrant=False checkpointing aborts its backward recompute mid-forward by design (early stop) once the last needed activation is rematerialized. A plain forward hook is skipped for the module holding that last save, so the global parametrize._cache_enabled counter leaked +1 per checkpointed region per step; once stuck above zero the cache is never cleared again and every dequantized parameter is retained for the rest of training -- a memory leak of the full dequantized model size (4x the packed 4-bit bytes; e.g. ~53 GiB for a 30B MoE, which can never fit a 24 GB device). Register the disable hook with always_call=True so it also runs on the aborted recompute, and clamp the decrement at zero (a negative counter is truthy, so 'if not P._cache_enabled' would stop clearing forever). Regression tests cover the checkpoint-early-stop shape and the clamp. Co-authored-by: Claude <noreply@anthropic.com>
3fbde4f to
98989eb
Compare
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
replace_parameter_4bit/replace_parameter_4bit_prequantizedregister aforward_pre_hook/forward_hookpair that gates torch's global parametrizationcache by counter: pre-hook does
parametrize._cache_enabled += 1, post-hookdecrements and clears
parametrize._cachewhen it reaches 0.Activation checkpointing with
use_reentrant=False(the recommended mode, andwhat HF
gradient_checkpointing_enableuses) aborts its backward recomputemid-forward by design — early stop raises an internal exception the moment
the last needed activation has been rematerialized. A plain
forward_hookisskipped when the forward is interrupted, so for the module holding the last
recomputed save the pair fires pre-only: the global counter leaks +1 per
checkpointed region per step and never returns to zero. From then on the cache
is permanently enabled and never cleared — every dequantized parameter the
model touches stays resident for the rest of training, i.e. a memory leak of
the full dequantized model size (4× the packed 4-bit bytes).
Observed in the wild training MoE models whose experts are quantized via this
path with gradient checkpointing on:
max_activeregardless of store); plain resident training OOMs a 16 GB card at step 0 with the leak and fits with the fix appliedand OOMs any 24 GB card at step 1 (allocated 22.37 GiB = base + ~14 blocks
of retained dequants,
Tried to allocate 384 MiBinsidebitsandbytes/nn/parametrize.py::forward → dequantize_4bit).Minimal repro of the torch-level mechanism (no bnb needed): a 3-Linear chain
under
checkpoint(..., use_reentrant=False), pre/post hooks counting on thetail module →
pre=2, post=1after one fwd/bwd.The fix
always_call=True(torch ≥ 2.0), so it alsoruns when the recompute is aborted — verified to balance the pair (2/2) under
early stop.
always_call=Truethe hook can also firewhen an earlier pre-hook raised before
_enableran; a negative counter istruthy, so
if not P._cache_enabledwould never clear the cache again.Tests
TestParametrizationCacheCounterUnderCheckpointing:test_counter_balanced_under_checkpoint_early_stop— real hook pair on thetail module of a checkpointed chain, 3 fwd/bwd steps, asserts counter == 0 and
cache empty. Fails on main, passes with the fix (verified by mutation).
test_counter_never_goes_negative— pins the clamp.CPU-only, no quantization kernels needed; runs in ~1.2 s.
Developed with Claude Code (Anthropic) — the diagnosis, fix, and regression tests were produced with AI assistance and validated by the author as described above.