feat(distributed): add selective activation checkpointing for FSDP2 - #2389
Merged
Conversation
Signed-off-by: Yuhe Zhang <yuhez@nvidia.com>
…ch.compile Signed-off-by: Yuhe Zhang <yuhez@nvidia.com>
yuhezhang-ai
force-pushed
the
yuhez/feat/selective-activation-checkpointing
branch
from
June 2, 2026 15:49
eb01d92 to
18d6870
Compare
Signed-off-by: Yuhe Zhang <yuhez@nvidia.com>
… GPU Signed-off-by: Yuhe Zhang <yuhez@nvidia.com>
Contributor
Author
|
/ok to test e390fd2 |
…allelism Signed-off-by: Yuhe Zhang <yuhez@nvidia.com>
Signed-off-by: Yuhe Zhang <yuhez@nvidia.com>
… trace Signed-off-by: Yuhe Zhang <yuhez@nvidia.com>
Signed-off-by: Yuhe Zhang <yuhez@nvidia.com>
Extract the TorchTitan-style selective activation checkpointing core out of the central parallelizer.py into a dedicated activation_checkpointing.py: op-set construction, the save/recompute policy, block/sub-module wrappers, KV-sharing detection, and the compile-outer wrapper flag. parallelizer.py keeps only the thin apply_selective_activation_checkpointing entry point, which still needs the heavy, transformers-aware _extract_model_layers, so the dependency stays one-directional (parallelizer -> activation_checkpointing -> parallelizer_utils) with no circular imports. Move the opt-in NEMO_SELECTIVE_AC_TRACE diagnostic out of parallelizer.py into parallelizer_utils.maybe_trace_selective_ac_decision so the hot policy is a single call site instead of trace globals plus a helper. Make the new module's cross-module interface public (drop the leading underscore) and keep internal op-resolution/plumbing private. Update the moe and fsdp2 consumers and the unit tests to import from the new module. Also fix doc wording: clarify that torch.compile must be held fixed when comparing full vs. selective, and refer to TorchTitan as a reference implementation rather than "upstream". Signed-off-by: Yuhe Zhang <yuhez@nvidia.com>
Contributor
Author
|
/ok to test 74d320b |
…after AC module split Signed-off-by: Yuhe Zhang <yuhez@nvidia.com>
Contributor
Author
|
/ok to test f04335c |
jgerh
reviewed
Jun 8, 2026
jgerh
left a comment
Contributor
There was a problem hiding this comment.
Completed tech pubs review of .md and .mdx files and provided a few suggestions
Contributor
There was a problem hiding this comment.
Suggested change
| Gradient checkpointing, also called _activation checkpointing_, trades a little extra compute for a **large reduction in GPU memory** by recomputing intermediate activations during the backward pass instead of storing them. |
| Use `true` or `full` for full activation checkpointing. Use `selective` for PyTorch selective activation checkpointing on FSDP2 configs. Selective checkpointing saves expensive operations such as attention, collectives, and part of the matrix multiplications while recomputing cheaper operations during backward. | ||
|
|
||
| <Note> | ||
| `selective` requires the FSDP2 strategy. Non-FSDP2 strategies (`ddp`, `megatron_fsdp`) raise an error when `selective` is requested. KV-sharing models (e.g. Gemma4) automatically fall back to sub-module checkpointing, because attention cannot be recomputed through the KV cache. |
Contributor
There was a problem hiding this comment.
Suggested change
| `selective` requires the FSDP2 strategy. Non-FSDP2 strategies (`ddp`, `megatron_fsdp`) raise an error when `selective` is requested. KV-sharing models (e.g. Gemma4) automatically fall back to sub-module checkpointing, because attention cannot be recomputed through the KV cache. | |
| `selective` requires the FSDP2 strategy. Non-FSDP2 strategies (`ddp`, `megatron_fsdp`) raise an error when `selective` is requested. KV-sharing models (e.g., Gemma4) automatically fall back to sub-module checkpointing, because attention cannot be recomputed through the KV cache. |
| </Note> | ||
|
|
||
| <Tip> | ||
| Selective AC only speeds things up when the model's expensive operations are the ones being saved. To see the per-op save/recompute decisions for your model, set `NEMO_SELECTIVE_AC_TRACE=1`; each unique operation is logged once as `SAVE`, `RECOMPUTE`, or `ALTERNATE`. If an expensive op (e.g. an expert grouped-GEMM) shows up as `RECOMPUTE`, selective AC will not beat full checkpointing for that model. |
Contributor
There was a problem hiding this comment.
Suggested change
| Selective AC only speeds things up when the model's expensive operations are the ones being saved. To see the per-op save/recompute decisions for your model, set `NEMO_SELECTIVE_AC_TRACE=1`; each unique operation is logged once as `SAVE`, `RECOMPUTE`, or `ALTERNATE`. If an expensive op (e.g. an expert grouped-GEMM) shows up as `RECOMPUTE`, selective AC will not beat full checkpointing for that model. | |
| Selective AC only speeds things up when the model's expensive operations are the ones being saved. To see the per-op save/recompute decisions for your model, set `NEMO_SELECTIVE_AC_TRACE=1`; each unique operation is logged once as `SAVE`, `RECOMPUTE`, or `ALTERNATE`. If an expensive op (e.g., an expert grouped-GEMM) shows up as `RECOMPUTE`, selective AC will not beat full checkpointing for that model. |
| </Tip> | ||
|
|
||
| <Note> | ||
| **Full vs. selective:** Selective AC saves the expensive operations (attention and part of the matmuls) and recomputes only the cheaper ones, so it does less recompute work than full AC while holding more activations in memory. Whether that nets out as faster — and at what memory cost — depends on the model, sequence length, and whether `torch.compile` is enabled, so benchmark full vs. selective for your own setup. When you do, keep the `torch.compile` setting the same on both sides (compare full and selective both compiled, or both uncompiled). `torch.compile` is a large speed lever on its own and helps both modes, so mixing it in makes it hard to tell which gain came from the AC mode. |
Contributor
There was a problem hiding this comment.
Suggested change
| **Full vs. selective:** Selective AC saves the expensive operations (attention and part of the matmuls) and recomputes only the cheaper ones, so it does less recompute work than full AC while holding more activations in memory. Whether that nets out as faster — and at what memory cost — depends on the model, sequence length, and whether `torch.compile` is enabled, so benchmark full vs. selective for your own setup. When you do, keep the `torch.compile` setting the same on both sides (compare full and selective both compiled, or both uncompiled). `torch.compile` is a large speed lever on its own and helps both modes, so mixing it in makes it hard to tell which gain came from the AC mode. | |
| **Full vs. selective:** Selective AC saves the expensive operations (attention and part of the matmuls) and recomputes only the cheaper ones, so it does less recompute work than full AC while holding more activations in memory. Whether that nets out as faster, and at what memory cost, depends on the model, sequence length, and whether `torch.compile` is enabled, so benchmark full vs. selective for your own setup. When you do, keep the `torch.compile` setting the same on both sides (compare full and selective both compiled, or both uncompiled). `torch.compile` is a large speed lever on its own and helps both modes, so mixing it in makes it hard to tell which gain came from the AC mode. |
| </Note> | ||
|
|
||
| <Note> | ||
| **MoE / expert parallelism:** Selective AC is designed for dense transformers and generally does **not** help Mixture-of-Experts models with expert parallelism. In an MoE block the experts dominate the cost — they are cheap to recompute but expensive to store — and the expert-parallel dispatch/communication is opaque to the selective policy, so it is recomputed regardless. As a result selective AC tends to add activation memory without a corresponding speedup for MoE, matching what reference implementations such as TorchTitan observe. Prefer **full** activation checkpointing (`true`/`full`) for MoE; selective remains supported for MoE+FSDP2 as an opt-in. |
Contributor
There was a problem hiding this comment.
Suggested change
| **MoE / expert parallelism:** Selective AC is designed for dense transformers and generally does **not** help Mixture-of-Experts models with expert parallelism. In an MoE block the experts dominate the cost — they are cheap to recompute but expensive to store — and the expert-parallel dispatch/communication is opaque to the selective policy, so it is recomputed regardless. As a result selective AC tends to add activation memory without a corresponding speedup for MoE, matching what reference implementations such as TorchTitan observe. Prefer **full** activation checkpointing (`true`/`full`) for MoE; selective remains supported for MoE+FSDP2 as an opt-in. | |
| **MoE/expert parallelism:** Selective AC is designed for dense transformers and generally does **not** help Mixture-of-Experts models with expert parallelism. In an MoE block the experts dominate the cost (they are cheap to recompute but expensive to store), and the expert-parallel dispatch/communication is opaque to the selective policy, so it is recomputed regardless. As a result, selective AC tends to add activation memory without a corresponding speedup for MoE, matching what reference implementations such as TorchTitan observe. Prefer **full** activation checkpointing (`true`/`full`) for MoE; selective remains supported for MoE and FSDP2 as an opt-in. |
|
|
||
| > **Tip:** Selective AC only speeds things up when the model's expensive operations are the ones being saved. To see the per-op save/recompute decisions for your model, set `NEMO_SELECTIVE_AC_TRACE=1`; each unique operation is logged once as `SAVE`, `RECOMPUTE`, or `ALTERNATE`. If an expensive op (e.g. an expert grouped-GEMM) shows up as `RECOMPUTE`, selective AC will not beat full checkpointing for that model. | ||
|
|
||
| > **Note (full vs. selective):** Selective AC saves the expensive operations (attention and part of the matmuls) and recomputes only the cheaper ones, so it does less recompute work than full AC while holding more activations in memory. Whether that nets out as faster — and at what memory cost — depends on the model, sequence length, and whether `torch.compile` is enabled, so benchmark full vs. selective for your own setup. When you do, keep the `torch.compile` setting the same on both sides (compare full and selective both compiled, or both uncompiled). `torch.compile` is a large speed lever on its own and helps both modes, so mixing it in makes it hard to tell which gain came from the AC mode. |
Contributor
There was a problem hiding this comment.
Suggested change
| > **Note (full vs. selective):** Selective AC saves the expensive operations (attention and part of the matmuls) and recomputes only the cheaper ones, so it does less recompute work than full AC while holding more activations in memory. Whether that nets out as faster — and at what memory cost — depends on the model, sequence length, and whether `torch.compile` is enabled, so benchmark full vs. selective for your own setup. When you do, keep the `torch.compile` setting the same on both sides (compare full and selective both compiled, or both uncompiled). `torch.compile` is a large speed lever on its own and helps both modes, so mixing it in makes it hard to tell which gain came from the AC mode. | |
| > **Note (full vs. selective):** Selective AC saves the expensive operations (attention and part of the matmuls) and recomputes only the cheaper ones, so it does less recompute work than full AC while holding more activations in memory. Whether that nets out as faster, and at what memory cost, depends on the model, sequence length, and whether `torch.compile` is enabled, so benchmark full vs. selective for your own setup. When you do, keep the `torch.compile` setting the same on both sides (compare full and selective both compiled, or both uncompiled). `torch.compile` is a large speed lever on its own and helps both modes, so mixing it in makes it hard to tell which gain came from the AC mode. |
|
|
||
| > **Note (full vs. selective):** Selective AC saves the expensive operations (attention and part of the matmuls) and recomputes only the cheaper ones, so it does less recompute work than full AC while holding more activations in memory. Whether that nets out as faster — and at what memory cost — depends on the model, sequence length, and whether `torch.compile` is enabled, so benchmark full vs. selective for your own setup. When you do, keep the `torch.compile` setting the same on both sides (compare full and selective both compiled, or both uncompiled). `torch.compile` is a large speed lever on its own and helps both modes, so mixing it in makes it hard to tell which gain came from the AC mode. | ||
|
|
||
| > **Note (MoE / expert parallelism):** Selective AC is designed for dense transformers and generally does **not** help Mixture-of-Experts models with expert parallelism. In an MoE block the experts dominate the cost — they are cheap to recompute but expensive to store — and the expert-parallel dispatch/communication is opaque to the selective policy, so it is recomputed regardless. As a result selective AC tends to add activation memory without a corresponding speedup for MoE, matching what reference implementations such as TorchTitan observe. Prefer **full** activation checkpointing (`true`/`full`) for MoE; selective remains supported for MoE+FSDP2 as an opt-in. |
Contributor
There was a problem hiding this comment.
Suggested change
| > **Note (MoE / expert parallelism):** Selective AC is designed for dense transformers and generally does **not** help Mixture-of-Experts models with expert parallelism. In an MoE block the experts dominate the cost — they are cheap to recompute but expensive to store — and the expert-parallel dispatch/communication is opaque to the selective policy, so it is recomputed regardless. As a result selective AC tends to add activation memory without a corresponding speedup for MoE, matching what reference implementations such as TorchTitan observe. Prefer **full** activation checkpointing (`true`/`full`) for MoE; selective remains supported for MoE+FSDP2 as an opt-in. | |
| > **Note (MoE/expert parallelism):** Selective AC is designed for dense transformers and generally does **not** help Mixture-of-Experts models with expert parallelism. In an MoE block the experts dominate the cost (they are cheap to recompute but expensive to store), and the expert-parallel dispatch/communication is opaque to the selective policy, so it is recomputed regardless. As a result, selective AC tends to add activation memory without a corresponding speedup for MoE, matching what reference implementations such as TorchTitan observe. Prefer **full** activation checkpointing (`true`/`full`) for MoE; selective remains supported for MoE and FSDP2 as an opt-in. |
Contributor
There was a problem hiding this comment.
Suggested change
| LC-CE and gradient checkpointing target **different memory hot-spots** (output layer vs. transformer blocks), so their benefits stack almost linearly. |
Contributor
There was a problem hiding this comment.
Suggested change
| - Expect ±5 % variance depending on exact model, sequence length, and GPU architecture. |
| @@ -82,4 +101,4 @@ automodel examples/llm_finetune/llama3_2/llama_3_2_1b_my_finetune.yaml | |||
| If we run with the above settings (activation ckpt = on, lc-ce = on, fsdp = on), look for a log line similar to: | |||
Contributor
There was a problem hiding this comment.
Suggested change
| If we run with the above settings (activation ckpt = on, lc-ce = on, fsdp = on), look for a log line similar to: | |
| If you run with the above settings (activation ckpt = on, lc-ce = on, fsdp = on), look for a log line similar to: |
Signed-off-by: Yuhe Zhang <yuhez@nvidia.com>
…activation-checkpointing Signed-off-by: Yuhe Zhang <yuhez@nvidia.com>
…activation-checkpointing Signed-off-by: Yuhe Zhang <yuhez@nvidia.com>
Contributor
Author
|
/ok to test ded10ca |
akoumpa
approved these changes
Jun 8, 2026
Contributor
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.
Summary
Adds a TorchTitan-style
selectiveactivation-checkpointing mode for FSDP2 — dense and Mixture-of-Experts (including expert parallelism), single- and multi-GPU — withtorch.compileinteroperability.selectivemode: wraps whole transformer blocks with a per-op policy — matmuls (mm/linear) and MoE expert grouped-GEMMs (_grouped_mm/_scaled_grouped_mm) alternate save/recompute, while attention, communication collectives, and other compute-intensive ops are always saved. Configured viadistributed.activation_checkpointing: selective.ctx.is_recomputeso forward and recompute agree even when a block has an odd number of matmuls (a shared counter silently corrupts gradients).get_default_op_list().compute_intensive_opsplus a curated supplement (attention variants, comm collectives incl. expert-parallel dispatch/combine, the inductor compiled HOP), with a guarded fallback when the private partitioner API is unavailable — rather than a frozen hand-written list.world_size == 1instead of silently degrading to full checkpointing.DynamicCache, so they fall back to sub-module checkpointing.torch.compile: selective wrappers are tagged and compiled outer (dense and MoE) so the partitioner honors the SAC recompute tags (compiling the inner layer would collapse selectivity into full recompute); the dynamo LRU cache is disabled (best-effort) for the SAC + compile + PP path.NEMO_SELECTIVE_AC_TRACE=1to log each op'sSAVE/RECOMPUTE/ALTERNATEdecision once, to confirm a model's expensive ops are actually being saved.nemo_automodel/components/distributed/activation_checkpointing.py;parallelizer.pykeeps only the thinapply_selective_activation_checkpointingentry point, and the opt-in trace helper lives inparallelizer_utils.py.selectiverequires FSDP2 (non-FSDP2 strategies such asddp/megatron_fsdpare rejected); expert parallelism is allowed.Test plan
parallelize_modelthreadsselective;apply_acwraps blocks with the shared policy and sets the compile-outer flagselective/full/true, EP allowed, non-FSDP2 rejecteddistributed+moe+recipesunit suites green (197 passed locally)torch.compile) — inductor/cluster-dependent, not covered by CPU unit testsReal runs:
Dense model qwen3-4b with real data (ac full vs selective, compiled vs uncompiled): https://wandb.ai/Nemo-automodel/yuhez_workspace/groups/qwen3-4b-squad-sdpa/workspace?nw=nwuseryuhez
selective is 5-10% faster.
MOE with EP (where selective is slower in practice): https://wandb.ai/Nemo-automodel/yuhez_workspace/groups/moonlight16b-pretrain-te-ep8-ac-compare/workspace?nw=nwuseryuhez
Better benchmark with mock data (so seq length steady) with llama3.1-8b:
W&B-logged matrix (llama-3.1-8b, train_ft, 50 steps, mock, H100x8, seq4096 bs1)
Batch-size sweep -- llama-3.1-8b, eager, seq4096