Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
6621d76
Add selective activation recompute support
seonjinn Apr 17, 2026
2c20d69
Update nemo_rl/models/policy/__init__.py
seonjinn Apr 20, 2026
391eb9b
Update nemo_rl/models/megatron/setup.py
seonjinn Apr 20, 2026
38f92e5
Update examples/configs/grpo_math_1B.yaml
seonjinn Apr 20, 2026
e5acdd8
Update nemo_rl/models/megatron/setup.py
seonjinn Apr 20, 2026
72d1d15
Merge branch 'main' into sj/selective-recompute
seonjinn Apr 23, 2026
79486ec
Merge branch 'main' into sj/selective-recompute
seonjinn Apr 29, 2026
8102a0d
Merge remote-tracking branch 'origin/main' into sj/selective-recompute
seonjinn May 14, 2026
7e54943
Merge remote-tracking branch 'origin/main' into sj/selective-recompute
seonjinn May 15, 2026
043ae0c
fix: pin NeMo Gym docs URL to v0.2.1 (latest 404)
seonjinn May 15, 2026
a3abda4
fix: guard missing recompute_granularity, dedupe lines, sync referenc…
seonjinn May 15, 2026
d53d346
Merge remote-tracking branch 'origin/main' into sj/selective-recompute
seonjinn May 15, 2026
810260c
test: add unit tests for recompute_granularity dispatch
seonjinn May 16, 2026
c40a482
Merge main into sj/selective-recompute
seonjinn May 19, 2026
05ca762
Merge branch 'main' into sj/selective-recompute
seonjinn May 20, 2026
638d22a
Merge branch 'main' into sj/selective-recompute
seonjinn May 20, 2026
9075f7d
Merge branch 'main' into sj/selective-recompute
seonjinn May 22, 2026
c4dc71e
Retry CI on L1 grpo_megatron_lora_async reward flake
seonjinn May 22, 2026
dd16310
Add selective recompute keys to remaining megatron exemplar configs
seonjinn May 22, 2026
b88ffab
Sync reference_configs with new recompute keys
seonjinn May 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions examples/configs/distillation_math.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,17 @@ policy: &POLICY_BASE
force_reconvert_from_hf: False # Set to True to force reconvert of the model from Hugging Face
empty_unused_memory_level: 0
activation_checkpointing: false
# recompute_granularity controls activation checkpointing depth.
# "full": recompute all activations (default, max memory savings).
# "selective": recompute only specific modules (see recompute_modules).
# Only takes effect when activation_checkpointing: true.
recompute_granularity: "full"
# Modules to selectively recompute when recompute_granularity="selective".
# MCore options: ["core_attn", "moe_act", "layernorm", "mla_up_proj", "mlp", "moe", "shared_experts"].
# Null defaults to ["core_attn"]. Full list and per-module constraints:
# https://github.com/NVIDIA/Megatron-LM/blob/d30c3ae5469fe3f6a64d4fd2e63b6e7f7844ea81/megatron/core/transformer/transformer_config.py#L483
# Use ["moe"] for MoE models to recompute expert activations only.
recompute_modules: null
converter_type: "Qwen3ForCausalLM"
tensor_model_parallel_size: 2
expert_tensor_parallel_size: 1
Expand Down
11 changes: 11 additions & 0 deletions examples/configs/distillation_math_megatron.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,17 @@ policy: &POLICY_BASE
enabled: true
empty_unused_memory_level: 0
activation_checkpointing: false
# recompute_granularity controls activation checkpointing depth.
# "full": recompute all activations (default, max memory savings).
# "selective": recompute only specific modules (see recompute_modules).
# Only takes effect when activation_checkpointing: true.
recompute_granularity: "full"
# Modules to selectively recompute when recompute_granularity="selective".
# MCore options: ["core_attn", "moe_act", "layernorm", "mla_up_proj", "mlp", "moe", "shared_experts"].
# Null defaults to ["core_attn"]. Full list and per-module constraints:
# https://github.com/NVIDIA/Megatron-LM/blob/d30c3ae5469fe3f6a64d4fd2e63b6e7f7844ea81/megatron/core/transformer/transformer_config.py#L483
# Use ["moe"] for MoE models to recompute expert activations only.
recompute_modules: null
converter_type: "Qwen3ForCausalLM"
tensor_model_parallel_size: 2
expert_tensor_parallel_size: 1
Expand Down
11 changes: 11 additions & 0 deletions examples/configs/dpo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,17 @@ policy:
force_reconvert_from_hf: False # Set to True to force reconvert of the model from Hugging Face
empty_unused_memory_level: 1
activation_checkpointing: false
# recompute_granularity controls activation checkpointing depth.
# "full": recompute all activations (default, max memory savings).
# "selective": recompute only specific modules (see recompute_modules).
# Only takes effect when activation_checkpointing: true.
recompute_granularity: "full"
# Modules to selectively recompute when recompute_granularity="selective".
# MCore options: ["core_attn", "moe_act", "layernorm", "mla_up_proj", "mlp", "moe", "shared_experts"].
# Null defaults to ["core_attn"]. Full list and per-module constraints:
# https://github.com/NVIDIA/Megatron-LM/blob/d30c3ae5469fe3f6a64d4fd2e63b6e7f7844ea81/megatron/core/transformer/transformer_config.py#L483
# Use ["moe"] for MoE models to recompute expert activations only.
recompute_modules: null
tensor_model_parallel_size: 2
expert_tensor_parallel_size: 1
expert_model_parallel_size: 1
Expand Down
11 changes: 11 additions & 0 deletions examples/configs/grpo_math_1B.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,17 @@ policy:
force_reconvert_from_hf: False # Set to True to force reconvert of the model from Hugging Face
empty_unused_memory_level: 1 # 1 is the minimum recommendation for RL since we almost always need to offload before beginning generation. Setting to 0 is faster, but you are more likely to run out of GPU memory.
activation_checkpointing: false
# recompute_granularity controls activation checkpointing depth.
# "full": recompute all activations (default, max memory savings).
# "selective": recompute only specific modules (see recompute_modules).
# Only takes effect when activation_checkpointing: true.
recompute_granularity: "full"
Comment thread
terrykong marked this conversation as resolved.
# Modules to selectively recompute when recompute_granularity="selective".
# MCore options: ["core_attn", "moe_act", "layernorm", "mla_up_proj", "mlp", "moe", "shared_experts"].
# Null defaults to ["core_attn"]. Full list and per-module constraints:
# https://github.com/NVIDIA/Megatron-LM/blob/d30c3ae5469fe3f6a64d4fd2e63b6e7f7844ea81/megatron/core/transformer/transformer_config.py#L483
# Use ["moe"] for MoE models to recompute expert activations only.
recompute_modules: null
converter_type: "Qwen2ForCausalLM"
tensor_model_parallel_size: 1
expert_tensor_parallel_size: 1
Expand Down
11 changes: 11 additions & 0 deletions examples/configs/grpo_math_1B_megatron.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,17 @@ policy:
force_reconvert_from_hf: False # Set to True to force reconvert of the model from Hugging Face
empty_unused_memory_level: 1 # 1 is the minimum recommendation for RL since we almost always need to offload before beginning generation. Setting to 0 is faster, but you are more likely to run out of GPU memory.
activation_checkpointing: false
# recompute_granularity controls activation checkpointing depth.
# "full": recompute all activations (default, max memory savings).
# "selective": recompute only specific modules (see recompute_modules).
# Only takes effect when activation_checkpointing: true.
recompute_granularity: "full"
# Modules to selectively recompute when recompute_granularity="selective".
# MCore options: ["core_attn", "moe_act", "layernorm", "mla_up_proj", "mlp", "moe", "shared_experts"].
# Null defaults to ["core_attn"]. Full list and per-module constraints:
# https://github.com/NVIDIA/Megatron-LM/blob/d30c3ae5469fe3f6a64d4fd2e63b6e7f7844ea81/megatron/core/transformer/transformer_config.py#L483
# Use ["moe"] for MoE models to recompute expert activations only.
recompute_modules: null
converter_type: "Qwen2ForCausalLM"
tensor_model_parallel_size: 1
expert_tensor_parallel_size: 1
Expand Down
11 changes: 11 additions & 0 deletions examples/configs/sft.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,17 @@ policy:
env_vars: {}
empty_unused_memory_level: 1
activation_checkpointing: false
# recompute_granularity controls activation checkpointing depth.
# "full": recompute all activations (default, max memory savings).
# "selective": recompute only specific modules (see recompute_modules).
# Only takes effect when activation_checkpointing: true.
recompute_granularity: "full"
# Modules to selectively recompute when recompute_granularity="selective".
# MCore options: ["core_attn", "moe_act", "layernorm", "mla_up_proj", "mlp", "moe", "shared_experts"].
# Null defaults to ["core_attn"]. Full list and per-module constraints:
# https://github.com/NVIDIA/Megatron-LM/blob/d30c3ae5469fe3f6a64d4fd2e63b6e7f7844ea81/megatron/core/transformer/transformer_config.py#L483
# Use ["moe"] for MoE models to recompute expert activations only.
recompute_modules: null
tensor_model_parallel_size: 1
expert_tensor_parallel_size: 1
expert_model_parallel_size: 1
Expand Down
23 changes: 20 additions & 3 deletions nemo_rl/models/megatron/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -669,9 +669,26 @@ def _apply_performance_config(model_cfg: Any, config: PolicyConfig) -> None:

