Skip to content

Honour every_n_layers and drop redundant offload in gradient_checkpointing_enable - #14403

Open
danielhanchen wants to merge 8 commits into
mainfrom
dh/s2-w6-gckwargs
Open

danielhanchen wants to merge 8 commits into
mainfrom
dh/s2-w6-gckwargs

Conversation

@danielhanchen

Copy link
Copy Markdown
Member

Summary

transformers 5.17 added every_n_layers and offload to gradient_checkpointing_enable(). Under Unsloth:

  • Passed inside gradient_checkpointing_kwargs (TrainingArguments on transformers < 5.17, direct calls, PEFT's prepare_model_for_kbit_training), both keys were bound into every decoder layer call by the Unsloth checkpoint shims. They were silently ignored, and every layer stayed checkpointed.
  • for_training / for_inference (every GRPO generation step) rewrote every layer's flag, so a native every_n_layers was reset after the first generation.
  • offload=True stacked save_on_cpu on top of Unsloth's own offload, copying the input Unsloth keeps on the GPU.

This wraps PreTrainedModel.gradient_checkpointing_enable once:

  • every_n_layers uses transformers' rule (GradientCheckpointingLayer 0, N, 2N, ...) on any version, held by the existing skip-aware layer flag so for_training keeps it.
  • offload=True is dropped with a one-time message while Unsloth's offloading checkpoint is active.

Tests

  • tests/test_gc_enable_kwargs.py (CPU, tiny Qwen3, fp32 grads vs a no-checkpointing reference): main 3 failed / 2 passed, this PR 5 passed on torch 2.10, 2.13, 2.14 with transformers 5.17; transformers 4.57.6 main 2 failed, this PR 3 passed (the 2 native-offload cases need 5.17).
  • Qwen3-0.6B on GPU: every_n_layers=2 checkpoints 14 layers per step instead of 28; offload=True makes 0 extra host copies instead of 1.
  • Kaggle T4 / A100: the doubled offload on main costs nothing measurable (inside A/A noise), so this is a correctness change, not a speedup.

Not covered

GRPO end to end with every_n_layers; transformers < 4.52 (no GradientCheckpointingLayer: every layer stays checkpointed).

…nting_enable

every_n_layers and offload are gradient_checkpointing_enable arguments (transformers 5.17+), not
torch.utils.checkpoint ones. Passed inside gradient_checkpointing_kwargs (TrainingArguments on older
transformers, direct calls) the Unsloth checkpoint shims bound them into every decoder layer call, so
both were silently ignored. every_n_layers is now applied on any transformers version through the
skip-aware layer flag, so for_training / for_inference no longer reset it. offload=True is dropped while
Unsloth's offloading checkpoint is active, where it only re-offloaded the inputs Unsloth keeps on the GPU.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-20T22:28:54.811785Z
We couldn’t complete this review. Comment @codex review to try again.
18f9f05 New commits
🔒 Security Review ⚠️ Failed 2026-10-20T20:07:01.008549Z
We couldn’t complete this review. Comment @codex security review to try again.
2f48d48 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@danielhanchen

Copy link
Copy Markdown
Member Author

@codex review

@danielhanchen danielhanchen self-assigned this Oct 20, 2026

@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: 2f48d4866e

ℹ️ 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 unsloth/models/_utils.py
"Unsloth: gradient checkpointing already offloads activations to host memory, "
"so transformers' offload=True is ignored.",
)
offload = False

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve explicit offload when the shim keeps tensors on-device

When the active checkpoint is unsloth_checkpoint, this unconditionally disables a direct Transformers 5.17 offload=True request, but the smart checkpointer only offloads inputs above its MINIMUM_SIZE and deliberately keeps the final checkpoint input on-device. The added test even sets MINIMUM_SIZE extremely high and then verifies that save_on_cpu is never entered, demonstrating that small activations receive no offloading at all. This violates the caller's explicit memory-saving request and can leave a marginal run OOM; native offload should be suppressed only for checkpoints the shim will actually offload.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not changing this. The tensors Unsloth's checkpointer keeps on the GPU are by design: inputs under its 2 MiB floor (unsloth_zoo gradient_checkpointing.py MINIMUM_SIZE), whose host round trip costs more than it saves, and the last checkpoint input, which backward consumes first. Running save_on_cpu on top copies those same small or immediately needed tensors to the host and back, which is the double offload this removes; every input large enough to matter is still offloaded by the shim. The test raises MINIMUM_SIZE only to prove the native hook is not entered, not to model real sizes.

Comment thread unsloth/models/_utils.py Outdated
layers = [m for m in model.modules() if isinstance(m, GradientCheckpointingLayer)]
skipped = []
for i, layer in enumerate(layers):
if every_n_layers > 1 and i % every_n_layers != 0 and hasattr(layer, "gradient_checkpointing"):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep block-swapped layers checkpointed

When every_n_layers > 1 is combined with offload_layers, this loop can mark block-swapped decoder layers as skipped because it ignores model._unsloth_block_swap.indices. For example, spread placement on four layers with two swapped layers selects indices 1 and 3, and every_n_layers=2 disables checkpointing on exactly those layers. install_block_swap requires per-layer checkpointing for swapped blocks; otherwise their weights remain resident through backward and the slot pool grows, defeating the memory plan and potentially causing the OOM that block swapping was intended to avoid. Swapped layer objects must remain enabled regardless of the interval.

Useful? React with 👍 / 👎.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

pre-commit-ci Bot and others added 2 commits October 20, 2026 21:03
…y_n_layers to remote layers

vision.py's _nonre_checkpoint already offloads through _offload_activation_hooks, so it now carries
an _unsloth_offloads marker that the offload=True suppression reads. Remote-code decoder layers
wrapped by install_remote_gradient_checkpointing read a per-layer every_n_layers mark in their
wrapper, so every_n_layers > 1 skips them instead of only warning.

[skip ci]
@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Oct 20, 2026
@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Oct 20, 2026
@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Oct 20, 2026

This branch has not been deployed

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant