Skip to content

feat(distributed): add selective activation checkpointing for FSDP2 - #2389

Merged
akoumpa merged 14 commits into
mainfrom
yuhez/feat/selective-activation-checkpointing
Jun 8, 2026
Merged

feat(distributed): add selective activation checkpointing for FSDP2#2389
akoumpa merged 14 commits into
mainfrom
yuhez/feat/selective-activation-checkpointing

Conversation

@yuhezhang-ai

@yuhezhang-ai yuhezhang-ai commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a TorchTitan-style selective activation-checkpointing mode for FSDP2 — dense and Mixture-of-Experts (including expert parallelism), single- and multi-GPU — with torch.compile interoperability.

  • New selective mode: 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 via distributed.activation_checkpointing: selective.
  • Correctness: the matmul counter is keyed on ctx.is_recompute so forward and recompute agree even when a block has an odd number of matmuls (a shared counter silently corrupts gradients).
  • Op set: seeded from PyTorch's get_default_op_list().compute_intensive_ops plus 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.
  • MoE + expert parallelism: selective AC is now supported for MoE under FSDP2/EP. MoE blocks reuse the shared dense policy, and DeepSeek MLP dispatch was made wrapper-safe so checkpointed MoE blocks work. Note: selective generally does not beat full AC for MoE (experts are cheap to recompute but expensive to store, and EP communication is opaque to the policy), so full AC is recommended for MoE; selective remains an opt-in.
  • Single-GPU: selective is honored on world_size == 1 instead of silently degrading to full checkpointing.
  • KV-shared models (e.g. Gemma4 E2B/E4B) cannot checkpoint attention through the 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.
  • Diagnostics: set NEMO_SELECTIVE_AC_TRACE=1 to log each op's SAVE/RECOMPUTE/ALTERNATE decision once, to confirm a model's expensive ops are actually being saved.
  • Code organization: the selective-AC core (op sets, policy, block/sub-module wrappers, KV detection, compile flag) lives in nemo_automodel/components/distributed/activation_checkpointing.py; parallelizer.py keeps only the thin apply_selective_activation_checkpointing entry point, and the opt-in trace helper lives in parallelizer_utils.py.
  • Validation: selective requires FSDP2 (non-FSDP2 strategies such as ddp/megatron_fsdp are rejected); expert parallelism is allowed.
  • Docs: the gradient-checkpointing guide (legacy + nightly) documents the mode, single-GPU/MoE behavior, the full-vs-selective trade-off, and the trace flag.

Test plan

  • Forward/backward numerics parity vs. non-checkpointed baseline (odd and even matmul counts)
  • Save-op-set composition + partitioner seeding + fallback; grouped-GEMM alternation in the matmul set
  • Compile-target selection (selective wrapper compiled outer; PP wrapper unwrapped) + LRU-cache guard
  • Single-GPU selective wrapping + KV-shared fallback
  • MoE: parallelize_model threads selective; apply_ac wraps blocks with the shared policy and sets the compile-outer flag
  • Policy trace logs each op once with its verdict
  • YAML routing/validation for selective/full/true, EP allowed, non-FSDP2 rejected
  • distributed + moe + recipes unit suites green (197 passed locally)
  • L1/functional multi-GPU EP parity for selective AC (and under torch.compile) — inductor/cluster-dependent, not covered by CPU unit tests

Real runs:

@copy-pr-bot

copy-pr-bot Bot commented Jun 2, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

Signed-off-by: Yuhe Zhang <yuhez@nvidia.com>
…ch.compile

Signed-off-by: Yuhe Zhang <yuhez@nvidia.com>
@yuhezhang-ai
yuhezhang-ai force-pushed the yuhez/feat/selective-activation-checkpointing branch from eb01d92 to 18d6870 Compare June 2, 2026 15:49
Signed-off-by: Yuhe Zhang <yuhez@nvidia.com>
… GPU

Signed-off-by: Yuhe Zhang <yuhez@nvidia.com>
@yuhezhang-ai

Copy link
Copy Markdown
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>
@yuhezhang-ai

Copy link
Copy Markdown
Contributor Author

/ok to test 74d320b

…after AC module split

Signed-off-by: Yuhe Zhang <yuhez@nvidia.com>
@yuhezhang-ai

Copy link
Copy Markdown
Contributor Author

/ok to test f04335c

@jgerh jgerh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Completed tech pubs review of .md and .mdx files and provided a few suggestions

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 fasterand 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 storeand 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.

Comment thread docs/guides/gradient-checkpointing.md Outdated

> **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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 fasterand 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.

Comment thread docs/guides/gradient-checkpointing.md Outdated

> **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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 storeand 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.

Comment thread docs/guides/gradient-checkpointing.md Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
LC-CE and gradient checkpointing target **different memory hot-spots** (output layer vs. transformer blocks), so their benefits stack almost linearly.

Comment thread docs/guides/gradient-checkpointing.md Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
- Expect ±5 % variance depending on exact model, sequence length, and GPU architecture.

Comment thread docs/guides/gradient-checkpointing.md Outdated
@@ -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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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>
@yuhezhang-ai

Copy link
Copy Markdown
Contributor Author

/ok to test ded10ca

@akoumpa

akoumpa commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants