Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
feee53e
Add fine-grained activation offloading for Megatron policy
seonjinn Apr 17, 2026
0b530c4
Update nemo_rl/models/policy/__init__.py
seonjinn Apr 23, 2026
d5df80a
Update nemo_rl/models/megatron/setup.py
seonjinn Apr 23, 2026
f742bf8
Merge remote-tracking branch 'origin/main' into sj/fine-grained-activ…
seonjinn May 14, 2026
e237987
fix: remove stray paren in setup.py raising ValueError
seonjinn May 14, 2026
4f4681c
Merge remote-tracking branch 'origin/main' into sj/fine-grained-activ…
seonjinn May 15, 2026
06b4d4a
fix: pin NeMo Gym docs URL to v0.2.1 (latest 404)
seonjinn May 15, 2026
2b33ad3
Merge branch 'main' into sj/fine-grained-activation-offload
seonjinn May 15, 2026
5225217
test: add unit tests for fine_grained_activation_offloading branch
seonjinn May 16, 2026
211e31a
Update nemo_rl/models/megatron/setup.py
seonjinn May 19, 2026
502d2dd
Update nemo_rl/models/megatron/setup.py
seonjinn May 19, 2026
da947f8
Update tests/unit/models/megatron/test_megatron_setup.py
seonjinn May 19, 2026
fcbd22b
Merge main into sj/fine-grained-activation-offload
seonjinn May 19, 2026
254c7d0
Fix syntax error from main merge in offload test
seonjinn May 19, 2026
625b25f
Merge branch 'main' into sj/fine-grained-activation-offload
seonjinn May 22, 2026
ed86517
Address review comments on fine_grained_activation_offloading
seonjinn May 23, 2026
d89ac84
Address remaining terrykong review comments
seonjinn May 23, 2026
aaece7b
Allow offload_modules to be None in MegatronConfig TypedDict
seonjinn May 23, 2026
bcccabf
Merge branch 'main' into sj/fine-grained-activation-offload
seonjinn May 23, 2026
08d121d
Merge branch 'main' into sj/fine-grained-activation-offload
seonjinn May 26, 2026
0bed72f
Merge branch 'main' into sj/fine-grained-activation-offload
seonjinn Aug 12, 2026
7051195
fix(megatron): honor activation offload overrides
seonjinn Aug 12, 2026
602e7b0
docs: clarify Megatron activation CPU offload
seonjinn Aug 12, 2026
5ddbde8
docs: correct activation offload graph requirement
seonjinn Aug 12, 2026
2f39df6
test(megatron): model activation offload happy path
seonjinn Aug 12, 2026
bf3407f
test(megatron): preserve activation offload warmup
seonjinn Aug 13, 2026
3a3bebd
fix(megatron): preserve activation offload warmup
seonjinn Aug 13, 2026
9c79efe
test(megatron): cover offload manager lifecycle
seonjinn Aug 13, 2026
aeeb3d6
fix(megatron): suspend activation offload manager
seonjinn Aug 13, 2026
0139846
test(megatron): assert offload warmup state
seonjinn Aug 13, 2026
ea09321
Merge remote-tracking branch 'origin/main' into codex/pr2279-main-syn…
seonjinn Aug 13, 2026
b9ed160
fix(megatron): address activation offload review feedback
seonjinn Aug 15, 2026
fef2afb
Merge remote-tracking branch 'origin/main' into codex/pr2279-main-syn…
seonjinn Aug 15, 2026
6a4f1fc
docs(megatron): future-proof offload module references
seonjinn Aug 15, 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
9 changes: 9 additions & 0 deletions examples/configs/distillation_math.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,15 @@ 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
# Offload specific module activations to CPU. Works for both dense and MoE
# models. Requires transformer_engine. Different from optimizer_cpu_offload
# which offloads optimizer states.
fine_grained_activation_offloading: false
# Modules to offload when fine_grained_activation_offloading is true.
# Valid options: ["attn_norm", "qkv_linear", "core_attn", "attn_proj",
# "mlp_norm", "expert_fc1", "moe_act"]. "attn_proj" requires "core_attn".
# See: https://github.com/NVIDIA/Megatron-LM/blob/d30c3ae5469fe3f6a64d4fd2e63b6e7f7844ea81/megatron/core/transformer/transformer_config.py#L1440-L1448
offload_modules: null
converter_type: "Qwen3ForCausalLM"
tensor_model_parallel_size: 2
expert_tensor_parallel_size: 1
Expand Down
9 changes: 9 additions & 0 deletions examples/configs/distillation_math_megatron.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ policy: &POLICY_BASE
enabled: true
empty_unused_memory_level: 0
activation_checkpointing: false
# Offload specific module activations to CPU. Works for both dense and MoE
# models. Requires transformer_engine. Different from optimizer_cpu_offload
# which offloads optimizer states.
fine_grained_activation_offloading: false
# Modules to offload when fine_grained_activation_offloading is true.
# Valid options: ["attn_norm", "qkv_linear", "core_attn", "attn_proj",
# "mlp_norm", "expert_fc1", "moe_act"]. "attn_proj" requires "core_attn".
# See: https://github.com/NVIDIA/Megatron-LM/blob/d30c3ae5469fe3f6a64d4fd2e63b6e7f7844ea81/megatron/core/transformer/transformer_config.py#L1440-L1448
offload_modules: null
converter_type: "Qwen3ForCausalLM"
tensor_model_parallel_size: 2
expert_tensor_parallel_size: 1
Expand Down
9 changes: 9 additions & 0 deletions examples/configs/dpo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,15 @@ 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
# Offload specific module activations to CPU. Works for both dense and MoE
# models. Requires transformer_engine. Different from optimizer_cpu_offload
# which offloads optimizer states.
fine_grained_activation_offloading: false
# Modules to offload when fine_grained_activation_offloading is true.
# Valid options: ["attn_norm", "qkv_linear", "core_attn", "attn_proj",
# "mlp_norm", "expert_fc1", "moe_act"]. "attn_proj" requires "core_attn".
# See: https://github.com/NVIDIA/Megatron-LM/blob/d30c3ae5469fe3f6a64d4fd2e63b6e7f7844ea81/megatron/core/transformer/transformer_config.py#L1440-L1448
offload_modules: null
tensor_model_parallel_size: 2
expert_tensor_parallel_size: 1
expert_model_parallel_size: 1
Expand Down
9 changes: 9 additions & 0 deletions examples/configs/grpo_math_1B.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,15 @@ 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
# Offload specific module activations to CPU. Works for both dense and MoE
# models. Requires transformer_engine. Different from optimizer_cpu_offload
# which offloads optimizer states.
fine_grained_activation_offloading: false
# Modules to offload when fine_grained_activation_offloading is true.
# Valid options: ["attn_norm", "qkv_linear", "core_attn", "attn_proj",
# "mlp_norm", "expert_fc1", "moe_act"]. "attn_proj" requires "core_attn".
# See: https://github.com/NVIDIA/Megatron-LM/blob/d30c3ae5469fe3f6a64d4fd2e63b6e7f7844ea81/megatron/core/transformer/transformer_config.py#L1440-L1448
offload_modules: null
converter_type: "Qwen2ForCausalLM"
tensor_model_parallel_size: 1
expert_tensor_parallel_size: 1
Expand Down
9 changes: 9 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,15 @@ 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
# Offload specific module activations to CPU. Works for both dense and MoE
# models. Requires transformer_engine. Different from optimizer_cpu_offload
# which offloads optimizer states.
fine_grained_activation_offloading: false
# Modules to offload when fine_grained_activation_offloading is true.
# Valid options: ["attn_norm", "qkv_linear", "core_attn", "attn_proj",
# "mlp_norm", "expert_fc1", "moe_act"]. "attn_proj" requires "core_attn".
# See: https://github.com/NVIDIA/Megatron-LM/blob/d30c3ae5469fe3f6a64d4fd2e63b6e7f7844ea81/megatron/core/transformer/transformer_config.py#L1440-L1448
offload_modules: null
converter_type: "Qwen2ForCausalLM"
tensor_model_parallel_size: 1
expert_tensor_parallel_size: 1
Expand Down
9 changes: 9 additions & 0 deletions examples/configs/sft.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,15 @@ policy:
env_vars: {}
empty_unused_memory_level: 1
activation_checkpointing: false
# Offload specific module activations to CPU. Works for both dense and MoE
# models. Requires transformer_engine. Different from optimizer_cpu_offload
# which offloads optimizer states.
fine_grained_activation_offloading: false
# Modules to offload when fine_grained_activation_offloading is true.
# Valid options: ["attn_norm", "qkv_linear", "core_attn", "attn_proj",
# "mlp_norm", "expert_fc1", "moe_act"]. "attn_proj" requires "core_attn".
# See: https://github.com/NVIDIA/Megatron-LM/blob/d30c3ae5469fe3f6a64d4fd2e63b6e7f7844ea81/megatron/core/transformer/transformer_config.py#L1440-L1448
offload_modules: null
tensor_model_parallel_size: 1
expert_tensor_parallel_size: 1
expert_model_parallel_size: 1
Expand Down
14 changes: 14 additions & 0 deletions nemo_rl/models/megatron/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,20 @@ def _apply_performance_config(model_cfg: Any, config: PolicyConfig) -> None:
"Refer to https://github.com/NVIDIA-NeMo/RL/issues/1164 for latest updates with this issue."
)

