Repository navigation
Gradient Checkpointing in Pure PyTorch #1700
Replies: 1 comment
|
Hi @pharringtonp19, Since you're working with QLoRA LLMs and a custom PyTorch training loop, you can incorporate gradient checkpointing without needing to switch to The important distinction is that gradient checkpointing and gradient accumulation solve different memory problems:
For your implementation, I would recommend enabling gradient checkpointing directly through the model's supported interface. 1. Enable gradient checkpointingIf your model is based on Hugging Face Transformers, you can generally enable checkpointing using: model.gradient_checkpointing_enable()Since you're using QLoRA, make sure the model is prepared appropriately for k-bit training. For example, with PEFT: from peft import prepare_model_for_kbit_training
model = prepare_model_for_kbit_training(
model,
use_gradient_checkpointing=True,
)This prepares the quantized model for adapter training and enables gradient checkpointing. If you have already configured checkpointing through 2. Disable the KV cacheOne important detail when using gradient checkpointing with decoder-only language models is disabling the KV cache during training. Add: model.config.use_cache = FalseThe KV cache is primarily useful for autoregressive generation, where previously computed keys and values are reused. During training with gradient checkpointing, it is generally unnecessary and can cause compatibility issues. You can re-enable it for inference later if needed. 3. Your training loop can remain mostly unchangedYou do not need to manually wrap every forward pass with Your existing code: logits = self.model(input_ids, attention_mask).logitscan remain as it is. The model handles checkpointing internally during the forward and backward passes. Your gradient accumulation logic can also remain unchanged. 4. One important consideration in your implementationI noticed that your code calculates logits for the entire input sequence: logits = self.model(input_ids, attention_mask).logitsBut your loss only uses the final labels = input_ids[:, -self.threshold:].contiguous()
logits = logits[:, -self.threshold-1:-1, :].contiguous()For long sequences, the full logits tensor can consume substantial GPU memory. Gradient checkpointing reduces the memory required for intermediate activations, but it does not automatically eliminate the memory required to produce and retain the full logits tensor. If your model architecture supports computing logits only for selected positions, you could investigate that optimization. Otherwise, consider reducing sequence length or physical batch size if memory remains a bottleneck. 5. A small improvement to your training loopYour current accumulation logic performs an optimizer step only when the number of batches is divisible by If the final batch group is incomplete, those accumulated gradients are never applied. You can handle this by checking whether the current batch is either a complete accumulation group or the final batch: for i, batch in enumerate(data_loader):
input_ids, logits, type_indicator, target_token = self.process_batch(
batch,
"target_token"
)
loss, log_prob_target = self.compute_loss(
input_ids,
logits,
type_indicator,
target_token
)
(loss / self.gradient_accumulation).backward()
is_accumulation_step = (
(i + 1) % self.gradient_accumulation == 0
)
is_last_batch = (i + 1) == len(data_loader)
if is_accumulation_step or is_last_batch:
self.optimizer.step()
self.scheduler.step()
self.optimizer.zero_grad()This is a simplified illustration. If the final accumulation group contains fewer batches than the configured accumulation count, you should adjust the loss scaling for that group to obtain the intended gradient average. 6. Recommended setupFor your QLoRA model, I would start with: model.config.use_cache = False
model.gradient_checkpointing_enable()
model.train()If you are using PEFT's k-bit preparation utility, use that to enable checkpointing instead of duplicating the configuration. Then retain your existing custom training loop and benchmark GPU memory usage. One final consideration: gradient checkpointing introduces additional computation during backward propagation. It should reduce memory consumption, but training may become somewhat slower. ConclusionYou can use gradient checkpointing directly with your existing custom PyTorch training loop. The simplest approach is to enable it on the Hugging Face model, disable the KV cache, and leave your forward/backward logic intact. For your particular implementation, I would also investigate the memory consumed by the full-sequence logits tensor, since your loss only uses a small number of final tokens. Hope this helps! If this answer helped or pointed you in the right direction, I'd appreciate it if you could mark it as the accepted answer so it's easier for others with the same issue to find. Also, if you found my contribution useful, I'd appreciate it if you could check out my GitHub profile, follow me, and star any repositories you find interesting. GitHub: https://github.com/Advait251206 |
Uh oh!
There was an error while loading. Please reload this page.
I was wondering if anyone had some suggestions about how to incorporate gradient checkpointing into the following code. I'm working with qlora LLMs
All reactions