Nemo Mbridge Perf Activation Recompute

Validate and use selective and full activation recompute in Megatron Bridge to reduce GPU memory usage at the cost of extra compute.

Published by @NVIDIA·0 agent reads / 30d·0 saves·

Activation Recompute

Stable docs: @docs/training/activation-recomputation.md Card: @skills/nemo-mbridge-perf-activation-recompute/card.yaml

Answer Checklist

For OOM or CUDA graph questions, lead with this exact sequence:

  1. First try PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True; many borderline failures are allocator fragmentation, not activation capacity.
  2. Prefer selective recompute before full-layer recompute: recompute_granularity="selective" with recompute_modules=["core_attn"].
  3. If still borderline, optionally add "layernorm"; use "mlp" only as a last resort because it has a large compute cost on wide dense FFNs.
  4. Use full-layer recompute only after selective recompute fails to fit, and always name the required fields: recompute_granularity="full", recompute_method, and recompute_num_layers.
  5. If FP8 or TE-scoped CUDA graphs are enabled, call out the assertion risk: full-layer recompute is incompatible with TE scopes such as attn, mlp, and moe_router. Valid fixes are selective recompute, cuda_graph_impl="none", or cuda_graph_impl="local" with cuda_graph_scope="full_iteration".

What It Is

Activation recompute trades GPU compute for memory by discarding intermediate activations during the forward pass and recomputing them during backward. Megatron Bridge supports two granularities:

GranularityWhat you specifyWhat gets recomputedMemory savingsCompute cost
selectiverecompute_modules list (e.g. core_attn, mlp)specific submodules within each layermoderate (module-dependent)low to high
fullrecompute_num_layers + recompute_methodentire transformer layers (N layers)strongesthighest

Note: MCore names these "selective" (submodule-level) vs "full" (layer-level). "Full" means recomputing full layers, not the full model — you still choose how many layers via recompute_num_layers.

Quick Decision

  1. Set PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True first — most borderline OOMs are caused by memory fragmentation, not capacity. This fixes it at zero cost. See @skills/nemo-mbridge-perf-memory-tuning/SKILL.md.
  2. Start with recompute_granularity=selective, recompute_modules=[core_attn] (often already the default in recipes).
  3. Add layernorm to recompute modules — nearly free compute-wise but saves negligible memory. Only helps in extremely borderline cases.
  4. Add mlp as a last resort — saves ~3 GB but costs ~16% GPU utilization on large dense models (Llama3 70B).
  5. Use recompute_granularity=full only when selective recompute still does not fit.

CPU offloading (cpu_offloading=True) is an alternative that avoids recompute cost entirely, but it is incompatible with PP > 1.

Enablement

Selective recompute

cfg.model.recompute_granularity = "selective"
cfg.model.recompute_modules = ["core_attn"]  # add "layernorm", "mlp", or other valid modules as needed

Full-layer recompute

cfg.model.recompute_granularity = "full"
cfg.model.recompute_method = "uniform"
cfg.model.recompute_num_layers = 4

Available recompute_modules

ModuleWhat it recomputesCompute costMemory savings
core_attnattention softmax/dropout/QKV dot productlow (Flash Attention already recomputes internally)moderate
layernormlayer normalizationnegligible (~0%)negligible
mlpfull FFN blockhigh (~16% on Llama3 70B, hidden=28672)~3 GB
moeMoE expert dispatchvariesvaries
moe_actMoE activation functionslowsmall
shared_expertsshared expert layersmoderatemoderate
mla_up_projMulti-Latent Attention up projectionmoderatemoderate

Performance harness CLI

python scripts/performance/run_performance_workload.py \
  --recompute_granularity selective \
  --recompute_modules core_attn layernorm \
  ...

Compatibility and Constraints

  • recompute_granularity=selective requires a non-empty recompute_modules list
  • recompute_granularity=full requires recompute_method and recompute_num_layers
  • Layer-level recompute (recompute_granularity="full" + recompute_num_layers) is incompatible with TE-scoped CUDA graphs. MCore calls this "full" granularity — the name refers to recomputing full transformer layers, not the full model. Even though you're selecting how many layers to recompute, MCore treats it differently from submodule recompute. Any TE-scoped scope (attn, mlp, moe_router, etc.) will assert. This commonly hits FP8 configs that enable TE-scoped graphs by default (e.g. LLAMA3_70B_SFT_CONFIG_H100_FP8_CS_V1 sets cuda_graph_impl="transformer_engine", cuda_graph_scope="mlp"). Options:
    • use submodule recompute (recompute_granularity="selective" + recompute_modules) — compatible with TE-scoped graphs
    • disable CUDA graphs (cuda_graph_impl="none") and use layer-level recompute
    • switch to cuda_graph_impl="local", cuda_graph_scope="full_iteration"
  • distribute_saved_activations=True cannot be combined with sequence_parallel=True
  • Combining mlp + core_attn recompute is slightly worse than mlp alone due to double recompute overhead