# Activation checkpointing
if config["megatron_cfg"]["activation_checkpointing"]:
model_cfg.recompute_granularity = "full"
model_cfg.recompute_method = "uniform"
model_cfg.recompute_num_layers = 1
granularity = config["megatron_cfg"].get("recompute_granularity", "full")
model_cfg.recompute_granularity = granularity
if granularity == "full":
model_cfg.recompute_method = "uniform"
model_cfg.recompute_num_layers = 1
elif granularity == "selective":
recompute_modules = config["megatron_cfg"].get("recompute_modules")
if recompute_modules is not None:
Comment thread
seonjinn marked this conversation as resolved.
# NOTE: MCore validates recompute_modules in TransformerConfig.__post_init__,
# but that validation doesn't re-run after attribute assignment here.
# Valid values: core_attn, moe_act, layernorm, mla_up_proj, mlp, moe, shared_experts
# See: https://github.com/NVIDIA/Megatron-LM/blob/d30c3ae5469fe3f6a64d4fd2e63b6e7f7844ea81/megatron/core/transformer/transformer_config.py#L1365
# Tracking: https://github.com/NVIDIA-NeMo/RL/issues/2291
model_cfg.recompute_modules = recompute_modules
# else: MCore defaults to ["core_attn"] when recompute_modules is None
else:
raise ValueError(
f"Invalid recompute_granularity: {granularity!r}. "
"Valid options are 'full' or 'selective'."
)
Comment thread
terrykong marked this conversation as resolved.

