From 2575aeb869edded870c7c97a0f566fd42ad35817 Mon Sep 17 00:00:00 2001 From: Yiqin <312065559@qq.com> Date: Mon, 1 Dec 2025 13:45:12 +0000 Subject: [PATCH 1/7] lint --- mmengine/runner/runner.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/mmengine/runner/runner.py b/mmengine/runner/runner.py index 9b7b64e2c1..2bc11f43dc 100644 --- a/mmengine/runner/runner.py +++ b/mmengine/runner/runner.py @@ -902,8 +902,14 @@ def wrap_model( find_unused_parameters=find_unused_parameters) else: model_wrapper_cfg.setdefault('type', 'MMDistributedDataParallel') - model_wrapper_type = MODEL_WRAPPERS.get( - model_wrapper_cfg.get('type')) # type: ignore + model_wrapper_type = model_wrapper_cfg.get('type') + if isinstance(model_wrapper_type, str): + model_wrapper_type = MODEL_WRAPPERS.get( + model_wrapper_type) # type: ignore + else: + assert isinstance(model_wrapper_type, + type), 'type should be a string or a class' + default_args: dict = dict() if issubclass( model_wrapper_type, # type: ignore From 6990d7f1855f91fec93c57b87f45d656f096a7c4 Mon Sep 17 00:00:00 2001 From: mgam <312065559@qq.com> Date: Tue, 2 Dec 2025 19:28:23 +0800 Subject: [PATCH 2/7] use `isclass` for clarity --- mmengine/runner/runner.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mmengine/runner/runner.py b/mmengine/runner/runner.py index 2bc11f43dc..c01bf77694 100644 --- a/mmengine/runner/runner.py +++ b/mmengine/runner/runner.py @@ -9,6 +9,7 @@ import warnings from collections import OrderedDict from functools import partial +from inspect import isclass from typing import Callable, Dict, List, Optional, Sequence, Union import torch @@ -904,11 +905,10 @@ def wrap_model( model_wrapper_cfg.setdefault('type', 'MMDistributedDataParallel') model_wrapper_type = model_wrapper_cfg.get('type') if isinstance(model_wrapper_type, str): - model_wrapper_type = MODEL_WRAPPERS.get( - model_wrapper_type) # type: ignore + model_wrapper_type = MODEL_WRAPPERS.get(model_wrapper_type) else: - assert isinstance(model_wrapper_type, - type), 'type should be a string or a class' + assert isclass( + model_wrapper_type), 'type should be a string or a class' default_args: dict = dict() if issubclass( From 1ac00796124fdba5c315c2022db20db676e86396 Mon Sep 17 00:00:00 2001 From: ZhangYiqin <312065559@qq.com> Date: Thu, 4 Dec 2025 19:29:19 +0800 Subject: [PATCH 3/7] Replace AssertionError with TypeError Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- mmengine/runner/runner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mmengine/runner/runner.py b/mmengine/runner/runner.py index c01bf77694..dc5f8b57f6 100644 --- a/mmengine/runner/runner.py +++ b/mmengine/runner/runner.py @@ -907,8 +907,8 @@ def wrap_model( if isinstance(model_wrapper_type, str): model_wrapper_type = MODEL_WRAPPERS.get(model_wrapper_type) else: - assert isclass( - model_wrapper_type), 'type should be a string or a class' + if not isclass(model_wrapper_type): + raise TypeError('type should be a string or a class') default_args: dict = dict() if issubclass( From d5bfa7eccc977129900030710e520b4025b1d2f3 Mon Sep 17 00:00:00 2001 From: mgam <312065559@qq.com> Date: Thu, 4 Dec 2025 22:17:53 +0800 Subject: [PATCH 4/7] Add test unit for pure-python-style config inited runner under distributed environment --- .../pure_python_style_toy_config.py | 68 +++++++++++++++++++ tests/test_dist/test_dist.py | 11 +++ 2 files changed, 79 insertions(+) create mode 100644 tests/data/config/lazy_module_config/pure_python_style_toy_config.py diff --git a/tests/data/config/lazy_module_config/pure_python_style_toy_config.py b/tests/data/config/lazy_module_config/pure_python_style_toy_config.py new file mode 100644 index 0000000000..660a8550e0 --- /dev/null +++ b/tests/data/config/lazy_module_config/pure_python_style_toy_config.py @@ -0,0 +1,68 @@ +# Copyright (c) VBTI. All rights reserved. +from torch.optim import SGD +from mmengine.dataset import DefaultSampler +from mmengine.hooks import (CheckpointHook, DistSamplerSeedHook, + IterTimerHook, LoggerHook, ParamSchedulerHook, + RuntimeInfoHook) +from mmengine.model import MMDistributedDataParallel +from mmengine.optim import MultiStepLR, OptimWrapper +import importlib.util +import os + +# Dynamically load the test module by file path to avoid relative import +# issues when the config is parsed during tests. +_mod_path = os.path.abspath( + os.path.join(os.path.dirname(__file__), '..', '..', '..', 'test_runner', 'test_runner.py')) +spec = importlib.util.spec_from_file_location('test_runner_testmod', _mod_path) +_mod = importlib.util.module_from_spec(spec) +spec.loader.exec_module(_mod) +ToyDataset = _mod.ToyDataset +ToyModel = _mod.ToyModel +ToyMetric1 = _mod.ToyMetric1 + +# Clean up temporary variables +del importlib +del os +del spec +del _mod +del _mod_path + +model=dict(type=ToyModel) +train_dataloader=dict( + dataset=dict(type=ToyDataset), + sampler=dict(type=DefaultSampler, shuffle=True), + batch_size=3, + num_workers=0) +val_dataloader=dict( + dataset=dict(type=ToyDataset), + sampler=dict(type=DefaultSampler, shuffle=False), + batch_size=3, + num_workers=0) +test_dataloader=dict( + dataset=dict(type=ToyDataset), + sampler=dict(type=DefaultSampler, shuffle=False), + batch_size=3, + num_workers=0) +auto_scale_lr=dict(base_batch_size=16, enable=False) +optim_wrapper=dict( + type=OptimWrapper, optimizer=dict(type=SGD, lr=0.01)) +model_wrapper_cfg=dict(type=MMDistributedDataParallel) +param_scheduler=dict(type=MultiStepLR, milestones=[1, 2]) +val_evaluator=dict(type=ToyMetric1) +test_evaluator=dict(type=ToyMetric1) +train_cfg=dict( + by_epoch=True, max_epochs=3, val_interval=1, val_begin=1) +val_cfg=dict() +test_cfg=dict() +custom_hooks=[] +default_hooks=dict( + runtime_info=dict(type=RuntimeInfoHook), + timer=dict(type=IterTimerHook), + logger=dict(type=LoggerHook), + param_scheduler=dict(type=ParamSchedulerHook), + checkpoint=dict( + type=CheckpointHook, interval=1, by_epoch=True), + sampler_seed=dict(type=DistSamplerSeedHook)) +data_preprocessor=None +launcher = 'pytorch' +env_cfg=dict(dist_cfg=dict(backend='nccl')) diff --git a/tests/test_dist/test_dist.py b/tests/test_dist/test_dist.py index a2ef07b713..2b87d014bd 100644 --- a/tests/test_dist/test_dist.py +++ b/tests/test_dist/test_dist.py @@ -11,8 +11,10 @@ import torch.distributed as torch_dist import mmengine.dist as dist +from mmengine.config import Config from mmengine.device import is_musa_available from mmengine.dist.dist import sync_random_seed +from mmengine.runner import Runner from mmengine.testing._internal import MultiProcessTestCase from mmengine.utils import digit_version from mmengine.utils.dl_utils import TORCH_VERSION @@ -656,3 +658,12 @@ def test_all_reduce_params(self): for item1, item2 in zip(data_gen, expected): self.assertTrue(torch.allclose(item1, item2)) + + def test_build_runner_pure_python_style(self): + cfg = Config.fromfile( + osp.join( + osp.dirname(__file__), '..', 'data', 'config', + 'lazy_module_config', 'pure_python_style_toy_config.py')) + cfg.work_dir = tempfile.mkdtemp() + cfg.experiment_name = 'test_build_runner_pure_python_style_config' + Runner.from_cfg(cfg) From b80838e74bf5ecb0b45344ad3298fd954daeff48 Mon Sep 17 00:00:00 2001 From: mgam <312065559@qq.com> Date: Thu, 4 Dec 2025 22:28:37 +0800 Subject: [PATCH 5/7] fix test --- tests/test_dist/test_dist.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_dist/test_dist.py b/tests/test_dist/test_dist.py index 2b87d014bd..de413ed508 100644 --- a/tests/test_dist/test_dist.py +++ b/tests/test_dist/test_dist.py @@ -660,6 +660,7 @@ def test_all_reduce_params(self): self.assertTrue(torch.allclose(item1, item2)) def test_build_runner_pure_python_style(self): + self._init_dist_env(self.rank, self.world_size) cfg = Config.fromfile( osp.join( osp.dirname(__file__), '..', 'data', 'config', From 2678222f909240539c505e7feb7162f96beabec1 Mon Sep 17 00:00:00 2001 From: mgam <312065559@qq.com> Date: Thu, 4 Dec 2025 22:38:21 +0800 Subject: [PATCH 6/7] Support pretty_text config item when `isclass(item)` --- mmengine/config/config.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mmengine/config/config.py b/mmengine/config/config.py index 183138eea9..873408b7d4 100644 --- a/mmengine/config/config.py +++ b/mmengine/config/config.py @@ -1394,6 +1394,11 @@ def _indent(s_, num_spaces): def _format_basic_types(k, v, use_mapping=False): if isinstance(v, str): v_str = repr(v) + elif isinstance(v, type): + if v.__module__ == 'builtins': + v_str = v.__name__ + else: + v_str = f'{v.__module__}.{v.__name__}' else: v_str = str(v) @@ -1425,6 +1430,12 @@ def _format_list_tuple(k, v, use_mapping=False): v_str += f'{_indent(_format_list_tuple(None, item), indent)},\n' # noqa: 501 elif isinstance(item, str): v_str += f'{_indent(repr(item), indent)},\n' + elif isinstance(item, type): + if item.__module__ == 'builtins': + item_str = item.__name__ + else: + item_str = f'{item.__module__}.{item.__name__}' + v_str += f'{_indent(item_str, indent)},\n' else: v_str += str(item) + ',\n' if k is None: From 7523faa6ebbb030963bfd53d93cd0460159ffb3b Mon Sep 17 00:00:00 2001 From: mgam <312065559@qq.com> Date: Thu, 4 Dec 2025 22:52:43 +0800 Subject: [PATCH 7/7] Fix distributed test --- tests/test_dist/test_dist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_dist/test_dist.py b/tests/test_dist/test_dist.py index de413ed508..f6ef3b6c7b 100644 --- a/tests/test_dist/test_dist.py +++ b/tests/test_dist/test_dist.py @@ -364,7 +364,7 @@ def _init_dist_env(self, rank, world_size): """Initialize the distributed environment.""" os.environ['MASTER_ADDR'] = '127.0.0.1' os.environ['MASTER_PORT'] = '29505' - os.environ['RANK'] = str(rank) + os.environ['LOCAL_RANK'] = os.environ['RANK'] = str(rank) num_gpus = torch.cuda.device_count() torch.cuda.set_device(rank % num_gpus)