Compare commits

...
24 Commits
err ... tb
Author SHA1 Message Date
William Falcon d794ee4522 Merge branch 'master' into tb 2020-01-13 22:13:59 -05:00
Ayberk Aydın 0ae3dd9ed4 Fix GAN training. (#603)
* fix dangling gradients

make sure only the gradients of the current optimizer's paramaters are calculated in the training step.

* add note about multiple optimizer gradient update

* Update training_loop.py
2020-01-13 22:12:04 -05:00
Ayla Khan 1969c6cc2a Remove extraneous f character from f-string. (#679)
Makes tracking experiment names confusion, especially when using uuids.
2020-01-13 22:11:04 -05:00
Jirka Borovec db6b404748 CI pass (#671)
* fix pillow in test

* test acc

* update version in deprecated msg
2020-01-13 22:09:47 -05:00
Vadim Bereznyuk 12edc3099c Fix the number of training batches used in the training loop (#653)
* Fix the number of processed training batches

* Fix tests

* fix tests

* fix tests

* One more attempt

* Fix another test
2020-01-05 14:37:09 -05:00
Vadim Bereznyuk 7824b5c5f5 Fix percent_checks (#649)
* fix percent_checks

* Added _percent_range_check

* remove max
2020-01-05 14:36:06 -05:00
Verena Haunschmid 9ac91adea9 Update requirements.txt (#664)
Fix typo 'buildins' -> 'builtins'
2020-01-05 14:34:44 -05:00
Nic Eggert 019f612204 Fix amp tests (#661)
* Run AMP tests in their own process

With opt_level="O1" (the default), AMP patches many
torch functions, which breaks any tests that run afterwards.
This patch introduces a pytest extension that lets
tests be marked with @pytest.mark.spawn so that they
are run in their own process using torch.multiprocessing.spawn
so that the main python interpreter stays un-patched.

Note that tests using DDP already run AMP in its own process,
so they don't need this annotation.

* Fix AMP tests

Since AMP defaults to O1 now, DP tests no longer throw exceptions.

Since AMP patches torch functions, CPU inference no longer works.
Skip prediction step for AMP tests.

* typo
2020-01-05 14:34:25 -05:00
Jirka Borovec c32f2b9116 Fix ci xos (#647)
* upgrade python 3.7

* upgrade python 3.7
2019-12-22 21:18:09 -05:00
Hao Sheng ca73b70d15 fix of issue 600 (#625) 2019-12-14 20:24:46 -08:00
Jeremy Jordan 3dd0b8c186 fix metric name to work with default earlystopping (#628) 2019-12-14 20:23:44 -08:00
William Falcon 8c5d66196b Update README.md 2019-12-12 11:08:02 -08:00
William Falcon d44c91d854 Update README.md 2019-12-12 11:07:06 -08:00
William Falcon af6d552d35 Update README.md 2019-12-12 11:06:20 -08:00
William Falcon 24bfa53894 Update README.md 2019-12-12 13:36:17 -05:00
William Falcon 52295986e4 Update README.md 2019-12-12 13:35:41 -05:00
William Falcon 64c428ec49 Update README.md 2019-12-12 13:33:49 -05:00
William Falcon a6fc172387 Update README.md 2019-12-12 10:24:53 -08:00
William Falcon be43fbb918 Update README.md 2019-12-12 10:23:49 -08:00
William Falcon 15cb79923a Add files via upload 2019-12-12 10:23:02 -08:00
William Falcon 99c9b82527 made tensorboard the default not test-tube 2019-12-07 23:36:51 -05:00
William Falcon 47a82cf1b9 refactor 2019-12-07 23:32:17 -05:00
William Falcon 1b86ed9cc3 refactor 2019-12-07 23:31:47 -05:00
William Falcon 94bd2ae3e1 refactor 2019-12-07 23:30:59 -05:00
27 changed files with 158 additions and 108 deletions
+7 -6
View File
@@ -33,13 +33,14 @@ matrix:
python: 3.7 python: 3.7
env: TOXENV=py37 env: TOXENV=py37
- os: osx - os: osx
osx_image: xcode9.4 # https://blog.travis-ci.com/2019-08-07-extensive-python-testing-on-travis-ci
osx_image: xcode10.3
language: generic language: generic
env: TOXENV=py36 env: TOXENV=py37
addons: #addons:
homebrew: # homebrew:
# update: true # # update: true
packages: python3.6 # packages: python3.7
before_install: before_install:
- pip3 install virtualenv - pip3 install virtualenv
- virtualenv -p python3 ~/venv - virtualenv -p python3 ~/venv
+14 -25
View File
@@ -38,10 +38,22 @@ pip install pytorch-lightning
[Copy and run this COLAB!](https://colab.research.google.com/drive/1F_RNcHzTfFuQf-LeKvSlud6x7jXYkG31#scrollTo=HOk9c4_35FKg) [Copy and run this COLAB!](https://colab.research.google.com/drive/1F_RNcHzTfFuQf-LeKvSlud6x7jXYkG31#scrollTo=HOk9c4_35FKg)
## What is it? ## What is it?
Lightning is a very lightweight wrapper on PyTorch. This means you don't have to learn a new library. To use Lightning, simply refactor your research code into the [LightningModule](https://github.com/williamFalcon/pytorch-lightning#how-do-i-do-use-it) format and Lightning will automate the rest. Lightning guarantees tested, correct, modern best practices for the automated parts. Lightning is a very lightweight wrapper on PyTorch that decouples the science code from the engineering code. It's more of a style-guide than a framework. By refactoring your code, we can automate most of the non-research code.
To use Lightning, simply refactor your research code into the [LightningModule](https://github.com/williamFalcon/pytorch-lightning#how-do-i-do-use-it) format (the science) and Lightning will automate the rest (the engineering). Lightning guarantees tested, correct, modern best practices for the automated parts.
- If you are a researcher, Lightning is infinitely flexible, you can modify everything down to the way .backward is called or distributed is set up.
- If you are a scientist or production team, lightning is very simple to use with best practice defaults.
## What does lightning control for me?
Everything in Blue!
This is how lightning separates the science (red) from the engineering (blue).
![Overview](docs/source/_static/images/pl.gif)
## How much effort is it to convert? ## How much effort is it to convert?
You're probably tired of switching frameworks at this point. But it is a very quick process to refactor into the Lightning format. [Check out this tutorial](https://towardsdatascience.com/how-to-refactor-your-pytorch-code-to-get-these-42-benefits-of-pytorch-lighting-6fdd0dc97538) You're probably tired of switching frameworks at this point. But it is a very quick process to refactor into the Lightning format (ie: hours). [Check out this tutorial](https://towardsdatascience.com/how-to-refactor-your-pytorch-code-to-get-these-42-benefits-of-pytorch-lighting-6fdd0dc97538)
## Starting a new project? ## Starting a new project?
[Use our seed-project aimed at reproducibility!](https://github.com/williamFalcon/pytorch-lightning-conference-seed) [Use our seed-project aimed at reproducibility!](https://github.com/williamFalcon/pytorch-lightning-conference-seed)
@@ -179,29 +191,6 @@ When you're all done you can even run the test set separately.
trainer.test() trainer.test()
``` ```
## What does lightning control for me?
Everything in gray!
You define the blue parts using the LightningModule interface:
![Overview](docs/source/_static/images/overview_flat.jpg)
```python
# what to do in the training loop
def training_step(self, batch, batch_idx):
# what to do in the validation loop
def validation_step(self, batch, batch_idx):
# how to aggregate validation_step outputs
def validation_end(self, outputs):
# and your dataloaders
def train_dataloader():
def val_dataloader():
def test_dataloader():
```
**Could be as complex as seq-2-seq + attention** **Could be as complex as seq-2-seq + attention**
```python ```python
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 MiB

+1 -1
View File
@@ -1,6 +1,6 @@
"""Package info""" """Package info"""
__version__ = '0.5.3.2' __version__ = '0.6.0'
__author__ = 'William Falcon et al.' __author__ = 'William Falcon et al.'
__author_email__ = 'waf2107@columbia.edu' __author_email__ = 'waf2107@columbia.edu'
__license__ = 'Apache-2.0' __license__ = 'Apache-2.0'
+4 -4
View File
@@ -48,8 +48,8 @@ Minimal example
def validation_end(self, outputs): def validation_end(self, outputs):
# OPTIONAL # OPTIONAL
avg_loss = torch.stack([x['val_loss'] for x in outputs]).mean() val_loss_mean = torch.stack([x['val_loss'] for x in outputs]).mean()
return {'avg_val_loss': avg_loss} return {'val_loss': val_loss_mean}
def test_step(self, batch, batch_idx): def test_step(self, batch, batch_idx):
# OPTIONAL # OPTIONAL
@@ -59,8 +59,8 @@ Minimal example
def test_end(self, outputs): def test_end(self, outputs):
# OPTIONAL # OPTIONAL
avg_loss = torch.stack([x['test_loss'] for x in outputs]).mean() test_loss_mean = torch.stack([x['test_loss'] for x in outputs]).mean()
return {'avg_test_loss': avg_loss} return {'test_loss': test_loss_mean}
def configure_optimizers(self): def configure_optimizers(self):
# REQUIRED # REQUIRED
+13 -8
View File
@@ -664,6 +664,8 @@ class LightningModule(GradInformation, ModelIO, ModelHooks):
.. note:: If you use multiple optimizers, training_step will have an additional `optimizer_idx` parameter. .. note:: If you use multiple optimizers, training_step will have an additional `optimizer_idx` parameter.
.. note:: If you use LBFGS lightning handles the closure function automatically for you. .. note:: If you use LBFGS lightning handles the closure function automatically for you.
.. note:: If you use multiple optimizers, gradients will be calculated only for the parameters of current optimizer at each training step.
Example Example
------- -------
@@ -930,7 +932,7 @@ class LightningModule(GradInformation, ModelIO, ModelHooks):
return None return None
@classmethod @classmethod
def load_from_metrics(cls, weights_path, tags_csv): def load_from_metrics(cls, weights_path, tags_csv, map_location=None):
"""Primary way of loading model from csv weights path. """Primary way of loading model from csv weights path.
:param str weights_path: Path to a PyTorch checkpoint :param str weights_path: Path to a PyTorch checkpoint
@@ -975,9 +977,10 @@ class LightningModule(GradInformation, ModelIO, ModelHooks):
hparams = load_hparams_from_tags_csv(tags_csv) hparams = load_hparams_from_tags_csv(tags_csv)
hparams.__setattr__('on_gpu', False) hparams.__setattr__('on_gpu', False)
# load on CPU only to avoid OOM issues if map_location is not None:
# then its up to user to put back on GPUs checkpoint = torch.load(weights_path, map_location=map_location)
checkpoint = torch.load(weights_path, map_location=lambda storage, loc: storage) else:
checkpoint = torch.load(weights_path, map_location=lambda storage, loc: storage)
# load the state_dict on the model automatically # load the state_dict on the model automatically
model = cls(hparams) model = cls(hparams)
@@ -989,7 +992,7 @@ class LightningModule(GradInformation, ModelIO, ModelHooks):
return model return model
@classmethod @classmethod
def load_from_checkpoint(cls, checkpoint_path): def load_from_checkpoint(cls, checkpoint_path, map_location=None):
""" """
Primary way of loading model from a checkpoint Primary way of loading model from a checkpoint
:param checkpoint_path: :param checkpoint_path:
@@ -997,9 +1000,11 @@ class LightningModule(GradInformation, ModelIO, ModelHooks):
:return: :return:
""" """
# load on CPU only to avoid OOM issues if map_location is not None:
# then its up to user to put back on GPUs checkpoint = torch.load(checkpoint_path, map_location=map_location)
checkpoint = torch.load(checkpoint_path, map_location=lambda storage, loc: storage) else:
checkpoint = torch.load(checkpoint_path, map_location=lambda storage, loc: storage)
try: try:
ckpt_hparams = checkpoint['hparams'] ckpt_hparams = checkpoint['hparams']
except KeyError: except KeyError:
+2 -2
View File
@@ -1,10 +1,10 @@
""" """
.. warning:: `model_saving` module has been renamed to `saving` since v0.5.3 and will be removed in v0.8.0 .. warning:: `model_saving` module has been renamed to `saving` since v0.6.0 and will be removed in v0.8.0
""" """
import warnings import warnings
warnings.warn("`model_saving` module has been renamed to `saving` since v0.5.3" warnings.warn("`model_saving` module has been renamed to `saving` since v0.6.0"
" and will be removed in v0.8.0", DeprecationWarning) " and will be removed in v0.8.0", DeprecationWarning)
from pytorch_lightning.core.saving import ModelIO # noqa: E402 from pytorch_lightning.core.saving import ModelIO # noqa: E402
+2 -2
View File
@@ -1,10 +1,10 @@
""" """
.. warning:: `root_module` module has been renamed to `lightning` since v0.5.3 and will be removed in v0.8.0 .. warning:: `root_module` module has been renamed to `lightning` since v0.6.0 and will be removed in v0.8.0
""" """
import warnings import warnings
warnings.warn("`root_module` module has been renamed to `lightning` since v0.5.3" warnings.warn("`root_module` module has been renamed to `lightning` since v0.6.0"
" and will be removed in v0.8.0", DeprecationWarning) " and will be removed in v0.8.0", DeprecationWarning)
from pytorch_lightning.core.lightning import LightningModule # noqa: E402 from pytorch_lightning.core.lightning import LightningModule # noqa: E402
+2 -2
View File
@@ -1,10 +1,10 @@
""" """
.. warning:: `comet_logger` module has been renamed to `comet` since v0.5.3 and will be removed in v0.8.0 .. warning:: `comet_logger` module has been renamed to `comet` since v0.6.0 and will be removed in v0.8.0
""" """
import warnings import warnings
warnings.warn("`comet_logger` module has been renamed to `comet` since v0.5.3" warnings.warn("`comet_logger` module has been renamed to `comet` since v0.6.0"
" and will be removed in v0.8.0", DeprecationWarning) " and will be removed in v0.8.0", DeprecationWarning)
from pytorch_lightning.logging.comet import CometLogger # noqa: E402 from pytorch_lightning.logging.comet import CometLogger # noqa: E402
+1 -1
View File
@@ -59,7 +59,7 @@ class MLFlowLogger(LightningLoggerBase):
if expt: if expt:
self._expt_id = expt.experiment_id self._expt_id = expt.experiment_id
else: else:
logger.warning(f"Experiment with name f{self.experiment_name} not found. Creating it.") logger.warning(f"Experiment with name {self.experiment_name} not found. Creating it.")
self._expt_id = self._mlflow_client.create_experiment(name=self.experiment_name) self._expt_id = self._mlflow_client.create_experiment(name=self.experiment_name)
run = self._mlflow_client.create_run(experiment_id=self._expt_id, tags=self.tags) run = self._mlflow_client.create_run(experiment_id=self._expt_id, tags=self.tags)
+2 -2
View File
@@ -1,10 +1,10 @@
""" """
.. warning:: `mlflow_logger` module has been renamed to `mlflow` since v0.5.3 and will be removed in v0.8.0 .. warning:: `mlflow_logger` module has been renamed to `mlflow` since v0.6.0 and will be removed in v0.8.0
""" """
import warnings import warnings
warnings.warn("`mlflow_logger` module has been renamed to `mlflow` since v0.5.3" warnings.warn("`mlflow_logger` module has been renamed to `mlflow` since v0.6.0"
" and will be removed in v0.8.0", DeprecationWarning) " and will be removed in v0.8.0", DeprecationWarning)
from pytorch_lightning.logging.mlflow import MLFlowLogger # noqa: E402 from pytorch_lightning.logging.mlflow import MLFlowLogger # noqa: E402
@@ -1,10 +1,10 @@
""" """
.. warning:: `test_tube_logger` module has been renamed to `test_tube` since v0.5.3 and will be removed in v0.8.0 .. warning:: `test_tube_logger` module has been renamed to `test_tube` since v0.6.0 and will be removed in v0.8.0
""" """
import warnings import warnings
warnings.warn("`test_tube_logger` module has been renamed to `test_tube` since v0.5.3" warnings.warn("`test_tube_logger` module has been renamed to `test_tube` since v0.6.0"
" and will be removed in v0.8.0", DeprecationWarning) " and will be removed in v0.8.0", DeprecationWarning)
from pytorch_lightning.logging.test_tube import TestTubeLogger # noqa: E402 from pytorch_lightning.logging.test_tube import TestTubeLogger # noqa: E402
@@ -1,11 +1,11 @@
""" """
.. warning:: `override_data_parallel` module has been renamed to `data_parallel` since v0.5.3 .. warning:: `override_data_parallel` module has been renamed to `data_parallel` since v0.6.0
and will be removed in v0.8.0 and will be removed in v0.8.0
""" """
import warnings import warnings
warnings.warn("`override_data_parallel` module has been renamed to `data_parallel` since v0.5.3" warnings.warn("`override_data_parallel` module has been renamed to `data_parallel` since v0.6.0"
" and will be removed in v0.8.0", DeprecationWarning) " and will be removed in v0.8.0", DeprecationWarning)
from pytorch_lightning.overrides.data_parallel import ( # noqa: E402 from pytorch_lightning.overrides.data_parallel import ( # noqa: E402
+2 -2
View File
@@ -1,10 +1,10 @@
""" """
.. warning:: `pt_overrides` package has been renamed to `overrides` since v0.5.3 and will be removed in v0.8.0 .. warning:: `pt_overrides` package has been renamed to `overrides` since v0.6.0 and will be removed in v0.8.0
""" """
import warnings import warnings
warnings.warn("`pt_overrides` package has been renamed to `overrides` since v0.5.3" warnings.warn("`pt_overrides` package has been renamed to `overrides` since v0.6.0"
" and will be removed in v0.8.0", DeprecationWarning) " and will be removed in v0.8.0", DeprecationWarning)
from pytorch_lightning.overrides import override_data_parallel # noqa: E402 from pytorch_lightning.overrides import override_data_parallel # noqa: E402
+2 -2
View File
@@ -1,10 +1,10 @@
""" """
.. warning:: `root_module` package has been renamed to `core` since v0.5.3 and will be removed in v0.8.0 .. warning:: `root_module` package has been renamed to `core` since v0.6.0 and will be removed in v0.8.0
""" """
import warnings import warnings
warnings.warn("`root_module` package has been renamed to `core` since v0.5.3" warnings.warn("`root_module` package has been renamed to `core` since v0.6.0"
" and will be removed in v0.8.0", DeprecationWarning) " and will be removed in v0.8.0", DeprecationWarning)
from pytorch_lightning.core import ( # noqa: E402 from pytorch_lightning.core import ( # noqa: E402
+2 -2
View File
@@ -2,7 +2,7 @@ import os
from abc import ABC from abc import ABC
from pytorch_lightning.callbacks import ModelCheckpoint, EarlyStopping from pytorch_lightning.callbacks import ModelCheckpoint, EarlyStopping
from pytorch_lightning.logging import TestTubeLogger from pytorch_lightning.logging import TensorboardLogger
class TrainerCallbackConfigMixin(ABC): class TrainerCallbackConfigMixin(ABC):
@@ -69,7 +69,7 @@ class TrainerCallbackConfigMixin(ABC):
# configure logger # configure logger
if logger is True: if logger is True:
# default logger # default logger
self.logger = TestTubeLogger( self.logger = TensorboardLogger(
save_dir=self.default_save_path, save_dir=self.default_save_path,
version=self.slurm_job_id, version=self.slurm_job_id,
name='lightning_logs' name='lightning_logs'
+27 -2
View File
@@ -36,6 +36,15 @@ class TrainerDataLoadingMixin(ABC):
self.shown_warnings = None self.shown_warnings = None
self.val_check_interval = None self.val_check_interval = None
def _percent_range_check(self, name):
value = getattr(self, name)
msg = f"`{name}` must lie in the range [0.0, 1.0], but got {value:.3f}."
if name == "val_check_interval":
msg += " If you want to disable validation set `val_percent_check` to 0.0 instead."
if not 0. <= value <= 1.:
raise ValueError(msg)
def init_train_dataloader(self, model): def init_train_dataloader(self, model):
""" """
Dataloaders are provided by the model Dataloaders are provided by the model
@@ -48,6 +57,8 @@ class TrainerDataLoadingMixin(ABC):
if EXIST_ITER_DATASET and isinstance(self.get_train_dataloader().dataset, IterableDataset): if EXIST_ITER_DATASET and isinstance(self.get_train_dataloader().dataset, IterableDataset):
self.num_training_batches = float('inf') self.num_training_batches = float('inf')
else: else:
self._percent_range_check('train_percent_check')
self.num_training_batches = len(self.get_train_dataloader()) self.num_training_batches = len(self.get_train_dataloader())
self.num_training_batches = int(self.num_training_batches * self.train_percent_check) self.num_training_batches = int(self.num_training_batches * self.train_percent_check)
@@ -56,7 +67,14 @@ class TrainerDataLoadingMixin(ABC):
# otherwise, it checks in [0, 1.0] % range of a training epoch # otherwise, it checks in [0, 1.0] % range of a training epoch
if isinstance(self.val_check_interval, int): if isinstance(self.val_check_interval, int):
self.val_check_batch = self.val_check_interval self.val_check_batch = self.val_check_interval
if self.val_check_batch > self.num_training_batches:
raise ValueError(
f"`val_check_interval` ({self.val_check_interval}) must be less than or equal "
f"to the number of the training batches ({self.num_training_batches}). "
f"If you want to disable validation set `val_percent_check` to 0.0 instead.")
else: else:
self._percent_range_check('val_check_interval')
self.val_check_batch = int(self.num_training_batches * self.val_check_interval) self.val_check_batch = int(self.num_training_batches * self.val_check_interval)
self.val_check_batch = max(1, self.val_check_batch) self.val_check_batch = max(1, self.val_check_batch)
@@ -89,13 +107,15 @@ class TrainerDataLoadingMixin(ABC):
:return: :return:
""" """
self.get_val_dataloaders = model.val_dataloader self.get_val_dataloaders = model.val_dataloader
self.num_val_batches = 0
# determine number of validation batches # determine number of validation batches
# val datasets could be none, 1 or 2+ # val datasets could be none, 1 or 2+
if self.get_val_dataloaders() is not None: if self.get_val_dataloaders() is not None:
self._percent_range_check('val_percent_check')
self.num_val_batches = sum(len(dataloader) for dataloader in self.get_val_dataloaders()) self.num_val_batches = sum(len(dataloader) for dataloader in self.get_val_dataloaders())
self.num_val_batches = int(self.num_val_batches * self.val_percent_check) self.num_val_batches = int(self.num_val_batches * self.val_percent_check)
self.num_val_batches = max(1, self.num_val_batches)
on_ddp = self.use_ddp or self.use_ddp2 on_ddp = self.use_ddp or self.use_ddp2
if on_ddp and self.get_val_dataloaders() is not None: if on_ddp and self.get_val_dataloaders() is not None:
@@ -134,10 +154,11 @@ class TrainerDataLoadingMixin(ABC):
# determine number of test batches # determine number of test batches
if self.get_test_dataloaders() is not None: if self.get_test_dataloaders() is not None:
self._percent_range_check('test_percent_check')
len_sum = sum(len(dataloader) for dataloader in self.get_test_dataloaders()) len_sum = sum(len(dataloader) for dataloader in self.get_test_dataloaders())
self.num_test_batches = len_sum self.num_test_batches = len_sum
self.num_test_batches = int(self.num_test_batches * self.test_percent_check) self.num_test_batches = int(self.num_test_batches * self.test_percent_check)
self.num_test_batches = max(1, self.num_test_batches)
on_ddp = self.use_ddp or self.use_ddp2 on_ddp = self.use_ddp or self.use_ddp2
if on_ddp and self.get_test_dataloaders() is not None: if on_ddp and self.get_test_dataloaders() is not None:
@@ -208,6 +229,10 @@ class TrainerDataLoadingMixin(ABC):
self.val_percent_check = val_percent_check self.val_percent_check = val_percent_check
self.test_percent_check = test_percent_check self.test_percent_check = test_percent_check
if overfit_pct > 0: if overfit_pct > 0:
if overfit_pct > 1:
raise ValueError(f"`overfit_pct` must be not greater than 1.0, but got "
f"{overfit_pct:.3f}.")
self.train_percent_check = overfit_pct self.train_percent_check = overfit_pct
self.val_percent_check = overfit_pct self.val_percent_check = overfit_pct
self.test_percent_check = overfit_pct self.test_percent_check = overfit_pct
+19 -6
View File
@@ -139,41 +139,53 @@ class Trainer(TrainerIOMixin,
""" """
# Transfer params # Transfer params
if nb_gpu_nodes is not None: # Backward compatibility # Backward compatibility
if nb_gpu_nodes is not None:
warnings.warn("`nb_gpu_nodes` has renamed to `num_nodes` since v0.5.0" warnings.warn("`nb_gpu_nodes` has renamed to `num_nodes` since v0.5.0"
" and will be removed in v0.8.0", DeprecationWarning) " and will be removed in v0.8.0", DeprecationWarning)
if not num_nodes: # in case you did not set the proper value if not num_nodes: # in case you did not set the proper value
num_nodes = nb_gpu_nodes num_nodes = nb_gpu_nodes
self.num_gpu_nodes = num_nodes self.num_gpu_nodes = num_nodes
self.log_gpu_memory = log_gpu_memory self.log_gpu_memory = log_gpu_memory
if gradient_clip is not None: # Backward compatibility
# Backward compatibility
if gradient_clip is not None:
warnings.warn("`gradient_clip` has renamed to `gradient_clip_val` since v0.5.0" warnings.warn("`gradient_clip` has renamed to `gradient_clip_val` since v0.5.0"
" and will be removed in v0.8.0", DeprecationWarning) " and will be removed in v0.8.0", DeprecationWarning)
if not gradient_clip_val: # in case you did not set the proper value if not gradient_clip_val: # in case you did not set the proper value
gradient_clip_val = gradient_clip gradient_clip_val = gradient_clip
self.gradient_clip_val = gradient_clip_val self.gradient_clip_val = gradient_clip_val
self.check_val_every_n_epoch = check_val_every_n_epoch self.check_val_every_n_epoch = check_val_every_n_epoch
self.track_grad_norm = track_grad_norm self.track_grad_norm = track_grad_norm
self.on_gpu = True if (gpus and torch.cuda.is_available()) else False self.on_gpu = True if (gpus and torch.cuda.is_available()) else False
self.process_position = process_position self.process_position = process_position
self.weights_summary = weights_summary self.weights_summary = weights_summary
if max_nb_epochs is not None: # Backward compatibility
# Backward compatibility
if max_nb_epochs is not None:
warnings.warn("`max_nb_epochs` has renamed to `max_epochs` since v0.5.0" warnings.warn("`max_nb_epochs` has renamed to `max_epochs` since v0.5.0"
" and will be removed in v0.8.0", DeprecationWarning) " and will be removed in v0.8.0", DeprecationWarning)
if not max_epochs: # in case you did not set the proper value if not max_epochs: # in case you did not set the proper value
max_epochs = max_nb_epochs max_epochs = max_nb_epochs
self.max_epochs = max_epochs self.max_epochs = max_epochs
if min_nb_epochs is not None: # Backward compatibility
# Backward compatibility
if min_nb_epochs is not None:
warnings.warn("`min_nb_epochs` has renamed to `min_epochs` since v0.5.0" warnings.warn("`min_nb_epochs` has renamed to `min_epochs` since v0.5.0"
" and will be removed in v0.8.0", DeprecationWarning) " and will be removed in v0.8.0", DeprecationWarning)
if not min_epochs: # in case you did not set the proper value if not min_epochs: # in case you did not set the proper value
min_epochs = min_nb_epochs min_epochs = min_nb_epochs
self.min_epochs = min_epochs self.min_epochs = min_epochs
if nb_sanity_val_steps is not None: # Backward compatibility
# Backward compatibility
if nb_sanity_val_steps is not None:
warnings.warn("`nb_sanity_val_steps` has renamed to `num_sanity_val_steps` since v0.5.0" warnings.warn("`nb_sanity_val_steps` has renamed to `num_sanity_val_steps` since v0.5.0"
" and will be removed in v0.8.0", DeprecationWarning) " and will be removed in v0.8.0", DeprecationWarning)
if not num_sanity_val_steps: # in case you did not set the proper value if not num_sanity_val_steps: # in case you did not set the proper value
num_sanity_val_steps = nb_sanity_val_steps num_sanity_val_steps = nb_sanity_val_steps
self.num_sanity_val_steps = num_sanity_val_steps self.num_sanity_val_steps = num_sanity_val_steps
self.print_nan_grads = print_nan_grads self.print_nan_grads = print_nan_grads
self.truncated_bptt_steps = truncated_bptt_steps self.truncated_bptt_steps = truncated_bptt_steps
@@ -261,8 +273,9 @@ class Trainer(TrainerIOMixin,
# logging # logging
self.log_save_interval = log_save_interval self.log_save_interval = log_save_interval
self.val_check_interval = val_check_interval self.val_check_interval = val_check_interval
# backward compatibility
if add_row_log_interval is not None: if add_row_log_interval is not None:
# backward compatibility
warnings.warn("`add_row_log_interval` has renamed to `row_log_interval` since v0.5.0" warnings.warn("`add_row_log_interval` has renamed to `row_log_interval` since v0.5.0"
" and will be removed in v0.8.0", DeprecationWarning) " and will be removed in v0.8.0", DeprecationWarning)
if not row_log_interval: # in case you did not set the proper value if not row_log_interval: # in case you did not set the proper value
+11 -5
View File
@@ -367,6 +367,10 @@ class TrainerTrainLoopMixin(ABC):
# run epoch # run epoch
for batch_idx, batch in enumerate(self.get_train_dataloader()): for batch_idx, batch in enumerate(self.get_train_dataloader()):
# stop epoch if we limited the number of training batches
if batch_idx >= self.num_training_batches:
break
self.batch_idx = batch_idx self.batch_idx = batch_idx
model = self.get_model() model = self.get_model()
@@ -413,11 +417,6 @@ class TrainerTrainLoopMixin(ABC):
if early_stop_epoch or self.fast_dev_run: if early_stop_epoch or self.fast_dev_run:
break break
# stop epoch if we limited the number of training batches
met_batch_limit = batch_idx >= self.num_training_batches
if met_batch_limit:
break
# epoch end hook # epoch end hook
if self.is_function_implemented('on_epoch_end'): if self.is_function_implemented('on_epoch_end'):
model = self.get_model() model = self.get_model()
@@ -455,6 +454,13 @@ class TrainerTrainLoopMixin(ABC):
# call training_step once per optimizer # call training_step once per optimizer
for opt_idx, optimizer in enumerate(self.optimizers): for opt_idx, optimizer in enumerate(self.optimizers):
# make sure only the gradients of the current optimizer's paramaters are calculated
# in the training step to prevent dangling gradients in multiple-optimizer setup.
for param in self.get_model().parameters():
param.requires_grad = False
for group in optimizer.param_groups:
for param in group['params']:
param.requires_grad = True
# wrap the forward step in a closure so second order methods work # wrap the forward step in a closure so second order methods work
def optimizer_closure(): def optimizer_closure():
+1 -1
View File
@@ -5,4 +5,4 @@ torch>=1.1
torchvision>=0.4.0 torchvision>=0.4.0
pandas>=0.24 # lower version do not support py3.7 pandas>=0.24 # lower version do not support py3.7
test-tube>=0.7.5 test-tube>=0.7.5
future>=0.17.1 # required for buildins in setup.py future>=0.17.1 # required for builtins in setup.py
+22
View File
@@ -0,0 +1,22 @@
import pytest
import torch.multiprocessing as mp
def pytest_configure(config):
config.addinivalue_line("markers", "spawn: spawn test in a separate process using torch.multiprocessing.spawn")
def wrap(i, fn, args):
return fn(*args)
@pytest.mark.tryfirst
def pytest_pyfunc_call(pyfuncitem):
if pyfuncitem.get_closest_marker("spawn"):
testfunction = pyfuncitem.obj
funcargs = pyfuncitem.funcargs
testargs = tuple([funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames])
mp.spawn(wrap, (testfunction, testargs))
return True
+2 -1
View File
@@ -8,4 +8,5 @@ check-manifest
# test_tube # already installed in main req. # test_tube # already installed in main req.
mlflow mlflow
comet_ml comet_ml
twine==1.13.0 twine==1.13.0
pillow<7.0.0
+12 -24
View File
@@ -32,6 +32,7 @@ def test_amp_single_gpu(tmpdir):
tutils.run_model_test(trainer_options, model) tutils.run_model_test(trainer_options, model)
@pytest.mark.spawn
def test_no_amp_single_gpu(tmpdir): def test_no_amp_single_gpu(tmpdir):
"""Make sure DDP + AMP work.""" """Make sure DDP + AMP work."""
tutils.reset_seed() tutils.reset_seed()
@@ -51,8 +52,10 @@ def test_no_amp_single_gpu(tmpdir):
use_amp=True use_amp=True
) )
with pytest.raises((MisconfigurationException, ModuleNotFoundError)): trainer = Trainer(**trainer_options)
tutils.run_model_test(trainer_options, model) result = trainer.fit(model)
assert result == 1
def test_amp_gpu_ddp(tmpdir): def test_amp_gpu_ddp(tmpdir):
@@ -78,6 +81,7 @@ def test_amp_gpu_ddp(tmpdir):
tutils.run_model_test(trainer_options, model) tutils.run_model_test(trainer_options, model)
@pytest.mark.spawn
def test_amp_gpu_ddp_slurm_managed(tmpdir): def test_amp_gpu_ddp_slurm_managed(tmpdir):
"""Make sure DDP + AMP work.""" """Make sure DDP + AMP work."""
if not tutils.can_run_gpu_test(): if not tutils.can_run_gpu_test():
@@ -124,26 +128,6 @@ def test_amp_gpu_ddp_slurm_managed(tmpdir):
assert trainer.resolve_root_node_address('abc[23-24]') == 'abc23' assert trainer.resolve_root_node_address('abc[23-24]') == 'abc23'
assert trainer.resolve_root_node_address('abc[23-24, 45-40, 40]') == 'abc23' assert trainer.resolve_root_node_address('abc[23-24, 45-40, 40]') == 'abc23'
# test model loading with a map_location
pretrained_model = tutils.load_model(logger.experiment, trainer.checkpoint_callback.filepath)
# test model preds
for dataloader in trainer.get_test_dataloaders():
tutils.run_prediction(dataloader, pretrained_model)
if trainer.use_ddp:
# on hpc this would work fine... but need to hack it for the purpose of the test
trainer.model = pretrained_model
trainer.optimizers, trainer.lr_schedulers = pretrained_model.configure_optimizers()
# test HPC loading / saving
trainer.hpc_save(tmpdir, logger)
trainer.hpc_load(tmpdir, on_gpu=True)
# test freeze on gpu
model.freeze()
model.unfreeze()
def test_cpu_model_with_amp(tmpdir): def test_cpu_model_with_amp(tmpdir):
"""Make sure model trains on CPU.""" """Make sure model trains on CPU."""
@@ -165,6 +149,7 @@ def test_cpu_model_with_amp(tmpdir):
tutils.run_model_test(trainer_options, model, on_gpu=False) tutils.run_model_test(trainer_options, model, on_gpu=False)
@pytest.mark.spawn
def test_amp_gpu_dp(tmpdir): def test_amp_gpu_dp(tmpdir):
"""Make sure DP + AMP work.""" """Make sure DP + AMP work."""
tutils.reset_seed() tutils.reset_seed()
@@ -180,8 +165,11 @@ def test_amp_gpu_dp(tmpdir):
distributed_backend='dp', distributed_backend='dp',
use_amp=True use_amp=True
) )
with pytest.raises(MisconfigurationException):
tutils.run_model_test(trainer_options, model, hparams) trainer = Trainer(**trainer_options)
result = trainer.fit(model)
assert result == 1
if __name__ == '__main__': if __name__ == '__main__':
+1 -1
View File
@@ -97,7 +97,7 @@ def test_running_test_after_fitting(tmpdir):
trainer_options = dict( trainer_options = dict(
default_save_path=tmpdir, default_save_path=tmpdir,
show_progress_bar=False, show_progress_bar=False,
max_epochs=1, max_epochs=4,
train_percent_check=0.4, train_percent_check=0.4,
val_percent_check=0.2, val_percent_check=0.2,
test_percent_check=0.2, test_percent_check=0.2,
+1 -1
View File
@@ -309,7 +309,7 @@ def test_custom_logger(tmpdir):
trainer_options = dict( trainer_options = dict(
max_epochs=1, max_epochs=1,
train_percent_check=0.01, train_percent_check=0.05,
logger=logger, logger=logger,
default_save_path=tmpdir default_save_path=tmpdir
) )
+3 -3
View File
@@ -153,7 +153,7 @@ def test_running_test_pretrained_model_dp(tmpdir):
trainer_options = dict( trainer_options = dict(
show_progress_bar=True, show_progress_bar=True,
max_epochs=1, max_epochs=4,
train_percent_check=0.4, train_percent_check=0.4,
val_percent_check=0.2, val_percent_check=0.2,
checkpoint_callback=checkpoint, checkpoint_callback=checkpoint,
@@ -269,12 +269,12 @@ def test_cpu_restore_training(tmpdir):
logger = tutils.get_test_tube_logger(tmpdir, False, version=test_logger_version) logger = tutils.get_test_tube_logger(tmpdir, False, version=test_logger_version)
trainer_options = dict( trainer_options = dict(
max_epochs=2, max_epochs=8,
val_check_interval=0.50, val_check_interval=0.50,
val_percent_check=0.2, val_percent_check=0.2,
train_percent_check=0.2, train_percent_check=0.2,
logger=logger, logger=logger,
checkpoint_callback=ModelCheckpoint(tmpdir) checkpoint_callback=ModelCheckpoint(tmpdir, save_top_k=-1)
) )
# fit model # fit model
+1 -1
View File
@@ -393,7 +393,7 @@ def test_multiple_test_dataloader(tmpdir):
default_save_path=tmpdir, default_save_path=tmpdir,
max_epochs=1, max_epochs=1,
val_percent_check=0.1, val_percent_check=0.1,
train_percent_check=0.1, train_percent_check=0.2,
) )
# fit model # fit model