@@ -1134,6 +1134,85 @@ def test_moe_logging_without_z_loss(
11341134 assert "z_loss" not in track_names
11351135 assert len (track_names ) == 1
11361136
1137+ @mock .patch ("megatron.bridge.training.utils.train_utils.get_num_microbatches" )
1138+ @mock .patch ("megatron.bridge.training.utils.train_utils.reduce_max_stat_across_model_parallel_group" )
1139+ @mock .patch ("megatron.bridge.training.utils.train_utils.get_world_size_safe" )
1140+ @mock .patch ("megatron.bridge.training.utils.train_utils.print_rank_last" )
1141+ @mock .patch ("megatron.bridge.training.utils.train_utils.track_moe_metrics" )
1142+ @mock .patch ("megatron.bridge.training.utils.train_utils.report_runtime" )
1143+ @mock .patch ("megatron.bridge.training.utils.train_utils.report_throughput" )
1144+ @mock .patch ("megatron.bridge.training.utils.train_utils.report_l2_norm_grad" )
1145+ def test_moe_logging_hybrid_pattern_default_freq (
1146+ self ,
1147+ mock_report_l2_norm_grad ,
1148+ mock_report_throughput ,
1149+ mock_report_runtime ,
1150+ mock_track_moe ,
1151+ mock_print_rank_last ,
1152+ mock_get_world_size ,
1153+ mock_reduce_lr ,
1154+ mock_get_microbatches ,
1155+ mock_config ,
1156+ mock_global_state ,
1157+ loss_dict ,
1158+ ):
1159+ """Hybrid-MoE models must derive the averaging denominator from the pattern.
1160+
1161+ Regression: recipes such as ``nemotron_3_nano`` set a ``hybrid_layer_pattern``
1162+ with a handful of ``E`` (MoE) layers but leave ``moe_layer_freq`` at the
1163+ inherited default (``1``). ``num_layers`` must stay at full depth for the
1164+ tracker storage size (the NCCL-deadlock fix), while ``moe_layer_freq`` is
1165+ passed as the per-layer binary MoE mask derived from the pattern so aux/z-loss
1166+ are averaged over only the MoE layers.
1167+ """
1168+ total_loss_dict = self .get_fresh_total_loss_dict ()
1169+
1170+ mock_report_l2_norm_grad .return_value = {}
1171+ mock_report_throughput .return_value = {}
1172+ mock_report_runtime .return_value = {}
1173+ mock_get_microbatches .return_value = 8
1174+ mock_reduce_lr .return_value = 1e-4
1175+ mock_get_world_size .return_value = 32
1176+
1177+ # Hybrid Mamba+MoE config: pattern places MoE ("E") on 2 of 8 layers,
1178+ # while moe_layer_freq is the inherited default (1, not None).
1179+ pattern = "M-M*E-M*E-"
1180+ mock_config .model .num_moe_experts = 8
1181+ mock_config .model .moe_router_load_balancing_type = "aux_loss"
1182+ mock_config .model .moe_z_loss_coeff = None
1183+ mock_config .model .moe_per_layer_logging = True
1184+ mock_config .model .num_layers = len (pattern )
1185+ mock_config .model .moe_layer_freq = 1
1186+ mock_config .model .mtp_num_layers = None
1187+ mock_config .model .is_hybrid_model = True
1188+ mock_config .model .hybrid_layer_pattern = pattern
1189+
1190+ training_log (
1191+ loss_dict = loss_dict ,
1192+ total_loss_dict = total_loss_dict ,
1193+ learning_rate = 1e-4 ,
1194+ decoupled_learning_rate = None ,
1195+ loss_scale = 1024.0 ,
1196+ report_memory_flag = False ,
1197+ skipped_iter = 0 ,
1198+ grad_norm = 2.5 ,
1199+ params_norm = 15.2 ,
1200+ num_zeros_in_grad = 0 ,
1201+ config = mock_config ,
1202+ global_state = mock_global_state ,
1203+ history_wct = None ,
1204+ model = None ,
1205+ )
1206+
1207+ mock_track_moe .assert_called_once ()
1208+ call_args = mock_track_moe .call_args
1209+ # Storage size stays at full depth so PP tensor sizes match (deadlock fix).
1210+ assert call_args .kwargs ["num_layers" ] == len (pattern )
1211+ # Averaging denominator is the per-layer MoE mask, summing to only the "E"s.
1212+ moe_layer_freq = call_args .kwargs ["moe_layer_freq" ]
1213+ assert moe_layer_freq == [1 if c == "E" else 0 for c in pattern ]
1214+ assert sum (moe_layer_freq ) == pattern .count ("E" ) == 2
1215+
11371216 @mock .patch ("megatron.bridge.training.utils.train_utils.get_num_microbatches" )
11381217 @mock .patch ("megatron.bridge.training.utils.train_utils.reduce_max_stat_across_model_parallel_group" )
11391218 @mock .patch ("megatron.bridge.training.utils.train_utils.get_world_size_safe" )
0 commit comments