# Megatron validates module names and per-model-type compatibility.
fine_grained_activation_offloading = config["megatron_cfg"].get(
"fine_grained_activation_offloading"
)
if fine_grained_activation_offloading:
Comment thread
terrykong marked this conversation as resolved.
Resolved
offload_modules = config["megatron_cfg"].get("offload_modules")
if not isinstance(offload_modules, list) or not offload_modules:
raise ValueError(
"offload_modules must be a non-empty list when "
"fine_grained_activation_offloading is True."
)
model_cfg.fine_grained_activation_offloading = True
model_cfg.offload_modules = offload_modules
Comment thread
seonjinn marked this conversation as resolved.
Resolved


def _validate_optimizer_config(config: PolicyConfig) -> None:
"""Validate optimizer configuration."""
Expand Down
11 changes: 11 additions & 0 deletions nemo_rl/models/policy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,17 @@ class MegatronConfig(TypedDict):
moe_token_dispatcher_type: str
# Can be used only with 'alltoall' token dispatcher
moe_shared_expert_overlap: bool
# Offload specific module activations to CPU to reduce peak GPU memory.
# Works with both dense and MoE models. Different from
# optimizer_cpu_offload which offloads optimizer states.
# Requires transformer_engine implementation.
fine_grained_activation_offloading: NotRequired[bool]
# Modules to offload when fine_grained_activation_offloading is True.
# Required (no default). Valid values:
# "attn_norm", "qkv_linear", "core_attn", "attn_proj", "mlp_norm",
# "expert_fc1", "moe_act". Note: "attn_proj" requires "core_attn".
# See: https://github.com/NVIDIA/Megatron-LM/blob/d30c3ae5469fe3f6a64d4fd2e63b6e7f7844ea81/megatron/core/transformer/transformer_config.py#L1440-L1448
offload_modules: NotRequired[list[str]]
Comment thread
seonjinn marked this conversation as resolved.
Resolved
# Enable grouped GEMM for MoE experts via CUTLASS. Significant throughput
# gain when multiple experts are assigned per rank (num_local_experts > 1).
# Requires TE >= 1.11.0 for FP8 and Ampere (sm_80) or newer.
Expand Down
93 changes: 93 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,99 @@ 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_fine_grained_activation_offloading_enabled(self):
"""Test happy path: enabled with non-empty offload_modules list."""
from nemo_rl.models.megatron.setup import _apply_performance_config

