Reduce peak backward memory in GumbelSoftmaxFunction (in-place grad arithmetic)#7
Open
nakaken3013-code wants to merge 1 commit into
Open
Conversation
…rithmetic) The tail of GumbelSoftmaxFunction.backward allocated three separate (4, out, in) tensors (g_q - dot, grad_z, grad_quant_logits) on every train_step. Reuse the already-allocated g_q buffer in place for all of them instead. Operation order is kept identical, so the result is bit-for-bit unchanged (verified for fp32 and bf16). Trims peak backward memory, which matters on 12GB-class cards under NPROC>1 (see issue IST-DASLab#6).
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.
Follow-up to #6 — the in-place backward micro-optimization I offered there.
What
In
GumbelSoftmaxFunction.backward(src/quantization/gumbel_quantizer_2bit.py), the tail of the gradient computation allocated three separate(4, out, in)tensors —g_q - dot,grad_z, andgrad_quant_logits— on everytrain_step. This reuses the already-allocatedg_qbuffer in place for all of them.Why
On 2×12GB consumer GPUs (the setup in #6),
NPROC=2OOMs on the firstmlp.down_projbackward by a small margin.out × inhere is the full weight shape (e.g.down_proj,intermediate_size=17408), so three transient(4, out, in)buffers per step are non-trivial; dropping them lowers peak backward memory.Correctness
The operation order is kept identical to the original, so the result is bit-for-bit unchanged. I verified on random inputs for both fp32 and bfloat16 —
torch.equal(original, in_place)→True, max abs diff0.0— including the* scale// temperaturesplit kept as two separate ops (folding them into one*(scale/temperature)does perturb bf16 rounding, so I did not do that). The change touches only the transient gradient arithmetic; it does not alter the forward pass, the RNG replay inbackward, orgrad_scales.g_qis a freshly allocated, contiguous, non-leaf tensor local tobackward(g_soft_output.unsqueeze(0) * values.view(4, 1, 1)), so mutating it in place is safe — no saved tensor or autograd-tracked buffer is touched.Deliberately left out
I did not include the other change from #6 (generating
torch.randninlogits_dtypeinstead ofQ.dtypeinGumbelQuantizer2Bit.__init__). As you pointed out, that shifts the init RNG draw and changes results, so it's better measured on your side than bundled into a behavior-preserving PR.