2828from megatron .bridge .training .state import GlobalState
2929from megatron .bridge .training .utils .train_utils import (
3030 LinearForLastLayer ,
31+ _get_num_moe_layers ,
3132 calc_params_l2_norm ,
3233 create_value_head_hook ,
3334 freeze_moe_router ,
@@ -83,6 +84,8 @@ def make_default_model_config():
8384 num_layers = 24 ,
8485 moe_layer_freq = 1 ,
8586 mtp_num_layers = None ,
87+ mtp_use_repeated_layer = False ,
88+ hybrid_layer_pattern = None ,
8689 kv_channels = 128 ,
8790 num_attention_heads = 32 ,
8891 hidden_size = 4096 ,
@@ -111,6 +114,62 @@ def make_default_model_config():
111114 )
112115
113116
117+ @pytest .mark .parametrize (
118+ ("num_layers" , "mtp_num_layers" , "hybrid_pattern" , "moe_layer_freq" , "repeated_mtp" , "expected" ),
119+ [
120+ pytest .param (
121+ 52 ,
122+ 2 ,
123+ "MEMEM*EMEMEM*EMEMEM*EMEMEM*EMEMEM*EMEMEMEM*EMEMEMEME/*E/*E" ,
124+ None ,
125+ True ,
126+ 24 ,
127+ id = "nemotron_3_5" ,
128+ ),
129+ pytest .param (
130+ 88 ,
131+ 2 ,
132+ "MEMEMEM*EMEMEMEM*EMEMEMEM*EMEMEMEMEM*EMEMEMEMEM*EMEMEMEMEM*EMEMEMEMEM*EMEMEMEM*EMEMEMEME/*E/*E" ,
133+ None ,
134+ True ,
135+ 41 ,
136+ id = "nemotron_3_super" ,
137+ ),
138+ pytest .param (
139+ 108 ,
140+ 2 ,
141+ (
142+ "MEMEMEM*EMEMEM*EMEMEMEM*EMEMEMEM*EMEMEM*EMEMEMEM*EMEMEMEM*"
143+ "EMEMEM*EMEMEMEM*EMEMEMEM*EMEMEM*EMEMEMEM*EMEMEMEME/*E/*E"
144+ ),
145+ None ,
146+ True ,
147+ 49 ,
148+ id = "nemotron_3_ultra" ,
149+ ),
150+ pytest .param (61 , 1 , None , [0 ] * 3 + [1 ] * 58 , False , 59 , id = "deepseek_v3" ),
151+ pytest .param (45 , 0 , None , [0 ] * 3 + [1 ] * 42 , False , 42 , id = "step_3_7" ),
152+ pytest .param (4 , 2 , "MEME/*E/*E" , None , False , 4 , id = "hybrid_distinct_mtp" ),
153+ pytest .param (4 , 2 , "MEME/**/**" , None , False , 2 , id = "hybrid_dense_mtp" ),
154+ pytest .param (4 , 2 , None , [1 , 0 , 1 , 1 ], True , 4 , id = "non_hybrid_repeated_mtp" ),
155+ pytest .param (4 , 2 , None , [1 , 0 , 1 , 1 ], False , 5 , id = "non_hybrid_distinct_mtp" ),
156+ pytest .param (4 , 2 , None , [1 , 0 , 1 , 0 ], False , 2 , id = "non_hybrid_dense_mtp" ),
157+ ],
158+ )
159+ def test_get_num_moe_layers (num_layers , mtp_num_layers , hybrid_pattern , moe_layer_freq , repeated_mtp , expected ):
160+ """Test representative architecture and edge-case contributor counts."""
161+ model_config = SimpleNamespace (
162+ num_layers = num_layers ,
163+ moe_layer_freq = moe_layer_freq ,
164+ mtp_num_layers = mtp_num_layers ,
165+ mtp_use_repeated_layer = repeated_mtp ,
166+ is_hybrid_model = hybrid_pattern is not None ,
167+ hybrid_layer_pattern = hybrid_pattern ,
168+ )
169+
170+ assert _get_num_moe_layers (model_config ) == expected
171+
172+
114173class TestTrainingLog :
115174 """Test suite for the training_log function."""
116175
@@ -991,6 +1050,13 @@ def test_memory_reporting_checkpoint_resume(
9911050 assert result is True
9921051 mock_report_memory .assert_called_once ()
9931052
1053+ @pytest .mark .parametrize (
1054+ ("model_num_layers" , "mtp_num_layers" , "hybrid_pattern" , "moe_layer_freq" , "expected_num_moe_layers" ),
1055+ [
1056+ pytest .param (12 , None , None , 2 , 6 , id = "non_hybrid" ),
1057+ pytest .param (4 , 2 , "MMME/*E/*E" , None , 3 , id = "hybrid" ),
1058+ ],
1059+ )
9941060 @mock .patch ("megatron.bridge.training.utils.train_utils.get_num_microbatches" )
9951061 @mock .patch ("megatron.bridge.training.utils.train_utils.reduce_max_stat_across_model_parallel_group" )
9961062 @mock .patch ("megatron.bridge.training.utils.train_utils.get_world_size_safe" )
@@ -1012,6 +1078,11 @@ def test_moe_logging(
10121078 mock_config ,
10131079 mock_global_state ,
10141080 loss_dict ,
1081+ model_num_layers ,
1082+ mtp_num_layers ,
1083+ hybrid_pattern ,
1084+ moe_layer_freq ,
1085+ expected_num_moe_layers ,
10151086 ):
10161087 """Test MoE (Mixture of Experts) logging when enabled."""
10171088 # Get fresh total_loss_dict for this test
@@ -1030,9 +1101,11 @@ def test_moe_logging(
10301101 mock_config .model .moe_router_load_balancing_type = "aux_loss"
10311102 mock_config .model .moe_z_loss_coeff = 0.1
10321103 mock_config .model .moe_per_layer_logging = True
1033- mock_config .model .num_layers = 12
1034- mock_config .model .moe_layer_freq = 2
1035- mock_config .model .mtp_num_layers = None
1104+ mock_config .model .num_layers = model_num_layers
1105+ mock_config .model .moe_layer_freq = moe_layer_freq
1106+ mock_config .model .mtp_num_layers = mtp_num_layers
1107+ mock_config .model .is_hybrid_model = hybrid_pattern is not None
1108+ mock_config .model .hybrid_layer_pattern = hybrid_pattern
10361109
10371110 training_log (
10381111 loss_dict = loss_dict ,
@@ -1056,6 +1129,9 @@ def test_moe_logging(
10561129 call_args = mock_track_moe .call_args
10571130 assert "load_balancing_loss" in call_args .kwargs ["track_names" ]
10581131 assert "z_loss" in call_args .kwargs ["track_names" ]
1132+ assert call_args .kwargs ["num_layers" ] == model_num_layers
1133+ assert call_args .kwargs ["num_moe_layers" ] == expected_num_moe_layers
1134+ assert call_args .kwargs ["mtp_num_layers" ] == mtp_num_layers
10591135
10601136 @mock .patch ("megatron.bridge.training.utils.train_utils.get_num_microbatches" )
10611137 @mock .patch ("megatron.bridge.training.utils.train_utils.reduce_max_stat_across_model_parallel_group" )
0 commit comments