Measured Results

Llama3 70B SFT on 32x H100 80GB, FP8 (Current Scaling):

  • Baseline: TP=4, PP=4, VPP=5, DP=2, MBS=1, GBS=32, seq_len=4096
  • Golden GPU utilization: 709.93 TFLOP/s/GPU
  • Regression threshold: 5%
Experimentrecompute_modulesTFLOP/s/GPUvs GoldenPeak Mem (GB)Result
Baseline[core_attn]~704-0.8%58.8 (OOM rank0)OOM
Exp 1[mlp]593.6-16.4%55.6Perf regression
Exp 2[mlp, core_attn]586.8-17.3%55.6Perf regression
Exp 3[core_attn, layernorm]~702-1.1%59.6 (OOM rank0)OOM

Key takeaways:

  • layernorm recompute is nearly free compute-wise but saves negligible memory
  • mlp recompute saves ~3 GB peak but costs ~16% because the Llama3 70B FFN (hidden=28672) is expensive to recompute
  • Combining mlp + core_attn is slightly worse than mlp alone
  • For this workload, the actual OOM fix was PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (memory fragmentation, not capacity). See @skills/nemo-mbridge-perf-memory-tuning/SKILL.md.

Code Anchors

Recompute modules enum and selective checkpoint logic

# 3rdparty/Megatron-LM/megatron/core/transformer/transformer_block.py
# _checkpointed_forward() applies selective recompute based on recompute_modules

Recompute config validation

# 3rdparty/Megatron-LM/megatron/core/transformer/transformer_config.py
# Validates recompute_granularity, recompute_method, recompute_num_layers

Llama3 recipe defaults

    # Memory saving (recompute & offloading)
    cfg.model.recompute_granularity = None
    cfg.model.recompute_modules = None
    cfg.model.fine_grained_activation_offloading = False
    cfg.model.offload_modules = None

Full recompute + CUDA graph assertion (MCore)

            if self.recompute_granularity:
                if self.recompute_granularity != "selective":
                    assert self.cuda_graph_scope == [
                        CudaGraphScope.full_iteration
                    ], "full recompute is only supported with full iteration CUDA graph."

CPU offloading PP incompatibility (MCore)

        if self.cpu_offloading and self.pipeline_model_parallel_size > 1:
            raise ValueError(
                "Currently there is no support for Pipeline parallelism with CPU offloading"
            )

Failure Diagnosis

SymptomCauseConfirmFix
>15% GPU utilization dropmlp recompute on large FFNcheck recompute_modules includes mlpcheck expandable_segments:True is set; consider reducing MBS
Still OOM after adding layernormlayernorm activations are too smallcompare peak memory before/afteradd mlp recompute or check expandable_segments:True
AssertionError: full recompute is only supported with full iteration CUDA graphlayer-level recompute (recompute_granularity=full + recompute_num_layers) with TE-scoped graphs. FP8 CS configs default to cuda_graph_impl=transformer_engine, scope=mlp.check cuda_graph_impl and cuda_graph_scopeuse submodule recompute (selective + recompute_modules), or cuda_graph_impl=none, or local + full_iteration
ValueError: PP + CPU offloadingcpu_offloading=True with pipeline_model_parallel_size > 1check PP configdisable CPU offloading or set PP=1
mlp+core_attn worse than mlp alonedouble recompute overheadcompare Exp 1 vs Exp 2use mlp alone

Known Limitations

  • Per-module memory savings vary significantly by model architecture and hidden dimension
  • No automatic module selection — users must choose which modules to recompute
  • layernorm recompute is almost never worth it as a standalone fix
  • CPU offloading (the zero-compute-cost alternative) is blocked when PP > 1

Verification

uv run python -m pytest \
  tests/unit_tests/training/test_config.py -k "recompute" -q

Success criteria:

  • Unit tests pass for recompute config validation
  • No assertion errors from config validation

Bundled with this artifact

5 files

Reference files that ship alongside this artifact. Agents pull these in only when the task needs them.

More on the bench

SKILL0

Whisper

OpenAI's general-purpose speech recognition model. Supports 99 languages, transcription, translation to English, and language identification. Six model sizes from tiny (39M params) to large (1550M params). Use for speech-to-text, podcast transcription, or multilingual audio processing. Best for robust, multilingual ASR.

data-science-ml+2
0
SKILL0

Guidance

Control LLM output with regex and grammars, guarantee valid JSON/XML/code generation, enforce structured formats, and build multi-step workflows with Guidance - Microsoft Research's constrained generation framework

ai-prompt-engineering+2
0
SKILL0

Pinecone

Managed vector database for production AI applications. Fully managed, auto-scaling, with hybrid search (dense + sparse), metadata filtering, and namespaces. Low latency (<100ms p95). Use for production RAG, recommendation systems, or semantic search at scale. Best for serverless, managed infrastructure.

data-science-ml+2
0