# Copyright (c) OpenMMLab. All rights reserved. import logging import numpy as np import pytest import torch import torch.nn as nn from mmcv.runner.fp16_utils import auto_fp16 from mmcv.utils import IS_IPU_AVAILABLE if IS_IPU_AVAILABLE: from mmcv.device.ipu import cfg2options, ipu_model_wrapper from mmcv.device.ipu.utils import compare_ndarray skip_no_ipu = pytest.mark.skipif( not IS_IPU_AVAILABLE, reason='test case under ipu environment') class MyBN(nn.BatchNorm2d): def forward(self, *args, **kwargs): result = super().forward(*args, **kwargs) return result, self.running_mean # TODO Once the model training and inference interfaces # of MMCLS and MMDET are unified, # construct the model according to the unified standards class ToyModel(nn.Module): def __init__(self): super().__init__() self.conv = nn.Conv2d(3, 3, 1) self.bn = MyBN(3) self.relu = nn.ReLU6() self.fp16_enabled = False @auto_fp16(apply_to=('img', )) def forward(self, img, return_loss=True, **kwargs): x = self.conv(img) x, running_mean = self.bn(x) x = self.relu(x) if return_loss: loss = ((x - kwargs['gt_label'])**2).sum() return { 'loss': loss, 'loss_list': [loss, loss], 'loss_dict': { 'loss1': loss } } return x def _parse_losses(self, losses): return losses['loss'], losses['loss'] def train_step(self, data, optimizer=None, **kwargs): losses = self(**data) loss, log_vars = self._parse_losses(losses) outputs = dict( loss=loss, log_vars=log_vars, num_samples=len(data['img'].data)) return outputs @skip_no_ipu def test_build_model(): for execution_strategy in \ ['SameAsIpu', 'ShardedExecution', 'error_strategy']: if execution_strategy == 'error_strategy': def maybe_catch_error(_error): return pytest.raises(_error) else: class NullContextManager: def __enter__(self, ): pass def __exit__(self, exc_type, exc_value, exc_traceback): pass def maybe_catch_error(_error): return NullContextManager() with maybe_catch_error(NotImplementedError): options_cfg = dict( randomSeed=888, enableExecutableCaching='cache_engine', train_cfg=dict( executionStrategy=execution_strategy, Training=dict(gradientAccumulation=8), availableMemoryProportion=[0.3, 0.3, 0.3, 0.3]), eval_cfg=dict(deviceIterations=1, ), partialsType='half') ipu_options = cfg2options(options_cfg) model = ToyModel() optimizer = torch.optim.SGD(model.parameters(), lr=0.1) logger = logging.getLogger() modules_to_record = None ipu_model_cfg = dict( train_split_edges=[dict(layer_to_call='conv', ipu_id=0)], train_ckpt_nodes=['bn', 'conv']) fp16_cfg = {'loss_scale': 0.5} ipu_model = ipu_model_wrapper( model, ipu_options, optimizer, logger, modules_to_record=modules_to_record, ipu_model_cfg=ipu_model_cfg, fp16_cfg=fp16_cfg) ipu_model.train() ipu_model.eval() ipu_model.train() def run_model(ipu_options, fp16_cfg, modules_to_record, ipu_model_wrapper_func, only_eval=False): model = ToyModel() optimizer = torch.optim.SGD(model.parameters(), lr=0.1)\ if not only_eval else None logger = logging.getLogger() ipu_model_cfg = dict( train_split_edges=[dict(layer_to_call='conv', ipu_id=0)], train_ckpt_nodes=['bn', 'conv']) ipu_model = ipu_model_wrapper_func( model, ipu_options, optimizer, logger, modules_to_record=modules_to_record, ipu_model_cfg=ipu_model_cfg, fp16_cfg=fp16_cfg) def get_dummy_input(training): if training: return { 'data': { 'img': torch.rand((16, 3, 10, 10)), 'gt_label': torch.rand((16, 3, 10, 10)) } } else: return { 'img': torch.rand((16, 3, 10, 10)), 'img_metas': { 'img': torch.rand((16, 3, 10, 10)) }, 'return_loss': False } if not only_eval: training = True ipu_model.train() for _ in range(3): dummy_input = get_dummy_input(training) output = ipu_model.train_step(**dummy_input) training = False ipu_model.eval() for _ in range(3): dummy_input = get_dummy_input(training) output = ipu_model(**dummy_input) return output, ipu_model @skip_no_ipu def test_run_model(): # test feature alignment not support gradientAccumulation mode options_cfg = dict( randomSeed=888, enableExecutableCaching='cache_engine', train_cfg=dict( executionStrategy='SameAsIpu', Training=dict(gradientAccumulation=8), availableMemoryProportion=[0.3, 0.3, 0.3, 0.3], ), eval_cfg=dict(deviceIterations=1, ), partialsType='half') ipu_options = cfg2options(options_cfg) modules_to_record = ['bn'] with pytest.raises(AssertionError, match='Feature alignment'): run_model(ipu_options, None, modules_to_record, ipu_model_wrapper) # test feature alignment not support multi-replica mode options_cfg = dict( randomSeed=888, replicationFactor=2, enableExecutableCaching='cache_engine', train_cfg=dict( executionStrategy='SameAsIpu', availableMemoryProportion=[0.3, 0.3, 0.3, 0.3], ), eval_cfg=dict(deviceIterations=1, ), partialsType='half') ipu_options = cfg2options(options_cfg) modules_to_record = ['bn'] with pytest.raises(AssertionError, match='Feature alignment'): run_model(ipu_options, None, modules_to_record, ipu_model_wrapper) # test feature alignment not support fp16 mode options_cfg = dict( randomSeed=888, enableExecutableCaching='cache_engine', train_cfg=dict( executionStrategy='SameAsIpu', availableMemoryProportion=[0.3, 0.3, 0.3, 0.3], ), eval_cfg=dict(deviceIterations=1, ), partialsType='half') ipu_options = cfg2options(options_cfg) fp16_cfg = { 'loss_scale': 0.5, 'velocity_accum_type': 'half', 'accum_type': 'half' } modules_to_record = ['bn'] with pytest.raises(NotImplementedError): run_model(ipu_options, fp16_cfg, modules_to_record, ipu_model_wrapper) # test velocity_accum_type and accum_type fp16_cfg = { 'loss_scale': 0.5, 'velocity_accum_type': 'float', 'accum_type': 'float' } run_model(ipu_options, fp16_cfg, None, ipu_model_wrapper) # test compile and run options_cfg = dict( randomSeed=888, enableExecutableCaching='cache_engine', train_cfg=dict( executionStrategy='SameAsIpu', availableMemoryProportion=[0.3, 0.3, 0.3, 0.3], ), eval_cfg=dict(deviceIterations=1, ), partialsType='half') ipu_options = cfg2options(options_cfg) modules_to_record = ['bn'] run_model(ipu_options, None, modules_to_record, ipu_model_wrapper) # test feature alignment options_cfg = dict( randomSeed=888, enableExecutableCaching='cache_engine', train_cfg=dict( executionStrategy='SameAsIpu', availableMemoryProportion=[0.3, 0.3, 0.3, 0.3], ), eval_cfg=dict(deviceIterations=1, )) ipu_options = cfg2options(options_cfg) modules_to_record = None run_model(ipu_options, None, modules_to_record, ipu_model_wrapper) # test inference mode options_cfg = dict( randomSeed=888, enableExecutableCaching='cache_engine', train_cfg=dict( executionStrategy='SameAsIpu', availableMemoryProportion=[0.3, 0.3, 0.3, 0.3], ), eval_cfg=dict(deviceIterations=1, ), partialsType='half') ipu_options = cfg2options(options_cfg) fp16_cfg = {'loss_scale': 0.5} modules_to_record = None _, ipu_model = run_model( ipu_options, fp16_cfg, modules_to_record, ipu_model_wrapper, only_eval=True) with pytest.raises(RuntimeError): ipu_model.train() with pytest.raises(ValueError): ipu_model.train(123) _, ipu_model = run_model(ipu_options, None, modules_to_record, ipu_model_wrapper) # test NotImplementedError in __call__ ipu_model.train() with pytest.raises(NotImplementedError): ipu_model() # test parse_losses with pytest.raises(TypeError): ipu_model._model.model._parse_losses({'loss': None}) @skip_no_ipu def test_compare_tensor(): compare_ndarray(np.random.rand(3, 4), np.random.rand(3, 4))