已合并
feat(monitor): add debug logs for issue 292 #814
feat(monitor): add debug logs for issue 292 #814
已合并
qiange_star创建于 6月22日
共 4 个文件变更+303-20
@@ -352,6 +352,16 @@ class TrainerMon:
352 self.cc_pre_hook = self.cc_distribution.get('cc_pre_hook', False)352 self.cc_pre_hook = self.cc_distribution.get('cc_pre_hook', False)
353 353 
354 self.common_info()354 self.common_info()
355+ logger.debug(
356+ "set_config normalized: "
357+ f"start_step={self.start_step}, collect_times={self.collect_times}, "
358+ f"step_interval={self.step_interval}, format={self.format}, ops_count={len(self.ops)}, "
359+ f"module_ranks={self.module_rank_list}, xy_distribution={self.xy_distribution}, "
360+ f"wg_distribution={self.wg_distribution}, mv_distribution={self.mv_distribution}, "
361+ f"param_distribution={self.param_distribution}, cc_enabled={self.cc_distribution.get('enable', False)}, "
362+ f"monitor_mbs_grad={self.monitor_mbs_grad}, print_struct={self.print_struct}, "
363+ f"stack_info={self.stack_info}, recording_l2_features={self.recording_l2_features}"
364+ )
355 365 
356 # 初始化AnomalyData工厂366 # 初始化AnomalyData工厂
357 alert_setting = self.config.get('alert', {"rules": []})367 alert_setting = self.config.get('alert', {"rules": []})
@@ -371,8 +381,14 @@ class TrainerMon:
371 381 
372 writer = FORMAT_MAPPING[self.format]382 writer = FORMAT_MAPPING[self.format]
373 self.step_count_per_record = self.config.get('step_count_per_record', 1)383 self.step_count_per_record = self.config.get('step_count_per_record', 1)
384+ should_init_writer = (self.rank in self.module_rank_list) or len(self.module_rank_list) == 0
385+ logger.debug(
386+ f"set_config writer setup: writer={writer.__name__}, should_init_writer={should_init_writer}, "
387+ f"tensorboard_dir={self.tensorboard_dir}, step_count_per_record={self.step_count_per_record}, "
388+ f"anomaly_dump={self.anomaly_data_factory is not None}"
389+ )
374 390 
375- if (self.rank in self.module_rank_list) or len(self.module_rank_list) == 0:391+ if should_init_writer:
376 self.summary_writer = writer(392 self.summary_writer = writer(
377 WriterInput(393 WriterInput(
378 self.tensorboard_dir,394 self.tensorboard_dir,
@@ -516,9 +532,18 @@ class TrainerMon:
516 532 
517 def generate_wgrad_metrics(self, post_grad_dict):533 def generate_wgrad_metrics(self, post_grad_dict):
518 if not self.wg_distribution:534 if not self.wg_distribution:
535+ logger.debug("generate_wgrad_metrics skipped because wg_distribution is disabled")
519 return {}, {}536 return {}, {}
520 537 
538+ logger.debug(
539+ f"generate_wgrad_metrics start: post_grad_count={len(post_grad_dict)}, "
540+ f"existing_post_metric_count={len(self.grad_context.post)}"
541+ )
521 get_metrics(self.ops, post_grad_dict, self.eps, self.grad_context.post)542 get_metrics(self.ops, post_grad_dict, self.eps, self.grad_context.post)
543+ logger.debug(
544+ f"generate_wgrad_metrics finish: post_metric_count={len(self.grad_context.post)}, "
545+ f"pre_metric_count={len(self.grad_context.pre)}"
546+ )
522 return self.grad_context.post, self.grad_context.pre547 return self.grad_context.post, self.grad_context.pre
523 548 
524 def generate_xy_metrics(self):549 def generate_xy_metrics(self):
@@ -604,8 +629,13 @@ class TrainerMon:
604 629 
605 def write_grad_tb(self, step):630 def write_grad_tb(self, step):
606 if not self.wg_distribution:631 if not self.wg_distribution:
632+ logger.debug(f"write_grad_tb skipped at step={step} because wg_distribution is disabled")
607 return633 return
608 634 
635+ logger.debug(
636+ f"write_grad_tb start: step={step}, monitor_mbs_grad={self.monitor_mbs_grad}, "
637+ f"pre_metric_count={len(self.grad_context.pre)}, post_metric_count={len(self.grad_context.post)}"
638+ )
609 self.summary_writer.write_metrics(639 self.summary_writer.write_metrics(
610 self.ops, self.grad_context.pre, step, 'grad_unreduced', use_micro_step=self.monitor_mbs_grad640 self.ops, self.grad_context.pre, step, 'grad_unreduced', use_micro_step=self.monitor_mbs_grad
611 )641 )
@@ -615,6 +645,12 @@ class TrainerMon:
615 # in DDP by default use params_have_main_grad645 # in DDP by default use params_have_main_grad
616 def optimizer_pre_step_hook(optimizer, args, kwargs):646 def optimizer_pre_step_hook(optimizer, args, kwargs):
617 context = self.optimizer_context[optimizer]647 context = self.optimizer_context[optimizer]
648+ logger.debug(
649+ f"optimizer_pre_step_hook enter: step={context.step}, start_step={self.start_step}, "
650+ f"step_interval={self.step_interval}, optimizer_type={optimizer.__class__.__name__}, "
651+ f"wg_distribution={self.wg_distribution}, mv_distribution={self.mv_distribution}, "
652+ f"ur_distribution={self.ur_distribution}, mg_direction={self.mg_direction}"
653+ )
618 654 
619 if (655 if (
620 self.print_struct656 self.print_struct
@@ -633,11 +669,19 @@ class TrainerMon:
633 669 
634 # skip generate metrics670 # skip generate metrics
635 if context.step < self.start_step or (context.step - self.start_step) % self.step_interval != 0:671 if context.step < self.start_step or (context.step - self.start_step) % self.step_interval != 0:
672+ logger.debug(
673+ f"optimizer_pre_step_hook skip: step={context.step}, start_step={self.start_step}, "
674+ f"step_interval={self.step_interval}"
675+ )
636 return676 return
637 677 
638 grad_dict = {}678 grad_dict = {}
639 if self.wg_distribution:679 if self.wg_distribution:
640 grad_dict = self.optimizer_mon.fetch_grad(self, self.param2name)680 grad_dict = self.optimizer_mon.fetch_grad(self, self.param2name)
681+ logger.debug(
682+ f"optimizer_pre_step_hook fetched grads: grad_count={len(grad_dict)}, "
683+ f"target_param_count={len(self.param2name)}"
684+ )
641 685 
642 mv_result = None686 mv_result = None
643 if self.mv_distribution or self.ur_distribution or self.mg_direction:687 if self.mv_distribution or self.ur_distribution or self.mg_direction:
@@ -647,10 +691,21 @@ class TrainerMon:
647 context.param_exp_avg_sq = mv_result.exp_avg_sq691 context.param_exp_avg_sq = mv_result.exp_avg_sq
648 context.param_adam_update = mv_result.update692 context.param_adam_update = mv_result.update
649 context.param_adam_ratio = mv_result.ratio693 context.param_adam_ratio = mv_result.ratio
694+ logger.debug(
695+ f"optimizer_pre_step_hook fetched mv: exp_avg_count={len(mv_result.exp_avg)}, "
696+ f"exp_avg_sq_count={len(mv_result.exp_avg_sq)}, update_count={len(mv_result.update)}, "
697+ f"ratio_count={len(mv_result.ratio)}"
698+ )
650 699 
651 _, _ = self.generate_wgrad_metrics(grad_dict)700 _, _ = self.generate_wgrad_metrics(grad_dict)
652 self.generate_mv_metrics(context)701 self.generate_mv_metrics(context)
653 self.generate_param_metrics(context, MonitorConst.PRE_PARAM)702 self.generate_param_metrics(context, MonitorConst.PRE_PARAM)
703+ logger.debug(
704+ f"optimizer_pre_step_hook metrics prepared: post_grad_metric_count={len(self.grad_context.post)}, "
705+ f"pre_grad_metric_count={len(self.grad_context.pre)}, param_metric_count={len(context.param_metric)}, "
706+ f"exp_avg_metric_count={len(context.exp_avg_metric)}, "
707+ f"exp_avg_sq_metric_count={len(context.exp_avg_sq_metric)}"
708+ )
捡到宝。
捡到宝。捡到宝。6月25日

此条代码评论区间+648至+708

热路径上使用 f-string 格式化 debug 日志存在性能开销。optimizer_pre_step_hook 和 step_final_hook 在每个 optimizer step 都会被调用,其中包含多达 7 个 logger.debug() 调用,每个都使用 f-string 格式化。Python 的 f-string 在函数调用前就会被求值,即使日志级别高于 DEBUG 时也无法跳过格式化开销。优化器钩子中的循环监控每个 step,累积的字符串格式化开销在长时间训练中会比较显著。 建议对热路径上的 debug 日志使用惰性求值模式,或使用 % 格式化方式让 logging 框架延迟求值。对于频繁调用的钩子函数,也可以考虑将多个日志合并为一条以减少调用次数。

# 推荐方式:使用 %s 格式化,logging 框架内部按需求值
logger.debug("optimizer_pre_step_hook enter: step=%s, start_step=%s, step_interval=%s, optimizer_type=%s, wg_distribution=%s, mv_distribution=%s, ur_distribution=%s, mg_direction=%s",
    context.step, self.start_step, self.step_interval, optimizer.__class__.__name__,
    self.wg_distribution, self.mv_distribution, self.ur_distribution, self.mg_direction)

# 或者将多条日志合并为一条
logger.debug(
    "optimizer_pre_step_hook: step=%s, grads=%s, mv_metrics=%s, param_metrics=%s",
    context.step,
    len(grad_dict) if self.wg_distribution else 'N/A',
    f'exp_avg={len(context.exp_avg_metric)}' if context.exp_avg_metric else 'N/A',
    f'pre={len(self.grad_context.pre)},post={len(self.grad_context.post)}'
)
likedislike
654 709 
655 tbtag_tensor_map = {}710 tbtag_tensor_map = {}
656 if self.mg_direction:711 if self.mg_direction:
@@ -673,7 +728,12 @@ class TrainerMon:
673 metric_dict.update(cc.data)728 metric_dict.update(cc.data)
674 cc.reset()729 cc.reset()
675 730 
731+ logger.debug(
732+ f"optimizer_pre_step_hook metric aggregation: mg_direction_metric_count={len(tbtag_tensor_map)}, "
733+ f"other_metric_count={len(metric_dict)}"
734+ )
676 if not metric_dict:735 if not metric_dict:
736+ logger.debug(f"optimizer_pre_step_hook no extra metrics generated at step={context.step}")
677 return737 return
678 context.metric_dict = metric_dict738 context.metric_dict = metric_dict
679 return739 return
@@ -683,8 +743,10 @@ class TrainerMon:
683 self.generate_param_metrics(context, MonitorConst.POST_PARAM)743 self.generate_param_metrics(context, MonitorConst.POST_PARAM)
684 744 
685 if self.optimizer_hooked:745 if self.optimizer_hooked:
746+ logger.debug("hook_optimizer skipped because optimizer hooks are already registered")
686 return747 return
687 748 
749+ logger.debug(f"hook_optimizer register hooks for optimizer_type={optimizer.__class__.__name__}")
688 self.pre_step_hooks.append(optimizer_pre_step_hook)750 self.pre_step_hooks.append(optimizer_pre_step_hook)
689 self.post_step_hooks.append(optimizer_post_step_hook)751 self.post_step_hooks.append(optimizer_post_step_hook)
690 752 
@@ -740,8 +802,18 @@ class TrainerMon:
740 step_condition = (802 step_condition = (
741 context.step >= self.start_step and (context.step - self.start_step) % self.step_interval == 0803 context.step >= self.start_step and (context.step - self.start_step) % self.step_interval == 0
742 )804 )
805+ logger.debug(
806+ f"step_final_hook enter: step={context.step}, monitoring={self.monitoring}, rank={rank}, "
807+ f"module_rank_valid={module_rank_valid}, step_condition={step_condition}, "
808+ f"has_collect_times={self.has_collect_times}, collect_times={self.collect_times}"
809+ )
743 if module_rank_valid and step_condition:810 if module_rank_valid and step_condition:
744 self.has_collect_times += 1811 self.has_collect_times += 1
812+ logger.debug(
813+ f"step_final_hook write path: step={context.step}, collect_progress="
814+ f"{self.has_collect_times}/{self.collect_times}, pre_grad_metric_count={len(self.grad_context.pre)}, "
815+ f"post_grad_metric_count={len(self.grad_context.post)}, other_metric_count={len(context.metric_dict)}"
816+ )
745 817 
746 if self.anomaly_data_factory:818 if self.anomaly_data_factory:
747 self.anomaly_data_factory.set_call_id(self.param_name_call_id)819 self.anomaly_data_factory.set_call_id(self.param_name_call_id)
@@ -784,9 +856,13 @@ class TrainerMon:
784 self.param_name_call_id.clear()856 self.param_name_call_id.clear()
785 857 
786 if self.has_collect_times >= self.collect_times:858 if self.has_collect_times >= self.collect_times:
859+ logger.debug(
860+ f"step_final_hook reached collect limit at step={context.step}, removing all hooks"
861+ )
787 self._remove_all_hooks_final(optimizer)862 self._remove_all_hooks_final(optimizer)
788 863 
789 context.step += 1864 context.step += 1
865+ logger.debug(f"step_final_hook finish: next_step={context.step}")
790 self.dynamic_monitor(optimizer)866 self.dynamic_monitor(optimizer)
791 867 
792 def patch_step(func, optimizer):868 def patch_step(func, optimizer):
@@ -959,15 +1035,22 @@ class TrainerMon:
959 return False1035 return False
960 1036 
961 def _register_chunk(self, model_chunk, prefix):1037 def _register_chunk(self, model_chunk, prefix):
1038+ logger.debug(
1039+ f"_register_chunk start: prefix={prefix}, model_chunk_type={model_chunk.__class__.__name__}, "
1040+ f"fsdp_wrapped_module={self.fsdp_wrapped_module}, fsdp2_wrapped_module={self.fsdp2_wrapped_module}"
1041+ )
962 if isinstance(model_chunk, FSDP):1042 if isinstance(model_chunk, FSDP):
963 if not model_chunk._use_orig_params:1043 if not model_chunk._use_orig_params:
964 raise ValueError("Only Support fsdp1 with use_orig_params=True")1044 raise ValueError("Only Support fsdp1 with use_orig_params=True")
965 self.fsdp_wrapped_module = True1045 self.fsdp_wrapped_module = True
1046+ logger.debug(f"_register_chunk detected fsdp1 wrapper for prefix={prefix}")
1047+ registered_count = 0
966 for param_name, param in model_chunk.named_parameters():1048 for param_name, param in model_chunk.named_parameters():
967 if not param.requires_grad:1049 if not param.requires_grad:
968 continue1050 continue
969 if not self.fsdp2_wrapped_module and param.__class__.__name__ == "DTensor":1051 if not self.fsdp2_wrapped_module and param.__class__.__name__ == "DTensor":
970 self.fsdp2_wrapped_module = True1052 self.fsdp2_wrapped_module = True
1053+ logger.debug(f"_register_chunk detected fsdp2 dtensor param: param_name={param_name}, prefix={prefix}")
971 if self.fsdp_wrapped_module: # FSDP1需要记录完整的不被target限制的flat权重名,以供后续对flat解包1054 if self.fsdp_wrapped_module: # FSDP1需要记录完整的不被target限制的flat权重名,以供后续对flat解包
972 self.fsdp_param_name_map[id(param)] = param_name1055 self.fsdp_param_name_map[id(param)] = param_name
973 1056 
@@ -991,6 +1074,16 @@ class TrainerMon:
991 MonitorConst.POST_PARAM,1074 MonitorConst.POST_PARAM,
992 ]1075 ]
993 self.name2tag[name] = {k: get_summary_writer_tag_name(name, k, self.rank) for k in keywords}1076 self.name2tag[name] = {k: get_summary_writer_tag_name(name, k, self.rank) for k in keywords}
1077+ registered_count += 1
1078+ logger.debug(
1079+ f"_register_chunk mapped param: origin_name={param_name}, mapped_name={name}, "
1080+ f"duplicate={self.duplicate_param.get(name, False)}, fsdp_flat_mapped={id(param) in self.fsdp_param_name_map}"
1081+ )
1082+ 
1083+ logger.debug(
1084+ f"_register_chunk finish: prefix={prefix}, registered_count={registered_count}, "
1085+ f"name2param_count={len(self.name2param)}, origin2squash_count={len(self.origin2squash)}"
1086+ )
994 1087 
995 def _register_param_name(self):1088 def _register_param_name(self):
996 for vpp_stage, model_chunk in enumerate(self.model):1089 for vpp_stage, model_chunk in enumerate(self.model):
@@ -1223,28 +1316,38 @@ class TrainerMon:
1223 1316 
1224 def _patch_grad_sync(self):1317 def _patch_grad_sync(self):
1225 if not self.wg_distribution:1318 if not self.wg_distribution:
1319+ logger.debug("_patch_grad_sync skipped because wg_distribution is disabled")
1226 return1320 return
1227 1321 
1228 if self.fsdp_wrapped_module:1322 if self.fsdp_wrapped_module:
1323+ logger.debug("_patch_grad_sync select fsdp1 post backward hook path")
1229 # patch fsdp _runtime_utils._post_backward_hook1324 # patch fsdp _runtime_utils._post_backward_hook
1230 self._patch_fsdp_post_backward_hook()1325 self._patch_fsdp_post_backward_hook()
1231 return1326 return
1232 1327 
1233 if self.fsdp2_wrapped_module:1328 if self.fsdp2_wrapped_module:
1329+ logger.debug("_patch_grad_sync select fsdp2 foreach_reduce path")
1234 # patch fsdp2 _fully_shard._fsdp_collectives.foreach_reduce1330 # patch fsdp2 _fully_shard._fsdp_collectives.foreach_reduce
1235 self._patch_fsdp2_foreach_reduce()1331 self._patch_fsdp2_foreach_reduce()
1236 return1332 return
1237 1333 
1238 if self.monitor_mbs_grad:1334 if self.monitor_mbs_grad:
1335+ logger.debug("_patch_grad_sync select param_hook path because monitor_mbs_grad is enabled")
1239 self._hook_weights()1336 self._hook_weights()
1240 return1337 return
1241 1338 
1339+ logger.debug("_patch_grad_sync delegate to optimizer patch path")
1242 self.optimizer_mon.patch_grad_sync(self)1340 self.optimizer_mon.patch_grad_sync(self)
1243 1341 
1244 if self.enable_megatron or self.enable_deepspeed:1342 if self.enable_megatron or self.enable_deepspeed:
1343+ logger.debug(
1344+ f"_patch_grad_sync using distributed optimizer path: enable_megatron={self.enable_megatron}, "
1345+ f"enable_deepspeed={self.enable_deepspeed}"
1346+ )
1245 return1347 return
1246 1348 
1247 # default hook weights1349 # default hook weights
1350+ logger.debug("_patch_grad_sync fallback to default param_hook path")
1248 self._hook_weights()1351 self._hook_weights()
1249 1352 
1250 def _patch_fsdp_post_backward_hook(self):1353 def _patch_fsdp_post_backward_hook(self):
@@ -1270,10 +1373,18 @@ class TrainerMon:
1270 offsets = handle._get_flat_param_offsets()1373 offsets = handle._get_flat_param_offsets()
1271 shapes = handle.flat_param._shapes1374 shapes = handle.flat_param._shapes
1272 params = handle.flat_param._params1375 params = handle.flat_param._params
1376+ logger.debug(
1377+ f"_post_backward_hook enter: micro_step={handle.flat_param.micro_step}, "
1378+ f"param_count={len(params)}, offsets_count={len(offsets)}, shape_count={len(shapes)}"
1379+ )
1273 for param, (start, end), local_shape in zip(params, offsets, shapes):1380 for param, (start, end), local_shape in zip(params, offsets, shapes):
1274 param_name = self.fsdp_param_name_map.get(id(param), "")1381 param_name = self.fsdp_param_name_map.get(id(param), "")
1275 tag = self.name2tag.get(self.origin2squash.get(param_name, ""), {}).get(MonitorConst.PRE_GRAD)1382 tag = self.name2tag.get(self.origin2squash.get(param_name, ""), {}).get(MonitorConst.PRE_GRAD)
1276 if tag is None:1383 if tag is None:
1384+ logger.debug(
1385+ f"_post_backward_hook skip param without tag: param_name={param_name}, "
1386+ f"micro_step={handle.flat_param.micro_step}"
1387+ )
1277 continue1388 continue
1278 grad = handle.flat_param.grad[start : end + 1].reshape(local_shape)1389 grad = handle.flat_param.grad[start : end + 1].reshape(local_shape)
1279 if self.monitor_mbs_grad:1390 if self.monitor_mbs_grad:
@@ -1281,6 +1392,10 @@ class TrainerMon:
1281 grad_dict[tag] = grad1392 grad_dict[tag] = grad
1282 self.register_param_call_id("_post_backward_hook", tag)1393 self.register_param_call_id("_post_backward_hook", tag)
1283 get_metrics(self.ops, grad_dict, self.eps, self.grad_context.pre)1394 get_metrics(self.ops, grad_dict, self.eps, self.grad_context.pre)
1395+ logger.debug(
1396+ f"_post_backward_hook collected pre_grad metrics: grad_count={len(grad_dict)}, "
1397+ f"pre_metric_count={len(self.grad_context.pre)}"
1398+ )
1284 handle.flat_param.micro_step = (handle.flat_param.micro_step + 1) % self.micro_batch_number1399 handle.flat_param.micro_step = (handle.flat_param.micro_step + 1) % self.micro_batch_number
1285 out = _post_backward_hook(state, handle, *unused)1400 out = _post_backward_hook(state, handle, *unused)
1286 return out1401 return out
@@ -1301,17 +1416,29 @@ class TrainerMon:
1301 1416 
1302 if self.monitor_mbs_grad or (fsdp_params[0].micro_step + 1 == self.micro_batch_number):1417 if self.monitor_mbs_grad or (fsdp_params[0].micro_step + 1 == self.micro_batch_number):
1303 grad_dict = {}1418 grad_dict = {}
1419+ logger.debug(
1420+ f"foreach_reduce enter: micro_step={fsdp_params[0].micro_step}, "
1421+ f"param_count={len(fsdp_params)}, grad_count={len(unsharded_grads)}"
1422+ )
1304 for param, grad in zip(fsdp_params, unsharded_grads):1423 for param, grad in zip(fsdp_params, unsharded_grads):
1305 tag = self.name2tag.get(self.origin2squash.get(param._param_fqn, ""), {}).get(1424 tag = self.name2tag.get(self.origin2squash.get(param._param_fqn, ""), {}).get(
1306 MonitorConst.PRE_GRAD1425 MonitorConst.PRE_GRAD
1307 )1426 )
1308 if tag is None:1427 if tag is None:
1428+ logger.debug(
1429+ f"foreach_reduce skip param without tag: param_fqn={param._param_fqn}, "
1430+ f"micro_step={fsdp_params[0].micro_step}"
1431+ )
1309 continue1432 continue
1310 if self.monitor_mbs_grad:1433 if self.monitor_mbs_grad:
1311 tag = tag.replace('/', f'{MonitorConst.NAME_SEP}{fsdp_params[0].micro_step}/', 1)1434 tag = tag.replace('/', f'{MonitorConst.NAME_SEP}{fsdp_params[0].micro_step}/', 1)
1312 grad_dict[tag] = grad1435 grad_dict[tag] = grad
1313 self.register_param_call_id("foreach_reduce", tag)1436 self.register_param_call_id("foreach_reduce", tag)
1314 get_metrics(self.ops, grad_dict, self.eps, self.grad_context.pre)1437 get_metrics(self.ops, grad_dict, self.eps, self.grad_context.pre)
1438+ logger.debug(
1439+ f"foreach_reduce collected pre_grad metrics: grad_count={len(grad_dict)}, "
1440+ f"pre_metric_count={len(self.grad_context.pre)}"
1441+ )
1315 fsdp_params[0].micro_step = (fsdp_params[0].micro_step + 1) % self.micro_batch_number1442 fsdp_params[0].micro_step = (fsdp_params[0].micro_step + 1) % self.micro_batch_number
1316 out = foreach_reduce(fsdp_params, unsharded_grads, *unused)1443 out = foreach_reduce(fsdp_params, unsharded_grads, *unused)
1317 return out1444 return out
@@ -1338,6 +1465,10 @@ class TrainerMon:
1338 key += f'{MonitorConst.NAME_SEP}{param.micro_step}'1465 key += f'{MonitorConst.NAME_SEP}{param.micro_step}'
1339 1466 
1340 key = get_summary_writer_tag_name(key, 'acc_grad', self.rank)1467 key = get_summary_writer_tag_name(key, 'acc_grad', self.rank)
1468+ logger.debug(
1469+ f"param_hook enter: name={name}, key={key}, micro_step={param.micro_step}, "
1470+ f"use_main_grad={self.params_have_main_grad}"
1471+ )
1341 self.register_param_call_id("param_hook", key)1472 self.register_param_call_id("param_hook", key)
1342 if self.monitor_mbs_grad or (param.micro_step + 1 == self.micro_batch_number):1473 if self.monitor_mbs_grad or (param.micro_step + 1 == self.micro_batch_number):
1343 if self.params_have_main_grad:1474 if self.params_have_main_grad:
@@ -1345,9 +1476,11 @@ class TrainerMon:
1345 else:1476 else:
1346 grad = param.grad1477 grad = param.grad
1347 get_metrics(self.ops, {key: grad.clone()}, self.eps, self.grad_context.pre)1478 get_metrics(self.ops, {key: grad.clone()}, self.eps, self.grad_context.pre)
1479+ logger.debug(f"param_hook collected metric: key={key}, pre_metric_count={len(self.grad_context.pre)}")
1348 param.micro_step = (param.micro_step + 1) % self.micro_batch_number1480 param.micro_step = (param.micro_step + 1) % self.micro_batch_number
1349 1481 
1350 logger.info("hooking weight grads.")1482 logger.info("hooking weight grads.")
1483+ logger.debug(f"_hook_weights register start: param_count={len(self.param2name)}")
1351 for param, name in self.param2name.items():1484 for param, name in self.param2name.items():
1352 setattr(param, 'micro_step', 0)1485 setattr(param, 'micro_step', 0)
1353 param_tmp = param.expand_as(param)1486 param_tmp = param.expand_as(param)
@@ -24,7 +24,7 @@ from msprobe.pytorch.monitor.module_metric import get_metrics
24from msprobe.core.common.const import MonitorConst24from msprobe.core.common.const import MonitorConst
25 25 
26 26 
27-class OptimizerMon(object):27+class OptimizerMon:
28 def __init__(self, torch_opt) -> None:28 def __init__(self, torch_opt) -> None:
29 self.fp16_to_fp32_param = {}29 self.fp16_to_fp32_param = {}
30 self.torch_opt = torch_opt30 self.torch_opt = torch_opt
@@ -131,6 +131,7 @@ class OptimizerMon(object):
131 def patch_sync(sync_grad_func):131 def patch_sync(sync_grad_func):
132 def wrapper(bucket, *args, **kwargs):132 def wrapper(bucket, *args, **kwargs):
133 grad_dict = {}133 grad_dict = {}
134+ param_source = "params"
134 # Megatron between core_r0.6.0 and core_r0.8.0, this bucket is Bucket.135 # Megatron between core_r0.6.0 and core_r0.8.0, this bucket is Bucket.
135 # When megatron is core_r0.9.0, this bucket is _ParamAndGradBucketGroup.136 # When megatron is core_r0.9.0, this bucket is _ParamAndGradBucketGroup.
136 # In megatron version core_r0.9.0, func start_grad_sync from Bucket moved to _ParamAndGradBucketGroup.137 # In megatron version core_r0.9.0, func start_grad_sync from Bucket moved to _ParamAndGradBucketGroup.
@@ -138,9 +139,16 @@ class OptimizerMon(object):
138 param_attr = bucket.params139 param_attr = bucket.params
139 elif hasattr(bucket, "params_list"):140 elif hasattr(bucket, "params_list"):
140 param_attr = bucket.params_list141 param_attr = bucket.params_list
142+ param_source = "params_list"
141 else:143 else:
142 raise AttributeError(144 raise AttributeError(
143- f"class {bucket.__class__.__name__} has no attribute 'params' or 'params_list', please check!")145+ f"class {bucket.__class__.__name__} has no attribute 'params' or 'params_list', please check!"
146+ )
147+ logger.debug(
148+ f"megatron sync_grad_func enter: bucket_type={bucket.__class__.__name__}, "
149+ f"param_source={param_source}, bucket_param_count={len(param_attr)}, "
150+ f"target_param_count={len(monitor.param2name)}"
151+ )
144 bucket_params_id_list = [id(params) for params in param_attr]152 bucket_params_id_list = [id(params) for params in param_attr]
145 for param, name in monitor.param2name.items():153 for param, name in monitor.param2name.items():
146 if id(param) not in bucket_params_id_list:154 if id(param) not in bucket_params_id_list:
@@ -151,10 +159,13 @@ class OptimizerMon(object):
151 continue159 continue
152 tag = monitor.name2tag.get(name, {}).get(MonitorConst.PRE_GRAD)160 tag = monitor.name2tag.get(name, {}).get(MonitorConst.PRE_GRAD)
153 if tag is None:161 if tag is None:
162+ logger.debug(f"megatron sync_grad_func skip param without tag: name={name}")
154 continue163 continue
155 grad_dict[tag] = grad164 grad_dict[tag] = grad
156 monitor.register_param_call_id("sync_grad_func", tag)165 monitor.register_param_call_id("sync_grad_func", tag)
166+ logger.debug(f"megatron sync_grad_func collected grad_count={len(grad_dict)}")
157 get_metrics(monitor.ops, grad_dict, monitor.eps, monitor.grad_context.pre)167 get_metrics(monitor.ops, grad_dict, monitor.eps, monitor.grad_context.pre)
168+ logger.debug(f"megatron sync_grad_func pre_metric_count={len(monitor.grad_context.pre)}")
158 out = sync_grad_func(bucket, *args, **kwargs)169 out = sync_grad_func(bucket, *args, **kwargs)
159 return out170 return out
160 171 
@@ -162,6 +173,7 @@ class OptimizerMon(object):
162 173 
163 try:174 try:
164 from megatron.core.distributed.param_and_grad_buffer import Bucket175 from megatron.core.distributed.param_and_grad_buffer import Bucket
176+ 
165 self.origin_funcs.append(Bucket.start_grad_sync)177 self.origin_funcs.append(Bucket.start_grad_sync)
166 self.bucket_class = Bucket178 self.bucket_class = Bucket
167 Bucket.start_grad_sync = patch_sync(Bucket.start_grad_sync)179 Bucket.start_grad_sync = patch_sync(Bucket.start_grad_sync)
@@ -172,6 +184,7 @@ class OptimizerMon(object):
172 184 
173 try:185 try:
174 from megatron.core.distributed.param_and_grad_buffer import _ParamAndGradBucketGroup186 from megatron.core.distributed.param_and_grad_buffer import _ParamAndGradBucketGroup
187+ 
175 self.origin_funcs.append(_ParamAndGradBucketGroup.start_grad_sync)188 self.origin_funcs.append(_ParamAndGradBucketGroup.start_grad_sync)
176 self.bucket_class = _ParamAndGradBucketGroup189 self.bucket_class = _ParamAndGradBucketGroup
177 _ParamAndGradBucketGroup.start_grad_sync = patch_sync(_ParamAndGradBucketGroup.start_grad_sync)190 _ParamAndGradBucketGroup.start_grad_sync = patch_sync(_ParamAndGradBucketGroup.start_grad_sync)
@@ -207,7 +220,8 @@ class MegatronMixPrecisionOptimizerMon(OptimizerMon):
207 if not (hasattr(torch_opt, "float16_groups") and hasattr(torch_opt, "fp32_from_float16_groups")):220 if not (hasattr(torch_opt, "float16_groups") and hasattr(torch_opt, "fp32_from_float16_groups")):
208 logger.warning(221 logger.warning(
209 "megatron class Float16OptimizerWithFloat16Params should have float16_groups and "222 "megatron class Float16OptimizerWithFloat16Params should have float16_groups and "
210- "fp32_from_float16_groups, please check Megatron-LM version.")223+ "fp32_from_float16_groups, please check Megatron-LM version."
224+ )
211 else:225 else:
212 for fp16_group, fp32_group in zip(torch_opt.float16_groups, torch_opt.fp32_from_float16_groups):226 for fp16_group, fp32_group in zip(torch_opt.float16_groups, torch_opt.fp32_from_float16_groups):
213 for fp16_param, fp32_param in zip(fp16_group, fp32_group):227 for fp16_param, fp32_param in zip(fp16_group, fp32_group):
@@ -219,10 +233,12 @@ class MegatronDistributedOptimizerMon(OptimizerMon):
219 if not (hasattr(torch_opt, "model_float16_groups") and hasattr(torch_opt, "shard_fp32_from_float16_groups")):233 if not (hasattr(torch_opt, "model_float16_groups") and hasattr(torch_opt, "shard_fp32_from_float16_groups")):
220 logger.warning(234 logger.warning(
221 "megatron class DistributedOptimizer should have model_float16_groups and "235 "megatron class DistributedOptimizer should have model_float16_groups and "
222- "shard_fp32_from_float16_groups, please check Megatron-LM version.")236+ "shard_fp32_from_float16_groups, please check Megatron-LM version."
237+ )
223 else:238 else:
224- for fp16_group, shard_fp32_group in zip(torch_opt.model_float16_groups,239+ for fp16_group, shard_fp32_group in zip(
225- torch_opt.shard_fp32_from_float16_groups):240+ torch_opt.model_float16_groups, torch_opt.shard_fp32_from_float16_groups
241+ ):
226 for fp16_param, shard_fp32_param in zip(fp16_group, shard_fp32_group):242 for fp16_param, shard_fp32_param in zip(fp16_group, shard_fp32_group):
227 self.fp16_to_fp32_param[fp16_param] = shard_fp32_param243 self.fp16_to_fp32_param[fp16_param] = shard_fp32_param
228 244 
@@ -373,14 +389,23 @@ class DeepSpeedZeroOptimizerStage1or2Mon(DeepSpeedZeroOptimizerMon):
373 def patch_sync(reduce_func):389 def patch_sync(reduce_func):
374 def wrapper(zero_optimizer, *args, **kwargs):390 def wrapper(zero_optimizer, *args, **kwargs):
375 grad_dict = {}391 grad_dict = {}
392+ logger.debug(
393+ f"deepspeed sync_grad_func enter: bucket_count={len(zero_optimizer.params_in_ipg_bucket)}, "
394+ f"target_param_count={len(monitor.param2name)}"
395+ )
376 for i, param, _ in zero_optimizer.params_in_ipg_bucket:396 for i, param, _ in zero_optimizer.params_in_ipg_bucket:
377 if isinstance(param, int): # for ds >= 0.17.0397 if isinstance(param, int): # for ds >= 0.17.0
378 param = zero_optimizer.bit16_groups[i][param]398 param = zero_optimizer.bit16_groups[i][param]
379 name = monitor.param2name[param]399 name = monitor.param2name[param]
380 tag = monitor.name2tag.get(name, {}).get(MonitorConst.PRE_GRAD)400 tag = monitor.name2tag.get(name, {}).get(MonitorConst.PRE_GRAD)
401+ if tag is None:
402+ logger.debug(f"deepspeed sync_grad_func skip param without tag: name={name}")
403+ continue
381 grad_dict[tag] = zero_optimizer.get_gradient_for_reduction(param)404 grad_dict[tag] = zero_optimizer.get_gradient_for_reduction(param)
382 monitor.register_param_call_id("sync_grad_func", tag)405 monitor.register_param_call_id("sync_grad_func", tag)
406+ logger.debug(f"deepspeed sync_grad_func collected grad_count={len(grad_dict)}")
383 get_metrics(monitor.ops, grad_dict, monitor.eps, monitor.grad_context.pre)407 get_metrics(monitor.ops, grad_dict, monitor.eps, monitor.grad_context.pre)
408+ logger.debug(f"deepspeed sync_grad_func pre_metric_count={len(monitor.grad_context.pre)}")
384 out = reduce_func(zero_optimizer, *args, **kwargs)409 out = reduce_func(zero_optimizer, *args, **kwargs)
385 return out410 return out
386 411 
@@ -388,16 +413,15 @@ class DeepSpeedZeroOptimizerStage1or2Mon(DeepSpeedZeroOptimizerMon):
388 413 
389 try:414 try:
390 from deepspeed.runtime.zero.stage_1_and_2 import DeepSpeedZeroOptimizer415 from deepspeed.runtime.zero.stage_1_and_2 import DeepSpeedZeroOptimizer
391- self.origin_funcs = [416+ 
392- DeepSpeedZeroOptimizer.average_tensor,417+ self.origin_funcs = [DeepSpeedZeroOptimizer.average_tensor, DeepSpeedZeroOptimizer.buffered_reduce_fallback]
393- DeepSpeedZeroOptimizer.buffered_reduce_fallback
394- ]
395 DeepSpeedZeroOptimizer.average_tensor = patch_sync(DeepSpeedZeroOptimizer.average_tensor)418 DeepSpeedZeroOptimizer.average_tensor = patch_sync(DeepSpeedZeroOptimizer.average_tensor)
396- DeepSpeedZeroOptimizer.buffered_reduce_fallback = \419+ DeepSpeedZeroOptimizer.buffered_reduce_fallback = patch_sync(
397- patch_sync(DeepSpeedZeroOptimizer.buffered_reduce_fallback)420+ DeepSpeedZeroOptimizer.buffered_reduce_fallback
421+ )
398 monitor.enable_deepspeed = True422 monitor.enable_deepspeed = True
399 logger.info('deepspeed enabled')423 logger.info('deepspeed enabled')
400- except Exception as e:424+ except Exception:
401 monitor.enable_deepspeed = False | monitor.enable_deepspeed425 monitor.enable_deepspeed = False | monitor.enable_deepspeed
402 logger.warning('Seems using deepspeed zero 1 or 2. But patch average tensor failed')426 logger.warning('Seems using deepspeed zero 1 or 2. But patch average tensor failed')
403 427 
@@ -406,6 +430,7 @@ class DeepSpeedZeroOptimizerStage1or2Mon(DeepSpeedZeroOptimizerMon):
406 return430 return
407 431 
408 from deepspeed.runtime.zero.stage_1_and_2 import DeepSpeedZeroOptimizer432 from deepspeed.runtime.zero.stage_1_and_2 import DeepSpeedZeroOptimizer
433+ 
409 DeepSpeedZeroOptimizer.average_tensor = self.origin_funcs[0]434 DeepSpeedZeroOptimizer.average_tensor = self.origin_funcs[0]
410 DeepSpeedZeroOptimizer.buffered_reduce_fallback = self.origin_funcs[1]435 DeepSpeedZeroOptimizer.buffered_reduce_fallback = self.origin_funcs[1]
411 436 
@@ -442,7 +467,7 @@ class OptimizerMonFactory:
442 "BF16_Optimizer": DeepSpeedZeroOptimizerStage0Mon,467 "BF16_Optimizer": DeepSpeedZeroOptimizerStage0Mon,
443 "DeepSpeedZeroOptimizer": DeepSpeedZeroOptimizerStage1or2Mon,468 "DeepSpeedZeroOptimizer": DeepSpeedZeroOptimizerStage1or2Mon,
444 "DeepSpeedZeroOptimizer_Stage3": DeepSpeedZeroOptimizerStage3Mon,469 "DeepSpeedZeroOptimizer_Stage3": DeepSpeedZeroOptimizerStage3Mon,
445- "Adam": OptimizerMon470+ "Adam": OptimizerMon,
446 }471 }
447 472 
448 @staticmethod473 @staticmethod
@@ -17,6 +17,7 @@
17import os.path17import os.path
18import shutil18import shutil
19import unittest19import unittest
20+from collections import defaultdict
20from unittest.mock import patch, MagicMock21from unittest.mock import patch, MagicMock
21 22 
22import pandas as pd23import pandas as pd
@@ -224,6 +225,71 @@ class TestTrainerMon(unittest.TestCase):
224 self.mon.dynamic_monitor(optimizer)225 self.mon.dynamic_monitor(optimizer)
225 self.assertEqual(self.mon.config_timestamp, 123456)226 self.assertEqual(self.mon.config_timestamp, 123456)
226 227 
228+ @patch("msprobe.pytorch.monitor.module_hook.logger.debug")
229+ def test_set_config_logs_normalized_summary(self, mock_debug):
230+ self.mon.set_config()
231+ logged_messages = [call.args[0] for call in mock_debug.call_args_list]
232+ self.assertTrue(any("set_config normalized" in msg for msg in logged_messages))
233+ self.assertTrue(any("set_config writer setup" in msg for msg in logged_messages))
234+ 
235+ @patch("msprobe.pytorch.monitor.module_hook.logger.debug")
236+ def test_generate_wgrad_metrics_logs_counts(self, mock_debug):
237+ self.mon.wg_distribution = True
238+ self.mon.ops = []
239+ self.mon.eps = 1e-8
240+ post_grad_dict = {"tag": torch.tensor([1.0])}
241+ 
242+ with patch("msprobe.pytorch.monitor.module_hook.get_metrics") as mock_get_metrics:
243+ self.mon.generate_wgrad_metrics(post_grad_dict)
244+ 
245+ mock_get_metrics.assert_called_once()
246+ logged_messages = [call.args[0] for call in mock_debug.call_args_list]
247+ self.assertTrue(any("generate_wgrad_metrics start" in msg for msg in logged_messages))
248+ self.assertTrue(any("generate_wgrad_metrics finish" in msg for msg in logged_messages))
249+ 
250+ @patch("msprobe.pytorch.monitor.module_hook.logger.debug")
251+ def test_write_grad_tb_logs_counts(self, mock_debug):
252+ self.mon.wg_distribution = True
253+ self.mon.monitor_mbs_grad = True
254+ self.mon.ops = []
255+ self.mon.grad_context.pre = {"pre": torch.tensor([1.0])}
256+ self.mon.grad_context.post = {"post": torch.tensor([2.0])}
257+ self.mon.summary_writer = MagicMock()
258+ 
259+ self.mon.write_grad_tb(step=3)
260+ 
261+ self.mon.summary_writer.write_metrics.assert_any_call(
262+ self.mon.ops, self.mon.grad_context.pre, 3, 'grad_unreduced', use_micro_step=True
263+ )
264+ self.mon.summary_writer.write_metrics.assert_any_call(
265+ self.mon.ops, self.mon.grad_context.post, 3, 'grad_reduced'
266+ )
267+ logged_messages = [call.args[0] for call in mock_debug.call_args_list]
268+ self.assertTrue(any("write_grad_tb start" in msg for msg in logged_messages))
269+ 
270+ @patch("msprobe.pytorch.monitor.module_hook.logger.debug")
271+ def test_register_chunk_logs_mapping(self, mock_debug):
272+ self.mon.config["targets"] = {"layer": {}}
273+ self.mon.param2name = defaultdict(str)
274+ self.mon.name2param = {}
275+ self.mon.origin2squash = {}
276+ self.mon.duplicate_param = {}
277+ self.mon.name2tag = {}
278+ self.mon.rank = 0
279+ 
280+ class DummyModule(torch.nn.Module):
281+ def __init__(self):
282+ super().__init__()
283+ self.layer = torch.nn.Linear(2, 2)
284+ 
285+ module = DummyModule()
286+ self.mon._register_chunk(module, "0:")
287+ 
288+ logged_messages = [call.args[0] for call in mock_debug.call_args_list]
289+ self.assertTrue(any("_register_chunk start" in msg for msg in logged_messages))
290+ self.assertTrue(any("_register_chunk mapped param" in msg for msg in logged_messages))
291+ self.assertTrue(any("_register_chunk finish" in msg for msg in logged_messages))
292+ 
227 @patch("msprobe.pytorch.monitor.module_hook.is_recomputation", return_value=False)293 @patch("msprobe.pytorch.monitor.module_hook.is_recomputation", return_value=False)
228 @patch("msprobe.pytorch.monitor.module_hook.get_entropy_metric")294 @patch("msprobe.pytorch.monitor.module_hook.get_entropy_metric")
229 @patch("msprobe.pytorch.monitor.module_hook.cal_qkt")295 @patch("msprobe.pytorch.monitor.module_hook.cal_qkt")
@@ -459,9 +525,10 @@ class TestTrainerMon(unittest.TestCase):
459 mock_save_json.assert_not_called()525 mock_save_json.assert_not_called()
460 mock_getmtime.assert_not_called()526 mock_getmtime.assert_not_called()
461 527 
528+ @patch("msprobe.pytorch.monitor.module_hook.logger.debug")
462 @patch("msprobe.pytorch.monitor.module_hook.get_sign_matches")529 @patch("msprobe.pytorch.monitor.module_hook.get_sign_matches")
463 @patch("msprobe.pytorch.monitor.module_hook.get_metrics")530 @patch("msprobe.pytorch.monitor.module_hook.get_metrics")
464- def test_hook_optimizer_mg_direction_branch(self, mock_get_metrics, mock_get_sign_matches):531+ def test_hook_optimizer_mg_direction_branch(self, mock_get_metrics, mock_get_sign_matches, mock_debug):
465 class DummyOptimizer:532 class DummyOptimizer:
466 def step(self):533 def step(self):
467 return None534 return None
@@ -507,6 +574,9 @@ class TestTrainerMon(unittest.TestCase):
507 pre_hook(optimizer, (), {})574 pre_hook(optimizer, (), {})
508 mock_get_sign_matches.assert_called_once()575 mock_get_sign_matches.assert_called_once()
509 self.assertTrue(torch.equal(context.param_mg_direction["p1"], mock_get_sign_matches.return_value))576 self.assertTrue(torch.equal(context.param_mg_direction["p1"], mock_get_sign_matches.return_value))
577+ logged_messages = [call.args[0] for call in mock_debug.call_args_list]
578+ self.assertTrue(any("optimizer_pre_step_hook enter" in msg for msg in logged_messages))
579+ self.assertTrue(any("optimizer_pre_step_hook metrics prepared" in msg for msg in logged_messages))
510 580 
511 def test_hook_optimizer_patches_instance_step_only(self):581 def test_hook_optimizer_patches_instance_step_only(self):
512 class DummyOptimizer:582 class DummyOptimizer:
@@ -528,9 +598,10 @@ class TestTrainerMon(unittest.TestCase):
528 result = optimizer1.step()598 result = optimizer1.step()
529 self.assertEqual(result, "original_step")599 self.assertEqual(result, "original_step")
530 600 
601+ @patch("msprobe.pytorch.monitor.module_hook.logger.debug")
531 @patch("msprobe.pytorch.monitor.module_hook.get_metrics")602 @patch("msprobe.pytorch.monitor.module_hook.get_metrics")
532 @patch("torch.distributed.fsdp._runtime_utils._post_backward_hook")603 @patch("torch.distributed.fsdp._runtime_utils._post_backward_hook")
533- def test_patch_fsdp_post_backward_hook(self, mock_post_hook, mock_get_metrics):604+ def test_patch_fsdp_post_backward_hook(self, mock_post_hook, mock_get_metrics, mock_debug):
534 from msprobe.core.common.const import MonitorConst as MC605 from msprobe.core.common.const import MonitorConst as MC
535 606 
536 class DummyFlatParam:607 class DummyFlatParam:
@@ -573,10 +644,14 @@ class TestTrainerMon(unittest.TestCase):
573 args, _ = mock_get_metrics.call_args644 args, _ = mock_get_metrics.call_args
574 grad_dict = args[1]645 grad_dict = args[1]
575 self.assertIn("pre_grad_tag", grad_dict)646 self.assertIn("pre_grad_tag", grad_dict)
647+ logged_messages = [call.args[0] for call in mock_debug.call_args_list]
648+ self.assertTrue(any("_post_backward_hook enter" in msg for msg in logged_messages))
649+ self.assertTrue(any("_post_backward_hook collected pre_grad metrics" in msg for msg in logged_messages))
576 650 
651+ @patch("msprobe.pytorch.monitor.module_hook.logger.debug")
577 @patch("msprobe.pytorch.monitor.module_hook.importlib.reload")652 @patch("msprobe.pytorch.monitor.module_hook.importlib.reload")
578 @patch("msprobe.pytorch.monitor.module_hook.get_metrics")653 @patch("msprobe.pytorch.monitor.module_hook.get_metrics")
579- def test_patch_fsdp2_foreach_reduce(self, mock_get_metrics, mock_reload):654+ def test_patch_fsdp2_foreach_reduce(self, mock_get_metrics, mock_reload, mock_debug):
580 from msprobe.core.common.const import MonitorConst as MC655 from msprobe.core.common.const import MonitorConst as MC
581 656 
582 # 构造假的 fully_shard 子模块,放入 sys.modules,避免环境缺少该模块时报错657 # 构造假的 fully_shard 子模块,放入 sys.modules,避免环境缺少该模块时报错
@@ -620,6 +695,9 @@ class TestTrainerMon(unittest.TestCase):
620 grad_dict = args[1]695 grad_dict = args[1]
621 self.assertIn("pre_grad_tag", grad_dict)696 self.assertIn("pre_grad_tag", grad_dict)
622 self.assertTrue(original_called["flag"])697 self.assertTrue(original_called["flag"])
698+ logged_messages = [call.args[0] for call in mock_debug.call_args_list]
699+ self.assertTrue(any("foreach_reduce enter" in msg for msg in logged_messages))
700+ self.assertTrue(any("foreach_reduce collected pre_grad metrics" in msg for msg in logged_messages))
623 701 
624 def test_is_recording_module(self):702 def test_is_recording_module(self):
625 # 1. 初始化核心属性703 # 1. 初始化核心属性
@@ -1,4 +1,5 @@
1import unittest1import unittest
2+from collections import defaultdict
2from types import SimpleNamespace3from types import SimpleNamespace
3from unittest.mock import Mock, patch, MagicMock4from unittest.mock import Mock, patch, MagicMock
4import sys5import sys
@@ -493,7 +494,8 @@ class TestPatchGradSyncWrapper(unittest.TestCase):
493 delattr(bucket, 'params_list')494 delattr(bucket, 'params_list')
494 return bucket495 return bucket
495 496 
496- def test_wrapper_passes_args_and_kwargs(self):497+ @patch("msprobe.pytorch.monitor.optimizer_collect.logger.debug")
498+ def test_wrapper_passes_args_and_kwargs(self, mock_debug):
497 with patch.dict('sys.modules', {499 with patch.dict('sys.modules', {
498 'megatron': MagicMock(),500 'megatron': MagicMock(),
499 'megatron.core': MagicMock(),501 'megatron.core': MagicMock(),
@@ -525,10 +527,55 @@ class TestPatchGradSyncWrapper(unittest.TestCase):
525 result = mock_bucket_module.Bucket.start_grad_sync(527 result = mock_bucket_module.Bucket.start_grad_sync(
526 bucket, 'arg1', 'arg2', key1='kwarg1', key2='kwarg2'528 bucket, 'arg1', 'arg2', key1='kwarg1', key2='kwarg2'
527 )529 )
528- 530+ 
529 self.assertEqual(result, 'sync_result')531 self.assertEqual(result, 'sync_result')
530 self.assertEqual(received_args, ['arg1', 'arg2'])532 self.assertEqual(received_args, ['arg1', 'arg2'])
531 self.assertEqual(received_kwargs, [('key1', 'kwarg1'), ('key2', 'kwarg2')])533 self.assertEqual(received_kwargs, [('key1', 'kwarg1'), ('key2', 'kwarg2')])
534+ logged_messages = [call.args[0] for call in mock_debug.call_args_list]
535+ self.assertTrue(any("megatron sync_grad_func enter" in msg for msg in logged_messages))
536+ self.assertTrue(any("megatron sync_grad_func collected grad_count" in msg for msg in logged_messages))
537+ 
538+ 
539+class TestDeepSpeedPatchGradSync(unittest.TestCase):
540+ @patch("msprobe.pytorch.monitor.optimizer_collect.logger.debug")
541+ @patch("msprobe.pytorch.monitor.optimizer_collect.get_metrics")
542+ def test_patch_grad_sync_logs_bucket_info(self, mock_get_metrics, mock_debug):
543+ optimizer = DeepSpeedZeroOptimizerStage1or2Mon(MagicMock())
544+ monitor = MagicMock()
545+ param = torch.nn.Parameter(torch.randn(2, 3))
546+ monitor.param2name = defaultdict(str, {param: "test_param"})
547+ monitor.name2tag = {"test_param": {MonitorConst.PRE_GRAD: "tag1"}}
548+ monitor.ops = []
549+ monitor.eps = 1e-8
550+ monitor.grad_context = SimpleNamespace(pre={})
551+ monitor.register_param_call_id = MagicMock()
552+ 
553+ with patch.dict('sys.modules', {
554+ 'deepspeed': MagicMock(),
555+ 'deepspeed.runtime': MagicMock(),
556+ 'deepspeed.runtime.zero': MagicMock(),
557+ 'deepspeed.runtime.zero.stage_1_and_2': MagicMock(),
558+ }):
559+ stage_module = sys.modules['deepspeed.runtime.zero.stage_1_and_2']
560+ 
561+ class DummyZeroOptimizer:
562+ average_tensor = staticmethod(lambda zero_optimizer, *args, **kwargs: "avg")
563+ buffered_reduce_fallback = staticmethod(lambda zero_optimizer, *args, **kwargs: "fallback")
564+ 
565+ stage_module.DeepSpeedZeroOptimizer = DummyZeroOptimizer
566+ optimizer.patch_grad_sync(monitor)
567+ zero_optimizer = MagicMock()
568+ zero_optimizer.params_in_ipg_bucket = [(0, param, None)]
569+ zero_optimizer.bit16_groups = [[param]]
570+ zero_optimizer.get_gradient_for_reduction.return_value = torch.ones_like(param)
571+ 
572+ result = stage_module.DeepSpeedZeroOptimizer.average_tensor(zero_optimizer)
573+ 
574+ self.assertEqual(result, "avg")
575+ mock_get_metrics.assert_called_once()
576+ logged_messages = [call.args[0] for call in mock_debug.call_args_list]
577+ self.assertTrue(any("deepspeed sync_grad_func enter" in msg for msg in logged_messages))
578+ self.assertTrue(any("deepspeed sync_grad_func collected grad_count" in msg for msg in logged_messages))
532 579 
533 580 
534class TestOptimizerMonFactory(unittest.TestCase):581class TestOptimizerMonFactory(unittest.TestCase):