model_cfg = MagicMock()
model_cfg.gated_linear_unit = True
offload_modules = ["mlp", "moe_act"]
Comment thread
seonjinn marked this conversation as resolved.
Resolved
config = {
"megatron_cfg": {
"activation_checkpointing": False,
"apply_rope_fusion": False,
"bias_activation_fusion": False,
"gradient_accumulation_fusion": False,
"fine_grained_activation_offloading": True,
"offload_modules": offload_modules,
}
}

_apply_performance_config(model_cfg, config)

assert model_cfg.fine_grained_activation_offloading is True
assert model_cfg.offload_modules == offload_modules

def test_fine_grained_activation_offloading_disabled_skips(self):
"""When flag is False (default), no offload attrs should be set."""
Comment thread
terrykong marked this conversation as resolved.
Resolved
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": False,
"apply_rope_fusion": False,
"bias_activation_fusion": False,
"gradient_accumulation_fusion": False,
}
}

_apply_performance_config(model_cfg, config)

assert not hasattr(model_cfg, "fine_grained_activation_offloading")
assert not hasattr(model_cfg, "offload_modules")

@pytest.mark.parametrize(
"offload_modules",
[[], None, "mlp", 42],
ids=["empty_list", "none", "string", "int"],
)
def test_fine_grained_activation_offloading_invalid_modules_raises(
self, offload_modules
):
"""offload_modules must be a non-empty list when feature is enabled."""
from nemo_rl.models.megatron.setup import _apply_performance_config

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

with pytest.raises(
ValueError, match="offload_modules must be a non-empty list"
):
_apply_performance_config(model_cfg, config)
Comment thread
seonjinn marked this conversation as resolved.
Resolved

def test_fine_grained_activation_offloading_missing_modules_raises(self):
"""When enabled but offload_modules key is absent, defaults to None → raises."""
from nemo_rl.models.megatron.setup import _apply_performance_config

model_cfg = MagicMock()
model_cfg.gated_linear_unit = True
config = {
Comment thread
seonjinn marked this conversation as resolved.
Resolved
"megatron_cfg": {
"activation_checkpointing": False,
"apply_rope_fusion": False,
"bias_activation_fusion": False,
"gradient_accumulation_fusion": False,
"fine_grained_activation_offloading": True,
}
}

with pytest.raises(
ValueError, match="offload_modules must be a non-empty list"
):
_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
fine_grained_activation_offloading: false
offload_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
fine_grained_activation_offloading: false
offload_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
fine_grained_activation_offloading: false
offload_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
fine_grained_activation_offloading: false
offload_modules: null
tensor_model_parallel_size: 1
expert_tensor_parallel_size: 1
expert_model_parallel_size: 1
Expand Down
Loading