From a344a24899e347e611347b165ab5fcc3a603bbe0 Mon Sep 17 00:00:00 2001 From: sungmanc <sungman.cho@intel.com> Date: Tue, 19 Sep 2023 16:18:08 +0900 Subject: [PATCH 01/17] Fix hook's ordering issue. AdaptiveRepeatHook changes the runner.max_iters before the ProgressHook --- .../adapters/mmcls/configurer.py | 7 ---- .../common/adapters/mmcv/clsincr_mixin.py | 8 ----- .../common/adapters/mmcv/configurer.py | 10 +++++- .../mmcv/hooks/adaptive_repeat_data_hook.py | 34 ++++++++++++++----- .../adapters/mmcv/hooks/task_adapt_hook.py | 5 --- .../adapters/mmcv/utils/config_utils.py | 23 +++++++++++++ .../dataloaders/samplers/balanced_sampler.py | 7 ++-- .../dataloaders/samplers/cls_incr_sampler.py | 7 ++-- .../torch/dataloaders/samplers/otx_sampler.py | 32 ++++++++--------- .../multilabel/incremental.yaml | 7 ++-- 10 files changed, 85 insertions(+), 55 deletions(-) diff --git a/src/otx/algorithms/classification/adapters/mmcls/configurer.py b/src/otx/algorithms/classification/adapters/mmcls/configurer.py index 3199d4cb5d2..4859a38a110 100644 --- a/src/otx/algorithms/classification/adapters/mmcls/configurer.py +++ b/src/otx/algorithms/classification/adapters/mmcls/configurer.py @@ -208,13 +208,6 @@ def get_sampler_type(self, cfg): sampler_type = "cls_incr" return sampler_type - def use_adaptive_repeat(self, cfg) -> bool: - """Return whether using adaptive repeat. - - Currently, only multi class classification supports adaptive repeat. - """ - return self._is_multiclass(cfg) - @staticmethod def _is_multiclass(cfg) -> bool: return not cfg.model.get("multilabel", False) and not cfg.model.get("hierarchical", False) diff --git a/src/otx/algorithms/common/adapters/mmcv/clsincr_mixin.py b/src/otx/algorithms/common/adapters/mmcv/clsincr_mixin.py index bdbdf35e080..02251a8f200 100644 --- a/src/otx/algorithms/common/adapters/mmcv/clsincr_mixin.py +++ b/src/otx/algorithms/common/adapters/mmcv/clsincr_mixin.py @@ -36,7 +36,6 @@ def configure_task_adapt_hook(self, cfg): sampler_flag=sampler_flag, sampler_type=self.get_sampler_type(cfg), efficient_mode=cfg["task_adapt"].get("efficient_mode", False), - use_adaptive_repeat=self.use_adaptive_repeat(cfg), priority="NORMAL", ), ) @@ -50,10 +49,3 @@ def is_incremental(self) -> bool: def get_sampler_type(self, cfg) -> str: """Return sampler type.""" return "cls_incr" - - def use_adaptive_repeat(self, cfg) -> bool: - """Return whether using adaptive repeat. - - Currently, only multi class classification supports adaptive repeat. - """ - return False diff --git a/src/otx/algorithms/common/adapters/mmcv/configurer.py b/src/otx/algorithms/common/adapters/mmcv/configurer.py index 87ce4b015c9..1c153c1e28e 100644 --- a/src/otx/algorithms/common/adapters/mmcv/configurer.py +++ b/src/otx/algorithms/common/adapters/mmcv/configurer.py @@ -16,6 +16,7 @@ patch_adaptive_interval_training, patch_early_stopping, patch_persistent_workers, + remove_from_configs_by_type, ) from otx.algorithms.common.adapters.mmcv.utils.config_utils import ( patch_color_conversion, @@ -195,7 +196,6 @@ def configure_samples_per_gpu( samples_per_gpu can be changed if it is larger than length of datset """ - for subset in subsets: if cfg.data.get(subset, None): dataloader_cfg = cfg.data.get(f"{subset}_dataloader", ConfigDict()) @@ -410,6 +410,14 @@ def configure_hooks( if hasattr(cfg, "algo_backend"): self._update_caching_modules(cfg) + # Update adaptive repeat + if not self.training: + remove_from_configs_by_type(cfg.custom_hooks, "AdaptiveRepeatDataHook") + for custom_hook in cfg.custom_hooks: + if custom_hook["type"] == "AdaptiveRepeatDataHook": + custom_hook["train_batch_size"] = cfg.data.train_dataloader.get("samples_per_gpu") + custom_hook["n_train_data"] = len(cfg.data.train.get("otx_dataset")) + @staticmethod def _update_caching_modules(cfg: Config) -> None: def _find_max_num_workers(cfg: dict): diff --git a/src/otx/algorithms/common/adapters/mmcv/hooks/adaptive_repeat_data_hook.py b/src/otx/algorithms/common/adapters/mmcv/hooks/adaptive_repeat_data_hook.py index 042defcea15..254eabee06d 100644 --- a/src/otx/algorithms/common/adapters/mmcv/hooks/adaptive_repeat_data_hook.py +++ b/src/otx/algorithms/common/adapters/mmcv/hooks/adaptive_repeat_data_hook.py @@ -6,6 +6,7 @@ from mmcv.runner import HOOKS, Hook, get_dist_info from torch.utils.data import DataLoader +from otx.algorithms.common.adapters.mmcv.utils.config_utils import get_proper_repeat_times from otx.algorithms.common.adapters.torch.dataloaders.samplers import OTXSampler from otx.algorithms.common.utils.logger import get_logger @@ -17,38 +18,55 @@ class AdaptiveRepeatDataHook(Hook): """Hook that adaptively repeats the dataset to control the number of iterations. Args: + train_batch_size (int) : The batch size of the train dataloader + n_train_data (int) : The number of the training dataset coef (float, optional) : coefficient that effects to number of repeats (coef * math.sqrt(num_iters-1)) +5 min_repeat (float, optional) : minimum repeats """ - def __init__(self, coef: float = -0.7, min_repeat: float = 1.0): + def __init__(self, train_batch_size: int, n_train_data: int, coef: float = -0.7, min_repeat: float = 1.0): self.coef = coef self.min_repeat = min_repeat + self.train_batch_size = train_batch_size + self.n_train_data = n_train_data + + self.n_repeats = get_proper_repeat_times(self.n_train_data, self.train_batch_size, self.coef, self.min_repeat) + self.rank, self.world_size = get_dist_info() + + def before_run(self, runner): + """Change the runner's max_iter.""" + iter_per_epoch = int(self.n_train_data / self.train_batch_size) + + logger.info("Adaptive repeat is enabled") + logger.info(f"- Repeat times: {self.n_repeats}") + logger.info(f"- Num iters per epoch: {iter_per_epoch} -> {iter_per_epoch * self.n_repeats}") + logger.info(f"- Total iters: {runner.max_iters} -> {runner.max_iters * self.n_repeats}") + + runner._max_iters = int(runner.max_iters * self.n_repeats) + def before_epoch(self, runner): """Convert to OTX Sampler.""" dataset = runner.data_loader.dataset - batch_size = runner.data_loader.batch_size num_workers = runner.data_loader.num_workers collate_fn = runner.data_loader.collate_fn worker_init_fn = runner.data_loader.worker_init_fn - rank, world_size = get_dist_info() sampler = OTXSampler( dataset=dataset, - samples_per_gpu=batch_size, - use_adaptive_repeats=True, - num_replicas=world_size, - rank=rank, + samples_per_gpu=self.train_batch_size, + num_replicas=self.world_size, + rank=self.rank, shuffle=True, coef=self.coef, min_repeat=self.min_repeat, + n_repeats=self.n_repeats, ) runner.data_loader = DataLoader( dataset, - batch_size=batch_size, + batch_size=self.train_batch_size, sampler=sampler, num_workers=num_workers, collate_fn=collate_fn, diff --git a/src/otx/algorithms/common/adapters/mmcv/hooks/task_adapt_hook.py b/src/otx/algorithms/common/adapters/mmcv/hooks/task_adapt_hook.py index 468fe0e23a3..29d4f0ad87b 100644 --- a/src/otx/algorithms/common/adapters/mmcv/hooks/task_adapt_hook.py +++ b/src/otx/algorithms/common/adapters/mmcv/hooks/task_adapt_hook.py @@ -36,7 +36,6 @@ def __init__( sampler_flag=False, sampler_type="cls_incr", efficient_mode=False, - use_adaptive_repeat=False, ): self.src_classes = src_classes self.dst_classes = dst_classes @@ -44,13 +43,11 @@ def __init__( self.sampler_flag = sampler_flag self.sampler_type = sampler_type self.efficient_mode = efficient_mode - self.use_adaptive_repeat = use_adaptive_repeat logger.info(f"Task Adaptation: {self.src_classes} => {self.dst_classes}") logger.info(f"- Efficient Mode: {self.efficient_mode}") logger.info(f"- Sampler type: {self.sampler_type}") logger.info(f"- Sampler flag: {self.sampler_flag}") - logger.info(f"- Adaptive repeat: {self.use_adaptive_repeat}") def before_epoch(self, runner): """Produce a proper sampler for task-adaptation.""" @@ -68,7 +65,6 @@ def before_epoch(self, runner): efficient_mode=self.efficient_mode, num_replicas=world_size, rank=rank, - use_adaptive_repeats=self.use_adaptive_repeat, ) else: sampler = ClsIncrSampler( @@ -77,7 +73,6 @@ def before_epoch(self, runner): efficient_mode=self.efficient_mode, num_replicas=world_size, rank=rank, - use_adaptive_repeats=self.use_adaptive_repeat, ) runner.data_loader = DataLoader( dataset, diff --git a/src/otx/algorithms/common/adapters/mmcv/utils/config_utils.py b/src/otx/algorithms/common/adapters/mmcv/utils/config_utils.py index 9a06a5d96f0..2cf044ab6ad 100644 --- a/src/otx/algorithms/common/adapters/mmcv/utils/config_utils.py +++ b/src/otx/algorithms/common/adapters/mmcv/utils/config_utils.py @@ -16,6 +16,7 @@ import copy import glob +import math import multiprocessing import os import os.path as osp @@ -887,3 +888,25 @@ def get_configured_input_size( parsed_tocken = re.match("(\\d+)x(\\d+)", input_size) return (int(parsed_tocken.group(1)), int(parsed_tocken.group(2))) + + +def get_proper_repeat_times( + n_data: int, + batch_size: int, + coef: float, + min_repeat: float, +) -> float: + """Get proper repeat times for adaptive training. + + Args: + n_data (int): The total number of the training dataset + batch_size (int): The batch size for the training data loader + coef (float, optional) : coefficient that effects to number of repeats + (coef * math.sqrt(num_iters-1)) +5 + min_repeat (float, optional) : minimum repeats + """ + if n_data == 0 or batch_size == 0: + logger.info("Repeat dataset enabled, but not a train mode. repeat times set to 1.") + return 1 + n_iters_per_epoch = math.ceil(n_data / batch_size) + return math.floor(max(coef * math.sqrt(n_iters_per_epoch - 1) + 5, min_repeat)) diff --git a/src/otx/algorithms/common/adapters/torch/dataloaders/samplers/balanced_sampler.py b/src/otx/algorithms/common/adapters/torch/dataloaders/samplers/balanced_sampler.py index 711f0f4729b..aa12338910c 100644 --- a/src/otx/algorithms/common/adapters/torch/dataloaders/samplers/balanced_sampler.py +++ b/src/otx/algorithms/common/adapters/torch/dataloaders/samplers/balanced_sampler.py @@ -4,6 +4,7 @@ # import math +from typing import Union import numpy as np from torch.utils.data import Dataset @@ -37,7 +38,7 @@ class BalancedSampler(OTXSampler): # pylint: disable=too-many-instance-attribut tail of the data to make it evenly divisible across the number of replicas. If ``False``, the sampler will add extra indices to make the data evenly divisible across the replicas. Default: ``False``. - use_adaptive_repeats (bool, optional): Flag about using adaptive repeats + n_repeats (Union[float, int, str], optional) : number of iterations for manual setting """ def __init__( @@ -48,14 +49,14 @@ def __init__( num_replicas: int = 1, rank: int = 0, drop_last: bool = False, - use_adaptive_repeats: bool = False, + n_repeats: Union[float, int, str] = "auto", ): self.samples_per_gpu = samples_per_gpu self.num_replicas = num_replicas self.rank = rank self.drop_last = drop_last - super().__init__(dataset, samples_per_gpu, use_adaptive_repeats) + super().__init__(dataset, samples_per_gpu, n_repeats=n_repeats) self.img_indices = self.dataset.img_indices # type: ignore[attr-defined] self.num_cls = len(self.img_indices.keys()) diff --git a/src/otx/algorithms/common/adapters/torch/dataloaders/samplers/cls_incr_sampler.py b/src/otx/algorithms/common/adapters/torch/dataloaders/samplers/cls_incr_sampler.py index 6b03f8cdf93..ee3fca43699 100644 --- a/src/otx/algorithms/common/adapters/torch/dataloaders/samplers/cls_incr_sampler.py +++ b/src/otx/algorithms/common/adapters/torch/dataloaders/samplers/cls_incr_sampler.py @@ -5,6 +5,7 @@ import math import random +from typing import Union import numpy as np from torch.utils.data import Dataset @@ -35,7 +36,7 @@ class ClsIncrSampler(OTXSampler): # pylint: disable=too-many-instance-attribute tail of the data to make it evenly divisible across the number of replicas. If ``False``, the sampler will add extra indices to make the data evenly divisible across the replicas. Default: ``False``. - use_adaptive_repeats (bool, optional): Flag about using adaptive repeats + n_repeats (Union[float, int, str], optional) : number of iterations for manual setting """ def __init__( @@ -46,14 +47,14 @@ def __init__( num_replicas: int = 1, rank: int = 0, drop_last: bool = False, - use_adaptive_repeats: bool = False, + n_repeats: Union[float, int, str] = "auto", ): self.samples_per_gpu = samples_per_gpu self.num_replicas = num_replicas self.rank = rank self.drop_last = drop_last - super().__init__(dataset, samples_per_gpu, use_adaptive_repeats) + super().__init__(dataset, samples_per_gpu, n_repeats=n_repeats) if hasattr(self.dataset, "img_indices"): self.new_indices = self.dataset.img_indices["new"] diff --git a/src/otx/algorithms/common/adapters/torch/dataloaders/samplers/otx_sampler.py b/src/otx/algorithms/common/adapters/torch/dataloaders/samplers/otx_sampler.py index 9f9a4cac2b6..6af9a269694 100644 --- a/src/otx/algorithms/common/adapters/torch/dataloaders/samplers/otx_sampler.py +++ b/src/otx/algorithms/common/adapters/torch/dataloaders/samplers/otx_sampler.py @@ -4,13 +4,14 @@ # import math -from typing import Optional +from typing import Optional, Union import numpy as np import torch from torch.utils.data import Dataset from torch.utils.data.sampler import Sampler +from otx.algorithms.common.adapters.mmcv.utils.config_utils import get_proper_repeat_times from otx.algorithms.common.utils.logger import get_logger from otx.algorithms.common.utils.task_adapt import unwrap_dataset @@ -32,7 +33,6 @@ class OTXSampler(Sampler): # pylint: disable=too-many-instance-attributes Args: dataset (Dataset): A built-up dataset samples_per_gpu (int): batch size of Sampling - use_adaptive_repeats (bool): Flag about using adaptive repeats num_replicas (int, optional): Number of processes participating in distributed training. By default, :attr:`world_size` is retrieved from the current distributed group. @@ -42,18 +42,22 @@ class OTXSampler(Sampler): # pylint: disable=too-many-instance-attributes shuffle (bool, optional): Flag about shuffling coef (int, optional): controls the repeat value min_repeat (float, optional): minimum value of the repeat dataset + n_repeats (Union[float, int str], optional) : number of iterations for manual setting + seed (int, optional): Random seed used to shuffle the sampler if + :attr:`shuffle=True`. This number should be identical across all + processes in the distributed group. Defaults to None. """ def __init__( self, dataset: Dataset, samples_per_gpu: int, - use_adaptive_repeats: bool, num_replicas: int = 1, rank: int = 0, shuffle: bool = True, coef: float = -0.7, min_repeat: float = 1.0, + n_repeats: Union[float, int, str] = "auto", seed: Optional[int] = None, ): @@ -62,8 +66,13 @@ def __init__( self.num_replicas = num_replicas self.rank = rank self.shuffle = shuffle - self.repeat = self._get_proper_repeats(use_adaptive_repeats, coef, min_repeat) - + if n_repeats == "auto": + repeat = get_proper_repeat_times(len(self.dataset), self.samples_per_gpu, coef, min_repeat) + elif type(n_repeats).__name__ == "float": + repeat = float(n_repeats) + else: + raise ValueError(f"n_repeats: {n_repeats} should be auto or int value") + self.repeat = int(repeat) self.num_samples = math.ceil(len(self.dataset) * self.repeat / self.num_replicas) self.total_size = self.num_samples * self.num_replicas @@ -73,19 +82,6 @@ def __init__( self.seed = seed self.epoch = 0 - def _get_proper_repeats(self, use_adaptive_repeats: bool, coef: float, min_repeat: float): - """Calculate the proper repeats with considering the number of iterations.""" - n_repeats = 1 - if use_adaptive_repeats: - # NOTE - # Currently, only support the integer type repeats. - # Will support the floating point repeats and large dataset cases. - n_iters_per_epoch = math.ceil(len(self.dataset) / self.samples_per_gpu) - n_repeats = math.floor(max(coef * math.sqrt(n_iters_per_epoch - 1) + 5, min_repeat)) - logger.info("OTX Sampler: adaptive repeats enabled") - logger.info(f"OTX will use {n_repeats} times larger dataset made by repeated sampling") - return n_repeats - def __iter__(self): """Iter.""" if self.shuffle: diff --git a/src/otx/recipes/stages/classification/multilabel/incremental.yaml b/src/otx/recipes/stages/classification/multilabel/incremental.yaml index 85b25d96aa9..cdc23c0eae9 100644 --- a/src/otx/recipes/stages/classification/multilabel/incremental.yaml +++ b/src/otx/recipes/stages/classification/multilabel/incremental.yaml @@ -23,5 +23,8 @@ task_adapt: type: "default_task_adapt" op: "REPLACE" -custom_hooks: - - type: ModelEmaV2Hook +custom_hooks: [ + { + type: ModelEmaV2Hook + } +] From 2db44f40119718d18c1a55547822b6c11188bc17 Mon Sep 17 00:00:00 2001 From: sungmanc <sungman.cho@intel.com> Date: Tue, 19 Sep 2023 16:39:48 +0900 Subject: [PATCH 02/17] Change the expression --- .../adapters/torch/dataloaders/samplers/otx_sampler.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/otx/algorithms/common/adapters/torch/dataloaders/samplers/otx_sampler.py b/src/otx/algorithms/common/adapters/torch/dataloaders/samplers/otx_sampler.py index 6af9a269694..9abc3d2966a 100644 --- a/src/otx/algorithms/common/adapters/torch/dataloaders/samplers/otx_sampler.py +++ b/src/otx/algorithms/common/adapters/torch/dataloaders/samplers/otx_sampler.py @@ -68,10 +68,12 @@ def __init__( self.shuffle = shuffle if n_repeats == "auto": repeat = get_proper_repeat_times(len(self.dataset), self.samples_per_gpu, coef, min_repeat) - elif type(n_repeats).__name__ == "float": + elif isinstance(n_repeats, (int, float)): repeat = float(n_repeats) else: - raise ValueError(f"n_repeats: {n_repeats} should be auto or int value") + raise ValueError(f"n_repeats: {n_repeats} should be auto or float orint value") + # TODO: Currently, only supporting the int variable. + # Will be removed. self.repeat = int(repeat) self.num_samples = math.ceil(len(self.dataset) * self.repeat / self.num_replicas) self.total_size = self.num_samples * self.num_replicas From 3a0f38f2d12a53dbab0994a02d5420eeeb0a241d Mon Sep 17 00:00:00 2001 From: sungmanc <sungman.cho@intel.com> Date: Tue, 19 Sep 2023 16:45:17 +0900 Subject: [PATCH 03/17] Fix typo --- .../common/adapters/torch/dataloaders/samplers/otx_sampler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/otx/algorithms/common/adapters/torch/dataloaders/samplers/otx_sampler.py b/src/otx/algorithms/common/adapters/torch/dataloaders/samplers/otx_sampler.py index 9abc3d2966a..b01f2aaef66 100644 --- a/src/otx/algorithms/common/adapters/torch/dataloaders/samplers/otx_sampler.py +++ b/src/otx/algorithms/common/adapters/torch/dataloaders/samplers/otx_sampler.py @@ -71,7 +71,7 @@ def __init__( elif isinstance(n_repeats, (int, float)): repeat = float(n_repeats) else: - raise ValueError(f"n_repeats: {n_repeats} should be auto or float orint value") + raise ValueError(f"n_repeats: {n_repeats} should be auto or float or int value") # TODO: Currently, only supporting the int variable. # Will be removed. self.repeat = int(repeat) From d0cf6b2a67295e4ddc13ef9d71cd6492a271d5ea Mon Sep 17 00:00:00 2001 From: sungmanc <sungman.cho@intel.com> Date: Wed, 20 Sep 2023 22:55:12 +0900 Subject: [PATCH 04/17] Fix multi-label, h-label issue --- .../common/adapters/mmcv/configurer.py | 7 +++-- .../adapters/mmcv/utils/config_utils.py | 14 ++++----- .../multilabel/incremental.yaml | 29 +++++++++++++++++++ .../classification/multilabel/train.yaml | 2 +- .../hooks/test_adaptive_repeat_data_hook.py | 4 +-- .../dataloaders/samplers/test_otx_sampler.py | 6 ++-- 6 files changed, 44 insertions(+), 18 deletions(-) diff --git a/src/otx/algorithms/common/adapters/mmcv/configurer.py b/src/otx/algorithms/common/adapters/mmcv/configurer.py index 1c153c1e28e..831f3e7bb84 100644 --- a/src/otx/algorithms/common/adapters/mmcv/configurer.py +++ b/src/otx/algorithms/common/adapters/mmcv/configurer.py @@ -415,8 +415,11 @@ def configure_hooks( remove_from_configs_by_type(cfg.custom_hooks, "AdaptiveRepeatDataHook") for custom_hook in cfg.custom_hooks: if custom_hook["type"] == "AdaptiveRepeatDataHook": - custom_hook["train_batch_size"] = cfg.data.train_dataloader.get("samples_per_gpu") - custom_hook["n_train_data"] = len(cfg.data.train.get("otx_dataset")) + data_cfg = cfg.get("data", {}) + bs = data_cfg.get("train_dataloader", {}).get("samples_per_gpu", 0) + bs = bs if bs is not None else data_cfg.get("samples_per_gpu", 0) + custom_hook["train_batch_size"] = bs + custom_hook["n_train_data"] = len(data_cfg.get("train",{}).get("otx_dataset",[])) @staticmethod def _update_caching_modules(cfg: Config) -> None: diff --git a/src/otx/algorithms/common/adapters/mmcv/utils/config_utils.py b/src/otx/algorithms/common/adapters/mmcv/utils/config_utils.py index 2cf044ab6ad..502389a2731 100644 --- a/src/otx/algorithms/common/adapters/mmcv/utils/config_utils.py +++ b/src/otx/algorithms/common/adapters/mmcv/utils/config_utils.py @@ -530,15 +530,11 @@ def patch_from_hyperparams(config: Config, hyperparams, **kwargs): algo_backend = hyperparams.algo_backend warmup_iters = int(params.learning_rate_warmup_iters) - model_label_type = config.filename.split("/")[-1] - if "multilabel" in model_label_type: - lr_config = ConfigDict(max_lr=params.learning_rate, warmup=None) - else: - lr_config = ( - ConfigDict(warmup_iters=warmup_iters) - if warmup_iters > 0 - else ConfigDict(warmup_iters=warmup_iters, warmup=None) - ) + lr_config = ( + ConfigDict(warmup_iters=warmup_iters) + if warmup_iters > 0 + else ConfigDict(warmup_iters=warmup_iters, warmup=None) + ) if params.enable_early_stopping and config.get("evaluation", None): early_stop = ConfigDict( diff --git a/src/otx/recipes/stages/classification/multilabel/incremental.yaml b/src/otx/recipes/stages/classification/multilabel/incremental.yaml index cdc23c0eae9..03a87dc5591 100644 --- a/src/otx/recipes/stages/classification/multilabel/incremental.yaml +++ b/src/otx/recipes/stages/classification/multilabel/incremental.yaml @@ -19,12 +19,41 @@ optimizer: evaluation: metric: ["accuracy", "class_accuracy"] +lr_config: + _delete_: True + policy: ReduceLROnPlateau + min_lr: 0.000001 + interval: 1 + metric: accuracy + factor: 0.5 + patience: 1 + iteration_patience: 0 + warmup: linear + warmup_iters: 1 + warmup_ratio: 0.333 + task_adapt: type: "default_task_adapt" op: "REPLACE" +ignore: True + custom_hooks: [ { type: ModelEmaV2Hook + }, + { + type: LazyEarlyStoppingHook, + interval: 1, + metric: accuracy, + patience: 3, + iteration_patience: 0, + start: 3, + min_delta_ratio: 0.01, + priority: 75, + }, + { + type: AdaptiveRepeatDataHook, + priority: ABOVE_NORMAL } ] diff --git a/src/otx/recipes/stages/classification/multilabel/train.yaml b/src/otx/recipes/stages/classification/multilabel/train.yaml index 5ff73be2aa9..4666a957e36 100644 --- a/src/otx/recipes/stages/classification/multilabel/train.yaml +++ b/src/otx/recipes/stages/classification/multilabel/train.yaml @@ -4,7 +4,7 @@ _base_: "../../_base_/logs/tensorboard_logger.py", "../../_base_/optimizers/sgd.py", "../../_base_/runners/epoch_runner_cancel.py", - "../../_base_/schedules/1cycle.py", + "../../_base_/schedules/plateau.py", ] optimizer: diff --git a/tests/unit/algorithms/common/adapters/mmcv/hooks/test_adaptive_repeat_data_hook.py b/tests/unit/algorithms/common/adapters/mmcv/hooks/test_adaptive_repeat_data_hook.py index 17afb05d007..8810e0f1e87 100644 --- a/tests/unit/algorithms/common/adapters/mmcv/hooks/test_adaptive_repeat_data_hook.py +++ b/tests/unit/algorithms/common/adapters/mmcv/hooks/test_adaptive_repeat_data_hook.py @@ -21,7 +21,7 @@ def __init__(self): def __len__(self): return 10 - + self.mock_dataset = MockDataset() self.mock_data_loader = DataLoader( dataset=MockDataset(), batch_size=len(MockDataset()), @@ -30,7 +30,7 @@ def __len__(self): @e2e_pytest_unit def test_before_epoch(self) -> None: - hook = AdaptiveRepeatDataHook() + hook = AdaptiveRepeatDataHook(64, len(self.mock_dataset)) hook.before_epoch(self.mock_runner) assert self.mock_runner.data_loader.sampler.repeat == 5 diff --git a/tests/unit/algorithms/common/adapters/torch/dataloaders/samplers/test_otx_sampler.py b/tests/unit/algorithms/common/adapters/torch/dataloaders/samplers/test_otx_sampler.py index 8b72418d72e..6d10da154ab 100644 --- a/tests/unit/algorithms/common/adapters/torch/dataloaders/samplers/test_otx_sampler.py +++ b/tests/unit/algorithms/common/adapters/torch/dataloaders/samplers/test_otx_sampler.py @@ -1,5 +1,4 @@ import pytest -import math from torch.utils.data import Dataset from otx.algorithms.common.adapters.torch.dataloaders.samplers import OTXSampler @@ -20,9 +19,8 @@ def __len__(self): @e2e_pytest_unit @pytest.mark.parametrize("batch", [1, 2, 4, 8, 16]) - @pytest.mark.parametrize("use_adaptive_repeat", [True, False]) - def test_sampler_iter(self, batch, use_adaptive_repeat): - sampler = OTXSampler(self.mock_dataset, batch, use_adaptive_repeats=use_adaptive_repeat) + def test_sampler_iter(self, batch): + sampler = OTXSampler(self.mock_dataset, batch) sampler_iter = iter(sampler) count = 0 From c38dcea34756aad4c2cbb254b18f6baeb4ac36b2 Mon Sep 17 00:00:00 2001 From: sungmanc <sungman.cho@intel.com> Date: Wed, 20 Sep 2023 22:55:45 +0900 Subject: [PATCH 05/17] Make black happy --- src/otx/algorithms/common/adapters/mmcv/configurer.py | 2 +- .../adapters/mmcv/hooks/test_adaptive_repeat_data_hook.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/otx/algorithms/common/adapters/mmcv/configurer.py b/src/otx/algorithms/common/adapters/mmcv/configurer.py index 831f3e7bb84..56631c1e495 100644 --- a/src/otx/algorithms/common/adapters/mmcv/configurer.py +++ b/src/otx/algorithms/common/adapters/mmcv/configurer.py @@ -419,7 +419,7 @@ def configure_hooks( bs = data_cfg.get("train_dataloader", {}).get("samples_per_gpu", 0) bs = bs if bs is not None else data_cfg.get("samples_per_gpu", 0) custom_hook["train_batch_size"] = bs - custom_hook["n_train_data"] = len(data_cfg.get("train",{}).get("otx_dataset",[])) + custom_hook["n_train_data"] = len(data_cfg.get("train", {}).get("otx_dataset", [])) @staticmethod def _update_caching_modules(cfg: Config) -> None: diff --git a/tests/unit/algorithms/common/adapters/mmcv/hooks/test_adaptive_repeat_data_hook.py b/tests/unit/algorithms/common/adapters/mmcv/hooks/test_adaptive_repeat_data_hook.py index 8810e0f1e87..550aea5f611 100644 --- a/tests/unit/algorithms/common/adapters/mmcv/hooks/test_adaptive_repeat_data_hook.py +++ b/tests/unit/algorithms/common/adapters/mmcv/hooks/test_adaptive_repeat_data_hook.py @@ -21,6 +21,7 @@ def __init__(self): def __len__(self): return 10 + self.mock_dataset = MockDataset() self.mock_data_loader = DataLoader( dataset=MockDataset(), From 6580630378256d881e5fa7cf3402902fe2302b7a Mon Sep 17 00:00:00 2001 From: sungmanc <sungman.cho@intel.com> Date: Thu, 21 Sep 2023 11:08:03 +0900 Subject: [PATCH 06/17] Fix precommit --- src/otx/algorithms/common/adapters/mmcv/utils/config_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/otx/algorithms/common/adapters/mmcv/utils/config_utils.py b/src/otx/algorithms/common/adapters/mmcv/utils/config_utils.py index c46ebe85aac..b4ce240214a 100644 --- a/src/otx/algorithms/common/adapters/mmcv/utils/config_utils.py +++ b/src/otx/algorithms/common/adapters/mmcv/utils/config_utils.py @@ -974,6 +974,7 @@ def area(x): logger.info(f"-> Closest preset: {input_size}") return input_size + def get_proper_repeat_times( n_data: int, batch_size: int, From 15cc67d519a71a7a8abef7e6fefdde6e15dc0c3d Mon Sep 17 00:00:00 2001 From: sungmanc <sungman.cho@intel.com> Date: Thu, 21 Sep 2023 14:02:36 +0900 Subject: [PATCH 07/17] Fix auto_bs issue --- .../algorithms/common/adapters/mmcv/configurer.py | 2 +- .../mmcv/hooks/adaptive_repeat_data_hook.py | 14 ++++++++------ .../common/adapters/mmcv/utils/automatic_bs.py | 8 ++++++-- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/otx/algorithms/common/adapters/mmcv/configurer.py b/src/otx/algorithms/common/adapters/mmcv/configurer.py index 36504e1ef92..c557f09d527 100644 --- a/src/otx/algorithms/common/adapters/mmcv/configurer.py +++ b/src/otx/algorithms/common/adapters/mmcv/configurer.py @@ -419,7 +419,7 @@ def configure_hooks( for custom_hook in cfg.custom_hooks: if custom_hook["type"] == "AdaptiveRepeatDataHook": data_cfg = cfg.get("data", {}) - bs = data_cfg.get("train_dataloader", {}).get("samples_per_gpu", 0) + bs = data_cfg.get("train_dataloader", {}).get("samples_per_gpu", None) bs = bs if bs is not None else data_cfg.get("samples_per_gpu", 0) custom_hook["train_batch_size"] = bs custom_hook["n_train_data"] = len(data_cfg.get("train", {}).get("otx_dataset", [])) diff --git a/src/otx/algorithms/common/adapters/mmcv/hooks/adaptive_repeat_data_hook.py b/src/otx/algorithms/common/adapters/mmcv/hooks/adaptive_repeat_data_hook.py index 254eabee06d..f3dc49a0f16 100644 --- a/src/otx/algorithms/common/adapters/mmcv/hooks/adaptive_repeat_data_hook.py +++ b/src/otx/algorithms/common/adapters/mmcv/hooks/adaptive_repeat_data_hook.py @@ -37,14 +37,16 @@ def __init__(self, train_batch_size: int, n_train_data: int, coef: float = -0.7, def before_run(self, runner): """Change the runner's max_iter.""" - iter_per_epoch = int(self.n_train_data / self.train_batch_size) + if self.n_repeats > 1: + iter_per_epoch = int(self.n_train_data / self.train_batch_size) - logger.info("Adaptive repeat is enabled") - logger.info(f"- Repeat times: {self.n_repeats}") - logger.info(f"- Num iters per epoch: {iter_per_epoch} -> {iter_per_epoch * self.n_repeats}") - logger.info(f"- Total iters: {runner.max_iters} -> {runner.max_iters * self.n_repeats}") + logger.info("Adaptive repeat is enabled") + logger.info(f"- Repeat times: {self.n_repeats}") + logger.info(f"- Batch size: {self.train_batch_size}") + logger.info(f"- Num iters per epoch: {iter_per_epoch} -> {iter_per_epoch * self.n_repeats}") + logger.info(f"- Total iters: {runner.max_iters} -> {runner.max_iters * self.n_repeats}") - runner._max_iters = int(runner.max_iters * self.n_repeats) + runner._max_iters = int(runner.max_iters * self.n_repeats) def before_epoch(self, runner): """Convert to OTX Sampler.""" diff --git a/src/otx/algorithms/common/adapters/mmcv/utils/automatic_bs.py b/src/otx/algorithms/common/adapters/mmcv/utils/automatic_bs.py index 9b95d58195f..520ff874a8d 100644 --- a/src/otx/algorithms/common/adapters/mmcv/utils/automatic_bs.py +++ b/src/otx/algorithms/common/adapters/mmcv/utils/automatic_bs.py @@ -68,7 +68,7 @@ def train_func_single_iter(batch_size): # earlystoppinghook => if eval hook is excluded, this hook makes an error due to absence of score history # CustomEvalHook => exclude validation in classification task idx_hooks_to_remove = [] - hooks_to_remove = ["OTXProgressHook", "earlystoppinghook", "CustomEvalHook"] + hooks_to_remove = ["OTXProgressHook", "earlystoppinghook", "CustomEvalHook", "AdaptiveRepeatDataHook"] for i, hook in enumerate(copied_cfg.custom_hooks): if not validate and hook["type"] == "AdaptiveTrainSchedulingHook": hook["enable_eval_before_run"] = False @@ -79,6 +79,7 @@ def train_func_single_iter(batch_size): if idx_hooks_to_remove: idx_hooks_to_remove.sort() for i in reversed(idx_hooks_to_remove): + print(f"{copied_cfg.custom_hooks[i]} deleted.") del copied_cfg.custom_hooks[i] new_datasets = [SubDataset(datasets[0], batch_size)] @@ -90,7 +91,6 @@ def train_func_single_iter(batch_size): ) default_bs = _get_batch_size(cfg) - bs_search_algo = BsSearchAlgo( train_func=train_func_single_iter, default_bs=default_bs, @@ -127,6 +127,10 @@ def _set_batch_size(cfg, batch_size: int): else: cfg.data.train_dataloader["samples_per_gpu"] = batch_size + for custom_hook in cfg.custom_hooks: + if custom_hook["type"] == "AdaptiveRepeatDataHook": + custom_hook["train_batch_size"] = batch_size + def _set_max_epoch(cfg, max_epoch: int): if cfg.runner.get("type") == "AccuracyAwareRunner": # nncf case From c0baa7290b841c98899b64fc0a537b312069f3e5 Mon Sep 17 00:00:00 2001 From: Sungman Cho <sungman.cho@intel.com> Date: Mon, 25 Sep 2023 14:41:34 +0900 Subject: [PATCH 08/17] Apply suggestions from code review Co-authored-by: Eunwoo Shin <eunwoo.shin@intel.com> --- src/otx/algorithms/common/adapters/mmcv/configurer.py | 2 ++ src/otx/algorithms/common/adapters/mmcv/utils/config_utils.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/otx/algorithms/common/adapters/mmcv/configurer.py b/src/otx/algorithms/common/adapters/mmcv/configurer.py index c557f09d527..bf648b0379c 100644 --- a/src/otx/algorithms/common/adapters/mmcv/configurer.py +++ b/src/otx/algorithms/common/adapters/mmcv/configurer.py @@ -416,6 +416,7 @@ def configure_hooks( # Update adaptive repeat if not self.training: remove_from_configs_by_type(cfg.custom_hooks, "AdaptiveRepeatDataHook") + return for custom_hook in cfg.custom_hooks: if custom_hook["type"] == "AdaptiveRepeatDataHook": data_cfg = cfg.get("data", {}) @@ -423,6 +424,7 @@ def configure_hooks( bs = bs if bs is not None else data_cfg.get("samples_per_gpu", 0) custom_hook["train_batch_size"] = bs custom_hook["n_train_data"] = len(data_cfg.get("train", {}).get("otx_dataset", [])) + break @staticmethod def _update_caching_modules(cfg: Config) -> None: diff --git a/src/otx/algorithms/common/adapters/mmcv/utils/config_utils.py b/src/otx/algorithms/common/adapters/mmcv/utils/config_utils.py index b4ce240214a..dee79c02242 100644 --- a/src/otx/algorithms/common/adapters/mmcv/utils/config_utils.py +++ b/src/otx/algorithms/common/adapters/mmcv/utils/config_utils.py @@ -986,9 +986,9 @@ def get_proper_repeat_times( Args: n_data (int): The total number of the training dataset batch_size (int): The batch size for the training data loader - coef (float, optional) : coefficient that effects to number of repeats + coef (float) : coefficient that effects to number of repeats (coef * math.sqrt(num_iters-1)) +5 - min_repeat (float, optional) : minimum repeats + min_repeat (float) : minimum repeats """ if n_data == 0 or batch_size == 0: logger.info("Repeat dataset enabled, but not a train mode. repeat times set to 1.") From e1cf24fe04c36af2963f098052c248475a68e2b7 Mon Sep 17 00:00:00 2001 From: sungmanc <sungman.cho@intel.com> Date: Mon, 25 Sep 2023 15:35:15 +0900 Subject: [PATCH 09/17] Reflecting reviews --- .../common/adapters/mmcv/configurer.py | 2 +- .../mmcv/hooks/adaptive_repeat_data_hook.py | 60 ++++++++++--------- .../adapters/mmcv/utils/automatic_bs.py | 8 ++- 3 files changed, 39 insertions(+), 31 deletions(-) diff --git a/src/otx/algorithms/common/adapters/mmcv/configurer.py b/src/otx/algorithms/common/adapters/mmcv/configurer.py index bf648b0379c..96b6e0b4061 100644 --- a/src/otx/algorithms/common/adapters/mmcv/configurer.py +++ b/src/otx/algorithms/common/adapters/mmcv/configurer.py @@ -423,7 +423,7 @@ def configure_hooks( bs = data_cfg.get("train_dataloader", {}).get("samples_per_gpu", None) bs = bs if bs is not None else data_cfg.get("samples_per_gpu", 0) custom_hook["train_batch_size"] = bs - custom_hook["n_train_data"] = len(data_cfg.get("train", {}).get("otx_dataset", [])) + custom_hook["train_data_size"] = len(self.get_data_cfg(cfg, "train")) break @staticmethod diff --git a/src/otx/algorithms/common/adapters/mmcv/hooks/adaptive_repeat_data_hook.py b/src/otx/algorithms/common/adapters/mmcv/hooks/adaptive_repeat_data_hook.py index f3dc49a0f16..79a7c750b0f 100644 --- a/src/otx/algorithms/common/adapters/mmcv/hooks/adaptive_repeat_data_hook.py +++ b/src/otx/algorithms/common/adapters/mmcv/hooks/adaptive_repeat_data_hook.py @@ -19,26 +19,28 @@ class AdaptiveRepeatDataHook(Hook): Args: train_batch_size (int) : The batch size of the train dataloader - n_train_data (int) : The number of the training dataset + train_data_size (int) : The number of the training dataset coef (float, optional) : coefficient that effects to number of repeats (coef * math.sqrt(num_iters-1)) +5 min_repeat (float, optional) : minimum repeats """ - def __init__(self, train_batch_size: int, n_train_data: int, coef: float = -0.7, min_repeat: float = 1.0): + def __init__(self, train_batch_size: int, train_data_size: int, coef: float = -0.7, min_repeat: float = 1.0): self.coef = coef self.min_repeat = min_repeat self.train_batch_size = train_batch_size - self.n_train_data = n_train_data + self.train_data_size =train_data_size - self.n_repeats = get_proper_repeat_times(self.n_train_data, self.train_batch_size, self.coef, self.min_repeat) + self.n_repeats = get_proper_repeat_times(self.train_data_size, self.train_batch_size, self.coef, self.min_repeat) self.rank, self.world_size = get_dist_info() + + self.is_sampler_changed = False def before_run(self, runner): """Change the runner's max_iter.""" if self.n_repeats > 1: - iter_per_epoch = int(self.n_train_data / self.train_batch_size) + iter_per_epoch = int(self.train_data_size / self.train_batch_size) logger.info("Adaptive repeat is enabled") logger.info(f"- Repeat times: {self.n_repeats}") @@ -50,28 +52,30 @@ def before_run(self, runner): def before_epoch(self, runner): """Convert to OTX Sampler.""" - dataset = runner.data_loader.dataset - num_workers = runner.data_loader.num_workers - collate_fn = runner.data_loader.collate_fn - worker_init_fn = runner.data_loader.worker_init_fn + if self.is_sampler_changed == False: + dataset = runner.data_loader.dataset + num_workers = runner.data_loader.num_workers + collate_fn = runner.data_loader.collate_fn + worker_init_fn = runner.data_loader.worker_init_fn - sampler = OTXSampler( - dataset=dataset, - samples_per_gpu=self.train_batch_size, - num_replicas=self.world_size, - rank=self.rank, - shuffle=True, - coef=self.coef, - min_repeat=self.min_repeat, - n_repeats=self.n_repeats, - ) + sampler = OTXSampler( + dataset=dataset, + samples_per_gpu=self.train_batch_size, + num_replicas=self.world_size, + rank=self.rank, + shuffle=True, + coef=self.coef, + min_repeat=self.min_repeat, + n_repeats=self.n_repeats, + ) - runner.data_loader = DataLoader( - dataset, - batch_size=self.train_batch_size, - sampler=sampler, - num_workers=num_workers, - collate_fn=collate_fn, - pin_memory=False, - worker_init_fn=worker_init_fn, - ) + runner.data_loader = DataLoader( + dataset, + batch_size=self.train_batch_size, + sampler=sampler, + num_workers=num_workers, + collate_fn=collate_fn, + pin_memory=False, + worker_init_fn=worker_init_fn, + ) + self.is_sampler_changed = True \ No newline at end of file diff --git a/src/otx/algorithms/common/adapters/mmcv/utils/automatic_bs.py b/src/otx/algorithms/common/adapters/mmcv/utils/automatic_bs.py index 520ff874a8d..63392938ed7 100644 --- a/src/otx/algorithms/common/adapters/mmcv/utils/automatic_bs.py +++ b/src/otx/algorithms/common/adapters/mmcv/utils/automatic_bs.py @@ -11,6 +11,7 @@ from torch.cuda import is_available as cuda_available from otx.algorithms.common.adapters.torch.utils import BsSearchAlgo +from otx.algorithms.common.adapters.mmcv.utils.config_utils import update_or_add_custom_hook from otx.algorithms.common.utils.logger import get_logger logger = get_logger() @@ -79,7 +80,7 @@ def train_func_single_iter(batch_size): if idx_hooks_to_remove: idx_hooks_to_remove.sort() for i in reversed(idx_hooks_to_remove): - print(f"{copied_cfg.custom_hooks[i]} deleted.") + print(f"{copied_cfg.custom_hooks[i]} to be deleted.") del copied_cfg.custom_hooks[i] new_datasets = [SubDataset(datasets[0], batch_size)] @@ -126,7 +127,10 @@ def _set_batch_size(cfg, batch_size: int): cfg.data.videos_per_gpu = batch_size else: cfg.data.train_dataloader["samples_per_gpu"] = batch_size - + update_or_add_custom_hook(cfg, { + "type": "AdaptiveRepeatDataHook", + "train_batch_size": batch_size + }) for custom_hook in cfg.custom_hooks: if custom_hook["type"] == "AdaptiveRepeatDataHook": custom_hook["train_batch_size"] = batch_size From 0b3fef8313b5071e434d09130f4aa3585c8a3a96 Mon Sep 17 00:00:00 2001 From: sungmanc <sungman.cho@intel.com> Date: Mon, 25 Sep 2023 16:45:28 +0900 Subject: [PATCH 10/17] Refactor the name of get_data_cfg --- .../common/adapters/mmcv/configurer.py | 31 ++++++++++--------- .../mmcv/hooks/adaptive_repeat_data_hook.py | 12 ++++--- .../adapters/mmcv/utils/automatic_bs.py | 7 ++--- .../adapters/mmcv/utils/config_utils.py | 8 ++--- .../adapters/mmcv/utils/test_config_utils.py | 16 ++++++++++ 5 files changed, 45 insertions(+), 29 deletions(-) diff --git a/src/otx/algorithms/common/adapters/mmcv/configurer.py b/src/otx/algorithms/common/adapters/mmcv/configurer.py index 96b6e0b4061..3c433ba3232 100644 --- a/src/otx/algorithms/common/adapters/mmcv/configurer.py +++ b/src/otx/algorithms/common/adapters/mmcv/configurer.py @@ -112,8 +112,8 @@ def merge_configs(self, cfg, data_cfg, data_pipeline_path, hyperparams_from_otx, if data_cfg: for subset in data_cfg.data: if subset in cfg.data: - src_data_cfg = self.get_data_cfg(cfg, subset) - new_data_cfg = self.get_data_cfg(data_cfg, subset) + src_data_cfg = self.get_subset_data_cfg(cfg, subset) + new_data_cfg = self.get_subset_data_cfg(data_cfg, subset) for key in new_data_cfg: src_data_cfg[key] = new_data_cfg[key] else: @@ -204,7 +204,7 @@ def configure_samples_per_gpu( dataloader_cfg = cfg.data.get(f"{subset}_dataloader", ConfigDict()) samples_per_gpu = dataloader_cfg.get("samples_per_gpu", cfg.data.get("samples_per_gpu", 1)) - data_cfg = self.get_data_cfg(cfg, subset) + data_cfg = self.get_subset_data_cfg(cfg, subset) if data_cfg.get("otx_dataset") is not None: dataset_len = len(data_cfg.otx_dataset) @@ -269,7 +269,7 @@ def configure_model(self, cfg, data_classes, model_classes, ir_options, **kwargs self.model_classes = model_classes self.data_classes = data_classes if data_classes is not None: - train_data_cfg = self.get_data_cfg(cfg, "train") + train_data_cfg = self.get_subset_data_cfg(cfg, "train") train_data_cfg["data_classes"] = data_classes new_classes = np.setdiff1d(data_classes, model_classes).tolist() train_data_cfg["new_classes"] = new_classes @@ -417,14 +417,15 @@ def configure_hooks( if not self.training: remove_from_configs_by_type(cfg.custom_hooks, "AdaptiveRepeatDataHook") return - for custom_hook in cfg.custom_hooks: - if custom_hook["type"] == "AdaptiveRepeatDataHook": - data_cfg = cfg.get("data", {}) - bs = data_cfg.get("train_dataloader", {}).get("samples_per_gpu", None) - bs = bs if bs is not None else data_cfg.get("samples_per_gpu", 0) - custom_hook["train_batch_size"] = bs - custom_hook["train_data_size"] = len(self.get_data_cfg(cfg, "train")) - break + + data_cfg = cfg.get("data", {}) + bs = data_cfg.get("train_dataloader", {}).get("samples_per_gpu", None) + bs = bs if bs is not None else data_cfg.get("samples_per_gpu", 0) + train_data_size = len(self.get_subset_data_cfg(cfg, "train").get("otx_dataset", [])) + + update_or_add_custom_hook( + cfg, {"type": "AdaptiveRepeatDataHook", "train_batch_size": bs, "train_data_size": train_data_size} + ) @staticmethod def _update_caching_modules(cfg: Config) -> None: @@ -491,7 +492,7 @@ def get_model_meta(cfg): def get_data_classes(self, cfg): """Get data classes from train cfg.""" data_classes = [] - train_cfg = self.get_data_cfg(cfg, "train") + train_cfg = self.get_subset_data_cfg(cfg, "train") if "data_classes" in train_cfg: data_classes = list(train_cfg.pop("data_classes", [])) elif "classes" in train_cfg: @@ -499,7 +500,7 @@ def get_data_classes(self, cfg): return data_classes @staticmethod - def get_data_cfg(cfg, subset): + def get_subset_data_cfg(cfg, subset): """Get subset's data cfg.""" assert subset in ["train", "val", "test", "unlabeled"], f"Unknown subset:{subset}" if "dataset" in cfg.data[subset]: # Concat|RepeatDataset @@ -525,7 +526,7 @@ def adapt_input_size_to_dataset( Tuple[int, int]: (width, height) or None """ - data_cfg = BaseConfigurer.get_data_cfg(cfg, "train") + data_cfg = BaseConfigurer.get_subset_data_cfg(cfg, "train") dataset = data_cfg.get("otx_dataset", None) if dataset is None: return None diff --git a/src/otx/algorithms/common/adapters/mmcv/hooks/adaptive_repeat_data_hook.py b/src/otx/algorithms/common/adapters/mmcv/hooks/adaptive_repeat_data_hook.py index 79a7c750b0f..f1633fbbfaf 100644 --- a/src/otx/algorithms/common/adapters/mmcv/hooks/adaptive_repeat_data_hook.py +++ b/src/otx/algorithms/common/adapters/mmcv/hooks/adaptive_repeat_data_hook.py @@ -30,11 +30,13 @@ def __init__(self, train_batch_size: int, train_data_size: int, coef: float = -0 self.min_repeat = min_repeat self.train_batch_size = train_batch_size - self.train_data_size =train_data_size + self.train_data_size = train_data_size - self.n_repeats = get_proper_repeat_times(self.train_data_size, self.train_batch_size, self.coef, self.min_repeat) + self.n_repeats = get_proper_repeat_times( + self.train_data_size, self.train_batch_size, self.coef, self.min_repeat + ) self.rank, self.world_size = get_dist_info() - + self.is_sampler_changed = False def before_run(self, runner): @@ -52,7 +54,7 @@ def before_run(self, runner): def before_epoch(self, runner): """Convert to OTX Sampler.""" - if self.is_sampler_changed == False: + if self.is_sampler_changed is False: dataset = runner.data_loader.dataset num_workers = runner.data_loader.num_workers collate_fn = runner.data_loader.collate_fn @@ -78,4 +80,4 @@ def before_epoch(self, runner): pin_memory=False, worker_init_fn=worker_init_fn, ) - self.is_sampler_changed = True \ No newline at end of file + self.is_sampler_changed = True diff --git a/src/otx/algorithms/common/adapters/mmcv/utils/automatic_bs.py b/src/otx/algorithms/common/adapters/mmcv/utils/automatic_bs.py index 63392938ed7..f88d2ef65a6 100644 --- a/src/otx/algorithms/common/adapters/mmcv/utils/automatic_bs.py +++ b/src/otx/algorithms/common/adapters/mmcv/utils/automatic_bs.py @@ -10,8 +10,8 @@ import numpy as np from torch.cuda import is_available as cuda_available -from otx.algorithms.common.adapters.torch.utils import BsSearchAlgo from otx.algorithms.common.adapters.mmcv.utils.config_utils import update_or_add_custom_hook +from otx.algorithms.common.adapters.torch.utils import BsSearchAlgo from otx.algorithms.common.utils.logger import get_logger logger = get_logger() @@ -127,10 +127,7 @@ def _set_batch_size(cfg, batch_size: int): cfg.data.videos_per_gpu = batch_size else: cfg.data.train_dataloader["samples_per_gpu"] = batch_size - update_or_add_custom_hook(cfg, { - "type": "AdaptiveRepeatDataHook", - "train_batch_size": batch_size - }) + update_or_add_custom_hook(cfg, {"type": "AdaptiveRepeatDataHook", "train_batch_size": batch_size}) for custom_hook in cfg.custom_hooks: if custom_hook["type"] == "AdaptiveRepeatDataHook": custom_hook["train_batch_size"] = batch_size diff --git a/src/otx/algorithms/common/adapters/mmcv/utils/config_utils.py b/src/otx/algorithms/common/adapters/mmcv/utils/config_utils.py index a310b8441f8..4e99ea291e1 100644 --- a/src/otx/algorithms/common/adapters/mmcv/utils/config_utils.py +++ b/src/otx/algorithms/common/adapters/mmcv/utils/config_utils.py @@ -983,7 +983,7 @@ def area(x): def get_proper_repeat_times( - n_data: int, + data_size: int, batch_size: int, coef: float, min_repeat: float, @@ -991,14 +991,14 @@ def get_proper_repeat_times( """Get proper repeat times for adaptive training. Args: - n_data (int): The total number of the training dataset + data_size (int): The total number of the training dataset batch_size (int): The batch size for the training data loader coef (float) : coefficient that effects to number of repeats (coef * math.sqrt(num_iters-1)) +5 min_repeat (float) : minimum repeats """ - if n_data == 0 or batch_size == 0: + if data_size == 0 or batch_size == 0: logger.info("Repeat dataset enabled, but not a train mode. repeat times set to 1.") return 1 - n_iters_per_epoch = math.ceil(n_data / batch_size) + n_iters_per_epoch = math.ceil(data_size / batch_size) return math.floor(max(coef * math.sqrt(n_iters_per_epoch - 1) + 5, min_repeat)) diff --git a/tests/unit/algorithms/common/adapters/mmcv/utils/test_config_utils.py b/tests/unit/algorithms/common/adapters/mmcv/utils/test_config_utils.py index 63d5faf887d..2a7b4014bbe 100644 --- a/tests/unit/algorithms/common/adapters/mmcv/utils/test_config_utils.py +++ b/tests/unit/algorithms/common/adapters/mmcv/utils/test_config_utils.py @@ -7,6 +7,7 @@ patch_persistent_workers, get_adaptive_num_workers, InputSizeManager, + get_proper_repeat_times, ) from otx.algorithms.common.configs.configuration_enums import InputSizePreset @@ -491,3 +492,18 @@ def test_adapt_input_size_to_dataset(self): downscale_only=False, ) # 1024 -> 2048 -> 1024 assert input_size == (1024, 1024) + + +@e2e_pytest_unit +def test_get_proper_repeat_times(): + batch_size = 2 + coef = 1.0 + min_repeat = 1.0 + + data_size = 0 + repeats = get_proper_repeat_times(data_size=data_size, batch_size=batch_size, coef=coef, min_repeat=min_repeat) + assert repeats == 1 + + batch_size = 0 + repeats = get_proper_repeat_times(data_size=data_size, batch_size=batch_size, coef=coef, min_repeat=min_repeat) + assert repeats == 1 From af4aeb22d81dbdc437d0799aa0a5bb741383bf1e Mon Sep 17 00:00:00 2001 From: sungmanc <sungman.cho@intel.com> Date: Mon, 25 Sep 2023 16:46:18 +0900 Subject: [PATCH 11/17] Revert adaptive hook sampler init --- .../mmcv/hooks/adaptive_repeat_data_hook.py | 52 +++++++++---------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/src/otx/algorithms/common/adapters/mmcv/hooks/adaptive_repeat_data_hook.py b/src/otx/algorithms/common/adapters/mmcv/hooks/adaptive_repeat_data_hook.py index f1633fbbfaf..3ba04893952 100644 --- a/src/otx/algorithms/common/adapters/mmcv/hooks/adaptive_repeat_data_hook.py +++ b/src/otx/algorithms/common/adapters/mmcv/hooks/adaptive_repeat_data_hook.py @@ -37,8 +37,6 @@ def __init__(self, train_batch_size: int, train_data_size: int, coef: float = -0 ) self.rank, self.world_size = get_dist_info() - self.is_sampler_changed = False - def before_run(self, runner): """Change the runner's max_iter.""" if self.n_repeats > 1: @@ -54,30 +52,28 @@ def before_run(self, runner): def before_epoch(self, runner): """Convert to OTX Sampler.""" - if self.is_sampler_changed is False: - dataset = runner.data_loader.dataset - num_workers = runner.data_loader.num_workers - collate_fn = runner.data_loader.collate_fn - worker_init_fn = runner.data_loader.worker_init_fn - - sampler = OTXSampler( - dataset=dataset, - samples_per_gpu=self.train_batch_size, - num_replicas=self.world_size, - rank=self.rank, - shuffle=True, - coef=self.coef, - min_repeat=self.min_repeat, - n_repeats=self.n_repeats, - ) + dataset = runner.data_loader.dataset + num_workers = runner.data_loader.num_workers + collate_fn = runner.data_loader.collate_fn + worker_init_fn = runner.data_loader.worker_init_fn + + sampler = OTXSampler( + dataset=dataset, + samples_per_gpu=self.train_batch_size, + num_replicas=self.world_size, + rank=self.rank, + shuffle=True, + coef=self.coef, + min_repeat=self.min_repeat, + n_repeats=self.n_repeats, + ) - runner.data_loader = DataLoader( - dataset, - batch_size=self.train_batch_size, - sampler=sampler, - num_workers=num_workers, - collate_fn=collate_fn, - pin_memory=False, - worker_init_fn=worker_init_fn, - ) - self.is_sampler_changed = True + runner.data_loader = DataLoader( + dataset, + batch_size=self.train_batch_size, + sampler=sampler, + num_workers=num_workers, + collate_fn=collate_fn, + pin_memory=False, + worker_init_fn=worker_init_fn, + ) From d67b119d43341a5f99378d40d957c0fd14f2d3d9 Mon Sep 17 00:00:00 2001 From: sungmanc <sungman.cho@intel.com> Date: Mon, 25 Sep 2023 16:55:04 +0900 Subject: [PATCH 12/17] Refactor the function name: get_data_cfg -> get_subset_data_cfg --- src/otx/algorithms/common/adapters/mmcv/utils/__init__.py | 2 -- .../algorithms/common/adapters/mmcv/utils/config_utils.py | 8 -------- src/otx/algorithms/detection/adapters/mmdet/configurer.py | 2 +- .../common/adapters/mmcv/utils/test_config_utils.py | 6 +++--- .../detection/adapters/mmdet/test_configurer.py | 4 ++-- 5 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/otx/algorithms/common/adapters/mmcv/utils/__init__.py b/src/otx/algorithms/common/adapters/mmcv/utils/__init__.py index 4fd056b5725..d6c1ce5a3db 100644 --- a/src/otx/algorithms/common/adapters/mmcv/utils/__init__.py +++ b/src/otx/algorithms/common/adapters/mmcv/utils/__init__.py @@ -12,7 +12,6 @@ InputSizeManager, OTXConfig, config_from_string, - get_data_cfg, get_dataset_configs, is_epoch_based_runner, patch_adaptive_interval_training, @@ -45,7 +44,6 @@ "patch_early_stopping", "patch_persistent_workers", "prepare_work_dir", - "get_data_cfg", "OTXConfig", "adapt_batch_size", "InputSizeManager", diff --git a/src/otx/algorithms/common/adapters/mmcv/utils/config_utils.py b/src/otx/algorithms/common/adapters/mmcv/utils/config_utils.py index 4e99ea291e1..2b211890232 100644 --- a/src/otx/algorithms/common/adapters/mmcv/utils/config_utils.py +++ b/src/otx/algorithms/common/adapters/mmcv/utils/config_utils.py @@ -596,14 +596,6 @@ def prepare_work_dir(config: Union[Config, ConfigDict]) -> str: return train_round_checkpoint_dir -def get_data_cfg(config: Union[Config, ConfigDict], subset: str = "train") -> Config: - """Return dataset configs.""" - data_cfg = config.data[subset] - while "dataset" in data_cfg: - data_cfg = data_cfg.dataset - return data_cfg - - class InputSizeManager: """Class for changing input size and getting input size value by checking data pipeline. diff --git a/src/otx/algorithms/detection/adapters/mmdet/configurer.py b/src/otx/algorithms/detection/adapters/mmdet/configurer.py index 8df3ad66d50..876e05ca822 100644 --- a/src/otx/algorithms/detection/adapters/mmdet/configurer.py +++ b/src/otx/algorithms/detection/adapters/mmdet/configurer.py @@ -112,7 +112,7 @@ def _configure_eval_dataset(self, cfg): def configure_task_data_pipeline(self, cfg): """Trying to alter class indices of training data according to model class order.""" - tr_data_cfg = self.get_data_cfg(cfg, "train") + tr_data_cfg = self.get_subset_data_cfg(cfg, "train") class_adapt_cfg = dict(type="AdaptClassLabels", src_classes=self.data_classes, dst_classes=self.model_classes) pipeline_cfg = tr_data_cfg.pipeline for i, operation in enumerate(pipeline_cfg): diff --git a/tests/unit/algorithms/common/adapters/mmcv/utils/test_config_utils.py b/tests/unit/algorithms/common/adapters/mmcv/utils/test_config_utils.py index 2a7b4014bbe..00405854eb9 100644 --- a/tests/unit/algorithms/common/adapters/mmcv/utils/test_config_utils.py +++ b/tests/unit/algorithms/common/adapters/mmcv/utils/test_config_utils.py @@ -14,7 +14,7 @@ from tests.test_suite.e2e_test_system import e2e_pytest_unit -def get_data_cfg(workers_per_gpu: int = 2) -> dict: +def get_subset_data_cfg(workers_per_gpu: int = 2) -> dict: data_cfg = {} for subset in ["train", "val", "test", "unlabeled"]: data_cfg[subset] = "fake" @@ -26,7 +26,7 @@ def get_data_cfg(workers_per_gpu: int = 2) -> dict: @e2e_pytest_unit @pytest.mark.parametrize("workers_per_gpu", [0, 2]) def test_patch_persistent_workers(mocker, workers_per_gpu): - data_cfg = get_data_cfg(workers_per_gpu) + data_cfg = get_subset_data_cfg(workers_per_gpu) config = mocker.MagicMock() config.data = data_cfg @@ -44,7 +44,7 @@ def test_patch_persistent_workers(mocker, workers_per_gpu): @e2e_pytest_unit def test_patch_persistent_workers_dist_semisl(mocker): - data_cfg = get_data_cfg() + data_cfg = get_subset_data_cfg() config = mocker.MagicMock() config.data = data_cfg diff --git a/tests/unit/algorithms/detection/adapters/mmdet/test_configurer.py b/tests/unit/algorithms/detection/adapters/mmdet/test_configurer.py index 5d5637f81ef..c341d9d1b4e 100644 --- a/tests/unit/algorithms/detection/adapters/mmdet/test_configurer.py +++ b/tests/unit/algorithms/detection/adapters/mmdet/test_configurer.py @@ -323,12 +323,12 @@ def test_configure_compat_cfg(self): self.configurer.configure_compat_cfg(model_cfg) @e2e_pytest_unit - def test_get_data_cfg(self): + def test_get_subset_data_cfg(self): config = copy.deepcopy(self.model_cfg) data_pipeline_cfg = OTXConfig.fromfile(self.data_pipeline_path) config.merge_from_dict(data_pipeline_cfg) config.data.train.dataset = ConfigDict({"dataset": [1, 2, 3]}) - assert [1, 2, 3] == self.configurer.get_data_cfg(config, "train") + assert [1, 2, 3] == self.configurer.get_subset_data_cfg(config, "train") class TestIncrDetectionConfigurer: From 13047348df33c325e73f10e33d69dc47443010c7 Mon Sep 17 00:00:00 2001 From: sungmanc <sungman.cho@intel.com> Date: Tue, 26 Sep 2023 10:19:09 +0900 Subject: [PATCH 13/17] Fix unit test errors --- .../common/adapters/mmcv/configurer.py | 17 ++++++++--------- .../mmcv/hooks/adaptive_repeat_data_hook.py | 2 ++ .../adapters/mmcls/test_configurer.py | 4 ++-- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/otx/algorithms/common/adapters/mmcv/configurer.py b/src/otx/algorithms/common/adapters/mmcv/configurer.py index 3c433ba3232..149fd79f9f5 100644 --- a/src/otx/algorithms/common/adapters/mmcv/configurer.py +++ b/src/otx/algorithms/common/adapters/mmcv/configurer.py @@ -417,15 +417,14 @@ def configure_hooks( if not self.training: remove_from_configs_by_type(cfg.custom_hooks, "AdaptiveRepeatDataHook") return - - data_cfg = cfg.get("data", {}) - bs = data_cfg.get("train_dataloader", {}).get("samples_per_gpu", None) - bs = bs if bs is not None else data_cfg.get("samples_per_gpu", 0) - train_data_size = len(self.get_subset_data_cfg(cfg, "train").get("otx_dataset", [])) - - update_or_add_custom_hook( - cfg, {"type": "AdaptiveRepeatDataHook", "train_batch_size": bs, "train_data_size": train_data_size} - ) + for custom_hook in cfg.custom_hooks: + if custom_hook["type"] == "AdaptiveRepeatDataHook": + data_cfg = cfg.get("data", {}) + bs = data_cfg.get("train_dataloader", {}).get("samples_per_gpu", None) + bs = bs if bs is not None else data_cfg.get("samples_per_gpu", 0) + custom_hook["train_batch_size"] = bs + custom_hook["train_data_size"] = len(data_cfg.get("train", {}).get("otx_dataset", [])) + break @staticmethod def _update_caching_modules(cfg: Config) -> None: diff --git a/src/otx/algorithms/common/adapters/mmcv/hooks/adaptive_repeat_data_hook.py b/src/otx/algorithms/common/adapters/mmcv/hooks/adaptive_repeat_data_hook.py index 3ba04893952..15526fda9bb 100644 --- a/src/otx/algorithms/common/adapters/mmcv/hooks/adaptive_repeat_data_hook.py +++ b/src/otx/algorithms/common/adapters/mmcv/hooks/adaptive_repeat_data_hook.py @@ -48,6 +48,8 @@ def before_run(self, runner): logger.info(f"- Num iters per epoch: {iter_per_epoch} -> {iter_per_epoch * self.n_repeats}") logger.info(f"- Total iters: {runner.max_iters} -> {runner.max_iters * self.n_repeats}") + #FIXME, although runner._max_iters is the protected attribute, + # There is no way to control the max_iters of runner. runner._max_iters = int(runner.max_iters * self.n_repeats) def before_epoch(self, runner): diff --git a/tests/unit/algorithms/classification/adapters/mmcls/test_configurer.py b/tests/unit/algorithms/classification/adapters/mmcls/test_configurer.py index 56f1ed27eeb..fd6f26d0805 100644 --- a/tests/unit/algorithms/classification/adapters/mmcls/test_configurer.py +++ b/tests/unit/algorithms/classification/adapters/mmcls/test_configurer.py @@ -266,11 +266,11 @@ def test_configure_compat_cfg(self): self.configurer.configure_compat_cfg(model_cfg) @e2e_pytest_unit - def test_get_data_cfg(self): + def test_get_subset_data_cfg(self): config = copy.deepcopy(self.model_cfg) config.update(self.data_cfg) config.data.train.dataset = ConfigDict({"dataset": [1, 2, 3]}) - assert [1, 2, 3] == self.configurer.get_data_cfg(config, "train") + assert [1, 2, 3] == self.configurer.get_subset_data_cfg(config, "train") class TestIncrClassificationConfigurer: From 334609568695a8f5ed3ac63d17964113c552d0a5 Mon Sep 17 00:00:00 2001 From: sungmanc <sungman.cho@intel.com> Date: Tue, 26 Sep 2023 10:28:39 +0900 Subject: [PATCH 14/17] Fix black --- .../common/adapters/mmcv/hooks/adaptive_repeat_data_hook.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/otx/algorithms/common/adapters/mmcv/hooks/adaptive_repeat_data_hook.py b/src/otx/algorithms/common/adapters/mmcv/hooks/adaptive_repeat_data_hook.py index 15526fda9bb..310e7316ba6 100644 --- a/src/otx/algorithms/common/adapters/mmcv/hooks/adaptive_repeat_data_hook.py +++ b/src/otx/algorithms/common/adapters/mmcv/hooks/adaptive_repeat_data_hook.py @@ -48,8 +48,8 @@ def before_run(self, runner): logger.info(f"- Num iters per epoch: {iter_per_epoch} -> {iter_per_epoch * self.n_repeats}") logger.info(f"- Total iters: {runner.max_iters} -> {runner.max_iters * self.n_repeats}") - #FIXME, although runner._max_iters is the protected attribute, - # There is no way to control the max_iters of runner. + # FIXME, although runner._max_iters is the protected attribute, + # There is no way to control the max_iters of runner. runner._max_iters = int(runner.max_iters * self.n_repeats) def before_epoch(self, runner): From 34b8f7157f6a79d8ec740b6cf22ebd6151681d2b Mon Sep 17 00:00:00 2001 From: sungmanc <sungman.cho@intel.com> Date: Wed, 4 Oct 2023 13:13:59 +0900 Subject: [PATCH 15/17] Remove useless line --- src/otx/algorithms/common/adapters/mmcv/utils/automatic_bs.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/otx/algorithms/common/adapters/mmcv/utils/automatic_bs.py b/src/otx/algorithms/common/adapters/mmcv/utils/automatic_bs.py index f88d2ef65a6..400558ceee4 100644 --- a/src/otx/algorithms/common/adapters/mmcv/utils/automatic_bs.py +++ b/src/otx/algorithms/common/adapters/mmcv/utils/automatic_bs.py @@ -80,7 +80,6 @@ def train_func_single_iter(batch_size): if idx_hooks_to_remove: idx_hooks_to_remove.sort() for i in reversed(idx_hooks_to_remove): - print(f"{copied_cfg.custom_hooks[i]} to be deleted.") del copied_cfg.custom_hooks[i] new_datasets = [SubDataset(datasets[0], batch_size)] From 040bec85652892574d8d4a9818db48cc0da3f306 Mon Sep 17 00:00:00 2001 From: sungmanc <sungman.cho@intel.com> Date: Wed, 4 Oct 2023 13:36:03 +0900 Subject: [PATCH 16/17] Remove adding AdaptiveRepeatDataHook for autobs --- src/otx/algorithms/common/adapters/mmcv/utils/automatic_bs.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/otx/algorithms/common/adapters/mmcv/utils/automatic_bs.py b/src/otx/algorithms/common/adapters/mmcv/utils/automatic_bs.py index 400558ceee4..637368bd557 100644 --- a/src/otx/algorithms/common/adapters/mmcv/utils/automatic_bs.py +++ b/src/otx/algorithms/common/adapters/mmcv/utils/automatic_bs.py @@ -69,7 +69,7 @@ def train_func_single_iter(batch_size): # earlystoppinghook => if eval hook is excluded, this hook makes an error due to absence of score history # CustomEvalHook => exclude validation in classification task idx_hooks_to_remove = [] - hooks_to_remove = ["OTXProgressHook", "earlystoppinghook", "CustomEvalHook", "AdaptiveRepeatDataHook"] + hooks_to_remove = ["OTXProgressHook", "earlystoppinghook", "CustomEvalHook"] for i, hook in enumerate(copied_cfg.custom_hooks): if not validate and hook["type"] == "AdaptiveTrainSchedulingHook": hook["enable_eval_before_run"] = False @@ -126,7 +126,6 @@ def _set_batch_size(cfg, batch_size: int): cfg.data.videos_per_gpu = batch_size else: cfg.data.train_dataloader["samples_per_gpu"] = batch_size - update_or_add_custom_hook(cfg, {"type": "AdaptiveRepeatDataHook", "train_batch_size": batch_size}) for custom_hook in cfg.custom_hooks: if custom_hook["type"] == "AdaptiveRepeatDataHook": custom_hook["train_batch_size"] = batch_size From 8518fbd6d1c8a37b98b5f0d61cee4148e3d274a9 Mon Sep 17 00:00:00 2001 From: sungmanc <sungman.cho@intel.com> Date: Wed, 4 Oct 2023 14:53:08 +0900 Subject: [PATCH 17/17] Remove unused import --- src/otx/algorithms/common/adapters/mmcv/utils/automatic_bs.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/otx/algorithms/common/adapters/mmcv/utils/automatic_bs.py b/src/otx/algorithms/common/adapters/mmcv/utils/automatic_bs.py index 637368bd557..3ccd2c81f3f 100644 --- a/src/otx/algorithms/common/adapters/mmcv/utils/automatic_bs.py +++ b/src/otx/algorithms/common/adapters/mmcv/utils/automatic_bs.py @@ -10,7 +10,6 @@ import numpy as np from torch.cuda import is_available as cuda_available -from otx.algorithms.common.adapters.mmcv.utils.config_utils import update_or_add_custom_hook from otx.algorithms.common.adapters.torch.utils import BsSearchAlgo from otx.algorithms.common.utils.logger import get_logger