# Activation function validation
if not model_cfg.gated_linear_unit:
Expand Down
10 changes: 10 additions & 0 deletions nemo_rl/models/policy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,16 @@ class MegatronConfig(TypedDict):
# Setting to 0 is faster, but you are more likely to run out of GPU memory. In SFT/DPO, the default is 0.
empty_unused_memory_level: int
activation_checkpointing: bool
# Recompute granularity: "full" recomputes all activations, "selective" recomputes
# only specific modules (see recompute_modules). "selective" typically saves ~10-18GB
# for MoE models while retaining higher throughput than "full".
recompute_granularity: NotRequired[Literal["full", "selective"]]
# Modules to selectively recompute when recompute_granularity="selective".
# MCore valid options: ["core_attn", "moe_act", "layernorm", "mla_up_proj", "mlp", "moe", "shared_experts"].
# Defaults to ["core_attn"] when None. Full list and per-module constraints:
# https://github.com/NVIDIA/Megatron-LM/blob/d30c3ae5469fe3f6a64d4fd2e63b6e7f7844ea81/megatron/core/transformer/transformer_config.py#L483
# when None. Use ["moe"] to recompute only expert activations (production-proven config).
recompute_modules: NotRequired[list[str] | None]
tensor_model_parallel_size: int
pipeline_model_parallel_size: int
num_layers_in_first_pipeline_stage: int | None
Expand Down
87 changes: 87 additions & 0 deletions tests/unit/models/megatron/test_megatron_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,93 @@ def test_fp8_param_warning(self):
with pytest.warns(UserWarning, match="fp8_param=True sometimes causes NaN"):
_apply_performance_config(model_cfg, config)

def test_recompute_granularity_full_explicit(self):
"""granularity='full' sets uniform method with 1 layer."""
from nemo_rl.models.megatron.setup import _apply_performance_config

model_cfg = MagicMock()
model_cfg.gated_linear_unit = True
config = {
"megatron_cfg": {
"activation_checkpointing": True,
"recompute_granularity": "full",
"apply_rope_fusion": False,
"bias_activation_fusion": False,
"gradient_accumulation_fusion": False,
}
}

_apply_performance_config(model_cfg, config)

assert model_cfg.recompute_granularity == "full"
assert model_cfg.recompute_method == "uniform"
assert model_cfg.recompute_num_layers == 1

def test_recompute_granularity_selective_with_modules(self):
"""granularity='selective' with explicit modules sets recompute_modules."""
from nemo_rl.models.megatron.setup import _apply_performance_config

model_cfg = MagicMock()
model_cfg.gated_linear_unit = True
modules = ["core_attn", "moe_act"]
config = {
"megatron_cfg": {
"activation_checkpointing": True,
"recompute_granularity": "selective",
"recompute_modules": modules,
"apply_rope_fusion": False,
"bias_activation_fusion": False,
"gradient_accumulation_fusion": False,
}
}

_apply_performance_config(model_cfg, config)

assert model_cfg.recompute_granularity == "selective"
assert model_cfg.recompute_modules == modules

def test_recompute_granularity_selective_without_modules_uses_mcore_default(self):
"""granularity='selective' without recompute_modules leaves attr untouched (MCore default applies)."""
from nemo_rl.models.megatron.setup import _apply_performance_config

model_cfg = MagicMock(spec=["gated_linear_unit"])
model_cfg.gated_linear_unit = True
config = {
"megatron_cfg": {
"activation_checkpointing": True,
"recompute_granularity": "selective",
"apply_rope_fusion": False,
"bias_activation_fusion": False,
"gradient_accumulation_fusion": False,
}
}

_apply_performance_config(model_cfg, config)

assert model_cfg.recompute_granularity == "selective"
assert not hasattr(model_cfg, "recompute_modules")
assert not hasattr(model_cfg, "recompute_method")
assert not hasattr(model_cfg, "recompute_num_layers")

def test_recompute_granularity_invalid_raises(self):
"""Invalid granularity raises ValueError with a helpful message."""
from nemo_rl.models.megatron.setup import _apply_performance_config

model_cfg = MagicMock()
model_cfg.gated_linear_unit = True
config = {
"megatron_cfg": {
"activation_checkpointing": True,
"recompute_granularity": "block",
"apply_rope_fusion": False,
"bias_activation_fusion": False,
"gradient_accumulation_fusion": False,
}
}

with pytest.raises(ValueError, match="Invalid recompute_granularity"):
_apply_performance_config(model_cfg, config)


@pytest.mark.mcore
class TestValidateOptimizerConfig:
Expand Down
2 changes: 2 additions & 0 deletions tests/unit/reference_configs/distillation_math.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ policy: &POLICY_BASE
force_reconvert_from_hf: False # Set to True to force reconvert of the model from Hugging Face
empty_unused_memory_level: 0
activation_checkpointing: false
recompute_granularity: "full"
recompute_modules: null
converter_type: "Qwen3ForCausalLM"
tensor_model_parallel_size: 2
expert_tensor_parallel_size: 1
Expand Down
2 changes: 2 additions & 0 deletions tests/unit/reference_configs/dpo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ policy:
force_reconvert_from_hf: False # Set to True to force reconvert of the model from Hugging Face
empty_unused_memory_level: 1
activation_checkpointing: false
recompute_granularity: "full"
recompute_modules: null
tensor_model_parallel_size: 2
expert_tensor_parallel_size: 1
expert_model_parallel_size: 1
Expand Down
2 changes: 2 additions & 0 deletions tests/unit/reference_configs/grpo_math_1B.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ policy:
force_reconvert_from_hf: False # Set to True to force reconvert of the model from Hugging Face
empty_unused_memory_level: 1 # 1 is the minimum recommendation for RL since we almost always need to offload before beginning generation. Setting to 0 is faster, but you are more likely to run out of GPU memory.
activation_checkpointing: false
recompute_granularity: "full"
recompute_modules: null
converter_type: "Qwen2ForCausalLM"
tensor_model_parallel_size: 1
expert_tensor_parallel_size: 1
Expand Down
2 changes: 2 additions & 0 deletions tests/unit/reference_configs/sft.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ policy:
env_vars: {}
empty_unused_memory_level: 1
activation_checkpointing: false
recompute_granularity: "full"
recompute_modules: null
tensor_model_parallel_size: 1
expert_tensor_parallel_size: 1
expert_model_parallel_size: 1
Expand Down
Loading